From 9d3b1904651321f44b8f801bc0452390afc16ffc Mon Sep 17 00:00:00 2001 From: Kelebek1 Date: Tue, 26 Jul 2022 22:04:18 +0100 Subject: Rework multi-core vsync --- src/core/hle/service/nvflinger/nvflinger.cpp | 42 +++++++++++++++++----------- src/core/hle/service/nvflinger/nvflinger.h | 5 +++- 2 files changed, 30 insertions(+), 17 deletions(-) (limited to 'src/core') 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) { Common::SetCurrentThreadName(name.c_str()); Common::SetCurrentThreadPriority(Common::ThreadPriority::High); - s64 delay = 0; + while (!stop_token.stop_requested()) { + vsync_signal.wait(false); + vsync_signal.store(false); + guard->lock(); - const s64 time_start = system.CoreTiming().GetGlobalTimeNs().count(); + Compose(); - const auto ticks = GetNextTicks(); - const s64 time_end = system.CoreTiming().GetGlobalTimeNs().count(); - const s64 time_passed = time_end - time_start; - const s64 next_time = std::max(0, ticks - time_passed - delay); + guard->unlock(); - if (next_time > 0) { - std::this_thread::sleep_for(std::chrono::nanoseconds{next_time}); - } - delay = (system.CoreTiming().GetGlobalTimeNs().count() - time_end) - next_time; } } @@ -66,27 +62,41 @@ NVFlinger::NVFlinger(Core::System& system_, HosBinderDriverServer& hos_binder_dr guard = std::make_shared(); // Schedule the screen composition events - composition_event = Core::Timing::CreateEvent( + multi_composition_event = Core::Timing::CreateEvent( + "ScreenComposition", + [this](std::uintptr_t, s64 time, + std::chrono::nanoseconds ns_late) -> std::optional { + vsync_signal.store(true); + vsync_signal.notify_all(); + return std::chrono::nanoseconds(GetNextTicks()); + }); + + single_composition_event = Core::Timing::CreateEvent( "ScreenComposition", [this](std::uintptr_t, s64 time, std::chrono::nanoseconds ns_late) -> std::optional { const auto lock_guard = Lock(); Compose(); - return std::max(std::chrono::nanoseconds::zero(), - std::chrono::nanoseconds(GetNextTicks()) - ns_late); + return std::chrono::nanoseconds(GetNextTicks()); }); if (system.IsMulticore()) { + system.CoreTiming().ScheduleLoopingEvent(frame_ns, frame_ns, multi_composition_event); vsync_thread = std::jthread([this](std::stop_token token) { SplitVSync(token); }); } else { - system.CoreTiming().ScheduleLoopingEvent(frame_ns, frame_ns, composition_event); + system.CoreTiming().ScheduleLoopingEvent(frame_ns, frame_ns, single_composition_event); } } NVFlinger::~NVFlinger() { - if (!system.IsMulticore()) { - system.CoreTiming().UnscheduleEvent(composition_event, 0); + if (system.IsMulticore()) { + system.CoreTiming().UnscheduleEvent(multi_composition_event, {}); + vsync_thread.request_stop(); + vsync_signal.store(true); + vsync_signal.notify_all(); + } else { + system.CoreTiming().UnscheduleEvent(single_composition_event, {}); } 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: u32 swap_interval = 1; /// Event that handles screen composition. - std::shared_ptr composition_event; + std::shared_ptr multi_composition_event; + std::shared_ptr single_composition_event; std::shared_ptr guard; Core::System& system; + std::atomic vsync_signal; + std::jthread vsync_thread; KernelHelpers::ServiceContext service_context; -- cgit v1.2.3 From 83a24ad638a2c3ff628fe109391586be9bd3a45d Mon Sep 17 00:00:00 2001 From: Kelebek1 Date: Wed, 27 Jul 2022 22:31:41 +0100 Subject: Make coretiming waiting more accurate --- src/common/thread.h | 4 ++++ src/core/core_timing.cpp | 38 +++++++++++++++++++++++++++----------- 2 files changed, 31 insertions(+), 11 deletions(-) (limited to 'src/core') 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: is_set = false; } + [[nodiscard]] bool IsSet() { + return is_set; + } + private: std::condition_variable condvar; std::mutex mutex; diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp index 2dbb99c8b..b45c1b918 100644 --- a/src/core/core_timing.cpp +++ b/src/core/core_timing.cpp @@ -243,17 +243,17 @@ std::optional CoreTiming::Advance() { basic_lock.lock(); if (evt.reschedule_time != 0) { + const auto next_schedule_time{new_schedule_time.has_value() + ? new_schedule_time.value().count() + : evt.reschedule_time}; + // If this event was scheduled into a pause, its time now is going to be way behind. // Re-set this event to continue from the end of the pause. - auto next_time{evt.time + evt.reschedule_time}; + auto next_time{evt.time + next_schedule_time}; if (evt.time < pause_end_time) { - next_time = pause_end_time + evt.reschedule_time; + next_time = pause_end_time + next_schedule_time; } - const auto next_schedule_time{new_schedule_time.has_value() - ? new_schedule_time.value().count() - : evt.reschedule_time}; - event_queue.emplace_back( Event{next_time, event_fifo_id++, evt.user_data, evt.type, next_schedule_time}); std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>()); @@ -264,8 +264,7 @@ std::optional CoreTiming::Advance() { } if (!event_queue.empty()) { - const s64 next_time = event_queue.front().time - global_timer; - return next_time; + return event_queue.front().time; } else { return std::nullopt; } @@ -278,11 +277,28 @@ void CoreTiming::ThreadLoop() { paused_set = false; const auto next_time = Advance(); if (next_time) { - if (*next_time > 0) { - std::chrono::nanoseconds next_time_ns = std::chrono::nanoseconds(*next_time); - event.WaitFor(next_time_ns); + // There are more events left in the queue, sleep until the next event. + const auto diff_ns{*next_time - GetGlobalTimeNs().count()}; + if (diff_ns > 0) { + // Only try to sleep if the remaining time is >= 1ms. Take off 500 microseconds + // from the target time to account for possible over-sleeping, and spin the + // remaining. + const auto sleep_time_ns{diff_ns - 500LL * 1'000LL}; + const auto sleep_time_ms{sleep_time_ns / 1'000'000LL}; + if (sleep_time_ms >= 1) { + event.WaitFor(std::chrono::nanoseconds(sleep_time_ns)); + } + + const auto end_time{std::chrono::nanoseconds(*next_time)}; + while (!paused && !event.IsSet() && GetGlobalTimeNs() < end_time) { + } + + if (event.IsSet()) { + event.Reset(); + } } } else { + // Queue is empty, wait until another event is scheduled and signals us to continue. wait_set = true; event.Wait(); } -- cgit v1.2.3 From 658e1ee4267f0475445c7152560783444fcda7d2 Mon Sep 17 00:00:00 2001 From: Kelebek1 Date: Tue, 2 Aug 2022 05:28:31 +0100 Subject: Add missing looping event schedule signal --- src/core/core_timing.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) (limited to 'src/core') diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp index b45c1b918..a75bfea60 100644 --- a/src/core/core_timing.cpp +++ b/src/core/core_timing.cpp @@ -143,13 +143,17 @@ void CoreTiming::ScheduleLoopingEvent(std::chrono::nanoseconds start_time, std::chrono::nanoseconds resched_time, const std::shared_ptr& event_type, std::uintptr_t user_data, bool absolute_time) { - std::scoped_lock scope{basic_lock}; - const auto next_time{absolute_time ? start_time : GetGlobalTimeNs() + start_time}; + { + std::scoped_lock scope{basic_lock}; + const auto next_time{absolute_time ? start_time : GetGlobalTimeNs() + start_time}; + + event_queue.emplace_back( + Event{next_time.count(), event_fifo_id++, user_data, event_type, resched_time.count()}); - event_queue.emplace_back( - Event{next_time.count(), event_fifo_id++, user_data, event_type, resched_time.count()}); + std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>()); + } - std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>()); + event.Set(); } void CoreTiming::UnscheduleEvent(const std::shared_ptr& event_type, -- cgit v1.2.3 From 606cdb17d3b8f3b4898c1f0a87691058074ad11a Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Mon, 1 Aug 2022 23:34:34 -0400 Subject: core_timing: Sleep in discrete intervals, yield during spin --- src/core/core_timing.cpp | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) (limited to 'src/core') diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp index a75bfea60..8d1ee3b51 100644 --- a/src/core/core_timing.cpp +++ b/src/core/core_timing.cpp @@ -281,20 +281,21 @@ void CoreTiming::ThreadLoop() { paused_set = false; const auto next_time = Advance(); if (next_time) { - // There are more events left in the queue, sleep until the next event. - const auto diff_ns{*next_time - GetGlobalTimeNs().count()}; - if (diff_ns > 0) { - // Only try to sleep if the remaining time is >= 1ms. Take off 500 microseconds - // from the target time to account for possible over-sleeping, and spin the - // remaining. - const auto sleep_time_ns{diff_ns - 500LL * 1'000LL}; - const auto sleep_time_ms{sleep_time_ns / 1'000'000LL}; - if (sleep_time_ms >= 1) { - event.WaitFor(std::chrono::nanoseconds(sleep_time_ns)); + // There are more events left in the queue, wait until the next event. + const auto wait_time = *next_time - GetGlobalTimeNs().count(); + if (wait_time > 0) { + // Assume a timer resolution of 1ms. + static constexpr s64 TimerResolutionNS = 1000000; + + // Sleep in discrete intervals of the timer resolution, and spin the rest. + const auto sleep_time = wait_time - (wait_time % TimerResolutionNS); + if (sleep_time > 0) { + event.WaitFor(std::chrono::nanoseconds(sleep_time)); } - const auto end_time{std::chrono::nanoseconds(*next_time)}; - while (!paused && !event.IsSet() && GetGlobalTimeNs() < end_time) { + while (!paused && !event.IsSet() && GetGlobalTimeNs().count() < *next_time) { + // Yield to reduce thread starvation. + std::this_thread::yield(); } if (event.IsSet()) { -- cgit v1.2.3 From b904652d69fb3d3bf1918a7dd7f04bc049c9f460 Mon Sep 17 00:00:00 2001 From: FearlessTobi Date: Tue, 16 Aug 2022 23:13:05 +0200 Subject: yuzu_room: Remove dependency on core --- src/core/CMakeLists.txt | 2 - src/core/announce_multiplayer_session.cpp | 164 --------------------------- src/core/announce_multiplayer_session.h | 98 ---------------- src/dedicated_room/CMakeLists.txt | 2 +- src/dedicated_room/yuzu_room.cpp | 2 +- src/network/CMakeLists.txt | 6 + src/network/announce_multiplayer_session.cpp | 164 +++++++++++++++++++++++++++ src/network/announce_multiplayer_session.h | 98 ++++++++++++++++ src/yuzu/multiplayer/chat_room.cpp | 2 +- src/yuzu/multiplayer/client_room.cpp | 2 +- src/yuzu/multiplayer/host_room.cpp | 2 +- src/yuzu/multiplayer/lobby.h | 2 +- src/yuzu/multiplayer/state.h | 2 +- 13 files changed, 275 insertions(+), 271 deletions(-) delete mode 100644 src/core/announce_multiplayer_session.cpp delete mode 100644 src/core/announce_multiplayer_session.h create mode 100644 src/network/announce_multiplayer_session.cpp create mode 100644 src/network/announce_multiplayer_session.h (limited to 'src/core') diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 8db9a3c65..25b39c52b 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -2,8 +2,6 @@ # SPDX-License-Identifier: GPL-2.0-or-later add_library(core STATIC - announce_multiplayer_session.cpp - announce_multiplayer_session.h arm/arm_interface.h arm/arm_interface.cpp arm/dynarmic/arm_dynarmic_32.cpp diff --git a/src/core/announce_multiplayer_session.cpp b/src/core/announce_multiplayer_session.cpp deleted file mode 100644 index 6737ce85a..000000000 --- a/src/core/announce_multiplayer_session.cpp +++ /dev/null @@ -1,164 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include -#include -#include -#include "announce_multiplayer_session.h" -#include "common/announce_multiplayer_room.h" -#include "common/assert.h" -#include "common/settings.h" -#include "network/network.h" - -#ifdef ENABLE_WEB_SERVICE -#include "web_service/announce_room_json.h" -#endif - -namespace Core { - -// Time between room is announced to web_service -static constexpr std::chrono::seconds announce_time_interval(15); - -AnnounceMultiplayerSession::AnnounceMultiplayerSession(Network::RoomNetwork& room_network_) - : room_network{room_network_} { -#ifdef ENABLE_WEB_SERVICE - backend = std::make_unique(Settings::values.web_api_url.GetValue(), - Settings::values.yuzu_username.GetValue(), - Settings::values.yuzu_token.GetValue()); -#else - backend = std::make_unique(); -#endif -} - -WebService::WebResult AnnounceMultiplayerSession::Register() { - auto room = room_network.GetRoom().lock(); - if (!room) { - return WebService::WebResult{WebService::WebResult::Code::LibError, - "Network is not initialized", ""}; - } - if (room->GetState() != Network::Room::State::Open) { - return WebService::WebResult{WebService::WebResult::Code::LibError, "Room is not open", ""}; - } - UpdateBackendData(room); - WebService::WebResult result = backend->Register(); - if (result.result_code != WebService::WebResult::Code::Success) { - return result; - } - LOG_INFO(WebService, "Room has been registered"); - room->SetVerifyUID(result.returned_data); - registered = true; - return WebService::WebResult{WebService::WebResult::Code::Success, "", ""}; -} - -void AnnounceMultiplayerSession::Start() { - if (announce_multiplayer_thread) { - Stop(); - } - shutdown_event.Reset(); - announce_multiplayer_thread = - std::make_unique(&AnnounceMultiplayerSession::AnnounceMultiplayerLoop, this); -} - -void AnnounceMultiplayerSession::Stop() { - if (announce_multiplayer_thread) { - shutdown_event.Set(); - announce_multiplayer_thread->join(); - announce_multiplayer_thread.reset(); - backend->Delete(); - registered = false; - } -} - -AnnounceMultiplayerSession::CallbackHandle AnnounceMultiplayerSession::BindErrorCallback( - std::function function) { - std::lock_guard lock(callback_mutex); - auto handle = std::make_shared>(function); - error_callbacks.insert(handle); - return handle; -} - -void AnnounceMultiplayerSession::UnbindErrorCallback(CallbackHandle handle) { - std::lock_guard lock(callback_mutex); - error_callbacks.erase(handle); -} - -AnnounceMultiplayerSession::~AnnounceMultiplayerSession() { - Stop(); -} - -void AnnounceMultiplayerSession::UpdateBackendData(std::shared_ptr room) { - Network::RoomInformation room_information = room->GetRoomInformation(); - std::vector memberlist = room->GetRoomMemberList(); - backend->SetRoomInformation(room_information.name, room_information.description, - room_information.port, room_information.member_slots, - Network::network_version, room->HasPassword(), - room_information.preferred_game); - backend->ClearPlayers(); - for (const auto& member : memberlist) { - backend->AddPlayer(member); - } -} - -void AnnounceMultiplayerSession::AnnounceMultiplayerLoop() { - // Invokes all current bound error callbacks. - const auto ErrorCallback = [this](WebService::WebResult result) { - std::lock_guard lock(callback_mutex); - for (auto callback : error_callbacks) { - (*callback)(result); - } - }; - - if (!registered) { - WebService::WebResult result = Register(); - if (result.result_code != WebService::WebResult::Code::Success) { - ErrorCallback(result); - return; - } - } - - auto update_time = std::chrono::steady_clock::now(); - std::future future; - while (!shutdown_event.WaitUntil(update_time)) { - update_time += announce_time_interval; - auto room = room_network.GetRoom().lock(); - if (!room) { - break; - } - if (room->GetState() != Network::Room::State::Open) { - break; - } - UpdateBackendData(room); - WebService::WebResult result = backend->Update(); - if (result.result_code != WebService::WebResult::Code::Success) { - ErrorCallback(result); - } - if (result.result_string == "404") { - registered = false; - // Needs to register the room again - WebService::WebResult register_result = Register(); - if (register_result.result_code != WebService::WebResult::Code::Success) { - ErrorCallback(register_result); - } - } - } -} - -AnnounceMultiplayerRoom::RoomList AnnounceMultiplayerSession::GetRoomList() { - return backend->GetRoomList(); -} - -bool AnnounceMultiplayerSession::IsRunning() const { - return announce_multiplayer_thread != nullptr; -} - -void AnnounceMultiplayerSession::UpdateCredentials() { - ASSERT_MSG(!IsRunning(), "Credentials can only be updated when session is not running"); - -#ifdef ENABLE_WEB_SERVICE - backend = std::make_unique(Settings::values.web_api_url.GetValue(), - Settings::values.yuzu_username.GetValue(), - Settings::values.yuzu_token.GetValue()); -#endif -} - -} // namespace Core diff --git a/src/core/announce_multiplayer_session.h b/src/core/announce_multiplayer_session.h deleted file mode 100644 index db790f7d2..000000000 --- a/src/core/announce_multiplayer_session.h +++ /dev/null @@ -1,98 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include -#include -#include -#include -#include -#include "common/announce_multiplayer_room.h" -#include "common/common_types.h" -#include "common/thread.h" - -namespace Network { -class Room; -class RoomNetwork; -} // namespace Network - -namespace Core { - -/** - * Instruments AnnounceMultiplayerRoom::Backend. - * Creates a thread that regularly updates the room information and submits them - * An async get of room information is also possible - */ -class AnnounceMultiplayerSession { -public: - using CallbackHandle = std::shared_ptr>; - AnnounceMultiplayerSession(Network::RoomNetwork& room_network_); - ~AnnounceMultiplayerSession(); - - /** - * Allows to bind a function that will get called if the announce encounters an error - * @param function The function that gets called - * @return A handle that can be used the unbind the function - */ - CallbackHandle BindErrorCallback(std::function function); - - /** - * Unbind a function from the error callbacks - * @param handle The handle for the function that should get unbind - */ - void UnbindErrorCallback(CallbackHandle handle); - - /** - * Registers a room to web services - * @return The result of the registration attempt. - */ - WebService::WebResult Register(); - - /** - * Starts the announce of a room to web services - */ - void Start(); - - /** - * Stops the announce to web services - */ - void Stop(); - - /** - * Returns a list of all room information the backend got - * @param func A function that gets executed when the async get finished, e.g. a signal - * @return a list of rooms received from the web service - */ - AnnounceMultiplayerRoom::RoomList GetRoomList(); - - /** - * Whether the announce session is still running - */ - bool IsRunning() const; - - /** - * Recreates the backend, updating the credentials. - * This can only be used when the announce session is not running. - */ - void UpdateCredentials(); - -private: - void UpdateBackendData(std::shared_ptr room); - void AnnounceMultiplayerLoop(); - - Common::Event shutdown_event; - std::mutex callback_mutex; - std::set error_callbacks; - std::unique_ptr announce_multiplayer_thread; - - /// Backend interface that logs fields - std::unique_ptr backend; - - std::atomic_bool registered = false; ///< Whether the room has been registered - - Network::RoomNetwork& room_network; -}; - -} // namespace Core diff --git a/src/dedicated_room/CMakeLists.txt b/src/dedicated_room/CMakeLists.txt index b674b915b..737aedbe4 100644 --- a/src/dedicated_room/CMakeLists.txt +++ b/src/dedicated_room/CMakeLists.txt @@ -10,7 +10,7 @@ add_executable(yuzu-room create_target_directory_groups(yuzu-room) -target_link_libraries(yuzu-room PRIVATE common core network) +target_link_libraries(yuzu-room PRIVATE common network) if (ENABLE_WEB_SERVICE) target_compile_definitions(yuzu-room PRIVATE -DENABLE_WEB_SERVICE) target_link_libraries(yuzu-room PRIVATE web_service) diff --git a/src/dedicated_room/yuzu_room.cpp b/src/dedicated_room/yuzu_room.cpp index 482e772fb..7c1a75de3 100644 --- a/src/dedicated_room/yuzu_room.cpp +++ b/src/dedicated_room/yuzu_room.cpp @@ -27,8 +27,8 @@ #include "common/scm_rev.h" #include "common/settings.h" #include "common/string_util.h" -#include "core/announce_multiplayer_session.h" #include "core/core.h" +#include "network/announce_multiplayer_session.h" #include "network/network.h" #include "network/room.h" #include "network/verify_user.h" diff --git a/src/network/CMakeLists.txt b/src/network/CMakeLists.txt index 312f79b68..6f8ca4b90 100644 --- a/src/network/CMakeLists.txt +++ b/src/network/CMakeLists.txt @@ -2,6 +2,8 @@ # SPDX-License-Identifier: GPL-3.0-or-later add_library(network STATIC + announce_multiplayer_session.cpp + announce_multiplayer_session.h network.cpp network.h packet.cpp @@ -17,3 +19,7 @@ add_library(network STATIC create_target_directory_groups(network) target_link_libraries(network PRIVATE common enet Boost::boost) +if (ENABLE_WEB_SERVICE) + target_compile_definitions(network PRIVATE -DENABLE_WEB_SERVICE) + target_link_libraries(network PRIVATE web_service) +endif() diff --git a/src/network/announce_multiplayer_session.cpp b/src/network/announce_multiplayer_session.cpp new file mode 100644 index 000000000..6737ce85a --- /dev/null +++ b/src/network/announce_multiplayer_session.cpp @@ -0,0 +1,164 @@ +// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include "announce_multiplayer_session.h" +#include "common/announce_multiplayer_room.h" +#include "common/assert.h" +#include "common/settings.h" +#include "network/network.h" + +#ifdef ENABLE_WEB_SERVICE +#include "web_service/announce_room_json.h" +#endif + +namespace Core { + +// Time between room is announced to web_service +static constexpr std::chrono::seconds announce_time_interval(15); + +AnnounceMultiplayerSession::AnnounceMultiplayerSession(Network::RoomNetwork& room_network_) + : room_network{room_network_} { +#ifdef ENABLE_WEB_SERVICE + backend = std::make_unique(Settings::values.web_api_url.GetValue(), + Settings::values.yuzu_username.GetValue(), + Settings::values.yuzu_token.GetValue()); +#else + backend = std::make_unique(); +#endif +} + +WebService::WebResult AnnounceMultiplayerSession::Register() { + auto room = room_network.GetRoom().lock(); + if (!room) { + return WebService::WebResult{WebService::WebResult::Code::LibError, + "Network is not initialized", ""}; + } + if (room->GetState() != Network::Room::State::Open) { + return WebService::WebResult{WebService::WebResult::Code::LibError, "Room is not open", ""}; + } + UpdateBackendData(room); + WebService::WebResult result = backend->Register(); + if (result.result_code != WebService::WebResult::Code::Success) { + return result; + } + LOG_INFO(WebService, "Room has been registered"); + room->SetVerifyUID(result.returned_data); + registered = true; + return WebService::WebResult{WebService::WebResult::Code::Success, "", ""}; +} + +void AnnounceMultiplayerSession::Start() { + if (announce_multiplayer_thread) { + Stop(); + } + shutdown_event.Reset(); + announce_multiplayer_thread = + std::make_unique(&AnnounceMultiplayerSession::AnnounceMultiplayerLoop, this); +} + +void AnnounceMultiplayerSession::Stop() { + if (announce_multiplayer_thread) { + shutdown_event.Set(); + announce_multiplayer_thread->join(); + announce_multiplayer_thread.reset(); + backend->Delete(); + registered = false; + } +} + +AnnounceMultiplayerSession::CallbackHandle AnnounceMultiplayerSession::BindErrorCallback( + std::function function) { + std::lock_guard lock(callback_mutex); + auto handle = std::make_shared>(function); + error_callbacks.insert(handle); + return handle; +} + +void AnnounceMultiplayerSession::UnbindErrorCallback(CallbackHandle handle) { + std::lock_guard lock(callback_mutex); + error_callbacks.erase(handle); +} + +AnnounceMultiplayerSession::~AnnounceMultiplayerSession() { + Stop(); +} + +void AnnounceMultiplayerSession::UpdateBackendData(std::shared_ptr room) { + Network::RoomInformation room_information = room->GetRoomInformation(); + std::vector memberlist = room->GetRoomMemberList(); + backend->SetRoomInformation(room_information.name, room_information.description, + room_information.port, room_information.member_slots, + Network::network_version, room->HasPassword(), + room_information.preferred_game); + backend->ClearPlayers(); + for (const auto& member : memberlist) { + backend->AddPlayer(member); + } +} + +void AnnounceMultiplayerSession::AnnounceMultiplayerLoop() { + // Invokes all current bound error callbacks. + const auto ErrorCallback = [this](WebService::WebResult result) { + std::lock_guard lock(callback_mutex); + for (auto callback : error_callbacks) { + (*callback)(result); + } + }; + + if (!registered) { + WebService::WebResult result = Register(); + if (result.result_code != WebService::WebResult::Code::Success) { + ErrorCallback(result); + return; + } + } + + auto update_time = std::chrono::steady_clock::now(); + std::future future; + while (!shutdown_event.WaitUntil(update_time)) { + update_time += announce_time_interval; + auto room = room_network.GetRoom().lock(); + if (!room) { + break; + } + if (room->GetState() != Network::Room::State::Open) { + break; + } + UpdateBackendData(room); + WebService::WebResult result = backend->Update(); + if (result.result_code != WebService::WebResult::Code::Success) { + ErrorCallback(result); + } + if (result.result_string == "404") { + registered = false; + // Needs to register the room again + WebService::WebResult register_result = Register(); + if (register_result.result_code != WebService::WebResult::Code::Success) { + ErrorCallback(register_result); + } + } + } +} + +AnnounceMultiplayerRoom::RoomList AnnounceMultiplayerSession::GetRoomList() { + return backend->GetRoomList(); +} + +bool AnnounceMultiplayerSession::IsRunning() const { + return announce_multiplayer_thread != nullptr; +} + +void AnnounceMultiplayerSession::UpdateCredentials() { + ASSERT_MSG(!IsRunning(), "Credentials can only be updated when session is not running"); + +#ifdef ENABLE_WEB_SERVICE + backend = std::make_unique(Settings::values.web_api_url.GetValue(), + Settings::values.yuzu_username.GetValue(), + Settings::values.yuzu_token.GetValue()); +#endif +} + +} // namespace Core diff --git a/src/network/announce_multiplayer_session.h b/src/network/announce_multiplayer_session.h new file mode 100644 index 000000000..db790f7d2 --- /dev/null +++ b/src/network/announce_multiplayer_session.h @@ -0,0 +1,98 @@ +// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include +#include +#include "common/announce_multiplayer_room.h" +#include "common/common_types.h" +#include "common/thread.h" + +namespace Network { +class Room; +class RoomNetwork; +} // namespace Network + +namespace Core { + +/** + * Instruments AnnounceMultiplayerRoom::Backend. + * Creates a thread that regularly updates the room information and submits them + * An async get of room information is also possible + */ +class AnnounceMultiplayerSession { +public: + using CallbackHandle = std::shared_ptr>; + AnnounceMultiplayerSession(Network::RoomNetwork& room_network_); + ~AnnounceMultiplayerSession(); + + /** + * Allows to bind a function that will get called if the announce encounters an error + * @param function The function that gets called + * @return A handle that can be used the unbind the function + */ + CallbackHandle BindErrorCallback(std::function function); + + /** + * Unbind a function from the error callbacks + * @param handle The handle for the function that should get unbind + */ + void UnbindErrorCallback(CallbackHandle handle); + + /** + * Registers a room to web services + * @return The result of the registration attempt. + */ + WebService::WebResult Register(); + + /** + * Starts the announce of a room to web services + */ + void Start(); + + /** + * Stops the announce to web services + */ + void Stop(); + + /** + * Returns a list of all room information the backend got + * @param func A function that gets executed when the async get finished, e.g. a signal + * @return a list of rooms received from the web service + */ + AnnounceMultiplayerRoom::RoomList GetRoomList(); + + /** + * Whether the announce session is still running + */ + bool IsRunning() const; + + /** + * Recreates the backend, updating the credentials. + * This can only be used when the announce session is not running. + */ + void UpdateCredentials(); + +private: + void UpdateBackendData(std::shared_ptr room); + void AnnounceMultiplayerLoop(); + + Common::Event shutdown_event; + std::mutex callback_mutex; + std::set error_callbacks; + std::unique_ptr announce_multiplayer_thread; + + /// Backend interface that logs fields + std::unique_ptr backend; + + std::atomic_bool registered = false; ///< Whether the room has been registered + + Network::RoomNetwork& room_network; +}; + +} // namespace Core diff --git a/src/yuzu/multiplayer/chat_room.cpp b/src/yuzu/multiplayer/chat_room.cpp index 1968a3c75..51ece1f21 100644 --- a/src/yuzu/multiplayer/chat_room.cpp +++ b/src/yuzu/multiplayer/chat_room.cpp @@ -16,7 +16,7 @@ #include #include #include "common/logging/log.h" -#include "core/announce_multiplayer_session.h" +#include "network/announce_multiplayer_session.h" #include "ui_chat_room.h" #include "yuzu/game_list_p.h" #include "yuzu/multiplayer/chat_room.h" diff --git a/src/yuzu/multiplayer/client_room.cpp b/src/yuzu/multiplayer/client_room.cpp index 86baafbf0..b34a8d004 100644 --- a/src/yuzu/multiplayer/client_room.cpp +++ b/src/yuzu/multiplayer/client_room.cpp @@ -10,7 +10,7 @@ #include #include #include "common/logging/log.h" -#include "core/announce_multiplayer_session.h" +#include "network/announce_multiplayer_session.h" #include "ui_client_room.h" #include "yuzu/game_list_p.h" #include "yuzu/multiplayer/client_room.h" diff --git a/src/yuzu/multiplayer/host_room.cpp b/src/yuzu/multiplayer/host_room.cpp index d70a9a3c8..8e7a81291 100644 --- a/src/yuzu/multiplayer/host_room.cpp +++ b/src/yuzu/multiplayer/host_room.cpp @@ -12,7 +12,7 @@ #include #include "common/logging/log.h" #include "common/settings.h" -#include "core/announce_multiplayer_session.h" +#include "network/announce_multiplayer_session.h" #include "ui_host_room.h" #include "yuzu/game_list_p.h" #include "yuzu/main.h" diff --git a/src/yuzu/multiplayer/lobby.h b/src/yuzu/multiplayer/lobby.h index 82744ca94..02cc766e4 100644 --- a/src/yuzu/multiplayer/lobby.h +++ b/src/yuzu/multiplayer/lobby.h @@ -9,7 +9,7 @@ #include #include #include "common/announce_multiplayer_room.h" -#include "core/announce_multiplayer_session.h" +#include "network/announce_multiplayer_session.h" #include "network/network.h" #include "yuzu/multiplayer/validation.h" diff --git a/src/yuzu/multiplayer/state.h b/src/yuzu/multiplayer/state.h index 9c60712d5..23960414e 100644 --- a/src/yuzu/multiplayer/state.h +++ b/src/yuzu/multiplayer/state.h @@ -4,7 +4,7 @@ #pragma once #include -#include "core/announce_multiplayer_session.h" +#include "network/announce_multiplayer_session.h" #include "network/network.h" class QStandardItemModel; -- cgit v1.2.3 From e431cb8d1606b4d43b091f0de64aefeb19ce88bf Mon Sep 17 00:00:00 2001 From: FearlessTobi Date: Sat, 27 Aug 2022 03:08:21 +0200 Subject: core/acc: Make CheckAvailability use LOG_DEBUG Previously it was spamming the logs in certain multiplayer games like Puyo Puyo Tetris. --- src/core/hle/service/acc/acc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/core') diff --git a/src/core/hle/service/acc/acc.cpp b/src/core/hle/service/acc/acc.cpp index def105832..bb838e285 100644 --- a/src/core/hle/service/acc/acc.cpp +++ b/src/core/hle/service/acc/acc.cpp @@ -534,7 +534,7 @@ public: private: void CheckAvailability(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_ACC, "(STUBBED) called"); + LOG_DEBUG(Service_ACC, "(STUBBED) called"); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); rb.Push(false); // TODO: Check when this is supposed to return true and when not -- cgit v1.2.3 From d92826963ad15415ad18b2b56144e5eafb68ac5f Mon Sep 17 00:00:00 2001 From: FearlessTobi Date: Sat, 27 Aug 2022 03:12:12 +0200 Subject: core/bsd: Correctly unbind methods in destructor Prevents yuzu from crashing when the BSD service is created a second time. --- src/core/hle/service/sockets/bsd.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'src/core') diff --git a/src/core/hle/service/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp index e08c3cb67..cc679cc81 100644 --- a/src/core/hle/service/sockets/bsd.cpp +++ b/src/core/hle/service/sockets/bsd.cpp @@ -933,7 +933,11 @@ BSD::BSD(Core::System& system_, const char* name) } } -BSD::~BSD() = default; +BSD::~BSD() { + if (auto room_member = room_network.GetRoomMember().lock()) { + room_member->Unbind(proxy_packet_received); + } +} BSDCFG::BSDCFG(Core::System& system_) : ServiceFramework{system_, "bsdcfg"} { // clang-format off -- cgit v1.2.3 From 839e1faf491776f4e2348c46773c248644e260ba Mon Sep 17 00:00:00 2001 From: FearlessTobi Date: Sat, 27 Aug 2022 03:31:17 +0200 Subject: yuzu: Display current game version in multiplayer room Makes it easier for users to recognize connection errors caused by different game versions. --- src/common/announce_multiplayer_room.h | 1 + src/core/core.cpp | 9 +++++++++ src/network/room.cpp | 12 ++++++++---- src/network/room_member.cpp | 4 +++- src/network/room_member.h | 2 +- src/yuzu/multiplayer/chat_room.cpp | 21 ++++++++++++++++----- 6 files changed, 38 insertions(+), 11 deletions(-) (limited to 'src/core') diff --git a/src/common/announce_multiplayer_room.h b/src/common/announce_multiplayer_room.h index cb004e0eb..4a3100fa4 100644 --- a/src/common/announce_multiplayer_room.h +++ b/src/common/announce_multiplayer_room.h @@ -16,6 +16,7 @@ namespace AnnounceMultiplayerRoom { struct GameInfo { std::string name{""}; u64 id{0}; + std::string version{""}; }; struct Member { diff --git a/src/core/core.cpp b/src/core/core.cpp index ea32a4a8d..e651ce100 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -319,10 +319,19 @@ struct System::Impl { if (app_loader->ReadTitle(name) != Loader::ResultStatus::Success) { LOG_ERROR(Core, "Failed to read title for ROM (Error {})", load_result); } + + std::string title_version; + const FileSys::PatchManager pm(program_id, system.GetFileSystemController(), + system.GetContentProvider()); + const auto metadata = pm.GetControlMetadata(); + if (metadata.first != nullptr) { + title_version = metadata.first->GetVersionString(); + } if (auto room_member = room_network.GetRoomMember().lock()) { Network::GameInfo game_info; game_info.name = name; game_info.id = program_id; + game_info.version = title_version; room_member->SendGameInfo(game_info); } diff --git a/src/network/room.cpp b/src/network/room.cpp index 34298f010..8c63b255b 100644 --- a/src/network/room.cpp +++ b/src/network/room.cpp @@ -221,7 +221,7 @@ public: * Extracts the game name from a received ENet packet and broadcasts it. * @param event The ENet event that was received. */ - void HandleGameNamePacket(const ENetEvent* event); + void HandleGameInfoPacket(const ENetEvent* event); /** * Removes the client from the members list if it was in it and announces the change @@ -242,7 +242,7 @@ void Room::RoomImpl::ServerLoop() { HandleJoinRequest(&event); break; case IdSetGameInfo: - HandleGameNamePacket(&event); + HandleGameInfoPacket(&event); break; case IdProxyPacket: HandleProxyPacket(&event); @@ -778,6 +778,7 @@ void Room::RoomImpl::BroadcastRoomInformation() { packet.Write(member.fake_ip); packet.Write(member.game_info.name); packet.Write(member.game_info.id); + packet.Write(member.game_info.version); packet.Write(member.user_data.username); packet.Write(member.user_data.display_name); packet.Write(member.user_data.avatar_url); @@ -817,6 +818,7 @@ void Room::RoomImpl::HandleProxyPacket(const ENetEvent* event) { in_packet.IgnoreBytes(sizeof(u16)); // Port in_packet.IgnoreBytes(sizeof(u8)); // Protocol + bool broadcast; in_packet.Read(broadcast); // Broadcast @@ -909,7 +911,7 @@ void Room::RoomImpl::HandleChatPacket(const ENetEvent* event) { } } -void Room::RoomImpl::HandleGameNamePacket(const ENetEvent* event) { +void Room::RoomImpl::HandleGameInfoPacket(const ENetEvent* event) { Packet in_packet; in_packet.Append(event->packet->data, event->packet->dataLength); @@ -917,6 +919,7 @@ void Room::RoomImpl::HandleGameNamePacket(const ENetEvent* event) { GameInfo game_info; in_packet.Read(game_info.name); in_packet.Read(game_info.id); + in_packet.Read(game_info.version); { std::lock_guard lock(member_mutex); @@ -935,7 +938,8 @@ void Room::RoomImpl::HandleGameNamePacket(const ENetEvent* event) { if (game_info.name.empty()) { LOG_INFO(Network, "{} is not playing", display_name); } else { - LOG_INFO(Network, "{} is playing {}", display_name, game_info.name); + LOG_INFO(Network, "{} is playing {} ({})", display_name, game_info.name, + game_info.version); } } } diff --git a/src/network/room_member.cpp b/src/network/room_member.cpp index 367bf377f..06818af78 100644 --- a/src/network/room_member.cpp +++ b/src/network/room_member.cpp @@ -103,7 +103,7 @@ public: /** * Extracts a ProxyPacket from a received ENet packet. - * @param event The ENet event that was received. + * @param event The ENet event that was received. */ void HandleProxyPackets(const ENetEvent* event); @@ -315,6 +315,7 @@ void RoomMember::RoomMemberImpl::HandleRoomInformationPacket(const ENetEvent* ev packet.Read(member.fake_ip); packet.Read(member.game_info.name); packet.Read(member.game_info.id); + packet.Read(member.game_info.version); packet.Read(member.username); packet.Read(member.display_name); packet.Read(member.avatar_url); @@ -622,6 +623,7 @@ void RoomMember::SendGameInfo(const GameInfo& game_info) { packet.Write(static_cast(IdSetGameInfo)); packet.Write(game_info.name); packet.Write(game_info.id); + packet.Write(game_info.version); room_member_impl->Send(std::move(packet)); } diff --git a/src/network/room_member.h b/src/network/room_member.h index 4252b7146..f578f7f6a 100644 --- a/src/network/room_member.h +++ b/src/network/room_member.h @@ -146,7 +146,7 @@ public: const std::string& password = "", const std::string& token = ""); /** - * Sends a WiFi packet to the room. + * Sends a Proxy packet to the room. * @param packet The WiFi packet to send. */ void SendProxyPacket(const ProxyPacket& packet); diff --git a/src/yuzu/multiplayer/chat_room.cpp b/src/yuzu/multiplayer/chat_room.cpp index 51ece1f21..21582b3d6 100644 --- a/src/yuzu/multiplayer/chat_room.cpp +++ b/src/yuzu/multiplayer/chat_room.cpp @@ -122,19 +122,22 @@ public: static const int UsernameRole = Qt::UserRole + 2; static const int AvatarUrlRole = Qt::UserRole + 3; static const int GameNameRole = Qt::UserRole + 4; + static const int GameVersionRole = Qt::UserRole + 5; PlayerListItem() = default; explicit PlayerListItem(const std::string& nickname, const std::string& username, - const std::string& avatar_url, const std::string& game_name) { + const std::string& avatar_url, + const AnnounceMultiplayerRoom::GameInfo& game_info) { setEditable(false); setData(QString::fromStdString(nickname), NicknameRole); setData(QString::fromStdString(username), UsernameRole); setData(QString::fromStdString(avatar_url), AvatarUrlRole); - if (game_name.empty()) { + if (game_info.name.empty()) { setData(QObject::tr("Not playing a game"), GameNameRole); } else { - setData(QString::fromStdString(game_name), GameNameRole); + setData(QString::fromStdString(game_info.name), GameNameRole); } + setData(QString::fromStdString(game_info.version), GameVersionRole); } QVariant data(int role) const override { @@ -149,7 +152,15 @@ public: } else { name = QStringLiteral("%1 (%2)").arg(nickname, username); } - return QStringLiteral("%1\n %2").arg(name, data(GameNameRole).toString()); + const QString version = data(GameVersionRole).toString(); + QString version_string; + if (version.isEmpty()) { + version_string = QString{}; + } else { + version_string = QStringLiteral("(%1)").arg(version); + } + return QStringLiteral("%1\n %2 %3") + .arg(name, data(GameNameRole).toString(), version_string); } }; @@ -366,7 +377,7 @@ void ChatRoom::SetPlayerList(const Network::RoomMember::MemberList& member_list) if (member.nickname.empty()) continue; QStandardItem* name_item = new PlayerListItem(member.nickname, member.username, - member.avatar_url, member.game_info.name); + member.avatar_url, member.game_info); #ifdef ENABLE_WEB_SERVICE if (!icon_cache.count(member.avatar_url) && !member.avatar_url.empty()) { -- cgit v1.2.3 From 339758c9fce9a211f85f62182d8fa0e1115c229b Mon Sep 17 00:00:00 2001 From: FearlessTobi Date: Sat, 27 Aug 2022 03:39:02 +0200 Subject: core/socket_proxy: Correct broadcast behavior Broadcasts should only be sent when the broadcast IP is used. They should also only be received when SO_BROADCAST is enabled. --- src/core/internal_network/socket_proxy.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'src/core') diff --git a/src/core/internal_network/socket_proxy.cpp b/src/core/internal_network/socket_proxy.cpp index 49d067f4c..0c746bd82 100644 --- a/src/core/internal_network/socket_proxy.cpp +++ b/src/core/internal_network/socket_proxy.cpp @@ -26,6 +26,12 @@ void ProxySocket::HandleProxyPacket(const ProxyPacket& packet) { closed) { return; } + + if (!broadcast && packet.broadcast) { + LOG_INFO(Network, "Received broadcast packet, but not configured for broadcast mode"); + return; + } + std::lock_guard guard(packets_mutex); received_packets.push(packet); } @@ -203,7 +209,7 @@ std::pair ProxySocket::SendTo(u32 flags, const std::vector& mess packet.local_endpoint = local_endpoint; packet.remote_endpoint = *addr; packet.protocol = protocol; - packet.broadcast = broadcast; + packet.broadcast = broadcast && packet.remote_endpoint.ip[3] == 255; auto& ip = local_endpoint.ip; auto ipv4 = Network::GetHostIPv4Address(); -- cgit v1.2.3 From 6791301d9a2fa8ddfe0de0d059763c701b830f94 Mon Sep 17 00:00:00 2001 From: FearlessTobi Date: Sat, 27 Aug 2022 04:49:10 +0200 Subject: core/ldn_types: Minor corrections and additions --- src/core/hle/service/ldn/ldn_types.h | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) (limited to 'src/core') diff --git a/src/core/hle/service/ldn/ldn_types.h b/src/core/hle/service/ldn/ldn_types.h index 0c07a7397..0af653164 100644 --- a/src/core/hle/service/ldn/ldn_types.h +++ b/src/core/hle/service/ldn/ldn_types.h @@ -113,7 +113,7 @@ enum class LinkLevel : s8 { Bad, Low, Good, - Excelent, + Excellent, }; struct NodeLatestUpdate { @@ -148,9 +148,24 @@ struct Ssid { u8 length; std::array raw; + Ssid() { + length = 0; + std::memset(raw.data(), 0, raw.size()); + } + + Ssid(std::string data) { + length = static_cast(std::min(data.size(), SsidLengthMax)); + std::memcpy(raw.data(), data.data(), length); + raw[length] = 0; + } + std::string GetStringValue() const { return std::string(raw.data(), length); } + + bool operator==(const Ssid& b) const { + return (length == b.length) && (std::memcmp(raw.data(), b.raw.data(), length) == 0); + } }; static_assert(sizeof(Ssid) == 0x22, "Ssid is an invalid size"); -- cgit v1.2.3 From 03aedccaa252a7989c3ab147a83305b386476748 Mon Sep 17 00:00:00 2001 From: Kelebek1 Date: Thu, 1 Sep 2022 16:47:47 +0100 Subject: Demote services from warning/info to debug to reduce log spam: GetCurrentFocusState SetClockSpeed EnableSixAxisSensorUnalteredPassthrough IsSixAxisSensorUnalteredPassthroughEnabled Get, GetOld SetAndWait, SetAndWaitOld IocParam IocFree --- src/core/hle/service/am/am.cpp | 2 +- src/core/hle/service/apm/apm_controller.cpp | 2 +- src/core/hle/service/hid/hid.cpp | 14 +++++++------- src/core/hle/service/mm/mm_u.cpp | 10 +++++----- src/core/hle/service/nvdrv/devices/nvmap.cpp | 4 ++-- 5 files changed, 16 insertions(+), 16 deletions(-) (limited to 'src/core') diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index 118f226e4..6fb7e198e 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -754,7 +754,7 @@ void ICommonStateGetter::ReceiveMessage(Kernel::HLERequestContext& ctx) { } void ICommonStateGetter::GetCurrentFocusState(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_AM, "(STUBBED) called"); + LOG_DEBUG(Service_AM, "(STUBBED) called"); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); diff --git a/src/core/hle/service/apm/apm_controller.cpp b/src/core/hle/service/apm/apm_controller.cpp index 4e710491b..d6de84066 100644 --- a/src/core/hle/service/apm/apm_controller.cpp +++ b/src/core/hle/service/apm/apm_controller.cpp @@ -80,7 +80,7 @@ PerformanceConfiguration Controller::GetCurrentPerformanceConfiguration(Performa } void Controller::SetClockSpeed(u32 mhz) { - LOG_INFO(Service_APM, "called, mhz={:08X}", mhz); + LOG_DEBUG(Service_APM, "called, mhz={:08X}", mhz); // TODO(DarkLordZach): Actually signal core_timing to change clock speed. // TODO(Rodrigo): Remove [[maybe_unused]] when core_timing is used. } diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index 7909141c0..3d3457160 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -819,12 +819,12 @@ void Hid::EnableSixAxisSensorUnalteredPassthrough(Kernel::HLERequestContext& ctx const auto result = controller.EnableSixAxisSensorUnalteredPassthrough( parameters.sixaxis_handle, parameters.enabled); - LOG_WARNING(Service_HID, - "(STUBBED) called, enabled={}, npad_type={}, npad_id={}, device_index={}, " - "applet_resource_user_id={}", - parameters.enabled, parameters.sixaxis_handle.npad_type, - parameters.sixaxis_handle.npad_id, parameters.sixaxis_handle.device_index, - parameters.applet_resource_user_id); + LOG_DEBUG(Service_HID, + "(STUBBED) called, enabled={}, npad_type={}, npad_id={}, device_index={}, " + "applet_resource_user_id={}", + parameters.enabled, parameters.sixaxis_handle.npad_type, + parameters.sixaxis_handle.npad_id, parameters.sixaxis_handle.device_index, + parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(result); @@ -846,7 +846,7 @@ void Hid::IsSixAxisSensorUnalteredPassthroughEnabled(Kernel::HLERequestContext& const auto result = controller.IsSixAxisSensorUnalteredPassthroughEnabled( parameters.sixaxis_handle, is_unaltered_sisxaxis_enabled); - LOG_WARNING( + LOG_DEBUG( Service_HID, "(STUBBED) called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id, diff --git a/src/core/hle/service/mm/mm_u.cpp b/src/core/hle/service/mm/mm_u.cpp index 5ebb124a7..ba8c0e230 100644 --- a/src/core/hle/service/mm/mm_u.cpp +++ b/src/core/hle/service/mm/mm_u.cpp @@ -46,7 +46,7 @@ private: IPC::RequestParser rp{ctx}; min = rp.Pop(); max = rp.Pop(); - LOG_WARNING(Service_MM, "(STUBBED) called, min=0x{:X}, max=0x{:X}", min, max); + LOG_DEBUG(Service_MM, "(STUBBED) called, min=0x{:X}, max=0x{:X}", min, max); current = min; IPC::ResponseBuilder rb{ctx, 2}; @@ -54,7 +54,7 @@ private: } void GetOld(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_MM, "(STUBBED) called"); + LOG_DEBUG(Service_MM, "(STUBBED) called"); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); @@ -81,8 +81,8 @@ private: u32 input_id = rp.Pop(); min = rp.Pop(); max = rp.Pop(); - LOG_WARNING(Service_MM, "(STUBBED) called, input_id=0x{:X}, min=0x{:X}, max=0x{:X}", - input_id, min, max); + LOG_DEBUG(Service_MM, "(STUBBED) called, input_id=0x{:X}, min=0x{:X}, max=0x{:X}", input_id, + min, max); current = min; IPC::ResponseBuilder rb{ctx, 2}; @@ -90,7 +90,7 @@ private: } void Get(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_MM, "(STUBBED) called"); + LOG_DEBUG(Service_MM, "(STUBBED) called"); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); diff --git a/src/core/hle/service/nvdrv/devices/nvmap.cpp b/src/core/hle/service/nvdrv/devices/nvmap.cpp index 728bfa00b..d8518149d 100644 --- a/src/core/hle/service/nvdrv/devices/nvmap.cpp +++ b/src/core/hle/service/nvdrv/devices/nvmap.cpp @@ -198,7 +198,7 @@ NvResult nvmap::IocParam(const std::vector& input, std::vector& output) IocParamParams params; std::memcpy(¶ms, input.data(), sizeof(params)); - LOG_WARNING(Service_NVDRV, "(STUBBED) called type={}", params.param); + LOG_DEBUG(Service_NVDRV, "(STUBBED) called type={}", params.param); auto object = GetObject(params.handle); if (!object) { @@ -243,7 +243,7 @@ NvResult nvmap::IocFree(const std::vector& input, std::vector& output) { IocFreeParams params; std::memcpy(¶ms, input.data(), sizeof(params)); - LOG_WARNING(Service_NVDRV, "(STUBBED) called"); + LOG_DEBUG(Service_NVDRV, "(STUBBED) called"); auto itr = handles.find(params.handle); if (itr == handles.end()) { -- cgit v1.2.3 From 65718e2876374aecf2ac29856387dab4394ca47f Mon Sep 17 00:00:00 2001 From: FearlessTobi Date: Sun, 28 Aug 2022 19:31:16 +0200 Subject: Address review comments --- src/core/hle/service/ldn/ldn_types.h | 19 ++++++------------- src/dedicated_room/yuzu_room.cpp | 7 +------ src/yuzu/multiplayer/chat_room.cpp | 4 +--- src/yuzu/multiplayer/direct_connect.cpp | 1 + src/yuzu/multiplayer/direct_connect.h | 5 ++++- src/yuzu/multiplayer/host_room.cpp | 1 + src/yuzu/multiplayer/host_room.h | 4 ++-- src/yuzu/multiplayer/lobby.cpp | 1 + src/yuzu/multiplayer/lobby.h | 5 ++++- src/yuzu/multiplayer/state.cpp | 1 + src/yuzu/multiplayer/state.h | 5 ++++- 11 files changed, 26 insertions(+), 27 deletions(-) (limited to 'src/core') diff --git a/src/core/hle/service/ldn/ldn_types.h b/src/core/hle/service/ldn/ldn_types.h index 0af653164..6231e936d 100644 --- a/src/core/hle/service/ldn/ldn_types.h +++ b/src/core/hle/service/ldn/ldn_types.h @@ -145,26 +145,19 @@ struct NetworkId { static_assert(sizeof(NetworkId) == 0x20, "NetworkId is an invalid size"); struct Ssid { - u8 length; - std::array raw; + u8 length{}; + std::array raw{}; - Ssid() { - length = 0; - std::memset(raw.data(), 0, raw.size()); - } + Ssid() = default; - Ssid(std::string data) { + explicit Ssid(std::string_view data) { length = static_cast(std::min(data.size(), SsidLengthMax)); - std::memcpy(raw.data(), data.data(), length); + data.copy(raw.data(), length); raw[length] = 0; } std::string GetStringValue() const { - return std::string(raw.data(), length); - } - - bool operator==(const Ssid& b) const { - return (length == b.length) && (std::memcmp(raw.data(), b.raw.data(), length) == 0); + return std::string(raw.data()); } }; static_assert(sizeof(Ssid) == 0x22, "Ssid is an invalid size"); diff --git a/src/dedicated_room/yuzu_room.cpp b/src/dedicated_room/yuzu_room.cpp index 173a53317..7b6deba41 100644 --- a/src/dedicated_room/yuzu_room.cpp +++ b/src/dedicated_room/yuzu_room.cpp @@ -76,12 +76,7 @@ static constexpr char BanListMagic[] = "YuzuRoom-BanList-1"; static constexpr char token_delimiter{':'}; static void PadToken(std::string& token) { - const auto remainder = token.size() % 3; - if (remainder == 0) { - return; - } - - for (size_t i = 0; i < (3 - remainder); i++) { + while (token.size() % 4 != 0) { token.push_back('='); } } diff --git a/src/yuzu/multiplayer/chat_room.cpp b/src/yuzu/multiplayer/chat_room.cpp index bddceb795..9e672f82e 100644 --- a/src/yuzu/multiplayer/chat_room.cpp +++ b/src/yuzu/multiplayer/chat_room.cpp @@ -154,9 +154,7 @@ public: } const QString version = data(GameVersionRole).toString(); QString version_string; - if (version.isEmpty()) { - version_string = QString{}; - } else { + if (!version.isEmpty()) { version_string = QStringLiteral("(%1)").arg(version); } return QStringLiteral("%1\n %2 %3") diff --git a/src/yuzu/multiplayer/direct_connect.cpp b/src/yuzu/multiplayer/direct_connect.cpp index 65b5f0b9d..017063074 100644 --- a/src/yuzu/multiplayer/direct_connect.cpp +++ b/src/yuzu/multiplayer/direct_connect.cpp @@ -8,6 +8,7 @@ #include #include #include "common/settings.h" +#include "core/core.h" #include "core/internal_network/network_interface.h" #include "network/network.h" #include "ui_direct_connect.h" diff --git a/src/yuzu/multiplayer/direct_connect.h b/src/yuzu/multiplayer/direct_connect.h index defa4f4ec..e39dd1e0d 100644 --- a/src/yuzu/multiplayer/direct_connect.h +++ b/src/yuzu/multiplayer/direct_connect.h @@ -6,13 +6,16 @@ #include #include #include -#include "core/core.h" #include "yuzu/multiplayer/validation.h" namespace Ui { class DirectConnect; } +namespace Core { +class System; +} + class DirectConnectWindow : public QDialog { Q_OBJECT diff --git a/src/yuzu/multiplayer/host_room.cpp b/src/yuzu/multiplayer/host_room.cpp index ad7dcc36d..0c6adfd04 100644 --- a/src/yuzu/multiplayer/host_room.cpp +++ b/src/yuzu/multiplayer/host_room.cpp @@ -12,6 +12,7 @@ #include #include "common/logging/log.h" #include "common/settings.h" +#include "core/core.h" #include "core/internal_network/network_interface.h" #include "network/announce_multiplayer_session.h" #include "ui_host_room.h" diff --git a/src/yuzu/multiplayer/host_room.h b/src/yuzu/multiplayer/host_room.h index 63c0d2333..034cb2eef 100644 --- a/src/yuzu/multiplayer/host_room.h +++ b/src/yuzu/multiplayer/host_room.h @@ -8,7 +8,6 @@ #include #include #include -#include "core/core.h" #include "network/network.h" #include "yuzu/multiplayer/chat_room.h" #include "yuzu/multiplayer/validation.h" @@ -18,8 +17,9 @@ class HostRoom; } namespace Core { +class System; class AnnounceMultiplayerSession; -} +} // namespace Core class ConnectionError; class ComboBoxProxyModel; diff --git a/src/yuzu/multiplayer/lobby.cpp b/src/yuzu/multiplayer/lobby.cpp index c5fb846c7..107d40547 100644 --- a/src/yuzu/multiplayer/lobby.cpp +++ b/src/yuzu/multiplayer/lobby.cpp @@ -6,6 +6,7 @@ #include #include "common/logging/log.h" #include "common/settings.h" +#include "core/core.h" #include "core/internal_network/network_interface.h" #include "network/network.h" #include "ui_lobby.h" diff --git a/src/yuzu/multiplayer/lobby.h b/src/yuzu/multiplayer/lobby.h index 49fd4c473..2696aec21 100644 --- a/src/yuzu/multiplayer/lobby.h +++ b/src/yuzu/multiplayer/lobby.h @@ -9,7 +9,6 @@ #include #include #include "common/announce_multiplayer_room.h" -#include "core/core.h" #include "network/announce_multiplayer_session.h" #include "network/network.h" #include "yuzu/multiplayer/validation.h" @@ -21,6 +20,10 @@ class Lobby; class LobbyModel; class LobbyFilterProxyModel; +namespace Core { +class System; +} + /** * Listing of all public games pulled from services. The lobby should be simple enough for users to * find the game they want to play, and join it. diff --git a/src/yuzu/multiplayer/state.cpp b/src/yuzu/multiplayer/state.cpp index d1a68b182..66e098296 100644 --- a/src/yuzu/multiplayer/state.cpp +++ b/src/yuzu/multiplayer/state.cpp @@ -8,6 +8,7 @@ #include #include "common/announce_multiplayer_room.h" #include "common/logging/log.h" +#include "core/core.h" #include "yuzu/game_list.h" #include "yuzu/multiplayer/client_room.h" #include "yuzu/multiplayer/direct_connect.h" diff --git a/src/yuzu/multiplayer/state.h b/src/yuzu/multiplayer/state.h index 3921f2fc3..c92496413 100644 --- a/src/yuzu/multiplayer/state.h +++ b/src/yuzu/multiplayer/state.h @@ -4,7 +4,6 @@ #pragma once #include -#include "core/core.h" #include "network/announce_multiplayer_session.h" #include "network/network.h" @@ -15,6 +14,10 @@ class ClientRoomWindow; class DirectConnectWindow; class ClickableLabel; +namespace Core { +class System; +} + class MultiplayerState : public QWidget { Q_OBJECT; -- cgit v1.2.3 From ea9ff71725113b8dbb159917c57aa536bba0cb53 Mon Sep 17 00:00:00 2001 From: Kelebek1 Date: Mon, 1 Aug 2022 02:58:13 +0100 Subject: Rework audio output, connecting AudioOut into coretiming to fix desync during heavy loads. --- src/audio_core/CMakeLists.txt | 1 + src/audio_core/audio_core.cpp | 8 - src/audio_core/audio_core.h | 16 - src/audio_core/device/audio_buffer.h | 4 + src/audio_core/device/audio_buffers.h | 13 +- src/audio_core/device/device_session.cpp | 52 ++- src/audio_core/device/device_session.h | 27 +- src/audio_core/in/audio_in_system.cpp | 10 +- src/audio_core/out/audio_out_system.cpp | 10 +- src/audio_core/renderer/adsp/audio_renderer.cpp | 9 +- src/audio_core/renderer/behavior/behavior_info.cpp | 14 +- src/audio_core/renderer/command/sink/device.cpp | 4 + src/audio_core/renderer/system_manager.cpp | 35 +-- src/audio_core/sink/cubeb_sink.cpp | 349 ++------------------ src/audio_core/sink/cubeb_sink.h | 2 +- src/audio_core/sink/null_sink.h | 47 +-- src/audio_core/sink/sdl2_sink.cpp | 350 ++------------------- src/audio_core/sink/sdl2_sink.h | 2 +- src/audio_core/sink/sink.h | 2 +- src/audio_core/sink/sink_details.cpp | 6 +- src/audio_core/sink/sink_stream.cpp | 259 +++++++++++++++ src/audio_core/sink/sink_stream.h | 171 +++++----- src/core/hle/result.h | 2 + 23 files changed, 551 insertions(+), 842 deletions(-) create mode 100644 src/audio_core/sink/sink_stream.cpp (limited to 'src/core') 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 sink/sink.h sink/sink_details.cpp sink/sink_details.h + sink/sink_stream.cpp sink/sink_stream.h ) diff --git a/src/audio_core/audio_core.cpp b/src/audio_core/audio_core.cpp index 78e615a10..cf7e763e6 100644 --- a/src/audio_core/audio_core.cpp +++ b/src/audio_core/audio_core.cpp @@ -57,12 +57,4 @@ void AudioCore::PauseSinks(const bool pausing) const { } } -u32 AudioCore::GetStreamQueue() const { - return estimated_queue.load(); -} - -void AudioCore::SetStreamQueue(u32 size) { - estimated_queue.store(size); -} - } // namespace AudioCore diff --git a/src/audio_core/audio_core.h b/src/audio_core/audio_core.h index 0f7d61ee4..fd1e43356 100644 --- a/src/audio_core/audio_core.h +++ b/src/audio_core/audio_core.h @@ -65,20 +65,6 @@ public: */ void PauseSinks(bool pausing) const; - /** - * Get the size of the current stream queue. - * - * @return Current stream queue size. - */ - u32 GetStreamQueue() const; - - /** - * Get the size of the current stream queue. - * - * @param size - New stream size. - */ - void SetStreamQueue(u32 size); - private: /** * Create the sinks on startup. @@ -93,8 +79,6 @@ private: std::unique_ptr input_sink; /// The ADSP in the sysmodule std::unique_ptr adsp; - /// Current size of the stream queue - std::atomic estimated_queue{0}; }; } // namespace AudioCore 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 @@ namespace AudioCore { struct AudioBuffer { + /// Timestamp this buffer started playing. + u64 start_timestamp; + /// Timestamp this buffer should finish playing. + u64 end_timestamp; /// Timestamp this buffer completed playing. s64 played_timestamp; /// 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..57c78d439 100644 --- a/src/audio_core/device/audio_buffers.h +++ b/src/audio_core/device/audio_buffers.h @@ -58,6 +58,7 @@ public: if (index < 0) { index += N; } + out_buffers.push_back(buffers[index]); registered_count++; registered_index = (registered_index + 1) % append_limit; @@ -100,7 +101,7 @@ public: } // Check with the backend if this buffer can be released yet. - if (!session.IsBufferConsumed(buffers[index].tag)) { + if (!session.IsBufferConsumed(buffers[index])) { break; } @@ -280,6 +281,16 @@ public: return true; } + u64 GetNextTimestamp() const { + // Iterate backwards through the buffer queue, and take the most recent buffer's end + std::scoped_lock l{lock}; + auto index{appended_index - 1}; + if (index < 0) { + index += append_limit; + } + return buffers[index].end_timestamp; + } + private: /// Buffer lock 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..c71c3a376 100644 --- a/src/audio_core/device/device_session.cpp +++ b/src/audio_core/device/device_session.cpp @@ -7,11 +7,20 @@ #include "audio_core/device/device_session.h" #include "audio_core/sink/sink_stream.h" #include "core/core.h" +#include "core/core_timing.h" #include "core/memory.h" namespace AudioCore { -DeviceSession::DeviceSession(Core::System& system_) : system{system_} {} +using namespace std::literals; +constexpr auto INCREMENT_TIME{5ms}; + +DeviceSession::DeviceSession(Core::System& system_) + : system{system_}, thread_event{Core::Timing::CreateEvent( + "AudioOutSampleTick", + [this](std::uintptr_t, s64 time, std::chrono::nanoseconds) { + return ThreadFunc(); + })} {} DeviceSession::~DeviceSession() { Finalize(); @@ -50,20 +59,21 @@ void DeviceSession::Finalize() { } void DeviceSession::Start() { - stream->SetPlayedSampleCount(played_sample_count); - stream->Start(); + if (stream) { + stream->Start(); + system.CoreTiming().ScheduleLoopingEvent(std::chrono::nanoseconds::zero(), INCREMENT_TIME, + thread_event); + } } void DeviceSession::Stop() { if (stream) { - played_sample_count = stream->GetPlayedSampleCount(); stream->Stop(); + system.CoreTiming().UnscheduleEvent(thread_event, {}); } } void DeviceSession::AppendBuffers(std::span buffers) const { - auto& memory{system.Memory()}; - for (size_t i = 0; i < buffers.size(); i++) { Sink::SinkBuffer new_buffer{ .frames = buffers[i].size / (channel_count * sizeof(s16)), @@ -77,7 +87,7 @@ void DeviceSession::AppendBuffers(std::span buffers) const { stream->AppendBuffer(new_buffer, samples); } else { std::vector samples(buffers[i].size / sizeof(s16)); - memory.ReadBlockUnsafe(buffers[i].samples, samples.data(), buffers[i].size); + system.Memory().ReadBlockUnsafe(buffers[i].samples, samples.data(), buffers[i].size); stream->AppendBuffer(new_buffer, samples); } } @@ -85,17 +95,13 @@ void DeviceSession::AppendBuffers(std::span buffers) const { void DeviceSession::ReleaseBuffer(AudioBuffer& buffer) const { if (type == Sink::StreamType::In) { - auto& memory{system.Memory()}; auto samples{stream->ReleaseBuffer(buffer.size / sizeof(s16))}; - memory.WriteBlockUnsafe(buffer.samples, samples.data(), buffer.size); + system.Memory().WriteBlockUnsafe(buffer.samples, samples.data(), buffer.size); } } -bool DeviceSession::IsBufferConsumed(u64 tag) const { - if (stream) { - return stream->IsBufferConsumed(tag); - } - return true; +bool DeviceSession::IsBufferConsumed(AudioBuffer& buffer) const { + return played_sample_count >= buffer.end_timestamp; } void DeviceSession::SetVolume(f32 volume) const { @@ -105,10 +111,22 @@ void DeviceSession::SetVolume(f32 volume) const { } u64 DeviceSession::GetPlayedSampleCount() const { - if (stream) { - return stream->GetPlayedSampleCount(); + return played_sample_count; +} + +std::optional DeviceSession::ThreadFunc() { + // Add 5ms of samples at a 48K sample rate. + played_sample_count += 48'000 * INCREMENT_TIME / 1s; + if (type == Sink::StreamType::Out) { + system.AudioCore().GetAudioManager().SetEvent(Event::Type::AudioOutManager, true); + } else { + system.AudioCore().GetAudioManager().SetEvent(Event::Type::AudioInManager, true); } - return 0; + return std::nullopt; +} + +void DeviceSession::SetRingSize(u32 ring_size) { + stream->SetRingSize(ring_size); } } // namespace AudioCore diff --git a/src/audio_core/device/device_session.h b/src/audio_core/device/device_session.h index 4a031b765..3414e2c06 100644 --- a/src/audio_core/device/device_session.h +++ b/src/audio_core/device/device_session.h @@ -3,6 +3,9 @@ #pragma once +#include +#include +#include #include #include "audio_core/common/common.h" @@ -11,9 +14,13 @@ namespace Core { class System; -} +namespace Timing { +struct EventType; +} // namespace Timing +} // namespace Core namespace AudioCore { + namespace Sink { class SinkStream; struct SinkBuffer; @@ -70,7 +77,7 @@ public: * @param tag - Unqiue tag of the buffer to check. * @return true if the buffer has been consumed, otherwise false. */ - bool IsBufferConsumed(u64 tag) const; + bool IsBufferConsumed(AudioBuffer& buffer) const; /** * Start this device session, starting the backend stream. @@ -96,6 +103,16 @@ public: */ u64 GetPlayedSampleCount() const; + /* + * CoreTiming callback to increment played_sample_count over time. + */ + std::optional ThreadFunc(); + + /* + * Set the size of the ring buffer. + */ + void SetRingSize(u32 ring_size); + private: /// System Core::System& system; @@ -118,9 +135,13 @@ private: /// Applet resource user id of this device session u64 applet_resource_user_id{}; /// Total number of samples played by this device session - u64 played_sample_count{}; + std::atomic played_sample_count{}; + /// Event increasing the played sample count every 5ms + std::shared_ptr thread_event; /// Is this session initialised? bool initialized{}; + /// Buffer queue + std::vector buffer_queue{}; }; } // namespace AudioCore diff --git a/src/audio_core/in/audio_in_system.cpp b/src/audio_core/in/audio_in_system.cpp index ec5d37ed4..7e80ba03c 100644 --- a/src/audio_core/in/audio_in_system.cpp +++ b/src/audio_core/in/audio_in_system.cpp @@ -93,6 +93,7 @@ Result System::Start() { std::vector buffers_to_flush{}; buffers.RegisterBuffers(buffers_to_flush); session->AppendBuffers(buffers_to_flush); + session->SetRingSize(static_cast(buffers_to_flush.size())); return ResultSuccess; } @@ -112,8 +113,13 @@ bool System::AppendBuffer(const AudioInBuffer& buffer, const u64 tag) { return false; } - AudioBuffer new_buffer{ - .played_timestamp = 0, .samples = buffer.samples, .tag = tag, .size = buffer.size}; + const auto timestamp{buffers.GetNextTimestamp()}; + AudioBuffer new_buffer{.start_timestamp = timestamp, + .end_timestamp = timestamp + buffer.size / (channel_count * sizeof(s16)), + .played_timestamp = 0, + .samples = buffer.samples, + .tag = tag, + .size = buffer.size}; buffers.AppendBuffer(new_buffer); RegisterBuffers(); diff --git a/src/audio_core/out/audio_out_system.cpp b/src/audio_core/out/audio_out_system.cpp index 35afddf06..8941b09a0 100644 --- a/src/audio_core/out/audio_out_system.cpp +++ b/src/audio_core/out/audio_out_system.cpp @@ -92,6 +92,7 @@ Result System::Start() { std::vector buffers_to_flush{}; buffers.RegisterBuffers(buffers_to_flush); session->AppendBuffers(buffers_to_flush); + session->SetRingSize(static_cast(buffers_to_flush.size())); return ResultSuccess; } @@ -111,8 +112,13 @@ bool System::AppendBuffer(const AudioOutBuffer& buffer, u64 tag) { return false; } - AudioBuffer new_buffer{ - .played_timestamp = 0, .samples = buffer.samples, .tag = tag, .size = buffer.size}; + const auto timestamp{buffers.GetNextTimestamp()}; + AudioBuffer new_buffer{.start_timestamp = timestamp, + .end_timestamp = timestamp + buffer.size / (channel_count * sizeof(s16)), + .played_timestamp = 0, + .samples = buffer.samples, + .tag = tag, + .size = buffer.size}; buffers.AppendBuffer(new_buffer); RegisterBuffers(); diff --git a/src/audio_core/renderer/adsp/audio_renderer.cpp b/src/audio_core/renderer/adsp/audio_renderer.cpp index 3967ccfe6..bcd889ecb 100644 --- a/src/audio_core/renderer/adsp/audio_renderer.cpp +++ b/src/audio_core/renderer/adsp/audio_renderer.cpp @@ -106,9 +106,6 @@ void AudioRenderer::Start(AudioRenderer_Mailbox* mailbox_) { mailbox = mailbox_; thread = std::thread(&AudioRenderer::ThreadFunc, this); - for (auto& stream : streams) { - stream->Start(); - } running = true; } @@ -130,6 +127,7 @@ void AudioRenderer::CreateSinkStreams() { std::string name{fmt::format("ADSP_RenderStream-{}", i)}; streams[i] = sink.AcquireSinkStream(system, channels, name, ::AudioCore::Sink::StreamType::Render); + streams[i]->SetRingSize(4); } } @@ -198,11 +196,6 @@ void AudioRenderer::ThreadFunc() { command_list_processor.Process(index) - start_time; } - if (index == 0) { - auto stream{command_list_processor.GetOutputSinkStream()}; - system.AudioCore().SetStreamQueue(stream->GetQueueSize()); - } - const auto end_time{system.CoreTiming().GetClockTicks()}; command_buffer.remaining_command_count = diff --git a/src/audio_core/renderer/behavior/behavior_info.cpp b/src/audio_core/renderer/behavior/behavior_info.cpp index c5d4d66d8..92140aaea 100644 --- a/src/audio_core/renderer/behavior/behavior_info.cpp +++ b/src/audio_core/renderer/behavior/behavior_info.cpp @@ -43,13 +43,15 @@ void BehaviorInfo::AppendError(ErrorInfo& error) { } void BehaviorInfo::CopyErrorInfo(std::span out_errors, u32& out_count) { - auto error_count_{std::min(error_count, MaxErrors)}; - std::memset(out_errors.data(), 0, MaxErrors * sizeof(ErrorInfo)); - - for (size_t i = 0; i < error_count_; i++) { - out_errors[i] = errors[i]; + out_count = std::min(error_count, MaxErrors); + + for (size_t i = 0; i < MaxErrors; i++) { + if (i < out_count) { + out_errors[i] = errors[i]; + } else { + out_errors[i] = {}; + } } - out_count = error_count_; } void BehaviorInfo::UpdateFlags(const Flags flags_) { 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) { out_buffer.tag = reinterpret_cast(samples.data()); stream->AppendBuffer(out_buffer, samples); + + if (stream->IsPaused()) { + stream->Start(); + } } bool DeviceSinkCommand::Verify(const ADSP::CommandListProcessor& processor) { diff --git a/src/audio_core/renderer/system_manager.cpp b/src/audio_core/renderer/system_manager.cpp index b326819ed..bc2dd9e6e 100644 --- a/src/audio_core/renderer/system_manager.cpp +++ b/src/audio_core/renderer/system_manager.cpp @@ -15,8 +15,7 @@ MICROPROFILE_DEFINE(Audio_RenderSystemManager, "Audio", "Render System Manager", MP_RGB(60, 19, 97)); namespace AudioCore::AudioRenderer { -constexpr std::chrono::nanoseconds BaseRenderTime{5'000'000UL}; -constexpr std::chrono::nanoseconds RenderTimeOffset{400'000UL}; +constexpr std::chrono::nanoseconds RENDER_TIME{5'000'000UL}; SystemManager::SystemManager(Core::System& core_) : core{core_}, adsp{core.AudioCore().GetADSP()}, mailbox{adsp.GetRenderMailbox()}, @@ -36,8 +35,8 @@ bool SystemManager::InitializeUnsafe() { if (adsp.Start()) { active = true; thread = std::jthread([this](std::stop_token stop_token) { ThreadFunc(); }); - core.CoreTiming().ScheduleLoopingEvent(std::chrono::nanoseconds(0), - BaseRenderTime - RenderTimeOffset, thread_event); + core.CoreTiming().ScheduleLoopingEvent(std::chrono::nanoseconds(0), RENDER_TIME, + thread_event); } } @@ -121,35 +120,9 @@ void SystemManager::ThreadFunc() { } std::optional SystemManager::ThreadFunc2(s64 time) { - std::optional new_schedule_time{std::nullopt}; - const auto queue_size{core.AudioCore().GetStreamQueue()}; - switch (state) { - case StreamState::Filling: - if (queue_size >= 5) { - new_schedule_time = BaseRenderTime; - state = StreamState::Steady; - } - break; - case StreamState::Steady: - if (queue_size <= 2) { - new_schedule_time = BaseRenderTime - RenderTimeOffset; - state = StreamState::Filling; - } else if (queue_size > 5) { - new_schedule_time = BaseRenderTime + RenderTimeOffset; - state = StreamState::Draining; - } - break; - case StreamState::Draining: - if (queue_size <= 5) { - new_schedule_time = BaseRenderTime; - state = StreamState::Steady; - } - break; - } - update.store(true); update.notify_all(); - return new_schedule_time; + return std::nullopt; } void SystemManager::PauseCallback(bool paused) { diff --git a/src/audio_core/sink/cubeb_sink.cpp b/src/audio_core/sink/cubeb_sink.cpp index 90d049e8e..9ae043611 100644 --- a/src/audio_core/sink/cubeb_sink.cpp +++ b/src/audio_core/sink/cubeb_sink.cpp @@ -1,21 +1,13 @@ // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include -#include #include +#include -#include "audio_core/audio_core.h" -#include "audio_core/audio_event.h" -#include "audio_core/audio_manager.h" +#include "audio_core/common/common.h" #include "audio_core/sink/cubeb_sink.h" #include "audio_core/sink/sink_stream.h" -#include "common/assert.h" -#include "common/fixed_point.h" #include "common/logging/log.h" -#include "common/reader_writer_queue.h" -#include "common/ring_buffer.h" -#include "common/settings.h" #include "core/core.h" #ifdef _WIN32 @@ -42,10 +34,10 @@ public: * @param system_ - Core system. * @param event - Event used only for audio renderer, signalled on buffer consume. */ - CubebSinkStream(cubeb* ctx_, const u32 device_channels_, const u32 system_channels_, + CubebSinkStream(cubeb* ctx_, u32 device_channels_, u32 system_channels_, cubeb_devid output_device, cubeb_devid input_device, const std::string& name_, - const StreamType type_, Core::System& system_) - : ctx{ctx_}, type{type_}, system{system_} { + StreamType type_, Core::System& system_) + : SinkStream(system_, type_), ctx{ctx_} { #ifdef _WIN32 CoInitializeEx(nullptr, COINIT_MULTITHREADED); #endif @@ -79,12 +71,10 @@ public: minimum_latency = std::max(minimum_latency, 256u); - playing_buffer.consumed = true; - - LOG_DEBUG(Service_Audio, - "Opening cubeb stream {} type {} with: rate {} channels {} (system channels {}) " - "latency {}", - name, type, params.rate, params.channels, system_channels, minimum_latency); + LOG_INFO(Service_Audio, + "Opening cubeb stream {} type {} with: rate {} channels {} (system channels {}) " + "latency {}", + name, type, params.rate, params.channels, system_channels, minimum_latency); auto init_error{0}; if (type == StreamType::In) { @@ -111,6 +101,8 @@ public: ~CubebSinkStream() override { LOG_DEBUG(Service_Audio, "Destructing cubeb stream {}", name); + Unstall(); + if (!ctx) { return; } @@ -136,7 +128,7 @@ public: * @param resume - Set to true if this is resuming the stream a previously-active stream. * Default false. */ - void Start(const bool resume = false) override { + void Start(bool resume = false) override { if (!ctx) { return; } @@ -158,6 +150,7 @@ public: * Stop the sink stream. */ void Stop() override { + Unstall(); if (!ctx) { return; } @@ -170,194 +163,7 @@ public: paused = true; } - /** - * Append a new buffer and its samples to a waiting queue to play. - * - * @param buffer - Audio buffer information to be queued. - * @param samples - The s16 samples to be queue for playback. - */ - void AppendBuffer(::AudioCore::Sink::SinkBuffer& buffer, std::vector& samples) override { - if (type == StreamType::In) { - queue.enqueue(buffer); - queued_buffers++; - } else { - constexpr s32 min{std::numeric_limits::min()}; - constexpr s32 max{std::numeric_limits::max()}; - - auto yuzu_volume{Settings::Volume()}; - if (yuzu_volume > 1.0f) { - yuzu_volume = 0.6f + 20 * std::log10(yuzu_volume); - } - auto volume{system_volume * device_volume * yuzu_volume}; - - if (system_channels == 6 && device_channels == 2) { - // We're given 6 channels, but our device only outputs 2, so downmix. - constexpr std::array down_mix_coeff{1.0f, 0.707f, 0.251f, 0.707f}; - - for (u32 read_index = 0, write_index = 0; read_index < samples.size(); - read_index += system_channels, write_index += device_channels) { - const auto left_sample{ - ((Common::FixedPoint<49, 15>( - samples[read_index + static_cast(Channels::FrontLeft)]) * - down_mix_coeff[0] + - samples[read_index + static_cast(Channels::Center)] * - down_mix_coeff[1] + - samples[read_index + static_cast(Channels::LFE)] * - down_mix_coeff[2] + - samples[read_index + static_cast(Channels::BackLeft)] * - down_mix_coeff[3]) * - volume) - .to_int()}; - - const auto right_sample{ - ((Common::FixedPoint<49, 15>( - samples[read_index + static_cast(Channels::FrontRight)]) * - down_mix_coeff[0] + - samples[read_index + static_cast(Channels::Center)] * - down_mix_coeff[1] + - samples[read_index + static_cast(Channels::LFE)] * - down_mix_coeff[2] + - samples[read_index + static_cast(Channels::BackRight)] * - down_mix_coeff[3]) * - volume) - .to_int()}; - - samples[write_index + static_cast(Channels::FrontLeft)] = - static_cast(std::clamp(left_sample, min, max)); - samples[write_index + static_cast(Channels::FrontRight)] = - static_cast(std::clamp(right_sample, min, max)); - } - - samples.resize(samples.size() / system_channels * device_channels); - - } else if (system_channels == 2 && device_channels == 6) { - // We need moar samples! Not all games will provide 6 channel audio. - // TODO: Implement some upmixing here. Currently just passthrough, with other - // channels left as silence. - std::vector new_samples(samples.size() / system_channels * device_channels, 0); - - for (u32 read_index = 0, write_index = 0; read_index < samples.size(); - read_index += system_channels, write_index += device_channels) { - const auto left_sample{static_cast(std::clamp( - static_cast( - static_cast( - samples[read_index + static_cast(Channels::FrontLeft)]) * - volume), - min, max))}; - - new_samples[write_index + static_cast(Channels::FrontLeft)] = left_sample; - - const auto right_sample{static_cast(std::clamp( - static_cast( - static_cast( - samples[read_index + static_cast(Channels::FrontRight)]) * - volume), - min, max))}; - - new_samples[write_index + static_cast(Channels::FrontRight)] = - right_sample; - } - samples = std::move(new_samples); - - } else if (volume != 1.0f) { - for (u32 i = 0; i < samples.size(); i++) { - samples[i] = static_cast(std::clamp( - static_cast(static_cast(samples[i]) * volume), min, max)); - } - } - - samples_buffer.Push(samples); - queue.enqueue(buffer); - queued_buffers++; - } - } - - /** - * Release a buffer. Audio In only, will fill a buffer with recorded samples. - * - * @param num_samples - Maximum number of samples to receive. - * @return Vector of recorded samples. May have fewer than num_samples. - */ - std::vector ReleaseBuffer(const u64 num_samples) override { - static constexpr s32 min = std::numeric_limits::min(); - static constexpr s32 max = std::numeric_limits::max(); - - auto samples{samples_buffer.Pop(num_samples)}; - - // TODO: Up-mix to 6 channels if the game expects it. - // For audio input this is unlikely to ever be the case though. - - // Incoming mic volume seems to always be very quiet, so multiply by an additional 8 here. - // TODO: Play with this and find something that works better. - auto volume{system_volume * device_volume * 8}; - for (u32 i = 0; i < samples.size(); i++) { - samples[i] = static_cast( - std::clamp(static_cast(static_cast(samples[i]) * volume), min, max)); - } - - if (samples.size() < num_samples) { - samples.resize(num_samples, 0); - } - return samples; - } - - /** - * Check if a certain buffer has been consumed (fully played). - * - * @param tag - Unique tag of a buffer to check for. - * @return True if the buffer has been played, otherwise false. - */ - bool IsBufferConsumed(const u64 tag) override { - if (released_buffer.tag == 0) { - if (!released_buffers.try_dequeue(released_buffer)) { - return false; - } - } - - if (released_buffer.tag == tag) { - released_buffer.tag = 0; - return true; - } - return false; - } - - /** - * Empty out the buffer queue. - */ - void ClearQueue() override { - samples_buffer.Pop(); - while (queue.pop()) { - } - while (released_buffers.pop()) { - } - queued_buffers = 0; - released_buffer = {}; - playing_buffer = {}; - playing_buffer.consumed = true; - } - private: - /** - * Signal events back to the audio system that a buffer was played/can be filled. - * - * @param buffer - Consumed audio buffer to be released. - */ - void SignalEvent(const ::AudioCore::Sink::SinkBuffer& buffer) { - auto& manager{system.AudioCore().GetAudioManager()}; - switch (type) { - case StreamType::Out: - released_buffers.enqueue(buffer); - manager.SetEvent(Event::Type::AudioOutManager, true); - break; - case StreamType::In: - released_buffers.enqueue(buffer); - manager.SetEvent(Event::Type::AudioInManager, true); - break; - case StreamType::Render: - break; - } - } - /** * Main callback from Cubeb. Either expects samples from us (audio render/audio out), or will * provide samples to be copied (audio in). @@ -378,106 +184,15 @@ private: const std::size_t num_channels = impl->GetDeviceChannels(); const std::size_t frame_size = num_channels; - const std::size_t frame_size_bytes = frame_size * sizeof(s16); const std::size_t num_frames{static_cast(num_frames_)}; - size_t frames_written{0}; - [[maybe_unused]] bool underrun{false}; if (impl->type == StreamType::In) { - // INPUT std::span input_buffer{reinterpret_cast(in_buff), num_frames * frame_size}; - - while (frames_written < num_frames) { - auto& playing_buffer{impl->playing_buffer}; - - // If the playing buffer has been consumed or has no frames, we need a new one - if (playing_buffer.consumed || playing_buffer.frames == 0) { - if (!impl->queue.try_dequeue(impl->playing_buffer)) { - // If no buffer was available we've underrun, just push the samples and - // continue. - underrun = true; - impl->samples_buffer.Push(&input_buffer[frames_written * frame_size], - (num_frames - frames_written) * frame_size); - frames_written = num_frames; - continue; - } else { - // Successfully got a new buffer, mark the old one as consumed and signal. - impl->queued_buffers--; - impl->SignalEvent(impl->playing_buffer); - } - } - - // Get the minimum frames available between the currently playing buffer, and the - // amount we have left to fill - size_t frames_available{ - std::min(playing_buffer.frames - playing_buffer.frames_played, - num_frames - frames_written)}; - - impl->samples_buffer.Push(&input_buffer[frames_written * frame_size], - frames_available * frame_size); - - frames_written += frames_available; - playing_buffer.frames_played += frames_available; - - // If that's all the frames in the current buffer, add its samples and mark it as - // consumed - if (playing_buffer.frames_played >= playing_buffer.frames) { - impl->AddPlayedSampleCount(playing_buffer.frames_played * num_channels); - impl->playing_buffer.consumed = true; - } - } - - std::memcpy(&impl->last_frame[0], &input_buffer[(frames_written - 1) * frame_size], - frame_size_bytes); + impl->ProcessAudioIn(input_buffer, num_frames); } else { - // OUTPUT std::span output_buffer{reinterpret_cast(out_buff), num_frames * frame_size}; - - while (frames_written < num_frames) { - auto& playing_buffer{impl->playing_buffer}; - - // If the playing buffer has been consumed or has no frames, we need a new one - if (playing_buffer.consumed || playing_buffer.frames == 0) { - if (!impl->queue.try_dequeue(impl->playing_buffer)) { - // If no buffer was available we've underrun, fill the remaining buffer with - // the last written frame and continue. - underrun = true; - for (size_t i = frames_written; i < num_frames; i++) { - std::memcpy(&output_buffer[i * frame_size], &impl->last_frame[0], - frame_size_bytes); - } - frames_written = num_frames; - continue; - } else { - // Successfully got a new buffer, mark the old one as consumed and signal. - impl->queued_buffers--; - impl->SignalEvent(impl->playing_buffer); - } - } - - // Get the minimum frames available between the currently playing buffer, and the - // amount we have left to fill - size_t frames_available{ - std::min(playing_buffer.frames - playing_buffer.frames_played, - num_frames - frames_written)}; - - impl->samples_buffer.Pop(&output_buffer[frames_written * frame_size], - frames_available * frame_size); - - frames_written += frames_available; - playing_buffer.frames_played += frames_available; - - // If that's all the frames in the current buffer, add its samples and mark it as - // consumed - if (playing_buffer.frames_played >= playing_buffer.frames) { - impl->AddPlayedSampleCount(playing_buffer.frames_played * num_channels); - impl->playing_buffer.consumed = true; - } - } - - std::memcpy(&impl->last_frame[0], &output_buffer[(frames_written - 1) * frame_size], - frame_size_bytes); + impl->ProcessAudioOutAndRender(output_buffer, num_frames); } return num_frames_; @@ -490,32 +205,12 @@ private: * @param user_data - Custom data pointer passed along, points to a CubebSinkStream. * @param state - New state of the device. */ - static void StateCallback([[maybe_unused]] cubeb_stream* stream, - [[maybe_unused]] void* user_data, - [[maybe_unused]] cubeb_state state) {} + static void StateCallback(cubeb_stream*, void*, cubeb_state) {} /// Main Cubeb context cubeb* ctx{}; /// Cubeb stream backend cubeb_stream* stream_backend{}; - /// Name of this stream - std::string name{}; - /// Type of this stream - StreamType type; - /// Core system - Core::System& system; - /// Ring buffer of the samples waiting to be played or consumed - Common::RingBuffer samples_buffer; - /// Audio buffers queued and waiting to play - Common::ReaderWriterQueue<::AudioCore::Sink::SinkBuffer> queue; - /// The currently-playing audio buffer - ::AudioCore::Sink::SinkBuffer playing_buffer{}; - /// Audio buffers which have been played and are in queue to be released by the audio system - Common::ReaderWriterQueue<::AudioCore::Sink::SinkBuffer> released_buffers{}; - /// Currently released buffer waiting to be taken by the audio system - ::AudioCore::Sink::SinkBuffer released_buffer{}; - /// The last played (or received) frame of audio, used when the callback underruns - std::array last_frame{}; }; CubebSink::CubebSink(std::string_view target_device_name) { @@ -569,15 +264,15 @@ CubebSink::~CubebSink() { #endif } -SinkStream* CubebSink::AcquireSinkStream(Core::System& system, const u32 system_channels, - const std::string& name, const StreamType type) { +SinkStream* CubebSink::AcquireSinkStream(Core::System& system, u32 system_channels, + const std::string& name, StreamType type) { SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique( ctx, device_channels, system_channels, output_device, input_device, name, type, system)); return stream.get(); } -void CubebSink::CloseStream(const SinkStream* stream) { +void CubebSink::CloseStream(SinkStream* stream) { for (size_t i = 0; i < sink_streams.size(); i++) { if (sink_streams[i].get() == stream) { sink_streams[i].reset(); @@ -611,19 +306,19 @@ f32 CubebSink::GetDeviceVolume() const { return sink_streams[0]->GetDeviceVolume(); } -void CubebSink::SetDeviceVolume(const f32 volume) { +void CubebSink::SetDeviceVolume(f32 volume) { for (auto& stream : sink_streams) { stream->SetDeviceVolume(volume); } } -void CubebSink::SetSystemVolume(const f32 volume) { +void CubebSink::SetSystemVolume(f32 volume) { for (auto& stream : sink_streams) { stream->SetSystemVolume(volume); } } -std::vector ListCubebSinkDevices(const bool capture) { +std::vector ListCubebSinkDevices(bool capture) { std::vector device_list; cubeb* ctx; diff --git a/src/audio_core/sink/cubeb_sink.h b/src/audio_core/sink/cubeb_sink.h index f0f43dfa1..91a6480fa 100644 --- a/src/audio_core/sink/cubeb_sink.h +++ b/src/audio_core/sink/cubeb_sink.h @@ -46,7 +46,7 @@ public: * * @param stream - The stream to close. */ - void CloseStream(const SinkStream* stream) override; + void CloseStream(SinkStream* stream) override; /** * Close all streams. diff --git a/src/audio_core/sink/null_sink.h b/src/audio_core/sink/null_sink.h index 47a342171..eab9c3a0c 100644 --- a/src/audio_core/sink/null_sink.h +++ b/src/audio_core/sink/null_sink.h @@ -3,10 +3,29 @@ #pragma once +#include +#include +#include + #include "audio_core/sink/sink.h" #include "audio_core/sink/sink_stream.h" +namespace Core { +class System; +} // namespace Core + namespace AudioCore::Sink { +class NullSinkStreamImpl final : public SinkStream { +public: + explicit NullSinkStreamImpl(Core::System& system_, StreamType type_) + : SinkStream{system_, type_} {} + ~NullSinkStreamImpl() override {} + void AppendBuffer(SinkBuffer&, std::vector&) override {} + std::vector ReleaseBuffer(u64) override { + return {}; + } +}; + /** * A no-op sink for when no audio out is wanted. */ @@ -15,14 +34,15 @@ public: explicit NullSink(std::string_view) {} ~NullSink() override = default; - SinkStream* AcquireSinkStream([[maybe_unused]] Core::System& system, - [[maybe_unused]] u32 system_channels, - [[maybe_unused]] const std::string& name, - [[maybe_unused]] StreamType type) override { - return &null_sink_stream; + SinkStream* AcquireSinkStream(Core::System& system, u32, const std::string&, + StreamType type) override { + if (null_sink == nullptr) { + null_sink = std::make_unique(system, type); + } + return null_sink.get(); } - void CloseStream([[maybe_unused]] const SinkStream* stream) override {} + void CloseStream(SinkStream*) override {} void CloseStreams() override {} void PauseStreams() override {} void UnpauseStreams() override {} @@ -33,20 +53,7 @@ public: void SetSystemVolume(f32 volume) override {} private: - struct NullSinkStreamImpl final : SinkStream { - void Finalize() override {} - void Start(bool resume = false) override {} - void Stop() override {} - void AppendBuffer([[maybe_unused]] ::AudioCore::Sink::SinkBuffer& buffer, - [[maybe_unused]] std::vector& samples) override {} - std::vector ReleaseBuffer([[maybe_unused]] u64 num_samples) override { - return {}; - } - bool IsBufferConsumed([[maybe_unused]] const u64 tag) { - return true; - } - void ClearQueue() override {} - } null_sink_stream; + SinkStreamPtr null_sink{}; }; } // namespace AudioCore::Sink diff --git a/src/audio_core/sink/sdl2_sink.cpp b/src/audio_core/sink/sdl2_sink.cpp index d6c9ec90d..7ee1dd7cd 100644 --- a/src/audio_core/sink/sdl2_sink.cpp +++ b/src/audio_core/sink/sdl2_sink.cpp @@ -1,20 +1,13 @@ // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include -#include +#include +#include -#include "audio_core/audio_core.h" -#include "audio_core/audio_event.h" -#include "audio_core/audio_manager.h" +#include "audio_core/common/common.h" #include "audio_core/sink/sdl2_sink.h" #include "audio_core/sink/sink_stream.h" -#include "common/assert.h" -#include "common/fixed_point.h" #include "common/logging/log.h" -#include "common/reader_writer_queue.h" -#include "common/ring_buffer.h" -#include "common/settings.h" #include "core/core.h" // Ignore -Wimplicit-fallthrough due to https://github.com/libsdl-org/SDL/issues/4307 @@ -44,10 +37,9 @@ public: * @param system_ - Core system. * @param event - Event used only for audio renderer, signalled on buffer consume. */ - SDLSinkStream(u32 device_channels_, const u32 system_channels_, - const std::string& output_device, const std::string& input_device, - const StreamType type_, Core::System& system_) - : type{type_}, system{system_} { + SDLSinkStream(u32 device_channels_, u32 system_channels_, const std::string& output_device, + const std::string& input_device, StreamType type_, Core::System& system_) + : SinkStream{system_, type_} { system_channels = system_channels_; device_channels = device_channels_; @@ -63,8 +55,6 @@ public: spec.callback = &SDLSinkStream::DataCallback; spec.userdata = this; - playing_buffer.consumed = true; - std::string device_name{output_device}; bool capture{false}; if (type == StreamType::In) { @@ -84,31 +74,30 @@ public: return; } - LOG_DEBUG(Service_Audio, - "Opening sdl stream {} with: rate {} channels {} (system channels {}) " - " samples {}", - device, obtained.freq, obtained.channels, system_channels, obtained.samples); + LOG_INFO(Service_Audio, + "Opening SDL stream {} with: rate {} channels {} (system channels {}) " + " samples {}", + device, obtained.freq, obtained.channels, system_channels, obtained.samples); } /** * Destroy the sink stream. */ ~SDLSinkStream() override { - if (device == 0) { - return; - } - - SDL_CloseAudioDevice(device); + LOG_DEBUG(Service_Audio, "Destructing SDL stream {}", name); + Finalize(); } /** * Finalize the sink stream. */ void Finalize() override { + Unstall(); if (device == 0) { return; } + Stop(); SDL_CloseAudioDevice(device); } @@ -118,7 +107,7 @@ public: * @param resume - Set to true if this is resuming the stream a previously-active stream. * Default false. */ - void Start(const bool resume = false) override { + void Start(bool resume = false) override { if (device == 0) { return; } @@ -135,7 +124,8 @@ public: /** * Stop the sink stream. */ - void Stop() { + void Stop() override { + Unstall(); if (device == 0) { return; } @@ -143,191 +133,7 @@ public: paused = true; } - /** - * Append a new buffer and its samples to a waiting queue to play. - * - * @param buffer - Audio buffer information to be queued. - * @param samples - The s16 samples to be queue for playback. - */ - void AppendBuffer(::AudioCore::Sink::SinkBuffer& buffer, std::vector& samples) override { - if (type == StreamType::In) { - queue.enqueue(buffer); - queued_buffers++; - } else { - constexpr s32 min = std::numeric_limits::min(); - constexpr s32 max = std::numeric_limits::max(); - - auto yuzu_volume{Settings::Volume()}; - auto volume{system_volume * device_volume * yuzu_volume}; - - if (system_channels == 6 && device_channels == 2) { - // We're given 6 channels, but our device only outputs 2, so downmix. - constexpr std::array down_mix_coeff{1.0f, 0.707f, 0.251f, 0.707f}; - - for (u32 read_index = 0, write_index = 0; read_index < samples.size(); - read_index += system_channels, write_index += device_channels) { - const auto left_sample{ - ((Common::FixedPoint<49, 15>( - samples[read_index + static_cast(Channels::FrontLeft)]) * - down_mix_coeff[0] + - samples[read_index + static_cast(Channels::Center)] * - down_mix_coeff[1] + - samples[read_index + static_cast(Channels::LFE)] * - down_mix_coeff[2] + - samples[read_index + static_cast(Channels::BackLeft)] * - down_mix_coeff[3]) * - volume) - .to_int()}; - - const auto right_sample{ - ((Common::FixedPoint<49, 15>( - samples[read_index + static_cast(Channels::FrontRight)]) * - down_mix_coeff[0] + - samples[read_index + static_cast(Channels::Center)] * - down_mix_coeff[1] + - samples[read_index + static_cast(Channels::LFE)] * - down_mix_coeff[2] + - samples[read_index + static_cast(Channels::BackRight)] * - down_mix_coeff[3]) * - volume) - .to_int()}; - - samples[write_index + static_cast(Channels::FrontLeft)] = - static_cast(std::clamp(left_sample, min, max)); - samples[write_index + static_cast(Channels::FrontRight)] = - static_cast(std::clamp(right_sample, min, max)); - } - - samples.resize(samples.size() / system_channels * device_channels); - - } else if (system_channels == 2 && device_channels == 6) { - // We need moar samples! Not all games will provide 6 channel audio. - // TODO: Implement some upmixing here. Currently just passthrough, with other - // channels left as silence. - std::vector new_samples(samples.size() / system_channels * device_channels, 0); - - for (u32 read_index = 0, write_index = 0; read_index < samples.size(); - read_index += system_channels, write_index += device_channels) { - const auto left_sample{static_cast(std::clamp( - static_cast( - static_cast( - samples[read_index + static_cast(Channels::FrontLeft)]) * - volume), - min, max))}; - - new_samples[write_index + static_cast(Channels::FrontLeft)] = left_sample; - - const auto right_sample{static_cast(std::clamp( - static_cast( - static_cast( - samples[read_index + static_cast(Channels::FrontRight)]) * - volume), - min, max))}; - - new_samples[write_index + static_cast(Channels::FrontRight)] = - right_sample; - } - samples = std::move(new_samples); - - } else if (volume != 1.0f) { - for (u32 i = 0; i < samples.size(); i++) { - samples[i] = static_cast(std::clamp( - static_cast(static_cast(samples[i]) * volume), min, max)); - } - } - - samples_buffer.Push(samples); - queue.enqueue(buffer); - queued_buffers++; - } - } - - /** - * Release a buffer. Audio In only, will fill a buffer with recorded samples. - * - * @param num_samples - Maximum number of samples to receive. - * @return Vector of recorded samples. May have fewer than num_samples. - */ - std::vector ReleaseBuffer(const u64 num_samples) override { - static constexpr s32 min = std::numeric_limits::min(); - static constexpr s32 max = std::numeric_limits::max(); - - auto samples{samples_buffer.Pop(num_samples)}; - - // TODO: Up-mix to 6 channels if the game expects it. - // For audio input this is unlikely to ever be the case though. - - // Incoming mic volume seems to always be very quiet, so multiply by an additional 8 here. - // TODO: Play with this and find something that works better. - auto volume{system_volume * device_volume * 8}; - for (u32 i = 0; i < samples.size(); i++) { - samples[i] = static_cast( - std::clamp(static_cast(static_cast(samples[i]) * volume), min, max)); - } - - if (samples.size() < num_samples) { - samples.resize(num_samples, 0); - } - return samples; - } - - /** - * Check if a certain buffer has been consumed (fully played). - * - * @param tag - Unique tag of a buffer to check for. - * @return True if the buffer has been played, otherwise false. - */ - bool IsBufferConsumed(const u64 tag) override { - if (released_buffer.tag == 0) { - if (!released_buffers.try_dequeue(released_buffer)) { - return false; - } - } - - if (released_buffer.tag == tag) { - released_buffer.tag = 0; - return true; - } - return false; - } - - /** - * Empty out the buffer queue. - */ - void ClearQueue() override { - samples_buffer.Pop(); - while (queue.pop()) { - } - while (released_buffers.pop()) { - } - released_buffer = {}; - playing_buffer = {}; - playing_buffer.consumed = true; - queued_buffers = 0; - } - private: - /** - * Signal events back to the audio system that a buffer was played/can be filled. - * - * @param buffer - Consumed audio buffer to be released. - */ - void SignalEvent(const ::AudioCore::Sink::SinkBuffer& buffer) { - auto& manager{system.AudioCore().GetAudioManager()}; - switch (type) { - case StreamType::Out: - released_buffers.enqueue(buffer); - manager.SetEvent(Event::Type::AudioOutManager, true); - break; - case StreamType::In: - released_buffers.enqueue(buffer); - manager.SetEvent(Event::Type::AudioInManager, true); - break; - case StreamType::Render: - break; - } - } - /** * Main callback from SDL. Either expects samples from us (audio render/audio out), or will * provide samples to be copied (audio in). @@ -345,122 +151,20 @@ private: const std::size_t num_channels = impl->GetDeviceChannels(); const std::size_t frame_size = num_channels; - const std::size_t frame_size_bytes = frame_size * sizeof(s16); const std::size_t num_frames{len / num_channels / sizeof(s16)}; - size_t frames_written{0}; - [[maybe_unused]] bool underrun{false}; if (impl->type == StreamType::In) { - std::span input_buffer{reinterpret_cast(stream), num_frames * frame_size}; - - while (frames_written < num_frames) { - auto& playing_buffer{impl->playing_buffer}; - - // If the playing buffer has been consumed or has no frames, we need a new one - if (playing_buffer.consumed || playing_buffer.frames == 0) { - if (!impl->queue.try_dequeue(impl->playing_buffer)) { - // If no buffer was available we've underrun, just push the samples and - // continue. - underrun = true; - impl->samples_buffer.Push(&input_buffer[frames_written * frame_size], - (num_frames - frames_written) * frame_size); - frames_written = num_frames; - continue; - } else { - impl->queued_buffers--; - impl->SignalEvent(impl->playing_buffer); - } - } - - // Get the minimum frames available between the currently playing buffer, and the - // amount we have left to fill - size_t frames_available{ - std::min(playing_buffer.frames - playing_buffer.frames_played, - num_frames - frames_written)}; - - impl->samples_buffer.Push(&input_buffer[frames_written * frame_size], - frames_available * frame_size); - - frames_written += frames_available; - playing_buffer.frames_played += frames_available; - - // If that's all the frames in the current buffer, add its samples and mark it as - // consumed - if (playing_buffer.frames_played >= playing_buffer.frames) { - impl->AddPlayedSampleCount(playing_buffer.frames_played * num_channels); - impl->playing_buffer.consumed = true; - } - } - - std::memcpy(&impl->last_frame[0], &input_buffer[(frames_written - 1) * frame_size], - frame_size_bytes); + std::span input_buffer{reinterpret_cast(stream), + num_frames * frame_size}; + impl->ProcessAudioIn(input_buffer, num_frames); } else { std::span output_buffer{reinterpret_cast(stream), num_frames * frame_size}; - - while (frames_written < num_frames) { - auto& playing_buffer{impl->playing_buffer}; - - // If the playing buffer has been consumed or has no frames, we need a new one - if (playing_buffer.consumed || playing_buffer.frames == 0) { - if (!impl->queue.try_dequeue(impl->playing_buffer)) { - // If no buffer was available we've underrun, fill the remaining buffer with - // the last written frame and continue. - underrun = true; - for (size_t i = frames_written; i < num_frames; i++) { - std::memcpy(&output_buffer[i * frame_size], &impl->last_frame[0], - frame_size_bytes); - } - frames_written = num_frames; - continue; - } else { - impl->queued_buffers--; - impl->SignalEvent(impl->playing_buffer); - } - } - - // Get the minimum frames available between the currently playing buffer, and the - // amount we have left to fill - size_t frames_available{ - std::min(playing_buffer.frames - playing_buffer.frames_played, - num_frames - frames_written)}; - - impl->samples_buffer.Pop(&output_buffer[frames_written * frame_size], - frames_available * frame_size); - - frames_written += frames_available; - playing_buffer.frames_played += frames_available; - - // If that's all the frames in the current buffer, add its samples and mark it as - // consumed - if (playing_buffer.frames_played >= playing_buffer.frames) { - impl->AddPlayedSampleCount(playing_buffer.frames_played * num_channels); - impl->playing_buffer.consumed = true; - } - } - - std::memcpy(&impl->last_frame[0], &output_buffer[(frames_written - 1) * frame_size], - frame_size_bytes); + impl->ProcessAudioOutAndRender(output_buffer, num_frames); } } /// SDL device id of the opened input/output device SDL_AudioDeviceID device{}; - /// Type of this stream - StreamType type; - /// Core system - Core::System& system; - /// Ring buffer of the samples waiting to be played or consumed - Common::RingBuffer samples_buffer; - /// Audio buffers queued and waiting to play - Common::ReaderWriterQueue<::AudioCore::Sink::SinkBuffer> queue; - /// The currently-playing audio buffer - ::AudioCore::Sink::SinkBuffer playing_buffer{}; - /// Audio buffers which have been played and are in queue to be released by the audio system - Common::ReaderWriterQueue<::AudioCore::Sink::SinkBuffer> released_buffers{}; - /// Currently released buffer waiting to be taken by the audio system - ::AudioCore::Sink::SinkBuffer released_buffer{}; - /// The last played (or received) frame of audio, used when the callback underruns - std::array last_frame{}; }; SDLSink::SDLSink(std::string_view target_device_name) { @@ -482,14 +186,14 @@ SDLSink::SDLSink(std::string_view target_device_name) { SDLSink::~SDLSink() = default; -SinkStream* SDLSink::AcquireSinkStream(Core::System& system, const u32 system_channels, - const std::string&, const StreamType type) { +SinkStream* SDLSink::AcquireSinkStream(Core::System& system, u32 system_channels, + const std::string&, StreamType type) { SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique( device_channels, system_channels, output_device, input_device, type, system)); return stream.get(); } -void SDLSink::CloseStream(const SinkStream* stream) { +void SDLSink::CloseStream(SinkStream* stream) { for (size_t i = 0; i < sink_streams.size(); i++) { if (sink_streams[i].get() == stream) { sink_streams[i].reset(); @@ -523,19 +227,19 @@ f32 SDLSink::GetDeviceVolume() const { return sink_streams[0]->GetDeviceVolume(); } -void SDLSink::SetDeviceVolume(const f32 volume) { +void SDLSink::SetDeviceVolume(f32 volume) { for (auto& stream : sink_streams) { stream->SetDeviceVolume(volume); } } -void SDLSink::SetSystemVolume(const f32 volume) { +void SDLSink::SetSystemVolume(f32 volume) { for (auto& stream : sink_streams) { stream->SetSystemVolume(volume); } } -std::vector ListSDLSinkDevices(const bool capture) { +std::vector ListSDLSinkDevices(bool capture) { std::vector device_list; 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..57de9b6c2 100644 --- a/src/audio_core/sink/sdl2_sink.h +++ b/src/audio_core/sink/sdl2_sink.h @@ -44,7 +44,7 @@ public: * * @param stream - The stream to close. */ - void CloseStream(const SinkStream* stream) override; + void CloseStream(SinkStream* stream) override; /** * Close all streams. diff --git a/src/audio_core/sink/sink.h b/src/audio_core/sink/sink.h index 91fe455e4..43d99b62e 100644 --- a/src/audio_core/sink/sink.h +++ b/src/audio_core/sink/sink.h @@ -32,7 +32,7 @@ public: * * @param stream - The stream to close. */ - virtual void CloseStream(const SinkStream* stream) = 0; + virtual void CloseStream(SinkStream* stream) = 0; /** * Close all streams. 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 @@ #include #include #include -#include "audio_core/sink/null_sink.h" + #include "audio_core/sink/sink_details.h" #ifdef HAVE_CUBEB #include "audio_core/sink/cubeb_sink.h" @@ -13,6 +13,7 @@ #ifdef HAVE_SDL2 #include "audio_core/sink/sdl2_sink.h" #endif +#include "audio_core/sink/null_sink.h" #include "common/logging/log.h" namespace AudioCore::Sink { @@ -59,8 +60,7 @@ const SinkDetails& GetOutputSinkDetails(std::string_view sink_id) { if (sink_id == "auto" || iter == std::end(sink_details)) { if (sink_id != "auto") { - LOG_ERROR(Audio, "AudioCore::Sink::GetOutputSinkDetails given invalid sink_id {}", - sink_id); + LOG_ERROR(Audio, "Invalid sink_id {}", sink_id); } // Auto-select. // 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..3770c515d --- /dev/null +++ b/src/audio_core/sink/sink_stream.cpp @@ -0,0 +1,259 @@ +// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include + +#include "audio_core/common/common.h" +#include "audio_core/sink/sink_stream.h" +#include "common/common_types.h" +#include "common/fixed_point.h" +#include "common/settings.h" +#include "core/core.h" + +namespace AudioCore::Sink { + +void SinkStream::AppendBuffer(SinkBuffer& buffer, std::vector& samples) { + if (type == StreamType::In) { + queue.enqueue(buffer); + queued_buffers++; + return; + } + + constexpr s32 min{std::numeric_limits::min()}; + constexpr s32 max{std::numeric_limits::max()}; + + auto yuzu_volume{Settings::Volume()}; + if (yuzu_volume > 1.0f) { + yuzu_volume = 0.6f + 20 * std::log10(yuzu_volume); + } + auto volume{system_volume * device_volume * yuzu_volume}; + + if (system_channels == 6 && device_channels == 2) { + // We're given 6 channels, but our device only outputs 2, so downmix. + constexpr std::array down_mix_coeff{1.0f, 0.707f, 0.251f, 0.707f}; + + for (u32 read_index = 0, write_index = 0; read_index < samples.size(); + read_index += system_channels, write_index += device_channels) { + const auto left_sample{ + ((Common::FixedPoint<49, 15>( + samples[read_index + static_cast(Channels::FrontLeft)]) * + down_mix_coeff[0] + + samples[read_index + static_cast(Channels::Center)] * down_mix_coeff[1] + + samples[read_index + static_cast(Channels::LFE)] * down_mix_coeff[2] + + samples[read_index + static_cast(Channels::BackLeft)] * down_mix_coeff[3]) * + volume) + .to_int()}; + + const auto right_sample{ + ((Common::FixedPoint<49, 15>( + samples[read_index + static_cast(Channels::FrontRight)]) * + down_mix_coeff[0] + + samples[read_index + static_cast(Channels::Center)] * down_mix_coeff[1] + + samples[read_index + static_cast(Channels::LFE)] * down_mix_coeff[2] + + samples[read_index + static_cast(Channels::BackRight)] * down_mix_coeff[3]) * + volume) + .to_int()}; + + samples[write_index + static_cast(Channels::FrontLeft)] = + static_cast(std::clamp(left_sample, min, max)); + samples[write_index + static_cast(Channels::FrontRight)] = + static_cast(std::clamp(right_sample, min, max)); + } + + samples.resize(samples.size() / system_channels * device_channels); + + } else if (system_channels == 2 && device_channels == 6) { + // We need moar samples! Not all games will provide 6 channel audio. + // TODO: Implement some upmixing here. Currently just passthrough, with other + // channels left as silence. + std::vector new_samples(samples.size() / system_channels * device_channels, 0); + + for (u32 read_index = 0, write_index = 0; read_index < samples.size(); + read_index += system_channels, write_index += device_channels) { + const auto left_sample{static_cast(std::clamp( + static_cast( + static_cast(samples[read_index + static_cast(Channels::FrontLeft)]) * + volume), + min, max))}; + + new_samples[write_index + static_cast(Channels::FrontLeft)] = left_sample; + + const auto right_sample{static_cast(std::clamp( + static_cast( + static_cast(samples[read_index + static_cast(Channels::FrontRight)]) * + volume), + min, max))}; + + new_samples[write_index + static_cast(Channels::FrontRight)] = right_sample; + } + samples = std::move(new_samples); + + } else if (volume != 1.0f) { + for (u32 i = 0; i < samples.size(); i++) { + samples[i] = static_cast( + std::clamp(static_cast(static_cast(samples[i]) * volume), min, max)); + } + } + + samples_buffer.Push(samples); + queue.enqueue(buffer); + queued_buffers++; +} + +std::vector SinkStream::ReleaseBuffer(u64 num_samples) { + constexpr s32 min = std::numeric_limits::min(); + constexpr s32 max = std::numeric_limits::max(); + + auto samples{samples_buffer.Pop(num_samples)}; + + // TODO: Up-mix to 6 channels if the game expects it. + // For audio input this is unlikely to ever be the case though. + + // Incoming mic volume seems to always be very quiet, so multiply by an additional 8 here. + // TODO: Play with this and find something that works better. + auto volume{system_volume * device_volume * 8}; + for (u32 i = 0; i < samples.size(); i++) { + samples[i] = static_cast( + std::clamp(static_cast(static_cast(samples[i]) * volume), min, max)); + } + + if (samples.size() < num_samples) { + samples.resize(num_samples, 0); + } + return samples; +} + +void SinkStream::ClearQueue() { + samples_buffer.Pop(); + while (queue.pop()) { + } + queued_buffers = 0; + playing_buffer = {}; + playing_buffer.consumed = true; +} + +void SinkStream::ProcessAudioIn(std::span input_buffer, std::size_t num_frames) { + const std::size_t num_channels = GetDeviceChannels(); + const std::size_t frame_size = num_channels; + const std::size_t frame_size_bytes = frame_size * sizeof(s16); + size_t frames_written{0}; + + if (queued_buffers > max_queue_size) { + Stall(); + } + + while (frames_written < num_frames) { + // If the playing buffer has been consumed or has no frames, we need a new one + if (playing_buffer.consumed || playing_buffer.frames == 0) { + if (!queue.try_dequeue(playing_buffer)) { + // If no buffer was available we've underrun, just push the samples and + // continue. + samples_buffer.Push(&input_buffer[frames_written * frame_size], + (num_frames - frames_written) * frame_size); + frames_written = num_frames; + continue; + } + // Successfully dequeued a new buffer. + queued_buffers--; + } + + // Get the minimum frames available between the currently playing buffer, and the + // amount we have left to fill + size_t frames_available{std::min(playing_buffer.frames - playing_buffer.frames_played, + num_frames - frames_written)}; + + samples_buffer.Push(&input_buffer[frames_written * frame_size], + frames_available * frame_size); + + frames_written += frames_available; + playing_buffer.frames_played += frames_available; + + // If that's all the frames in the current buffer, add its samples and mark it as + // consumed + if (playing_buffer.frames_played >= playing_buffer.frames) { + playing_buffer.consumed = true; + } + } + + std::memcpy(&last_frame[0], &input_buffer[(frames_written - 1) * frame_size], frame_size_bytes); + + if (queued_buffers <= max_queue_size) { + Unstall(); + } +} + +void SinkStream::ProcessAudioOutAndRender(std::span output_buffer, std::size_t num_frames) { + const std::size_t num_channels = GetDeviceChannels(); + const std::size_t frame_size = num_channels; + const std::size_t frame_size_bytes = frame_size * sizeof(s16); + size_t frames_written{0}; + + if (queued_buffers > max_queue_size) { + Stall(); + } + + while (frames_written < num_frames) { + // If the playing buffer has been consumed or has no frames, we need a new one + if (playing_buffer.consumed || playing_buffer.frames == 0) { + if (!queue.try_dequeue(playing_buffer)) { + // If no buffer was available we've underrun, fill the remaining buffer with + // the last written frame and continue. + for (size_t i = frames_written; i < num_frames; i++) { + std::memcpy(&output_buffer[i * frame_size], &last_frame[0], frame_size_bytes); + } + frames_written = num_frames; + continue; + } + // Successfully dequeued a new buffer. + queued_buffers--; + } + + // Get the minimum frames available between the currently playing buffer, and the + // amount we have left to fill + size_t frames_available{std::min(playing_buffer.frames - playing_buffer.frames_played, + num_frames - frames_written)}; + + samples_buffer.Pop(&output_buffer[frames_written * frame_size], + frames_available * frame_size); + + frames_written += frames_available; + playing_buffer.frames_played += frames_available; + + // If that's all the frames in the current buffer, add its samples and mark it as + // consumed + if (playing_buffer.frames_played >= playing_buffer.frames) { + playing_buffer.consumed = true; + } + } + + std::memcpy(&last_frame[0], &output_buffer[(frames_written - 1) * frame_size], + frame_size_bytes); + + if (stalled && queued_buffers <= max_queue_size) { + Unstall(); + } +} + +void SinkStream::Stall() { + if (stalled) { + return; + } + stalled = true; + system.StallProcesses(); +} + +void SinkStream::Unstall() { + if (!stalled) { + return; + } + system.UnstallProcesses(); + stalled = false; +} + +} // namespace AudioCore::Sink diff --git a/src/audio_core/sink/sink_stream.h b/src/audio_core/sink/sink_stream.h index 17ed6593f..db7cff45e 100644 --- a/src/audio_core/sink/sink_stream.h +++ b/src/audio_core/sink/sink_stream.h @@ -3,12 +3,20 @@ #pragma once +#include #include #include +#include #include #include "audio_core/common/common.h" #include "common/common_types.h" +#include "common/reader_writer_queue.h" +#include "common/ring_buffer.h" + +namespace Core { +class System; +} // namespace Core namespace AudioCore::Sink { @@ -34,20 +42,24 @@ struct SinkBuffer { * You should regularly call IsBufferConsumed with the unique SinkBuffer tag to check if the buffer * has been consumed. * - * Since these are a FIFO queue, always check IsBufferConsumed in the same order you appended the - * buffers, skipping a buffer will result in all following buffers to never release. + * Since these are a FIFO queue, IsBufferConsumed must be checked in the same order buffers were + * appended, skipping a buffer will result in the queue getting stuck, and all following buffers to + * never release. * * If the buffers appear to be stuck, you can stop and re-open an IAudioIn/IAudioOut service (this * is what games do), or call ClearQueue to flush all of the buffers without a full restart. */ class SinkStream { public: - virtual ~SinkStream() = default; + explicit SinkStream(Core::System& system_, StreamType type_) : system{system_}, type{type_} {} + virtual ~SinkStream() { + Unstall(); + } /** * Finalize the sink stream. */ - virtual void Finalize() = 0; + virtual void Finalize() {} /** * Start the sink stream. @@ -55,48 +67,19 @@ public: * @param resume - Set to true if this is resuming the stream a previously-active stream. * Default false. */ - virtual void Start(bool resume = false) = 0; + virtual void Start(bool resume = false) {} /** * Stop the sink stream. */ - virtual void Stop() = 0; - - /** - * Append a new buffer and its samples to a waiting queue to play. - * - * @param buffer - Audio buffer information to be queued. - * @param samples - The s16 samples to be queue for playback. - */ - virtual void AppendBuffer(SinkBuffer& buffer, std::vector& samples) = 0; - - /** - * Release a buffer. Audio In only, will fill a buffer with recorded samples. - * - * @param num_samples - Maximum number of samples to receive. - * @return Vector of recorded samples. May have fewer than num_samples. - */ - virtual std::vector ReleaseBuffer(u64 num_samples) = 0; - - /** - * Check if a certain buffer has been consumed (fully played). - * - * @param tag - Unique tag of a buffer to check for. - * @return True if the buffer has been played, otherwise false. - */ - virtual bool IsBufferConsumed(u64 tag) = 0; - - /** - * Empty out the buffer queue. - */ - virtual void ClearQueue() = 0; + virtual void Stop() {} /** * Check if the stream is paused. * * @return True if paused, otherwise false. */ - bool IsPaused() { + bool IsPaused() const { return paused; } @@ -127,34 +110,6 @@ public: return device_channels; } - /** - * Get the total number of samples played by this stream. - * - * @return Number of samples played. - */ - u64 GetPlayedSampleCount() const { - return played_sample_count; - } - - /** - * Set the number of samples played. - * This is started and stopped on system start/stop. - * - * @param played_sample_count_ - Number of samples to set. - */ - void SetPlayedSampleCount(u64 played_sample_count_) { - played_sample_count = played_sample_count_; - } - - /** - * Add to the played sample count. - * - * @param num_samples - Number of samples to add. - */ - void AddPlayedSampleCount(u64 num_samples) { - played_sample_count += num_samples; - } - /** * Get the system volume. * @@ -200,15 +155,65 @@ public: return queued_buffers.load(); } + /** + * Set the maximum buffer queue size. + */ + void SetRingSize(u32 ring_size) { + max_queue_size = ring_size; + } + + /** + * Append a new buffer and its samples to a waiting queue to play. + * + * @param buffer - Audio buffer information to be queued. + * @param samples - The s16 samples to be queue for playback. + */ + virtual void AppendBuffer(SinkBuffer& buffer, std::vector& samples); + + /** + * Release a buffer. Audio In only, will fill a buffer with recorded samples. + * + * @param num_samples - Maximum number of samples to receive. + * @return Vector of recorded samples. May have fewer than num_samples. + */ + virtual std::vector ReleaseBuffer(u64 num_samples); + + /** + * Empty out the buffer queue. + */ + void ClearQueue(); + + /** + * Callback for AudioIn. + * + * @param input_buffer - Input buffer to be filled with samples. + * @param num_frames - Number of frames to be filled. + */ + void ProcessAudioIn(std::span input_buffer, std::size_t num_frames); + + /** + * Callback for AudioOut and AudioRenderer. + * + * @param output_buffer - Output buffer to be filled with samples. + * @param num_frames - Number of frames to be filled. + */ + void ProcessAudioOutAndRender(std::span output_buffer, std::size_t num_frames); + + /** + * Stall core processes if the audio thread falls too far behind. + */ + void Stall(); + + /** + * Unstall core processes. + */ + void Unstall(); + protected: - /// Number of buffers waiting to be played - std::atomic queued_buffers{}; - /// Total samples played by this stream - std::atomic played_sample_count{}; - /// Set by the audio render/in/out system which uses this stream - f32 system_volume{1.0f}; - /// Set via IAudioDevice service calls - f32 device_volume{1.0f}; + /// Core system + Core::System& system; + /// Type of this stream + StreamType type; /// Set by the audio render/in/out systen which uses this stream u32 system_channels{2}; /// Channels supported by hardware @@ -217,6 +222,28 @@ protected: std::atomic paused{true}; /// Was this stream previously playing? std::atomic was_playing{false}; + /// Name of this stream + std::string name{}; + +private: + /// Ring buffer of the samples waiting to be played or consumed + Common::RingBuffer samples_buffer; + /// Audio buffers queued and waiting to play + Common::ReaderWriterQueue queue; + /// The currently-playing audio buffer + SinkBuffer playing_buffer{}; + /// The last played (or received) frame of audio, used when the callback underruns + std::array last_frame{}; + /// Number of buffers waiting to be played + std::atomic queued_buffers{}; + /// The ring size for audio out buffers (usually 4, rarely 2 or 8) + u32 max_queue_size{}; + /// Set by the audio render/in/out system which uses this stream + f32 system_volume{1.0f}; + /// Set via IAudioDevice service calls + f32 device_volume{1.0f}; + /// True if coretiming has been stalled + bool stalled{false}; }; using SinkStreamPtr = std::unique_ptr; 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 { BitField<0, 9, ErrorModule> module; BitField<9, 13, u32> description; + Result() = default; constexpr explicit Result(u32 raw_) : raw(raw_) {} constexpr Result(ErrorModule module_, u32 description_) @@ -130,6 +131,7 @@ union Result { return !IsSuccess(); } }; +static_assert(std::is_trivial_v); [[nodiscard]] constexpr bool operator==(const Result& a, const Result& b) { return a.raw == b.raw; -- cgit v1.2.3 From c7a814f10fd927dfcc3591fada7d6732213892b2 Mon Sep 17 00:00:00 2001 From: Narr the Reg Date: Sat, 3 Sep 2022 13:30:29 -0500 Subject: core: ns: Implement pl:s service --- src/core/CMakeLists.txt | 4 +- src/core/file_sys/system_archive/shared_font.cpp | 2 +- .../hle/service/am/applets/applet_web_browser.cpp | 2 +- .../hle/service/ns/iplatform_service_manager.cpp | 299 +++++++++++++++++++++ .../hle/service/ns/iplatform_service_manager.h | 58 ++++ src/core/hle/service/ns/ns.cpp | 5 +- src/core/hle/service/ns/pl_u.cpp | 299 --------------------- src/core/hle/service/ns/pl_u.h | 58 ---- 8 files changed, 364 insertions(+), 363 deletions(-) create mode 100644 src/core/hle/service/ns/iplatform_service_manager.cpp create mode 100644 src/core/hle/service/ns/iplatform_service_manager.h delete mode 100644 src/core/hle/service/ns/pl_u.cpp delete mode 100644 src/core/hle/service/ns/pl_u.h (limited to 'src/core') diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 8db9a3c65..4137ec9a7 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -540,14 +540,14 @@ add_library(core STATIC hle/service/npns/npns.cpp hle/service/npns/npns.h hle/service/ns/errors.h + hle/service/ns/iplatform_service_manager.cpp + hle/service/ns/iplatform_service_manager.h hle/service/ns/language.cpp hle/service/ns/language.h hle/service/ns/ns.cpp hle/service/ns/ns.h hle/service/ns/pdm_qry.cpp hle/service/ns/pdm_qry.h - hle/service/ns/pl_u.cpp - hle/service/ns/pl_u.h hle/service/nvdrv/devices/nvdevice.h hle/service/nvdrv/devices/nvdisp_disp0.cpp hle/service/nvdrv/devices/nvdisp_disp0.h diff --git a/src/core/file_sys/system_archive/shared_font.cpp b/src/core/file_sys/system_archive/shared_font.cpp index f841988ff..3210583f0 100644 --- a/src/core/file_sys/system_archive/shared_font.cpp +++ b/src/core/file_sys/system_archive/shared_font.cpp @@ -9,7 +9,7 @@ #include "core/file_sys/system_archive/data/font_standard.h" #include "core/file_sys/system_archive/shared_font.h" #include "core/file_sys/vfs_vector.h" -#include "core/hle/service/ns/pl_u.h" +#include "core/hle/service/ns/iplatform_service_manager.h" namespace FileSys::SystemArchive { diff --git a/src/core/hle/service/am/applets/applet_web_browser.cpp b/src/core/hle/service/am/applets/applet_web_browser.cpp index 4b804b78c..14aa6f69e 100644 --- a/src/core/hle/service/am/applets/applet_web_browser.cpp +++ b/src/core/hle/service/am/applets/applet_web_browser.cpp @@ -21,7 +21,7 @@ #include "core/hle/service/am/am.h" #include "core/hle/service/am/applets/applet_web_browser.h" #include "core/hle/service/filesystem/filesystem.h" -#include "core/hle/service/ns/pl_u.h" +#include "core/hle/service/ns/iplatform_service_manager.h" #include "core/loader/loader.h" namespace Service::AM::Applets { diff --git a/src/core/hle/service/ns/iplatform_service_manager.cpp b/src/core/hle/service/ns/iplatform_service_manager.cpp new file mode 100644 index 000000000..fd047ff26 --- /dev/null +++ b/src/core/hle/service/ns/iplatform_service_manager.cpp @@ -0,0 +1,299 @@ +// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include + +#include "common/assert.h" +#include "common/common_types.h" +#include "common/logging/log.h" +#include "common/swap.h" +#include "core/core.h" +#include "core/file_sys/content_archive.h" +#include "core/file_sys/nca_metadata.h" +#include "core/file_sys/registered_cache.h" +#include "core/file_sys/romfs.h" +#include "core/file_sys/system_archive/system_archive.h" +#include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/k_shared_memory.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/physical_memory.h" +#include "core/hle/service/filesystem/filesystem.h" +#include "core/hle/service/ns/iplatform_service_manager.h" + +namespace Service::NS { + +struct FontRegion { + u32 offset; + u32 size; +}; + +// The below data is specific to shared font data dumped from Switch on f/w 2.2 +// Virtual address and offsets/sizes likely will vary by dump +[[maybe_unused]] constexpr VAddr SHARED_FONT_MEM_VADDR{0x00000009d3016000ULL}; +constexpr u32 EXPECTED_RESULT{0x7f9a0218}; // What we expect the decrypted bfttf first 4 bytes to be +constexpr u32 EXPECTED_MAGIC{0x36f81a1e}; // What we expect the encrypted bfttf first 4 bytes to be +constexpr u64 SHARED_FONT_MEM_SIZE{0x1100000}; +constexpr FontRegion EMPTY_REGION{0, 0}; + +enum class LoadState : u32 { + Loading = 0, + Done = 1, +}; + +static void DecryptSharedFont(const std::vector& input, Kernel::PhysicalMemory& output, + std::size_t& offset) { + ASSERT_MSG(offset + (input.size() * sizeof(u32)) < SHARED_FONT_MEM_SIZE, + "Shared fonts exceeds 17mb!"); + ASSERT_MSG(input[0] == EXPECTED_MAGIC, "Failed to derive key, unexpected magic number"); + + const u32 KEY = input[0] ^ EXPECTED_RESULT; // Derive key using an inverse xor + std::vector transformed_font(input.size()); + // TODO(ogniK): Figure out a better way to do this + std::transform(input.begin(), input.end(), transformed_font.begin(), + [&KEY](u32 font_data) { return Common::swap32(font_data ^ KEY); }); + transformed_font[1] = Common::swap32(transformed_font[1]) ^ KEY; // "re-encrypt" the size + std::memcpy(output.data() + offset, transformed_font.data(), + transformed_font.size() * sizeof(u32)); + offset += transformed_font.size() * sizeof(u32); +} + +void DecryptSharedFontToTTF(const std::vector& input, std::vector& output) { + ASSERT_MSG(input[0] == EXPECTED_MAGIC, "Failed to derive key, unexpected magic number"); + + if (input.size() < 2) { + LOG_ERROR(Service_NS, "Input font is empty"); + return; + } + + const u32 KEY = input[0] ^ EXPECTED_RESULT; // Derive key using an inverse xor + std::vector transformed_font(input.size()); + // TODO(ogniK): Figure out a better way to do this + std::transform(input.begin(), input.end(), transformed_font.begin(), + [&KEY](u32 font_data) { return Common::swap32(font_data ^ KEY); }); + std::memcpy(output.data(), transformed_font.data() + 2, + (transformed_font.size() - 2) * sizeof(u32)); +} + +void EncryptSharedFont(const std::vector& input, std::vector& output, + std::size_t& offset) { + ASSERT_MSG(offset + (input.size() * sizeof(u32)) < SHARED_FONT_MEM_SIZE, + "Shared fonts exceeds 17mb!"); + + const auto key = Common::swap32(EXPECTED_RESULT ^ EXPECTED_MAGIC); + std::vector transformed_font(input.size() + 2); + transformed_font[0] = Common::swap32(EXPECTED_MAGIC); + transformed_font[1] = Common::swap32(static_cast(input.size() * sizeof(u32))) ^ key; + std::transform(input.begin(), input.end(), transformed_font.begin() + 2, + [key](u32 in) { return in ^ key; }); + std::memcpy(output.data() + offset, transformed_font.data(), + transformed_font.size() * sizeof(u32)); + offset += transformed_font.size() * sizeof(u32); +} + +// Helper function to make BuildSharedFontsRawRegions a bit nicer +static u32 GetU32Swapped(const u8* data) { + u32 value; + std::memcpy(&value, data, sizeof(value)); + return Common::swap32(value); +} + +struct IPlatformServiceManager::Impl { + const FontRegion& GetSharedFontRegion(std::size_t index) const { + if (index >= shared_font_regions.size() || shared_font_regions.empty()) { + // No font fallback + return EMPTY_REGION; + } + return shared_font_regions.at(index); + } + + void BuildSharedFontsRawRegions(const Kernel::PhysicalMemory& input) { + // As we can derive the xor key we can just populate the offsets + // based on the shared memory dump + unsigned cur_offset = 0; + + for (std::size_t i = 0; i < SHARED_FONTS.size(); i++) { + // Out of shared fonts/invalid font + if (GetU32Swapped(input.data() + cur_offset) != EXPECTED_RESULT) { + break; + } + + // Derive key withing inverse xor + const u32 KEY = GetU32Swapped(input.data() + cur_offset) ^ EXPECTED_MAGIC; + const u32 SIZE = GetU32Swapped(input.data() + cur_offset + 4) ^ KEY; + shared_font_regions.push_back(FontRegion{cur_offset + 8, SIZE}); + cur_offset += SIZE + 8; + } + } + + /// Backing memory for the shared font data + std::shared_ptr shared_font; + + // Automatically populated based on shared_fonts dump or system archives. + std::vector shared_font_regions; +}; + +IPlatformServiceManager::IPlatformServiceManager(Core::System& system_, const char* service_name_) + : ServiceFramework{system_, service_name_}, impl{std::make_unique()} { + // clang-format off + static const FunctionInfo functions[] = { + {0, &IPlatformServiceManager::RequestLoad, "RequestLoad"}, + {1, &IPlatformServiceManager::GetLoadState, "GetLoadState"}, + {2, &IPlatformServiceManager::GetSize, "GetSize"}, + {3, &IPlatformServiceManager::GetSharedMemoryAddressOffset, "GetSharedMemoryAddressOffset"}, + {4, &IPlatformServiceManager::GetSharedMemoryNativeHandle, "GetSharedMemoryNativeHandle"}, + {5, &IPlatformServiceManager::GetSharedFontInOrderOfPriority, "GetSharedFontInOrderOfPriority"}, + {6, nullptr, "GetSharedFontInOrderOfPriorityForSystem"}, + {100, nullptr, "RequestApplicationFunctionAuthorization"}, + {101, nullptr, "RequestApplicationFunctionAuthorizationByProcessId"}, + {102, nullptr, "RequestApplicationFunctionAuthorizationByApplicationId"}, + {103, nullptr, "RefreshApplicationFunctionBlackListDebugRecord"}, + {104, nullptr, "RequestApplicationFunctionAuthorizationByProgramId"}, + {105, nullptr, "GetFunctionBlackListSystemVersionToAuthorize"}, + {106, nullptr, "GetFunctionBlackListVersion"}, + {1000, nullptr, "LoadNgWordDataForPlatformRegionChina"}, + {1001, nullptr, "GetNgWordDataSizeForPlatformRegionChina"}, + }; + // clang-format on + RegisterHandlers(functions); + + auto& fsc = system.GetFileSystemController(); + + // Attempt to load shared font data from disk + const auto* nand = fsc.GetSystemNANDContents(); + std::size_t offset = 0; + // Rebuild shared fonts from data ncas or synthesize + + impl->shared_font = std::make_shared(SHARED_FONT_MEM_SIZE); + for (auto font : SHARED_FONTS) { + FileSys::VirtualFile romfs; + const auto nca = + nand->GetEntry(static_cast(font.first), FileSys::ContentRecordType::Data); + if (nca) { + romfs = nca->GetRomFS(); + } + + if (!romfs) { + romfs = FileSys::SystemArchive::SynthesizeSystemArchive(static_cast(font.first)); + } + + if (!romfs) { + LOG_ERROR(Service_NS, "Failed to find or synthesize {:016X}! Skipping", font.first); + continue; + } + + const auto extracted_romfs = FileSys::ExtractRomFS(romfs); + if (!extracted_romfs) { + LOG_ERROR(Service_NS, "Failed to extract RomFS for {:016X}! Skipping", font.first); + continue; + } + const auto font_fp = extracted_romfs->GetFile(font.second); + if (!font_fp) { + LOG_ERROR(Service_NS, "{:016X} has no file \"{}\"! Skipping", font.first, font.second); + continue; + } + std::vector font_data_u32(font_fp->GetSize() / sizeof(u32)); + font_fp->ReadBytes(font_data_u32.data(), font_fp->GetSize()); + // We need to be BigEndian as u32s for the xor encryption + std::transform(font_data_u32.begin(), font_data_u32.end(), font_data_u32.begin(), + Common::swap32); + // Font offset and size do not account for the header + const FontRegion region{static_cast(offset + 8), + static_cast((font_data_u32.size() * sizeof(u32)) - 8)}; + DecryptSharedFont(font_data_u32, *impl->shared_font, offset); + impl->shared_font_regions.push_back(region); + } +} + +IPlatformServiceManager::~IPlatformServiceManager() = default; + +void IPlatformServiceManager::RequestLoad(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const u32 shared_font_type{rp.Pop()}; + // Games don't call this so all fonts should be loaded + LOG_DEBUG(Service_NS, "called, shared_font_type={}", shared_font_type); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); +} + +void IPlatformServiceManager::GetLoadState(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const u32 font_id{rp.Pop()}; + LOG_DEBUG(Service_NS, "called, font_id={}", font_id); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(static_cast(LoadState::Done)); +} + +void IPlatformServiceManager::GetSize(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const u32 font_id{rp.Pop()}; + LOG_DEBUG(Service_NS, "called, font_id={}", font_id); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(impl->GetSharedFontRegion(font_id).size); +} + +void IPlatformServiceManager::GetSharedMemoryAddressOffset(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const u32 font_id{rp.Pop()}; + LOG_DEBUG(Service_NS, "called, font_id={}", font_id); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(impl->GetSharedFontRegion(font_id).offset); +} + +void IPlatformServiceManager::GetSharedMemoryNativeHandle(Kernel::HLERequestContext& ctx) { + // Map backing memory for the font data + LOG_DEBUG(Service_NS, "called"); + + // Create shared font memory object + std::memcpy(kernel.GetFontSharedMem().GetPointer(), impl->shared_font->data(), + impl->shared_font->size()); + + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(ResultSuccess); + rb.PushCopyObjects(&kernel.GetFontSharedMem()); +} + +void IPlatformServiceManager::GetSharedFontInOrderOfPriority(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const u64 language_code{rp.Pop()}; // TODO(ogniK): Find out what this is used for + LOG_DEBUG(Service_NS, "called, language_code={:X}", language_code); + + IPC::ResponseBuilder rb{ctx, 4}; + std::vector font_codes; + std::vector font_offsets; + std::vector font_sizes; + + // TODO(ogniK): Have actual priority order + for (std::size_t i = 0; i < impl->shared_font_regions.size(); i++) { + font_codes.push_back(static_cast(i)); + auto region = impl->GetSharedFontRegion(i); + font_offsets.push_back(region.offset); + font_sizes.push_back(region.size); + } + + // Resize buffers if game requests smaller size output. + font_codes.resize( + std::min(font_codes.size(), ctx.GetWriteBufferSize(0) / sizeof(u32))); + font_offsets.resize( + std::min(font_offsets.size(), ctx.GetWriteBufferSize(1) / sizeof(u32))); + font_sizes.resize( + std::min(font_sizes.size(), ctx.GetWriteBufferSize(2) / sizeof(u32))); + + ctx.WriteBuffer(font_codes, 0); + ctx.WriteBuffer(font_offsets, 1); + ctx.WriteBuffer(font_sizes, 2); + + rb.Push(ResultSuccess); + rb.Push(static_cast(LoadState::Done)); // Fonts Loaded + rb.Push(static_cast(font_codes.size())); +} + +} // namespace Service::NS diff --git a/src/core/hle/service/ns/iplatform_service_manager.h b/src/core/hle/service/ns/iplatform_service_manager.h new file mode 100644 index 000000000..ed6eda89f --- /dev/null +++ b/src/core/hle/service/ns/iplatform_service_manager.h @@ -0,0 +1,58 @@ +// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include "core/hle/service/service.h" + +namespace Service { + +namespace FileSystem { +class FileSystemController; +} // namespace FileSystem + +namespace NS { + +enum class FontArchives : u64 { + Extension = 0x0100000000000810, + Standard = 0x0100000000000811, + Korean = 0x0100000000000812, + ChineseTraditional = 0x0100000000000813, + ChineseSimple = 0x0100000000000814, +}; + +constexpr std::array, 7> SHARED_FONTS{ + std::make_pair(FontArchives::Standard, "nintendo_udsg-r_std_003.bfttf"), + std::make_pair(FontArchives::ChineseSimple, "nintendo_udsg-r_org_zh-cn_003.bfttf"), + std::make_pair(FontArchives::ChineseSimple, "nintendo_udsg-r_ext_zh-cn_003.bfttf"), + std::make_pair(FontArchives::ChineseTraditional, "nintendo_udjxh-db_zh-tw_003.bfttf"), + std::make_pair(FontArchives::Korean, "nintendo_udsg-r_ko_003.bfttf"), + std::make_pair(FontArchives::Extension, "nintendo_ext_003.bfttf"), + std::make_pair(FontArchives::Extension, "nintendo_ext2_003.bfttf"), +}; + +void DecryptSharedFontToTTF(const std::vector& input, std::vector& output); +void EncryptSharedFont(const std::vector& input, std::vector& output, std::size_t& offset); + +class IPlatformServiceManager final : public ServiceFramework { +public: + explicit IPlatformServiceManager(Core::System& system_, const char* service_name_); + ~IPlatformServiceManager() override; + +private: + void RequestLoad(Kernel::HLERequestContext& ctx); + void GetLoadState(Kernel::HLERequestContext& ctx); + void GetSize(Kernel::HLERequestContext& ctx); + void GetSharedMemoryAddressOffset(Kernel::HLERequestContext& ctx); + void GetSharedMemoryNativeHandle(Kernel::HLERequestContext& ctx); + void GetSharedFontInOrderOfPriority(Kernel::HLERequestContext& ctx); + + struct Impl; + std::unique_ptr impl; +}; + +} // namespace NS + +} // namespace Service diff --git a/src/core/hle/service/ns/ns.cpp b/src/core/hle/service/ns/ns.cpp index aafc8fe03..f7318c3cb 100644 --- a/src/core/hle/service/ns/ns.cpp +++ b/src/core/hle/service/ns/ns.cpp @@ -9,10 +9,10 @@ #include "core/file_sys/vfs.h" #include "core/hle/ipc_helpers.h" #include "core/hle/service/ns/errors.h" +#include "core/hle/service/ns/iplatform_service_manager.h" #include "core/hle/service/ns/language.h" #include "core/hle/service/ns/ns.h" #include "core/hle/service/ns/pdm_qry.h" -#include "core/hle/service/ns/pl_u.h" #include "core/hle/service/set/set.h" namespace Service::NS { @@ -764,7 +764,8 @@ void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system std::make_shared(system)->InstallAsService(service_manager); - std::make_shared(system)->InstallAsService(service_manager); + std::make_shared(system, "pl:s")->InstallAsService(service_manager); + std::make_shared(system, "pl:u")->InstallAsService(service_manager); } } // namespace Service::NS diff --git a/src/core/hle/service/ns/pl_u.cpp b/src/core/hle/service/ns/pl_u.cpp deleted file mode 100644 index cc11f3e08..000000000 --- a/src/core/hle/service/ns/pl_u.cpp +++ /dev/null @@ -1,299 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include -#include -#include - -#include "common/assert.h" -#include "common/common_types.h" -#include "common/logging/log.h" -#include "common/swap.h" -#include "core/core.h" -#include "core/file_sys/content_archive.h" -#include "core/file_sys/nca_metadata.h" -#include "core/file_sys/registered_cache.h" -#include "core/file_sys/romfs.h" -#include "core/file_sys/system_archive/system_archive.h" -#include "core/hle/ipc_helpers.h" -#include "core/hle/kernel/k_shared_memory.h" -#include "core/hle/kernel/kernel.h" -#include "core/hle/kernel/physical_memory.h" -#include "core/hle/service/filesystem/filesystem.h" -#include "core/hle/service/ns/pl_u.h" - -namespace Service::NS { - -struct FontRegion { - u32 offset; - u32 size; -}; - -// The below data is specific to shared font data dumped from Switch on f/w 2.2 -// Virtual address and offsets/sizes likely will vary by dump -[[maybe_unused]] constexpr VAddr SHARED_FONT_MEM_VADDR{0x00000009d3016000ULL}; -constexpr u32 EXPECTED_RESULT{0x7f9a0218}; // What we expect the decrypted bfttf first 4 bytes to be -constexpr u32 EXPECTED_MAGIC{0x36f81a1e}; // What we expect the encrypted bfttf first 4 bytes to be -constexpr u64 SHARED_FONT_MEM_SIZE{0x1100000}; -constexpr FontRegion EMPTY_REGION{0, 0}; - -enum class LoadState : u32 { - Loading = 0, - Done = 1, -}; - -static void DecryptSharedFont(const std::vector& input, Kernel::PhysicalMemory& output, - std::size_t& offset) { - ASSERT_MSG(offset + (input.size() * sizeof(u32)) < SHARED_FONT_MEM_SIZE, - "Shared fonts exceeds 17mb!"); - ASSERT_MSG(input[0] == EXPECTED_MAGIC, "Failed to derive key, unexpected magic number"); - - const u32 KEY = input[0] ^ EXPECTED_RESULT; // Derive key using an inverse xor - std::vector transformed_font(input.size()); - // TODO(ogniK): Figure out a better way to do this - std::transform(input.begin(), input.end(), transformed_font.begin(), - [&KEY](u32 font_data) { return Common::swap32(font_data ^ KEY); }); - transformed_font[1] = Common::swap32(transformed_font[1]) ^ KEY; // "re-encrypt" the size - std::memcpy(output.data() + offset, transformed_font.data(), - transformed_font.size() * sizeof(u32)); - offset += transformed_font.size() * sizeof(u32); -} - -void DecryptSharedFontToTTF(const std::vector& input, std::vector& output) { - ASSERT_MSG(input[0] == EXPECTED_MAGIC, "Failed to derive key, unexpected magic number"); - - if (input.size() < 2) { - LOG_ERROR(Service_NS, "Input font is empty"); - return; - } - - const u32 KEY = input[0] ^ EXPECTED_RESULT; // Derive key using an inverse xor - std::vector transformed_font(input.size()); - // TODO(ogniK): Figure out a better way to do this - std::transform(input.begin(), input.end(), transformed_font.begin(), - [&KEY](u32 font_data) { return Common::swap32(font_data ^ KEY); }); - std::memcpy(output.data(), transformed_font.data() + 2, - (transformed_font.size() - 2) * sizeof(u32)); -} - -void EncryptSharedFont(const std::vector& input, std::vector& output, - std::size_t& offset) { - ASSERT_MSG(offset + (input.size() * sizeof(u32)) < SHARED_FONT_MEM_SIZE, - "Shared fonts exceeds 17mb!"); - - const auto key = Common::swap32(EXPECTED_RESULT ^ EXPECTED_MAGIC); - std::vector transformed_font(input.size() + 2); - transformed_font[0] = Common::swap32(EXPECTED_MAGIC); - transformed_font[1] = Common::swap32(static_cast(input.size() * sizeof(u32))) ^ key; - std::transform(input.begin(), input.end(), transformed_font.begin() + 2, - [key](u32 in) { return in ^ key; }); - std::memcpy(output.data() + offset, transformed_font.data(), - transformed_font.size() * sizeof(u32)); - offset += transformed_font.size() * sizeof(u32); -} - -// Helper function to make BuildSharedFontsRawRegions a bit nicer -static u32 GetU32Swapped(const u8* data) { - u32 value; - std::memcpy(&value, data, sizeof(value)); - return Common::swap32(value); -} - -struct PL_U::Impl { - const FontRegion& GetSharedFontRegion(std::size_t index) const { - if (index >= shared_font_regions.size() || shared_font_regions.empty()) { - // No font fallback - return EMPTY_REGION; - } - return shared_font_regions.at(index); - } - - void BuildSharedFontsRawRegions(const Kernel::PhysicalMemory& input) { - // As we can derive the xor key we can just populate the offsets - // based on the shared memory dump - unsigned cur_offset = 0; - - for (std::size_t i = 0; i < SHARED_FONTS.size(); i++) { - // Out of shared fonts/invalid font - if (GetU32Swapped(input.data() + cur_offset) != EXPECTED_RESULT) { - break; - } - - // Derive key withing inverse xor - const u32 KEY = GetU32Swapped(input.data() + cur_offset) ^ EXPECTED_MAGIC; - const u32 SIZE = GetU32Swapped(input.data() + cur_offset + 4) ^ KEY; - shared_font_regions.push_back(FontRegion{cur_offset + 8, SIZE}); - cur_offset += SIZE + 8; - } - } - - /// Backing memory for the shared font data - std::shared_ptr shared_font; - - // Automatically populated based on shared_fonts dump or system archives. - std::vector shared_font_regions; -}; - -PL_U::PL_U(Core::System& system_) - : ServiceFramework{system_, "pl:u"}, impl{std::make_unique()} { - // clang-format off - static const FunctionInfo functions[] = { - {0, &PL_U::RequestLoad, "RequestLoad"}, - {1, &PL_U::GetLoadState, "GetLoadState"}, - {2, &PL_U::GetSize, "GetSize"}, - {3, &PL_U::GetSharedMemoryAddressOffset, "GetSharedMemoryAddressOffset"}, - {4, &PL_U::GetSharedMemoryNativeHandle, "GetSharedMemoryNativeHandle"}, - {5, &PL_U::GetSharedFontInOrderOfPriority, "GetSharedFontInOrderOfPriority"}, - {6, nullptr, "GetSharedFontInOrderOfPriorityForSystem"}, - {100, nullptr, "RequestApplicationFunctionAuthorization"}, - {101, nullptr, "RequestApplicationFunctionAuthorizationByProcessId"}, - {102, nullptr, "RequestApplicationFunctionAuthorizationByApplicationId"}, - {103, nullptr, "RefreshApplicationFunctionBlackListDebugRecord"}, - {104, nullptr, "RequestApplicationFunctionAuthorizationByProgramId"}, - {105, nullptr, "GetFunctionBlackListSystemVersionToAuthorize"}, - {106, nullptr, "GetFunctionBlackListVersion"}, - {1000, nullptr, "LoadNgWordDataForPlatformRegionChina"}, - {1001, nullptr, "GetNgWordDataSizeForPlatformRegionChina"}, - }; - // clang-format on - RegisterHandlers(functions); - - auto& fsc = system.GetFileSystemController(); - - // Attempt to load shared font data from disk - const auto* nand = fsc.GetSystemNANDContents(); - std::size_t offset = 0; - // Rebuild shared fonts from data ncas or synthesize - - impl->shared_font = std::make_shared(SHARED_FONT_MEM_SIZE); - for (auto font : SHARED_FONTS) { - FileSys::VirtualFile romfs; - const auto nca = - nand->GetEntry(static_cast(font.first), FileSys::ContentRecordType::Data); - if (nca) { - romfs = nca->GetRomFS(); - } - - if (!romfs) { - romfs = FileSys::SystemArchive::SynthesizeSystemArchive(static_cast(font.first)); - } - - if (!romfs) { - LOG_ERROR(Service_NS, "Failed to find or synthesize {:016X}! Skipping", font.first); - continue; - } - - const auto extracted_romfs = FileSys::ExtractRomFS(romfs); - if (!extracted_romfs) { - LOG_ERROR(Service_NS, "Failed to extract RomFS for {:016X}! Skipping", font.first); - continue; - } - const auto font_fp = extracted_romfs->GetFile(font.second); - if (!font_fp) { - LOG_ERROR(Service_NS, "{:016X} has no file \"{}\"! Skipping", font.first, font.second); - continue; - } - std::vector font_data_u32(font_fp->GetSize() / sizeof(u32)); - font_fp->ReadBytes(font_data_u32.data(), font_fp->GetSize()); - // We need to be BigEndian as u32s for the xor encryption - std::transform(font_data_u32.begin(), font_data_u32.end(), font_data_u32.begin(), - Common::swap32); - // Font offset and size do not account for the header - const FontRegion region{static_cast(offset + 8), - static_cast((font_data_u32.size() * sizeof(u32)) - 8)}; - DecryptSharedFont(font_data_u32, *impl->shared_font, offset); - impl->shared_font_regions.push_back(region); - } -} - -PL_U::~PL_U() = default; - -void PL_U::RequestLoad(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const u32 shared_font_type{rp.Pop()}; - // Games don't call this so all fonts should be loaded - LOG_DEBUG(Service_NS, "called, shared_font_type={}", shared_font_type); - - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); -} - -void PL_U::GetLoadState(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const u32 font_id{rp.Pop()}; - LOG_DEBUG(Service_NS, "called, font_id={}", font_id); - - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); - rb.Push(static_cast(LoadState::Done)); -} - -void PL_U::GetSize(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const u32 font_id{rp.Pop()}; - LOG_DEBUG(Service_NS, "called, font_id={}", font_id); - - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); - rb.Push(impl->GetSharedFontRegion(font_id).size); -} - -void PL_U::GetSharedMemoryAddressOffset(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const u32 font_id{rp.Pop()}; - LOG_DEBUG(Service_NS, "called, font_id={}", font_id); - - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); - rb.Push(impl->GetSharedFontRegion(font_id).offset); -} - -void PL_U::GetSharedMemoryNativeHandle(Kernel::HLERequestContext& ctx) { - // Map backing memory for the font data - LOG_DEBUG(Service_NS, "called"); - - // Create shared font memory object - std::memcpy(kernel.GetFontSharedMem().GetPointer(), impl->shared_font->data(), - impl->shared_font->size()); - - IPC::ResponseBuilder rb{ctx, 2, 1}; - rb.Push(ResultSuccess); - rb.PushCopyObjects(&kernel.GetFontSharedMem()); -} - -void PL_U::GetSharedFontInOrderOfPriority(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const u64 language_code{rp.Pop()}; // TODO(ogniK): Find out what this is used for - LOG_DEBUG(Service_NS, "called, language_code={:X}", language_code); - - IPC::ResponseBuilder rb{ctx, 4}; - std::vector font_codes; - std::vector font_offsets; - std::vector font_sizes; - - // TODO(ogniK): Have actual priority order - for (std::size_t i = 0; i < impl->shared_font_regions.size(); i++) { - font_codes.push_back(static_cast(i)); - auto region = impl->GetSharedFontRegion(i); - font_offsets.push_back(region.offset); - font_sizes.push_back(region.size); - } - - // Resize buffers if game requests smaller size output. - font_codes.resize( - std::min(font_codes.size(), ctx.GetWriteBufferSize(0) / sizeof(u32))); - font_offsets.resize( - std::min(font_offsets.size(), ctx.GetWriteBufferSize(1) / sizeof(u32))); - font_sizes.resize( - std::min(font_sizes.size(), ctx.GetWriteBufferSize(2) / sizeof(u32))); - - ctx.WriteBuffer(font_codes, 0); - ctx.WriteBuffer(font_offsets, 1); - ctx.WriteBuffer(font_sizes, 2); - - rb.Push(ResultSuccess); - rb.Push(static_cast(LoadState::Done)); // Fonts Loaded - rb.Push(static_cast(font_codes.size())); -} - -} // namespace Service::NS diff --git a/src/core/hle/service/ns/pl_u.h b/src/core/hle/service/ns/pl_u.h deleted file mode 100644 index 07d0ac934..000000000 --- a/src/core/hle/service/ns/pl_u.h +++ /dev/null @@ -1,58 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include -#include "core/hle/service/service.h" - -namespace Service { - -namespace FileSystem { -class FileSystemController; -} // namespace FileSystem - -namespace NS { - -enum class FontArchives : u64 { - Extension = 0x0100000000000810, - Standard = 0x0100000000000811, - Korean = 0x0100000000000812, - ChineseTraditional = 0x0100000000000813, - ChineseSimple = 0x0100000000000814, -}; - -constexpr std::array, 7> SHARED_FONTS{ - std::make_pair(FontArchives::Standard, "nintendo_udsg-r_std_003.bfttf"), - std::make_pair(FontArchives::ChineseSimple, "nintendo_udsg-r_org_zh-cn_003.bfttf"), - std::make_pair(FontArchives::ChineseSimple, "nintendo_udsg-r_ext_zh-cn_003.bfttf"), - std::make_pair(FontArchives::ChineseTraditional, "nintendo_udjxh-db_zh-tw_003.bfttf"), - std::make_pair(FontArchives::Korean, "nintendo_udsg-r_ko_003.bfttf"), - std::make_pair(FontArchives::Extension, "nintendo_ext_003.bfttf"), - std::make_pair(FontArchives::Extension, "nintendo_ext2_003.bfttf"), -}; - -void DecryptSharedFontToTTF(const std::vector& input, std::vector& output); -void EncryptSharedFont(const std::vector& input, std::vector& output, std::size_t& offset); - -class PL_U final : public ServiceFramework { -public: - explicit PL_U(Core::System& system_); - ~PL_U() override; - -private: - void RequestLoad(Kernel::HLERequestContext& ctx); - void GetLoadState(Kernel::HLERequestContext& ctx); - void GetSize(Kernel::HLERequestContext& ctx); - void GetSharedMemoryAddressOffset(Kernel::HLERequestContext& ctx); - void GetSharedMemoryNativeHandle(Kernel::HLERequestContext& ctx); - void GetSharedFontInOrderOfPriority(Kernel::HLERequestContext& ctx); - - struct Impl; - std::unique_ptr impl; -}; - -} // namespace NS - -} // namespace Service -- cgit v1.2.3 From 2129d040a509754839b82b1ff6d387cb4f84f168 Mon Sep 17 00:00:00 2001 From: Kelebek1 Date: Sun, 4 Sep 2022 05:41:06 +0100 Subject: Don't stall with nvdec --- src/audio_core/audio_core.cpp | 8 ++++++++ src/audio_core/audio_core.h | 14 ++++++++++++++ src/audio_core/sink/sink_stream.cpp | 8 +++++++- src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp | 7 ++++++- 4 files changed, 35 insertions(+), 2 deletions(-) (limited to 'src/core') diff --git a/src/audio_core/audio_core.cpp b/src/audio_core/audio_core.cpp index cf7e763e6..9feec1829 100644 --- a/src/audio_core/audio_core.cpp +++ b/src/audio_core/audio_core.cpp @@ -57,4 +57,12 @@ void AudioCore::PauseSinks(const bool pausing) const { } } +void AudioCore::SetNVDECActive(bool active) { + nvdec_active = active; +} + +bool AudioCore::IsNVDECActive() const { + return nvdec_active; +} + } // namespace AudioCore diff --git a/src/audio_core/audio_core.h b/src/audio_core/audio_core.h index fd1e43356..ac9afefaa 100644 --- a/src/audio_core/audio_core.h +++ b/src/audio_core/audio_core.h @@ -65,6 +65,18 @@ public: */ void PauseSinks(bool pausing) const; + /** + * Toggle NVDEC state, used to avoid stall in playback. + * + * @param active - Set true if nvdec is active, otherwise false. + */ + void SetNVDECActive(bool active); + + /** + * Get NVDEC state. + */ + bool IsNVDECActive() const; + private: /** * Create the sinks on startup. @@ -79,6 +91,8 @@ private: std::unique_ptr input_sink; /// The ADSP in the sysmodule std::unique_ptr adsp; + /// Is NVDec currently active? + bool nvdec_active{false}; }; } // namespace AudioCore diff --git a/src/audio_core/sink/sink_stream.cpp b/src/audio_core/sink/sink_stream.cpp index 3770c515d..24636e512 100644 --- a/src/audio_core/sink/sink_stream.cpp +++ b/src/audio_core/sink/sink_stream.cpp @@ -9,6 +9,7 @@ #include #include +#include "audio_core/audio_core.h" #include "audio_core/common/common.h" #include "audio_core/sink/sink_stream.h" #include "common/common_types.h" @@ -194,7 +195,12 @@ void SinkStream::ProcessAudioOutAndRender(std::span output_buffer, std::siz const std::size_t frame_size_bytes = frame_size * sizeof(s16); size_t frames_written{0}; - if (queued_buffers > max_queue_size) { + // Due to many frames being queued up with nvdec (5 frames or so?), a lot of buffers also get + // queued up (30+) but not all at once, which causes constant stalling here, so just let the + // video play out without attempting to stall. + // Can hopefully remove this later with a more complete NVDEC implementation. + const auto nvdec_active{system.AudioCore().IsNVDECActive()}; + if (!nvdec_active && queued_buffers > max_queue_size) { Stall(); } 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 @@ // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include "audio_core/audio_core.h" #include "common/assert.h" #include "common/logging/log.h" #include "core/core.h" @@ -65,7 +66,10 @@ NvResult nvhost_nvdec::Ioctl3(DeviceFD fd, Ioctl command, const std::vector& return NvResult::NotImplemented; } -void nvhost_nvdec::OnOpen(DeviceFD fd) {} +void nvhost_nvdec::OnOpen(DeviceFD fd) { + LOG_INFO(Service_NVDRV, "NVDEC video stream started"); + system.AudioCore().SetNVDECActive(true); +} void nvhost_nvdec::OnClose(DeviceFD fd) { LOG_INFO(Service_NVDRV, "NVDEC video stream ended"); @@ -73,6 +77,7 @@ void nvhost_nvdec::OnClose(DeviceFD fd) { if (iter != fd_to_id.end()) { system.GPU().ClearCdmaInstance(iter->second); } + system.AudioCore().SetNVDECActive(false); } } // namespace Service::Nvidia::Devices -- cgit v1.2.3 From dc8d42243b1eb4ea2b1192273e58586a617419be Mon Sep 17 00:00:00 2001 From: Narr the Reg Date: Mon, 5 Sep 2022 16:08:30 -0500 Subject: core: hid: Fix GC triggers overwritting ZL and ZR buttons --- src/core/hid/emulated_controller.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'src/core') diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp index f9f902c2d..01c43be93 100644 --- a/src/core/hid/emulated_controller.cpp +++ b/src/core/hid/emulated_controller.cpp @@ -562,6 +562,16 @@ void EmulatedController::SetButton(const Common::Input::CallbackStatus& callback return; } + // GC controllers have triggers not buttons + if (npad_type == NpadStyleIndex::GameCube) { + if (index == Settings::NativeButton::ZR) { + return; + } + if (index == Settings::NativeButton::ZL) { + return; + } + } + switch (index) { case Settings::NativeButton::A: controller.npad_button_state.a.Assign(current_status.value); @@ -738,6 +748,11 @@ void EmulatedController::SetTrigger(const Common::Input::CallbackStatus& callbac return; } + // Only GC controllers have analog triggers + if (npad_type != NpadStyleIndex::GameCube) { + return; + } + const auto& trigger = controller.trigger_values[index]; switch (index) { -- cgit v1.2.3 From 2898be69f414a2735b8693c58e039097853f5ec7 Mon Sep 17 00:00:00 2001 From: Narr the Reg Date: Tue, 6 Sep 2022 11:20:53 -0500 Subject: input_common: Add support for analog toggle --- src/common/input.h | 5 +++++ src/core/hid/input_converter.cpp | 3 +++ src/input_common/input_poller.cpp | 1 + src/yuzu/configuration/configure_input_player.cpp | 6 ++++++ 4 files changed, 15 insertions(+) (limited to 'src/core') diff --git a/src/common/input.h b/src/common/input.h index 213aa2384..825b0d650 100644 --- a/src/common/input.h +++ b/src/common/input.h @@ -102,6 +102,8 @@ struct AnalogProperties { float offset{}; // Invert direction of the sensor data bool inverted{}; + // Press once to activate, press again to release + bool toggle{}; }; // Single analog sensor data @@ -115,8 +117,11 @@ struct AnalogStatus { struct ButtonStatus { Common::UUID uuid{}; bool value{}; + // Invert value of the button bool inverted{}; + // Press once to activate, press again to release bool toggle{}; + // Internal lock for the toggle status bool locked{}; }; diff --git a/src/core/hid/input_converter.cpp b/src/core/hid/input_converter.cpp index 68d143a01..52fb69e9c 100644 --- a/src/core/hid/input_converter.cpp +++ b/src/core/hid/input_converter.cpp @@ -52,6 +52,9 @@ Common::Input::ButtonStatus TransformToButton(const Common::Input::CallbackStatu Common::Input::ButtonStatus status{}; switch (callback.type) { case Common::Input::InputType::Analog: + status.value = TransformToTrigger(callback).pressed.value; + status.toggle = callback.analog_status.properties.toggle; + break; case Common::Input::InputType::Trigger: status.value = TransformToTrigger(callback).pressed.value; break; diff --git a/src/input_common/input_poller.cpp b/src/input_common/input_poller.cpp index 133422d5c..ffb9b945e 100644 --- a/src/input_common/input_poller.cpp +++ b/src/input_common/input_poller.cpp @@ -824,6 +824,7 @@ std::unique_ptr InputFactory::CreateAnalogDevice( .threshold = std::clamp(params.Get("threshold", 0.5f), 0.0f, 1.0f), .offset = std::clamp(params.Get("offset", 0.0f), -1.0f, 1.0f), .inverted = params.Get("invert", "+") == "-", + .toggle = static_cast(params.Get("toggle", false)), }; input_engine->PreSetController(identifier); input_engine->PreSetAxis(identifier, axis); diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp index 109689c88..972c311f6 100644 --- a/src/yuzu/configuration/configure_input_player.cpp +++ b/src/yuzu/configuration/configure_input_player.cpp @@ -382,6 +382,12 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i button_map[button_id]->setText(ButtonToText(param)); emulated_controller->SetButtonParam(button_id, param); }); + context_menu.addAction(tr("Toggle axis"), [&] { + const bool toggle_value = !param.Get("toggle", false); + param.Set("toggle", toggle_value); + button_map[button_id]->setText(ButtonToText(param)); + emulated_controller->SetButtonParam(button_id, param); + }); context_menu.addAction(tr("Set threshold"), [&] { const int button_threshold = static_cast(param.Get("threshold", 0.5f) * 100.0f); -- cgit v1.2.3 From 848f69eb19ddeffd6e2879108eb2604ec390a14e Mon Sep 17 00:00:00 2001 From: german77 Date: Sun, 13 Feb 2022 11:54:39 -0600 Subject: core: nfp: Implement amiibo encryption --- src/core/CMakeLists.txt | 3 + src/core/hle/service/nfp/amiibo_crypto.cpp | 455 +++++++++++++++++++++++++++ src/core/hle/service/nfp/amiibo_crypto.h | 102 ++++++ src/core/hle/service/nfp/amiibo_types.h | 304 ++++++++++++++++++ src/core/hle/service/nfp/nfp.cpp | 481 +++++++++++++++++++++-------- src/core/hle/service/nfp/nfp.h | 137 +------- src/yuzu/main.cpp | 21 +- 7 files changed, 1227 insertions(+), 276 deletions(-) create mode 100644 src/core/hle/service/nfp/amiibo_crypto.cpp create mode 100644 src/core/hle/service/nfp/amiibo_crypto.h create mode 100644 src/core/hle/service/nfp/amiibo_types.h (limited to 'src/core') diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 806e7ff6c..22ff3d304 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -525,6 +525,9 @@ add_library(core STATIC hle/service/ncm/ncm.h hle/service/nfc/nfc.cpp hle/service/nfc/nfc.h + hle/service/nfp/amiibo_crypto.cpp + hle/service/nfp/amiibo_crypto.h + hle/service/nfp/amiibo_types.h hle/service/nfp/nfp.cpp hle/service/nfp/nfp.h hle/service/nfp/nfp_user.cpp 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..211e518b0 --- /dev/null +++ b/src/core/hle/service/nfp/amiibo_crypto.cpp @@ -0,0 +1,455 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2017 socram8888/amiitool +// SPDX-License-Identifier: MIT + +#include +#include +#include + +#include "common/fs/file.h" +#include "common/fs/path_util.h" +#include "common/logging/log.h" +#include "core/hle/service/mii/mii_manager.h" +#include "core/hle/service/nfp/amiibo_crypto.h" + +namespace Service::NFP::AmiiboCrypto { + +Service::Mii::MiiInfo AmiiboRegisterInfoToMii(const AmiiboRegisterInfo& mii_info) { + + Service::Mii::MiiManager manager; + auto mii = manager.BuildDefault(0); + + // TODO: We are ignoring a bunch of data from the amiibo mii + + mii.gender = static_cast(mii_info.mii_information.gender); + mii.favorite_color = static_cast(mii_info.mii_information.favorite_color); + memcpy(mii.name.data(), mii_info.mii_name.data(), 10); + mii.height = mii_info.height; + mii.build = mii_info.build; + + mii.faceline_type = mii_info.appearance_bits1.face_shape; + mii.faceline_color = mii_info.appearance_bits1.skin_color; + mii.faceline_wrinkle = mii_info.appearance_bits2.wrinkles; + mii.faceline_make = mii_info.appearance_bits2.makeup; + + mii.hair_type = mii_info.hair_style; + mii.hair_color = mii_info.appearance_bits3.hair_color; + mii.hair_flip = mii_info.appearance_bits3.flip_hair; + + mii.eye_type = static_cast(mii_info.appearance_bits4.eye_type); + mii.eye_color = static_cast(mii_info.appearance_bits4.eye_color); + mii.eye_scale = static_cast(mii_info.appearance_bits4.eye_scale); + mii.eye_aspect = static_cast(mii_info.appearance_bits4.eye_vertical_stretch); + mii.eye_rotate = static_cast(mii_info.appearance_bits4.eye_rotation); + mii.eye_x = static_cast(mii_info.appearance_bits4.eye_spacing); + mii.eye_y = static_cast(mii_info.appearance_bits4.eye_y_position); + + mii.eyebrow_type = static_cast(mii_info.appearance_bits5.eyebrow_style); + mii.eyebrow_color = static_cast(mii_info.appearance_bits5.eyebrow_color); + mii.eyebrow_scale = static_cast(mii_info.appearance_bits5.eyebrow_scale); + mii.eyebrow_aspect = static_cast(mii_info.appearance_bits5.eyebrow_yscale); + mii.eyebrow_rotate = static_cast(mii_info.appearance_bits5.eyebrow_rotation); + mii.eyebrow_x = static_cast(mii_info.appearance_bits5.eyebrow_spacing); + mii.eyebrow_y = static_cast(mii_info.appearance_bits5.eyebrow_y_position); + + mii.nose_type = static_cast(mii_info.appearance_bits6.nose_type); + mii.nose_scale = static_cast(mii_info.appearance_bits6.nose_scale); + mii.nose_y = static_cast(mii_info.appearance_bits6.nose_y_position); + + mii.mouth_type = static_cast(mii_info.appearance_bits7.mouth_type); + mii.mouth_color = static_cast(mii_info.appearance_bits7.mouth_color); + mii.mouth_scale = static_cast(mii_info.appearance_bits7.mouth_scale); + mii.mouth_aspect = static_cast(mii_info.appearance_bits7.mouth_horizontal_stretch); + mii.mouth_y = static_cast(mii_info.appearance_bits8.mouth_y_position); + + mii.mustache_type = static_cast(mii_info.appearance_bits8.mustache_type); + mii.mustache_scale = static_cast(mii_info.appearance_bits9.mustache_scale); + mii.mustache_y = static_cast(mii_info.appearance_bits9.mustache_y_position); + + mii.beard_type = static_cast(mii_info.appearance_bits9.bear_type); + mii.beard_color = static_cast(mii_info.appearance_bits9.facial_hair_color); + + mii.glasses_type = static_cast(mii_info.appearance_bits10.glasses_type); + mii.glasses_color = static_cast(mii_info.appearance_bits10.glasses_color); + mii.glasses_scale = static_cast(mii_info.appearance_bits10.glasses_scale); + mii.glasses_y = static_cast(mii_info.appearance_bits10.glasses_y_position); + + mii.mole_type = static_cast(mii_info.appearance_bits11.mole_enabled); + mii.mole_scale = static_cast(mii_info.appearance_bits11.mole_scale); + mii.mole_x = static_cast(mii_info.appearance_bits11.mole_x_position); + mii.mole_y = static_cast(mii_info.appearance_bits11.mole_y_position); + + // TODO: Validate mii data + + return mii; +} + +bool IsAmiiboValid(const EncryptedNTAG215File& ntag_file) { + const auto& amiibo_data = ntag_file.user_memory; + LOG_DEBUG(Service_NFP, "uuid_lock=0x{0:x}", ntag_file.static_lock); + LOG_DEBUG(Service_NFP, "compability_container=0x{0:x}", ntag_file.compability_container); + LOG_INFO(Service_NFP, "write_count={}", amiibo_data.write_counter); + + LOG_INFO(Service_NFP, "character_id=0x{0:x}", amiibo_data.model_info.character_id); + LOG_INFO(Service_NFP, "character_variant={}", amiibo_data.model_info.character_variant); + LOG_INFO(Service_NFP, "amiibo_type={}", amiibo_data.model_info.amiibo_type); + LOG_INFO(Service_NFP, "model_number=0x{0:x}", amiibo_data.model_info.model_number); + LOG_INFO(Service_NFP, "series={}", amiibo_data.model_info.series); + LOG_DEBUG(Service_NFP, "fixed_value=0x{0:x}", amiibo_data.model_info.constant_value); + + LOG_DEBUG(Service_NFP, "tag_dynamic_lock=0x{0:x}", ntag_file.dynamic_lock); + LOG_DEBUG(Service_NFP, "tag_CFG0=0x{0:x}", ntag_file.CFG0); + LOG_DEBUG(Service_NFP, "tag_CFG1=0x{0:x}", ntag_file.CFG1); + + // Validate UUID + constexpr u8 CT = 0x88; // As defined in `ISO / IEC 14443 - 3` + if ((CT ^ ntag_file.uuid[0] ^ ntag_file.uuid[1] ^ ntag_file.uuid[2]) != ntag_file.uuid[3]) { + return false; + } + if ((ntag_file.uuid[4] ^ ntag_file.uuid[5] ^ ntag_file.uuid[6] ^ ntag_file.uuid[7]) != + ntag_file.uuid[8]) { + return false; + } + + // Check against all know constants on an amiibo binary + if (ntag_file.static_lock != 0xE00F) { + return false; + } + if (ntag_file.compability_container != 0xEEFF10F1U) { + return false; + } + if (amiibo_data.constant_value != 0xA5) { + return false; + } + if (amiibo_data.model_info.constant_value != 0x02) { + return false; + } + if ((ntag_file.dynamic_lock & 0xFFFFFF) != 0x0F0001) { + return false; + } + if (ntag_file.CFG0 != 0x04000000U) { + return false; + } + if (ntag_file.CFG1 != 0x5F) { + return false; + } + return true; +} + +NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data) { + NTAG215File encoded_data{}; + + memcpy(encoded_data.uuid2.data(), nfc_data.uuid.data() + 0x8, 2); + encoded_data.static_lock = nfc_data.static_lock; + encoded_data.compability_container = nfc_data.compability_container; + encoded_data.unfixed_hash = nfc_data.user_memory.unfixed_hash; + encoded_data.constant_value = nfc_data.user_memory.constant_value; + encoded_data.write_counter = nfc_data.user_memory.write_counter; + encoded_data.settings = nfc_data.user_memory.settings; + encoded_data.owner_mii = nfc_data.user_memory.owner_mii; + encoded_data.title_id = nfc_data.user_memory.title_id; + encoded_data.applicaton_write_counter = nfc_data.user_memory.applicaton_write_counter; + encoded_data.application_area_id = nfc_data.user_memory.application_area_id; + encoded_data.unknown = nfc_data.user_memory.unknown; + encoded_data.hash = nfc_data.user_memory.hash; + encoded_data.application_area = nfc_data.user_memory.application_area; + encoded_data.locked_hash = nfc_data.user_memory.locked_hash; + memcpy(encoded_data.uuid.data(), nfc_data.uuid.data(), 8); + encoded_data.model_info = nfc_data.user_memory.model_info; + encoded_data.keygen_salt = nfc_data.user_memory.keygen_salt; + encoded_data.dynamic_lock = nfc_data.dynamic_lock; + encoded_data.CFG0 = nfc_data.CFG0; + encoded_data.CFG1 = nfc_data.CFG1; + encoded_data.password = nfc_data.password; + + return encoded_data; +} + +EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data) { + EncryptedNTAG215File nfc_data{}; + + memcpy(nfc_data.uuid.data() + 0x8, encoded_data.uuid2.data(), 2); + memcpy(nfc_data.uuid.data(), encoded_data.uuid.data(), 8); + nfc_data.static_lock = encoded_data.static_lock; + nfc_data.compability_container = encoded_data.compability_container; + nfc_data.user_memory.unfixed_hash = encoded_data.unfixed_hash; + nfc_data.user_memory.constant_value = encoded_data.constant_value; + nfc_data.user_memory.write_counter = encoded_data.write_counter; + nfc_data.user_memory.settings = encoded_data.settings; + nfc_data.user_memory.owner_mii = encoded_data.owner_mii; + nfc_data.user_memory.title_id = encoded_data.title_id; + nfc_data.user_memory.applicaton_write_counter = encoded_data.applicaton_write_counter; + nfc_data.user_memory.application_area_id = encoded_data.application_area_id; + nfc_data.user_memory.unknown = encoded_data.unknown; + nfc_data.user_memory.hash = encoded_data.hash; + nfc_data.user_memory.application_area = encoded_data.application_area; + nfc_data.user_memory.locked_hash = encoded_data.locked_hash; + nfc_data.user_memory.model_info = encoded_data.model_info; + nfc_data.user_memory.keygen_salt = encoded_data.keygen_salt; + nfc_data.dynamic_lock = encoded_data.dynamic_lock; + nfc_data.CFG0 = encoded_data.CFG0; + nfc_data.CFG1 = encoded_data.CFG1; + nfc_data.password = encoded_data.password; + + return nfc_data; +} + +u32 GetTagPassword(const TagUuid& uuid) { + // Verifiy that the generated password is correct + u32 password = 0xAA ^ (uuid[1] ^ uuid[3]); + password &= (0x55 ^ (uuid[2] ^ uuid[4])) << 8; + password &= (0xAA ^ (uuid[3] ^ uuid[5])) << 16; + password &= (0x55 ^ (uuid[4] ^ uuid[6])) << 24; + return password; +} + +HashSeed GetSeed(const NTAG215File& data) { + HashSeed seed{ + .data = + { + .magic = data.write_counter, + .padding = {}, + .uuid1 = {}, + .uuid2 = {}, + .keygen_salt = data.keygen_salt, + }, + }; + + // Copy the first 8 bytes of uuid + memcpy(seed.data.uuid1.data(), data.uuid.data(), sizeof(seed.data.uuid1)); + memcpy(seed.data.uuid2.data(), data.uuid.data(), sizeof(seed.data.uuid2)); + + return seed; +} + +void PreGenerateKey(const InternalKey& key, const HashSeed& seed, u8* output, + std::size_t& outputLen) { + std::size_t index = 0; + + // Copy whole type string + memccpy(output + index, key.type_string.data(), '\0', key.type_string.size()); + index += key.type_string.size(); + + // Append (16 - magic_length) from the input seed + std::size_t seedPart1Len = 16 - key.magic_length; + memcpy(output + index, &seed, seedPart1Len); + index += seedPart1Len; + + // Append all bytes from magicBytes + memcpy(output + index, &key.magic_bytes, key.magic_length); + index += key.magic_length; + + // Seed 16 bytes at +0x10 + memcpy(output + index, &seed.raw[0x10], 16); + index += 16; + + // 32 bytes at +0x20 from input seed xored with xor pad + for (std::size_t i = 0; i < 32; i++) + output[index + i] = seed.raw[i + 32] ^ key.xor_pad[i]; + index += 32; + + outputLen = index; +} + +void CryptoInit(CryptoCtx& ctx, mbedtls_md_context_t& hmac_ctx, const HmacKey& hmac_key, + const u8* seed, std::size_t seed_size) { + + // Initialize context + ctx.used = false; + ctx.counter = 0; + ctx.buffer_size = sizeof(ctx.counter) + seed_size; + memcpy(ctx.buffer.data() + sizeof(u16), seed, seed_size); + + // Initialize HMAC context + mbedtls_md_init(&hmac_ctx); + mbedtls_md_setup(&hmac_ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 1); + mbedtls_md_hmac_starts(&hmac_ctx, hmac_key.data(), hmac_key.size()); +} + +void CryptoStep(CryptoCtx& ctx, mbedtls_md_context_t& hmac_ctx, DrgbOutput& output) { + // If used at least once, reinitialize the HMAC + if (ctx.used) { + mbedtls_md_hmac_reset(&hmac_ctx); + } + + ctx.used = true; + + // Store counter in big endian, and increment it + ctx.buffer[0] = static_cast(ctx.counter >> 8); + ctx.buffer[1] = static_cast(ctx.counter >> 0); + ctx.counter++; + + // Do HMAC magic + mbedtls_md_hmac_update(&hmac_ctx, reinterpret_cast(ctx.buffer.data()), + ctx.buffer_size); + mbedtls_md_hmac_finish(&hmac_ctx, output.data()); +} + +DerivedKeys GenerateKey(const InternalKey& key, const NTAG215File& data) { + constexpr std::size_t OUTPUT_SIZE = 512; + const auto seed = GetSeed(data); + + // Generate internal seed + u8 internal_key[OUTPUT_SIZE]; + std::size_t internal_key_lenght = 0; + PreGenerateKey(key, seed, internal_key, internal_key_lenght); + + // Initialize context + CryptoCtx ctx{}; + mbedtls_md_context_t hmac_ctx; + CryptoInit(ctx, hmac_ctx, key.hmac_key, internal_key, internal_key_lenght); + + // Generate derived keys + DerivedKeys derived_keys{}; + std::array temp{}; + CryptoStep(ctx, hmac_ctx, temp[0]); + CryptoStep(ctx, hmac_ctx, temp[1]); + memcpy(&derived_keys, temp.data(), sizeof(DerivedKeys)); + + // Cleanup context + mbedtls_md_free(&hmac_ctx); + + return derived_keys; +} + +void Cipher(const DerivedKeys& keys, const NTAG215File& in_data, NTAG215File& out_data) { + mbedtls_aes_context aes; + std::size_t nc_off = 0; + std::array nonce_counter{}; + std::array stream_block{}; + + mbedtls_aes_setkey_enc(&aes, keys.aes_key.data(), 128); + memcpy(nonce_counter.data(), keys.aes_iv.data(), sizeof(nonce_counter)); + + std::array in_data_byes{}; + std::array out_data_bytes{}; + memcpy(in_data_byes.data(), &in_data, sizeof(NTAG215File)); + memcpy(out_data_bytes.data(), &out_data, sizeof(NTAG215File)); + + mbedtls_aes_crypt_ctr(&aes, 0x188, &nc_off, nonce_counter.data(), stream_block.data(), + in_data_byes.data() + 0x2c, out_data_bytes.data() + 0x2c); + + memcpy(out_data_bytes.data(), in_data_byes.data(), 0x008); + // Data signature NOT copied + memcpy(out_data_bytes.data() + 0x028, in_data_byes.data() + 0x028, 0x004); + // Tag signature NOT copied + memcpy(out_data_bytes.data() + 0x1D4, in_data_byes.data() + 0x1D4, 0x048); + + memcpy(&out_data, out_data_bytes.data(), sizeof(NTAG215File)); +} + +bool LoadKeys(InternalKey& locked_secret, InternalKey& unfixed_info) { + const auto yuzu_keys_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::KeysDir); + + const Common::FS::IOFile keys_file{yuzu_keys_dir / "key_retail.bin", + Common::FS::FileAccessMode::Read, + Common::FS::FileType::BinaryFile}; + + if (!keys_file.IsOpen()) { + LOG_ERROR(Core, "No keys detected"); + return false; + } + + if (keys_file.Read(unfixed_info) != 1) { + LOG_ERROR(Core, "Failed to read unfixed_info"); + return false; + } + if (keys_file.Read(locked_secret) != 1) { + LOG_ERROR(Core, "Failed to read locked-secret"); + return false; + } + + return true; +} + +bool DecodeAmiibo(const EncryptedNTAG215File& encrypted_tag_data, NTAG215File& tag_data) { + InternalKey locked_secret{}; + InternalKey unfixed_info{}; + + if (!LoadKeys(locked_secret, unfixed_info)) { + return false; + } + + // Generate keys + NTAG215File encoded_data = NfcDataToEncodedData(encrypted_tag_data); + const auto data_keys = GenerateKey(unfixed_info, encoded_data); + const auto tag_keys = GenerateKey(locked_secret, encoded_data); + + // Decrypt + Cipher(data_keys, encoded_data, tag_data); + + std::array out{}; + memcpy(out.data(), &tag_data, sizeof(NTAG215File)); + + // Regenerate tag HMAC. Note: order matters, data HMAC depends on tag HMAC! + mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), tag_keys.hmac_key.data(), + sizeof(HmacKey), out.data() + 0x1D4, 0x34, out.data() + HMAC_POS_TAG); + + // Regenerate data HMAC + mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), data_keys.hmac_key.data(), + sizeof(HmacKey), out.data() + 0x29, 0x1DF, out.data() + HMAC_POS_DATA); + + memcpy(&tag_data, out.data(), sizeof(NTAG215File)); + + if (memcmp(tag_data.unfixed_hash.data(), encrypted_tag_data.user_memory.unfixed_hash.data(), + 32) != 0) { + return false; + } + + if (memcmp(tag_data.locked_hash.data(), encrypted_tag_data.user_memory.locked_hash.data(), + 32) != 0) { + return false; + } + + return true; +} + +bool EncodeAmiibo(const NTAG215File& tag_data, EncryptedNTAG215File& encrypted_tag_data) { + InternalKey locked_secret{}; + InternalKey unfixed_info{}; + + if (!LoadKeys(locked_secret, unfixed_info)) { + return false; + } + + // Generate keys + const auto data_keys = GenerateKey(unfixed_info, tag_data); + const auto tag_keys = GenerateKey(locked_secret, tag_data); + + std::array plain{}; + std::array cipher{}; + memcpy(plain.data(), &tag_data, sizeof(NTAG215File)); + + // Generate tag HMAC + mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), tag_keys.hmac_key.data(), + sizeof(HmacKey), plain.data() + 0x1D4, 0x34, cipher.data() + HMAC_POS_TAG); + + // Init mbedtls HMAC context + mbedtls_md_context_t ctx; + mbedtls_md_init(&ctx); + mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 1); + + // Generate data HMAC + mbedtls_md_hmac_starts(&ctx, data_keys.hmac_key.data(), sizeof(HmacKey)); + mbedtls_md_hmac_update(&ctx, plain.data() + 0x029, 0x18B); // Data + mbedtls_md_hmac_update(&ctx, cipher.data() + HMAC_POS_TAG, 0x20); // Tag HMAC + mbedtls_md_hmac_update(&ctx, plain.data() + 0x1D4, 0x34); + mbedtls_md_hmac_finish(&ctx, cipher.data() + HMAC_POS_DATA); + + // HMAC cleanup + mbedtls_md_free(&ctx); + + // Encrypt + NTAG215File encoded_tag_data{}; + memcpy(&encoded_tag_data, cipher.data(), sizeof(NTAG215File)); + Cipher(data_keys, tag_data, encoded_tag_data); + + // Convert back to hardware + encrypted_tag_data = EncodedDataToNfcData(encoded_tag_data); + + return true; +} + +} // 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..bfba5dcb2 --- /dev/null +++ b/src/core/hle/service/nfp/amiibo_crypto.h @@ -0,0 +1,102 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +#include "core/hle/service/nfp/amiibo_types.h" + +struct mbedtls_md_context_t; + +namespace Service::NFP::AmiiboCrypto { +constexpr std::size_t HMAC_POS_DATA = 0x8; +constexpr std::size_t HMAC_POS_TAG = 0x1B4; + +using HmacKey = std::array; +using DrgbOutput = std::array; + +struct HashSeed { + union { + std::array raw; + struct { + u16 magic; + std::array padding; + std::array uuid1; + std::array uuid2; + std::array keygen_salt; + } data; + }; +}; +static_assert(sizeof(HashSeed) == 0x40, "HashSeed is an invalid size"); + +struct InternalKey { + HmacKey hmac_key; + std::array type_string; + u8 reserved; + u8 magic_length; + std::array magic_bytes; + std::array xor_pad; +}; +static_assert(sizeof(InternalKey) == 0x50, "InternalKey is an invalid size"); +static_assert(std::is_trivially_copyable_v, "InternalKey must be trivially copyable."); + +struct CryptoCtx { + std::array buffer; + bool used; + std::size_t buffer_size; + s16 counter; +}; + +struct DerivedKeys { + std::array aes_key; + std::array aes_iv; + std::array hmac_key; +}; +static_assert(sizeof(DerivedKeys) == 0x30, "DerivedKeys is an invalid size"); + +/// Converts mii data from nintendo 3ds format to nintendo switch format +Service::Mii::MiiInfo AmiiboRegisterInfoToMii(const AmiiboRegisterInfo& register_info); + +/// Validates that the amiibo file is not corrupted +bool IsAmiiboValid(const EncryptedNTAG215File& ntag_file); + +/// Converts from encrypted file format to encoded file format +NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data); + +/// Converts from encoded file format to encrypted file format +EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data); + +/// Returns password needed to allow write access to protected memory +u32 GetTagPassword(const TagUuid& uuid); + +// Generates Seed needed for key derivation +HashSeed GetSeed(const NTAG215File& data); + +// Middle step on the generation of derived keys +void PreGenerateKey(const InternalKey& key, const HashSeed& seed, u8* output, + std::size_t& outputLen); + +// Initializes mbedtls context +void CryptoInit(CryptoCtx& ctx, mbedtls_md_context_t& hmac_ctx, const HmacKey& hmac_key, + const u8* seed, std::size_t seed_size); + +// Feeds data to mbedtls context to generate the derived key +void CryptoStep(CryptoCtx& ctx, mbedtls_md_context_t& hmac_ctx, DrgbOutput& output); + +// Generates the derived key from amiibo data +DerivedKeys GenerateKey(const InternalKey& key, const NTAG215File& data); + +// Encodes or decodes amiibo data +void Cipher(const DerivedKeys& keys, const NTAG215File& in_data, NTAG215File& out_data); + +/// Loads both amiibo keys from key_retail.bin +bool LoadKeys(InternalKey& locked_secret, InternalKey& unfixed_info); + +/// Decodes encripted amiibo data returns true if output is valid +bool DecodeAmiibo(const EncryptedNTAG215File& encrypted_tag_data, NTAG215File& tag_data); + +/// Encodes plain amiibo data returns true if output is valid +bool EncodeAmiibo(const NTAG215File& tag_data, EncryptedNTAG215File& encrypted_tag_data); + +} // 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..49875cff4 --- /dev/null +++ b/src/core/hle/service/nfp/amiibo_types.h @@ -0,0 +1,304 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +namespace Service::NFP { +enum class ServiceType : u32 { + User, + Debug, + System, +}; + +enum class State : u32 { + NonInitialized, + Initialized, +}; + +enum class DeviceState : u32 { + Initialized, + SearchingForTag, + TagFound, + TagRemoved, + TagMounted, + Unaviable, + Finalized, +}; + +enum class ModelType : u32 { + Amiibo, +}; + +enum class MountTarget : u32 { + Rom, + Ram, + All, +}; + +enum class AmiiboType : u8 { + Figure, + Card, + Yarn, +}; + +enum class AmiiboSeries : u8 { + SuperSmashBros, + SuperMario, + ChibiRobo, + YoshiWoollyWorld, + Splatoon, + AnimalCrossing, + EightBitMario, + Skylanders, + Unknown8, + TheLegendOfZelda, + ShovelKnight, + Unknown11, + Kiby, + Pokemon, + MarioSportsSuperstars, + MonsterHunter, + BoxBoy, + Pikmin, + FireEmblem, + Metroid, + Others, + MegaMan, + Diablo, +}; + +using TagUuid = std::array; +using HashData = std::array; +using ApplicationArea = std::array; + +struct AmiiboDate { + union { + u16_be raw{}; + + BitField<0, 5, u16> day; + BitField<5, 4, u16> month; + BitField<9, 7, u16> year; + }; +}; +static_assert(sizeof(AmiiboDate) == 2, "AmiiboDate is an invalid size"); + +struct Settings { + union { + u8 raw{}; + + BitField<4, 1, u8> amiibo_initialized; + BitField<5, 1, u8> appdata_initialized; + }; +}; +static_assert(sizeof(Settings) == 1, "AmiiboDate is an invalid size"); + +struct AmiiboSettings { + Settings settings; + u8 country_code_id; + u16_be crc_counter; // Incremented each time crc is changed + AmiiboDate init_date; + AmiiboDate write_date; + u32_be crc; + std::array amiibo_name; // UTF-16 text +}; +static_assert(sizeof(AmiiboSettings) == 0x20, "AmiiboSettings is an invalid size"); + +struct AmiiboModelInfo { + u16 character_id; + u8 character_variant; + AmiiboType amiibo_type; + u16 model_number; + AmiiboSeries series; + u8 constant_value; // Must be 02 + INSERT_PADDING_BYTES(0x4); // Unknown +}; +static_assert(sizeof(AmiiboModelInfo) == 0xC, "AmiiboModelInfo is an invalid size"); + +struct NTAG215Password { + u32 PWD; // Password to allow write access + u16 PACK; // Password acknowledge reply + u16 RFUI; // Reserved for future use +}; +static_assert(sizeof(NTAG215Password) == 0x8, "NTAG215Password is an invalid size"); + +// Based on citra HLE::Applets::MiiData and PretendoNetwork. +// https://github.com/citra-emu/citra/blob/master/src/core/hle/applets/mii_selector.h#L48 +// https://github.com/PretendoNetwork/mii-js/blob/master/mii.js#L299 +#pragma pack(1) +struct AmiiboRegisterInfo { + u32_be mii_id; + u64_be system_id; + u32_be specialness_and_creation_date; + std::array creator_mac; + u16_be padding; + union { + u16 raw; + + BitField<0, 1, u16> gender; + BitField<1, 4, u16> birth_month; + BitField<5, 5, u16> birth_day; + BitField<10, 4, u16> favorite_color; + BitField<14, 1, u16> favorite; + } mii_information; + std::array mii_name; + u8 height; + u8 build; + union { + u8 raw; + + BitField<0, 1, u8> disable_sharing; + BitField<1, 4, u8> face_shape; + BitField<5, 3, u8> skin_color; + } appearance_bits1; + union { + u8 raw; + + BitField<0, 4, u8> wrinkles; + BitField<4, 4, u8> makeup; + } appearance_bits2; + u8 hair_style; + union { + u8 raw; + + BitField<0, 3, u8> hair_color; + BitField<3, 1, u8> flip_hair; + } appearance_bits3; + union { + u32 raw; + + BitField<0, 6, u32> eye_type; + BitField<6, 3, u32> eye_color; + BitField<9, 4, u32> eye_scale; + BitField<13, 3, u32> eye_vertical_stretch; + BitField<16, 5, u32> eye_rotation; + BitField<21, 4, u32> eye_spacing; + BitField<25, 5, u32> eye_y_position; + } appearance_bits4; + union { + u32 raw; + + BitField<0, 5, u32> eyebrow_style; + BitField<5, 3, u32> eyebrow_color; + BitField<8, 4, u32> eyebrow_scale; + BitField<12, 3, u32> eyebrow_yscale; + BitField<16, 4, u32> eyebrow_rotation; + BitField<21, 4, u32> eyebrow_spacing; + BitField<25, 5, u32> eyebrow_y_position; + } appearance_bits5; + union { + u16 raw; + + BitField<0, 5, u16> nose_type; + BitField<5, 4, u16> nose_scale; + BitField<9, 5, u16> nose_y_position; + } appearance_bits6; + union { + u16 raw; + + BitField<0, 6, u16> mouth_type; + BitField<6, 3, u16> mouth_color; + BitField<9, 4, u16> mouth_scale; + BitField<13, 3, u16> mouth_horizontal_stretch; + } appearance_bits7; + union { + u8 raw; + + BitField<0, 5, u8> mouth_y_position; + BitField<5, 3, u8> mustache_type; + } appearance_bits8; + u8 allow_copying; + union { + u16 raw; + + BitField<0, 3, u16> bear_type; + BitField<3, 3, u16> facial_hair_color; + BitField<6, 4, u16> mustache_scale; + BitField<10, 5, u16> mustache_y_position; + } appearance_bits9; + union { + u16 raw; + + BitField<0, 4, u16> glasses_type; + BitField<4, 3, u16> glasses_color; + BitField<7, 4, u16> glasses_scale; + BitField<11, 5, u16> glasses_y_position; + } appearance_bits10; + union { + u16 raw; + + BitField<0, 1, u16> mole_enabled; + BitField<1, 4, u16> mole_scale; + BitField<5, 5, u16> mole_x_position; + BitField<10, 5, u16> mole_y_position; + } appearance_bits11; + + std::array author_name; + INSERT_PADDING_BYTES(0x4); +}; +static_assert(sizeof(AmiiboRegisterInfo) == 0x60, "AmiiboRegisterInfo is an invalid size"); + +struct EncryptedAmiiboFile { + u8 constant_value; // Must be A5 + u16 write_counter; // Number of times the amiibo has been written? + INSERT_PADDING_BYTES(0x1); // Unknown 1 + AmiiboSettings settings; // Encrypted amiibo settings + HashData locked_hash; // Hash + AmiiboModelInfo model_info; // Encrypted amiibo model info + HashData keygen_salt; // Salt + HashData unfixed_hash; // Hash + AmiiboRegisterInfo owner_mii; // Encrypted Mii data + u64_be title_id; // Encrypted Game id + u16_be applicaton_write_counter; // Encrypted Counter + u32_be application_area_id; // Encrypted Game id + std::array unknown; + HashData hash; // Probably a SHA256-HMAC hash? + ApplicationArea application_area; // Encrypted Game data +}; +static_assert(sizeof(EncryptedAmiiboFile) == 0x1F8, "AmiiboFile is an invalid size"); + +struct NTAG215File { + std::array uuid2; + u16 static_lock; // Set defined pages as read only + u32 compability_container; // Defines available memory + HashData unfixed_hash; // Hash + u8 constant_value; // Must be A5 + u16 write_counter; // Number of times the amiibo has been written? + INSERT_PADDING_BYTES(0x1); // Unknown 1 + AmiiboSettings settings; + AmiiboRegisterInfo owner_mii; // Encrypted Mii data + u64_be title_id; + u16_be applicaton_write_counter; // Encrypted Counter + u32_be application_area_id; + std::array unknown; + HashData hash; // Probably a SHA256-HMAC hash? + ApplicationArea application_area; // Encrypted Game data + HashData locked_hash; // Hash + std::array uuid; + AmiiboModelInfo model_info; + HashData keygen_salt; // Salt + u32 dynamic_lock; // Dynamic lock + u32 CFG0; // Defines memory protected by password + u32 CFG1; // Defines number of verification attempts + NTAG215Password password; // Password data +}; +static_assert(sizeof(NTAG215File) == 0x21C, "NTAG215File is an invalid size"); +static_assert(std::is_trivially_copyable_v, "NTAG215File must be trivially copyable."); +#pragma pack() + +struct EncryptedNTAG215File { + TagUuid uuid; // Unique serial number + u16 static_lock; // Set defined pages as read only + u32 compability_container; // Defines available memory + EncryptedAmiiboFile user_memory; // Writable data + u32 dynamic_lock; // Dynamic lock + u32 CFG0; // Defines memory protected by password + u32 CFG1; // Defines number of verification attempts + NTAG215Password password; // Password data +}; +static_assert(sizeof(EncryptedNTAG215File) == 0x21C, "EncryptedNTAG215File is an invalid size"); +static_assert(std::is_trivially_copyable_v, + "EncryptedNTAG215File must be trivially copyable."); + +} // namespace Service::NFP diff --git a/src/core/hle/service/nfp/nfp.cpp b/src/core/hle/service/nfp/nfp.cpp index 6c5b41dd1..4dba05a6a 100644 --- a/src/core/hle/service/nfp/nfp.cpp +++ b/src/core/hle/service/nfp/nfp.cpp @@ -4,6 +4,8 @@ #include #include +#include "common/fs/file.h" +#include "common/fs/path_util.h" #include "common/logging/log.h" #include "core/core.h" #include "core/hid/emulated_controller.h" @@ -12,6 +14,7 @@ #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/k_event.h" #include "core/hle/service/mii/mii_manager.h" +#include "core/hle/service/nfp/amiibo_crypto.h" #include "core/hle/service/nfp/nfp.h" #include "core/hle/service/nfp/nfp_user.h" @@ -19,12 +22,13 @@ namespace Service::NFP { namespace ErrCodes { constexpr Result DeviceNotFound(ErrorModule::NFP, 64); constexpr Result WrongDeviceState(ErrorModule::NFP, 73); +constexpr Result NfcDisabled(ErrorModule::NFP, 80); +constexpr Result WriteAmiiboFailed(ErrorModule::NFP, 88); constexpr Result ApplicationAreaIsNotInitialized(ErrorModule::NFP, 128); +constexpr Result WrongApplicationAreaId(ErrorModule::NFP, 152); constexpr Result ApplicationAreaExist(ErrorModule::NFP, 168); } // namespace ErrCodes -constexpr u32 ApplicationAreaSize = 0xD8; - IUser::IUser(Module::Interface& nfp_interface_, Core::System& system_) : ServiceFramework{system_, "NFP::IUser"}, service_context{system_, service_name}, nfp_interface{nfp_interface_} { @@ -39,7 +43,7 @@ IUser::IUser(Module::Interface& nfp_interface_, Core::System& system_) {7, &IUser::OpenApplicationArea, "OpenApplicationArea"}, {8, &IUser::GetApplicationArea, "GetApplicationArea"}, {9, &IUser::SetApplicationArea, "SetApplicationArea"}, - {10, nullptr, "Flush"}, + {10, &IUser::Flush, "Flush"}, {11, nullptr, "Restore"}, {12, &IUser::CreateApplicationArea, "CreateApplicationArea"}, {13, &IUser::GetTagInfo, "GetTagInfo"}, @@ -87,11 +91,23 @@ void IUser::Finalize(Kernel::HLERequestContext& ctx) { void IUser::ListDevices(Kernel::HLERequestContext& ctx) { LOG_INFO(Service_NFP, "called"); + if (state == State::NonInitialized) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ErrCodes::NfcDisabled); + return; + } + std::vector devices; // TODO(german77): Loop through all interfaces devices.push_back(nfp_interface.GetHandle()); + if (devices.size() == 0) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ErrCodes::DeviceNotFound); + return; + } + ctx.WriteBuffer(devices); IPC::ResponseBuilder rb{ctx, 3}; @@ -105,6 +121,12 @@ void IUser::StartDetection(Kernel::HLERequestContext& ctx) { const auto nfp_protocol{rp.Pop()}; LOG_INFO(Service_NFP, "called, device_handle={}, nfp_protocol={}", device_handle, nfp_protocol); + if (state == State::NonInitialized) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ErrCodes::NfcDisabled); + return; + } + // TODO(german77): Loop through all interfaces if (device_handle == nfp_interface.GetHandle()) { const auto result = nfp_interface.StartDetection(nfp_protocol); @@ -124,6 +146,12 @@ void IUser::StopDetection(Kernel::HLERequestContext& ctx) { const auto device_handle{rp.Pop()}; LOG_INFO(Service_NFP, "called, device_handle={}", device_handle); + if (state == State::NonInitialized) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ErrCodes::NfcDisabled); + return; + } + // TODO(german77): Loop through all interfaces if (device_handle == nfp_interface.GetHandle()) { const auto result = nfp_interface.StopDetection(); @@ -146,6 +174,12 @@ void IUser::Mount(Kernel::HLERequestContext& ctx) { LOG_INFO(Service_NFP, "called, device_handle={}, model_type={}, mount_target={}", device_handle, model_type, mount_target); + if (state == State::NonInitialized) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ErrCodes::NfcDisabled); + return; + } + // TODO(german77): Loop through all interfaces if (device_handle == nfp_interface.GetHandle()) { const auto result = nfp_interface.Mount(); @@ -165,6 +199,12 @@ void IUser::Unmount(Kernel::HLERequestContext& ctx) { const auto device_handle{rp.Pop()}; LOG_INFO(Service_NFP, "called, device_handle={}", device_handle); + if (state == State::NonInitialized) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ErrCodes::NfcDisabled); + return; + } + // TODO(german77): Loop through all interfaces if (device_handle == nfp_interface.GetHandle()) { const auto result = nfp_interface.Unmount(); @@ -186,6 +226,12 @@ void IUser::OpenApplicationArea(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}, access_id={}", device_handle, access_id); + if (state == State::NonInitialized) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ErrCodes::NfcDisabled); + return; + } + // TODO(german77): Loop through all interfaces if (device_handle == nfp_interface.GetHandle()) { const auto result = nfp_interface.OpenApplicationArea(access_id); @@ -205,9 +251,15 @@ void IUser::GetApplicationArea(Kernel::HLERequestContext& ctx) { const auto device_handle{rp.Pop()}; LOG_INFO(Service_NFP, "called, device_handle={}", device_handle); + if (state == State::NonInitialized) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ErrCodes::NfcDisabled); + return; + } + // TODO(german77): Loop through all interfaces if (device_handle == nfp_interface.GetHandle()) { - std::vector data{}; + ApplicationArea data{}; const auto result = nfp_interface.GetApplicationArea(data); ctx.WriteBuffer(data); IPC::ResponseBuilder rb{ctx, 3}; @@ -229,6 +281,12 @@ void IUser::SetApplicationArea(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}, data_size={}", device_handle, data.size()); + if (state == State::NonInitialized) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ErrCodes::NfcDisabled); + return; + } + // TODO(german77): Loop through all interfaces if (device_handle == nfp_interface.GetHandle()) { const auto result = nfp_interface.SetApplicationArea(data); @@ -243,6 +301,31 @@ void IUser::SetApplicationArea(Kernel::HLERequestContext& ctx) { rb.Push(ErrCodes::DeviceNotFound); } +void IUser::Flush(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto device_handle{rp.Pop()}; + LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}", device_handle); + + if (state == State::NonInitialized) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ErrCodes::NfcDisabled); + return; + } + + // TODO(german77): Loop through all interfaces + if (device_handle == nfp_interface.GetHandle()) { + const auto result = nfp_interface.Flush(); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + return; + } + + LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ErrCodes::DeviceNotFound); +} + void IUser::CreateApplicationArea(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto device_handle{rp.Pop()}; @@ -251,6 +334,12 @@ void IUser::CreateApplicationArea(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}, data_size={}, access_id={}", device_handle, access_id, data.size()); + if (state == State::NonInitialized) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ErrCodes::NfcDisabled); + return; + } + // TODO(german77): Loop through all interfaces if (device_handle == nfp_interface.GetHandle()) { const auto result = nfp_interface.CreateApplicationArea(access_id, data); @@ -270,6 +359,12 @@ void IUser::GetTagInfo(Kernel::HLERequestContext& ctx) { const auto device_handle{rp.Pop()}; LOG_INFO(Service_NFP, "called, device_handle={}", device_handle); + if (state == State::NonInitialized) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ErrCodes::NfcDisabled); + return; + } + // TODO(german77): Loop through all interfaces if (device_handle == nfp_interface.GetHandle()) { TagInfo tag_info{}; @@ -291,6 +386,12 @@ void IUser::GetRegisterInfo(Kernel::HLERequestContext& ctx) { const auto device_handle{rp.Pop()}; LOG_INFO(Service_NFP, "called, device_handle={}", device_handle); + if (state == State::NonInitialized) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ErrCodes::NfcDisabled); + return; + } + // TODO(german77): Loop through all interfaces if (device_handle == nfp_interface.GetHandle()) { RegisterInfo register_info{}; @@ -312,6 +413,12 @@ void IUser::GetCommonInfo(Kernel::HLERequestContext& ctx) { const auto device_handle{rp.Pop()}; LOG_INFO(Service_NFP, "called, device_handle={}", device_handle); + if (state == State::NonInitialized) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ErrCodes::NfcDisabled); + return; + } + // TODO(german77): Loop through all interfaces if (device_handle == nfp_interface.GetHandle()) { CommonInfo common_info{}; @@ -333,6 +440,12 @@ void IUser::GetModelInfo(Kernel::HLERequestContext& ctx) { const auto device_handle{rp.Pop()}; LOG_INFO(Service_NFP, "called, device_handle={}", device_handle); + if (state == State::NonInitialized) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ErrCodes::NfcDisabled); + return; + } + // TODO(german77): Loop through all interfaces if (device_handle == nfp_interface.GetHandle()) { ModelInfo model_info{}; @@ -354,6 +467,12 @@ void IUser::AttachActivateEvent(Kernel::HLERequestContext& ctx) { const auto device_handle{rp.Pop()}; LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle); + if (state == State::NonInitialized) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ErrCodes::NfcDisabled); + return; + } + // TODO(german77): Loop through all interfaces if (device_handle == nfp_interface.GetHandle()) { IPC::ResponseBuilder rb{ctx, 2, 1}; @@ -373,6 +492,12 @@ void IUser::AttachDeactivateEvent(Kernel::HLERequestContext& ctx) { const auto device_handle{rp.Pop()}; LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle); + if (state == State::NonInitialized) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ErrCodes::NfcDisabled); + return; + } + // TODO(german77): Loop through all interfaces if (device_handle == nfp_interface.GetHandle()) { IPC::ResponseBuilder rb{ctx, 2, 1}; @@ -419,6 +544,12 @@ void IUser::GetNpadId(Kernel::HLERequestContext& ctx) { const auto device_handle{rp.Pop()}; LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle); + if (state == State::NonInitialized) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ErrCodes::NfcDisabled); + return; + } + // TODO(german77): Loop through all interfaces if (device_handle == nfp_interface.GetHandle()) { IPC::ResponseBuilder rb{ctx, 3}; @@ -442,7 +573,7 @@ void IUser::GetApplicationAreaSize(Kernel::HLERequestContext& ctx) { if (device_handle == nfp_interface.GetHandle()) { IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - rb.Push(ApplicationAreaSize); + rb.Push(sizeof(ApplicationArea)); return; } @@ -455,6 +586,12 @@ void IUser::GetApplicationAreaSize(Kernel::HLERequestContext& ctx) { void IUser::AttachAvailabilityChangeEvent(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_NFP, "(STUBBED) called"); + if (state == State::NonInitialized) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ErrCodes::NfcDisabled); + return; + } + IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(ResultSuccess); rb.PushCopyObjects(availability_change_event->GetReadableEvent()); @@ -478,36 +615,42 @@ void Module::Interface::CreateUserInterface(Kernel::HLERequestContext& ctx) { rb.PushIpcInterface(*this, system); } -bool Module::Interface::LoadAmiibo(const std::vector& buffer) { - if (device_state != DeviceState::SearchingForTag) { - LOG_ERROR(Service_NFP, "Game is not looking for amiibos, current state {}", device_state); - return false; - } - - constexpr auto tag_size = sizeof(NTAG215File); +bool Module::Interface::LoadAmiiboFile(const std::string& filename) { constexpr auto tag_size_without_password = sizeof(NTAG215File) - sizeof(NTAG215Password); + const Common::FS::IOFile amiibo_file{filename, Common::FS::FileAccessMode::Read, + Common::FS::FileType::BinaryFile}; - std::vector amiibo_buffer = buffer; + if (!amiibo_file.IsOpen()) { + LOG_ERROR(Core, "Amiibo is already on use"); + return false; + } - if (amiibo_buffer.size() < tag_size_without_password) { - LOG_ERROR(Service_NFP, "Wrong file size {}", buffer.size()); + // Workaround for files with missing password data + std::array buffer{}; + if (amiibo_file.Read(buffer) < tag_size_without_password) { + LOG_ERROR(Core, "Failed to read amiibo file"); return false; } + memcpy(&encrypted_tag_data, buffer.data(), sizeof(EncryptedNTAG215File)); - // Ensure it has the correct size - if (amiibo_buffer.size() != tag_size) { - amiibo_buffer.resize(tag_size, 0); + if (!AmiiboCrypto::IsAmiiboValid(encrypted_tag_data)) { + LOG_INFO(Service_NFP, "Invalid amiibo"); + return false; } - LOG_INFO(Service_NFP, "Amiibo detected"); - std::memcpy(&tag_data, buffer.data(), tag_size); + file_path = filename; + return true; +} - if (!IsAmiiboValid()) { +bool Module::Interface::LoadAmiibo(const std::string& filename) { + if (device_state != DeviceState::SearchingForTag) { + LOG_ERROR(Service_NFP, "Game is not looking for amiibos, current state {}", device_state); return false; } - // This value can't be dumped from a tag. Generate it - tag_data.password.PWD = GetTagPassword(tag_data.uuid); + if (!LoadAmiiboFile(filename)) { + return false; + } device_state = DeviceState::TagFound; activate_event->GetWritableEvent().Signal(); @@ -517,55 +660,13 @@ bool Module::Interface::LoadAmiibo(const std::vector& buffer) { void Module::Interface::CloseAmiibo() { LOG_INFO(Service_NFP, "Remove amiibo"); device_state = DeviceState::TagRemoved; + is_data_decoded = false; is_application_area_initialized = false; - application_area_id = 0; - application_area_data.clear(); + encrypted_tag_data = {}; + tag_data = {}; deactivate_event->GetWritableEvent().Signal(); } -bool Module::Interface::IsAmiiboValid() const { - const auto& amiibo_data = tag_data.user_memory; - LOG_DEBUG(Service_NFP, "uuid_lock=0x{0:x}", tag_data.lock_bytes); - LOG_DEBUG(Service_NFP, "compability_container=0x{0:x}", tag_data.compability_container); - LOG_DEBUG(Service_NFP, "crypto_init=0x{0:x}", amiibo_data.crypto_init); - LOG_DEBUG(Service_NFP, "write_count={}", amiibo_data.write_count); - - LOG_DEBUG(Service_NFP, "character_id=0x{0:x}", amiibo_data.model_info.character_id); - LOG_DEBUG(Service_NFP, "character_variant={}", amiibo_data.model_info.character_variant); - LOG_DEBUG(Service_NFP, "amiibo_type={}", amiibo_data.model_info.amiibo_type); - LOG_DEBUG(Service_NFP, "model_number=0x{0:x}", amiibo_data.model_info.model_number); - LOG_DEBUG(Service_NFP, "series={}", amiibo_data.model_info.series); - LOG_DEBUG(Service_NFP, "fixed_value=0x{0:x}", amiibo_data.model_info.fixed); - - LOG_DEBUG(Service_NFP, "tag_dynamic_lock=0x{0:x}", tag_data.dynamic_lock); - LOG_DEBUG(Service_NFP, "tag_CFG0=0x{0:x}", tag_data.CFG0); - LOG_DEBUG(Service_NFP, "tag_CFG1=0x{0:x}", tag_data.CFG1); - - // Check against all know constants on an amiibo binary - if (tag_data.lock_bytes != 0xE00F) { - return false; - } - if (tag_data.compability_container != 0xEEFF10F1U) { - return false; - } - if ((amiibo_data.crypto_init & 0xFF) != 0xA5) { - return false; - } - if (amiibo_data.model_info.fixed != 0x02) { - return false; - } - if ((tag_data.dynamic_lock & 0xFFFFFF) != 0x0F0001) { - return false; - } - if (tag_data.CFG0 != 0x04000000U) { - return false; - } - if (tag_data.CFG1 != 0x5F) { - return false; - } - return true; -} - Kernel::KReadableEvent& Module::Interface::GetActivateEvent() const { return activate_event->GetReadableEvent(); } @@ -576,13 +677,20 @@ Kernel::KReadableEvent& Module::Interface::GetDeactivateEvent() const { void Module::Interface::Initialize() { device_state = DeviceState::Initialized; + is_data_decoded = false; + is_application_area_initialized = false; + encrypted_tag_data = {}; + tag_data = {}; } void Module::Interface::Finalize() { + if (device_state == DeviceState::TagMounted) { + Unmount(); + } + if (device_state == DeviceState::SearchingForTag || device_state == DeviceState::TagRemoved) { + StopDetection(); + } device_state = DeviceState::Unaviable; - is_application_area_initialized = false; - application_area_id = 0; - application_area_data.clear(); } Result Module::Interface::StartDetection(s32 protocol_) { @@ -618,42 +726,102 @@ Result Module::Interface::StopDetection() { return ErrCodes::WrongDeviceState; } -Result Module::Interface::Mount() { - if (device_state == DeviceState::TagFound) { - device_state = DeviceState::TagMounted; +Result Module::Interface::Flush() { + // Ignore write command if we can't encrypt the data + if (!is_data_decoded) { return ResultSuccess; } - LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); - return ErrCodes::WrongDeviceState; + constexpr auto tag_size_without_password = sizeof(NTAG215File) - sizeof(NTAG215Password); + EncryptedNTAG215File tmp_encrypted_tag_data{}; + const Common::FS::IOFile amiibo_file{file_path, Common::FS::FileAccessMode::ReadWrite, + Common::FS::FileType::BinaryFile}; + + if (!amiibo_file.IsOpen()) { + LOG_ERROR(Core, "Amiibo is already on use"); + return ErrCodes::WriteAmiiboFailed; + } + + // Workaround for files with missing password data + std::array buffer{}; + if (amiibo_file.Read(buffer) < tag_size_without_password) { + LOG_ERROR(Core, "Failed to read amiibo file"); + return ErrCodes::WriteAmiiboFailed; + } + memcpy(&tmp_encrypted_tag_data, buffer.data(), sizeof(EncryptedNTAG215File)); + + if (!AmiiboCrypto::IsAmiiboValid(tmp_encrypted_tag_data)) { + LOG_INFO(Service_NFP, "Invalid amiibo"); + return ErrCodes::WriteAmiiboFailed; + } + + bool is_uuid_equal = memcmp(tmp_encrypted_tag_data.uuid.data(), tag_data.uuid.data(), 8) == 0; + bool is_character_equal = tmp_encrypted_tag_data.user_memory.model_info.character_id == + tag_data.model_info.character_id; + if (!is_uuid_equal || !is_character_equal) { + LOG_ERROR(Core, "Not the same amiibo"); + return ErrCodes::WriteAmiiboFailed; + } + + if (!AmiiboCrypto::EncodeAmiibo(tag_data, encrypted_tag_data)) { + LOG_ERROR(Core, "Failed to encode data"); + return ErrCodes::WriteAmiiboFailed; + } + + // Return to the start of the file + if (!amiibo_file.Seek(0)) { + LOG_ERROR(Service_NFP, "Error writting to file"); + return ErrCodes::WriteAmiiboFailed; + } + + if (!amiibo_file.Write(encrypted_tag_data)) { + LOG_ERROR(Service_NFP, "Error writting to file"); + return ErrCodes::WriteAmiiboFailed; + } + + return ResultSuccess; +} + +Result Module::Interface::Mount() { + if (device_state != DeviceState::TagFound) { + LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); + return ErrCodes::WrongDeviceState; + } + + is_data_decoded = AmiiboCrypto::DecodeAmiibo(encrypted_tag_data, tag_data); + LOG_INFO(Service_NFP, "Is amiibo decoded {}", is_data_decoded); + + is_application_area_initialized = false; + device_state = DeviceState::TagMounted; + return ResultSuccess; } Result Module::Interface::Unmount() { - if (device_state == DeviceState::TagMounted) { - is_application_area_initialized = false; - application_area_id = 0; - application_area_data.clear(); - device_state = DeviceState::TagFound; - return ResultSuccess; + if (device_state != DeviceState::TagMounted) { + LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); + return ErrCodes::WrongDeviceState; } - LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); - return ErrCodes::WrongDeviceState; + is_data_decoded = false; + is_application_area_initialized = false; + device_state = DeviceState::TagFound; + return ResultSuccess; } Result Module::Interface::GetTagInfo(TagInfo& tag_info) const { - if (device_state == DeviceState::TagFound || device_state == DeviceState::TagMounted) { - tag_info = { - .uuid = tag_data.uuid, - .uuid_length = static_cast(tag_data.uuid.size()), - .protocol = protocol, - .tag_type = static_cast(tag_data.user_memory.model_info.amiibo_type), - }; - return ResultSuccess; + if (device_state != DeviceState::TagFound && device_state != DeviceState::TagMounted) { + LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); + return ErrCodes::WrongDeviceState; } - LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); - return ErrCodes::WrongDeviceState; + tag_info = { + .uuid = encrypted_tag_data.uuid, + .uuid_length = static_cast(encrypted_tag_data.uuid.size()), + .protocol = protocol, + .tag_type = static_cast(encrypted_tag_data.user_memory.model_info.amiibo_type), + }; + + return ResultSuccess; } Result Module::Interface::GetCommonInfo(CommonInfo& common_info) const { @@ -662,14 +830,28 @@ Result Module::Interface::GetCommonInfo(CommonInfo& common_info) const { return ErrCodes::WrongDeviceState; } - // Read this data from the amiibo save file + if (is_data_decoded) { + const auto& settings = tag_data.settings; + // TODO: Validate this data + common_info = { + .last_write_year = static_cast(settings.write_date.year.Value()), + .last_write_month = static_cast(settings.write_date.month.Value()), + .last_write_day = static_cast(settings.write_date.day.Value()), + .write_counter = settings.crc_counter, + .version = 1, + .application_area_size = sizeof(ApplicationArea), + }; + return ResultSuccess; + } + + // Generate a generic answer common_info = { .last_write_year = 2022, .last_write_month = 2, .last_write_day = 7, - .write_counter = tag_data.user_memory.write_count, + .write_counter = 0, .version = 1, - .application_area_size = ApplicationAreaSize, + .application_area_size = sizeof(ApplicationArea), }; return ResultSuccess; } @@ -680,7 +862,15 @@ Result Module::Interface::GetModelInfo(ModelInfo& model_info) const { return ErrCodes::WrongDeviceState; } - model_info = tag_data.user_memory.model_info; + const auto& model_info_data = encrypted_tag_data.user_memory.model_info; + model_info = { + .character_id = model_info_data.character_id, + .character_variant = model_info_data.character_variant, + .amiibo_type = model_info_data.amiibo_type, + .model_number = model_info_data.model_number, + .series = model_info_data.series, + .constant_value = model_info_data.constant_value, + }; return ResultSuccess; } @@ -690,9 +880,30 @@ Result Module::Interface::GetRegisterInfo(RegisterInfo& register_info) const { return ErrCodes::WrongDeviceState; } - Service::Mii::MiiManager manager; + if (is_data_decoded) { + const auto& settings = tag_data.settings; + + // Amiibo name is u16 while the register info is u8. Figure out how to handle this properly + std::array amiibo_name{}; + for (std::size_t i = 0; i < sizeof(amiibo_name) - 1; ++i) { + amiibo_name[i] = static_cast(settings.amiibo_name[i]); + } + + // TODO: Validate this data + register_info = { + .mii_char_info = AmiiboCrypto::AmiiboRegisterInfoToMii(tag_data.owner_mii), + .first_write_year = static_cast(settings.init_date.year.Value()), + .first_write_month = static_cast(settings.init_date.month.Value()), + .first_write_day = static_cast(settings.init_date.day.Value()), + .amiibo_name = amiibo_name, + .unknown = {}, + }; + + return ResultSuccess; + } - // Read this data from the amiibo save file + // Generate a generic answer + Service::Mii::MiiManager manager; register_info = { .mii_char_info = manager.BuildDefault(0), .first_write_year = 2022, @@ -709,29 +920,39 @@ Result Module::Interface::OpenApplicationArea(u32 access_id) { LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); return ErrCodes::WrongDeviceState; } - if (AmiiboApplicationDataExist(access_id)) { - application_area_data = LoadAmiiboApplicationData(access_id); - application_area_id = access_id; - is_application_area_initialized = true; + + // Fallback for lack of amiibo keys + if (!is_data_decoded) { + LOG_WARNING(Service_NFP, "Application area is not initialized"); + return ErrCodes::ApplicationAreaIsNotInitialized; } - if (!is_application_area_initialized) { + + if (tag_data.settings.settings.appdata_initialized == 0) { LOG_WARNING(Service_NFP, "Application area is not initialized"); return ErrCodes::ApplicationAreaIsNotInitialized; } + + if (tag_data.application_area_id != access_id) { + LOG_WARNING(Service_NFP, "Wrong application area id"); + return ErrCodes::WrongApplicationAreaId; + } + + is_application_area_initialized = true; return ResultSuccess; } -Result Module::Interface::GetApplicationArea(std::vector& data) const { +Result Module::Interface::GetApplicationArea(ApplicationArea& data) const { if (device_state != DeviceState::TagMounted) { LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); return ErrCodes::WrongDeviceState; } + if (!is_application_area_initialized) { LOG_ERROR(Service_NFP, "Application area is not initialized"); return ErrCodes::ApplicationAreaIsNotInitialized; } - data = application_area_data; + data = tag_data.application_area; return ResultSuccess; } @@ -741,12 +962,18 @@ Result Module::Interface::SetApplicationArea(const std::vector& data) { LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); return ErrCodes::WrongDeviceState; } + if (!is_application_area_initialized) { LOG_ERROR(Service_NFP, "Application area is not initialized"); return ErrCodes::ApplicationAreaIsNotInitialized; } - application_area_data = data; - SaveAmiiboApplicationData(application_area_id, application_area_data); + + if (data.size() != sizeof(ApplicationArea)) { + LOG_ERROR(Service_NFP, "Wrong data size {}", data.size()); + return ResultUnknown; + } + + std::memcpy(&tag_data.application_area, data.data(), sizeof(ApplicationArea)); return ResultSuccess; } @@ -755,30 +982,21 @@ Result Module::Interface::CreateApplicationArea(u32 access_id, const std::vector LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); return ErrCodes::WrongDeviceState; } - if (AmiiboApplicationDataExist(access_id)) { + + if (tag_data.settings.settings.appdata_initialized != 0) { LOG_ERROR(Service_NFP, "Application area already exist"); return ErrCodes::ApplicationAreaExist; } - application_area_data = data; - application_area_id = access_id; - SaveAmiiboApplicationData(application_area_id, application_area_data); - return ResultSuccess; -} -bool Module::Interface::AmiiboApplicationDataExist(u32 access_id) const { - // TODO(german77): Check if file exist - return false; -} + if (data.size() != sizeof(ApplicationArea)) { + LOG_ERROR(Service_NFP, "Wrong data size {}", data.size()); + return ResultUnknown; + } -std::vector Module::Interface::LoadAmiiboApplicationData(u32 access_id) const { - // TODO(german77): Read file - std::vector data(ApplicationAreaSize); - return data; -} + std::memcpy(&tag_data.application_area, data.data(), sizeof(ApplicationArea)); + tag_data.application_area_id = access_id; -void Module::Interface::SaveAmiiboApplicationData(u32 access_id, - const std::vector& data) const { - // TODO(german77): Save file + return ResultSuccess; } u64 Module::Interface::GetHandle() const { @@ -794,15 +1012,6 @@ Core::HID::NpadIdType Module::Interface::GetNpadId() const { return npad_id; } -u32 Module::Interface::GetTagPassword(const TagUuid& uuid) const { - // Verifiy that the generated password is correct - u32 password = 0xAA ^ (uuid[1] ^ uuid[3]); - password &= (0x55 ^ (uuid[2] ^ uuid[4])) << 8; - password &= (0xAA ^ (uuid[3] ^ uuid[5])) << 16; - password &= (0x55 ^ (uuid[4] ^ uuid[6])) << 24; - return password; -} - void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) { auto module = std::make_shared(); std::make_shared(module, system)->InstallAsService(service_manager); diff --git a/src/core/hle/service/nfp/nfp.h b/src/core/hle/service/nfp/nfp.h index 0fc808781..3410dcfb0 100644 --- a/src/core/hle/service/nfp/nfp.h +++ b/src/core/hle/service/nfp/nfp.h @@ -9,6 +9,7 @@ #include "common/common_funcs.h" #include "core/hle/service/kernel_helpers.h" #include "core/hle/service/mii/types.h" +#include "core/hle/service/nfp/amiibo_types.h" #include "core/hle/service/service.h" namespace Kernel { @@ -21,72 +22,6 @@ enum class NpadIdType : u32; } // namespace Core::HID namespace Service::NFP { - -enum class ServiceType : u32 { - User, - Debug, - System, -}; - -enum class State : u32 { - NonInitialized, - Initialized, -}; - -enum class DeviceState : u32 { - Initialized, - SearchingForTag, - TagFound, - TagRemoved, - TagMounted, - Unaviable, - Finalized, -}; - -enum class ModelType : u32 { - Amiibo, -}; - -enum class MountTarget : u32 { - Rom, - Ram, - All, -}; - -enum class AmiiboType : u8 { - Figure, - Card, - Yarn, -}; - -enum class AmiiboSeries : u8 { - SuperSmashBros, - SuperMario, - ChibiRobo, - YoshiWoollyWorld, - Splatoon, - AnimalCrossing, - EightBitMario, - Skylanders, - Unknown8, - TheLegendOfZelda, - ShovelKnight, - Unknown11, - Kiby, - Pokemon, - MarioSportsSuperstars, - MonsterHunter, - BoxBoy, - Pikmin, - FireEmblem, - Metroid, - Others, - MegaMan, - Diablo -}; - -using TagUuid = std::array; - struct TagInfo { TagUuid uuid; u8 uuid_length; @@ -114,10 +49,8 @@ struct ModelInfo { AmiiboType amiibo_type; u16 model_number; AmiiboSeries series; - u8 fixed; // Must be 02 - INSERT_PADDING_BYTES(0x4); // Unknown - INSERT_PADDING_BYTES(0x20); // Probably a SHA256-(HMAC?) hash - INSERT_PADDING_BYTES(0x14); // SHA256-HMAC + u8 constant_value; // Must be 02 + INSERT_PADDING_BYTES(0x38); // Unknown }; static_assert(sizeof(ModelInfo) == 0x40, "ModelInfo is an invalid size"); @@ -126,7 +59,7 @@ struct RegisterInfo { u16 first_write_year; u8 first_write_month; u8 first_write_day; - std::array amiibo_name; + std::array amiibo_name; u8 unknown; INSERT_PADDING_BYTES(0x98); }; @@ -140,39 +73,9 @@ public: const char* name); ~Interface() override; - struct EncryptedAmiiboFile { - u16 crypto_init; // Must be A5 XX - u16 write_count; // Number of times the amiibo has been written? - INSERT_PADDING_BYTES(0x20); // System crypts - INSERT_PADDING_BYTES(0x20); // SHA256-(HMAC?) hash - ModelInfo model_info; // This struct is bigger than documentation - INSERT_PADDING_BYTES(0xC); // SHA256-HMAC - INSERT_PADDING_BYTES(0x114); // section 1 encrypted buffer - INSERT_PADDING_BYTES(0x54); // section 2 encrypted buffer - }; - static_assert(sizeof(EncryptedAmiiboFile) == 0x1F8, "AmiiboFile is an invalid size"); - - struct NTAG215Password { - u32 PWD; // Password to allow write access - u16 PACK; // Password acknowledge reply - u16 RFUI; // Reserved for future use - }; - static_assert(sizeof(NTAG215Password) == 0x8, "NTAG215Password is an invalid size"); - - struct NTAG215File { - TagUuid uuid; // Unique serial number - u16 lock_bytes; // Set defined pages as read only - u32 compability_container; // Defines available memory - EncryptedAmiiboFile user_memory; // Writable data - u32 dynamic_lock; // Dynamic lock - u32 CFG0; // Defines memory protected by password - u32 CFG1; // Defines number of verification attempts - NTAG215Password password; // Password data - }; - static_assert(sizeof(NTAG215File) == 0x21C, "NTAG215File is an invalid size"); - void CreateUserInterface(Kernel::HLERequestContext& ctx); - bool LoadAmiibo(const std::vector& buffer); + bool LoadAmiibo(const std::string& filename); + bool LoadAmiiboFile(const std::string& filename); void CloseAmiibo(); void Initialize(); @@ -182,6 +85,7 @@ public: Result StopDetection(); Result Mount(); Result Unmount(); + Result Flush(); Result GetTagInfo(TagInfo& tag_info) const; Result GetCommonInfo(CommonInfo& common_info) const; @@ -189,7 +93,7 @@ public: Result GetRegisterInfo(RegisterInfo& register_info) const; Result OpenApplicationArea(u32 access_id); - Result GetApplicationArea(std::vector& data) const; + Result GetApplicationArea(ApplicationArea& data) const; Result SetApplicationArea(const std::vector& data); Result CreateApplicationArea(u32 access_id, const std::vector& data); @@ -204,27 +108,19 @@ public: std::shared_ptr module; private: - /// Validates that the amiibo file is not corrupted - bool IsAmiiboValid() const; - - bool AmiiboApplicationDataExist(u32 access_id) const; - std::vector LoadAmiiboApplicationData(u32 access_id) const; - void SaveAmiiboApplicationData(u32 access_id, const std::vector& data) const; - - /// return password needed to allow write access to protected memory - u32 GetTagPassword(const TagUuid& uuid) const; - const Core::HID::NpadIdType npad_id; - DeviceState device_state{DeviceState::Unaviable}; - KernelHelpers::ServiceContext service_context; + bool is_data_decoded{}; + bool is_application_area_initialized{}; + s32 protocol; + std::string file_path{}; Kernel::KEvent* activate_event; Kernel::KEvent* deactivate_event; + DeviceState device_state{DeviceState::Unaviable}; + KernelHelpers::ServiceContext service_context; + NTAG215File tag_data{}; - s32 protocol; - bool is_application_area_initialized{}; - u32 application_area_id; - std::vector application_area_data; + EncryptedNTAG215File encrypted_tag_data{}; }; }; @@ -243,6 +139,7 @@ private: void OpenApplicationArea(Kernel::HLERequestContext& ctx); void GetApplicationArea(Kernel::HLERequestContext& ctx); void SetApplicationArea(Kernel::HLERequestContext& ctx); + void Flush(Kernel::HLERequestContext& ctx); void CreateApplicationArea(Kernel::HLERequestContext& ctx); void GetTagInfo(Kernel::HLERequestContext& ctx); void GetRegisterInfo(Kernel::HLERequestContext& ctx); diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index a85adc072..fa9548fed 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -3254,26 +3254,7 @@ void GMainWindow::LoadAmiibo(const QString& filename) { return; } - QFile nfc_file{filename}; - if (!nfc_file.open(QIODevice::ReadOnly)) { - QMessageBox::warning(this, tr("Error opening Amiibo data file"), - tr("Unable to open Amiibo file \"%1\" for reading.").arg(filename)); - return; - } - - const u64 nfc_file_size = nfc_file.size(); - std::vector buffer(nfc_file_size); - const u64 read_size = nfc_file.read(reinterpret_cast(buffer.data()), nfc_file_size); - if (nfc_file_size != read_size) { - QMessageBox::warning(this, tr("Error reading Amiibo data file"), - tr("Unable to fully read Amiibo data. Expected to read %1 bytes, but " - "was only able to read %2 bytes.") - .arg(nfc_file_size) - .arg(read_size)); - return; - } - - if (!nfc->LoadAmiibo(buffer)) { + if (!nfc->LoadAmiibo(filename.toStdString())) { QMessageBox::warning(this, tr("Error loading Amiibo data"), tr("Unable to load Amiibo data.")); } -- cgit v1.2.3 From 19a4e12e6ea6c3d06ee227f3ef1a6cbf93850f6e Mon Sep 17 00:00:00 2001 From: Narr the Reg Date: Tue, 30 Aug 2022 00:33:47 -0500 Subject: core: nfp: Implement Convert and RecreateApplicationArea, accuracy fixes --- .../hle/service/am/applets/applet_mii_edit_types.h | 2 +- src/core/hle/service/mii/mii.cpp | 32 ++-- src/core/hle/service/mii/mii_manager.cpp | 88 ++++++++++- src/core/hle/service/mii/mii_manager.h | 9 +- src/core/hle/service/mii/types.h | 138 +++++++++++++++++- src/core/hle/service/nfp/amiibo_crypto.cpp | 81 +---------- src/core/hle/service/nfp/amiibo_crypto.h | 3 - src/core/hle/service/nfp/amiibo_types.h | 161 ++++----------------- src/core/hle/service/nfp/nfp.cpp | 95 ++++++++++-- src/core/hle/service/nfp/nfp.h | 4 +- 10 files changed, 356 insertions(+), 257 deletions(-) (limited to 'src/core') 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 { }; struct MiiEditCharInfo { - Service::Mii::MiiInfo mii_info{}; + Service::Mii::CharInfo mii_info{}; }; static_assert(sizeof(MiiEditCharInfo) == 0x58, "MiiEditCharInfo has incorrect size."); 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: {20, nullptr, "IsBrokenDatabaseWithClearFlag"}, {21, &IDatabaseService::GetIndex, "GetIndex"}, {22, &IDatabaseService::SetInterfaceVersion, "SetInterfaceVersion"}, - {23, nullptr, "Convert"}, + {23, &IDatabaseService::Convert, "Convert"}, {24, nullptr, "ConvertCoreDataToCharInfo"}, {25, nullptr, "ConvertCharInfoToCoreData"}, {26, nullptr, "Append"}, @@ -130,7 +130,7 @@ private: return; } - std::vector values; + std::vector values; for (const auto& element : *result) { values.emplace_back(element.info); } @@ -144,7 +144,7 @@ private: void UpdateLatest(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto info{rp.PopRaw()}; + const auto info{rp.PopRaw()}; const auto source_flag{rp.PopRaw()}; LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag); @@ -156,9 +156,9 @@ private: return; } - IPC::ResponseBuilder rb{ctx, 2 + sizeof(MiiInfo) / sizeof(u32)}; + IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; rb.Push(ResultSuccess); - rb.PushRaw(*result); + rb.PushRaw(*result); } void BuildRandom(Kernel::HLERequestContext& ctx) { @@ -191,9 +191,9 @@ private: return; } - IPC::ResponseBuilder rb{ctx, 2 + sizeof(MiiInfo) / sizeof(u32)}; + IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; rb.Push(ResultSuccess); - rb.PushRaw(manager.BuildRandom(age, gender, race)); + rb.PushRaw(manager.BuildRandom(age, gender, race)); } void BuildDefault(Kernel::HLERequestContext& ctx) { @@ -210,14 +210,14 @@ private: return; } - IPC::ResponseBuilder rb{ctx, 2 + sizeof(MiiInfo) / sizeof(u32)}; + IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; rb.Push(ResultSuccess); - rb.PushRaw(manager.BuildDefault(index)); + rb.PushRaw(manager.BuildDefault(index)); } void GetIndex(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto info{rp.PopRaw()}; + const auto info{rp.PopRaw()}; LOG_DEBUG(Service_Mii, "called"); @@ -239,6 +239,18 @@ private: rb.Push(ResultSuccess); } + void Convert(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + const auto mii_v3{rp.PopRaw()}; + + LOG_INFO(Service_Mii, "called"); + + IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; + rb.Push(ResultSuccess); + rb.PushRaw(manager.ConvertV3ToCharInfo(mii_v3)); + } + constexpr bool IsInterfaceVersionSupported(u32 interface_version) const { return current_interface_version >= interface_version; } diff --git a/src/core/hle/service/mii/mii_manager.cpp b/src/core/hle/service/mii/mii_manager.cpp index 544c92a00..97d1b948f 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 ResizeArray(const std::array& i return out; } -MiiInfo ConvertStoreDataToInfo(const MiiStoreData& data) { +CharInfo ConvertStoreDataToInfo(const MiiStoreData& data) { MiiStoreBitFields bf; std::memcpy(&bf, data.data.data.data(), sizeof(MiiStoreBitFields)); @@ -409,8 +409,8 @@ u32 MiiManager::GetCount(SourceFlag source_flag) const { return static_cast(count); } -ResultVal MiiManager::UpdateLatest([[maybe_unused]] const MiiInfo& info, - SourceFlag source_flag) { +ResultVal MiiManager::UpdateLatest([[maybe_unused]] const CharInfo& info, + SourceFlag source_flag) { if ((source_flag & SourceFlag::Database) == SourceFlag::None) { return ERROR_CANNOT_FIND_ENTRY; } @@ -419,14 +419,90 @@ ResultVal MiiManager::UpdateLatest([[maybe_unused]] const MiiInfo& info return ERROR_CANNOT_FIND_ENTRY; } -MiiInfo MiiManager::BuildRandom(Age age, Gender gender, Race race) { +CharInfo MiiManager::BuildRandom(Age age, Gender gender, Race race) { return ConvertStoreDataToInfo(BuildRandomStoreData(age, gender, race, user_id)); } -MiiInfo MiiManager::BuildDefault(std::size_t index) { +CharInfo MiiManager::BuildDefault(std::size_t index) { return ConvertStoreDataToInfo(BuildDefaultStoreData(RawData::DefaultMii.at(index), user_id)); } +CharInfo MiiManager::ConvertV3ToCharInfo(Ver3StoreData mii_v3) const { + Service::Mii::MiiManager manager; + auto mii = manager.BuildDefault(0); + + // Check if mii data exist + if (mii_v3.mii_name[0] == 0) { + return mii; + } + + // TODO: We are ignoring a bunch of data from the mii_v3 + + mii.gender = static_cast(mii_v3.mii_information.gender); + mii.favorite_color = static_cast(mii_v3.mii_information.favorite_color); + mii.height = mii_v3.height; + mii.build = mii_v3.build; + + mii.font_region = mii_v3.region_information.character_set; + memcpy(mii.name.data(), mii_v3.mii_name.data(), 10); + + mii.faceline_type = mii_v3.appearance_bits1.face_shape; + mii.faceline_color = mii_v3.appearance_bits1.skin_color; + mii.faceline_wrinkle = mii_v3.appearance_bits2.wrinkles; + mii.faceline_make = mii_v3.appearance_bits2.makeup; + + mii.hair_type = mii_v3.hair_style; + mii.hair_color = mii_v3.appearance_bits3.hair_color; + mii.hair_flip = mii_v3.appearance_bits3.flip_hair; + + mii.eye_type = static_cast(mii_v3.appearance_bits4.eye_type); + mii.eye_color = static_cast(mii_v3.appearance_bits4.eye_color); + mii.eye_scale = static_cast(mii_v3.appearance_bits4.eye_scale); + mii.eye_aspect = static_cast(mii_v3.appearance_bits4.eye_vertical_stretch); + mii.eye_rotate = static_cast(mii_v3.appearance_bits4.eye_rotation); + mii.eye_x = static_cast(mii_v3.appearance_bits4.eye_spacing); + mii.eye_y = static_cast(mii_v3.appearance_bits4.eye_y_position); + + mii.eyebrow_type = static_cast(mii_v3.appearance_bits5.eyebrow_style); + mii.eyebrow_color = static_cast(mii_v3.appearance_bits5.eyebrow_color); + mii.eyebrow_scale = static_cast(mii_v3.appearance_bits5.eyebrow_scale); + mii.eyebrow_aspect = static_cast(mii_v3.appearance_bits5.eyebrow_yscale); + mii.eyebrow_rotate = static_cast(mii_v3.appearance_bits5.eyebrow_rotation); + mii.eyebrow_x = static_cast(mii_v3.appearance_bits5.eyebrow_spacing); + mii.eyebrow_y = static_cast(mii_v3.appearance_bits5.eyebrow_y_position); + + mii.nose_type = static_cast(mii_v3.appearance_bits6.nose_type); + mii.nose_scale = static_cast(mii_v3.appearance_bits6.nose_scale); + mii.nose_y = static_cast(mii_v3.appearance_bits6.nose_y_position); + + mii.mouth_type = static_cast(mii_v3.appearance_bits7.mouth_type); + mii.mouth_color = static_cast(mii_v3.appearance_bits7.mouth_color); + mii.mouth_scale = static_cast(mii_v3.appearance_bits7.mouth_scale); + mii.mouth_aspect = static_cast(mii_v3.appearance_bits7.mouth_horizontal_stretch); + mii.mouth_y = static_cast(mii_v3.appearance_bits8.mouth_y_position); + + mii.mustache_type = static_cast(mii_v3.appearance_bits8.mustache_type); + mii.mustache_scale = static_cast(mii_v3.appearance_bits9.mustache_scale); + mii.mustache_y = static_cast(mii_v3.appearance_bits9.mustache_y_position); + + mii.beard_type = static_cast(mii_v3.appearance_bits9.bear_type); + mii.beard_color = static_cast(mii_v3.appearance_bits9.facial_hair_color); + + mii.glasses_type = static_cast(mii_v3.appearance_bits10.glasses_type); + mii.glasses_color = static_cast(mii_v3.appearance_bits10.glasses_color); + mii.glasses_scale = static_cast(mii_v3.appearance_bits10.glasses_scale); + mii.glasses_y = static_cast(mii_v3.appearance_bits10.glasses_y_position); + + mii.mole_type = static_cast(mii_v3.appearance_bits11.mole_enabled); + mii.mole_scale = static_cast(mii_v3.appearance_bits11.mole_scale); + mii.mole_x = static_cast(mii_v3.appearance_bits11.mole_x_position); + mii.mole_y = static_cast(mii_v3.appearance_bits11.mole_y_position); + + // TODO: Validate mii data + + return mii; +} + ResultVal> MiiManager::GetDefault(SourceFlag source_flag) { std::vector result; @@ -441,7 +517,7 @@ ResultVal> MiiManager::GetDefault(SourceFlag source_ return result; } -Result MiiManager::GetIndex([[maybe_unused]] const MiiInfo& info, u32& index) { +Result MiiManager::GetIndex([[maybe_unused]] const CharInfo& info, u32& index) { constexpr u32 INVALID_INDEX{0xFFFFFFFF}; 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: bool CheckAndResetUpdateCounter(SourceFlag source_flag, u64& current_update_counter); bool IsFullDatabase() const; u32 GetCount(SourceFlag source_flag) const; - ResultVal UpdateLatest(const MiiInfo& info, SourceFlag source_flag); - MiiInfo BuildRandom(Age age, Gender gender, Race race); - MiiInfo BuildDefault(std::size_t index); + ResultVal UpdateLatest(const CharInfo& info, SourceFlag source_flag); + CharInfo BuildRandom(Age age, Gender gender, Race race); + CharInfo BuildDefault(std::size_t index); + CharInfo ConvertV3ToCharInfo(Ver3StoreData mii_v3) const; ResultVal> GetDefault(SourceFlag source_flag); - Result GetIndex(const MiiInfo& info, u32& index); + Result GetIndex(const CharInfo& info, u32& index); private: 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 { }; DECLARE_ENUM_FLAG_OPERATORS(SourceFlag); -struct MiiInfo { +// nn::mii::CharInfo +struct CharInfo { Common::UUID uuid; std::array name; u8 font_region; @@ -140,16 +141,16 @@ struct MiiInfo { u8 mole_y; u8 padding; }; -static_assert(sizeof(MiiInfo) == 0x58, "MiiInfo has incorrect size."); -static_assert(std::has_unique_object_representations_v, - "All bits of MiiInfo must contribute to its value."); +static_assert(sizeof(CharInfo) == 0x58, "CharInfo has incorrect size."); +static_assert(std::has_unique_object_representations_v, + "All bits of CharInfo must contribute to its value."); #pragma pack(push, 4) struct MiiInfoElement { - MiiInfoElement(const MiiInfo& info_, Source source_) : info{info_}, source{source_} {} + MiiInfoElement(const CharInfo& info_, Source source_) : info{info_}, source{source_} {} - MiiInfo info{}; + CharInfo info{}; Source source{}; }; static_assert(sizeof(MiiInfoElement) == 0x5c, "MiiInfoElement has incorrect size."); @@ -243,6 +244,131 @@ static_assert(sizeof(MiiStoreBitFields) == 0x1c, "MiiStoreBitFields has incorrec static_assert(std::is_trivially_copyable_v, "MiiStoreBitFields is not trivially copyable."); +// This is nn::mii::Ver3StoreData +// Based on citra HLE::Applets::MiiData and PretendoNetwork. +// https://github.com/citra-emu/citra/blob/master/src/core/hle/applets/mii_selector.h#L48 +// https://github.com/PretendoNetwork/mii-js/blob/master/mii.js#L299 +struct Ver3StoreData { + u8 version; + union { + u8 raw; + + BitField<0, 1, u8> allow_copying; + BitField<1, 1, u8> profanity_flag; + BitField<2, 2, u8> region_lock; + BitField<4, 2, u8> character_set; + } region_information; + u16_be mii_id; + u64_be system_id; + u32_be specialness_and_creation_date; + std::array creator_mac; + u16_be padding; + union { + u16 raw; + + BitField<0, 1, u16> gender; + BitField<1, 4, u16> birth_month; + BitField<5, 5, u16> birth_day; + BitField<10, 4, u16> favorite_color; + BitField<14, 1, u16> favorite; + } mii_information; + std::array mii_name; + u8 height; + u8 build; + union { + u8 raw; + + BitField<0, 1, u8> disable_sharing; + BitField<1, 4, u8> face_shape; + BitField<5, 3, u8> skin_color; + } appearance_bits1; + union { + u8 raw; + + BitField<0, 4, u8> wrinkles; + BitField<4, 4, u8> makeup; + } appearance_bits2; + u8 hair_style; + union { + u8 raw; + + BitField<0, 3, u8> hair_color; + BitField<3, 1, u8> flip_hair; + } appearance_bits3; + union { + u32 raw; + + BitField<0, 6, u32> eye_type; + BitField<6, 3, u32> eye_color; + BitField<9, 4, u32> eye_scale; + BitField<13, 3, u32> eye_vertical_stretch; + BitField<16, 5, u32> eye_rotation; + BitField<21, 4, u32> eye_spacing; + BitField<25, 5, u32> eye_y_position; + } appearance_bits4; + union { + u32 raw; + + BitField<0, 5, u32> eyebrow_style; + BitField<5, 3, u32> eyebrow_color; + BitField<8, 4, u32> eyebrow_scale; + BitField<12, 3, u32> eyebrow_yscale; + BitField<16, 4, u32> eyebrow_rotation; + BitField<21, 4, u32> eyebrow_spacing; + BitField<25, 5, u32> eyebrow_y_position; + } appearance_bits5; + union { + u16 raw; + + BitField<0, 5, u16> nose_type; + BitField<5, 4, u16> nose_scale; + BitField<9, 5, u16> nose_y_position; + } appearance_bits6; + union { + u16 raw; + + BitField<0, 6, u16> mouth_type; + BitField<6, 3, u16> mouth_color; + BitField<9, 4, u16> mouth_scale; + BitField<13, 3, u16> mouth_horizontal_stretch; + } appearance_bits7; + union { + u8 raw; + + BitField<0, 5, u8> mouth_y_position; + BitField<5, 3, u8> mustache_type; + } appearance_bits8; + u8 allow_copying; + union { + u16 raw; + + BitField<0, 3, u16> bear_type; + BitField<3, 3, u16> facial_hair_color; + BitField<6, 4, u16> mustache_scale; + BitField<10, 5, u16> mustache_y_position; + } appearance_bits9; + union { + u16 raw; + + BitField<0, 4, u16> glasses_type; + BitField<4, 3, u16> glasses_color; + BitField<7, 4, u16> glasses_scale; + BitField<11, 5, u16> glasses_y_position; + } appearance_bits10; + union { + u16 raw; + + BitField<0, 1, u16> mole_enabled; + BitField<1, 4, u16> mole_scale; + BitField<5, 5, u16> mole_x_position; + BitField<10, 5, u16> mole_y_position; + } appearance_bits11; + + std::array author_name; + INSERT_PADDING_BYTES(0x4); +}; +static_assert(sizeof(Ver3StoreData) == 0x60, "Ver3StoreData is an invalid size"); + struct MiiStoreData { using Name = std::array; diff --git a/src/core/hle/service/nfp/amiibo_crypto.cpp b/src/core/hle/service/nfp/amiibo_crypto.cpp index 211e518b0..d9d0c8f62 100644 --- a/src/core/hle/service/nfp/amiibo_crypto.cpp +++ b/src/core/hle/service/nfp/amiibo_crypto.cpp @@ -16,76 +16,6 @@ namespace Service::NFP::AmiiboCrypto { -Service::Mii::MiiInfo AmiiboRegisterInfoToMii(const AmiiboRegisterInfo& mii_info) { - - Service::Mii::MiiManager manager; - auto mii = manager.BuildDefault(0); - - // TODO: We are ignoring a bunch of data from the amiibo mii - - mii.gender = static_cast(mii_info.mii_information.gender); - mii.favorite_color = static_cast(mii_info.mii_information.favorite_color); - memcpy(mii.name.data(), mii_info.mii_name.data(), 10); - mii.height = mii_info.height; - mii.build = mii_info.build; - - mii.faceline_type = mii_info.appearance_bits1.face_shape; - mii.faceline_color = mii_info.appearance_bits1.skin_color; - mii.faceline_wrinkle = mii_info.appearance_bits2.wrinkles; - mii.faceline_make = mii_info.appearance_bits2.makeup; - - mii.hair_type = mii_info.hair_style; - mii.hair_color = mii_info.appearance_bits3.hair_color; - mii.hair_flip = mii_info.appearance_bits3.flip_hair; - - mii.eye_type = static_cast(mii_info.appearance_bits4.eye_type); - mii.eye_color = static_cast(mii_info.appearance_bits4.eye_color); - mii.eye_scale = static_cast(mii_info.appearance_bits4.eye_scale); - mii.eye_aspect = static_cast(mii_info.appearance_bits4.eye_vertical_stretch); - mii.eye_rotate = static_cast(mii_info.appearance_bits4.eye_rotation); - mii.eye_x = static_cast(mii_info.appearance_bits4.eye_spacing); - mii.eye_y = static_cast(mii_info.appearance_bits4.eye_y_position); - - mii.eyebrow_type = static_cast(mii_info.appearance_bits5.eyebrow_style); - mii.eyebrow_color = static_cast(mii_info.appearance_bits5.eyebrow_color); - mii.eyebrow_scale = static_cast(mii_info.appearance_bits5.eyebrow_scale); - mii.eyebrow_aspect = static_cast(mii_info.appearance_bits5.eyebrow_yscale); - mii.eyebrow_rotate = static_cast(mii_info.appearance_bits5.eyebrow_rotation); - mii.eyebrow_x = static_cast(mii_info.appearance_bits5.eyebrow_spacing); - mii.eyebrow_y = static_cast(mii_info.appearance_bits5.eyebrow_y_position); - - mii.nose_type = static_cast(mii_info.appearance_bits6.nose_type); - mii.nose_scale = static_cast(mii_info.appearance_bits6.nose_scale); - mii.nose_y = static_cast(mii_info.appearance_bits6.nose_y_position); - - mii.mouth_type = static_cast(mii_info.appearance_bits7.mouth_type); - mii.mouth_color = static_cast(mii_info.appearance_bits7.mouth_color); - mii.mouth_scale = static_cast(mii_info.appearance_bits7.mouth_scale); - mii.mouth_aspect = static_cast(mii_info.appearance_bits7.mouth_horizontal_stretch); - mii.mouth_y = static_cast(mii_info.appearance_bits8.mouth_y_position); - - mii.mustache_type = static_cast(mii_info.appearance_bits8.mustache_type); - mii.mustache_scale = static_cast(mii_info.appearance_bits9.mustache_scale); - mii.mustache_y = static_cast(mii_info.appearance_bits9.mustache_y_position); - - mii.beard_type = static_cast(mii_info.appearance_bits9.bear_type); - mii.beard_color = static_cast(mii_info.appearance_bits9.facial_hair_color); - - mii.glasses_type = static_cast(mii_info.appearance_bits10.glasses_type); - mii.glasses_color = static_cast(mii_info.appearance_bits10.glasses_color); - mii.glasses_scale = static_cast(mii_info.appearance_bits10.glasses_scale); - mii.glasses_y = static_cast(mii_info.appearance_bits10.glasses_y_position); - - mii.mole_type = static_cast(mii_info.appearance_bits11.mole_enabled); - mii.mole_scale = static_cast(mii_info.appearance_bits11.mole_scale); - mii.mole_x = static_cast(mii_info.appearance_bits11.mole_x_position); - mii.mole_y = static_cast(mii_info.appearance_bits11.mole_y_position); - - // TODO: Validate mii data - - return mii; -} - bool IsAmiiboValid(const EncryptedNTAG215File& ntag_file) { const auto& amiibo_data = ntag_file.user_memory; LOG_DEBUG(Service_NFP, "uuid_lock=0x{0:x}", ntag_file.static_lock); @@ -126,9 +56,8 @@ bool IsAmiiboValid(const EncryptedNTAG215File& ntag_file) { if (amiibo_data.model_info.constant_value != 0x02) { return false; } - if ((ntag_file.dynamic_lock & 0xFFFFFF) != 0x0F0001) { - return false; - } + // dynamic_lock value apparently is not constant + // ntag_file.dynamic_lock == 0x0F0001 if (ntag_file.CFG0 != 0x04000000U) { return false; } @@ -348,16 +277,16 @@ bool LoadKeys(InternalKey& locked_secret, InternalKey& unfixed_info) { Common::FS::FileType::BinaryFile}; if (!keys_file.IsOpen()) { - LOG_ERROR(Core, "No keys detected"); + LOG_ERROR(Service_NFP, "No keys detected"); return false; } if (keys_file.Read(unfixed_info) != 1) { - LOG_ERROR(Core, "Failed to read unfixed_info"); + LOG_ERROR(Service_NFP, "Failed to read unfixed_info"); return false; } if (keys_file.Read(locked_secret) != 1) { - LOG_ERROR(Core, "Failed to read locked-secret"); + LOG_ERROR(Service_NFP, "Failed to read locked-secret"); return false; } diff --git a/src/core/hle/service/nfp/amiibo_crypto.h b/src/core/hle/service/nfp/amiibo_crypto.h index bfba5dcb2..9b021a5eb 100644 --- a/src/core/hle/service/nfp/amiibo_crypto.h +++ b/src/core/hle/service/nfp/amiibo_crypto.h @@ -55,9 +55,6 @@ struct DerivedKeys { }; static_assert(sizeof(DerivedKeys) == 0x30, "DerivedKeys is an invalid size"); -/// Converts mii data from nintendo 3ds format to nintendo switch format -Service::Mii::MiiInfo AmiiboRegisterInfoToMii(const AmiiboRegisterInfo& register_info); - /// Validates that the amiibo file is not corrupted bool IsAmiiboValid(const EncryptedNTAG215File& ntag_file); diff --git a/src/core/hle/service/nfp/amiibo_types.h b/src/core/hle/service/nfp/amiibo_types.h index 49875cff4..bd0424ffd 100644 --- a/src/core/hle/service/nfp/amiibo_types.h +++ b/src/core/hle/service/nfp/amiibo_types.h @@ -5,6 +5,8 @@ #include +#include "core/hle/service/mii/types.h" + namespace Service::NFP { enum class ServiceType : u32 { User, @@ -74,13 +76,17 @@ using HashData = std::array; using ApplicationArea = std::array; struct AmiiboDate { - union { - u16_be raw{}; - - BitField<0, 5, u16> day; - BitField<5, 4, u16> month; - BitField<9, 7, u16> year; - }; + u16_be raw_date{}; + + u16 GetYear() const { + return ((raw_date & 0xFE00) >> 9) + 2000; + } + u8 GetMonth() const { + return ((raw_date & 0x01E0) >> 5) - 1; + } + u8 GetDay() const { + return raw_date & 0x001F; + } }; static_assert(sizeof(AmiiboDate) == 2, "AmiiboDate is an invalid size"); @@ -123,135 +129,20 @@ struct NTAG215Password { }; static_assert(sizeof(NTAG215Password) == 0x8, "NTAG215Password is an invalid size"); -// Based on citra HLE::Applets::MiiData and PretendoNetwork. -// https://github.com/citra-emu/citra/blob/master/src/core/hle/applets/mii_selector.h#L48 -// https://github.com/PretendoNetwork/mii-js/blob/master/mii.js#L299 #pragma pack(1) -struct AmiiboRegisterInfo { - u32_be mii_id; - u64_be system_id; - u32_be specialness_and_creation_date; - std::array creator_mac; - u16_be padding; - union { - u16 raw; - - BitField<0, 1, u16> gender; - BitField<1, 4, u16> birth_month; - BitField<5, 5, u16> birth_day; - BitField<10, 4, u16> favorite_color; - BitField<14, 1, u16> favorite; - } mii_information; - std::array mii_name; - u8 height; - u8 build; - union { - u8 raw; - - BitField<0, 1, u8> disable_sharing; - BitField<1, 4, u8> face_shape; - BitField<5, 3, u8> skin_color; - } appearance_bits1; - union { - u8 raw; - - BitField<0, 4, u8> wrinkles; - BitField<4, 4, u8> makeup; - } appearance_bits2; - u8 hair_style; - union { - u8 raw; - - BitField<0, 3, u8> hair_color; - BitField<3, 1, u8> flip_hair; - } appearance_bits3; - union { - u32 raw; - - BitField<0, 6, u32> eye_type; - BitField<6, 3, u32> eye_color; - BitField<9, 4, u32> eye_scale; - BitField<13, 3, u32> eye_vertical_stretch; - BitField<16, 5, u32> eye_rotation; - BitField<21, 4, u32> eye_spacing; - BitField<25, 5, u32> eye_y_position; - } appearance_bits4; - union { - u32 raw; - - BitField<0, 5, u32> eyebrow_style; - BitField<5, 3, u32> eyebrow_color; - BitField<8, 4, u32> eyebrow_scale; - BitField<12, 3, u32> eyebrow_yscale; - BitField<16, 4, u32> eyebrow_rotation; - BitField<21, 4, u32> eyebrow_spacing; - BitField<25, 5, u32> eyebrow_y_position; - } appearance_bits5; - union { - u16 raw; - - BitField<0, 5, u16> nose_type; - BitField<5, 4, u16> nose_scale; - BitField<9, 5, u16> nose_y_position; - } appearance_bits6; - union { - u16 raw; - - BitField<0, 6, u16> mouth_type; - BitField<6, 3, u16> mouth_color; - BitField<9, 4, u16> mouth_scale; - BitField<13, 3, u16> mouth_horizontal_stretch; - } appearance_bits7; - union { - u8 raw; - - BitField<0, 5, u8> mouth_y_position; - BitField<5, 3, u8> mustache_type; - } appearance_bits8; - u8 allow_copying; - union { - u16 raw; - - BitField<0, 3, u16> bear_type; - BitField<3, 3, u16> facial_hair_color; - BitField<6, 4, u16> mustache_scale; - BitField<10, 5, u16> mustache_y_position; - } appearance_bits9; - union { - u16 raw; - - BitField<0, 4, u16> glasses_type; - BitField<4, 3, u16> glasses_color; - BitField<7, 4, u16> glasses_scale; - BitField<11, 5, u16> glasses_y_position; - } appearance_bits10; - union { - u16 raw; - - BitField<0, 1, u16> mole_enabled; - BitField<1, 4, u16> mole_scale; - BitField<5, 5, u16> mole_x_position; - BitField<10, 5, u16> mole_y_position; - } appearance_bits11; - - std::array author_name; - INSERT_PADDING_BYTES(0x4); -}; -static_assert(sizeof(AmiiboRegisterInfo) == 0x60, "AmiiboRegisterInfo is an invalid size"); - struct EncryptedAmiiboFile { - u8 constant_value; // Must be A5 - u16 write_counter; // Number of times the amiibo has been written? - INSERT_PADDING_BYTES(0x1); // Unknown 1 - AmiiboSettings settings; // Encrypted amiibo settings - HashData locked_hash; // Hash - AmiiboModelInfo model_info; // Encrypted amiibo model info - HashData keygen_salt; // Salt - HashData unfixed_hash; // Hash - AmiiboRegisterInfo owner_mii; // Encrypted Mii data - u64_be title_id; // Encrypted Game id - u16_be applicaton_write_counter; // Encrypted Counter - u32_be application_area_id; // Encrypted Game id + u8 constant_value; // Must be A5 + u16 write_counter; // Number of times the amiibo has been written? + INSERT_PADDING_BYTES(0x1); // Unknown 1 + AmiiboSettings settings; // Encrypted amiibo settings + HashData locked_hash; // Hash + AmiiboModelInfo model_info; // Encrypted amiibo model info + HashData keygen_salt; // Salt + HashData unfixed_hash; // Hash + Service::Mii::Ver3StoreData owner_mii; // Encrypted Mii data + u64_be title_id; // Encrypted Game id + u16_be applicaton_write_counter; // Encrypted Counter + u32_be application_area_id; // Encrypted Game id std::array unknown; HashData hash; // Probably a SHA256-HMAC hash? ApplicationArea application_area; // Encrypted Game data @@ -267,7 +158,7 @@ struct NTAG215File { u16 write_counter; // Number of times the amiibo has been written? INSERT_PADDING_BYTES(0x1); // Unknown 1 AmiiboSettings settings; - AmiiboRegisterInfo owner_mii; // Encrypted Mii data + Service::Mii::Ver3StoreData owner_mii; // Encrypted Mii data u64_be title_id; u16_be applicaton_write_counter; // Encrypted Counter u32_be application_area_id; diff --git a/src/core/hle/service/nfp/nfp.cpp b/src/core/hle/service/nfp/nfp.cpp index 4dba05a6a..20fea87e6 100644 --- a/src/core/hle/service/nfp/nfp.cpp +++ b/src/core/hle/service/nfp/nfp.cpp @@ -24,6 +24,7 @@ constexpr Result DeviceNotFound(ErrorModule::NFP, 64); constexpr Result WrongDeviceState(ErrorModule::NFP, 73); constexpr Result NfcDisabled(ErrorModule::NFP, 80); constexpr Result WriteAmiiboFailed(ErrorModule::NFP, 88); +constexpr Result TagRemoved(ErrorModule::NFP, 97); constexpr Result ApplicationAreaIsNotInitialized(ErrorModule::NFP, 128); constexpr Result WrongApplicationAreaId(ErrorModule::NFP, 152); constexpr Result ApplicationAreaExist(ErrorModule::NFP, 168); @@ -57,7 +58,7 @@ IUser::IUser(Module::Interface& nfp_interface_, Core::System& system_) {21, &IUser::GetNpadId, "GetNpadId"}, {22, &IUser::GetApplicationAreaSize, "GetApplicationAreaSize"}, {23, &IUser::AttachAvailabilityChangeEvent, "AttachAvailabilityChangeEvent"}, - {24, nullptr, "RecreateApplicationArea"}, + {24, &IUser::RecreateApplicationArea, "RecreateApplicationArea"}, }; RegisterHandlers(functions); @@ -597,6 +598,34 @@ void IUser::AttachAvailabilityChangeEvent(Kernel::HLERequestContext& ctx) { rb.PushCopyObjects(availability_change_event->GetReadableEvent()); } +void IUser::RecreateApplicationArea(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto device_handle{rp.Pop()}; + const auto access_id{rp.Pop()}; + const auto data{ctx.ReadBuffer()}; + LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}, data_size={}, access_id={}", + device_handle, access_id, data.size()); + + if (state == State::NonInitialized) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ErrCodes::NfcDisabled); + return; + } + + // TODO(german77): Loop through all interfaces + if (device_handle == nfp_interface.GetHandle()) { + const auto result = nfp_interface.RecreateApplicationArea(access_id, data); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + return; + } + + LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ErrCodes::DeviceNotFound); +} + Module::Interface::Interface(std::shared_ptr module_, Core::System& system_, const char* name) : ServiceFramework{system_, name}, module{std::move(module_)}, @@ -621,14 +650,14 @@ bool Module::Interface::LoadAmiiboFile(const std::string& filename) { Common::FS::FileType::BinaryFile}; if (!amiibo_file.IsOpen()) { - LOG_ERROR(Core, "Amiibo is already on use"); + LOG_ERROR(Service_NFP, "Amiibo is already on use"); return false; } // Workaround for files with missing password data std::array buffer{}; if (amiibo_file.Read(buffer) < tag_size_without_password) { - LOG_ERROR(Core, "Failed to read amiibo file"); + LOG_ERROR(Service_NFP, "Failed to read amiibo file"); return false; } memcpy(&encrypted_tag_data, buffer.data(), sizeof(EncryptedNTAG215File)); @@ -759,12 +788,12 @@ Result Module::Interface::Flush() { bool is_character_equal = tmp_encrypted_tag_data.user_memory.model_info.character_id == tag_data.model_info.character_id; if (!is_uuid_equal || !is_character_equal) { - LOG_ERROR(Core, "Not the same amiibo"); + LOG_ERROR(Service_NFP, "Not the same amiibo"); return ErrCodes::WriteAmiiboFailed; } if (!AmiiboCrypto::EncodeAmiibo(tag_data, encrypted_tag_data)) { - LOG_ERROR(Core, "Failed to encode data"); + LOG_ERROR(Service_NFP, "Failed to encode data"); return ErrCodes::WriteAmiiboFailed; } @@ -830,13 +859,13 @@ Result Module::Interface::GetCommonInfo(CommonInfo& common_info) const { return ErrCodes::WrongDeviceState; } - if (is_data_decoded) { + if (is_data_decoded && tag_data.settings.settings.amiibo_initialized != 0) { const auto& settings = tag_data.settings; // TODO: Validate this data common_info = { - .last_write_year = static_cast(settings.write_date.year.Value()), - .last_write_month = static_cast(settings.write_date.month.Value()), - .last_write_day = static_cast(settings.write_date.day.Value()), + .last_write_year = settings.write_date.GetYear(), + .last_write_month = settings.write_date.GetMonth(), + .last_write_day = settings.write_date.GetDay(), .write_counter = settings.crc_counter, .version = 1, .application_area_size = sizeof(ApplicationArea), @@ -877,10 +906,15 @@ Result Module::Interface::GetModelInfo(ModelInfo& model_info) const { Result Module::Interface::GetRegisterInfo(RegisterInfo& register_info) const { if (device_state != DeviceState::TagMounted) { LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); + if (device_state == DeviceState::TagRemoved) { + return ErrCodes::TagRemoved; + } return ErrCodes::WrongDeviceState; } - if (is_data_decoded) { + Service::Mii::MiiManager manager; + + if (is_data_decoded && tag_data.settings.settings.amiibo_initialized != 0) { const auto& settings = tag_data.settings; // Amiibo name is u16 while the register info is u8. Figure out how to handle this properly @@ -891,10 +925,10 @@ Result Module::Interface::GetRegisterInfo(RegisterInfo& register_info) const { // TODO: Validate this data register_info = { - .mii_char_info = AmiiboCrypto::AmiiboRegisterInfoToMii(tag_data.owner_mii), - .first_write_year = static_cast(settings.init_date.year.Value()), - .first_write_month = static_cast(settings.init_date.month.Value()), - .first_write_day = static_cast(settings.init_date.day.Value()), + .mii_char_info = manager.ConvertV3ToCharInfo(tag_data.owner_mii), + .first_write_year = settings.init_date.GetYear(), + .first_write_month = settings.init_date.GetMonth(), + .first_write_day = settings.init_date.GetDay(), .amiibo_name = amiibo_name, .unknown = {}, }; @@ -903,7 +937,6 @@ Result Module::Interface::GetRegisterInfo(RegisterInfo& register_info) const { } // Generate a generic answer - Service::Mii::MiiManager manager; register_info = { .mii_char_info = manager.BuildDefault(0), .first_write_year = 2022, @@ -918,6 +951,9 @@ Result Module::Interface::GetRegisterInfo(RegisterInfo& register_info) const { Result Module::Interface::OpenApplicationArea(u32 access_id) { if (device_state != DeviceState::TagMounted) { LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); + if (device_state == DeviceState::TagRemoved) { + return ErrCodes::TagRemoved; + } return ErrCodes::WrongDeviceState; } @@ -944,6 +980,9 @@ Result Module::Interface::OpenApplicationArea(u32 access_id) { Result Module::Interface::GetApplicationArea(ApplicationArea& data) const { if (device_state != DeviceState::TagMounted) { LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); + if (device_state == DeviceState::TagRemoved) { + return ErrCodes::TagRemoved; + } return ErrCodes::WrongDeviceState; } @@ -960,6 +999,9 @@ Result Module::Interface::GetApplicationArea(ApplicationArea& data) const { Result Module::Interface::SetApplicationArea(const std::vector& data) { if (device_state != DeviceState::TagMounted) { LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); + if (device_state == DeviceState::TagRemoved) { + return ErrCodes::TagRemoved; + } return ErrCodes::WrongDeviceState; } @@ -980,6 +1022,9 @@ Result Module::Interface::SetApplicationArea(const std::vector& data) { Result Module::Interface::CreateApplicationArea(u32 access_id, const std::vector& data) { if (device_state != DeviceState::TagMounted) { LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); + if (device_state == DeviceState::TagRemoved) { + return ErrCodes::TagRemoved; + } return ErrCodes::WrongDeviceState; } @@ -999,6 +1044,26 @@ Result Module::Interface::CreateApplicationArea(u32 access_id, const std::vector return ResultSuccess; } +Result Module::Interface::RecreateApplicationArea(u32 access_id, const std::vector& data) { + if (device_state != DeviceState::TagMounted) { + LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); + if (device_state == DeviceState::TagRemoved) { + return ErrCodes::TagRemoved; + } + return ErrCodes::WrongDeviceState; + } + + if (data.size() != sizeof(ApplicationArea)) { + LOG_ERROR(Service_NFP, "Wrong data size {}", data.size()); + return ResultUnknown; + } + + std::memcpy(&tag_data.application_area, data.data(), sizeof(ApplicationArea)); + tag_data.application_area_id = access_id; + + return ResultSuccess; +} + u64 Module::Interface::GetHandle() const { // Generate a handle based of the npad id return static_cast(npad_id); diff --git a/src/core/hle/service/nfp/nfp.h b/src/core/hle/service/nfp/nfp.h index 3410dcfb0..6b979daba 100644 --- a/src/core/hle/service/nfp/nfp.h +++ b/src/core/hle/service/nfp/nfp.h @@ -55,7 +55,7 @@ struct ModelInfo { static_assert(sizeof(ModelInfo) == 0x40, "ModelInfo is an invalid size"); struct RegisterInfo { - Service::Mii::MiiInfo mii_char_info; + Service::Mii::CharInfo mii_char_info; u16 first_write_year; u8 first_write_month; u8 first_write_day; @@ -96,6 +96,7 @@ public: Result GetApplicationArea(ApplicationArea& data) const; Result SetApplicationArea(const std::vector& data); Result CreateApplicationArea(u32 access_id, const std::vector& data); + Result RecreateApplicationArea(u32 access_id, const std::vector& data); u64 GetHandle() const; DeviceState GetCurrentState() const; @@ -152,6 +153,7 @@ private: void GetNpadId(Kernel::HLERequestContext& ctx); void GetApplicationAreaSize(Kernel::HLERequestContext& ctx); void AttachAvailabilityChangeEvent(Kernel::HLERequestContext& ctx); + void RecreateApplicationArea(Kernel::HLERequestContext& ctx); KernelHelpers::ServiceContext service_context; -- cgit v1.2.3 From caa138b33f6bbc18cde4d403017dad6bd4387e13 Mon Sep 17 00:00:00 2001 From: Narr the Reg Date: Tue, 30 Aug 2022 19:28:37 -0500 Subject: core: nfp: Correct date and amiibo name --- src/core/hle/service/mii/mii_manager.cpp | 3 ++- src/core/hle/service/nfp/amiibo_types.h | 12 +++++++----- src/core/hle/service/nfp/nfp.cpp | 29 ++++++++++++++++++++--------- src/core/hle/service/nfp/nfp.h | 10 +++++++--- 4 files changed, 36 insertions(+), 18 deletions(-) (limited to 'src/core') diff --git a/src/core/hle/service/mii/mii_manager.cpp b/src/core/hle/service/mii/mii_manager.cpp index 97d1b948f..c484a9c8d 100644 --- a/src/core/hle/service/mii/mii_manager.cpp +++ b/src/core/hle/service/mii/mii_manager.cpp @@ -443,8 +443,9 @@ CharInfo MiiManager::ConvertV3ToCharInfo(Ver3StoreData mii_v3) const { mii.height = mii_v3.height; mii.build = mii_v3.build; + memset(mii.name.data(), 0, sizeof(mii.name)); + memcpy(mii.name.data(), mii_v3.mii_name.data(), sizeof(mii_v3.mii_name)); mii.font_region = mii_v3.region_information.character_set; - memcpy(mii.name.data(), mii_v3.mii_name.data(), 10); mii.faceline_type = mii_v3.appearance_bits1.face_shape; mii.faceline_color = mii_v3.appearance_bits1.skin_color; diff --git a/src/core/hle/service/nfp/amiibo_types.h b/src/core/hle/service/nfp/amiibo_types.h index bd0424ffd..c9c0932d0 100644 --- a/src/core/hle/service/nfp/amiibo_types.h +++ b/src/core/hle/service/nfp/amiibo_types.h @@ -8,6 +8,8 @@ #include "core/hle/service/mii/types.h" namespace Service::NFP { +static constexpr std::size_t amiibo_name_length = 0xA; + enum class ServiceType : u32 { User, Debug, @@ -76,16 +78,16 @@ using HashData = std::array; using ApplicationArea = std::array; struct AmiiboDate { - u16_be raw_date{}; + u16 raw_date{}; u16 GetYear() const { - return ((raw_date & 0xFE00) >> 9) + 2000; + return static_cast(((raw_date & 0xFE00) >> 9) + 2000); } u8 GetMonth() const { - return ((raw_date & 0x01E0) >> 5) - 1; + return static_cast(((raw_date & 0x01E0) >> 5) - 1); } u8 GetDay() const { - return raw_date & 0x001F; + return static_cast(raw_date & 0x001F); } }; static_assert(sizeof(AmiiboDate) == 2, "AmiiboDate is an invalid size"); @@ -107,7 +109,7 @@ struct AmiiboSettings { AmiiboDate init_date; AmiiboDate write_date; u32_be crc; - std::array amiibo_name; // UTF-16 text + std::array amiibo_name; // UTF-16 text }; static_assert(sizeof(AmiiboSettings) == 0x20, "AmiiboSettings is an invalid size"); diff --git a/src/core/hle/service/nfp/nfp.cpp b/src/core/hle/service/nfp/nfp.cpp index 20fea87e6..fb89f6911 100644 --- a/src/core/hle/service/nfp/nfp.cpp +++ b/src/core/hle/service/nfp/nfp.cpp @@ -7,6 +7,7 @@ #include "common/fs/file.h" #include "common/fs/path_util.h" #include "common/logging/log.h" +#include "common/string_util.h" #include "core/core.h" #include "core/hid/emulated_controller.h" #include "core/hid/hid_core.h" @@ -917,20 +918,14 @@ Result Module::Interface::GetRegisterInfo(RegisterInfo& register_info) const { if (is_data_decoded && tag_data.settings.settings.amiibo_initialized != 0) { const auto& settings = tag_data.settings; - // Amiibo name is u16 while the register info is u8. Figure out how to handle this properly - std::array amiibo_name{}; - for (std::size_t i = 0; i < sizeof(amiibo_name) - 1; ++i) { - amiibo_name[i] = static_cast(settings.amiibo_name[i]); - } - // TODO: Validate this data register_info = { .mii_char_info = manager.ConvertV3ToCharInfo(tag_data.owner_mii), .first_write_year = settings.init_date.GetYear(), .first_write_month = settings.init_date.GetMonth(), .first_write_day = settings.init_date.GetDay(), - .amiibo_name = amiibo_name, - .unknown = {}, + .amiibo_name = GetAmiiboName(settings), + .font_region = {}, }; return ResultSuccess; @@ -943,7 +938,7 @@ Result Module::Interface::GetRegisterInfo(RegisterInfo& register_info) const { .first_write_month = 2, .first_write_day = 7, .amiibo_name = {'Y', 'u', 'z', 'u', 'A', 'm', 'i', 'i', 'b', 'o', 0}, - .unknown = {}, + .font_region = {}, }; return ResultSuccess; } @@ -1077,6 +1072,22 @@ Core::HID::NpadIdType Module::Interface::GetNpadId() const { return npad_id; } +AmiiboName Module::Interface::GetAmiiboName(const AmiiboSettings& settings) const { + std::array settings_amiibo_name{}; + AmiiboName amiibo_name{}; + + // Convert from big endian to little endian + for (std::size_t i = 0; i < amiibo_name_length; i++) { + settings_amiibo_name[i] = static_cast(settings.amiibo_name[i]); + } + + // Convert from utf16 to utf8 + const auto amiibo_name_utf8 = Common::UTF16ToUTF8(settings_amiibo_name.data()); + memcpy(amiibo_name.data(), amiibo_name_utf8.data(), amiibo_name_utf8.size()); + + return amiibo_name; +} + void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) { auto module = std::make_shared(); std::make_shared(module, system)->InstallAsService(service_manager); diff --git a/src/core/hle/service/nfp/nfp.h b/src/core/hle/service/nfp/nfp.h index 6b979daba..0de0b48e7 100644 --- a/src/core/hle/service/nfp/nfp.h +++ b/src/core/hle/service/nfp/nfp.h @@ -22,6 +22,8 @@ enum class NpadIdType : u32; } // namespace Core::HID namespace Service::NFP { +using AmiiboName = std::array; + struct TagInfo { TagUuid uuid; u8 uuid_length; @@ -59,9 +61,9 @@ struct RegisterInfo { u16 first_write_year; u8 first_write_month; u8 first_write_day; - std::array amiibo_name; - u8 unknown; - INSERT_PADDING_BYTES(0x98); + AmiiboName amiibo_name; + u8 font_region; + INSERT_PADDING_BYTES(0x7A); }; static_assert(sizeof(RegisterInfo) == 0x100, "RegisterInfo is an invalid size"); @@ -109,6 +111,8 @@ public: std::shared_ptr module; private: + AmiiboName GetAmiiboName(const AmiiboSettings& settings) const; + const Core::HID::NpadIdType npad_id; bool is_data_decoded{}; -- cgit v1.2.3 From 4834961736f95ac99deb8edd2af51be5276a041f Mon Sep 17 00:00:00 2001 From: german77 Date: Thu, 1 Sep 2022 00:20:22 -0500 Subject: core: nfp: Workaround for lack of multiple nfp interfaces --- src/core/hle/service/nfp/nfp.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src/core') diff --git a/src/core/hle/service/nfp/nfp.cpp b/src/core/hle/service/nfp/nfp.cpp index fb89f6911..e0ed3f771 100644 --- a/src/core/hle/service/nfp/nfp.cpp +++ b/src/core/hle/service/nfp/nfp.cpp @@ -1069,7 +1069,9 @@ DeviceState Module::Interface::GetCurrentState() const { } Core::HID::NpadIdType Module::Interface::GetNpadId() const { - return npad_id; + // Return first connected npad id as a workaround for lack of a single nfc interface per + // controller + return system.HIDCore().GetFirstNpadId(); } AmiiboName Module::Interface::GetAmiiboName(const AmiiboSettings& settings) const { -- cgit v1.2.3 From 063b23cc58b1e7367f9e530e752ab56fe1f56532 Mon Sep 17 00:00:00 2001 From: german77 Date: Wed, 7 Sep 2022 01:03:02 -0500 Subject: core: nfp: Remove magic numbers --- src/core/hle/service/nfp/amiibo_crypto.cpp | 171 ++++++++++++++--------------- src/core/hle/service/nfp/amiibo_crypto.h | 29 +++-- src/core/hle/service/nfp/amiibo_types.h | 8 +- 3 files changed, 103 insertions(+), 105 deletions(-) (limited to 'src/core') diff --git a/src/core/hle/service/nfp/amiibo_crypto.cpp b/src/core/hle/service/nfp/amiibo_crypto.cpp index d9d0c8f62..31dd3a307 100644 --- a/src/core/hle/service/nfp/amiibo_crypto.cpp +++ b/src/core/hle/service/nfp/amiibo_crypto.cpp @@ -70,10 +70,10 @@ bool IsAmiiboValid(const EncryptedNTAG215File& ntag_file) { NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data) { NTAG215File encoded_data{}; - memcpy(encoded_data.uuid2.data(), nfc_data.uuid.data() + 0x8, 2); + memcpy(encoded_data.uuid2.data(), nfc_data.uuid.data() + 0x8, sizeof(encoded_data.uuid2)); encoded_data.static_lock = nfc_data.static_lock; encoded_data.compability_container = nfc_data.compability_container; - encoded_data.unfixed_hash = nfc_data.user_memory.unfixed_hash; + encoded_data.hmac_data = nfc_data.user_memory.hmac_data; encoded_data.constant_value = nfc_data.user_memory.constant_value; encoded_data.write_counter = nfc_data.user_memory.write_counter; encoded_data.settings = nfc_data.user_memory.settings; @@ -84,8 +84,8 @@ NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data) { encoded_data.unknown = nfc_data.user_memory.unknown; encoded_data.hash = nfc_data.user_memory.hash; encoded_data.application_area = nfc_data.user_memory.application_area; - encoded_data.locked_hash = nfc_data.user_memory.locked_hash; - memcpy(encoded_data.uuid.data(), nfc_data.uuid.data(), 8); + encoded_data.hmac_tag = nfc_data.user_memory.hmac_tag; + memcpy(encoded_data.uuid.data(), nfc_data.uuid.data(), sizeof(encoded_data.uuid)); encoded_data.model_info = nfc_data.user_memory.model_info; encoded_data.keygen_salt = nfc_data.user_memory.keygen_salt; encoded_data.dynamic_lock = nfc_data.dynamic_lock; @@ -99,11 +99,11 @@ NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data) { EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data) { EncryptedNTAG215File nfc_data{}; - memcpy(nfc_data.uuid.data() + 0x8, encoded_data.uuid2.data(), 2); - memcpy(nfc_data.uuid.data(), encoded_data.uuid.data(), 8); + memcpy(nfc_data.uuid.data() + 0x8, encoded_data.uuid2.data(), sizeof(encoded_data.uuid2)); + memcpy(nfc_data.uuid.data(), encoded_data.uuid.data(), sizeof(encoded_data.uuid)); nfc_data.static_lock = encoded_data.static_lock; nfc_data.compability_container = encoded_data.compability_container; - nfc_data.user_memory.unfixed_hash = encoded_data.unfixed_hash; + nfc_data.user_memory.hmac_data = encoded_data.hmac_data; nfc_data.user_memory.constant_value = encoded_data.constant_value; nfc_data.user_memory.write_counter = encoded_data.write_counter; nfc_data.user_memory.settings = encoded_data.settings; @@ -114,7 +114,7 @@ EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data) { nfc_data.user_memory.unknown = encoded_data.unknown; nfc_data.user_memory.hash = encoded_data.hash; nfc_data.user_memory.application_area = encoded_data.application_area; - nfc_data.user_memory.locked_hash = encoded_data.locked_hash; + nfc_data.user_memory.hmac_tag = encoded_data.hmac_tag; nfc_data.user_memory.model_info = encoded_data.model_info; nfc_data.user_memory.keygen_salt = encoded_data.keygen_salt; nfc_data.dynamic_lock = encoded_data.dynamic_lock; @@ -136,60 +136,53 @@ u32 GetTagPassword(const TagUuid& uuid) { HashSeed GetSeed(const NTAG215File& data) { HashSeed seed{ - .data = - { - .magic = data.write_counter, - .padding = {}, - .uuid1 = {}, - .uuid2 = {}, - .keygen_salt = data.keygen_salt, - }, + .magic = data.write_counter, + .padding = {}, + .uuid1 = {}, + .uuid2 = {}, + .keygen_salt = data.keygen_salt, }; // Copy the first 8 bytes of uuid - memcpy(seed.data.uuid1.data(), data.uuid.data(), sizeof(seed.data.uuid1)); - memcpy(seed.data.uuid2.data(), data.uuid.data(), sizeof(seed.data.uuid2)); + memcpy(seed.uuid1.data(), data.uuid.data(), sizeof(seed.uuid1)); + memcpy(seed.uuid2.data(), data.uuid.data(), sizeof(seed.uuid2)); return seed; } -void PreGenerateKey(const InternalKey& key, const HashSeed& seed, u8* output, - std::size_t& outputLen) { - std::size_t index = 0; +std::vector GenerateInternalKey(const InternalKey& key, const HashSeed& seed) { + const std::size_t seedPart1Len = sizeof(key.magic_bytes) - key.magic_length; + const std::size_t string_size = key.type_string.size(); + std::vector output(string_size + seedPart1Len); // Copy whole type string - memccpy(output + index, key.type_string.data(), '\0', key.type_string.size()); - index += key.type_string.size(); + memccpy(output.data(), key.type_string.data(), '\0', string_size); // Append (16 - magic_length) from the input seed - std::size_t seedPart1Len = 16 - key.magic_length; - memcpy(output + index, &seed, seedPart1Len); - index += seedPart1Len; + memcpy(output.data() + string_size, &seed, seedPart1Len); // Append all bytes from magicBytes - memcpy(output + index, &key.magic_bytes, key.magic_length); - index += key.magic_length; + output.insert(output.end(), key.magic_bytes.begin(), + key.magic_bytes.begin() + key.magic_length); - // Seed 16 bytes at +0x10 - memcpy(output + index, &seed.raw[0x10], 16); - index += 16; + output.insert(output.end(), seed.uuid1.begin(), seed.uuid1.end()); + output.insert(output.end(), seed.uuid2.begin(), seed.uuid2.end()); - // 32 bytes at +0x20 from input seed xored with xor pad - for (std::size_t i = 0; i < 32; i++) - output[index + i] = seed.raw[i + 32] ^ key.xor_pad[i]; - index += 32; + for (std::size_t i = 0; i < sizeof(seed.keygen_salt); i++) { + output.emplace_back(static_cast(seed.keygen_salt[i] ^ key.xor_pad[i])); + } - outputLen = index; + return output; } void CryptoInit(CryptoCtx& ctx, mbedtls_md_context_t& hmac_ctx, const HmacKey& hmac_key, - const u8* seed, std::size_t seed_size) { + const std::vector& seed) { // Initialize context ctx.used = false; ctx.counter = 0; - ctx.buffer_size = sizeof(ctx.counter) + seed_size; - memcpy(ctx.buffer.data() + sizeof(u16), seed, seed_size); + ctx.buffer_size = sizeof(ctx.counter) + seed.size(); + memcpy(ctx.buffer.data() + sizeof(u16), seed.data(), seed.size()); // Initialize HMAC context mbedtls_md_init(&hmac_ctx); @@ -217,18 +210,15 @@ void CryptoStep(CryptoCtx& ctx, mbedtls_md_context_t& hmac_ctx, DrgbOutput& outp } DerivedKeys GenerateKey(const InternalKey& key, const NTAG215File& data) { - constexpr std::size_t OUTPUT_SIZE = 512; const auto seed = GetSeed(data); // Generate internal seed - u8 internal_key[OUTPUT_SIZE]; - std::size_t internal_key_lenght = 0; - PreGenerateKey(key, seed, internal_key, internal_key_lenght); + const std::vector internal_key = GenerateInternalKey(key, seed); // Initialize context CryptoCtx ctx{}; mbedtls_md_context_t hmac_ctx; - CryptoInit(ctx, hmac_ctx, key.hmac_key, internal_key, internal_key_lenght); + CryptoInit(ctx, hmac_ctx, key.hmac_key, internal_key); // Generate derived keys DerivedKeys derived_keys{}; @@ -246,27 +236,34 @@ DerivedKeys GenerateKey(const InternalKey& key, const NTAG215File& data) { void Cipher(const DerivedKeys& keys, const NTAG215File& in_data, NTAG215File& out_data) { mbedtls_aes_context aes; std::size_t nc_off = 0; - std::array nonce_counter{}; - std::array stream_block{}; - - mbedtls_aes_setkey_enc(&aes, keys.aes_key.data(), 128); - memcpy(nonce_counter.data(), keys.aes_iv.data(), sizeof(nonce_counter)); - - std::array in_data_byes{}; - std::array out_data_bytes{}; - memcpy(in_data_byes.data(), &in_data, sizeof(NTAG215File)); - memcpy(out_data_bytes.data(), &out_data, sizeof(NTAG215File)); - - mbedtls_aes_crypt_ctr(&aes, 0x188, &nc_off, nonce_counter.data(), stream_block.data(), - in_data_byes.data() + 0x2c, out_data_bytes.data() + 0x2c); - - memcpy(out_data_bytes.data(), in_data_byes.data(), 0x008); - // Data signature NOT copied - memcpy(out_data_bytes.data() + 0x028, in_data_byes.data() + 0x028, 0x004); - // Tag signature NOT copied - memcpy(out_data_bytes.data() + 0x1D4, in_data_byes.data() + 0x1D4, 0x048); - - memcpy(&out_data, out_data_bytes.data(), sizeof(NTAG215File)); + std::array nonce_counter{}; + std::array stream_block{}; + + const auto aes_key_size = static_cast(keys.aes_key.size() * 8); + mbedtls_aes_setkey_enc(&aes, keys.aes_key.data(), aes_key_size); + memcpy(nonce_counter.data(), keys.aes_iv.data(), sizeof(keys.aes_iv)); + + constexpr std::size_t encrypted_data_size = HMAC_TAG_START - SETTINGS_START; + mbedtls_aes_crypt_ctr(&aes, encrypted_data_size, &nc_off, nonce_counter.data(), + stream_block.data(), + reinterpret_cast(&in_data.settings), + reinterpret_cast(&out_data.settings)); + + // Copy the rest of the data directly + out_data.uuid2 = in_data.uuid2; + out_data.static_lock = in_data.static_lock; + out_data.compability_container = in_data.compability_container; + + out_data.constant_value = in_data.constant_value; + out_data.write_counter = in_data.write_counter; + + out_data.uuid = in_data.uuid; + out_data.model_info = in_data.model_info; + out_data.keygen_salt = in_data.keygen_salt; + out_data.dynamic_lock = in_data.dynamic_lock; + out_data.CFG0 = in_data.CFG0; + out_data.CFG1 = in_data.CFG1; + out_data.password = in_data.password; } bool LoadKeys(InternalKey& locked_secret, InternalKey& unfixed_info) { @@ -309,26 +306,26 @@ bool DecodeAmiibo(const EncryptedNTAG215File& encrypted_tag_data, NTAG215File& t // Decrypt Cipher(data_keys, encoded_data, tag_data); - std::array out{}; - memcpy(out.data(), &tag_data, sizeof(NTAG215File)); - // Regenerate tag HMAC. Note: order matters, data HMAC depends on tag HMAC! + constexpr std::size_t input_length = DYNAMIC_LOCK_START - UUID_START; mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), tag_keys.hmac_key.data(), - sizeof(HmacKey), out.data() + 0x1D4, 0x34, out.data() + HMAC_POS_TAG); + sizeof(HmacKey), reinterpret_cast(&tag_data.uuid), + input_length, reinterpret_cast(&tag_data.hmac_tag)); // Regenerate data HMAC + constexpr std::size_t input_length2 = DYNAMIC_LOCK_START - WRITE_COUNTER_START; mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), data_keys.hmac_key.data(), - sizeof(HmacKey), out.data() + 0x29, 0x1DF, out.data() + HMAC_POS_DATA); - - memcpy(&tag_data, out.data(), sizeof(NTAG215File)); + sizeof(HmacKey), + reinterpret_cast(&tag_data.write_counter), input_length2, + reinterpret_cast(&tag_data.hmac_data)); - if (memcmp(tag_data.unfixed_hash.data(), encrypted_tag_data.user_memory.unfixed_hash.data(), - 32) != 0) { + if (tag_data.hmac_data != encrypted_tag_data.user_memory.hmac_data) { + LOG_ERROR(Service_NFP, "hmac_data doesn't match"); return false; } - if (memcmp(tag_data.locked_hash.data(), encrypted_tag_data.user_memory.locked_hash.data(), - 32) != 0) { + if (tag_data.hmac_tag != encrypted_tag_data.user_memory.hmac_tag) { + LOG_ERROR(Service_NFP, "hmac_tag doesn't match"); return false; } @@ -347,13 +344,14 @@ bool EncodeAmiibo(const NTAG215File& tag_data, EncryptedNTAG215File& encrypted_t const auto data_keys = GenerateKey(unfixed_info, tag_data); const auto tag_keys = GenerateKey(locked_secret, tag_data); - std::array plain{}; - std::array cipher{}; - memcpy(plain.data(), &tag_data, sizeof(NTAG215File)); + NTAG215File encoded_tag_data{}; // Generate tag HMAC + constexpr std::size_t input_length = DYNAMIC_LOCK_START - UUID_START; + constexpr std::size_t input_length2 = HMAC_TAG_START - WRITE_COUNTER_START; mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), tag_keys.hmac_key.data(), - sizeof(HmacKey), plain.data() + 0x1D4, 0x34, cipher.data() + HMAC_POS_TAG); + sizeof(HmacKey), reinterpret_cast(&tag_data.uuid), + input_length, reinterpret_cast(&encoded_tag_data.hmac_tag)); // Init mbedtls HMAC context mbedtls_md_context_t ctx; @@ -362,17 +360,18 @@ bool EncodeAmiibo(const NTAG215File& tag_data, EncryptedNTAG215File& encrypted_t // Generate data HMAC mbedtls_md_hmac_starts(&ctx, data_keys.hmac_key.data(), sizeof(HmacKey)); - mbedtls_md_hmac_update(&ctx, plain.data() + 0x029, 0x18B); // Data - mbedtls_md_hmac_update(&ctx, cipher.data() + HMAC_POS_TAG, 0x20); // Tag HMAC - mbedtls_md_hmac_update(&ctx, plain.data() + 0x1D4, 0x34); - mbedtls_md_hmac_finish(&ctx, cipher.data() + HMAC_POS_DATA); + mbedtls_md_hmac_update(&ctx, reinterpret_cast(&tag_data.write_counter), + input_length2); // Data + mbedtls_md_hmac_update(&ctx, reinterpret_cast(&encoded_tag_data.hmac_tag), + sizeof(HashData)); // Tag HMAC + mbedtls_md_hmac_update(&ctx, reinterpret_cast(&tag_data.uuid), + input_length); + mbedtls_md_hmac_finish(&ctx, reinterpret_cast(&encoded_tag_data.hmac_data)); // HMAC cleanup mbedtls_md_free(&ctx); // Encrypt - NTAG215File encoded_tag_data{}; - memcpy(&encoded_tag_data, cipher.data(), sizeof(NTAG215File)); Cipher(data_keys, tag_data, encoded_tag_data); // Convert back to hardware diff --git a/src/core/hle/service/nfp/amiibo_crypto.h b/src/core/hle/service/nfp/amiibo_crypto.h index 9b021a5eb..af7335912 100644 --- a/src/core/hle/service/nfp/amiibo_crypto.h +++ b/src/core/hle/service/nfp/amiibo_crypto.h @@ -10,23 +10,23 @@ struct mbedtls_md_context_t; namespace Service::NFP::AmiiboCrypto { -constexpr std::size_t HMAC_POS_DATA = 0x8; -constexpr std::size_t HMAC_POS_TAG = 0x1B4; +// Byte locations in Service::NFP::NTAG215File +constexpr std::size_t HMAC_DATA_START = 0x8; +constexpr std::size_t SETTINGS_START = 0x2c; +constexpr std::size_t WRITE_COUNTER_START = 0x29; +constexpr std::size_t HMAC_TAG_START = 0x1B4; +constexpr std::size_t UUID_START = 0x1D4; +constexpr std::size_t DYNAMIC_LOCK_START = 0x208; using HmacKey = std::array; using DrgbOutput = std::array; struct HashSeed { - union { - std::array raw; - struct { - u16 magic; - std::array padding; - std::array uuid1; - std::array uuid2; - std::array keygen_salt; - } data; - }; + u16 magic; + std::array padding; + std::array uuid1; + std::array uuid2; + std::array keygen_salt; }; static_assert(sizeof(HashSeed) == 0x40, "HashSeed is an invalid size"); @@ -71,12 +71,11 @@ u32 GetTagPassword(const TagUuid& uuid); HashSeed GetSeed(const NTAG215File& data); // Middle step on the generation of derived keys -void PreGenerateKey(const InternalKey& key, const HashSeed& seed, u8* output, - std::size_t& outputLen); +std::vector GenerateInternalKey(const InternalKey& key, const HashSeed& seed); // Initializes mbedtls context void CryptoInit(CryptoCtx& ctx, mbedtls_md_context_t& hmac_ctx, const HmacKey& hmac_key, - const u8* seed, std::size_t seed_size); + const std::vector& seed); // Feeds data to mbedtls context to generate the derived key void CryptoStep(CryptoCtx& ctx, mbedtls_md_context_t& hmac_ctx, DrgbOutput& output); diff --git a/src/core/hle/service/nfp/amiibo_types.h b/src/core/hle/service/nfp/amiibo_types.h index c9c0932d0..bf2de811a 100644 --- a/src/core/hle/service/nfp/amiibo_types.h +++ b/src/core/hle/service/nfp/amiibo_types.h @@ -137,10 +137,10 @@ struct EncryptedAmiiboFile { u16 write_counter; // Number of times the amiibo has been written? INSERT_PADDING_BYTES(0x1); // Unknown 1 AmiiboSettings settings; // Encrypted amiibo settings - HashData locked_hash; // Hash + HashData hmac_tag; // Hash AmiiboModelInfo model_info; // Encrypted amiibo model info HashData keygen_salt; // Salt - HashData unfixed_hash; // Hash + HashData hmac_data; // Hash Service::Mii::Ver3StoreData owner_mii; // Encrypted Mii data u64_be title_id; // Encrypted Game id u16_be applicaton_write_counter; // Encrypted Counter @@ -155,7 +155,7 @@ struct NTAG215File { std::array uuid2; u16 static_lock; // Set defined pages as read only u32 compability_container; // Defines available memory - HashData unfixed_hash; // Hash + HashData hmac_data; // Hash u8 constant_value; // Must be A5 u16 write_counter; // Number of times the amiibo has been written? INSERT_PADDING_BYTES(0x1); // Unknown 1 @@ -167,7 +167,7 @@ struct NTAG215File { std::array unknown; HashData hash; // Probably a SHA256-HMAC hash? ApplicationArea application_area; // Encrypted Game data - HashData locked_hash; // Hash + HashData hmac_tag; // Hash std::array uuid; AmiiboModelInfo model_info; HashData keygen_salt; // Salt -- cgit v1.2.3 From 9c6cd93195025a7e1a2b14eb95c9d684240b11b4 Mon Sep 17 00:00:00 2001 From: SachinVin <26602104+SachinVin@users.noreply.github.com> Date: Thu, 8 Sep 2022 21:56:11 +0530 Subject: core/CMakeLists.txt: Remove duplicate files. --- src/core/CMakeLists.txt | 6 ------ 1 file changed, 6 deletions(-) (limited to 'src/core') diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 806e7ff6c..405a2f993 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -4,12 +4,6 @@ add_library(core STATIC arm/arm_interface.h arm/arm_interface.cpp - arm/dynarmic/arm_dynarmic_32.cpp - arm/dynarmic/arm_dynarmic_32.h - arm/dynarmic/arm_dynarmic_64.cpp - arm/dynarmic/arm_dynarmic_64.h - arm/dynarmic/arm_dynarmic_cp15.cpp - arm/dynarmic/arm_dynarmic_cp15.h arm/dynarmic/arm_exclusive_monitor.cpp arm/dynarmic/arm_exclusive_monitor.h arm/exclusive_monitor.cpp -- cgit v1.2.3 From e93e898df528d013e2e0cfeba22e2b6d76bf99b6 Mon Sep 17 00:00:00 2001 From: Kelebek1 Date: Sat, 10 Sep 2022 21:14:03 +0100 Subject: Remove pause callbacks from coretiming --- src/audio_core/audio_core.cpp | 10 --------- src/audio_core/audio_core.h | 8 ------- src/audio_core/renderer/system_manager.cpp | 11 +--------- src/audio_core/renderer/system_manager.h | 9 -------- src/audio_core/sink/cubeb_sink.cpp | 34 ++++++------------------------ src/audio_core/sink/cubeb_sink.h | 10 --------- src/audio_core/sink/null_sink.h | 2 -- src/audio_core/sink/sdl2_sink.cpp | 27 +++++------------------- src/audio_core/sink/sdl2_sink.h | 10 --------- src/audio_core/sink/sink.h | 10 --------- src/audio_core/sink/sink_stream.cpp | 16 ++++++++++++++ src/audio_core/sink/sink_stream.h | 2 -- src/core/core.cpp | 4 ---- src/core/core_timing.cpp | 14 ------------ src/core/core_timing.h | 6 ------ 15 files changed, 29 insertions(+), 144 deletions(-) (limited to 'src/core') diff --git a/src/audio_core/audio_core.cpp b/src/audio_core/audio_core.cpp index 9feec1829..c845330cd 100644 --- a/src/audio_core/audio_core.cpp +++ b/src/audio_core/audio_core.cpp @@ -47,16 +47,6 @@ AudioRenderer::ADSP::ADSP& AudioCore::GetADSP() { return *adsp; } -void AudioCore::PauseSinks(const bool pausing) const { - if (pausing) { - output_sink->PauseStreams(); - input_sink->PauseStreams(); - } else { - output_sink->UnpauseStreams(); - input_sink->UnpauseStreams(); - } -} - void AudioCore::SetNVDECActive(bool active) { nvdec_active = active; } diff --git a/src/audio_core/audio_core.h b/src/audio_core/audio_core.h index ac9afefaa..c0a11e6b1 100644 --- a/src/audio_core/audio_core.h +++ b/src/audio_core/audio_core.h @@ -57,14 +57,6 @@ public: */ AudioRenderer::ADSP::ADSP& GetADSP(); - /** - * Pause the sink. Called from the core. - * - * @param pausing - Is this pause due to an actual pause, or shutdown? - * Unfortunately, shutdown also pauses streams, which can cause issues. - */ - void PauseSinks(bool pausing) const; - /** * Toggle NVDEC state, used to avoid stall in playback. * diff --git a/src/audio_core/renderer/system_manager.cpp b/src/audio_core/renderer/system_manager.cpp index bc2dd9e6e..9c1331e19 100644 --- a/src/audio_core/renderer/system_manager.cpp +++ b/src/audio_core/renderer/system_manager.cpp @@ -22,9 +22,7 @@ SystemManager::SystemManager(Core::System& core_) thread_event{Core::Timing::CreateEvent( "AudioRendererSystemManager", [this](std::uintptr_t, s64 time, std::chrono::nanoseconds) { return ThreadFunc2(time); - })} { - core.CoreTiming().RegisterPauseCallback([this](bool paused) { PauseCallback(paused); }); -} + })} {} SystemManager::~SystemManager() { Stop(); @@ -125,11 +123,4 @@ std::optional SystemManager::ThreadFunc2(s64 time) { return std::nullopt; } -void SystemManager::PauseCallback(bool paused) { - if (paused && core.IsPoweredOn() && core.IsShuttingDown()) { - update.store(true); - update.notify_all(); - } -} - } // 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: */ std::optional ThreadFunc2(s64 time); - /** - * Callback from core timing when pausing, used to detect shutdowns and stop ThreadFunc. - * - * @param paused - Are we pausing or resuming? - */ - void PauseCallback(bool paused); - enum class StreamState { Filling, Steady, @@ -106,8 +99,6 @@ private: std::shared_ptr thread_event; /// Atomic for main thread to wait on std::atomic update{}; - /// Current state of the streams - StreamState state{StreamState::Filling}; }; } // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/sink/cubeb_sink.cpp b/src/audio_core/sink/cubeb_sink.cpp index 9ae043611..36b115ad6 100644 --- a/src/audio_core/sink/cubeb_sink.cpp +++ b/src/audio_core/sink/cubeb_sink.cpp @@ -129,20 +129,13 @@ public: * Default false. */ void Start(bool resume = false) override { - if (!ctx) { + if (!ctx || !paused) { return; } - if (resume && was_playing) { - if (cubeb_stream_start(stream_backend) != CUBEB_OK) { - LOG_CRITICAL(Audio_Sink, "Error starting cubeb stream"); - } - paused = false; - } else if (!resume) { - if (cubeb_stream_start(stream_backend) != CUBEB_OK) { - LOG_CRITICAL(Audio_Sink, "Error starting cubeb stream"); - } - paused = false; + paused = false; + if (cubeb_stream_start(stream_backend) != CUBEB_OK) { + LOG_CRITICAL(Audio_Sink, "Error starting cubeb stream"); } } @@ -151,16 +144,15 @@ public: */ void Stop() override { Unstall(); - if (!ctx) { + + if (!ctx || paused) { return; } + paused = true; if (cubeb_stream_stop(stream_backend) != CUBEB_OK) { LOG_CRITICAL(Audio_Sink, "Error stopping cubeb stream"); } - - was_playing.store(!paused); - paused = true; } private: @@ -286,18 +278,6 @@ void CubebSink::CloseStreams() { sink_streams.clear(); } -void CubebSink::PauseStreams() { - for (auto& stream : sink_streams) { - stream->Stop(); - } -} - -void CubebSink::UnpauseStreams() { - for (auto& stream : sink_streams) { - stream->Start(true); - } -} - f32 CubebSink::GetDeviceVolume() const { if (sink_streams.empty()) { return 1.0f; diff --git a/src/audio_core/sink/cubeb_sink.h b/src/audio_core/sink/cubeb_sink.h index 91a6480fa..d98fc0866 100644 --- a/src/audio_core/sink/cubeb_sink.h +++ b/src/audio_core/sink/cubeb_sink.h @@ -53,16 +53,6 @@ public: */ void CloseStreams() override; - /** - * Pause all streams. - */ - void PauseStreams() override; - - /** - * Unpause all streams. - */ - void UnpauseStreams() override; - /** * Get the device volume. Set from calls to the IAudioDevice service. * diff --git a/src/audio_core/sink/null_sink.h b/src/audio_core/sink/null_sink.h index eab9c3a0c..1215d3cd2 100644 --- a/src/audio_core/sink/null_sink.h +++ b/src/audio_core/sink/null_sink.h @@ -44,8 +44,6 @@ public: void CloseStream(SinkStream*) override {} void CloseStreams() override {} - void PauseStreams() override {} - void UnpauseStreams() override {} f32 GetDeviceVolume() const override { return 1.0f; } diff --git a/src/audio_core/sink/sdl2_sink.cpp b/src/audio_core/sink/sdl2_sink.cpp index 7ee1dd7cd..1bd001b94 100644 --- a/src/audio_core/sink/sdl2_sink.cpp +++ b/src/audio_core/sink/sdl2_sink.cpp @@ -108,17 +108,12 @@ public: * Default false. */ void Start(bool resume = false) override { - if (device == 0) { + if (device == 0 || !paused) { return; } - if (resume && was_playing) { - SDL_PauseAudioDevice(device, 0); - paused = false; - } else if (!resume) { - SDL_PauseAudioDevice(device, 0); - paused = false; - } + paused = false; + SDL_PauseAudioDevice(device, 0); } /** @@ -126,11 +121,11 @@ public: */ void Stop() override { Unstall(); - if (device == 0) { + if (device == 0 || paused) { return; } - SDL_PauseAudioDevice(device, 1); paused = true; + SDL_PauseAudioDevice(device, 1); } private: @@ -207,18 +202,6 @@ void SDLSink::CloseStreams() { sink_streams.clear(); } -void SDLSink::PauseStreams() { - for (auto& stream : sink_streams) { - stream->Stop(); - } -} - -void SDLSink::UnpauseStreams() { - for (auto& stream : sink_streams) { - stream->Start(); - } -} - f32 SDLSink::GetDeviceVolume() const { if (sink_streams.empty()) { return 1.0f; diff --git a/src/audio_core/sink/sdl2_sink.h b/src/audio_core/sink/sdl2_sink.h index 57de9b6c2..9e76dde4f 100644 --- a/src/audio_core/sink/sdl2_sink.h +++ b/src/audio_core/sink/sdl2_sink.h @@ -51,16 +51,6 @@ public: */ void CloseStreams() override; - /** - * Pause all streams. - */ - void PauseStreams() override; - - /** - * Unpause all streams. - */ - void UnpauseStreams() override; - /** * Get the device volume. Set from calls to the IAudioDevice service. * diff --git a/src/audio_core/sink/sink.h b/src/audio_core/sink/sink.h index 43d99b62e..61e3d80cc 100644 --- a/src/audio_core/sink/sink.h +++ b/src/audio_core/sink/sink.h @@ -39,16 +39,6 @@ public: */ virtual void CloseStreams() = 0; - /** - * Pause all streams. - */ - virtual void PauseStreams() = 0; - - /** - * Unpause all streams. - */ - virtual void UnpauseStreams() = 0; - /** * Create a new sink stream, kept within this sink, with a pointer returned for use. * Do not free the returned pointer. When done with the stream, call CloseStream on the sink. diff --git a/src/audio_core/sink/sink_stream.cpp b/src/audio_core/sink/sink_stream.cpp index 24636e512..59a8d69f0 100644 --- a/src/audio_core/sink/sink_stream.cpp +++ b/src/audio_core/sink/sink_stream.cpp @@ -145,6 +145,12 @@ void SinkStream::ProcessAudioIn(std::span input_buffer, std::size_t n const std::size_t frame_size_bytes = frame_size * sizeof(s16); size_t frames_written{0}; + // If we're paused or going to shut down, we don't want to consume buffers as coretiming is + // paused and we'll desync, so just return. + if (system.IsPaused() || system.IsShuttingDown()) { + return; + } + if (queued_buffers > max_queue_size) { Stall(); } @@ -195,6 +201,16 @@ void SinkStream::ProcessAudioOutAndRender(std::span output_buffer, std::siz const std::size_t frame_size_bytes = frame_size * sizeof(s16); size_t frames_written{0}; + // If we're paused or going to shut down, we don't want to consume buffers as coretiming is + // paused and we'll desync, so just play silence. + if (system.IsPaused() || system.IsShuttingDown()) { + constexpr std::array silence{}; + for (size_t i = frames_written; i < num_frames; i++) { + std::memcpy(&output_buffer[i * frame_size], &silence[0], frame_size_bytes); + } + return; + } + // Due to many frames being queued up with nvdec (5 frames or so?), a lot of buffers also get // queued up (30+) but not all at once, which causes constant stalling here, so just let the // video play out without attempting to stall. diff --git a/src/audio_core/sink/sink_stream.h b/src/audio_core/sink/sink_stream.h index db7cff45e..9aada54f1 100644 --- a/src/audio_core/sink/sink_stream.h +++ b/src/audio_core/sink/sink_stream.h @@ -220,8 +220,6 @@ protected: u32 device_channels{2}; /// Is this stream currently paused? std::atomic paused{true}; - /// Was this stream previously playing? - std::atomic was_playing{false}; /// Name of this stream std::string name{}; 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 { core_timing.SyncPause(false); is_paused = false; - audio_core->PauseSinks(false); - return status; } @@ -150,8 +148,6 @@ struct System::Impl { std::unique_lock lk(suspend_guard); status = SystemResultStatus::Success; - audio_core->PauseSinks(true); - core_timing.SyncPause(true); kernel.Suspend(true); is_paused = true; diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp index 2dbb99c8b..5375a5d59 100644 --- a/src/core/core_timing.cpp +++ b/src/core/core_timing.cpp @@ -73,7 +73,6 @@ void CoreTiming::Shutdown() { if (timer_thread) { timer_thread->join(); } - pause_callbacks.clear(); ClearPendingEvents(); timer_thread.reset(); has_started = false; @@ -86,10 +85,6 @@ void CoreTiming::Pause(bool is_paused) { if (!is_paused) { pause_end_time = GetGlobalTimeNs().count(); } - - for (auto& cb : pause_callbacks) { - cb(is_paused); - } } void CoreTiming::SyncPause(bool is_paused) { @@ -110,10 +105,6 @@ void CoreTiming::SyncPause(bool is_paused) { if (!is_paused) { pause_end_time = GetGlobalTimeNs().count(); } - - for (auto& cb : pause_callbacks) { - cb(is_paused); - } } bool CoreTiming::IsRunning() const { @@ -219,11 +210,6 @@ void CoreTiming::RemoveEvent(const std::shared_ptr& event_type) { } } -void CoreTiming::RegisterPauseCallback(PauseCallback&& callback) { - std::scoped_lock lock{basic_lock}; - pause_callbacks.emplace_back(std::move(callback)); -} - std::optional CoreTiming::Advance() { std::scoped_lock lock{advance_lock, basic_lock}; global_timer = GetGlobalTimeNs().count(); 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 { /// A callback that may be scheduled for a particular core timing event. using TimedCallback = std::function( std::uintptr_t user_data, s64 time, std::chrono::nanoseconds ns_late)>; -using PauseCallback = std::function; /// Contains the characteristics of a particular event. struct EventType { @@ -134,9 +133,6 @@ public: /// Checks for events manually and returns time in nanoseconds for next event, threadsafe. std::optional Advance(); - /// Register a callback function to be called when coretiming pauses. - void RegisterPauseCallback(PauseCallback&& callback); - private: struct Event; @@ -176,8 +172,6 @@ private: /// Cycle timing u64 ticks{}; s64 downcount{}; - - std::vector pause_callbacks{}; }; /// Creates a core timing event with the given name and callback. -- cgit v1.2.3 From 1c7dae966d52287ba5812c27d2fe0c59938aa416 Mon Sep 17 00:00:00 2001 From: Lioncash Date: Wed, 14 Sep 2022 03:32:14 -0400 Subject: audio_device: Make AudioDeviceName constructor constexpr These are used as read-only arrays, so we can make the data read-only and available at compile-time. Now constructing an AudioDevice no longer needs to initialize some tables --- src/audio_core/audio_in_manager.cpp | 2 +- src/audio_core/audio_out_manager.cpp | 2 +- src/audio_core/renderer/audio_device.cpp | 24 +++++++++++++++++++++++- src/audio_core/renderer/audio_device.h | 16 ++++------------ src/core/hle/service/audio/audout_u.cpp | 3 +-- 5 files changed, 30 insertions(+), 17 deletions(-) (limited to 'src/core') 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 1) { - names.push_back(AudioRenderer::AudioDevice::AudioDeviceName("Uac")); + names.emplace_back("Uac"); return 1; } return 0; 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() { u32 Manager::GetAudioOutDeviceNames( std::vector& names) const { - names.push_back({"DeviceOut"}); + names.emplace_back("DeviceOut"); return 1; } diff --git a/src/audio_core/renderer/audio_device.cpp b/src/audio_core/renderer/audio_device.cpp index d5886e55e..a4696065e 100644 --- a/src/audio_core/renderer/audio_device.cpp +++ b/src/audio_core/renderer/audio_device.cpp @@ -1,6 +1,9 @@ // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include +#include + #include "audio_core/audio_core.h" #include "audio_core/common/feature_support.h" #include "audio_core/renderer/audio_device.h" @@ -9,6 +12,25 @@ namespace AudioCore::AudioRenderer { +constexpr std::array usb_device_names{ + AudioDevice::AudioDeviceName{"AudioStereoJackOutput"}, + AudioDevice::AudioDeviceName{"AudioBuiltInSpeakerOutput"}, + AudioDevice::AudioDeviceName{"AudioTvOutput"}, + AudioDevice::AudioDeviceName{"AudioUsbDeviceOutput"}, +}; + +constexpr std::array device_names{ + AudioDevice::AudioDeviceName{"AudioStereoJackOutput"}, + AudioDevice::AudioDeviceName{"AudioBuiltInSpeakerOutput"}, + AudioDevice::AudioDeviceName{"AudioTvOutput"}, +}; + +constexpr std::array output_device_names{ + AudioDevice::AudioDeviceName{"AudioBuiltInSpeakerOutput"}, + AudioDevice::AudioDeviceName{"AudioTvOutput"}, + AudioDevice::AudioDeviceName{"AudioExternalOutput"}, +}; + AudioDevice::AudioDevice(Core::System& system, const u64 applet_resource_user_id_, const u32 revision) : output_sink{system.AudioCore().GetOutputSink()}, @@ -16,7 +38,7 @@ AudioDevice::AudioDevice(Core::System& system, const u64 applet_resource_user_id u32 AudioDevice::ListAudioDeviceName(std::vector& out_buffer, const size_t max_count) { - std::span names{}; + std::span names{}; if (CheckFeatureSupported(SupportTags::AudioUsbDeviceOutput, user_revision)) { names = usb_device_names; diff --git a/src/audio_core/renderer/audio_device.h b/src/audio_core/renderer/audio_device.h index 1f449f261..ba1f4c748 100644 --- a/src/audio_core/renderer/audio_device.h +++ b/src/audio_core/renderer/audio_device.h @@ -3,7 +3,7 @@ #pragma once -#include +#include #include "audio_core/audio_render_manager.h" @@ -23,21 +23,13 @@ namespace AudioRenderer { class AudioDevice { public: struct AudioDeviceName { - std::array name; + std::array name{}; - AudioDeviceName(const char* name_) { - std::strncpy(name.data(), name_, name.size()); + constexpr AudioDeviceName(std::string_view name_) { + name_.copy(name.data(), name.size() - 1); } }; - std::array usb_device_names{"AudioStereoJackOutput", - "AudioBuiltInSpeakerOutput", "AudioTvOutput", - "AudioUsbDeviceOutput"}; - std::array device_names{"AudioStereoJackOutput", - "AudioBuiltInSpeakerOutput", "AudioTvOutput"}; - std::array output_device_names{"AudioBuiltInSpeakerOutput", "AudioTvOutput", - "AudioExternalOutput"}; - explicit AudioDevice(Core::System& system, u64 applet_resource_user_id, u32 revision); /** 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) { const auto write_count = static_cast(ctx.GetWriteBufferSize() / sizeof(AudioDevice::AudioDeviceName)); std::vector device_names{}; - std::string print_names{}; if (write_count > 0) { - device_names.push_back(AudioDevice::AudioDeviceName("DeviceOut")); + device_names.emplace_back("DeviceOut"); LOG_DEBUG(Service_Audio, "called. \nName=DeviceOut"); } else { LOG_DEBUG(Service_Audio, "called. Empty buffer passed in."); -- cgit v1.2.3 From d55046c5e97d2dc0a55d175e1101122d646ad540 Mon Sep 17 00:00:00 2001 From: Lioncash Date: Thu, 15 Sep 2022 09:06:14 -0400 Subject: audio_device: Mark member functions as const where applicable These member functions don't modify any internal state. --- src/audio_core/renderer/audio_device.cpp | 10 +++++----- src/audio_core/renderer/audio_device.h | 6 +++--- src/core/hle/service/audio/audren_u.cpp | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) (limited to 'src/core') diff --git a/src/audio_core/renderer/audio_device.cpp b/src/audio_core/renderer/audio_device.cpp index a4696065e..0d9d8f6ce 100644 --- a/src/audio_core/renderer/audio_device.cpp +++ b/src/audio_core/renderer/audio_device.cpp @@ -37,7 +37,7 @@ AudioDevice::AudioDevice(Core::System& system, const u64 applet_resource_user_id applet_resource_user_id{applet_resource_user_id_}, user_revision{revision} {} u32 AudioDevice::ListAudioDeviceName(std::vector& out_buffer, - const size_t max_count) { + const size_t max_count) const { std::span names{}; if (CheckFeatureSupported(SupportTags::AudioUsbDeviceOutput, user_revision)) { @@ -46,7 +46,7 @@ u32 AudioDevice::ListAudioDeviceName(std::vector& out_buffer, names = device_names; } - u32 out_count{static_cast(std::min(max_count, names.size()))}; + const u32 out_count{static_cast(std::min(max_count, names.size()))}; for (u32 i = 0; i < out_count; i++) { out_buffer.push_back(names[i]); } @@ -54,8 +54,8 @@ u32 AudioDevice::ListAudioDeviceName(std::vector& out_buffer, } u32 AudioDevice::ListAudioOutputDeviceName(std::vector& out_buffer, - const size_t max_count) { - u32 out_count{static_cast(std::min(max_count, output_device_names.size()))}; + const size_t max_count) const { + const u32 out_count{static_cast(std::min(max_count, output_device_names.size()))}; for (u32 i = 0; i < out_count; i++) { out_buffer.push_back(output_device_names[i]); @@ -67,7 +67,7 @@ void AudioDevice::SetDeviceVolumes(const f32 volume) { output_sink.SetDeviceVolume(volume); } -f32 AudioDevice::GetDeviceVolume([[maybe_unused]] std::string_view name) { +f32 AudioDevice::GetDeviceVolume([[maybe_unused]] std::string_view name) const { return output_sink.GetDeviceVolume(); } diff --git a/src/audio_core/renderer/audio_device.h b/src/audio_core/renderer/audio_device.h index ba1f4c748..dd6be70ee 100644 --- a/src/audio_core/renderer/audio_device.h +++ b/src/audio_core/renderer/audio_device.h @@ -39,7 +39,7 @@ public: * @param max_count - Maximum number of devices to write (count of out_buffer). * @return Number of device names written. */ - u32 ListAudioDeviceName(std::vector& out_buffer, size_t max_count); + u32 ListAudioDeviceName(std::vector& out_buffer, size_t max_count) const; /** * Get a list of the available output devices. @@ -49,7 +49,7 @@ public: * @param max_count - Maximum number of devices to write (count of out_buffer). * @return Number of device names written. */ - u32 ListAudioOutputDeviceName(std::vector& out_buffer, size_t max_count); + u32 ListAudioOutputDeviceName(std::vector& out_buffer, size_t max_count) const; /** * Set the volume of all streams in the backend sink. @@ -65,7 +65,7 @@ public: * @param name - Name of the device to check. Unused. * @return Volume of the device. */ - f32 GetDeviceVolume(std::string_view name); + f32 GetDeviceVolume(std::string_view name) const; private: /// Backend output sink for the device 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: std::vector out_names{}; - u32 out_count = impl->ListAudioDeviceName(out_names, in_count); + const u32 out_count = impl->ListAudioDeviceName(out_names, in_count); std::string out{}; for (u32 i = 0; i < out_count; i++) { @@ -365,7 +365,7 @@ private: std::vector out_names{}; - u32 out_count = impl->ListAudioOutputDeviceName(out_names, in_count); + const u32 out_count = impl->ListAudioOutputDeviceName(out_names, in_count); std::string out{}; for (u32 i = 0; i < out_count; i++) { -- cgit v1.2.3 From 672e61d8022aaa178b66bd92f2038d260dc3d104 Mon Sep 17 00:00:00 2001 From: FengChen Date: Sat, 17 Sep 2022 00:28:56 +0800 Subject: core: implement HwOpus GetWorkBufferSizeForMultiStreamEx --- src/core/hle/service/audio/hwopus.cpp | 28 +++++++++++++++++++++++++++- src/core/hle/service/audio/hwopus.h | 11 +++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) (limited to 'src/core') 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) { GetWorkBufferSize(ctx); } +void HwOpus::GetWorkBufferSizeForMultiStreamEx(Kernel::HLERequestContext& ctx) { + OpusMultiStreamParametersEx param; + std::memcpy(¶m, ctx.ReadBuffer().data(), ctx.GetReadBufferSize()); + + const auto sample_rate = param.sample_rate; + const auto channel_count = param.channel_count; + const auto number_streams = param.number_streams; + const auto number_stereo_streams = param.number_stereo_streams; + + LOG_DEBUG( + Audio, + "called with sample_rate={}, channel_count={}, number_streams={}, number_stereo_streams={}", + sample_rate, channel_count, number_streams, number_stereo_streams); + + ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 || + sample_rate == 12000 || sample_rate == 8000, + "Invalid sample rate"); + + const u32 worker_buffer_sz = + static_cast(opus_multistream_decoder_get_size(number_streams, number_stereo_streams)); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(worker_buffer_sz); +} + void HwOpus::OpenHardwareOpusDecoder(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto sample_rate = rp.Pop(); @@ -335,7 +361,7 @@ HwOpus::HwOpus(Core::System& system_) : ServiceFramework{system_, "hwopus"} { {4, &HwOpus::OpenHardwareOpusDecoderEx, "OpenHardwareOpusDecoderEx"}, {5, &HwOpus::GetWorkBufferSizeEx, "GetWorkBufferSizeEx"}, {6, nullptr, "OpenHardwareOpusDecoderForMultiStreamEx"}, - {7, nullptr, "GetWorkBufferSizeForMultiStreamEx"}, + {7, &HwOpus::GetWorkBufferSizeForMultiStreamEx, "GetWorkBufferSizeForMultiStreamEx"}, }; RegisterHandlers(functions); } 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; namespace Service::Audio { +struct OpusMultiStreamParametersEx { + u32 sample_rate; + u32 channel_count; + u32 number_streams; + u32 number_stereo_streams; + u32 use_large_frame_size; + u32 padding; + std::array channel_mappings; +}; + class HwOpus final : public ServiceFramework { public: explicit HwOpus(Core::System& system_); @@ -21,6 +31,7 @@ private: void OpenHardwareOpusDecoderEx(Kernel::HLERequestContext& ctx); void GetWorkBufferSize(Kernel::HLERequestContext& ctx); void GetWorkBufferSizeEx(Kernel::HLERequestContext& ctx); + void GetWorkBufferSizeForMultiStreamEx(Kernel::HLERequestContext& ctx); }; } // namespace Service::Audio -- cgit v1.2.3