From dc915aff6200f957ad6de2b6877f42d1ee71660b Mon Sep 17 00:00:00 2001 From: lat9nq <22451773+lat9nq@users.noreply.github.com> Date: Sat, 17 Jul 2021 16:58:52 -0400 Subject: [PATCH 01/78] ci,linux: Support Patreon releases The Early Access AppImage needs to be accessible through liftinstall, so a couple modifications need to made: The DIR_NAME needs to not include the revision info. The EA AppImage name cannot contain revision info. The EA AppImage has to be packaged with the rest of the yuzu package, which means both binaries and the source are bundled with it now in an archive. In addition, fix the source archive so yuzu can actually be built from it. upload: Copy AppImage to both mainline and EA release package --- .ci/scripts/linux/upload.sh | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/.ci/scripts/linux/upload.sh b/.ci/scripts/linux/upload.sh index 8173c5728..155edf25b 100755 --- a/.ci/scripts/linux/upload.sh +++ b/.ci/scripts/linux/upload.sh @@ -5,15 +5,16 @@ . .ci/scripts/common/pre-upload.sh -APPIMAGE_NAME="yuzu-${GITDATE}-${GITREV}.AppImage" -REV_NAME="yuzu-linux-${GITDATE}-${GITREV}" +APPIMAGE_NAME="yuzu-${RELEASE_NAME}-${GITDATE}-${GITREV}.AppImage" +BASE_NAME="yuzu-linux" +REV_NAME="${BASE_NAME}-${GITDATE}-${GITREV}" ARCHIVE_NAME="${REV_NAME}.tar.xz" COMPRESSION_FLAGS="-cJvf" -if [ "${RELEASE_NAME}" = "mainline" ]; then - DIR_NAME="${REV_NAME}" +if [ "${RELEASE_NAME}" = "mainline" ] || [ "${RELEASE_NAME}" = "early-access" ]; then + DIR_NAME="${BASE_NAME}-${RELEASE_NAME}" else - DIR_NAME="${REV_NAME}_${RELEASE_NAME}" + DIR_NAME="${REV_NAME}-${RELEASE_NAME}" fi mkdir "$DIR_NAME" @@ -46,4 +47,9 @@ if [ -f "build/${APPIMAGE_NAME}.zsync" ]; then cp "build/${APPIMAGE_NAME}.zsync" "${ARTIFACTS_DIR}/" fi +# Copy the AppImage to the general release directory and remove git revision info +if [ "${RELEASE_NAME}" = "mainline" ] || [ "${RELEASE_NAME}" = "early-access" ]; then + cp "build/${APPIMAGE_NAME}" "${DIR_NAME}/yuzu-${RELEASE_NAME}.AppImage" +fi + . .ci/scripts/common/post-upload.sh From 69bd6cd4904b9c392d1c79f3049e01a3f5a1e15c Mon Sep 17 00:00:00 2001 From: lat9nq <22451773+lat9nq@users.noreply.github.com> Date: Sat, 17 Jul 2021 18:41:11 -0400 Subject: [PATCH 02/78] patreon step2: Enable Linux build I sure as heck don't know what I'm doing :) patreon_step2: Fix caching :limesDance: still don't know what I'm doing :limesDance: --- .ci/yuzu-patreon-step2.yml | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/.ci/yuzu-patreon-step2.yml b/.ci/yuzu-patreon-step2.yml index 33c081c53..4c5ede3bc 100644 --- a/.ci/yuzu-patreon-step2.yml +++ b/.ci/yuzu-patreon-step2.yml @@ -8,12 +8,37 @@ variables: DisplayVersion: $[counter(variables['DisplayPrefix'], 1)] stages: -- stage: build - displayName: 'build' +- stage: build_gcc + displayName: 'build-gcc' jobs: - job: build timeoutInMinutes: 120 - displayName: 'windows-msvc' + displayName: 'linux' + pool: + vmImage: ubuntu-latest + strategy: + maxParallel: 10 + matrix: + linux: + BuildSuffix: 'linux' + ScriptFolder: 'linux' + steps: + - template: ./templates/sync-source.yml + parameters: + artifactSource: $(parameters.artifactSource) + needSubmodules: 'true' + - template: ./templates/build-single.yml + parameters: + artifactSource: 'false' + cache: $(parameters.cache) + version: $(DisplayVersion) +- stage: build_msvc + dependsOn: [] + displayName: 'build-msvc' + jobs: + - job: build + timeoutInMinutes: 120 + displayName: 'windows' pool: vmImage: windows-2019 steps: @@ -28,7 +53,9 @@ stages: version: $(DisplayVersion) - stage: release displayName: 'release' - dependsOn: build + dependsOn: + - build_gcc + - build_msvc jobs: - job: release displayName: 'source' From d77fe3b1c2990e97a5c8f3f5f76476f44b8892b0 Mon Sep 17 00:00:00 2001 From: lat9nq Date: Sun, 31 Jul 2022 00:07:30 -0400 Subject: [PATCH 03/78] ci/linux: EA AppImage adjustments Prevent AppImageLauncher from trying to integrate our AppImage on end user systems. Don't include the basic yuzu executable with EA or Mainline. --- .ci/scripts/linux/upload.sh | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.ci/scripts/linux/upload.sh b/.ci/scripts/linux/upload.sh index 155edf25b..e0f336427 100755 --- a/.ci/scripts/linux/upload.sh +++ b/.ci/scripts/linux/upload.sh @@ -20,7 +20,9 @@ fi mkdir "$DIR_NAME" cp build/bin/yuzu-cmd "$DIR_NAME" -cp build/bin/yuzu "$DIR_NAME" +if [ "${RELEASE_NAME}" != "early-access" ] && [ "${RELEASE_NAME}" != "mainline" ]; then + cp build/bin/yuzu "$DIR_NAME" +fi # Build an AppImage cd build @@ -33,6 +35,11 @@ if ! ./appimagetool-x86_64.AppImage --version; then export APPIMAGE_EXTRACT_AND_RUN=1 fi +# Don't let AppImageLauncher ask to integrate EA +if [ "${RELEASE_NAME}" = "mainline" ] || [ "${RELEASE_NAME}" = "early-access" ]; then + echo "X-AppImage-Integrate=false" >> AppDir/org.yuzu_emu.yuzu.desktop +fi + if [ "${RELEASE_NAME}" = "mainline" ]; then # Generate update information if releasing to mainline ./appimagetool-x86_64.AppImage -u "gh-releases-zsync|yuzu-emu|yuzu-${RELEASE_NAME}|latest|yuzu-*.AppImage.zsync" AppDir "${APPIMAGE_NAME}" From 6b58db9ccda970aada8f7d4b3d53c6e5759a6fb7 Mon Sep 17 00:00:00 2001 From: lat9nq Date: Sun, 31 Jul 2022 21:07:46 -0400 Subject: [PATCH 04/78] patreon step2: Use jobs to build for Windows and Linux Apparently the two stages were not building in parallel. Specify individual jobs that run MSVC and Linux building instead. --- .ci/yuzu-patreon-step2.yml | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/.ci/yuzu-patreon-step2.yml b/.ci/yuzu-patreon-step2.yml index 4c5ede3bc..b4995782b 100644 --- a/.ci/yuzu-patreon-step2.yml +++ b/.ci/yuzu-patreon-step2.yml @@ -8,10 +8,10 @@ variables: DisplayVersion: $[counter(variables['DisplayPrefix'], 1)] stages: -- stage: build_gcc - displayName: 'build-gcc' +- stage: build + displayName: 'build' jobs: - - job: build + - job: linux timeoutInMinutes: 120 displayName: 'linux' pool: @@ -32,11 +32,7 @@ stages: artifactSource: 'false' cache: $(parameters.cache) version: $(DisplayVersion) -- stage: build_msvc - dependsOn: [] - displayName: 'build-msvc' - jobs: - - job: build + - job: msvc timeoutInMinutes: 120 displayName: 'windows' pool: @@ -53,9 +49,7 @@ stages: version: $(DisplayVersion) - stage: release displayName: 'release' - dependsOn: - - build_gcc - - build_msvc + dependsOn: build jobs: - job: release displayName: 'source' From 260430c84951166ca1466de42937d70edf1ab5d0 Mon Sep 17 00:00:00 2001 From: lat9nq Date: Tue, 26 Jul 2022 16:46:55 -0400 Subject: [PATCH 05/78] common: Use PROJECT_SOURCE_DIR to find CMakeModules Fixes CMake configuration when yuzu is a submodule of another project. --- src/common/CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 05fdfea82..3ff373f7f 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -16,7 +16,7 @@ find_package(Git QUIET) add_custom_command(OUTPUT scm_rev.cpp COMMAND ${CMAKE_COMMAND} - -DSRC_DIR=${CMAKE_SOURCE_DIR} + -DSRC_DIR=${PROJECT_SOURCE_DIR} -DBUILD_REPOSITORY=${BUILD_REPOSITORY} -DTITLE_BAR_FORMAT_IDLE=${TITLE_BAR_FORMAT_IDLE} -DTITLE_BAR_FORMAT_RUNNING=${TITLE_BAR_FORMAT_RUNNING} @@ -28,13 +28,13 @@ add_custom_command(OUTPUT scm_rev.cpp -DGIT_BRANCH=${GIT_BRANCH} -DBUILD_FULLNAME=${BUILD_FULLNAME} -DGIT_EXECUTABLE=${GIT_EXECUTABLE} - -P ${CMAKE_SOURCE_DIR}/CMakeModules/GenerateSCMRev.cmake + -P ${PROJECT_SOURCE_DIR}/CMakeModules/GenerateSCMRev.cmake DEPENDS # Check that the scm_rev files haven't changed "${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.cpp.in" "${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.h" # technically we should regenerate if the git version changed, but its not worth the effort imo - "${CMAKE_SOURCE_DIR}/CMakeModules/GenerateSCMRev.cmake" + "${PROJECT_SOURCE_DIR}/CMakeModules/GenerateSCMRev.cmake" VERBATIM ) From 9d3b1904651321f44b8f801bc0452390afc16ffc Mon Sep 17 00:00:00 2001 From: Kelebek1 Date: Tue, 26 Jul 2022 22:04:18 +0100 Subject: [PATCH 06/78] 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(-) 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; From 83a24ad638a2c3ff628fe109391586be9bd3a45d Mon Sep 17 00:00:00 2001 From: Kelebek1 Date: Wed, 27 Jul 2022 22:31:41 +0100 Subject: [PATCH 07/78] Make coretiming waiting more accurate --- src/common/thread.h | 4 ++++ src/core/core_timing.cpp | 40 ++++++++++++++++++++++++++++------------ 2 files changed, 32 insertions(+), 12 deletions(-) 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) { - // 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}; - if (evt.time < pause_end_time) { - next_time = pause_end_time + evt.reschedule_time; - } - 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 + next_schedule_time}; + if (evt.time < pause_end_time) { + next_time = pause_end_time + next_schedule_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(); } From 658e1ee4267f0475445c7152560783444fcda7d2 Mon Sep 17 00:00:00 2001 From: Kelebek1 Date: Tue, 2 Aug 2022 05:28:31 +0100 Subject: [PATCH 08/78] Add missing looping event schedule signal --- src/core/core_timing.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) 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, 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: [PATCH 09/78] core_timing: Sleep in discrete intervals, yield during spin --- src/core/core_timing.cpp | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) 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()) { From db3eb168cd6a55c6387e3bfb23fb2eca061a691b Mon Sep 17 00:00:00 2001 From: Liam Date: Thu, 25 Aug 2022 12:32:14 -0400 Subject: [PATCH 10/78] video_core: add option for pessimistic flushing --- src/common/settings.cpp | 1 + src/common/settings.h | 1 + src/video_core/buffer_cache/buffer_base.h | 5 ++++- src/yuzu/configuration/config.cpp | 2 ++ src/yuzu/configuration/configure_graphics_advanced.cpp | 8 ++++++++ src/yuzu/configuration/configure_graphics_advanced.h | 1 + src/yuzu/configuration/configure_graphics_advanced.ui | 10 ++++++++++ src/yuzu_cmd/config.cpp | 1 + src/yuzu_cmd/default_ini.h | 4 ++++ 9 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/common/settings.cpp b/src/common/settings.cpp index 7282a45d3..0a560ebb7 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp @@ -195,6 +195,7 @@ void RestoreGlobalState(bool is_powered_on) { values.shader_backend.SetGlobal(true); values.use_asynchronous_shaders.SetGlobal(true); values.use_fast_gpu_time.SetGlobal(true); + values.use_pessimistic_flushes.SetGlobal(true); values.bg_red.SetGlobal(true); values.bg_green.SetGlobal(true); values.bg_blue.SetGlobal(true); diff --git a/src/common/settings.h b/src/common/settings.h index 14ed9b237..13651de57 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -446,6 +446,7 @@ struct Values { ShaderBackend::SPIRV, "shader_backend"}; SwitchableSetting use_asynchronous_shaders{false, "use_asynchronous_shaders"}; SwitchableSetting use_fast_gpu_time{true, "use_fast_gpu_time"}; + SwitchableSetting use_pessimistic_flushes{false, "use_pessimistic_flushes"}; SwitchableSetting bg_red{0, "bg_red"}; SwitchableSetting bg_green{0, "bg_green"}; diff --git a/src/video_core/buffer_cache/buffer_base.h b/src/video_core/buffer_cache/buffer_base.h index 0b2bc67b1..f9a6472cf 100644 --- a/src/video_core/buffer_cache/buffer_base.h +++ b/src/video_core/buffer_cache/buffer_base.h @@ -12,6 +12,7 @@ #include "common/common_funcs.h" #include "common/common_types.h" #include "common/div_ceil.h" +#include "common/settings.h" #include "core/memory.h" namespace VideoCommon { @@ -219,7 +220,9 @@ public: NotifyRasterizer(word_index, untracked_words[word_index], cached_bits); untracked_words[word_index] |= cached_bits; cpu_words[word_index] |= cached_bits; - cached_words[word_index] = 0; + if (!Settings::values.use_pessimistic_flushes) { + cached_words[word_index] = 0; + } } } diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index da6e5aa88..8ecd87150 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -684,6 +684,7 @@ void Config::ReadRendererValues() { ReadGlobalSetting(Settings::values.shader_backend); ReadGlobalSetting(Settings::values.use_asynchronous_shaders); ReadGlobalSetting(Settings::values.use_fast_gpu_time); + ReadGlobalSetting(Settings::values.use_pessimistic_flushes); ReadGlobalSetting(Settings::values.bg_red); ReadGlobalSetting(Settings::values.bg_green); ReadGlobalSetting(Settings::values.bg_blue); @@ -1300,6 +1301,7 @@ void Config::SaveRendererValues() { Settings::values.shader_backend.UsingGlobal()); WriteGlobalSetting(Settings::values.use_asynchronous_shaders); WriteGlobalSetting(Settings::values.use_fast_gpu_time); + WriteGlobalSetting(Settings::values.use_pessimistic_flushes); WriteGlobalSetting(Settings::values.bg_red); WriteGlobalSetting(Settings::values.bg_green); WriteGlobalSetting(Settings::values.bg_blue); diff --git a/src/yuzu/configuration/configure_graphics_advanced.cpp b/src/yuzu/configuration/configure_graphics_advanced.cpp index 7c3196c83..01f074699 100644 --- a/src/yuzu/configuration/configure_graphics_advanced.cpp +++ b/src/yuzu/configuration/configure_graphics_advanced.cpp @@ -28,6 +28,7 @@ void ConfigureGraphicsAdvanced::SetConfiguration() { ui->use_vsync->setChecked(Settings::values.use_vsync.GetValue()); ui->use_asynchronous_shaders->setChecked(Settings::values.use_asynchronous_shaders.GetValue()); ui->use_fast_gpu_time->setChecked(Settings::values.use_fast_gpu_time.GetValue()); + ui->use_pessimistic_flushes->setChecked(Settings::values.use_pessimistic_flushes.GetValue()); if (Settings::IsConfiguringGlobal()) { ui->gpu_accuracy->setCurrentIndex( @@ -55,6 +56,8 @@ void ConfigureGraphicsAdvanced::ApplyConfiguration() { use_asynchronous_shaders); ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_fast_gpu_time, ui->use_fast_gpu_time, use_fast_gpu_time); + ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_pessimistic_flushes, + ui->use_pessimistic_flushes, use_pessimistic_flushes); } void ConfigureGraphicsAdvanced::changeEvent(QEvent* event) { @@ -77,6 +80,8 @@ void ConfigureGraphicsAdvanced::SetupPerGameUI() { ui->use_asynchronous_shaders->setEnabled( Settings::values.use_asynchronous_shaders.UsingGlobal()); ui->use_fast_gpu_time->setEnabled(Settings::values.use_fast_gpu_time.UsingGlobal()); + ui->use_pessimistic_flushes->setEnabled( + Settings::values.use_pessimistic_flushes.UsingGlobal()); ui->anisotropic_filtering_combobox->setEnabled( Settings::values.max_anisotropy.UsingGlobal()); @@ -89,6 +94,9 @@ void ConfigureGraphicsAdvanced::SetupPerGameUI() { use_asynchronous_shaders); ConfigurationShared::SetColoredTristate(ui->use_fast_gpu_time, Settings::values.use_fast_gpu_time, use_fast_gpu_time); + ConfigurationShared::SetColoredTristate(ui->use_pessimistic_flushes, + Settings::values.use_pessimistic_flushes, + use_pessimistic_flushes); ConfigurationShared::SetColoredComboBox( ui->gpu_accuracy, ui->label_gpu_accuracy, static_cast(Settings::values.gpu_accuracy.GetValue(true))); diff --git a/src/yuzu/configuration/configure_graphics_advanced.h b/src/yuzu/configuration/configure_graphics_advanced.h index 1ef7bd916..12e816905 100644 --- a/src/yuzu/configuration/configure_graphics_advanced.h +++ b/src/yuzu/configuration/configure_graphics_advanced.h @@ -39,6 +39,7 @@ private: ConfigurationShared::CheckState use_vsync; ConfigurationShared::CheckState use_asynchronous_shaders; ConfigurationShared::CheckState use_fast_gpu_time; + ConfigurationShared::CheckState use_pessimistic_flushes; const Core::System& system; }; diff --git a/src/yuzu/configuration/configure_graphics_advanced.ui b/src/yuzu/configuration/configure_graphics_advanced.ui index d6d819364..87a121471 100644 --- a/src/yuzu/configuration/configure_graphics_advanced.ui +++ b/src/yuzu/configuration/configure_graphics_advanced.ui @@ -99,6 +99,16 @@ + + + + Enables pessimistic buffer flushes. This option will force unmodified buffers to be flushed, which can cost performance. + + + Use pessimistic buffer flushes (Hack) + + + diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp index bd0fb75f8..66dd0dc15 100644 --- a/src/yuzu_cmd/config.cpp +++ b/src/yuzu_cmd/config.cpp @@ -314,6 +314,7 @@ void Config::ReadValues() { ReadSetting("Renderer", Settings::values.nvdec_emulation); ReadSetting("Renderer", Settings::values.accelerate_astc); ReadSetting("Renderer", Settings::values.use_fast_gpu_time); + ReadSetting("Renderer", Settings::values.use_pessimistic_flushes); ReadSetting("Renderer", Settings::values.bg_red); ReadSetting("Renderer", Settings::values.bg_green); diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h index 1168cf136..d214771b0 100644 --- a/src/yuzu_cmd/default_ini.h +++ b/src/yuzu_cmd/default_ini.h @@ -319,6 +319,10 @@ use_asynchronous_gpu_emulation = # 0: Off, 1 (default): On use_fast_gpu_time = +# Force unmodified buffers to be flushed, which can cost performance. +# 0: Off (default), 1: On +use_pessimistic_flushes = + # Whether to use garbage collection or not for GPU caches. # 0 (default): Off, 1: On use_caches_gc = From b904652d69fb3d3bf1918a7dd7f04bc049c9f460 Mon Sep 17 00:00:00 2001 From: FearlessTobi Date: Tue, 16 Aug 2022 23:13:05 +0200 Subject: [PATCH 11/78] yuzu_room: Remove dependency on core --- src/core/CMakeLists.txt | 2 -- src/dedicated_room/CMakeLists.txt | 2 +- src/dedicated_room/yuzu_room.cpp | 2 +- src/network/CMakeLists.txt | 6 ++++++ src/{core => network}/announce_multiplayer_session.cpp | 0 src/{core => network}/announce_multiplayer_session.h | 0 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 +- 11 files changed, 13 insertions(+), 9 deletions(-) rename src/{core => network}/announce_multiplayer_session.cpp (100%) rename src/{core => network}/announce_multiplayer_session.h (100%) 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/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/core/announce_multiplayer_session.cpp b/src/network/announce_multiplayer_session.cpp similarity index 100% rename from src/core/announce_multiplayer_session.cpp rename to src/network/announce_multiplayer_session.cpp diff --git a/src/core/announce_multiplayer_session.h b/src/network/announce_multiplayer_session.h similarity index 100% rename from src/core/announce_multiplayer_session.h rename to src/network/announce_multiplayer_session.h 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; From e431cb8d1606b4d43b091f0de64aefeb19ce88bf Mon Sep 17 00:00:00 2001 From: FearlessTobi Date: Sat, 27 Aug 2022 03:08:21 +0200 Subject: [PATCH 12/78] 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(-) 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 From d92826963ad15415ad18b2b56144e5eafb68ac5f Mon Sep 17 00:00:00 2001 From: FearlessTobi Date: Sat, 27 Aug 2022 03:12:12 +0200 Subject: [PATCH 13/78] 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(-) 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 From b961b385c373fd015178f789b3dc6b0565da9056 Mon Sep 17 00:00:00 2001 From: FearlessTobi Date: Sat, 27 Aug 2022 03:26:31 +0200 Subject: [PATCH 14/78] network: Use lower timeout for enet_host_service This allows us to have a 10x higher throughput of packets by using a much shorter waiting time. --- src/network/room.cpp | 2 +- src/network/room_member.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/network/room.cpp b/src/network/room.cpp index b06797bf1..34298f010 100644 --- a/src/network/room.cpp +++ b/src/network/room.cpp @@ -234,7 +234,7 @@ public: void Room::RoomImpl::ServerLoop() { while (state != State::Closed) { ENetEvent event; - if (enet_host_service(server, &event, 50) > 0) { + if (enet_host_service(server, &event, 5) > 0) { switch (event.type) { case ENET_EVENT_TYPE_RECEIVE: switch (event.packet->data[0]) { diff --git a/src/network/room_member.cpp b/src/network/room_member.cpp index 9f08bf611..367bf377f 100644 --- a/src/network/room_member.cpp +++ b/src/network/room_member.cpp @@ -159,7 +159,7 @@ void RoomMember::RoomMemberImpl::MemberLoop() { while (IsConnected()) { std::lock_guard lock(network_mutex); ENetEvent event; - if (enet_host_service(client, &event, 100) > 0) { + if (enet_host_service(client, &event, 5) > 0) { switch (event.type) { case ENET_EVENT_TYPE_RECEIVE: switch (event.packet->data[0]) { From 839e1faf491776f4e2348c46773c248644e260ba Mon Sep 17 00:00:00 2001 From: FearlessTobi Date: Sat, 27 Aug 2022 03:31:17 +0200 Subject: [PATCH 15/78] 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(-) 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()) { From 339758c9fce9a211f85f62182d8fa0e1115c229b Mon Sep 17 00:00:00 2001 From: FearlessTobi Date: Sat, 27 Aug 2022 03:39:02 +0200 Subject: [PATCH 16/78] 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(-) 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(); From 2b6ac4463c06cfdf50b1d150311a217d2ee11688 Mon Sep 17 00:00:00 2001 From: FearlessTobi Date: Sat, 27 Aug 2022 03:41:19 +0200 Subject: [PATCH 17/78] yuzu/multiplayer: Warn when game is running or no network interface is selected --- src/yuzu/main.cpp | 2 +- src/yuzu/multiplayer/direct_connect.cpp | 16 ++++++++++++++-- src/yuzu/multiplayer/direct_connect.h | 4 +++- src/yuzu/multiplayer/host_room.cpp | 16 ++++++++++++++-- src/yuzu/multiplayer/host_room.h | 4 +++- src/yuzu/multiplayer/lobby.cpp | 19 ++++++++++++++++--- src/yuzu/multiplayer/lobby.h | 4 +++- src/yuzu/multiplayer/message.cpp | 10 ++++++++++ src/yuzu/multiplayer/message.h | 9 +++++++++ src/yuzu/multiplayer/state.cpp | 12 +++++------- src/yuzu/multiplayer/state.h | 4 +++- 11 files changed, 81 insertions(+), 19 deletions(-) diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index e103df977..a85adc072 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -860,7 +860,7 @@ void GMainWindow::InitializeWidgets() { }); multiplayer_state = new MultiplayerState(this, game_list->GetModel(), ui->action_Leave_Room, - ui->action_Show_Room, system->GetRoomNetwork()); + ui->action_Show_Room, *system); multiplayer_state->setVisible(false); // Create status bar diff --git a/src/yuzu/multiplayer/direct_connect.cpp b/src/yuzu/multiplayer/direct_connect.cpp index 4c0ea0a6b..65b5f0b9d 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/internal_network/network_interface.h" #include "network/network.h" #include "ui_direct_connect.h" #include "yuzu/main.h" @@ -20,9 +21,10 @@ enum class ConnectionType : u8 { TraversalServer, IP }; -DirectConnectWindow::DirectConnectWindow(Network::RoomNetwork& room_network_, QWidget* parent) +DirectConnectWindow::DirectConnectWindow(Core::System& system_, QWidget* parent) : QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint), - ui(std::make_unique()), room_network{room_network_} { + ui(std::make_unique()), system{system_}, room_network{ + system.GetRoomNetwork()} { ui->setupUi(this); @@ -53,10 +55,20 @@ void DirectConnectWindow::RetranslateUi() { } void DirectConnectWindow::Connect() { + if (!Network::GetSelectedNetworkInterface()) { + NetworkMessage::ErrorManager::ShowError( + NetworkMessage::ErrorManager::NO_INTERFACE_SELECTED); + return; + } if (!ui->nickname->hasAcceptableInput()) { NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::USERNAME_NOT_VALID); return; } + if (system.IsPoweredOn()) { + if (!NetworkMessage::WarnGameRunning()) { + return; + } + } if (const auto member = room_network.GetRoomMember().lock()) { // Prevent the user from trying to join a room while they are already joining. if (member->GetState() == Network::RoomMember::State::Joining) { diff --git a/src/yuzu/multiplayer/direct_connect.h b/src/yuzu/multiplayer/direct_connect.h index 4e1043053..defa4f4ec 100644 --- a/src/yuzu/multiplayer/direct_connect.h +++ b/src/yuzu/multiplayer/direct_connect.h @@ -6,6 +6,7 @@ #include #include #include +#include "core/core.h" #include "yuzu/multiplayer/validation.h" namespace Ui { @@ -16,7 +17,7 @@ class DirectConnectWindow : public QDialog { Q_OBJECT public: - explicit DirectConnectWindow(Network::RoomNetwork& room_network_, QWidget* parent = nullptr); + explicit DirectConnectWindow(Core::System& system_, QWidget* parent = nullptr); ~DirectConnectWindow(); void RetranslateUi(); @@ -39,5 +40,6 @@ private: QFutureWatcher* watcher; std::unique_ptr ui; Validation validation; + Core::System& system; Network::RoomNetwork& room_network; }; diff --git a/src/yuzu/multiplayer/host_room.cpp b/src/yuzu/multiplayer/host_room.cpp index 8e7a81291..ad7dcc36d 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/internal_network/network_interface.h" #include "network/announce_multiplayer_session.h" #include "ui_host_room.h" #include "yuzu/game_list_p.h" @@ -27,10 +28,11 @@ HostRoomWindow::HostRoomWindow(QWidget* parent, QStandardItemModel* list, std::shared_ptr session, - Network::RoomNetwork& room_network_) + Core::System& system_) : QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint), ui(std::make_unique()), - announce_multiplayer_session(session), room_network{room_network_} { + announce_multiplayer_session(session), system{system_}, room_network{ + system.GetRoomNetwork()} { ui->setupUi(this); // set up validation for all of the fields @@ -105,6 +107,11 @@ std::unique_ptr HostRoomWindow::CreateVerifyBacken } void HostRoomWindow::Host() { + if (!Network::GetSelectedNetworkInterface()) { + NetworkMessage::ErrorManager::ShowError( + NetworkMessage::ErrorManager::NO_INTERFACE_SELECTED); + return; + } if (!ui->username->hasAcceptableInput()) { NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::USERNAME_NOT_VALID); return; @@ -121,6 +128,11 @@ void HostRoomWindow::Host() { NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::GAME_NOT_SELECTED); return; } + if (system.IsPoweredOn()) { + if (!NetworkMessage::WarnGameRunning()) { + return; + } + } if (auto member = room_network.GetRoomMember().lock()) { if (member->GetState() == Network::RoomMember::State::Joining) { return; diff --git a/src/yuzu/multiplayer/host_room.h b/src/yuzu/multiplayer/host_room.h index a968042d0..63c0d2333 100644 --- a/src/yuzu/multiplayer/host_room.h +++ b/src/yuzu/multiplayer/host_room.h @@ -8,6 +8,7 @@ #include #include #include +#include "core/core.h" #include "network/network.h" #include "yuzu/multiplayer/chat_room.h" #include "yuzu/multiplayer/validation.h" @@ -35,7 +36,7 @@ class HostRoomWindow : public QDialog { public: explicit HostRoomWindow(QWidget* parent, QStandardItemModel* list, std::shared_ptr session, - Network::RoomNetwork& room_network_); + Core::System& system_); ~HostRoomWindow(); /** @@ -54,6 +55,7 @@ private: QStandardItemModel* game_list; ComboBoxProxyModel* proxy; Validation validation; + Core::System& system; Network::RoomNetwork& room_network; }; diff --git a/src/yuzu/multiplayer/lobby.cpp b/src/yuzu/multiplayer/lobby.cpp index 1cc518279..c5fb846c7 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/internal_network/network_interface.h" #include "network/network.h" #include "ui_lobby.h" #include "yuzu/game_list_p.h" @@ -22,11 +23,11 @@ #endif Lobby::Lobby(QWidget* parent, QStandardItemModel* list, - std::shared_ptr session, - Network::RoomNetwork& room_network_) + std::shared_ptr session, Core::System& system_) : QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint), ui(std::make_unique()), - announce_multiplayer_session(session), room_network{room_network_} { + announce_multiplayer_session(session), system{system_}, room_network{ + system.GetRoomNetwork()} { ui->setupUi(this); // setup the watcher for background connections @@ -114,6 +115,18 @@ void Lobby::OnExpandRoom(const QModelIndex& index) { } void Lobby::OnJoinRoom(const QModelIndex& source) { + if (!Network::GetSelectedNetworkInterface()) { + NetworkMessage::ErrorManager::ShowError( + NetworkMessage::ErrorManager::NO_INTERFACE_SELECTED); + return; + } + + if (system.IsPoweredOn()) { + if (!NetworkMessage::WarnGameRunning()) { + return; + } + } + if (const auto member = room_network.GetRoomMember().lock()) { // Prevent the user from trying to join a room while they are already joining. if (member->GetState() == Network::RoomMember::State::Joining) { diff --git a/src/yuzu/multiplayer/lobby.h b/src/yuzu/multiplayer/lobby.h index 02cc766e4..49fd4c473 100644 --- a/src/yuzu/multiplayer/lobby.h +++ b/src/yuzu/multiplayer/lobby.h @@ -9,6 +9,7 @@ #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" @@ -30,7 +31,7 @@ class Lobby : public QDialog { public: explicit Lobby(QWidget* parent, QStandardItemModel* list, std::shared_ptr session, - Network::RoomNetwork& room_network_); + Core::System& system_); ~Lobby() override; /** @@ -94,6 +95,7 @@ private: std::weak_ptr announce_multiplayer_session; QFutureWatcher* watcher; Validation validation; + Core::System& system; Network::RoomNetwork& room_network; }; diff --git a/src/yuzu/multiplayer/message.cpp b/src/yuzu/multiplayer/message.cpp index 94d7a38b8..758b5b731 100644 --- a/src/yuzu/multiplayer/message.cpp +++ b/src/yuzu/multiplayer/message.cpp @@ -49,6 +49,9 @@ const ConnectionError ErrorManager::PERMISSION_DENIED( QT_TR_NOOP("You do not have enough permission to perform this action.")); const ConnectionError ErrorManager::NO_SUCH_USER(QT_TR_NOOP( "The user you are trying to kick/ban could not be found.\nThey may have left the room.")); +const ConnectionError ErrorManager::NO_INTERFACE_SELECTED( + QT_TR_NOOP("No network interface is selected.\nPlease go to Configure -> System -> Network and " + "make a selection.")); static bool WarnMessage(const std::string& title, const std::string& text) { return QMessageBox::Ok == QMessageBox::warning(nullptr, QObject::tr(title.c_str()), @@ -60,6 +63,13 @@ void ErrorManager::ShowError(const ConnectionError& e) { QMessageBox::critical(nullptr, tr("Error"), tr(e.GetString().c_str())); } +bool WarnGameRunning() { + return WarnMessage( + QT_TR_NOOP("Game already running"), + QT_TR_NOOP("Joining a room when the game is already running is discouraged " + "and can cause the room feature not to work correctly.\nProceed anyway?")); +} + bool WarnCloseRoom() { return WarnMessage( QT_TR_NOOP("Leave Room"), diff --git a/src/yuzu/multiplayer/message.h b/src/yuzu/multiplayer/message.h index 812495c72..f038b9a1f 100644 --- a/src/yuzu/multiplayer/message.h +++ b/src/yuzu/multiplayer/message.h @@ -43,11 +43,20 @@ public: static const ConnectionError IP_COLLISION; static const ConnectionError PERMISSION_DENIED; static const ConnectionError NO_SUCH_USER; + static const ConnectionError NO_INTERFACE_SELECTED; /** * Shows a standard QMessageBox with a error message */ static void ShowError(const ConnectionError& e); }; + +/** + * Show a standard QMessageBox with a warning message about joining a room when + * the game is already running + * return true if the user wants to close the network connection + */ +bool WarnGameRunning(); + /** * Show a standard QMessageBox with a warning message about leaving the room * return true if the user wants to close the network connection diff --git a/src/yuzu/multiplayer/state.cpp b/src/yuzu/multiplayer/state.cpp index dba76b22b..d1a68b182 100644 --- a/src/yuzu/multiplayer/state.cpp +++ b/src/yuzu/multiplayer/state.cpp @@ -19,10 +19,9 @@ #include "yuzu/util/clickable_label.h" MultiplayerState::MultiplayerState(QWidget* parent, QStandardItemModel* game_list_model_, - QAction* leave_room_, QAction* show_room_, - Network::RoomNetwork& room_network_) + QAction* leave_room_, QAction* show_room_, Core::System& system_) : QWidget(parent), game_list_model(game_list_model_), leave_room(leave_room_), - show_room(show_room_), room_network{room_network_} { + show_room(show_room_), system{system_}, room_network{system.GetRoomNetwork()} { if (auto member = room_network.GetRoomMember().lock()) { // register the network structs to use in slots and signals state_callback_handle = member->BindOnStateChanged( @@ -208,15 +207,14 @@ static void BringWidgetToFront(QWidget* widget) { void MultiplayerState::OnViewLobby() { if (lobby == nullptr) { - lobby = new Lobby(this, game_list_model, announce_multiplayer_session, room_network); + lobby = new Lobby(this, game_list_model, announce_multiplayer_session, system); } BringWidgetToFront(lobby); } void MultiplayerState::OnCreateRoom() { if (host_room == nullptr) { - host_room = - new HostRoomWindow(this, game_list_model, announce_multiplayer_session, room_network); + host_room = new HostRoomWindow(this, game_list_model, announce_multiplayer_session, system); } BringWidgetToFront(host_room); } @@ -279,7 +277,7 @@ void MultiplayerState::OnOpenNetworkRoom() { void MultiplayerState::OnDirectConnectToRoom() { if (direct_connect == nullptr) { - direct_connect = new DirectConnectWindow(room_network, this); + direct_connect = new DirectConnectWindow(system, this); } BringWidgetToFront(direct_connect); } diff --git a/src/yuzu/multiplayer/state.h b/src/yuzu/multiplayer/state.h index 23960414e..3921f2fc3 100644 --- a/src/yuzu/multiplayer/state.h +++ b/src/yuzu/multiplayer/state.h @@ -4,6 +4,7 @@ #pragma once #include +#include "core/core.h" #include "network/announce_multiplayer_session.h" #include "network/network.h" @@ -19,7 +20,7 @@ class MultiplayerState : public QWidget { public: explicit MultiplayerState(QWidget* parent, QStandardItemModel* game_list, QAction* leave_room, - QAction* show_room, Network::RoomNetwork& room_network_); + QAction* show_room, Core::System& system_); ~MultiplayerState(); /** @@ -86,6 +87,7 @@ private: Network::RoomMember::CallbackHandle error_callback_handle; bool show_notification = false; + Core::System& system; Network::RoomNetwork& room_network; }; From 27d7db1faecd761f6dccd753792b2e72d71e9fdd Mon Sep 17 00:00:00 2001 From: FearlessTobi Date: Sat, 27 Aug 2022 04:00:27 +0200 Subject: [PATCH 18/78] dedicated_room: Correctly handle token decoding Correctly handle token decoding when '=' has been trimmed by the backend server. Co-Authored-By: liushuyu --- src/dedicated_room/yuzu_room.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/dedicated_room/yuzu_room.cpp b/src/dedicated_room/yuzu_room.cpp index 7c1a75de3..173a53317 100644 --- a/src/dedicated_room/yuzu_room.cpp +++ b/src/dedicated_room/yuzu_room.cpp @@ -75,6 +75,17 @@ 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++) { + token.push_back('='); + } +} + static std::string UsernameFromDisplayToken(const std::string& display_token) { std::size_t outlen; @@ -300,6 +311,7 @@ int main(int argc, char** argv) { if (username.empty()) { LOG_INFO(Network, "Hosting a public room"); Settings::values.web_api_url = web_api_url; + PadToken(token); Settings::values.yuzu_username = UsernameFromDisplayToken(token); username = Settings::values.yuzu_username.GetValue(); Settings::values.yuzu_token = TokenFromDisplayToken(token); From 63b236d8532ef77742875f433b94242b8f955f31 Mon Sep 17 00:00:00 2001 From: FearlessTobi Date: Sat, 27 Aug 2022 04:04:00 +0200 Subject: [PATCH 19/78] yuzu/chat_room: Make font size bigger --- src/yuzu/multiplayer/chat_room.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/yuzu/multiplayer/chat_room.cpp b/src/yuzu/multiplayer/chat_room.cpp index 21582b3d6..bddceb795 100644 --- a/src/yuzu/multiplayer/chat_room.cpp +++ b/src/yuzu/multiplayer/chat_room.cpp @@ -178,6 +178,10 @@ ChatRoom::ChatRoom(QWidget* parent) : QWidget(parent), ui(std::make_uniquechat_history->document()->setMaximumBlockCount(max_chat_lines); + auto font = ui->chat_history->font(); + font.setPointSizeF(10); + ui->chat_history->setFont(font); + // register the network structs to use in slots and signals qRegisterMetaType(); qRegisterMetaType(); From 6791301d9a2fa8ddfe0de0d059763c701b830f94 Mon Sep 17 00:00:00 2001 From: FearlessTobi Date: Sat, 27 Aug 2022 04:49:10 +0200 Subject: [PATCH 20/78] core/ldn_types: Minor corrections and additions --- src/core/hle/service/ldn/ldn_types.h | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) 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"); From 118503f6e5fd4f0c17d89df57600ab17c9b42d52 Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Tue, 30 Aug 2022 14:28:33 -0400 Subject: [PATCH 21/78] ci: Enable building with Visual Studio 2022 (again) Since the following https://developercommunity.visualstudio.com/t/Type-alias-lookup-failure-within-paramet/10039150 compiler bug has been fixed, we can finally build with VS 2022 again. --- .ci/templates/build-msvc.yml | 2 +- .ci/yuzu-mainline-step2.yml | 2 +- .ci/yuzu-patreon-step2.yml | 2 +- .github/workflows/verify.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.ci/templates/build-msvc.yml b/.ci/templates/build-msvc.yml index 2a1bf93bc..a2ee71bd8 100644 --- a/.ci/templates/build-msvc.yml +++ b/.ci/templates/build-msvc.yml @@ -9,7 +9,7 @@ parameters: steps: - script: choco install vulkan-sdk displayName: 'Install vulkan-sdk' -- script: refreshenv && mkdir build && cd build && cmake -G "Visual Studio 16 2019" -A x64 -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_QT_WEB_ENGINE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DYUZU_TESTS=OFF -DUSE_DISCORD_PRESENCE=ON -DENABLE_QT_TRANSLATION=ON -DDISPLAY_VERSION=${{ parameters['version'] }} -DCMAKE_BUILD_TYPE=Release .. && cd .. +- script: refreshenv && mkdir build && cd build && cmake -G "Visual Studio 17 2022" -A x64 -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_QT_WEB_ENGINE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DYUZU_TESTS=OFF -DUSE_DISCORD_PRESENCE=ON -DENABLE_QT_TRANSLATION=ON -DDISPLAY_VERSION=${{ parameters['version'] }} -DCMAKE_BUILD_TYPE=Release .. && cd .. displayName: 'Configure CMake' - task: MSBuild@1 displayName: 'Build' diff --git a/.ci/yuzu-mainline-step2.yml b/.ci/yuzu-mainline-step2.yml index 0e99f43fa..b294827f4 100644 --- a/.ci/yuzu-mainline-step2.yml +++ b/.ci/yuzu-mainline-step2.yml @@ -50,7 +50,7 @@ stages: timeoutInMinutes: 120 displayName: 'msvc' pool: - vmImage: windows-2019 + vmImage: windows-2022 steps: - template: ./templates/sync-source.yml parameters: diff --git a/.ci/yuzu-patreon-step2.yml b/.ci/yuzu-patreon-step2.yml index 33c081c53..5d5b140fd 100644 --- a/.ci/yuzu-patreon-step2.yml +++ b/.ci/yuzu-patreon-step2.yml @@ -15,7 +15,7 @@ stages: timeoutInMinutes: 120 displayName: 'windows-msvc' pool: - vmImage: windows-2019 + vmImage: windows-2022 steps: - template: ./templates/sync-source.yml parameters: diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index 9a973ee0c..733a52764 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -71,7 +71,7 @@ jobs: build-msvc: name: 'test build (windows, msvc)' needs: format - runs-on: windows-2019 + runs-on: windows-2022 steps: - name: Set up cache uses: actions/cache@v3 From 7e379207ec7164cf9020bb83a5e2297dc463835d Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Wed, 31 Aug 2022 08:37:00 -0400 Subject: [PATCH 22/78] (shader/pipeline)_cache: Raise shader/pipeline cache version Since the following commit: https://github.com/yuzu-emu/yuzu/commit/a83a5d2e4c8932df864dd4cea2b04d87a12c8760 , many games will refuse to boot unless the shader/pipeline cache has been invalidated. --- src/video_core/renderer_opengl/gl_shader_cache.cpp | 2 +- src/video_core/renderer_vulkan/vk_pipeline_cache.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp index 1ad56d9e7..ddb70934c 100644 --- a/src/video_core/renderer_opengl/gl_shader_cache.cpp +++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp @@ -49,7 +49,7 @@ using VideoCommon::LoadPipelines; using VideoCommon::SerializePipeline; using Context = ShaderContext::Context; -constexpr u32 CACHE_VERSION = 5; +constexpr u32 CACHE_VERSION = 6; template auto MakeSpan(Container& container) { diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 3adad5af4..9708dc45e 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -53,7 +53,7 @@ using VideoCommon::FileEnvironment; using VideoCommon::GenericEnvironment; using VideoCommon::GraphicsEnvironment; -constexpr u32 CACHE_VERSION = 5; +constexpr u32 CACHE_VERSION = 6; template auto MakeSpan(Container& container) { From 95333654861b62167b9a8052ff3faaa931930d17 Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Wed, 31 Aug 2022 08:51:47 -0400 Subject: [PATCH 23/78] style: General style changes to match with the rest of the codebase --- src/shader_recompiler/ir_opt/texture_pass.cpp | 10 +++++----- src/video_core/shader_environment.cpp | 7 ++----- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/shader_recompiler/ir_opt/texture_pass.cpp b/src/shader_recompiler/ir_opt/texture_pass.cpp index 5cead5135..597112ba4 100644 --- a/src/shader_recompiler/ir_opt/texture_pass.cpp +++ b/src/shader_recompiler/ir_opt/texture_pass.cpp @@ -415,11 +415,11 @@ void TexturePass(Environment& env, IR::Program& program) { inst->SetFlags(flags); break; case IR::Opcode::ImageSampleImplicitLod: - if (flags.type == TextureType::Color2D) { - auto texture_type = ReadTextureType(env, cbuf); - if (texture_type == TextureType::Color2DRect) { - PatchImageSampleImplicitLod(*texture_inst.block, *texture_inst.inst); - } + if (flags.type != TextureType::Color2D) { + break; + } + if (ReadTextureType(env, cbuf) == TextureType::Color2DRect) { + PatchImageSampleImplicitLod(*texture_inst.block, *texture_inst.inst); } break; case IR::Opcode::ImageFetch: diff --git a/src/video_core/shader_environment.cpp b/src/video_core/shader_environment.cpp index 808d88eec..5f7625947 100644 --- a/src/video_core/shader_environment.cpp +++ b/src/video_core/shader_environment.cpp @@ -39,11 +39,8 @@ static Shader::TextureType ConvertType(const Tegra::Texture::TICEntry& entry) { return Shader::TextureType::Color1D; case Tegra::Texture::TextureType::Texture2D: case Tegra::Texture::TextureType::Texture2DNoMipmap: - if (entry.normalized_coords) { - return Shader::TextureType::Color2D; - } else { - return Shader::TextureType::Color2DRect; - } + return entry.normalized_coords ? Shader::TextureType::Color2D + : Shader::TextureType::Color2DRect; case Tegra::Texture::TextureType::Texture3D: return Shader::TextureType::Color3D; case Tegra::Texture::TextureType::TextureCubemap: From f294b886dbb6e34895da90dfb834c454ee3638de Mon Sep 17 00:00:00 2001 From: Kelebek1 Date: Thu, 1 Sep 2022 15:47:33 +0100 Subject: [PATCH 24/78] Silence std::aligned_storage warnings as it's deprecated in C++23, replace it with alignas() and a C array --- src/common/parent_of_member.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/parent_of_member.h b/src/common/parent_of_member.h index 70b1c5624..8e03f17d8 100644 --- a/src/common/parent_of_member.h +++ b/src/common/parent_of_member.h @@ -11,7 +11,7 @@ namespace Common { namespace detail { template struct TypedStorageImpl { - std::aligned_storage_t storage_; + alignas(Align) u8 storage_[Size]; }; } // namespace detail From 03aedccaa252a7989c3ab147a83305b386476748 Mon Sep 17 00:00:00 2001 From: Kelebek1 Date: Thu, 1 Sep 2022 16:47:47 +0100 Subject: [PATCH 25/78] 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(-) 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()) { From 65718e2876374aecf2ac29856387dab4394ca47f Mon Sep 17 00:00:00 2001 From: FearlessTobi Date: Sun, 28 Aug 2022 19:31:16 +0200 Subject: [PATCH 26/78] 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(-) 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; From ea9ff71725113b8dbb159917c57aa536bba0cb53 Mon Sep 17 00:00:00 2001 From: Kelebek1 Date: Mon, 1 Aug 2022 02:58:13 +0100 Subject: [PATCH 27/78] 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 +- .../renderer/adsp/audio_renderer.cpp | 9 +- .../renderer/behavior/behavior_info.cpp | 12 +- .../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, 550 insertions(+), 841 deletions(-) create mode 100644 src/audio_core/sink/sink_stream.cpp 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)); + out_count = std::min(error_count, MaxErrors); - for (size_t i = 0; i < error_count_; i++) { - out_errors[i] = errors[i]; + 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; From b1076859c45ba6ef732cbfa0a8ba8e6bebb0b1b5 Mon Sep 17 00:00:00 2001 From: Kyle Kienapfel Date: Sat, 3 Sep 2022 04:39:28 -0700 Subject: [PATCH 28/78] Qt: Make General->Debug scrollable Configuration -> General -> Debug is getting a bit crowded. yzct12345 submit this originally, so I'm tagging them as a co-author. The original #6714 also modifies the Controls -> Player N sections, but it looks like more work is needed to make the current area scrollable. Co-authored-by: yzct12345 <87620833+yzct12345@users.noreply.github.com> --- src/yuzu/configuration/configure_debug.cpp | 2 +- src/yuzu/configuration/configure_debug.h | 4 ++-- src/yuzu/configuration/configure_debug.ui | 7 ++++++- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp index e16d127a8..04d397750 100644 --- a/src/yuzu/configuration/configure_debug.cpp +++ b/src/yuzu/configuration/configure_debug.cpp @@ -14,7 +14,7 @@ #include "yuzu/uisettings.h" ConfigureDebug::ConfigureDebug(const Core::System& system_, QWidget* parent) - : QWidget(parent), ui{std::make_unique()}, system{system_} { + : QScrollArea(parent), ui{std::make_unique()}, system{system_} { ui->setupUi(this); SetConfiguration(); diff --git a/src/yuzu/configuration/configure_debug.h b/src/yuzu/configuration/configure_debug.h index 64d68ab8f..42d30f170 100644 --- a/src/yuzu/configuration/configure_debug.h +++ b/src/yuzu/configuration/configure_debug.h @@ -4,7 +4,7 @@ #pragma once #include -#include +#include namespace Core { class System; @@ -14,7 +14,7 @@ namespace Ui { class ConfigureDebug; } -class ConfigureDebug : public QWidget { +class ConfigureDebug : public QScrollArea { Q_OBJECT public: diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui index 4c16274fc..47b8b80f1 100644 --- a/src/yuzu/configuration/configure_debug.ui +++ b/src/yuzu/configuration/configure_debug.ui @@ -1,7 +1,11 @@ ConfigureDebug - + + + true + + @@ -322,6 +326,7 @@ + log_filter_edit toggle_console From c7a814f10fd927dfcc3591fada7d6732213892b2 Mon Sep 17 00:00:00 2001 From: Narr the Reg Date: Sat, 3 Sep 2022 13:30:29 -0500 Subject: [PATCH 29/78] core: ns: Implement pl:s service --- src/core/CMakeLists.txt | 4 +-- .../file_sys/system_archive/shared_font.cpp | 2 +- .../service/am/applets/applet_web_browser.cpp | 2 +- ...pl_u.cpp => iplatform_service_manager.cpp} | 34 +++++++++---------- .../{pl_u.h => iplatform_service_manager.h} | 6 ++-- src/core/hle/service/ns/ns.cpp | 5 +-- 6 files changed, 27 insertions(+), 26 deletions(-) rename src/core/hle/service/ns/{pl_u.cpp => iplatform_service_manager.cpp} (89%) rename src/core/hle/service/ns/{pl_u.h => iplatform_service_manager.h} (89%) 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/pl_u.cpp b/src/core/hle/service/ns/iplatform_service_manager.cpp similarity index 89% rename from src/core/hle/service/ns/pl_u.cpp rename to src/core/hle/service/ns/iplatform_service_manager.cpp index cc11f3e08..fd047ff26 100644 --- a/src/core/hle/service/ns/pl_u.cpp +++ b/src/core/hle/service/ns/iplatform_service_manager.cpp @@ -20,7 +20,7 @@ #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" +#include "core/hle/service/ns/iplatform_service_manager.h" namespace Service::NS { @@ -99,7 +99,7 @@ static u32 GetU32Swapped(const u8* data) { return Common::swap32(value); } -struct PL_U::Impl { +struct IPlatformServiceManager::Impl { const FontRegion& GetSharedFontRegion(std::size_t index) const { if (index >= shared_font_regions.size() || shared_font_regions.empty()) { // No font fallback @@ -134,16 +134,16 @@ struct PL_U::Impl { std::vector shared_font_regions; }; -PL_U::PL_U(Core::System& system_) - : ServiceFramework{system_, "pl:u"}, impl{std::make_unique()} { +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, &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"}, + {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"}, @@ -206,9 +206,9 @@ PL_U::PL_U(Core::System& system_) } } -PL_U::~PL_U() = default; +IPlatformServiceManager::~IPlatformServiceManager() = default; -void PL_U::RequestLoad(Kernel::HLERequestContext& ctx) { +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 @@ -218,7 +218,7 @@ void PL_U::RequestLoad(Kernel::HLERequestContext& ctx) { rb.Push(ResultSuccess); } -void PL_U::GetLoadState(Kernel::HLERequestContext& ctx) { +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); @@ -228,7 +228,7 @@ void PL_U::GetLoadState(Kernel::HLERequestContext& ctx) { rb.Push(static_cast(LoadState::Done)); } -void PL_U::GetSize(Kernel::HLERequestContext& ctx) { +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); @@ -238,7 +238,7 @@ void PL_U::GetSize(Kernel::HLERequestContext& ctx) { rb.Push(impl->GetSharedFontRegion(font_id).size); } -void PL_U::GetSharedMemoryAddressOffset(Kernel::HLERequestContext& ctx) { +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); @@ -248,7 +248,7 @@ void PL_U::GetSharedMemoryAddressOffset(Kernel::HLERequestContext& ctx) { rb.Push(impl->GetSharedFontRegion(font_id).offset); } -void PL_U::GetSharedMemoryNativeHandle(Kernel::HLERequestContext& ctx) { +void IPlatformServiceManager::GetSharedMemoryNativeHandle(Kernel::HLERequestContext& ctx) { // Map backing memory for the font data LOG_DEBUG(Service_NS, "called"); @@ -261,7 +261,7 @@ void PL_U::GetSharedMemoryNativeHandle(Kernel::HLERequestContext& ctx) { rb.PushCopyObjects(&kernel.GetFontSharedMem()); } -void PL_U::GetSharedFontInOrderOfPriority(Kernel::HLERequestContext& ctx) { +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); diff --git a/src/core/hle/service/ns/pl_u.h b/src/core/hle/service/ns/iplatform_service_manager.h similarity index 89% rename from src/core/hle/service/ns/pl_u.h rename to src/core/hle/service/ns/iplatform_service_manager.h index 07d0ac934..ed6eda89f 100644 --- a/src/core/hle/service/ns/pl_u.h +++ b/src/core/hle/service/ns/iplatform_service_manager.h @@ -36,10 +36,10 @@ constexpr std::array, 7> SHARED_FONTS{ 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 { +class IPlatformServiceManager final : public ServiceFramework { public: - explicit PL_U(Core::System& system_); - ~PL_U() override; + explicit IPlatformServiceManager(Core::System& system_, const char* service_name_); + ~IPlatformServiceManager() override; private: void RequestLoad(Kernel::HLERequestContext& ctx); 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 From c3b16cf8d32f1925b7a539e9da7e5d630a354be5 Mon Sep 17 00:00:00 2001 From: Narr the Reg Date: Fri, 2 Sep 2022 04:36:46 -0500 Subject: [PATCH 30/78] input_common: sdl: Always check for motion on reconnect --- src/input_common/drivers/sdl_driver.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/input_common/drivers/sdl_driver.cpp b/src/input_common/drivers/sdl_driver.cpp index de388ec4c..5cc1ccbd9 100644 --- a/src/input_common/drivers/sdl_driver.cpp +++ b/src/input_common/drivers/sdl_driver.cpp @@ -40,13 +40,13 @@ public: void EnableMotion() { if (sdl_controller) { SDL_GameController* controller = sdl_controller.get(); - if (SDL_GameControllerHasSensor(controller, SDL_SENSOR_ACCEL) && !has_accel) { + has_accel = SDL_GameControllerHasSensor(controller, SDL_SENSOR_ACCEL); + has_gyro = SDL_GameControllerHasSensor(controller, SDL_SENSOR_GYRO); + if (has_accel) { SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, SDL_TRUE); - has_accel = true; } - if (SDL_GameControllerHasSensor(controller, SDL_SENSOR_GYRO) && !has_gyro) { + if (has_gyro) { SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_GYRO, SDL_TRUE); - has_gyro = true; } } } @@ -305,6 +305,7 @@ void SDLDriver::InitJoystick(int joystick_index) { auto joystick = std::make_shared(guid, 0, sdl_joystick, sdl_gamecontroller); PreSetController(joystick->GetPadIdentifier()); SetBattery(joystick->GetPadIdentifier(), joystick->GetBatteryLevel()); + joystick->EnableMotion(); joystick_map[guid].emplace_back(std::move(joystick)); return; } @@ -316,6 +317,7 @@ void SDLDriver::InitJoystick(int joystick_index) { if (joystick_it != joystick_guid_list.end()) { (*joystick_it)->SetSDLJoystick(sdl_joystick, sdl_gamecontroller); + (*joystick_it)->EnableMotion(); return; } @@ -323,6 +325,7 @@ void SDLDriver::InitJoystick(int joystick_index) { auto joystick = std::make_shared(guid, port, sdl_joystick, sdl_gamecontroller); PreSetController(joystick->GetPadIdentifier()); SetBattery(joystick->GetPadIdentifier(), joystick->GetBatteryLevel()); + joystick->EnableMotion(); joystick_guid_list.emplace_back(std::move(joystick)); } From 2129d040a509754839b82b1ff6d387cb4f84f168 Mon Sep 17 00:00:00 2001 From: Kelebek1 Date: Sun, 4 Sep 2022 05:41:06 +0100 Subject: [PATCH 31/78] 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 +++++++- .../hle/service/nvdrv/devices/nvhost_nvdec.cpp | 7 ++++++- 4 files changed, 35 insertions(+), 2 deletions(-) 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 From 016fa3ffeea6ae01f906f830cca86ae364268327 Mon Sep 17 00:00:00 2001 From: Vamsi Krishna Date: Sun, 4 Sep 2022 13:45:33 +0530 Subject: [PATCH 32/78] Fix Cmake warning for CMP0077 --- externals/CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index eea70fc27..e80fd124e 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -16,7 +16,6 @@ endif() # Dynarmic if (ARCHITECTURE_x86_64) - set(DYNARMIC_TESTS OFF) set(DYNARMIC_NO_BUNDLED_FMT ON) set(DYNARMIC_IGNORE_ASSERTS ON CACHE BOOL "" FORCE) add_subdirectory(dynarmic) From f958cbc737542332ed4de9cf503fa4a8d1106564 Mon Sep 17 00:00:00 2001 From: lat9nq Date: Sun, 10 Jul 2022 11:29:10 -0400 Subject: [PATCH 33/78] yuzu: Use a debugger to generate minidumps yuzu: Move mini_dump out of core startup_checks: Better exception handling --- src/common/settings.h | 1 + src/yuzu/CMakeLists.txt | 10 + src/yuzu/applets/qt_controller.cpp | 2 +- src/yuzu/configuration/config.cpp | 6 +- src/yuzu/configuration/config.h | 4 +- src/yuzu/configuration/configure_debug.cpp | 21 +- src/yuzu/configuration/configure_debug.h | 2 + src/yuzu/configuration/configure_debug.ui | 122 ++++++------ src/yuzu/configuration/configure_input.cpp | 2 +- src/yuzu/configuration/configure_per_game.cpp | 3 +- src/yuzu/configuration/input_profiles.cpp | 9 +- src/yuzu/configuration/input_profiles.h | 4 +- src/yuzu/main.cpp | 32 ++- src/yuzu/main.h | 2 +- src/yuzu/mini_dump.cpp | 185 ++++++++++++++++++ src/yuzu/mini_dump.h | 12 ++ src/yuzu/startup_checks.cpp | 29 ++- src/yuzu/startup_checks.h | 5 +- 18 files changed, 360 insertions(+), 91 deletions(-) create mode 100644 src/yuzu/mini_dump.cpp create mode 100644 src/yuzu/mini_dump.h diff --git a/src/common/settings.h b/src/common/settings.h index 14ed9b237..8354fdba7 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -529,6 +529,7 @@ struct Values { Setting use_debug_asserts{false, "use_debug_asserts"}; Setting use_auto_stub{false, "use_auto_stub"}; Setting enable_all_controllers{false, "enable_all_controllers"}; + Setting create_crash_dumps{false, "create_crash_dumps"}; // Miscellaneous Setting log_filter{"*:Info", "log_filter"}; diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 50007338f..3d9906ade 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -208,6 +208,16 @@ add_executable(yuzu yuzu.rc ) +if (WIN32 AND NOT ("${DBGHELP_LIBRARY}" STREQUAL "DBGHELP_LIBRARY-NOTFOUND")) + target_sources(yuzu PRIVATE + mini_dump.cpp + mini_dump.h + ) + + target_link_libraries(yuzu PUBLIC ${DBGHELP_LIBRARY}) + target_compile_definitions(yuzu PRIVATE -DYUZU_DBGHELP) +endif() + file(GLOB COMPAT_LIST ${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.qrc ${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.json) diff --git a/src/yuzu/applets/qt_controller.cpp b/src/yuzu/applets/qt_controller.cpp index 8be311fcb..1d8072243 100644 --- a/src/yuzu/applets/qt_controller.cpp +++ b/src/yuzu/applets/qt_controller.cpp @@ -63,7 +63,7 @@ QtControllerSelectorDialog::QtControllerSelectorDialog( InputCommon::InputSubsystem* input_subsystem_, Core::System& system_) : QDialog(parent), ui(std::make_unique()), parameters(std::move(parameters_)), input_subsystem{input_subsystem_}, - input_profiles(std::make_unique(system_)), system{system_} { + input_profiles(std::make_unique()), system{system_} { ui->setupUi(this); player_widgets = { diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index da6e5aa88..e44759856 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -15,8 +15,7 @@ namespace FS = Common::FS; -Config::Config(Core::System& system_, const std::string& config_name, ConfigType config_type) - : type(config_type), system{system_} { +Config::Config(const std::string& config_name, ConfigType config_type) : type(config_type) { global = config_type == ConfigType::GlobalConfig; Initialize(config_name); @@ -546,6 +545,7 @@ void Config::ReadDebuggingValues() { ReadBasicSetting(Settings::values.use_debug_asserts); ReadBasicSetting(Settings::values.use_auto_stub); ReadBasicSetting(Settings::values.enable_all_controllers); + ReadBasicSetting(Settings::values.create_crash_dumps); qt_config->endGroup(); } @@ -1160,6 +1160,7 @@ void Config::SaveDebuggingValues() { WriteBasicSetting(Settings::values.use_debug_asserts); WriteBasicSetting(Settings::values.disable_macro_jit); WriteBasicSetting(Settings::values.enable_all_controllers); + WriteBasicSetting(Settings::values.create_crash_dumps); qt_config->endGroup(); } @@ -1545,7 +1546,6 @@ void Config::Reload() { ReadValues(); // To apply default value changes SaveValues(); - system.ApplySettings(); } void Config::Save() { diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h index 486ceea94..06fa7d2d0 100644 --- a/src/yuzu/configuration/config.h +++ b/src/yuzu/configuration/config.h @@ -25,7 +25,7 @@ public: InputProfile, }; - explicit Config(Core::System& system_, const std::string& config_name = "qt-config", + explicit Config(const std::string& config_name = "qt-config", ConfigType config_type = ConfigType::GlobalConfig); ~Config(); @@ -194,8 +194,6 @@ private: std::unique_ptr qt_config; std::string qt_config_loc; bool global; - - Core::System& system; }; // These metatype declarations cannot be in common/settings.h because core is devoid of QT diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp index 04d397750..622808e94 100644 --- a/src/yuzu/configuration/configure_debug.cpp +++ b/src/yuzu/configuration/configure_debug.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include #include #include "common/fs/path_util.h" #include "common/logging/backend.h" @@ -26,6 +27,16 @@ ConfigureDebug::ConfigureDebug(const Core::System& system_, QWidget* parent) connect(ui->toggle_gdbstub, &QCheckBox::toggled, [&]() { ui->gdbport_spinbox->setEnabled(ui->toggle_gdbstub->isChecked()); }); + + connect(ui->create_crash_dumps, &QCheckBox::stateChanged, [&](int) { + if (crash_dump_warning_shown) { + return; + } + QMessageBox::warning(this, tr("Restart Required"), + tr("yuzu is required to restart in order to apply this setting."), + QMessageBox::Ok, QMessageBox::Ok); + crash_dump_warning_shown = true; + }); } ConfigureDebug::~ConfigureDebug() = default; @@ -71,7 +82,14 @@ void ConfigureDebug::SetConfiguration() { ui->disable_web_applet->setChecked(UISettings::values.disable_web_applet.GetValue()); #else ui->disable_web_applet->setEnabled(false); - ui->disable_web_applet->setText(QString::fromUtf8("Web applet not compiled")); + ui->disable_web_applet->setText(tr("Web applet not compiled")); +#endif + +#ifdef YUZU_DBGHELP + ui->create_crash_dumps->setChecked(Settings::values.create_crash_dumps.GetValue()); +#else + ui->create_crash_dumps->setEnabled(false); + ui->create_crash_dumps->setText(tr("MiniDump creation not compiled")); #endif } @@ -84,6 +102,7 @@ void ConfigureDebug::ApplyConfiguration() { Settings::values.enable_fs_access_log = ui->fs_access_log->isChecked(); Settings::values.reporting_services = ui->reporting_services->isChecked(); Settings::values.dump_audio_commands = ui->dump_audio_commands->isChecked(); + Settings::values.create_crash_dumps = ui->create_crash_dumps->isChecked(); Settings::values.quest_flag = ui->quest_flag->isChecked(); Settings::values.use_debug_asserts = ui->use_debug_asserts->isChecked(); Settings::values.use_auto_stub = ui->use_auto_stub->isChecked(); diff --git a/src/yuzu/configuration/configure_debug.h b/src/yuzu/configuration/configure_debug.h index 42d30f170..030a0b7f7 100644 --- a/src/yuzu/configuration/configure_debug.h +++ b/src/yuzu/configuration/configure_debug.h @@ -32,4 +32,6 @@ private: std::unique_ptr ui; const Core::System& system; + + bool crash_dump_warning_shown{false}; }; diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui index 47b8b80f1..314d47af5 100644 --- a/src/yuzu/configuration/configure_debug.ui +++ b/src/yuzu/configuration/configure_debug.ui @@ -7,60 +7,60 @@ - - - - - - Debugger - - + + + + + + Debugger + + + + - - - - - Enable GDB Stub - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Port: - - - - - - - 1024 - - - 65535 - - - - + + + Enable GDB Stub + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Port: + + + + + + + 1024 + + + 65535 + + - - - + + + + + @@ -231,6 +231,13 @@ Debugging + + + + Enable Verbose Reporting Services** + + + @@ -238,20 +245,20 @@ - + - - Dump Audio Commands To Console** - Enable this to output the latest generated audio command list to the console. Only affects games using the audio renderer. + + Dump Audio Commands To Console** + - - + + - Enable Verbose Reporting Services** + Create Minidump After Crash @@ -340,7 +347,6 @@ disable_loop_safety_checks fs_access_log reporting_services - dump_audio_commands quest_flag enable_cpu_debugging use_debug_asserts diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp index 16fba3deb..cb55472c9 100644 --- a/src/yuzu/configuration/configure_input.cpp +++ b/src/yuzu/configuration/configure_input.cpp @@ -65,7 +65,7 @@ void OnDockedModeChanged(bool last_state, bool new_state, Core::System& system) ConfigureInput::ConfigureInput(Core::System& system_, QWidget* parent) : QWidget(parent), ui(std::make_unique()), - profiles(std::make_unique(system_)), system{system_} { + profiles(std::make_unique()), system{system_} { ui->setupUi(this); } diff --git a/src/yuzu/configuration/configure_per_game.cpp b/src/yuzu/configuration/configure_per_game.cpp index af8343b2e..c3cb8f61d 100644 --- a/src/yuzu/configuration/configure_per_game.cpp +++ b/src/yuzu/configuration/configure_per_game.cpp @@ -42,8 +42,7 @@ ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const std::st const auto file_path = std::filesystem::path(Common::FS::ToU8String(file_name)); const auto config_file_name = title_id == 0 ? Common::FS::PathToUTF8String(file_path.filename()) : fmt::format("{:016X}", title_id); - game_config = - std::make_unique(system, config_file_name, Config::ConfigType::PerGameConfig); + game_config = std::make_unique(config_file_name, Config::ConfigType::PerGameConfig); addons_tab = std::make_unique(system_, this); audio_tab = std::make_unique(system_, this); diff --git a/src/yuzu/configuration/input_profiles.cpp b/src/yuzu/configuration/input_profiles.cpp index 20b22e7de..807afbeb2 100644 --- a/src/yuzu/configuration/input_profiles.cpp +++ b/src/yuzu/configuration/input_profiles.cpp @@ -27,7 +27,7 @@ std::filesystem::path GetNameWithoutExtension(std::filesystem::path filename) { } // namespace -InputProfiles::InputProfiles(Core::System& system_) : system{system_} { +InputProfiles::InputProfiles() { const auto input_profile_loc = FS::GetYuzuPath(FS::YuzuPath::ConfigDir) / "input"; if (!FS::IsDir(input_profile_loc)) { @@ -43,8 +43,8 @@ InputProfiles::InputProfiles(Core::System& system_) : system{system_} { if (IsINI(filename) && IsProfileNameValid(name_without_ext)) { map_profiles.insert_or_assign( - name_without_ext, std::make_unique(system, name_without_ext, - Config::ConfigType::InputProfile)); + name_without_ext, + std::make_unique(name_without_ext, Config::ConfigType::InputProfile)); } return true; @@ -80,8 +80,7 @@ bool InputProfiles::CreateProfile(const std::string& profile_name, std::size_t p } map_profiles.insert_or_assign( - profile_name, - std::make_unique(system, profile_name, Config::ConfigType::InputProfile)); + profile_name, std::make_unique(profile_name, Config::ConfigType::InputProfile)); return SaveProfile(profile_name, player_index); } diff --git a/src/yuzu/configuration/input_profiles.h b/src/yuzu/configuration/input_profiles.h index 65fc9e62c..2bf3e4250 100644 --- a/src/yuzu/configuration/input_profiles.h +++ b/src/yuzu/configuration/input_profiles.h @@ -15,7 +15,7 @@ class Config; class InputProfiles { public: - explicit InputProfiles(Core::System& system_); + explicit InputProfiles(); virtual ~InputProfiles(); std::vector GetInputProfileNames(); @@ -31,6 +31,4 @@ private: bool ProfileExistsInMap(const std::string& profile_name) const; std::unordered_map> map_profiles; - - Core::System& system; }; diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index a85adc072..ca3f4da70 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -138,6 +138,10 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "yuzu/uisettings.h" #include "yuzu/util/clickable_label.h" +#ifdef YUZU_DBGHELP +#include "yuzu/mini_dump.h" +#endif + using namespace Common::Literals; #ifdef USE_DISCORD_PRESENCE @@ -269,10 +273,9 @@ bool GMainWindow::CheckDarkMode() { #endif // __linux__ } -GMainWindow::GMainWindow(bool has_broken_vulkan) +GMainWindow::GMainWindow(std::unique_ptr config_, bool has_broken_vulkan) : ui{std::make_unique()}, system{std::make_unique()}, - input_subsystem{std::make_shared()}, - config{std::make_unique(*system)}, + input_subsystem{std::make_shared()}, config{std::move(config_)}, vfs{std::make_shared()}, provider{std::make_unique()} { #ifdef __linux__ @@ -1637,7 +1640,8 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t const auto config_file_name = title_id == 0 ? Common::FS::PathToUTF8String(file_path.filename()) : fmt::format("{:016X}", title_id); - Config per_game_config(*system, config_file_name, Config::ConfigType::PerGameConfig); + Config per_game_config(config_file_name, Config::ConfigType::PerGameConfig); + system->ApplySettings(); } // Save configurations @@ -2981,7 +2985,7 @@ void GMainWindow::OnConfigure() { Settings::values.disabled_addons.clear(); - config = std::make_unique(*system); + config = std::make_unique(); UISettings::values.reset_to_defaults = false; UISettings::values.game_dirs = std::move(old_game_dirs); @@ -3042,6 +3046,7 @@ void GMainWindow::OnConfigure() { UpdateStatusButtons(); controller_dialog->refreshConfiguration(); + system->ApplySettings(); } void GMainWindow::OnConfigureTas() { @@ -4082,7 +4087,22 @@ void GMainWindow::changeEvent(QEvent* event) { #endif int main(int argc, char* argv[]) { + std::unique_ptr config = std::make_unique(); bool has_broken_vulkan = false; + bool is_child = false; + if (CheckEnvVars(&is_child)) { + return 0; + } + +#ifdef YUZU_DBGHELP + PROCESS_INFORMATION pi; + if (!is_child && Settings::values.create_crash_dumps.GetValue() && SpawnDebuggee(argv[0], pi)) { + config.reset(nullptr); + DebugDebuggee(pi); + return 0; + } +#endif + if (StartupChecks(argv[0], &has_broken_vulkan)) { return 0; } @@ -4135,7 +4155,7 @@ int main(int argc, char* argv[]) { // generating shaders setlocale(LC_ALL, "C"); - GMainWindow main_window{has_broken_vulkan}; + GMainWindow main_window{std::move(config), has_broken_vulkan}; // After settings have been loaded by GMainWindow, apply the filter main_window.show(); diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 1ae2b93d9..716aef063 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -120,7 +120,7 @@ class GMainWindow : public QMainWindow { public: void filterBarSetChecked(bool state); void UpdateUITheme(); - explicit GMainWindow(bool has_broken_vulkan); + explicit GMainWindow(std::unique_ptr config_, bool has_broken_vulkan); ~GMainWindow() override; bool DropAction(QDropEvent* event); diff --git a/src/yuzu/mini_dump.cpp b/src/yuzu/mini_dump.cpp new file mode 100644 index 000000000..ad8a4f607 --- /dev/null +++ b/src/yuzu/mini_dump.cpp @@ -0,0 +1,185 @@ +#include +#include +#include +#include +#include "common/logging/log.h" +#include "yuzu/mini_dump.h" +#include "yuzu/startup_checks.h" + +// dbghelp.h must be included after windows.h +#include + +void CreateMiniDump(HANDLE process_handle, DWORD process_id, MINIDUMP_EXCEPTION_INFORMATION* info, + EXCEPTION_POINTERS* pep) { + LOG_INFO(Core, "called"); + + char file_name[255]; + const std::time_t the_time = std::time(nullptr); + std::strftime(file_name, 255, "yuzu-crash-%Y%m%d%H%M%S.dmp", std::localtime(&the_time)); + + // Open the file + HANDLE file_handle = CreateFile(file_name, GENERIC_READ | GENERIC_WRITE, 0, nullptr, + CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); + + if ((file_handle != nullptr) && (file_handle != INVALID_HANDLE_VALUE)) { + // Create the minidump + const MINIDUMP_TYPE dump_type = MiniDumpNormal; + + const bool write_dump_status = MiniDumpWriteDump(process_handle, process_id, file_handle, + dump_type, (pep != 0) ? info : 0, 0, 0); + + if (!write_dump_status) { + LOG_ERROR(Core, "MiniDumpWriteDump failed. Error: {}", GetLastError()); + } else { + LOG_INFO(Core, "Minidump created."); + } + + // Close the file + CloseHandle(file_handle); + + } else { + LOG_ERROR(Core, "CreateFile failed. Error: {}", GetLastError()); + } +} + +bool SpawnDebuggee(const char* arg0, PROCESS_INFORMATION& pi) { + std::memset(&pi, 0, sizeof(pi)); + + if (!SpawnChild(arg0, &pi, 0)) { + std::fprintf(stderr, "warning: continuing without crash dumps\n"); + return false; + } + + // Don't debug if we are already being debugged + if (IsDebuggerPresent()) { + return false; + } + + const bool can_debug = DebugActiveProcess(pi.dwProcessId); + if (!can_debug) { + std::fprintf(stderr, + "warning: DebugActiveProcess failed (%d), continuing without crash dumps\n", + GetLastError()); + return false; + } + + return true; +} + +void DebugDebuggee(PROCESS_INFORMATION& pi) { + DEBUG_EVENT deb_ev; + + while (deb_ev.dwDebugEventCode != EXIT_PROCESS_DEBUG_EVENT) { + const bool wait_success = WaitForDebugEvent(&deb_ev, INFINITE); + if (!wait_success) { + std::fprintf(stderr, "error: WaitForDebugEvent failed (%d)\n", GetLastError()); + return; + } + + switch (deb_ev.dwDebugEventCode) { + case OUTPUT_DEBUG_STRING_EVENT: + case CREATE_PROCESS_DEBUG_EVENT: + case CREATE_THREAD_DEBUG_EVENT: + case EXIT_PROCESS_DEBUG_EVENT: + case EXIT_THREAD_DEBUG_EVENT: + case LOAD_DLL_DEBUG_EVENT: + case RIP_EVENT: + case UNLOAD_DLL_DEBUG_EVENT: + ContinueDebugEvent(deb_ev.dwProcessId, deb_ev.dwThreadId, DBG_CONTINUE); + break; + case EXCEPTION_DEBUG_EVENT: + EXCEPTION_RECORD& record = deb_ev.u.Exception.ExceptionRecord; + + std::fprintf(stderr, "ExceptionCode: 0x%08x %s\n", record.ExceptionCode, + ExceptionName(record.ExceptionCode)); + if (!deb_ev.u.Exception.dwFirstChance) { + HANDLE thread_handle = OpenThread(THREAD_ALL_ACCESS, false, deb_ev.dwThreadId); + if (thread_handle == nullptr) { + std::fprintf(stderr, "OpenThread failed (%d)\n", GetLastError()); + } + if (SuspendThread(thread_handle) == (DWORD)-1) { + std::fprintf(stderr, "SuspendThread failed (%d)\n", GetLastError()); + } + + CONTEXT context; + std::memset(&context, 0, sizeof(context)); + context.ContextFlags = CONTEXT_ALL; + if (!GetThreadContext(thread_handle, &context)) { + std::fprintf(stderr, "GetThreadContext failed (%d)\n", GetLastError()); + break; + } + + EXCEPTION_POINTERS ep; + ep.ExceptionRecord = &record; + ep.ContextRecord = &context; + + MINIDUMP_EXCEPTION_INFORMATION info; + info.ThreadId = deb_ev.dwThreadId; + info.ExceptionPointers = &ep; + info.ClientPointers = false; + + CreateMiniDump(pi.hProcess, pi.dwProcessId, &info, &ep); + + std::fprintf(stderr, "previous thread suspend count: %d\n", + ResumeThread(thread_handle)); + if (CloseHandle(thread_handle) == 0) { + std::fprintf(stderr, "error: CloseHandle(thread_handle) failed (%d)\n", + GetLastError()); + } + } + ContinueDebugEvent(deb_ev.dwProcessId, deb_ev.dwThreadId, DBG_EXCEPTION_NOT_HANDLED); + break; + } + } +} + +const char* ExceptionName(DWORD exception) { + switch (exception) { + case EXCEPTION_ACCESS_VIOLATION: + return "EXCEPTION_ACCESS_VIOLATION"; + case EXCEPTION_DATATYPE_MISALIGNMENT: + return "EXCEPTION_DATATYPE_MISALIGNMENT"; + case EXCEPTION_BREAKPOINT: + return "EXCEPTION_BREAKPOINT"; + case EXCEPTION_SINGLE_STEP: + return "EXCEPTION_SINGLE_STEP"; + case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: + return "EXCEPTION_ARRAY_BOUNDS_EXCEEDED"; + case EXCEPTION_FLT_DENORMAL_OPERAND: + return "EXCEPTION_FLT_DENORMAL_OPERAND"; + case EXCEPTION_FLT_DIVIDE_BY_ZERO: + return "EXCEPTION_FLT_DIVIDE_BY_ZERO"; + case EXCEPTION_FLT_INEXACT_RESULT: + return "EXCEPTION_FLT_INEXACT_RESULT"; + case EXCEPTION_FLT_INVALID_OPERATION: + return "EXCEPTION_FLT_INVALID_OPERATION"; + case EXCEPTION_FLT_OVERFLOW: + return "EXCEPTION_FLT_OVERFLOW"; + case EXCEPTION_FLT_STACK_CHECK: + return "EXCEPTION_FLT_STACK_CHECK"; + case EXCEPTION_FLT_UNDERFLOW: + return "EXCEPTION_FLT_UNDERFLOW"; + case EXCEPTION_INT_DIVIDE_BY_ZERO: + return "EXCEPTION_INT_DIVIDE_BY_ZERO"; + case EXCEPTION_INT_OVERFLOW: + return "EXCEPTION_INT_OVERFLOW"; + case EXCEPTION_PRIV_INSTRUCTION: + return "EXCEPTION_PRIV_INSTRUCTION"; + case EXCEPTION_IN_PAGE_ERROR: + return "EXCEPTION_IN_PAGE_ERROR"; + case EXCEPTION_ILLEGAL_INSTRUCTION: + return "EXCEPTION_ILLEGAL_INSTRUCTION"; + case EXCEPTION_NONCONTINUABLE_EXCEPTION: + return "EXCEPTION_NONCONTINUABLE_EXCEPTION"; + case EXCEPTION_STACK_OVERFLOW: + return "EXCEPTION_STACK_OVERFLOW"; + case EXCEPTION_INVALID_DISPOSITION: + return "EXCEPTION_INVALID_DISPOSITION"; + case EXCEPTION_GUARD_PAGE: + return "EXCEPTION_GUARD_PAGE"; + case EXCEPTION_INVALID_HANDLE: + return "EXCEPTION_INVALID_HANDLE"; + default: + return nullptr; + } +} diff --git a/src/yuzu/mini_dump.h b/src/yuzu/mini_dump.h new file mode 100644 index 000000000..59ba615e4 --- /dev/null +++ b/src/yuzu/mini_dump.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +#include + +void CreateMiniDump(HANDLE process_handle, DWORD process_id, MINIDUMP_EXCEPTION_INFORMATION* info, + EXCEPTION_POINTERS* pep); + +bool SpawnDebuggee(const char* arg0, PROCESS_INFORMATION& pi); +void DebugDebuggee(PROCESS_INFORMATION& pi); +const char* ExceptionName(DWORD exception); diff --git a/src/yuzu/startup_checks.cpp b/src/yuzu/startup_checks.cpp index 8421280bf..29b87da05 100644 --- a/src/yuzu/startup_checks.cpp +++ b/src/yuzu/startup_checks.cpp @@ -31,19 +31,36 @@ void CheckVulkan() { } } -bool StartupChecks(const char* arg0, bool* has_broken_vulkan) { +bool CheckEnvVars(bool* is_child) { #ifdef _WIN32 // Check environment variable to see if we are the child char variable_contents[8]; const DWORD startup_check_var = GetEnvironmentVariableA(STARTUP_CHECK_ENV_VAR, variable_contents, 8); - if (startup_check_var > 0 && std::strncmp(variable_contents, "ON", 8) == 0) { + if (startup_check_var > 0 && std::strncmp(variable_contents, ENV_VAR_ENABLED_TEXT, 8) == 0) { CheckVulkan(); return true; } + // Don't perform startup checks if we are a child process + char is_child_s[8]; + const DWORD is_child_len = GetEnvironmentVariableA(IS_CHILD_ENV_VAR, is_child_s, 8); + if (is_child_len > 0 && std::strncmp(is_child_s, ENV_VAR_ENABLED_TEXT, 8) == 0) { + *is_child = true; + return false; + } else if (!SetEnvironmentVariableA(IS_CHILD_ENV_VAR, ENV_VAR_ENABLED_TEXT)) { + std::fprintf(stderr, "SetEnvironmentVariableA failed to set %s with error %d\n", + IS_CHILD_ENV_VAR, GetLastError()); + return true; + } +#endif + return false; +} + +bool StartupChecks(const char* arg0, bool* has_broken_vulkan) { +#ifdef _WIN32 // Set the startup variable for child processes - const bool env_var_set = SetEnvironmentVariableA(STARTUP_CHECK_ENV_VAR, "ON"); + const bool env_var_set = SetEnvironmentVariableA(STARTUP_CHECK_ENV_VAR, ENV_VAR_ENABLED_TEXT); if (!env_var_set) { std::fprintf(stderr, "SetEnvironmentVariableA failed to set %s with error %d\n", STARTUP_CHECK_ENV_VAR, GetLastError()); @@ -53,7 +70,7 @@ bool StartupChecks(const char* arg0, bool* has_broken_vulkan) { PROCESS_INFORMATION process_info; std::memset(&process_info, '\0', sizeof(process_info)); - if (!SpawnChild(arg0, &process_info)) { + if (!SpawnChild(arg0, &process_info, 0)) { return false; } @@ -106,7 +123,7 @@ bool StartupChecks(const char* arg0, bool* has_broken_vulkan) { } #ifdef _WIN32 -bool SpawnChild(const char* arg0, PROCESS_INFORMATION* pi) { +bool SpawnChild(const char* arg0, PROCESS_INFORMATION* pi, int flags) { STARTUPINFOA startup_info; std::memset(&startup_info, '\0', sizeof(startup_info)); @@ -120,7 +137,7 @@ bool SpawnChild(const char* arg0, PROCESS_INFORMATION* pi) { nullptr, // lpProcessAttributes nullptr, // lpThreadAttributes false, // bInheritHandles - 0, // dwCreationFlags + flags, // dwCreationFlags nullptr, // lpEnvironment nullptr, // lpCurrentDirectory &startup_info, // lpStartupInfo diff --git a/src/yuzu/startup_checks.h b/src/yuzu/startup_checks.h index 096dd54a8..f2fc2d9d4 100644 --- a/src/yuzu/startup_checks.h +++ b/src/yuzu/startup_checks.h @@ -7,11 +7,14 @@ #include #endif +constexpr char IS_CHILD_ENV_VAR[] = "YUZU_IS_CHILD"; constexpr char STARTUP_CHECK_ENV_VAR[] = "YUZU_DO_STARTUP_CHECKS"; +constexpr char ENV_VAR_ENABLED_TEXT[] = "ON"; void CheckVulkan(); +bool CheckEnvVars(bool* is_child); bool StartupChecks(const char* arg0, bool* has_broken_vulkan); #ifdef _WIN32 -bool SpawnChild(const char* arg0, PROCESS_INFORMATION* pi); +bool SpawnChild(const char* arg0, PROCESS_INFORMATION* pi, int flags); #endif From 3dbaafe1f3364db2721a3318e0cde66fa0c81a5e Mon Sep 17 00:00:00 2001 From: lat9nq Date: Wed, 13 Jul 2022 12:14:48 -0400 Subject: [PATCH 34/78] mini_dump: Cleanup and add comments Removes some unnecessary code. wip --- src/yuzu/main.cpp | 1 + src/yuzu/mini_dump.cpp | 126 +++++++++++++++++++++++++++-------------- src/yuzu/mini_dump.h | 1 + 3 files changed, 86 insertions(+), 42 deletions(-) diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index ca3f4da70..ff59c64c3 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -4097,6 +4097,7 @@ int main(int argc, char* argv[]) { #ifdef YUZU_DBGHELP PROCESS_INFORMATION pi; if (!is_child && Settings::values.create_crash_dumps.GetValue() && SpawnDebuggee(argv[0], pi)) { + // Delete the config object so that it doesn't save when the program exits config.reset(nullptr); DebugDebuggee(pi); return 0; diff --git a/src/yuzu/mini_dump.cpp b/src/yuzu/mini_dump.cpp index ad8a4f607..e60456d9b 100644 --- a/src/yuzu/mini_dump.cpp +++ b/src/yuzu/mini_dump.cpp @@ -1,8 +1,8 @@ #include +#include #include #include #include -#include "common/logging/log.h" #include "yuzu/mini_dump.h" #include "yuzu/startup_checks.h" @@ -11,8 +11,6 @@ void CreateMiniDump(HANDLE process_handle, DWORD process_id, MINIDUMP_EXCEPTION_INFORMATION* info, EXCEPTION_POINTERS* pep) { - LOG_INFO(Core, "called"); - char file_name[255]; const std::time_t the_time = std::time(nullptr); std::strftime(file_name, 255, "yuzu-crash-%Y%m%d%H%M%S.dmp", std::localtime(&the_time)); @@ -29,16 +27,50 @@ void CreateMiniDump(HANDLE process_handle, DWORD process_id, MINIDUMP_EXCEPTION_ dump_type, (pep != 0) ? info : 0, 0, 0); if (!write_dump_status) { - LOG_ERROR(Core, "MiniDumpWriteDump failed. Error: {}", GetLastError()); + std::fprintf(stderr, "MiniDumpWriteDump failed. Error: %d\n", GetLastError()); } else { - LOG_INFO(Core, "Minidump created."); + std::fprintf(stderr, "MiniDump created: %s\n", file_name); } // Close the file CloseHandle(file_handle); } else { - LOG_ERROR(Core, "CreateFile failed. Error: {}", GetLastError()); + std::fprintf(stderr, "CreateFile failed. Error: %d\n", GetLastError()); + } +} + +void DumpFromDebugEvent(DEBUG_EVENT& deb_ev, PROCESS_INFORMATION& pi) { + EXCEPTION_RECORD& record = deb_ev.u.Exception.ExceptionRecord; + + HANDLE thread_handle = OpenThread(THREAD_GET_CONTEXT, false, deb_ev.dwThreadId); + if (thread_handle == nullptr) { + std::fprintf(stderr, "OpenThread failed (%d)\n", GetLastError()); + } + + // Get child process context + CONTEXT context; + std::memset(&context, 0, sizeof(context)); + context.ContextFlags = CONTEXT_ALL; + if (!GetThreadContext(thread_handle, &context)) { + std::fprintf(stderr, "GetThreadContext failed (%d)\n", GetLastError()); + return; + } + + // Create exception pointers for minidump + EXCEPTION_POINTERS ep; + ep.ExceptionRecord = &record; + ep.ContextRecord = &context; + + MINIDUMP_EXCEPTION_INFORMATION info; + info.ThreadId = deb_ev.dwThreadId; + info.ExceptionPointers = &ep; + info.ClientPointers = false; + + CreateMiniDump(pi.hProcess, pi.dwProcessId, &info, &ep); + + if (CloseHandle(thread_handle) == 0) { + std::fprintf(stderr, "error: CloseHandle(thread_handle) failed (%d)\n", GetLastError()); } } @@ -68,6 +100,8 @@ bool SpawnDebuggee(const char* arg0, PROCESS_INFORMATION& pi) { void DebugDebuggee(PROCESS_INFORMATION& pi) { DEBUG_EVENT deb_ev; + const std::time_t start_time = std::time(nullptr); + //~ bool seen_nonzero_thread_exit = false; while (deb_ev.dwDebugEventCode != EXIT_PROCESS_DEBUG_EVENT) { const bool wait_success = WaitForDebugEvent(&deb_ev, INFINITE); @@ -87,49 +121,57 @@ void DebugDebuggee(PROCESS_INFORMATION& pi) { case UNLOAD_DLL_DEBUG_EVENT: ContinueDebugEvent(deb_ev.dwProcessId, deb_ev.dwThreadId, DBG_CONTINUE); break; - case EXCEPTION_DEBUG_EVENT: + //~ case EXIT_THREAD_DEBUG_EVENT: { + //~ const DWORD& exit_code = deb_ev.u.ExitThread.dwExitCode; + + //~ // Generate a crash dump on the first abnormal thread exit. + //~ // We don't want to generate on every abnormal thread exit since ALL the other + // threads ~ // in the application will follow by exiting with the same code. ~ if + //(!seen_nonzero_thread_exit && exit_code != 0) { ~ seen_nonzero_thread_exit = true; ~ + // std::fprintf(stderr, ~ "Creating MiniDump on first non-zero thread exit: code + // 0x%08x\n", ~ exit_code); + //~ DumpFromDebugEvent(deb_ev, pi); + //~ } + //~ ContinueDebugEvent(deb_ev.dwProcessId, deb_ev.dwThreadId, DBG_CONTINUE); + //~ break; + //~ } + case EXCEPTION_DEBUG_EVENT: { EXCEPTION_RECORD& record = deb_ev.u.Exception.ExceptionRecord; + const std::time_t now = std::time(nullptr); + const std::time_t delta = now - start_time; - std::fprintf(stderr, "ExceptionCode: 0x%08x %s\n", record.ExceptionCode, - ExceptionName(record.ExceptionCode)); + if (ExceptionName(record.ExceptionCode) == nullptr) { + int record_count = 0; + EXCEPTION_RECORD* next_record = &deb_ev.u.Exception.ExceptionRecord; + while (next_record != nullptr) { + std::fprintf(stderr, + "[%d] code(%d): 0x%08x\n\tflags: %08x %s\n\taddress: " + "0x%08x\n\tparameters: %d\n", + delta, record_count, next_record->ExceptionCode, + next_record->ExceptionFlags, + next_record->ExceptionFlags == EXCEPTION_NONCONTINUABLE + ? "noncontinuable" + : "", + next_record->ExceptionAddress, next_record->NumberParameters); + for (int i = 0; i < static_cast(next_record->NumberParameters); i++) { + std::fprintf(stderr, "\t\t%0d: 0x%08x\n", i, + next_record->ExceptionInformation[i]); + } + + record_count++; + next_record = next_record->ExceptionRecord; + } + } + // We want to generate a crash dump if we are seeing the same exception again. if (!deb_ev.u.Exception.dwFirstChance) { - HANDLE thread_handle = OpenThread(THREAD_ALL_ACCESS, false, deb_ev.dwThreadId); - if (thread_handle == nullptr) { - std::fprintf(stderr, "OpenThread failed (%d)\n", GetLastError()); - } - if (SuspendThread(thread_handle) == (DWORD)-1) { - std::fprintf(stderr, "SuspendThread failed (%d)\n", GetLastError()); - } - - CONTEXT context; - std::memset(&context, 0, sizeof(context)); - context.ContextFlags = CONTEXT_ALL; - if (!GetThreadContext(thread_handle, &context)) { - std::fprintf(stderr, "GetThreadContext failed (%d)\n", GetLastError()); - break; - } - - EXCEPTION_POINTERS ep; - ep.ExceptionRecord = &record; - ep.ContextRecord = &context; - - MINIDUMP_EXCEPTION_INFORMATION info; - info.ThreadId = deb_ev.dwThreadId; - info.ExceptionPointers = &ep; - info.ClientPointers = false; - - CreateMiniDump(pi.hProcess, pi.dwProcessId, &info, &ep); - - std::fprintf(stderr, "previous thread suspend count: %d\n", - ResumeThread(thread_handle)); - if (CloseHandle(thread_handle) == 0) { - std::fprintf(stderr, "error: CloseHandle(thread_handle) failed (%d)\n", - GetLastError()); - } + std::fprintf(stderr, "Creating MiniDump on ExceptionCode: 0x%08x %s\n", + record.ExceptionCode, ExceptionName(record.ExceptionCode)); + DumpFromDebugEvent(deb_ev, pi); } ContinueDebugEvent(deb_ev.dwProcessId, deb_ev.dwThreadId, DBG_EXCEPTION_NOT_HANDLED); break; } + } } } diff --git a/src/yuzu/mini_dump.h b/src/yuzu/mini_dump.h index 59ba615e4..f6f5dc2c7 100644 --- a/src/yuzu/mini_dump.h +++ b/src/yuzu/mini_dump.h @@ -7,6 +7,7 @@ void CreateMiniDump(HANDLE process_handle, DWORD process_id, MINIDUMP_EXCEPTION_INFORMATION* info, EXCEPTION_POINTERS* pep); +void DumpFromDebugEvent(DEBUG_EVENT& deb_ev, PROCESS_INFORMATION& pi); bool SpawnDebuggee(const char* arg0, PROCESS_INFORMATION& pi); void DebugDebuggee(PROCESS_INFORMATION& pi); const char* ExceptionName(DWORD exception); From e339ec0e00905821dbb4fee8e45a2514555f5b0e Mon Sep 17 00:00:00 2001 From: lat9nq Date: Mon, 18 Jul 2022 21:36:26 -0400 Subject: [PATCH 35/78] mini_dump: Check for debugger before spawning a child mini_dump: Clean up mini_dump: Fix MSVC error mini_dump: Silence MSVC warning C4700 Zero initialize deb_ev. mini_dump: Add license info --- src/yuzu/mini_dump.cpp | 103 +++++++++++++++-------------------------- src/yuzu/mini_dump.h | 3 ++ 2 files changed, 40 insertions(+), 66 deletions(-) diff --git a/src/yuzu/mini_dump.cpp b/src/yuzu/mini_dump.cpp index e60456d9b..b25067c10 100644 --- a/src/yuzu/mini_dump.cpp +++ b/src/yuzu/mini_dump.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + #include #include #include @@ -16,28 +19,28 @@ void CreateMiniDump(HANDLE process_handle, DWORD process_id, MINIDUMP_EXCEPTION_ std::strftime(file_name, 255, "yuzu-crash-%Y%m%d%H%M%S.dmp", std::localtime(&the_time)); // Open the file - HANDLE file_handle = CreateFile(file_name, GENERIC_READ | GENERIC_WRITE, 0, nullptr, - CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); + HANDLE file_handle = CreateFileA(file_name, GENERIC_READ | GENERIC_WRITE, 0, nullptr, + CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); - if ((file_handle != nullptr) && (file_handle != INVALID_HANDLE_VALUE)) { - // Create the minidump - const MINIDUMP_TYPE dump_type = MiniDumpNormal; - - const bool write_dump_status = MiniDumpWriteDump(process_handle, process_id, file_handle, - dump_type, (pep != 0) ? info : 0, 0, 0); - - if (!write_dump_status) { - std::fprintf(stderr, "MiniDumpWriteDump failed. Error: %d\n", GetLastError()); - } else { - std::fprintf(stderr, "MiniDump created: %s\n", file_name); - } - - // Close the file - CloseHandle(file_handle); - - } else { - std::fprintf(stderr, "CreateFile failed. Error: %d\n", GetLastError()); + if (file_handle == nullptr || file_handle == INVALID_HANDLE_VALUE) { + std::fprintf(stderr, "CreateFileA failed. Error: %d\n", GetLastError()); + return; } + + // Create the minidump + const MINIDUMP_TYPE dump_type = MiniDumpNormal; + + const bool write_dump_status = MiniDumpWriteDump(process_handle, process_id, file_handle, + dump_type, (pep != 0) ? info : 0, 0, 0); + + if (write_dump_status) { + std::fprintf(stderr, "MiniDump created: %s\n", file_name); + } else { + std::fprintf(stderr, "MiniDumpWriteDump failed. Error: %d\n", GetLastError()); + } + + // Close the file + CloseHandle(file_handle); } void DumpFromDebugEvent(DEBUG_EVENT& deb_ev, PROCESS_INFORMATION& pi) { @@ -77,13 +80,13 @@ void DumpFromDebugEvent(DEBUG_EVENT& deb_ev, PROCESS_INFORMATION& pi) { bool SpawnDebuggee(const char* arg0, PROCESS_INFORMATION& pi) { std::memset(&pi, 0, sizeof(pi)); - if (!SpawnChild(arg0, &pi, 0)) { - std::fprintf(stderr, "warning: continuing without crash dumps\n"); + // Don't debug if we are already being debugged + if (IsDebuggerPresent()) { return false; } - // Don't debug if we are already being debugged - if (IsDebuggerPresent()) { + if (!SpawnChild(arg0, &pi, 0)) { + std::fprintf(stderr, "warning: continuing without crash dumps\n"); return false; } @@ -100,8 +103,7 @@ bool SpawnDebuggee(const char* arg0, PROCESS_INFORMATION& pi) { void DebugDebuggee(PROCESS_INFORMATION& pi) { DEBUG_EVENT deb_ev; - const std::time_t start_time = std::time(nullptr); - //~ bool seen_nonzero_thread_exit = false; + std::memset(&deb_ev, 0, sizeof(deb_ev)); while (deb_ev.dwDebugEventCode != EXIT_PROCESS_DEBUG_EVENT) { const bool wait_success = WaitForDebugEvent(&deb_ev, INFINITE); @@ -119,59 +121,28 @@ void DebugDebuggee(PROCESS_INFORMATION& pi) { case LOAD_DLL_DEBUG_EVENT: case RIP_EVENT: case UNLOAD_DLL_DEBUG_EVENT: + // Continue on all other debug events ContinueDebugEvent(deb_ev.dwProcessId, deb_ev.dwThreadId, DBG_CONTINUE); break; - //~ case EXIT_THREAD_DEBUG_EVENT: { - //~ const DWORD& exit_code = deb_ev.u.ExitThread.dwExitCode; - - //~ // Generate a crash dump on the first abnormal thread exit. - //~ // We don't want to generate on every abnormal thread exit since ALL the other - // threads ~ // in the application will follow by exiting with the same code. ~ if - //(!seen_nonzero_thread_exit && exit_code != 0) { ~ seen_nonzero_thread_exit = true; ~ - // std::fprintf(stderr, ~ "Creating MiniDump on first non-zero thread exit: code - // 0x%08x\n", ~ exit_code); - //~ DumpFromDebugEvent(deb_ev, pi); - //~ } - //~ ContinueDebugEvent(deb_ev.dwProcessId, deb_ev.dwThreadId, DBG_CONTINUE); - //~ break; - //~ } - case EXCEPTION_DEBUG_EVENT: { + case EXCEPTION_DEBUG_EVENT: EXCEPTION_RECORD& record = deb_ev.u.Exception.ExceptionRecord; - const std::time_t now = std::time(nullptr); - const std::time_t delta = now - start_time; - if (ExceptionName(record.ExceptionCode) == nullptr) { - int record_count = 0; - EXCEPTION_RECORD* next_record = &deb_ev.u.Exception.ExceptionRecord; - while (next_record != nullptr) { - std::fprintf(stderr, - "[%d] code(%d): 0x%08x\n\tflags: %08x %s\n\taddress: " - "0x%08x\n\tparameters: %d\n", - delta, record_count, next_record->ExceptionCode, - next_record->ExceptionFlags, - next_record->ExceptionFlags == EXCEPTION_NONCONTINUABLE - ? "noncontinuable" - : "", - next_record->ExceptionAddress, next_record->NumberParameters); - for (int i = 0; i < static_cast(next_record->NumberParameters); i++) { - std::fprintf(stderr, "\t\t%0d: 0x%08x\n", i, - next_record->ExceptionInformation[i]); - } - - record_count++; - next_record = next_record->ExceptionRecord; - } - } // We want to generate a crash dump if we are seeing the same exception again. if (!deb_ev.u.Exception.dwFirstChance) { std::fprintf(stderr, "Creating MiniDump on ExceptionCode: 0x%08x %s\n", record.ExceptionCode, ExceptionName(record.ExceptionCode)); DumpFromDebugEvent(deb_ev, pi); } + + // Continue without handling the exception. + // Lets the debuggee use its own exception handler. + // - If one does not exist, we will see the exception once more where we make a minidump + // for. Then when it reaches here again, yuzu will probably crash. + // - DBG_CONTINUE on an exception that the debuggee does not handle can set us up for an + // infinite loop of exceptions. ContinueDebugEvent(deb_ev.dwProcessId, deb_ev.dwThreadId, DBG_EXCEPTION_NOT_HANDLED); break; } - } } } diff --git a/src/yuzu/mini_dump.h b/src/yuzu/mini_dump.h index f6f5dc2c7..2052e5248 100644 --- a/src/yuzu/mini_dump.h +++ b/src/yuzu/mini_dump.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + #pragma once #include From c976613ab25130e8fc8b6c0de03cf380eb46c64e Mon Sep 17 00:00:00 2001 From: lat9nq Date: Mon, 25 Jul 2022 16:45:38 -0400 Subject: [PATCH 36/78] vcpkg,cmake: Use vcpkg for dbghelp --- CMakeLists.txt | 12 ++++++++++++ src/yuzu/CMakeLists.txt | 2 +- vcpkg.json | 4 ++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2ab0ea589..20dd1383f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,6 +38,8 @@ option(YUZU_USE_BUNDLED_OPUS "Compile bundled opus" ON) option(YUZU_TESTS "Compile tests" ON) +CMAKE_DEPENDENT_OPTION(YUZU_CRASH_DUMPS "Compile Windows crash dump (Minidump) support" OFF "WIN32" OFF) + option(YUZU_USE_BUNDLED_VCPKG "Use vcpkg for yuzu dependencies" "${MSVC}") option(YUZU_CHECK_SUBMODULES "Check if submodules are present" ON) @@ -46,6 +48,9 @@ if (YUZU_USE_BUNDLED_VCPKG) if (YUZU_TESTS) list(APPEND VCPKG_MANIFEST_FEATURES "yuzu-tests") endif() + if (YUZU_CRASH_DUMPS) + list(APPEND VCPKG_MANIFEST_FEATURES "dbghelp") + endif() include(${CMAKE_SOURCE_DIR}/externals/vcpkg/scripts/buildsystems/vcpkg.cmake) elseif(NOT "$ENV{VCPKG_TOOLCHAIN_FILE}" STREQUAL "") @@ -447,6 +452,13 @@ elseif (WIN32) # PSAPI is the Process Status API set(PLATFORM_LIBRARIES ${PLATFORM_LIBRARIES} psapi imm32 version) endif() + + if (YUZU_CRASH_DUMPS) + find_library(DBGHELP_LIBRARY dbghelp) + if ("${DBGHELP_LIBRARY}" STREQUAL "DBGHELP_LIBRARY-NOTFOUND") + message(FATAL_ERROR "YUZU_CRASH_DUMPS enabled but dbghelp library not found") + endif() + endif() elseif (CMAKE_SYSTEM_NAME MATCHES "^(Linux|kFreeBSD|GNU|SunOS)$") set(PLATFORM_LIBRARIES rt) endif() diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 3d9906ade..df0f64b83 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -208,7 +208,7 @@ add_executable(yuzu yuzu.rc ) -if (WIN32 AND NOT ("${DBGHELP_LIBRARY}" STREQUAL "DBGHELP_LIBRARY-NOTFOUND")) +if (WIN32 AND YUZU_CRASH_DUMPS) target_sources(yuzu PRIVATE mini_dump.cpp mini_dump.h diff --git a/vcpkg.json b/vcpkg.json index c4413e22a..3c92510d6 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -31,6 +31,10 @@ "yuzu-tests": { "description": "Compile tests", "dependencies": [ "catch2" ] + }, + "dbghelp": { + "description": "Compile Windows crash dump (Minidump) support", + "dependencies": [ "dbghelp" ] } }, "overrides": [ From 45b343d1d09f30193a1da52924ee83a834356fae Mon Sep 17 00:00:00 2001 From: lat9nq Date: Sat, 30 Jul 2022 09:18:33 -0400 Subject: [PATCH 37/78] ci,workflows: Enable crash dumps on MSVC builds ci/windows: Enable crash dumps on MinGW builds --- .ci/scripts/windows/docker.sh | 1 + .ci/templates/build-msvc.yml | 2 +- .github/workflows/verify.yml | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.ci/scripts/windows/docker.sh b/.ci/scripts/windows/docker.sh index 790ba8218..6f522feed 100755 --- a/.ci/scripts/windows/docker.sh +++ b/.ci/scripts/windows/docker.sh @@ -21,6 +21,7 @@ cmake .. \ -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON \ -DENABLE_QT_TRANSLATION=ON \ -DUSE_CCACHE=ON \ + -DYUZU_CRASH_DUMPS=ON \ -DYUZU_USE_BUNDLED_SDL2=OFF \ -DYUZU_USE_EXTERNAL_SDL2=OFF \ -DYUZU_TESTS=OFF \ diff --git a/.ci/templates/build-msvc.yml b/.ci/templates/build-msvc.yml index a2ee71bd8..ea405e5dc 100644 --- a/.ci/templates/build-msvc.yml +++ b/.ci/templates/build-msvc.yml @@ -9,7 +9,7 @@ parameters: steps: - script: choco install vulkan-sdk displayName: 'Install vulkan-sdk' -- script: refreshenv && mkdir build && cd build && cmake -G "Visual Studio 17 2022" -A x64 -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_QT_WEB_ENGINE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DYUZU_TESTS=OFF -DUSE_DISCORD_PRESENCE=ON -DENABLE_QT_TRANSLATION=ON -DDISPLAY_VERSION=${{ parameters['version'] }} -DCMAKE_BUILD_TYPE=Release .. && cd .. +- script: refreshenv && mkdir build && cd build && cmake -G "Visual Studio 17 2022" -A x64 -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_QT_WEB_ENGINE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DYUZU_TESTS=OFF -DUSE_DISCORD_PRESENCE=ON -DENABLE_QT_TRANSLATION=ON -DDISPLAY_VERSION=${{ parameters['version'] }} -DCMAKE_BUILD_TYPE=Release -DYUZU_CRASH_DUMPS=ON .. && cd .. displayName: 'Configure CMake' - task: MSBuild@1 displayName: 'Build' diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index 733a52764..7cde8380b 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -104,7 +104,7 @@ jobs: run: | glslangValidator --version mkdir build - cmake . -B build -GNinja -DCMAKE_TOOLCHAIN_FILE="CMakeModules/MSVCCache.cmake" -DUSE_CCACHE=ON -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_QT_WEB_ENGINE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=ON -DUSE_DISCORD_PRESENCE=ON -DENABLE_QT_TRANSLATION=ON -DCMAKE_BUILD_TYPE=Release -DGIT_BRANCH=pr-verify + cmake . -B build -GNinja -DCMAKE_TOOLCHAIN_FILE="CMakeModules/MSVCCache.cmake" -DUSE_CCACHE=ON -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_QT_WEB_ENGINE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=ON -DUSE_DISCORD_PRESENCE=ON -DENABLE_QT_TRANSLATION=ON -DCMAKE_BUILD_TYPE=Release -DGIT_BRANCH=pr-verify -DYUZU_CRASH_DUMPS=ON - name: Build run: cmake --build build - name: Cache Summary From 12f7d42d32511955ee27875d42b6e8e3cda9e523 Mon Sep 17 00:00:00 2001 From: lat9nq Date: Sat, 30 Jul 2022 10:23:14 -0400 Subject: [PATCH 38/78] mini_dump: Address review feedback Uses fmt::print as opposed to std::fprintf. Adds a missing return. static's a single-use function. Initializes structs as opposed to std::memset where possible. Fixes CMake linkage. Co-authored-by: Lioncash mini_dump: Use a namespace Co-authored-by: Lioncash --- src/yuzu/CMakeLists.txt | 2 +- src/yuzu/main.cpp | 5 +- src/yuzu/mini_dump.cpp | 122 +++++++++++++++++++++------------------- src/yuzu/mini_dump.h | 5 +- 4 files changed, 71 insertions(+), 63 deletions(-) diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index df0f64b83..29d506c47 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -214,7 +214,7 @@ if (WIN32 AND YUZU_CRASH_DUMPS) mini_dump.h ) - target_link_libraries(yuzu PUBLIC ${DBGHELP_LIBRARY}) + target_link_libraries(yuzu PRIVATE ${DBGHELP_LIBRARY}) target_compile_definitions(yuzu PRIVATE -DYUZU_DBGHELP) endif() diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index ff59c64c3..bda9986e1 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -4096,10 +4096,11 @@ int main(int argc, char* argv[]) { #ifdef YUZU_DBGHELP PROCESS_INFORMATION pi; - if (!is_child && Settings::values.create_crash_dumps.GetValue() && SpawnDebuggee(argv[0], pi)) { + if (!is_child && Settings::values.create_crash_dumps.GetValue() && + MiniDump::SpawnDebuggee(argv[0], pi)) { // Delete the config object so that it doesn't save when the program exits config.reset(nullptr); - DebugDebuggee(pi); + MiniDump::DebugDebuggee(pi); return 0; } #endif diff --git a/src/yuzu/mini_dump.cpp b/src/yuzu/mini_dump.cpp index b25067c10..a34dc6a9c 100644 --- a/src/yuzu/mini_dump.cpp +++ b/src/yuzu/mini_dump.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include "yuzu/mini_dump.h" #include "yuzu/startup_checks.h" @@ -12,6 +13,8 @@ // dbghelp.h must be included after windows.h #include +namespace MiniDump { + void CreateMiniDump(HANDLE process_handle, DWORD process_id, MINIDUMP_EXCEPTION_INFORMATION* info, EXCEPTION_POINTERS* pep) { char file_name[255]; @@ -23,7 +26,7 @@ void CreateMiniDump(HANDLE process_handle, DWORD process_id, MINIDUMP_EXCEPTION_ CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); if (file_handle == nullptr || file_handle == INVALID_HANDLE_VALUE) { - std::fprintf(stderr, "CreateFileA failed. Error: %d\n", GetLastError()); + fmt::print(stderr, "CreateFileA failed. Error: {}", GetLastError()); return; } @@ -34,9 +37,9 @@ void CreateMiniDump(HANDLE process_handle, DWORD process_id, MINIDUMP_EXCEPTION_ dump_type, (pep != 0) ? info : 0, 0, 0); if (write_dump_status) { - std::fprintf(stderr, "MiniDump created: %s\n", file_name); + fmt::print(stderr, "MiniDump created: {}", file_name); } else { - std::fprintf(stderr, "MiniDumpWriteDump failed. Error: %d\n", GetLastError()); + fmt::print(stderr, "MiniDumpWriteDump failed. Error: {}", GetLastError()); } // Close the file @@ -48,15 +51,15 @@ void DumpFromDebugEvent(DEBUG_EVENT& deb_ev, PROCESS_INFORMATION& pi) { HANDLE thread_handle = OpenThread(THREAD_GET_CONTEXT, false, deb_ev.dwThreadId); if (thread_handle == nullptr) { - std::fprintf(stderr, "OpenThread failed (%d)\n", GetLastError()); + fmt::print(stderr, "OpenThread failed ({})", GetLastError()); + return; } // Get child process context - CONTEXT context; - std::memset(&context, 0, sizeof(context)); + CONTEXT context = {}; context.ContextFlags = CONTEXT_ALL; if (!GetThreadContext(thread_handle, &context)) { - std::fprintf(stderr, "GetThreadContext failed (%d)\n", GetLastError()); + fmt::print(stderr, "GetThreadContext failed ({})", GetLastError()); return; } @@ -73,7 +76,7 @@ void DumpFromDebugEvent(DEBUG_EVENT& deb_ev, PROCESS_INFORMATION& pi) { CreateMiniDump(pi.hProcess, pi.dwProcessId, &info, &ep); if (CloseHandle(thread_handle) == 0) { - std::fprintf(stderr, "error: CloseHandle(thread_handle) failed (%d)\n", GetLastError()); + fmt::print(stderr, "error: CloseHandle(thread_handle) failed ({})", GetLastError()); } } @@ -86,67 +89,22 @@ bool SpawnDebuggee(const char* arg0, PROCESS_INFORMATION& pi) { } if (!SpawnChild(arg0, &pi, 0)) { - std::fprintf(stderr, "warning: continuing without crash dumps\n"); + fmt::print(stderr, "warning: continuing without crash dumps"); return false; } const bool can_debug = DebugActiveProcess(pi.dwProcessId); if (!can_debug) { - std::fprintf(stderr, - "warning: DebugActiveProcess failed (%d), continuing without crash dumps\n", - GetLastError()); + fmt::print(stderr, + "warning: DebugActiveProcess failed ({}), continuing without crash dumps", + GetLastError()); return false; } return true; } -void DebugDebuggee(PROCESS_INFORMATION& pi) { - DEBUG_EVENT deb_ev; - std::memset(&deb_ev, 0, sizeof(deb_ev)); - - while (deb_ev.dwDebugEventCode != EXIT_PROCESS_DEBUG_EVENT) { - const bool wait_success = WaitForDebugEvent(&deb_ev, INFINITE); - if (!wait_success) { - std::fprintf(stderr, "error: WaitForDebugEvent failed (%d)\n", GetLastError()); - return; - } - - switch (deb_ev.dwDebugEventCode) { - case OUTPUT_DEBUG_STRING_EVENT: - case CREATE_PROCESS_DEBUG_EVENT: - case CREATE_THREAD_DEBUG_EVENT: - case EXIT_PROCESS_DEBUG_EVENT: - case EXIT_THREAD_DEBUG_EVENT: - case LOAD_DLL_DEBUG_EVENT: - case RIP_EVENT: - case UNLOAD_DLL_DEBUG_EVENT: - // Continue on all other debug events - ContinueDebugEvent(deb_ev.dwProcessId, deb_ev.dwThreadId, DBG_CONTINUE); - break; - case EXCEPTION_DEBUG_EVENT: - EXCEPTION_RECORD& record = deb_ev.u.Exception.ExceptionRecord; - - // We want to generate a crash dump if we are seeing the same exception again. - if (!deb_ev.u.Exception.dwFirstChance) { - std::fprintf(stderr, "Creating MiniDump on ExceptionCode: 0x%08x %s\n", - record.ExceptionCode, ExceptionName(record.ExceptionCode)); - DumpFromDebugEvent(deb_ev, pi); - } - - // Continue without handling the exception. - // Lets the debuggee use its own exception handler. - // - If one does not exist, we will see the exception once more where we make a minidump - // for. Then when it reaches here again, yuzu will probably crash. - // - DBG_CONTINUE on an exception that the debuggee does not handle can set us up for an - // infinite loop of exceptions. - ContinueDebugEvent(deb_ev.dwProcessId, deb_ev.dwThreadId, DBG_EXCEPTION_NOT_HANDLED); - break; - } - } -} - -const char* ExceptionName(DWORD exception) { +static const char* ExceptionName(DWORD exception) { switch (exception) { case EXCEPTION_ACCESS_VIOLATION: return "EXCEPTION_ACCESS_VIOLATION"; @@ -193,6 +151,52 @@ const char* ExceptionName(DWORD exception) { case EXCEPTION_INVALID_HANDLE: return "EXCEPTION_INVALID_HANDLE"; default: - return nullptr; + return "unknown exception type"; } } + +void DebugDebuggee(PROCESS_INFORMATION& pi) { + DEBUG_EVENT deb_ev = {}; + + while (deb_ev.dwDebugEventCode != EXIT_PROCESS_DEBUG_EVENT) { + const bool wait_success = WaitForDebugEvent(&deb_ev, INFINITE); + if (!wait_success) { + fmt::print(stderr, "error: WaitForDebugEvent failed ({})", GetLastError()); + return; + } + + switch (deb_ev.dwDebugEventCode) { + case OUTPUT_DEBUG_STRING_EVENT: + case CREATE_PROCESS_DEBUG_EVENT: + case CREATE_THREAD_DEBUG_EVENT: + case EXIT_PROCESS_DEBUG_EVENT: + case EXIT_THREAD_DEBUG_EVENT: + case LOAD_DLL_DEBUG_EVENT: + case RIP_EVENT: + case UNLOAD_DLL_DEBUG_EVENT: + // Continue on all other debug events + ContinueDebugEvent(deb_ev.dwProcessId, deb_ev.dwThreadId, DBG_CONTINUE); + break; + case EXCEPTION_DEBUG_EVENT: + EXCEPTION_RECORD& record = deb_ev.u.Exception.ExceptionRecord; + + // We want to generate a crash dump if we are seeing the same exception again. + if (!deb_ev.u.Exception.dwFirstChance) { + fmt::print(stderr, "Creating MiniDump on ExceptionCode: 0x{:08x} {}\n", + record.ExceptionCode, ExceptionName(record.ExceptionCode)); + DumpFromDebugEvent(deb_ev, pi); + } + + // Continue without handling the exception. + // Lets the debuggee use its own exception handler. + // - If one does not exist, we will see the exception once more where we make a minidump + // for. Then when it reaches here again, yuzu will probably crash. + // - DBG_CONTINUE on an exception that the debuggee does not handle can set us up for an + // infinite loop of exceptions. + ContinueDebugEvent(deb_ev.dwProcessId, deb_ev.dwThreadId, DBG_EXCEPTION_NOT_HANDLED); + break; + } + } +} + +} // namespace MiniDump diff --git a/src/yuzu/mini_dump.h b/src/yuzu/mini_dump.h index 2052e5248..d6b6cca84 100644 --- a/src/yuzu/mini_dump.h +++ b/src/yuzu/mini_dump.h @@ -7,10 +7,13 @@ #include +namespace MiniDump { + void CreateMiniDump(HANDLE process_handle, DWORD process_id, MINIDUMP_EXCEPTION_INFORMATION* info, EXCEPTION_POINTERS* pep); void DumpFromDebugEvent(DEBUG_EVENT& deb_ev, PROCESS_INFORMATION& pi); bool SpawnDebuggee(const char* arg0, PROCESS_INFORMATION& pi); void DebugDebuggee(PROCESS_INFORMATION& pi); -const char* ExceptionName(DWORD exception); + +} // namespace MiniDump From 9dc9e501debda06ff31a3e2fe8875ffa0d94e40b Mon Sep 17 00:00:00 2001 From: lat9nq Date: Sat, 30 Jul 2022 12:41:30 -0400 Subject: [PATCH 39/78] ci/windows: Upload debugging symbols --- .ci/scripts/windows/upload.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.ci/scripts/windows/upload.ps1 b/.ci/scripts/windows/upload.ps1 index d463281de..21abcd752 100644 --- a/.ci/scripts/windows/upload.ps1 +++ b/.ci/scripts/windows/upload.ps1 @@ -65,8 +65,8 @@ if ("$env:GITHUB_ACTIONS" -eq "true") { # None of the other GHA builds are including source, so commenting out today #Copy-Item $MSVC_SOURCE_TARXZ -Destination "artifacts" - # Are debug symbols important? - # cp .\build\bin\yuzu*.pdb .\pdb\ + # Debugging symbols + cp .\build\bin\yuzu*.pdb .\artifacts\ # Write out a tag BUILD_TAG to environment for the Upload step # We're getting ${{ github.event.number }} as $env:PR_NUMBER" From dc8d42243b1eb4ea2b1192273e58586a617419be Mon Sep 17 00:00:00 2001 From: Narr the Reg Date: Mon, 5 Sep 2022 16:08:30 -0500 Subject: [PATCH 40/78] core: hid: Fix GC triggers overwritting ZL and ZR buttons --- src/core/hid/emulated_controller.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) 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) { From 2898be69f414a2735b8693c58e039097853f5ec7 Mon Sep 17 00:00:00 2001 From: Narr the Reg Date: Tue, 6 Sep 2022 11:20:53 -0500 Subject: [PATCH 41/78] 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(+) 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); From de8f7e12509440d4fb2af20432d14e71612a4cba Mon Sep 17 00:00:00 2001 From: Narr the Reg Date: Tue, 6 Sep 2022 11:44:29 -0500 Subject: [PATCH 42/78] yuzu: input: fix invert symbol on axis and order options alphabetically --- .../configuration/configure_input_player.cpp | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp index 972c311f6..9b4f765ce 100644 --- a/src/yuzu/configuration/configure_input_player.cpp +++ b/src/yuzu/configuration/configure_input_player.cpp @@ -161,6 +161,7 @@ QString ConfigureInputPlayer::ButtonToText(const Common::ParamPackage& param) { const QString toggle = QString::fromStdString(param.Get("toggle", false) ? "~" : ""); const QString inverted = QString::fromStdString(param.Get("inverted", false) ? "!" : ""); + const QString invert = QString::fromStdString(param.Get("invert", "+") == "-" ? "-" : ""); const auto common_button_name = input_subsystem->GetButtonName(param); // Retrieve the names from Qt @@ -184,7 +185,7 @@ QString ConfigureInputPlayer::ButtonToText(const Common::ParamPackage& param) { } if (param.Has("axis")) { const QString axis = QString::fromStdString(param.Get("axis", "")); - return QObject::tr("%1%2Axis %3").arg(toggle, inverted, axis); + return QObject::tr("%1%2Axis %3").arg(toggle, invert, axis); } if (param.Has("axis_x") && param.Has("axis_y") && param.Has("axis_z")) { const QString axis_x = QString::fromStdString(param.Get("axis_x", "")); @@ -362,18 +363,18 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i button_map[button_id]->setText(tr("[not set]")); }); if (param.Has("code") || param.Has("button") || param.Has("hat")) { - context_menu.addAction(tr("Toggle button"), [&] { - 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("Invert button"), [&] { const bool invert_value = !param.Get("inverted", false); param.Set("inverted", invert_value); button_map[button_id]->setText(ButtonToText(param)); emulated_controller->SetButtonParam(button_id, param); }); + context_menu.addAction(tr("Toggle button"), [&] { + 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); + }); } if (param.Has("axis")) { context_menu.addAction(tr("Invert axis"), [&] { @@ -382,12 +383,6 @@ 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); @@ -404,6 +399,12 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i } 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.exec(button_map[button_id]->mapToGlobal(menu_location)); }); From 848f69eb19ddeffd6e2879108eb2604ec390a14e Mon Sep 17 00:00:00 2001 From: german77 Date: Sun, 13 Feb 2022 11:54:39 -0600 Subject: [PATCH 43/78] 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 | 497 +++++++++++++++------ src/core/hle/service/nfp/nfp.h | 137 +----- src/yuzu/main.cpp | 21 +- 7 files changed, 1235 insertions(+), 284 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 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,37 +615,43 @@ void Module::Interface::CreateUserInterface(Kernel::HLERequestContext& ctx) { rb.PushIpcInterface(*this, system); } -bool Module::Interface::LoadAmiibo(const std::vector& buffer) { +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}; + + if (!amiibo_file.IsOpen()) { + LOG_ERROR(Core, "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"); + return false; + } + memcpy(&encrypted_tag_data, buffer.data(), sizeof(EncryptedNTAG215File)); + + if (!AmiiboCrypto::IsAmiiboValid(encrypted_tag_data)) { + LOG_INFO(Service_NFP, "Invalid amiibo"); + return false; + } + + file_path = filename; + return true; +} + +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; } - constexpr auto tag_size = sizeof(NTAG215File); - constexpr auto tag_size_without_password = sizeof(NTAG215File) - sizeof(NTAG215Password); - - std::vector amiibo_buffer = buffer; - - if (amiibo_buffer.size() < tag_size_without_password) { - LOG_ERROR(Service_NFP, "Wrong file size {}", buffer.size()); + if (!LoadAmiiboFile(filename)) { return false; } - // Ensure it has the correct size - if (amiibo_buffer.size() != tag_size) { - amiibo_buffer.resize(tag_size, 0); - } - - LOG_INFO(Service_NFP, "Amiibo detected"); - std::memcpy(&tag_data, buffer.data(), tag_size); - - if (!IsAmiiboValid()) { - return false; - } - - // This value can't be dumped from a tag. Generate it - tag_data.password.PWD = GetTagPassword(tag_data.uuid); - device_state = DeviceState::TagFound; activate_event->GetWritableEvent().Signal(); return true; @@ -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; - // Read this data from the amiibo save file + // 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; + } + + // 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; - } - if (!is_application_area_initialized) { + + // Fallback for lack of amiibo keys + if (!is_data_decoded) { LOG_WARNING(Service_NFP, "Application area is not initialized"); return ErrCodes::ApplicationAreaIsNotInitialized; } + + 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,32 +982,23 @@ 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); + + 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; } -bool Module::Interface::AmiiboApplicationDataExist(u32 access_id) const { - // TODO(german77): Check if file exist - return false; -} - -std::vector Module::Interface::LoadAmiiboApplicationData(u32 access_id) const { - // TODO(german77): Read file - std::vector data(ApplicationAreaSize); - return data; -} - -void Module::Interface::SaveAmiiboApplicationData(u32 access_id, - const std::vector& data) const { - // TODO(german77): Save file -} - u64 Module::Interface::GetHandle() const { // Generate a handle based of the npad id return static_cast(npad_id); @@ -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.")); } From 19a4e12e6ea6c3d06ee227f3ef1a6cbf93850f6e Mon Sep 17 00:00:00 2001 From: Narr the Reg Date: Tue, 30 Aug 2022 00:33:47 -0500 Subject: [PATCH 44/78] core: nfp: Implement Convert and RecreateApplicationArea, accuracy fixes --- .../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 | 159 +++--------------- src/core/hle/service/nfp/nfp.cpp | 95 +++++++++-- src/core/hle/service/nfp/nfp.h | 4 +- 10 files changed, 355 insertions(+), 256 deletions(-) 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{}; + u16_be raw_date{}; - BitField<0, 5, u16> day; - BitField<5, 4, u16> month; - BitField<9, 7, u16> year; - }; + 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; From caa138b33f6bbc18cde4d403017dad6bd4387e13 Mon Sep 17 00:00:00 2001 From: Narr the Reg Date: Tue, 30 Aug 2022 19:28:37 -0500 Subject: [PATCH 45/78] 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(-) 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{}; From 4834961736f95ac99deb8edd2af51be5276a041f Mon Sep 17 00:00:00 2001 From: german77 Date: Thu, 1 Sep 2022 00:20:22 -0500 Subject: [PATCH 46/78] core: nfp: Workaround for lack of multiple nfp interfaces --- src/core/hle/service/nfp/nfp.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 { From 063b23cc58b1e7367f9e530e752ab56fe1f56532 Mon Sep 17 00:00:00 2001 From: german77 Date: Wed, 7 Sep 2022 01:03:02 -0500 Subject: [PATCH 47/78] core: nfp: Remove magic numbers --- src/core/hle/service/nfp/amiibo_crypto.cpp | 161 ++++++++++----------- src/core/hle/service/nfp/amiibo_crypto.h | 29 ++-- src/core/hle/service/nfp/amiibo_types.h | 8 +- 3 files changed, 98 insertions(+), 100 deletions(-) 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{}; + 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)); + 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)); - 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)); + 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)); - mbedtls_aes_crypt_ctr(&aes, 0x188, &nc_off, nonce_counter.data(), stream_block.data(), - in_data_byes.data() + 0x2c, out_data_bytes.data() + 0x2c); + // 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; - 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); + out_data.constant_value = in_data.constant_value; + out_data.write_counter = in_data.write_counter; - memcpy(&out_data, out_data_bytes.data(), sizeof(NTAG215File)); + 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); + sizeof(HmacKey), + reinterpret_cast(&tag_data.write_counter), input_length2, + reinterpret_cast(&tag_data.hmac_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) { + 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 From e78a623342ecab2d10fa4940d40c2c90d008b061 Mon Sep 17 00:00:00 2001 From: Kyle Kienapfel Date: Thu, 8 Sep 2022 05:34:36 -0700 Subject: [PATCH 48/78] CMake: explicitly link mbedcrypto for yuzu-room Doesn't appear to effect anything regular, but in both Linux and Windows builds it looks like our project has all the libraries available for linking. If this feature is turned off, there is only one thing that quit working, when linking yuzu-room it couldn't find a function called mbedtls_base64_decode mbedtls is split into three libraries for some reason: mbedtls mbedx509 mbedcrypto mbedtls_base64_decode is in mbedcrypto --- src/dedicated_room/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dedicated_room/CMakeLists.txt b/src/dedicated_room/CMakeLists.txt index 737aedbe4..1efdbc1f7 100644 --- a/src/dedicated_room/CMakeLists.txt +++ b/src/dedicated_room/CMakeLists.txt @@ -16,7 +16,7 @@ if (ENABLE_WEB_SERVICE) target_link_libraries(yuzu-room PRIVATE web_service) endif() -target_link_libraries(yuzu-room PRIVATE mbedtls) +target_link_libraries(yuzu-room PRIVATE mbedtls mbedcrypto) if (MSVC) target_link_libraries(yuzu-room PRIVATE getopt) endif() 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: [PATCH 49/78] core/CMakeLists.txt: Remove duplicate files. --- src/core/CMakeLists.txt | 6 ------ 1 file changed, 6 deletions(-) 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 From 5d907d9acd3cf767eaca8023ff5d8d7b9e2a10ba Mon Sep 17 00:00:00 2001 From: german77 Date: Sun, 11 Sep 2022 08:56:12 -0500 Subject: [PATCH 50/78] input_common: Increase mapping timer from 2.5 seconds to 4 seconds --- src/yuzu/configuration/configure_input_player.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp index 109689c88..4646a94f5 100644 --- a/src/yuzu/configuration/configure_input_player.cpp +++ b/src/yuzu/configuration/configure_input_player.cpp @@ -1410,7 +1410,7 @@ void ConfigureInputPlayer::HandleClick( ui->controllerFrame->BeginMappingAnalog(button_id); } - timeout_timer->start(2500); // Cancel after 2.5 seconds + timeout_timer->start(4000); // Cancel after 4 seconds poll_timer->start(25); // Check for new inputs every 25ms } From 1deecc6f7017bb749b12fdf0914d343b1687a0a4 Mon Sep 17 00:00:00 2001 From: Kelebek1 Date: Mon, 12 Sep 2022 19:27:11 +0100 Subject: [PATCH 51/78] Remove a pragma once from a cpp file --- src/audio_core/sink/sink_stream.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/audio_core/sink/sink_stream.cpp b/src/audio_core/sink/sink_stream.cpp index 24636e512..68987eba6 100644 --- a/src/audio_core/sink/sink_stream.cpp +++ b/src/audio_core/sink/sink_stream.cpp @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#pragma once - #include #include #include From 949917babc0db8c9e3e4b95d07c2b58335eab8df Mon Sep 17 00:00:00 2001 From: Dev-draco Date: Mon, 12 Sep 2022 16:50:57 -0400 Subject: [PATCH 52/78] qt_themes: Update sd card icon --- dist/qt_themes/default/icons/48x48/sd_card.png | Bin 561 -> 198 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/dist/qt_themes/default/icons/48x48/sd_card.png b/dist/qt_themes/default/icons/48x48/sd_card.png index 60dfba2693ca39fe823f89fbcbeb25ea274698cd..6bcb7f6b1d93ab9bf8731849832aa88df68b0fd3 100644 GIT binary patch delta 170 zcmdnUa*T0;NJN!-xO%$JDRf zh)|TwQ?q@u#pF^?d1&FXqUwYF59L$OJbO6Za=k>{^<#Zc4!$p70_wLt-S5NA$Z&@7 zN{WN8UE{1fPEUoH85p)mEMJ}Nr^}Ks#lQ=w3Iu@GGSvJ~TX#-!xBaW>Kl4pG#z{m$RA@u(nN2GNQ5?okDDn~#l0w;75GgM!%F<`B@(pC;gV?aL zXJa8**!cjmvavvNkq|;Db9QiG1*@Wy= zi>{PffZS$YS4wrlSGcCDXb|_|DXixTumrswf#k@ObJLPDcn^652l^-$n%vKUi{AU@3;h* zfVnzt;*@$3|HK&VHIBn>I10Pq3?#FV^)zgT9GD*12Zv!l9D@Ar6wHi8Xz%ydIcPb7 z$r9*rsy4u&X*-N!lpA0j@}aB%Te(7tznnsiuzxOElp|ck(o}qaTGJMT&pgL) zHzsFI&Z@rQi_Sr`m@PXnZRX6{FcXusXCeE3D^9ozNfQD{69PyR0!R}AND~4`69PyR z0!UL80lfd^S%hEr?GoU~v=U^t?`@cTFZL~ePI39!$B8Dm3ZG!w&a#yMA#cET7|1zF aeFH7#$`QfAFU^Jk0000 Date: Mon, 12 Sep 2022 18:53:01 -0400 Subject: [PATCH 53/78] qt_themes: Add colorful and dark mode sd card icons --- .reuse/dep5 | 5 ++--- dist/qt_themes/colorful/icons/48x48/sd_card.png | Bin 981 -> 228 bytes .../qdarkstyle/icons/48x48/sd_card.png | Bin 587 -> 214 bytes 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.reuse/dep5 b/.reuse/dep5 index fe4fa2f07..5ba017494 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -6,6 +6,7 @@ Files: dist/english_plurals/* dist/icons/controller/*.png dist/icons/overlay/*.png dist/languages/* + dist/qt_themes/*/icons/48x48/sd_card.png dist/qt_themes/*/icons/index.theme dist/qt_themes/default/style.qss Copyright: yuzu Emulator Project @@ -66,9 +67,7 @@ Files: dist/qt_themes/*/icons/48x48/no_avatar.png Copyright: Ionic (http://ionic.io/) License: MIT - -Files: dist/qt_themes/*/icons/48x48/sd_card.png - dist/qt_themes/colorful/icons/48x48/star.png +Files: dist/qt_themes/colorful/icons/48x48/star.png dist/qt_themes/default/icons/16x16/checked.png dist/qt_themes/default/icons/16x16/failed.png Copyright: SVG Repo diff --git a/dist/qt_themes/colorful/icons/48x48/sd_card.png b/dist/qt_themes/colorful/icons/48x48/sd_card.png index 47e491d32c99fe208c59dae4d4a568e8e7dd6485..652d61bc325bb1aad05f0cf316a540539007db27 100644 GIT binary patch delta 200 zcmcc0{)BOYN@F}{H_o?V z(x$0X+HK3(b?TXbhF;LLONmirU=U+G^U5KXVbw!thNmAKLmR3pb_p;r)JrZ;FIPU6 u$&C&jq)GBt2dIT-G@yGywoRz)ko7 delta 959 zcmV;w13>)b0o4bPB!2;OQb$4nuFf3k00004XF*Lt006O%3;baP00009a7bBm0003+ z0003+0iR1(xBvhHYDq*vRA@u(na^v~KorMcer)zflD0~v6)P4MTPYS4ya)>3t={ZO zFBL6lL9{0idRFuxTIr!`!IPpVMew5kfrolfRMbP+ifp^>{(sD7lZkI;XQkq%NtA3N z?1v8TeJ4|PzMc2Fvn_Op7=cIsIF4{?ZuvHT=M^)n(B{ok>NeTWQ&;>M*kJR!)i0okCd-o;cV)h^oU$dEW1~rswSMt7u)*fHrx)Ss z&P=c%tf~SGg4|3D%H@{c^2*cVZmTPi_MO7~U7Mt@L zYC0OZ|FuEzG~f`}$y3JJ9rJK*@F7>x<7uF7k%K&C&>C*maUAeI0l`st8cV#JIOhD5 zfhD64md(xZx!h^Ja@jTtH82j1jI40!il+fV77vJ`AaNNxV%x-WLf$rhZ|NnMuEf@W0D=Ib zFuJsVfvD*ih?>w0ID|m4Scd${8m#0CP%2iyaR_@>ts1blR^+3^ z*4674dlrXvGC>K=KmyC7XHysGx(W$J2GcZIy)##>aB%)Fb`zR`JT`8*Tm?l*fTpRCN~Kt1 zBN{F5cEte+w)dCt_V{0Tyf;2XJP(m|6 zn^V^_Soe)jqZw>iUDsGn&84tMSWG2Zne}vQ`1vLHKTSh3Kv5Kh8PT*1Zd9S4vG{zV z@f!$7I-O$r*TaB>YM>qb_Xa%AZS2;!IXJumOmNv&1VOz2{PN%#E?eSx0=8`%1m9qy h81%LIHaf%+Ux)T002ovPDHLkV1mW4#Tozr diff --git a/dist/qt_themes/qdarkstyle/icons/48x48/sd_card.png b/dist/qt_themes/qdarkstyle/icons/48x48/sd_card.png index 87ae5186d95a9aaee49247dea04264aea58a2353..15e5e40245555fef17e4ee5344d07a6115ed050f 100644 GIT binary patch delta 186 zcmX@ja*c634Q% zXRhLTnV$YADbAFmQ%YJ?(6CO!GwR-BDZB0o#XoqG`ktAV`#-7GijS9P-%`&6H0Oh+ zUCJIO1_l$xkkSRp4A(zsHWdF*Jz#4S%fZkPeeC6{JmVu4ybXa(fgsgT(9kvaMy33= d_*cgJrEMx@Osb~Nt!W3zdAjpG;7LS5RA@u(n$0T&VI0SY6p1JyDU^%6M9RyFa@_I{$jQl7F8l}n z0S9i*oRov)=qkB8EXiA1l8~46`OQ3|X)?Rd?wWmO^L*;l^MCxF*=^=KGtbQK&Q@i! zS*MbzR&B*nYJhRfpd7|HT)~`_57{vzIMEOv5jZHFOic(IY zdaUuf=CG64G!-8}=SzJ*&UoD)7IDDq#>y(LdEE%YcQ#G;upHPsv9N&QUkg&02<%nJ zG&X=~Yyi{P0H(14Ok)F>#s)Br4Pcso5kUWu=iife-8unIRcnI0>(&WyjJsa9tQ_Hi z*NsMZ4lB&zItHJz$Hgmd;Vgd2KrZaRRm|cz--^dg%w+gF%m4rY07*qoM6N<$f|Rof A`~Uy| From e93e898df528d013e2e0cfeba22e2b6d76bf99b6 Mon Sep 17 00:00:00 2001 From: Kelebek1 Date: Sat, 10 Sep 2022 21:14:03 +0100 Subject: [PATCH 54/78] 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(-) 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. From bdb866af1d28d47c36ec84fbcde53f64f09c3fd1 Mon Sep 17 00:00:00 2001 From: Lioncash Date: Tue, 13 Sep 2022 13:28:50 -0400 Subject: [PATCH 55/78] compressor: Remove unneeded casts in ApplyCompressorEffect Same behavior, but also silences a -Wcast-qual warning, since the second cast casts away const. --- src/audio_core/renderer/command/effect/compressor.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/audio_core/renderer/command/effect/compressor.cpp b/src/audio_core/renderer/command/effect/compressor.cpp index 2ebc140f1..f9bba9f39 100644 --- a/src/audio_core/renderer/command/effect/compressor.cpp +++ b/src/audio_core/renderer/command/effect/compressor.cpp @@ -103,8 +103,7 @@ static void ApplyCompressorEffect(CompressorInfo::ParameterVersion2& params, } else { for (s16 channel = 0; channel < params.channel_count; channel++) { if (params.inputs[channel] != params.outputs[channel]) { - std::memcpy((char*)output_buffers[channel].data(), - (char*)input_buffers[channel].data(), + std::memcpy(output_buffers[channel].data(), input_buffers[channel].data(), output_buffers[channel].size_bytes()); } } From fd876f200f1c09cd4d840c1f3401390e00322b15 Mon Sep 17 00:00:00 2001 From: Lioncash Date: Tue, 13 Sep 2022 13:33:38 -0400 Subject: [PATCH 56/78] compressor: Mark params parameters as const These functions don't modify the parameters. --- src/audio_core/renderer/command/effect/compressor.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/audio_core/renderer/command/effect/compressor.cpp b/src/audio_core/renderer/command/effect/compressor.cpp index f9bba9f39..8c1b07609 100644 --- a/src/audio_core/renderer/command/effect/compressor.cpp +++ b/src/audio_core/renderer/command/effect/compressor.cpp @@ -11,7 +11,7 @@ namespace AudioCore::AudioRenderer { -static void SetCompressorEffectParameter(CompressorInfo::ParameterVersion2& params, +static void SetCompressorEffectParameter(const CompressorInfo::ParameterVersion2& params, CompressorInfo::State& state) { const auto ratio{1.0f / params.compressor_ratio}; auto makeup_gain{0.0f}; @@ -31,7 +31,7 @@ static void SetCompressorEffectParameter(CompressorInfo::ParameterVersion2& para state.unk_20 = c; } -static void InitializeCompressorEffect(CompressorInfo::ParameterVersion2& params, +static void InitializeCompressorEffect(const CompressorInfo::ParameterVersion2& params, CompressorInfo::State& state) { std::memset(&state, 0, sizeof(CompressorInfo::State)); @@ -42,7 +42,7 @@ static void InitializeCompressorEffect(CompressorInfo::ParameterVersion2& params SetCompressorEffectParameter(params, state); } -static void ApplyCompressorEffect(CompressorInfo::ParameterVersion2& params, +static void ApplyCompressorEffect(const CompressorInfo::ParameterVersion2& params, CompressorInfo::State& state, bool enabled, std::vector> input_buffers, std::vector> output_buffers, u32 sample_count) { From f08046f4d7f86207bac547263113f5ba0ceab9ff Mon Sep 17 00:00:00 2001 From: Lioncash Date: Tue, 13 Sep 2022 13:34:56 -0400 Subject: [PATCH 57/78] compressor: Simplify memset in InitializeCompressorEffect Provides equivalent behavior while being significantly smaller. --- src/audio_core/renderer/command/effect/compressor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/audio_core/renderer/command/effect/compressor.cpp b/src/audio_core/renderer/command/effect/compressor.cpp index 8c1b07609..7229618e8 100644 --- a/src/audio_core/renderer/command/effect/compressor.cpp +++ b/src/audio_core/renderer/command/effect/compressor.cpp @@ -33,7 +33,7 @@ static void SetCompressorEffectParameter(const CompressorInfo::ParameterVersion2 static void InitializeCompressorEffect(const CompressorInfo::ParameterVersion2& params, CompressorInfo::State& state) { - std::memset(&state, 0, sizeof(CompressorInfo::State)); + state = {}; state.unk_00 = 0; state.unk_04 = 1.0f; From 7fda6de5cbe0447c5960f4ab8f7820de09d9269d Mon Sep 17 00:00:00 2001 From: liushuyu Date: Mon, 12 Sep 2022 23:01:44 -0600 Subject: [PATCH 58/78] common: do not link to xbyak on non-amd64 architectures --- src/common/CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 635fb85c8..b1e0ba6cc 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -166,6 +166,7 @@ if(ARCHITECTURE_x86_64) x64/xbyak_abi.h x64/xbyak_util.h ) + target_link_libraries(common PRIVATE xbyak) endif() if (MSVC) @@ -189,7 +190,7 @@ endif() create_target_directory_groups(common) target_link_libraries(common PUBLIC ${Boost_LIBRARIES} fmt::fmt microprofile Threads::Threads) -target_link_libraries(common PRIVATE lz4::lz4 xbyak) +target_link_libraries(common PRIVATE lz4::lz4) if (TARGET zstd::zstd) target_link_libraries(common PRIVATE zstd::zstd) else() From fbbedb032c41c0a2ded321981cbda2617c35dbfa Mon Sep 17 00:00:00 2001 From: Kyle Kienapfel Date: Thu, 15 Sep 2022 01:43:03 -0700 Subject: [PATCH 59/78] UI: Fix link to TAS help page Tools -> TAS -> Configure TAS Thanks to Rei on discord for the fix. Basically: openExternalLinks is a checkbox in Qt Creator --- src/yuzu/configuration/configure_tas.ui | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/yuzu/configuration/configure_tas.ui b/src/yuzu/configuration/configure_tas.ui index cf88a5bf0..625af0c89 100644 --- a/src/yuzu/configuration/configure_tas.ui +++ b/src/yuzu/configuration/configure_tas.ui @@ -16,6 +16,9 @@ <html><head/><body><p>Reads controller input from scripts in the same format as TAS-nx scripts.<br/>For a more detailed explanation, please consult the <a href="https://yuzu-emu.org/help/feature/tas/"><span style=" text-decoration: underline; color:#039be5;">help page</span></a> on the yuzu website.</p></body></html> + + true + From 1c7dae966d52287ba5812c27d2fe0c59938aa416 Mon Sep 17 00:00:00 2001 From: Lioncash Date: Wed, 14 Sep 2022 03:32:14 -0400 Subject: [PATCH 60/78] 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(-) 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."); From d55046c5e97d2dc0a55d175e1101122d646ad540 Mon Sep 17 00:00:00 2001 From: Lioncash Date: Thu, 15 Sep 2022 09:06:14 -0400 Subject: [PATCH 61/78] 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(-) 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++) { From 2c91fbf7f1384b5cb01e20e4f59e27a1f9bd9a61 Mon Sep 17 00:00:00 2001 From: Lioncash Date: Tue, 13 Sep 2022 13:50:39 -0400 Subject: [PATCH 62/78] audio_core: Amend documentation tags Resolves a wackload of -Wdocumentation warnings due to mismatching tags and whatnot. --- src/audio_core/audio_core.h | 2 +- src/audio_core/audio_event.h | 4 +- src/audio_core/audio_in_manager.h | 5 +- src/audio_core/audio_manager.h | 2 +- src/audio_core/audio_render_manager.h | 6 +-- src/audio_core/device/audio_buffers.h | 4 +- src/audio_core/device/device_session.h | 3 +- src/audio_core/in/audio_in_system.h | 2 +- src/audio_core/out/audio_out_system.h | 2 +- src/audio_core/renderer/adsp/adsp.h | 2 - src/audio_core/renderer/adsp/audio_renderer.h | 6 +-- .../renderer/adsp/command_list_processor.h | 13 +++--- .../renderer/command/command_buffer.h | 12 +++-- .../renderer/command/command_generator.h | 46 +++++++++---------- .../renderer/command/mix/mix_ramp.cpp | 18 ++------ .../renderer/command/mix/mix_ramp.h | 8 ++-- .../renderer/command/mix/mix_ramp_grouped.h | 4 +- .../renderer/effect/effect_context.h | 14 +++--- .../renderer/effect/effect_info_base.h | 4 +- src/audio_core/renderer/memory/address_info.h | 5 +- src/audio_core/renderer/nodes/node_states.h | 4 +- .../performance/performance_manager.h | 8 ++-- .../renderer/upsampler/upsampler_manager.h | 2 +- src/audio_core/renderer/voice/voice_info.h | 26 ++++++----- src/audio_core/sink/cubeb_sink.h | 5 +- src/audio_core/sink/sdl2_sink.h | 5 +- src/audio_core/sink/sink.h | 3 +- src/audio_core/sink/sink_stream.h | 2 +- 28 files changed, 105 insertions(+), 112 deletions(-) diff --git a/src/audio_core/audio_core.h b/src/audio_core/audio_core.h index ac9afefaa..e48fdc323 100644 --- a/src/audio_core/audio_core.h +++ b/src/audio_core/audio_core.h @@ -17,7 +17,7 @@ namespace AudioCore { class AudioManager; /** - * Main audio class, sotred inside the core, and holding the audio manager, all sinks, and the ADSP. + * Main audio class, stored inside the core, and holding the audio manager, all sinks, and the ADSP. */ class AudioCore { public: diff --git a/src/audio_core/audio_event.h b/src/audio_core/audio_event.h index 82dd32dca..012d2ed70 100644 --- a/src/audio_core/audio_event.h +++ b/src/audio_core/audio_event.h @@ -14,7 +14,7 @@ namespace AudioCore { * Responsible for the input/output events, set by the stream backend when buffers are consumed, and * waited on by the audio manager. These callbacks signal the game's events to keep the audio buffer * recycling going. - * In a real Switch this is not a seprate class, and exists entirely within the audio manager. + * In a real Switch this is not a separate class, and exists entirely within the audio manager. * On the Switch it's implemented more simply through a MultiWaitEventHolder, where it can * wait on multiple events at once, and the events are not needed by the backend. */ @@ -81,7 +81,7 @@ public: void ClearEvents(); private: - /// Lock, used bythe audio manager + /// Lock, used by the audio manager std::mutex event_lock; /// Array of events, one per system type (see Type), last event is used to terminate std::array, 4> events_signalled; diff --git a/src/audio_core/audio_in_manager.h b/src/audio_core/audio_in_manager.h index 75b73a0b6..8a519df99 100644 --- a/src/audio_core/audio_in_manager.h +++ b/src/audio_core/audio_in_manager.h @@ -59,9 +59,10 @@ public: /** * Get a list of audio in device names. * - * @oaram names - Output container to write names to. - * @param max_count - Maximum numebr of deivce names to write. Unused + * @param names - Output container to write names to. + * @param max_count - Maximum number of device names to write. Unused * @param filter - Should the list be filtered? Unused. + * * @return Number of names written. */ u32 GetDeviceNames(std::vector& names, diff --git a/src/audio_core/audio_manager.h b/src/audio_core/audio_manager.h index 70316e9cb..8cbd95e22 100644 --- a/src/audio_core/audio_manager.h +++ b/src/audio_core/audio_manager.h @@ -76,7 +76,7 @@ public: private: /** - * Main thread, waiting on a manager signal and calling the registered fucntion. + * Main thread, waiting on a manager signal and calling the registered function. */ void ThreadFunc(); diff --git a/src/audio_core/audio_render_manager.h b/src/audio_core/audio_render_manager.h index 6a508ec56..7119e1b99 100644 --- a/src/audio_core/audio_render_manager.h +++ b/src/audio_core/audio_render_manager.h @@ -64,10 +64,10 @@ public: /** * Add a renderer system to the manager. - * The system will be reguarly called to generate commands for the AudioRenderer. + * The system will be regularly called to generate commands for the AudioRenderer. * * @param system - The system to add. - * @return True if the system was sucessfully added, otherwise false. + * @return True if the system was successfully added, otherwise false. */ bool AddSystem(System& system); @@ -75,7 +75,7 @@ public: * Remove a renderer system from the manager. * * @param system - The system to remove. - * @return True if the system was sucessfully removed, otherwise false. + * @return True if the system was successfully removed, otherwise false. */ bool RemoveSystem(System& system); diff --git a/src/audio_core/device/audio_buffers.h b/src/audio_core/device/audio_buffers.h index 57c78d439..3ecbbb63f 100644 --- a/src/audio_core/device/audio_buffers.h +++ b/src/audio_core/device/audio_buffers.h @@ -88,7 +88,9 @@ public: /** * Release all registered buffers. * - * @param timestamp - The released timestamp for this buffer. + * @param core_timing - The CoreTiming instance + * @param session - The device session + * * @return Is the buffer was released. */ bool ReleaseBuffers(Core::Timing::CoreTiming& core_timing, DeviceSession& session) { diff --git a/src/audio_core/device/device_session.h b/src/audio_core/device/device_session.h index 3414e2c06..53b649c61 100644 --- a/src/audio_core/device/device_session.h +++ b/src/audio_core/device/device_session.h @@ -74,7 +74,8 @@ public: /** * Check if the buffer for the given tag has been consumed by the backend. * - * @param tag - Unqiue tag of the buffer to check. + * @param buffer - the buffer to check. + * * @return true if the buffer has been consumed, otherwise false. */ bool IsBufferConsumed(AudioBuffer& buffer) const; diff --git a/src/audio_core/in/audio_in_system.h b/src/audio_core/in/audio_in_system.h index 165e35d83..9ddc8daae 100644 --- a/src/audio_core/in/audio_in_system.h +++ b/src/audio_core/in/audio_in_system.h @@ -208,7 +208,7 @@ public: /** * Set this system's current volume. * - * @param The new volume. + * @param volume The new volume. */ void SetVolume(f32 volume); diff --git a/src/audio_core/out/audio_out_system.h b/src/audio_core/out/audio_out_system.h index 4ca2f3417..205ead861 100644 --- a/src/audio_core/out/audio_out_system.h +++ b/src/audio_core/out/audio_out_system.h @@ -199,7 +199,7 @@ public: /** * Set this system's current volume. * - * @param The new volume. + * @param volume The new volume. */ void SetVolume(f32 volume); diff --git a/src/audio_core/renderer/adsp/adsp.h b/src/audio_core/renderer/adsp/adsp.h index 4dfcef4a5..523184dc2 100644 --- a/src/audio_core/renderer/adsp/adsp.h +++ b/src/audio_core/renderer/adsp/adsp.h @@ -63,8 +63,6 @@ public: /** * Stop the ADSP. - * - * @return True if started or already running, otherwise false. */ void Stop(); diff --git a/src/audio_core/renderer/adsp/audio_renderer.h b/src/audio_core/renderer/adsp/audio_renderer.h index b6ced9d2b..49f66f21c 100644 --- a/src/audio_core/renderer/adsp/audio_renderer.h +++ b/src/audio_core/renderer/adsp/audio_renderer.h @@ -52,7 +52,7 @@ public: /** * Send a message from the host to the AudioRenderer. * - * @param message_ - The message to send to the AudioRenderer. + * @param message - The message to send to the AudioRenderer. */ void HostSendMessage(RenderMessage message); @@ -66,7 +66,7 @@ public: /** * Send a message from the AudioRenderer to the host. * - * @param message_ - The message to send to the host. + * @param message - The message to send to the host. */ void ADSPSendMessage(RenderMessage message); @@ -163,7 +163,7 @@ public: /** * Start the AudioRenderer. * - * @param The mailbox to use for this session. + * @param mailbox The mailbox to use for this session. */ void Start(AudioRenderer_Mailbox* mailbox); diff --git a/src/audio_core/renderer/adsp/command_list_processor.h b/src/audio_core/renderer/adsp/command_list_processor.h index 3f99173e3..d78269e1d 100644 --- a/src/audio_core/renderer/adsp/command_list_processor.h +++ b/src/audio_core/renderer/adsp/command_list_processor.h @@ -33,10 +33,10 @@ public: /** * Initialize the processor. * - * @param system_ - The core system. - * @param buffer - The command buffer to process. - * @param size - The size of the buffer. - * @param stream_ - The stream to be used for sending the samples. + * @param system - The core system. + * @param buffer - The command buffer to process. + * @param size - The size of the buffer. + * @param stream - The stream to be used for sending the samples. */ void Initialize(Core::System& system, CpuAddr buffer, u64 size, Sink::SinkStream* stream); @@ -72,7 +72,8 @@ public: /** * Process the command list. * - * @param index - Index of the current command list. + * @param session_id - Session ID for the commands being processed. + * * @return The time taken to process. */ u64 Process(u32 session_id); @@ -89,7 +90,7 @@ public: u8* commands{}; /// The command buffer size u64 commands_buffer_size{}; - /// The maximum processing time alloted + /// The maximum processing time allotted u64 max_process_time{}; /// The number of commands in the buffer u32 command_count{}; diff --git a/src/audio_core/renderer/command/command_buffer.h b/src/audio_core/renderer/command/command_buffer.h index 496b0e50a..162170846 100644 --- a/src/audio_core/renderer/command/command_buffer.h +++ b/src/audio_core/renderer/command/command_buffer.h @@ -191,6 +191,7 @@ public: * @param volume - Current mix volume used for calculating the ramp. * @param prev_volume - Previous mix volume, used for calculating the ramp, * also applied to the input. + * @param prev_samples - Previous sample buffer. Used for depopping. * @param precision - Number of decimal bits for fixed point operations. */ void GenerateMixRampCommand(s32 node_id, s16 buffer_count, s16 input_index, s16 output_index, @@ -208,6 +209,7 @@ public: * @param volumes - Current mix volumes used for calculating the ramp. * @param prev_volumes - Previous mix volumes, used for calculating the ramp, * also applied to the input. + * @param prev_samples - Previous sample buffer. Used for depopping. * @param precision - Number of decimal bits for fixed point operations. */ void GenerateMixRampGroupedCommand(s32 node_id, s16 buffer_count, s16 input_index, @@ -297,11 +299,11 @@ public: /** * Generate a device sink command, adding it to the command list. * - * @param node_id - Node id of the voice this command is generated for. - * @param buffer_offset - Base mix buffer offset to use. - * @param sink_info - The sink_info to generate this command from. - * @session_id - System session id this command is generated from. - * @samples_buffer - The buffer to be sent to the sink if upsampling is not used. + * @param node_id - Node id of the voice this command is generated for. + * @param buffer_offset - Base mix buffer offset to use. + * @param sink_info - The sink_info to generate this command from. + * @param session_id - System session id this command is generated from. + * @param samples_buffer - The buffer to be sent to the sink if upsampling is not used. */ void GenerateDeviceSinkCommand(s32 node_id, s16 buffer_offset, SinkInfoBase& sink_info, u32 session_id, std::span samples_buffer); diff --git a/src/audio_core/renderer/command/command_generator.h b/src/audio_core/renderer/command/command_generator.h index d80d9b0d8..b3cd7b408 100644 --- a/src/audio_core/renderer/command/command_generator.h +++ b/src/audio_core/renderer/command/command_generator.h @@ -197,9 +197,9 @@ public: /** * Generate an I3DL2 reverb effect command. * - * @param buffer_offset - Base mix buffer offset to use. - * @param effect_info_base - I3DL2Reverb effect info. - * @param node_id - Node id of the mix this command is generated for. + * @param buffer_offset - Base mix buffer offset to use. + * @param effect_info - I3DL2Reverb effect info. + * @param node_id - Node id of the mix this command is generated for. */ void GenerateI3dl2ReverbEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id); @@ -207,18 +207,18 @@ public: /** * Generate an aux effect command. * - * @param buffer_offset - Base mix buffer offset to use. - * @param effect_info_base - Aux effect info. - * @param node_id - Node id of the mix this command is generated for. + * @param buffer_offset - Base mix buffer offset to use. + * @param effect_info - Aux effect info. + * @param node_id - Node id of the mix this command is generated for. */ void GenerateAuxCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id); /** * Generate a biquad filter effect command. * - * @param buffer_offset - Base mix buffer offset to use. - * @param effect_info_base - Aux effect info. - * @param node_id - Node id of the mix this command is generated for. + * @param buffer_offset - Base mix buffer offset to use. + * @param effect_info - Aux effect info. + * @param node_id - Node id of the mix this command is generated for. */ void GenerateBiquadFilterEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id); @@ -226,10 +226,10 @@ public: /** * Generate a light limiter effect command. * - * @param buffer_offset - Base mix buffer offset to use. - * @param effect_info_base - Limiter effect info. - * @param node_id - Node id of the mix this command is generated for. - * @param effect_index - Index for the statistics state. + * @param buffer_offset - Base mix buffer offset to use. + * @param effect_info - Limiter effect info. + * @param node_id - Node id of the mix this command is generated for. + * @param effect_index - Index for the statistics state. */ void GenerateLightLimiterEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id, u32 effect_index); @@ -238,21 +238,20 @@ public: * Generate a capture effect command. * Writes a mix buffer back to game memory. * - * @param buffer_offset - Base mix buffer offset to use. - * @param effect_info_base - Capture effect info. - * @param node_id - Node id of the mix this command is generated for. + * @param buffer_offset - Base mix buffer offset to use. + * @param effect_info - Capture effect info. + * @param node_id - Node id of the mix this command is generated for. */ void GenerateCaptureCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id); /** * Generate a compressor effect command. * - * @param buffer_offset - Base mix buffer offset to use. - * @param effect_info_base - Compressor effect info. - * @param node_id - Node id of the mix this command is generated for. + * @param buffer_offset - Base mix buffer offset to use. + * @param effect_info - Compressor effect info. + * @param node_id - Node id of the mix this command is generated for. */ - void GenerateCompressorCommand(const s16 buffer_offset, EffectInfoBase& effect_info, - const s32 node_id); + void GenerateCompressorCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id); /** * Generate all effect commands for a mix. @@ -318,8 +317,9 @@ public: * Generate a performance command. * Used to report performance metrics of the AudioRenderer back to the game. * - * @param buffer_offset - Base mix buffer offset to use. - * @param sink_info - Sink info to generate the commands from. + * @param node_id - Node ID of the mix this command is generated for + * @param state - Output state of the generated performance command + * @param entry_addresses - Addresses to be written */ void GeneratePerformanceCommand(s32 node_id, PerformanceState state, const PerformanceEntryAddresses& entry_addresses); diff --git a/src/audio_core/renderer/command/mix/mix_ramp.cpp b/src/audio_core/renderer/command/mix/mix_ramp.cpp index ffdafa1c8..d67123cd8 100644 --- a/src/audio_core/renderer/command/mix/mix_ramp.cpp +++ b/src/audio_core/renderer/command/mix/mix_ramp.cpp @@ -7,17 +7,7 @@ #include "common/logging/log.h" namespace AudioCore::AudioRenderer { -/** - * Mix input mix buffer into output mix buffer, with volume applied to the input. - * - * @tparam Q - Number of bits for fixed point operations. - * @param output - Output mix buffer. - * @param input - Input mix buffer. - * @param volume - Volume applied to the input. - * @param ramp - Ramp applied to volume every sample. - * @param sample_count - Number of samples to process. - * @return The final gained input sample, used for depopping. - */ + template s32 ApplyMixRamp(std::span output, std::span input, const f32 volume_, const f32 ramp_, const u32 sample_count) { @@ -40,10 +30,8 @@ s32 ApplyMixRamp(std::span output, std::span input, const f32 vo return sample.to_int(); } -template s32 ApplyMixRamp<15>(std::span, std::span, const f32, const f32, - const u32); -template s32 ApplyMixRamp<23>(std::span, std::span, const f32, const f32, - const u32); +template s32 ApplyMixRamp<15>(std::span, std::span, f32, f32, u32); +template s32 ApplyMixRamp<23>(std::span, std::span, f32, f32, u32); void MixRampCommand::Dump(const ADSP::CommandListProcessor& processor, std::string& string) { const auto ramp{(volume - prev_volume) / static_cast(processor.sample_count)}; diff --git a/src/audio_core/renderer/command/mix/mix_ramp.h b/src/audio_core/renderer/command/mix/mix_ramp.h index 770f57e80..52f74a273 100644 --- a/src/audio_core/renderer/command/mix/mix_ramp.h +++ b/src/audio_core/renderer/command/mix/mix_ramp.h @@ -61,13 +61,13 @@ struct MixRampCommand : ICommand { * @tparam Q - Number of bits for fixed point operations. * @param output - Output mix buffer. * @param input - Input mix buffer. - * @param volume - Volume applied to the input. - * @param ramp - Ramp applied to volume every sample. + * @param volume_ - Volume applied to the input. + * @param ramp_ - Ramp applied to volume every sample. * @param sample_count - Number of samples to process. * @return The final gained input sample, used for depopping. */ template -s32 ApplyMixRamp(std::span output, std::span input, const f32 volume_, - const f32 ramp_, const u32 sample_count); +s32 ApplyMixRamp(std::span output, std::span input, f32 volume_, f32 ramp_, + u32 sample_count); } // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/mix/mix_ramp_grouped.h b/src/audio_core/renderer/command/mix/mix_ramp_grouped.h index 027276e5a..3b0ce67ef 100644 --- a/src/audio_core/renderer/command/mix/mix_ramp_grouped.h +++ b/src/audio_core/renderer/command/mix/mix_ramp_grouped.h @@ -50,9 +50,9 @@ struct MixRampGroupedCommand : ICommand { std::array inputs; /// Output mix buffer indexes for each mix buffer std::array outputs; - /// Previous mix vloumes for each mix buffer + /// Previous mix volumes for each mix buffer std::array prev_volumes; - /// Current mix vloumes for each mix buffer + /// Current mix volumes for each mix buffer std::array volumes; /// Pointer to the previous sample buffer, used for depop CpuAddr previous_samples; diff --git a/src/audio_core/renderer/effect/effect_context.h b/src/audio_core/renderer/effect/effect_context.h index 85955bd9c..8f6d6e7d8 100644 --- a/src/audio_core/renderer/effect/effect_context.h +++ b/src/audio_core/renderer/effect/effect_context.h @@ -15,15 +15,15 @@ class EffectContext { public: /** * Initialize the effect context - * @param effect_infos List of effect infos for this context - * @param effect_count The number of effects in the list - * @param result_states_cpu The workbuffer of result states for the CPU for this context - * @param result_states_dsp The workbuffer of result states for the DSP for this context - * @param state_count The number of result states + * @param effect_infos_ - List of effect infos for this context + * @param effect_count_ - The number of effects in the list + * @param result_states_cpu_ - The workbuffer of result states for the CPU for this context + * @param result_states_dsp_ - The workbuffer of result states for the DSP for this context + * @param dsp_state_count - The number of result states */ - void Initialize(std::span effect_infos_, const u32 effect_count_, + void Initialize(std::span effect_infos_, u32 effect_count_, std::span result_states_cpu_, - std::span result_states_dsp_, const size_t dsp_state_count); + std::span result_states_dsp_, size_t dsp_state_count); /** * Get the EffectInfo for a given index diff --git a/src/audio_core/renderer/effect/effect_info_base.h b/src/audio_core/renderer/effect/effect_info_base.h index 8c9583878..8525fde05 100644 --- a/src/audio_core/renderer/effect/effect_info_base.h +++ b/src/audio_core/renderer/effect/effect_info_base.h @@ -291,7 +291,7 @@ public: * Update the info with new parameters, version 1. * * @param error_info - Used to write call result code. - * @param in_params - New parameters to update the info with. + * @param params - New parameters to update the info with. * @param pool_mapper - Pool for mapping buffers. */ virtual void Update(BehaviorInfo::ErrorInfo& error_info, @@ -305,7 +305,7 @@ public: * Update the info with new parameters, version 2. * * @param error_info - Used to write call result code. - * @param in_params - New parameters to update the info with. + * @param params - New parameters to update the info with. * @param pool_mapper - Pool for mapping buffers. */ virtual void Update(BehaviorInfo::ErrorInfo& error_info, diff --git a/src/audio_core/renderer/memory/address_info.h b/src/audio_core/renderer/memory/address_info.h index 4cfefea8e..bb5c930e1 100644 --- a/src/audio_core/renderer/memory/address_info.h +++ b/src/audio_core/renderer/memory/address_info.h @@ -19,8 +19,8 @@ public: /** * Setup a new AddressInfo. * - * @param cpu_address - The CPU address of this region. - * @param size - The size of this region. + * @param cpu_address_ - The CPU address of this region. + * @param size_ - The size of this region. */ void Setup(CpuAddr cpu_address_, u64 size_) { cpu_address = cpu_address_; @@ -42,7 +42,6 @@ public: * Assign this region to a memory pool. * * @param memory_pool_ - Memory pool to assign. - * @return The CpuAddr address of this region. */ void SetPool(MemoryPoolInfo* memory_pool_) { memory_pool = memory_pool_; diff --git a/src/audio_core/renderer/nodes/node_states.h b/src/audio_core/renderer/nodes/node_states.h index a1e0958a2..c0fced56f 100644 --- a/src/audio_core/renderer/nodes/node_states.h +++ b/src/audio_core/renderer/nodes/node_states.h @@ -112,11 +112,11 @@ public: /** * Initialize the node states. * - * @param buffer - The workbuffer to use. Unused. + * @param buffer_ - The workbuffer to use. Unused. * @param node_buffer_size - The size of the workbuffer. Unused. * @param count - The number of nodes in the graph. */ - void Initialize(std::span nodes, u64 node_buffer_size, u32 count); + void Initialize(std::span buffer_, u64 node_buffer_size, u32 count); /** * Sort the graph. Only calls DepthFirstSearch. diff --git a/src/audio_core/renderer/performance/performance_manager.h b/src/audio_core/renderer/performance/performance_manager.h index b82176bef..b65caa9b6 100644 --- a/src/audio_core/renderer/performance/performance_manager.h +++ b/src/audio_core/renderer/performance/performance_manager.h @@ -73,7 +73,8 @@ public: * Calculate the required size for the performance workbuffer. * * @param behavior - Check which version is supported. - * @param params - Input parameters. + * @param params - Input parameters. + * * @return Required workbuffer size. */ static u64 GetRequiredBufferSizeForPerformanceMetricsPerFrame( @@ -104,7 +105,7 @@ public: * @param workbuffer - Workbuffer to use for performance frames. * @param workbuffer_size - Size of the workbuffer. * @param params - Input parameters. - * @param behavior - Behaviour to check version and data format. + * @param behavior - Behaviour to check version and data format. * @param memory_pool - Used to translate the workbuffer address for the DSP. */ virtual void Initialize(std::span workbuffer, u64 workbuffer_size, @@ -160,7 +161,8 @@ public: * workbuffer, to be written by the AudioRenderer. * * @param addresses - Filled with pointers to the new detail, which should be passed - * to the AudioRenderer with Performance commands to be written. + * to the AudioRenderer with Performance commands to be written. + * @param detail_type - Performance detail type. * @param entry_type - The type of this detail. See PerformanceEntryType * @param node_id - Node id for this detail. * @return True if a new detail was created and the offsets are valid, otherwise false. diff --git a/src/audio_core/renderer/upsampler/upsampler_manager.h b/src/audio_core/renderer/upsampler/upsampler_manager.h index 70cd42b08..83c697c0c 100644 --- a/src/audio_core/renderer/upsampler/upsampler_manager.h +++ b/src/audio_core/renderer/upsampler/upsampler_manager.h @@ -27,7 +27,7 @@ public: /** * Free the given upsampler. * - * @param The upsampler to be freed. + * @param info The upsampler to be freed. */ void Free(UpsamplerInfo* info); diff --git a/src/audio_core/renderer/voice/voice_info.h b/src/audio_core/renderer/voice/voice_info.h index 896723e0c..930180895 100644 --- a/src/audio_core/renderer/voice/voice_info.h +++ b/src/audio_core/renderer/voice/voice_info.h @@ -185,7 +185,8 @@ public: /** * Does this voice ned an update? * - * @param params - Input parametetrs to check matching. + * @param params - Input parameters to check matching. + * * @return True if this voice needs an update, otherwise false. */ bool ShouldUpdateParameters(const InParameter& params) const; @@ -194,9 +195,9 @@ public: * Update the parameters of this voice. * * @param error_info - Output error code. - * @param params - Input parametters to udpate from. + * @param params - Input parameters to update from. * @param pool_mapper - Used to map buffers. - * @param behavior - behavior to check supported features. + * @param behavior - behavior to check supported features. */ void UpdateParameters(BehaviorInfo::ErrorInfo& error_info, const InParameter& params, const PoolMapper& pool_mapper, const BehaviorInfo& behavior); @@ -218,12 +219,12 @@ public: /** * Update all wavebuffers. * - * @param error_infos - Output 2D array of errors, 2 per wavebuffer. - * @param error_count - Number of errors provided. Unused. - * @param params - Input parametters to be used for the update. + * @param error_infos - Output 2D array of errors, 2 per wavebuffer. + * @param error_count - Number of errors provided. Unused. + * @param params - Input parameters to be used for the update. * @param voice_states - The voice states for each channel in this voice to be updated. - * @param pool_mapper - Used to map the wavebuffers. - * @param behavior - Used to check for supported features. + * @param pool_mapper - Used to map the wavebuffers. + * @param behavior - Used to check for supported features. */ void UpdateWaveBuffers(std::span> error_infos, u32 error_count, const InParameter& params, @@ -233,13 +234,13 @@ public: /** * Update a wavebuffer. * - * @param error_infos - Output array of errors. + * @param error_info - Output array of errors. * @param wave_buffer - The wavebuffer to be updated. * @param wave_buffer_internal - Input parametters to be used for the update. * @param sample_format - Sample format of the wavebuffer. * @param valid - Is this wavebuffer valid? * @param pool_mapper - Used to map the wavebuffers. - * @param behavior - Used to check for supported features. + * @param behavior - Used to check for supported features. */ void UpdateWaveBuffer(std::span error_info, WaveBuffer& wave_buffer, const WaveBufferInternal& wave_buffer_internal, @@ -276,7 +277,7 @@ public: /** * Check if this voice has any mixing connections. * - * @return True if this voice participes in mixing, otherwise false. + * @return True if this voice participates in mixing, otherwise false. */ bool HasAnyConnection() const; @@ -301,7 +302,8 @@ public: /** * Update this voice on command generation. * - * @param voice_states - Voice states for these wavebuffers. + * @param voice_context - Voice context for these wavebuffers. + * * @return True if this voice should be generated, otherwise false. */ bool UpdateForCommandGeneration(VoiceContext& voice_context); diff --git a/src/audio_core/sink/cubeb_sink.h b/src/audio_core/sink/cubeb_sink.h index 91a6480fa..a450a3343 100644 --- a/src/audio_core/sink/cubeb_sink.h +++ b/src/audio_core/sink/cubeb_sink.h @@ -34,8 +34,7 @@ public: * May differ from the device's channel count. * @param name - Name of this stream. * @param type - Type of this stream, render/in/out. - * @param event - Audio render only, a signal used to prevent the renderer running too - * fast. + * * @return A pointer to the created SinkStream */ SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels, @@ -101,7 +100,7 @@ private: }; /** - * Get a list of conencted devices from Cubeb. + * Get a list of connected devices from Cubeb. * * @param capture - Return input (capture) devices if true, otherwise output devices. */ diff --git a/src/audio_core/sink/sdl2_sink.h b/src/audio_core/sink/sdl2_sink.h index 57de9b6c2..b00f55849 100644 --- a/src/audio_core/sink/sdl2_sink.h +++ b/src/audio_core/sink/sdl2_sink.h @@ -32,8 +32,7 @@ public: * May differ from the device's channel count. * @param name - Name of this stream. * @param type - Type of this stream, render/in/out. - * @param event - Audio render only, a signal used to prevent the renderer running too - * fast. + * * @return A pointer to the created SinkStream */ SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels, @@ -92,7 +91,7 @@ private: }; /** - * Get a list of conencted devices from Cubeb. + * Get a list of connected devices from SDL. * * @param capture - Return input (capture) devices if true, otherwise output devices. */ diff --git a/src/audio_core/sink/sink.h b/src/audio_core/sink/sink.h index 43d99b62e..b3f65792d 100644 --- a/src/audio_core/sink/sink.h +++ b/src/audio_core/sink/sink.h @@ -58,8 +58,7 @@ public: * May differ from the device's channel count. * @param name - Name of this stream. * @param type - Type of this stream, render/in/out. - * @param event - Audio render only, a signal used to prevent the renderer running too - * fast. + * * @return A pointer to the created SinkStream */ virtual SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels, diff --git a/src/audio_core/sink/sink_stream.h b/src/audio_core/sink/sink_stream.h index db7cff45e..5e9a86a10 100644 --- a/src/audio_core/sink/sink_stream.h +++ b/src/audio_core/sink/sink_stream.h @@ -214,7 +214,7 @@ protected: Core::System& system; /// Type of this stream StreamType type; - /// Set by the audio render/in/out systen which uses this stream + /// Set by the audio render/in/out system which uses this stream u32 system_channels{2}; /// Channels supported by hardware u32 device_channels{2}; From 9554c67809a674cad2de1482634da2f79de90474 Mon Sep 17 00:00:00 2001 From: Kyle Kienapfel Date: Mon, 12 Sep 2022 08:56:24 -0700 Subject: [PATCH 63/78] UI: move icons from default into colorful theme. colorful theme has been default theme for awhile. having colorful theme try and grab icons from other theme doesn't work on Linux. Also adding two additional icons, info is to hint to the user that they should hit verify after pasting in a token, sync is to show that the verification is occurring. --- .reuse/dep5 | 6 +++-- .../icons/16x16/checked.png | Bin .../icons/16x16/failed.png | Bin dist/qt_themes/colorful/icons/16x16/info.png | Bin 0 -> 428 bytes dist/qt_themes/colorful/icons/16x16/sync.png | Bin 0 -> 548 bytes .../icons/16x16/view-refresh.png | Bin .../icons/48x48/no_avatar.png | Bin dist/qt_themes/colorful/icons/index.theme | 1 - dist/qt_themes/colorful/style.qrc | 6 +++++ dist/qt_themes/colorful_dark/style.qrc | 10 --------- dist/qt_themes/default/default.qrc | 11 ++++----- dist/qt_themes/default/icons/index.theme | 3 ++- dist/qt_themes/qdarkstyle/icons/index.theme | 4 ++-- .../icons/index.theme | 2 +- src/yuzu/configuration/configure_web.cpp | 21 +++++++++++------- 15 files changed, 32 insertions(+), 32 deletions(-) rename dist/qt_themes/{default => colorful}/icons/16x16/checked.png (100%) rename dist/qt_themes/{default => colorful}/icons/16x16/failed.png (100%) create mode 100644 dist/qt_themes/colorful/icons/16x16/info.png create mode 100644 dist/qt_themes/colorful/icons/16x16/sync.png rename dist/qt_themes/{default => colorful}/icons/16x16/view-refresh.png (100%) rename dist/qt_themes/{default => colorful}/icons/48x48/no_avatar.png (100%) diff --git a/.reuse/dep5 b/.reuse/dep5 index 5ba017494..9a90f9eb6 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -52,6 +52,8 @@ Files: dist/qt_themes/colorful/icons/16x16/lock.png dist/qt_themes/colorful/icons/48x48/chip.png dist/qt_themes/colorful/icons/48x48/folder.png dist/qt_themes/colorful_dark/icons/16x16/lock.png + dist/qt_themes/colorful/icons/16x16/info.png + dist/qt_themes/colorful/icons/16x16/sync.png Copyright: Icons8 License: MIT Comment: https://github.com/icons8/flat-color-icons @@ -68,8 +70,8 @@ Copyright: Ionic (http://ionic.io/) License: MIT Files: dist/qt_themes/colorful/icons/48x48/star.png - dist/qt_themes/default/icons/16x16/checked.png - dist/qt_themes/default/icons/16x16/failed.png + dist/qt_themes/colorful/icons/16x16/checked.png + dist/qt_themes/colorful/icons/16x16/failed.png Copyright: SVG Repo License: CC0-1.0 diff --git a/dist/qt_themes/default/icons/16x16/checked.png b/dist/qt_themes/colorful/icons/16x16/checked.png similarity index 100% rename from dist/qt_themes/default/icons/16x16/checked.png rename to dist/qt_themes/colorful/icons/16x16/checked.png diff --git a/dist/qt_themes/default/icons/16x16/failed.png b/dist/qt_themes/colorful/icons/16x16/failed.png similarity index 100% rename from dist/qt_themes/default/icons/16x16/failed.png rename to dist/qt_themes/colorful/icons/16x16/failed.png diff --git a/dist/qt_themes/colorful/icons/16x16/info.png b/dist/qt_themes/colorful/icons/16x16/info.png new file mode 100644 index 0000000000000000000000000000000000000000..8b9330f4c85c308cdded20086095eca71bff2490 GIT binary patch literal 428 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`EX7WqAsj$Z!;#Vf2>u z-+(Y~}U&3=E8#o-U3d z7QJgH8u~FginQ&&c`)E?nOCaf8o37U40pz*MGd(eiffGQZwJP{p>8~GY@h%q`&&~OAOXGFsRnxV*wkg^)@F=9p zo}INUax;?(f5)51wOyYYPUbHcy|aAzUup9frQY4*-|nWhs!F1a9oR>wN)QsJCwC+ERJ$5_$Y=Gq!R||%KE`h|Li$FVdQ&MBb@058+682|tP literal 0 HcmV?d00001 diff --git a/dist/qt_themes/colorful/icons/16x16/sync.png b/dist/qt_themes/colorful/icons/16x16/sync.png new file mode 100644 index 0000000000000000000000000000000000000000..0d57789c3a5167710d1746423610dd994777a5a6 GIT binary patch literal 548 zcmV+<0^9wGP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGZhyVZuhylMZe;xn;0l7&;K~y+Tl~YS7 zgi#d!?wuizg}1V0EjtUcRK$YC?CfOYH8Tx1EXYbE&m<+;%0|S}%49*2orsN;x2$`-P~4WeGn;lS!7 zd>1gHgBYe9BOmB(!oeA>!aG*s^VdN;E6luP>IJ`Wf;ng5r$7)RG~+y2CBteS&+dr} z6G8=yzHuGAu~@U3!M-ivwSCNkHMZJmlTyG0^({>i;P{za%iJSvPu!Uw3K4yMPB+)i$Z z@7{vJB-<)PLT|Ib1{+HOyKq02DD&t{NbKtxbNEPNR5DwceKbknXytsVvi$(dg!u-} zC9Z7Me%;GY&Sk3TR>IL;$W25ML$@RmGIJ}LMTXQYmL(`idjtFz)^a icons/index.theme + icons/16x16/checked.png icons/16x16/connected.png icons/16x16/connected_notification.png icons/16x16/disconnected.png + icons/16x16/failed.png + icons/16x16/info.png icons/16x16/lock.png + icons/16x16/sync.png + icons/16x16/view-refresh.png icons/48x48/bad_folder.png icons/48x48/chip.png icons/48x48/folder.png icons/48x48/list-add.png + icons/48x48/no_avatar.png icons/48x48/sd_card.png icons/48x48/star.png icons/256x256/plus_folder.png diff --git a/dist/qt_themes/colorful_dark/style.qrc b/dist/qt_themes/colorful_dark/style.qrc index 9853fd438..72451ef02 100644 --- a/dist/qt_themes/colorful_dark/style.qrc +++ b/dist/qt_themes/colorful_dark/style.qrc @@ -5,19 +5,9 @@ SPDX-License-Identifier: GPL-2.0-or-later - ../colorful/icons/16x16/connected.png - ../colorful/icons/16x16/connected_notification.png - ../colorful/icons/16x16/disconnected.png icons/index.theme icons/16x16/lock.png icons/16x16/view-refresh.png - ../colorful/icons/48x48/bad_folder.png - ../colorful/icons/48x48/chip.png - ../colorful/icons/48x48/folder.png - ../qdarkstyle/icons/48x48/no_avatar.png - ../colorful/icons/48x48/list-add.png - ../colorful/icons/48x48/sd_card.png - ../colorful/icons/256x256/plus_folder.png diff --git a/dist/qt_themes/default/default.qrc b/dist/qt_themes/default/default.qrc index a07f2a9c1..2e01a3434 100644 --- a/dist/qt_themes/default/default.qrc +++ b/dist/qt_themes/default/default.qrc @@ -5,23 +5,20 @@ SPDX-License-Identifier: GPL-2.0-or-later + icons/index.theme - icons/16x16/checked.png - icons/16x16/failed.png - icons/16x16/lock.png icons/16x16/connected.png - icons/16x16/disconnected.png icons/16x16/connected_notification.png - icons/16x16/view-refresh.png + icons/16x16/disconnected.png + icons/16x16/lock.png icons/48x48/bad_folder.png icons/48x48/chip.png icons/48x48/folder.png - icons/48x48/no_avatar.png icons/48x48/list-add.png icons/48x48/sd_card.png icons/48x48/star.png - icons/256x256/yuzu.png icons/256x256/plus_folder.png + icons/256x256/yuzu.png style.qss diff --git a/dist/qt_themes/default/icons/index.theme b/dist/qt_themes/default/icons/index.theme index 1edbe6408..21b35e3e3 100644 --- a/dist/qt_themes/default/icons/index.theme +++ b/dist/qt_themes/default/icons/index.theme @@ -1,6 +1,7 @@ [Icon Theme] Name=default Comment=default theme +Inherits=colorful Directories=16x16,48x48,256x256 [16x16] @@ -10,4 +11,4 @@ Size=16 Size=48 [256x256] -Size=256 \ No newline at end of file +Size=256 diff --git a/dist/qt_themes/qdarkstyle/icons/index.theme b/dist/qt_themes/qdarkstyle/icons/index.theme index d1e12f3ef..502717617 100644 --- a/dist/qt_themes/qdarkstyle/icons/index.theme +++ b/dist/qt_themes/qdarkstyle/icons/index.theme @@ -1,7 +1,7 @@ [Icon Theme] Name=qdarkstyle Comment=dark theme -Inherits=default +Inherits=colorful Directories=16x16,48x48,256x256 [16x16] @@ -11,4 +11,4 @@ Size=16 Size=48 [256x256] -Size=256 \ No newline at end of file +Size=256 diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/icons/index.theme b/dist/qt_themes/qdarkstyle_midnight_blue/icons/index.theme index 447a6c8be..20f9f6d63 100644 --- a/dist/qt_themes/qdarkstyle_midnight_blue/icons/index.theme +++ b/dist/qt_themes/qdarkstyle_midnight_blue/icons/index.theme @@ -1,7 +1,7 @@ [Icon Theme] Name=qdarkstyle_midnight_blue Comment=dark theme -Inherits=default +Inherits=colorful Directories=16x16,48x48,256x256 [16x16] diff --git a/src/yuzu/configuration/configure_web.cpp b/src/yuzu/configuration/configure_web.cpp index d668c992b..ab526e4ca 100644 --- a/src/yuzu/configuration/configure_web.cpp +++ b/src/yuzu/configuration/configure_web.cpp @@ -128,20 +128,25 @@ void ConfigureWeb::RefreshTelemetryID() { void ConfigureWeb::OnLoginChanged() { if (ui->edit_token->text().isEmpty()) { user_verified = true; - - const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("checked")).pixmap(16); - ui->label_token_verified->setPixmap(pixmap); + // Empty = no icon + ui->label_token_verified->setPixmap(QPixmap()); + ui->label_token_verified->setToolTip(QString()); } else { user_verified = false; - const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("failed")).pixmap(16); + // Show an info icon if it's been changed, clearer than showing failure + const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("info")).pixmap(16); ui->label_token_verified->setPixmap(pixmap); + ui->label_token_verified->setToolTip( + tr("Unverified, please click Verify before saving configuration", "Tooltip")); } } void ConfigureWeb::VerifyLogin() { ui->button_verify_login->setDisabled(true); ui->button_verify_login->setText(tr("Verifying...")); + ui->label_token_verified->setPixmap(QIcon::fromTheme(QStringLiteral("sync")).pixmap(16)); + ui->label_token_verified->setToolTip(tr("Verifying...")); verify_watcher.setFuture(QtConcurrent::run( [username = UsernameFromDisplayToken(ui->edit_token->text().toStdString()), token = TokenFromDisplayToken(ui->edit_token->text().toStdString())] { @@ -155,13 +160,13 @@ void ConfigureWeb::OnLoginVerified() { if (verify_watcher.result()) { user_verified = true; - const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("checked")).pixmap(16); - ui->label_token_verified->setPixmap(pixmap); + ui->label_token_verified->setPixmap(QIcon::fromTheme(QStringLiteral("checked")).pixmap(16)); + ui->label_token_verified->setToolTip(tr("Verified", "Tooltip")); ui->username->setText( QString::fromStdString(UsernameFromDisplayToken(ui->edit_token->text().toStdString()))); } else { - const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("failed")).pixmap(16); - ui->label_token_verified->setPixmap(pixmap); + ui->label_token_verified->setPixmap(QIcon::fromTheme(QStringLiteral("failed")).pixmap(16)); + ui->label_token_verified->setToolTip(tr("Verification failed", "Tooltip")); ui->username->setText(tr("Unspecified")); QMessageBox::critical(this, tr("Verification failed"), tr("Verification failed. Check that you have entered your token " From a278fa6e2a8e06bf20b608ec6a79a53c32321d9b Mon Sep 17 00:00:00 2001 From: Lioncash Date: Fri, 16 Sep 2022 09:29:28 -0400 Subject: [PATCH 64/78] device_session: Pass arguments by const-ref in relevant functions These functions don't modify the passed in audio buffers, so we can signify that in the interface. --- src/audio_core/device/audio_buffers.h | 2 +- src/audio_core/device/device_session.cpp | 6 +++--- src/audio_core/device/device_session.h | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/audio_core/device/audio_buffers.h b/src/audio_core/device/audio_buffers.h index 3ecbbb63f..4918a61c7 100644 --- a/src/audio_core/device/audio_buffers.h +++ b/src/audio_core/device/audio_buffers.h @@ -93,7 +93,7 @@ public: * * @return Is the buffer was released. */ - bool ReleaseBuffers(Core::Timing::CoreTiming& core_timing, DeviceSession& session) { + bool ReleaseBuffers(const Core::Timing::CoreTiming& core_timing, const DeviceSession& session) { std::scoped_lock l{lock}; bool buffer_released{false}; while (registered_count > 0) { diff --git a/src/audio_core/device/device_session.cpp b/src/audio_core/device/device_session.cpp index c71c3a376..6cde33f93 100644 --- a/src/audio_core/device/device_session.cpp +++ b/src/audio_core/device/device_session.cpp @@ -73,7 +73,7 @@ void DeviceSession::Stop() { } } -void DeviceSession::AppendBuffers(std::span buffers) const { +void DeviceSession::AppendBuffers(std::span buffers) const { for (size_t i = 0; i < buffers.size(); i++) { Sink::SinkBuffer new_buffer{ .frames = buffers[i].size / (channel_count * sizeof(s16)), @@ -93,14 +93,14 @@ void DeviceSession::AppendBuffers(std::span buffers) const { } } -void DeviceSession::ReleaseBuffer(AudioBuffer& buffer) const { +void DeviceSession::ReleaseBuffer(const AudioBuffer& buffer) const { if (type == Sink::StreamType::In) { auto samples{stream->ReleaseBuffer(buffer.size / sizeof(s16))}; system.Memory().WriteBlockUnsafe(buffer.samples, samples.data(), buffer.size); } } -bool DeviceSession::IsBufferConsumed(AudioBuffer& buffer) const { +bool DeviceSession::IsBufferConsumed(const AudioBuffer& buffer) const { return played_sample_count >= buffer.end_timestamp; } diff --git a/src/audio_core/device/device_session.h b/src/audio_core/device/device_session.h index 53b649c61..74f4dc085 100644 --- a/src/audio_core/device/device_session.h +++ b/src/audio_core/device/device_session.h @@ -62,14 +62,14 @@ public: * * @param buffers - The buffers to play. */ - void AppendBuffers(std::span buffers) const; + void AppendBuffers(std::span buffers) const; /** * (Audio In only) Pop samples from the backend, and write them back to this buffer's address. * * @param buffer - The buffer to write to. */ - void ReleaseBuffer(AudioBuffer& buffer) const; + void ReleaseBuffer(const AudioBuffer& buffer) const; /** * Check if the buffer for the given tag has been consumed by the backend. @@ -78,7 +78,7 @@ public: * * @return true if the buffer has been consumed, otherwise false. */ - bool IsBufferConsumed(AudioBuffer& buffer) const; + bool IsBufferConsumed(const AudioBuffer& buffer) const; /** * Start this device session, starting the backend stream. From cb2a33babc831fa39c86663714f0951743530b2b Mon Sep 17 00:00:00 2001 From: Lioncash Date: Fri, 16 Sep 2022 09:32:55 -0400 Subject: [PATCH 65/78] device_session: Convert for loop into ranged for in AppendBuffers Simplifies the indexing code a little bit. --- src/audio_core/device/device_session.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/audio_core/device/device_session.cpp b/src/audio_core/device/device_session.cpp index 6cde33f93..995060414 100644 --- a/src/audio_core/device/device_session.cpp +++ b/src/audio_core/device/device_session.cpp @@ -74,11 +74,11 @@ void DeviceSession::Stop() { } void DeviceSession::AppendBuffers(std::span buffers) const { - for (size_t i = 0; i < buffers.size(); i++) { + for (const auto& buffer : buffers) { Sink::SinkBuffer new_buffer{ - .frames = buffers[i].size / (channel_count * sizeof(s16)), + .frames = buffer.size / (channel_count * sizeof(s16)), .frames_played = 0, - .tag = buffers[i].tag, + .tag = buffer.tag, .consumed = false, }; @@ -86,8 +86,8 @@ void DeviceSession::AppendBuffers(std::span buffers) const { std::vector samples{}; stream->AppendBuffer(new_buffer, samples); } else { - std::vector samples(buffers[i].size / sizeof(s16)); - system.Memory().ReadBlockUnsafe(buffers[i].samples, samples.data(), buffers[i].size); + std::vector samples(buffer.size / sizeof(s16)); + system.Memory().ReadBlockUnsafe(buffer.samples, samples.data(), buffer.size); stream->AppendBuffer(new_buffer, samples); } } From e9109cb5f214218909c978b7fbd5a7cb71bdf890 Mon Sep 17 00:00:00 2001 From: Lioncash Date: Fri, 16 Sep 2022 09:36:00 -0400 Subject: [PATCH 66/78] audio_buffers: Pass by const-ref in AppendBuffers This function doesn't modify the passed in buffer, so we can make that explicit. --- src/audio_core/device/audio_buffers.h | 2 +- src/audio_core/in/audio_in_system.cpp | 14 ++++++++------ src/audio_core/out/audio_out_system.cpp | 14 ++++++++------ 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/audio_core/device/audio_buffers.h b/src/audio_core/device/audio_buffers.h index 4918a61c7..3dae1a3b7 100644 --- a/src/audio_core/device/audio_buffers.h +++ b/src/audio_core/device/audio_buffers.h @@ -36,7 +36,7 @@ public: * * @param buffer - The new buffer. */ - void AppendBuffer(AudioBuffer& buffer) { + void AppendBuffer(const AudioBuffer& buffer) { std::scoped_lock l{lock}; buffers[appended_index] = buffer; appended_count++; diff --git a/src/audio_core/in/audio_in_system.cpp b/src/audio_core/in/audio_in_system.cpp index 7e80ba03c..9c6039aea 100644 --- a/src/audio_core/in/audio_in_system.cpp +++ b/src/audio_core/in/audio_in_system.cpp @@ -114,12 +114,14 @@ bool System::AppendBuffer(const AudioInBuffer& buffer, const u64 tag) { } 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}; + const 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 8941b09a0..ae605f65b 100644 --- a/src/audio_core/out/audio_out_system.cpp +++ b/src/audio_core/out/audio_out_system.cpp @@ -113,12 +113,14 @@ bool System::AppendBuffer(const AudioOutBuffer& buffer, u64 tag) { } 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}; + const 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(); From d1f3c121a04fdc1e4d8840ef6a5bbb65212ed7a7 Mon Sep 17 00:00:00 2001 From: Lioncash Date: Fri, 16 Sep 2022 09:38:17 -0400 Subject: [PATCH 67/78] audio_out: Mark several functions as const These don't affect class state, so we can mark them as such. --- src/audio_core/out/audio_out.cpp | 8 ++++---- src/audio_core/out/audio_out.h | 8 ++++---- src/audio_core/out/audio_out_system.cpp | 9 +++++---- src/audio_core/out/audio_out_system.h | 8 ++++---- 4 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/audio_core/out/audio_out.cpp b/src/audio_core/out/audio_out.cpp index 9a8d8a742..d3ee4f0eb 100644 --- a/src/audio_core/out/audio_out.cpp +++ b/src/audio_core/out/audio_out.cpp @@ -72,7 +72,7 @@ Kernel::KReadableEvent& Out::GetBufferEvent() { return event->GetReadableEvent(); } -f32 Out::GetVolume() { +f32 Out::GetVolume() const { std::scoped_lock l{parent_mutex}; return system.GetVolume(); } @@ -82,17 +82,17 @@ void Out::SetVolume(const f32 volume) { system.SetVolume(volume); } -bool Out::ContainsAudioBuffer(const u64 tag) { +bool Out::ContainsAudioBuffer(const u64 tag) const { std::scoped_lock l{parent_mutex}; return system.ContainsAudioBuffer(tag); } -u32 Out::GetBufferCount() { +u32 Out::GetBufferCount() const { std::scoped_lock l{parent_mutex}; return system.GetBufferCount(); } -u64 Out::GetPlayedSampleCount() { +u64 Out::GetPlayedSampleCount() const { std::scoped_lock l{parent_mutex}; return system.GetPlayedSampleCount(); } diff --git a/src/audio_core/out/audio_out.h b/src/audio_core/out/audio_out.h index f6b921645..946f345c6 100644 --- a/src/audio_core/out/audio_out.h +++ b/src/audio_core/out/audio_out.h @@ -102,7 +102,7 @@ public: * * @return The current volume. */ - f32 GetVolume(); + f32 GetVolume() const; /** * Set the system volume. @@ -117,21 +117,21 @@ public: * @param tag - The tag to search for. * @return True if the buffer is in the system, otherwise false. */ - bool ContainsAudioBuffer(u64 tag); + bool ContainsAudioBuffer(u64 tag) const; /** * Get the maximum number of buffers. * * @return The maximum number of buffers. */ - u32 GetBufferCount(); + u32 GetBufferCount() const; /** * Get the total played sample count for this audio out. * * @return The played sample count. */ - u64 GetPlayedSampleCount(); + u64 GetPlayedSampleCount() const; private: /// The AudioOut::Manager this audio out is registered with diff --git a/src/audio_core/out/audio_out_system.cpp b/src/audio_core/out/audio_out_system.cpp index ae605f65b..8b907590a 100644 --- a/src/audio_core/out/audio_out_system.cpp +++ b/src/audio_core/out/audio_out_system.cpp @@ -27,11 +27,12 @@ void System::Finalize() { buffer_event->GetWritableEvent().Signal(); } -std::string_view System::GetDefaultOutputDeviceName() { +std::string_view System::GetDefaultOutputDeviceName() const { return "DeviceOut"; } -Result System::IsConfigValid(std::string_view device_name, const AudioOutParameter& in_params) { +Result System::IsConfigValid(std::string_view device_name, + const AudioOutParameter& in_params) const { if ((device_name.size() > 0) && (device_name != GetDefaultOutputDeviceName())) { return Service::Audio::ERR_INVALID_DEVICE_NAME; } @@ -200,11 +201,11 @@ void System::SetVolume(const f32 volume_) { session->SetVolume(volume_); } -bool System::ContainsAudioBuffer(const u64 tag) { +bool System::ContainsAudioBuffer(const u64 tag) const { return buffers.ContainsBuffer(tag); } -u32 System::GetBufferCount() { +u32 System::GetBufferCount() const { return buffers.GetAppendedRegisteredCount(); } diff --git a/src/audio_core/out/audio_out_system.h b/src/audio_core/out/audio_out_system.h index 205ead861..0817b2f37 100644 --- a/src/audio_core/out/audio_out_system.h +++ b/src/audio_core/out/audio_out_system.h @@ -68,7 +68,7 @@ public: * * @return The default audio output device name. */ - std::string_view GetDefaultOutputDeviceName(); + std::string_view GetDefaultOutputDeviceName() const; /** * Is the given initialize config valid? @@ -77,7 +77,7 @@ public: * @param in_params - Input parameters, see AudioOutParameter. * @return Result code. */ - Result IsConfigValid(std::string_view device_name, const AudioOutParameter& in_params); + Result IsConfigValid(std::string_view device_name, const AudioOutParameter& in_params) const; /** * Initialize this system. @@ -209,14 +209,14 @@ public: * @param tag - Unique tag to search for. * @return True if the buffer is in the system, otherwise false. */ - bool ContainsAudioBuffer(u64 tag); + bool ContainsAudioBuffer(u64 tag) const; /** * Get the maximum number of usable buffers (default 32). * * @return The number of buffers. */ - u32 GetBufferCount(); + u32 GetBufferCount() const; /** * Get the total number of samples played by this system. From 7a5d235d944bb3837abb0be649303a5c15ea36f5 Mon Sep 17 00:00:00 2001 From: Lioncash Date: Fri, 16 Sep 2022 09:40:41 -0400 Subject: [PATCH 68/78] audio_in: Mark several functions as const These functions don't modify class state, so we can mark them as such --- src/audio_core/in/audio_in.cpp | 8 ++++---- src/audio_core/in/audio_in.h | 8 ++++---- src/audio_core/in/audio_in_system.cpp | 10 +++++----- src/audio_core/in/audio_in_system.h | 10 +++++----- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/audio_core/in/audio_in.cpp b/src/audio_core/in/audio_in.cpp index c946895d6..91ccd5ad7 100644 --- a/src/audio_core/in/audio_in.cpp +++ b/src/audio_core/in/audio_in.cpp @@ -72,7 +72,7 @@ Kernel::KReadableEvent& In::GetBufferEvent() { return event->GetReadableEvent(); } -f32 In::GetVolume() { +f32 In::GetVolume() const { std::scoped_lock l{parent_mutex}; return system.GetVolume(); } @@ -82,17 +82,17 @@ void In::SetVolume(f32 volume) { system.SetVolume(volume); } -bool In::ContainsAudioBuffer(u64 tag) { +bool In::ContainsAudioBuffer(u64 tag) const { std::scoped_lock l{parent_mutex}; return system.ContainsAudioBuffer(tag); } -u32 In::GetBufferCount() { +u32 In::GetBufferCount() const { std::scoped_lock l{parent_mutex}; return system.GetBufferCount(); } -u64 In::GetPlayedSampleCount() { +u64 In::GetPlayedSampleCount() const { std::scoped_lock l{parent_mutex}; return system.GetPlayedSampleCount(); } diff --git a/src/audio_core/in/audio_in.h b/src/audio_core/in/audio_in.h index 6253891d5..092ab7236 100644 --- a/src/audio_core/in/audio_in.h +++ b/src/audio_core/in/audio_in.h @@ -102,7 +102,7 @@ public: * * @return The current volume. */ - f32 GetVolume(); + f32 GetVolume() const; /** * Set the system volume. @@ -117,21 +117,21 @@ public: * @param tag - The tag to search for. * @return True if the buffer is in the system, otherwise false. */ - bool ContainsAudioBuffer(u64 tag); + bool ContainsAudioBuffer(u64 tag) const; /** * Get the maximum number of buffers. * * @return The maximum number of buffers. */ - u32 GetBufferCount(); + u32 GetBufferCount() const; /** * Get the total played sample count for this audio in. * * @return The played sample count. */ - u64 GetPlayedSampleCount(); + u64 GetPlayedSampleCount() const; private: /// The AudioIn::Manager this audio in is registered with diff --git a/src/audio_core/in/audio_in_system.cpp b/src/audio_core/in/audio_in_system.cpp index 9c6039aea..e7f918a47 100644 --- a/src/audio_core/in/audio_in_system.cpp +++ b/src/audio_core/in/audio_in_system.cpp @@ -34,16 +34,16 @@ size_t System::GetSessionId() const { return session_id; } -std::string_view System::GetDefaultDeviceName() { +std::string_view System::GetDefaultDeviceName() const { return "BuiltInHeadset"; } -std::string_view System::GetDefaultUacDeviceName() { +std::string_view System::GetDefaultUacDeviceName() const { return "Uac"; } Result System::IsConfigValid(const std::string_view device_name, - const AudioInParameter& in_params) { + const AudioInParameter& in_params) const { if ((device_name.size() > 0) && (device_name != GetDefaultDeviceName() && device_name != GetDefaultUacDeviceName())) { return Service::Audio::ERR_INVALID_DEVICE_NAME; @@ -202,11 +202,11 @@ void System::SetVolume(const f32 volume_) { session->SetVolume(volume_); } -bool System::ContainsAudioBuffer(const u64 tag) { +bool System::ContainsAudioBuffer(const u64 tag) const { return buffers.ContainsBuffer(tag); } -u32 System::GetBufferCount() { +u32 System::GetBufferCount() const { return buffers.GetAppendedRegisteredCount(); } diff --git a/src/audio_core/in/audio_in_system.h b/src/audio_core/in/audio_in_system.h index 9ddc8daae..b9dc0e60f 100644 --- a/src/audio_core/in/audio_in_system.h +++ b/src/audio_core/in/audio_in_system.h @@ -68,7 +68,7 @@ public: * * @return The default audio input device name. */ - std::string_view GetDefaultDeviceName(); + std::string_view GetDefaultDeviceName() const; /** * Get the default USB audio input device name. @@ -77,7 +77,7 @@ public: * * @return The default USB audio input device name. */ - std::string_view GetDefaultUacDeviceName(); + std::string_view GetDefaultUacDeviceName() const; /** * Is the given initialize config valid? @@ -86,7 +86,7 @@ public: * @param in_params - Input parameters, see AudioInParameter. * @return Result code. */ - Result IsConfigValid(std::string_view device_name, const AudioInParameter& in_params); + Result IsConfigValid(std::string_view device_name, const AudioInParameter& in_params) const; /** * Initialize this system. @@ -218,14 +218,14 @@ public: * @param tag - Unique tag to search for. * @return True if the buffer is in the system, otherwise false. */ - bool ContainsAudioBuffer(u64 tag); + bool ContainsAudioBuffer(u64 tag) const; /** * Get the maximum number of usable buffers (default 32). * * @return The number of buffers. */ - u32 GetBufferCount(); + u32 GetBufferCount() const; /** * Get the total number of samples played by this system. From 36c77761cfeeab1afbc6a41bc868eeb4e9079c65 Mon Sep 17 00:00:00 2001 From: Lioncash Date: Fri, 16 Sep 2022 09:50:32 -0400 Subject: [PATCH 69/78] audio_render_manager: Mark several functions as const --- src/audio_core/audio_render_manager.cpp | 6 +++--- src/audio_core/audio_render_manager.h | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/audio_core/audio_render_manager.cpp b/src/audio_core/audio_render_manager.cpp index 7a846835b..7aba2b423 100644 --- a/src/audio_core/audio_render_manager.cpp +++ b/src/audio_core/audio_render_manager.cpp @@ -25,8 +25,8 @@ SystemManager& Manager::GetSystemManager() { return *system_manager; } -auto Manager::GetWorkBufferSize(const AudioRendererParameterInternal& params, u64& out_count) - -> Result { +Result Manager::GetWorkBufferSize(const AudioRendererParameterInternal& params, + u64& out_count) const { if (!CheckValidRevision(params.revision)) { return Service::Audio::ERR_INVALID_REVISION; } @@ -54,7 +54,7 @@ void Manager::ReleaseSessionId(const s32 session_id) { session_ids[--session_count] = session_id; } -u32 Manager::GetSessionCount() { +u32 Manager::GetSessionCount() const { std::scoped_lock l{session_lock}; return session_count; } diff --git a/src/audio_core/audio_render_manager.h b/src/audio_core/audio_render_manager.h index 7119e1b99..bf4837190 100644 --- a/src/audio_core/audio_render_manager.h +++ b/src/audio_core/audio_render_manager.h @@ -46,7 +46,7 @@ public: * @param out_count - Output size of the required workbuffer. * @return Result code. */ - Result GetWorkBufferSize(const AudioRendererParameterInternal& params, u64& out_count); + Result GetWorkBufferSize(const AudioRendererParameterInternal& params, u64& out_count) const; /** * Get a new session id. @@ -60,7 +60,7 @@ public: * * @return The number of active sessions. */ - u32 GetSessionCount(); + u32 GetSessionCount() const; /** * Add a renderer system to the manager. @@ -94,7 +94,7 @@ private: /// Number of active renderers u32 session_count{}; /// Lock for interacting with the sessions - std::mutex session_lock{}; + mutable std::mutex session_lock{}; /// Regularly generates commands from the registered systems for the AudioRenderer std::unique_ptr system_manager{}; }; From b862d5d8d851724e53f0da122ccfc1af1fb1c9bd Mon Sep 17 00:00:00 2001 From: Lioncash Date: Fri, 16 Sep 2022 09:52:32 -0400 Subject: [PATCH 70/78] audio_device: Mark GetDeviceVolume as const This doesn't modify instance state. --- src/audio_core/renderer/audio_device.cpp | 2 +- src/audio_core/renderer/audio_device.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/audio_core/renderer/audio_device.cpp b/src/audio_core/renderer/audio_device.cpp index d5886e55e..b2d34e45a 100644 --- a/src/audio_core/renderer/audio_device.cpp +++ b/src/audio_core/renderer/audio_device.cpp @@ -45,7 +45,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 1f449f261..16522ad2f 100644 --- a/src/audio_core/renderer/audio_device.h +++ b/src/audio_core/renderer/audio_device.h @@ -73,7 +73,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 From b2c2138af7c0ed5e6c254c9c62e19a30e80b3b42 Mon Sep 17 00:00:00 2001 From: Lioncash Date: Fri, 16 Sep 2022 09:55:14 -0400 Subject: [PATCH 71/78] behavior_info: Mark CopyErrorInfo as const This doesn't modify member state. We can also mark the parameter of AppendError as const as well, since it isn't modified. --- src/audio_core/renderer/behavior/behavior_info.cpp | 4 ++-- src/audio_core/renderer/behavior/behavior_info.h | 4 ++-- src/audio_core/renderer/behavior/info_updater.cpp | 2 +- src/audio_core/renderer/behavior/info_updater.h | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/audio_core/renderer/behavior/behavior_info.cpp b/src/audio_core/renderer/behavior/behavior_info.cpp index 92140aaea..3d2a91312 100644 --- a/src/audio_core/renderer/behavior/behavior_info.cpp +++ b/src/audio_core/renderer/behavior/behavior_info.cpp @@ -34,7 +34,7 @@ void BehaviorInfo::ClearError() { error_count = 0; } -void BehaviorInfo::AppendError(ErrorInfo& error) { +void BehaviorInfo::AppendError(const ErrorInfo& error) { LOG_ERROR(Service_Audio, "Error during RequestUpdate, reporting code {:04X} address {:08X}", error.error_code.raw, error.address); if (error_count < MaxErrors) { @@ -42,7 +42,7 @@ void BehaviorInfo::AppendError(ErrorInfo& error) { } } -void BehaviorInfo::CopyErrorInfo(std::span out_errors, u32& out_count) { +void BehaviorInfo::CopyErrorInfo(std::span out_errors, u32& out_count) const { out_count = std::min(error_count, MaxErrors); for (size_t i = 0; i < MaxErrors; i++) { diff --git a/src/audio_core/renderer/behavior/behavior_info.h b/src/audio_core/renderer/behavior/behavior_info.h index 7333c297f..15c948344 100644 --- a/src/audio_core/renderer/behavior/behavior_info.h +++ b/src/audio_core/renderer/behavior/behavior_info.h @@ -94,7 +94,7 @@ public: * * @param error - The new error. */ - void AppendError(ErrorInfo& error); + void AppendError(const ErrorInfo& error); /** * Copy errors to the given output container. @@ -102,7 +102,7 @@ public: * @param out_errors - Output container to receive the errors. * @param out_count - The number of errors written. */ - void CopyErrorInfo(std::span out_errors, u32& out_count); + void CopyErrorInfo(std::span out_errors, u32& out_count) const; /** * Update the behaviour flags. diff --git a/src/audio_core/renderer/behavior/info_updater.cpp b/src/audio_core/renderer/behavior/info_updater.cpp index 06a37e1a6..c0a307b89 100644 --- a/src/audio_core/renderer/behavior/info_updater.cpp +++ b/src/audio_core/renderer/behavior/info_updater.cpp @@ -485,7 +485,7 @@ Result InfoUpdater::UpdateBehaviorInfo(BehaviorInfo& behaviour_) { return ResultSuccess; } -Result InfoUpdater::UpdateErrorInfo(BehaviorInfo& behaviour_) { +Result InfoUpdater::UpdateErrorInfo(const BehaviorInfo& behaviour_) { auto out_params{reinterpret_cast(output)}; behaviour_.CopyErrorInfo(out_params->errors, out_params->error_count); diff --git a/src/audio_core/renderer/behavior/info_updater.h b/src/audio_core/renderer/behavior/info_updater.h index f0b445d9c..c817d8d8d 100644 --- a/src/audio_core/renderer/behavior/info_updater.h +++ b/src/audio_core/renderer/behavior/info_updater.h @@ -130,7 +130,7 @@ public: * @param behaviour - Behaviour to update. * @return Result code. */ - Result UpdateErrorInfo(BehaviorInfo& behaviour); + Result UpdateErrorInfo(const BehaviorInfo& behaviour); /** * Update splitter. From e4bc7b8611677591fb41c9f97bad22b5eb57ab98 Mon Sep 17 00:00:00 2001 From: Lioncash Date: Fri, 16 Sep 2022 09:57:23 -0400 Subject: [PATCH 72/78] i3dl2/reverb: Mark relevant member functions as const These two don't modify member state. --- src/audio_core/renderer/effect/i3dl2.h | 4 ++-- src/audio_core/renderer/effect/reverb.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/audio_core/renderer/effect/i3dl2.h b/src/audio_core/renderer/effect/i3dl2.h index 7a088a627..1ebbc5c4c 100644 --- a/src/audio_core/renderer/effect/i3dl2.h +++ b/src/audio_core/renderer/effect/i3dl2.h @@ -99,7 +99,7 @@ public: return out_sample; } - Common::FixedPoint<50, 14> Read() { + Common::FixedPoint<50, 14> Read() const { return *output; } @@ -110,7 +110,7 @@ public: } } - Common::FixedPoint<50, 14> TapOut(const s32 index) { + Common::FixedPoint<50, 14> TapOut(const s32 index) const { auto out{input - (index + 1)}; if (out < buffer.data()) { out += max_delay + 1; diff --git a/src/audio_core/renderer/effect/reverb.h b/src/audio_core/renderer/effect/reverb.h index b4df9f6ef..a72475c3c 100644 --- a/src/audio_core/renderer/effect/reverb.h +++ b/src/audio_core/renderer/effect/reverb.h @@ -95,7 +95,7 @@ public: return out_sample; } - Common::FixedPoint<50, 14> Read() { + Common::FixedPoint<50, 14> Read() const { return *output; } @@ -106,7 +106,7 @@ public: } } - Common::FixedPoint<50, 14> TapOut(const s32 index) { + Common::FixedPoint<50, 14> TapOut(const s32 index) const { auto out{input - (index + 1)}; if (out < buffer.data()) { out += sample_count; From 6b1cb73350d2e16219bea9e1b3ba6bf66c730190 Mon Sep 17 00:00:00 2001 From: Lioncash Date: Fri, 16 Sep 2022 09:59:56 -0400 Subject: [PATCH 73/78] node_states: Mark relevant member functions as const --- src/audio_core/renderer/nodes/node_states.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/audio_core/renderer/nodes/node_states.h b/src/audio_core/renderer/nodes/node_states.h index c0fced56f..94b1d1254 100644 --- a/src/audio_core/renderer/nodes/node_states.h +++ b/src/audio_core/renderer/nodes/node_states.h @@ -56,7 +56,7 @@ class NodeStates { * * @return The current stack position. */ - u32 Count() { + u32 Count() const { return pos; } @@ -83,7 +83,7 @@ class NodeStates { * * @return The node on the top of the stack. */ - u32 top() { + u32 top() const { return stack[pos - 1]; } From d5d632264086756f0202d15ad7f1e20c49b64934 Mon Sep 17 00:00:00 2001 From: Lioncash Date: Fri, 16 Sep 2022 10:00:52 -0400 Subject: [PATCH 74/78] sink_stream: Mark GetQueueSize as const --- src/audio_core/sink/sink_stream.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/audio_core/sink/sink_stream.h b/src/audio_core/sink/sink_stream.h index 9366ebbd3..38a4b2f51 100644 --- a/src/audio_core/sink/sink_stream.h +++ b/src/audio_core/sink/sink_stream.h @@ -151,7 +151,7 @@ public: * * @return The number of queued buffers. */ - u32 GetQueueSize() { + u32 GetQueueSize() const { return queued_buffers.load(); } From 7e3cdfc453e27af80238d0ec9cd5005e1f189319 Mon Sep 17 00:00:00 2001 From: Lioncash Date: Fri, 16 Sep 2022 10:06:51 -0400 Subject: [PATCH 75/78] audio_renderer: Pass command buffer by const reference This is just being copied and isn't modified at all. --- src/audio_core/renderer/adsp/adsp.cpp | 2 +- src/audio_core/renderer/adsp/adsp.h | 2 +- src/audio_core/renderer/adsp/audio_renderer.cpp | 2 +- src/audio_core/renderer/adsp/audio_renderer.h | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/audio_core/renderer/adsp/adsp.cpp b/src/audio_core/renderer/adsp/adsp.cpp index e05a22d86..a28395663 100644 --- a/src/audio_core/renderer/adsp/adsp.cpp +++ b/src/audio_core/renderer/adsp/adsp.cpp @@ -50,7 +50,7 @@ u32 ADSP::GetRemainCommandCount(const u32 session_id) const { return render_mailbox.GetRemainCommandCount(session_id); } -void ADSP::SendCommandBuffer(const u32 session_id, CommandBuffer& command_buffer) { +void ADSP::SendCommandBuffer(const u32 session_id, const CommandBuffer& command_buffer) { render_mailbox.SetCommandBuffer(session_id, command_buffer); } diff --git a/src/audio_core/renderer/adsp/adsp.h b/src/audio_core/renderer/adsp/adsp.h index 523184dc2..f7a2f25e4 100644 --- a/src/audio_core/renderer/adsp/adsp.h +++ b/src/audio_core/renderer/adsp/adsp.h @@ -131,7 +131,7 @@ public: * @param session_id - The session id to check (0 or 1). * @param command_buffer - The command buffer to process. */ - void SendCommandBuffer(u32 session_id, CommandBuffer& command_buffer); + void SendCommandBuffer(u32 session_id, const CommandBuffer& command_buffer); /** * Clear the command buffers (does not clear the time taken or the remaining command count) diff --git a/src/audio_core/renderer/adsp/audio_renderer.cpp b/src/audio_core/renderer/adsp/audio_renderer.cpp index bcd889ecb..bafe4822a 100644 --- a/src/audio_core/renderer/adsp/audio_renderer.cpp +++ b/src/audio_core/renderer/adsp/audio_renderer.cpp @@ -51,7 +51,7 @@ CommandBuffer& AudioRenderer_Mailbox::GetCommandBuffer(const s32 session_id) { return command_buffers[session_id]; } -void AudioRenderer_Mailbox::SetCommandBuffer(const u32 session_id, CommandBuffer& buffer) { +void AudioRenderer_Mailbox::SetCommandBuffer(const u32 session_id, const CommandBuffer& buffer) { command_buffers[session_id] = buffer; } diff --git a/src/audio_core/renderer/adsp/audio_renderer.h b/src/audio_core/renderer/adsp/audio_renderer.h index 49f66f21c..02e923c84 100644 --- a/src/audio_core/renderer/adsp/audio_renderer.h +++ b/src/audio_core/renderer/adsp/audio_renderer.h @@ -91,7 +91,7 @@ public: * @param session_id - The session id to get (0 or 1). * @param buffer - The command buffer to set. */ - void SetCommandBuffer(u32 session_id, CommandBuffer& buffer); + void SetCommandBuffer(u32 session_id, const CommandBuffer& buffer); /** * Get the total render time taken for the last command lists sent. From 809126c94a0ed8e7964d5a550abf7b3731d00512 Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Thu, 1 Sep 2022 21:29:22 -0400 Subject: [PATCH 76/78] astc: Enable parallel CPU astc decoding Given the issues with GPU accelerated ASTC decoding with NVIDIA's latest drivers, parallelize astc decoding on the CPU. Uses half the available threads in the system for astc decoding. --- src/video_core/textures/astc.cpp | 50 ++++++++++++++++++++------------ 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/src/video_core/textures/astc.cpp b/src/video_core/textures/astc.cpp index e3f3d3c5d..b159494c5 100644 --- a/src/video_core/textures/astc.cpp +++ b/src/video_core/textures/astc.cpp @@ -13,7 +13,9 @@ #include +#include "common/alignment.h" #include "common/common_types.h" +#include "common/thread_worker.h" #include "video_core/textures/astc.h" class InputBitStream { @@ -1650,29 +1652,41 @@ static void DecompressBlock(std::span inBuf, const u32 blockWidth, void Decompress(std::span data, uint32_t width, uint32_t height, uint32_t depth, uint32_t block_width, uint32_t block_height, std::span output) { - u32 block_index = 0; - std::size_t depth_offset = 0; - for (u32 z = 0; z < depth; z++) { - for (u32 y = 0; y < height; y += block_height) { - for (u32 x = 0; x < width; x += block_width) { - const std::span blockPtr{data.subspan(block_index * 16, 16)}; + const u32 rows = Common::DivideUp(height, block_height); + const u32 cols = Common::DivideUp(width, block_width); - // Blocks can be at most 12x12 - std::array uncompData; - DecompressBlock(blockPtr, block_width, block_height, uncompData); + Common::ThreadWorker workers{std::max(std::thread::hardware_concurrency(), 2U) / 2, + "yuzu:ASTCDecompress"}; - u32 decompWidth = std::min(block_width, width - x); - u32 decompHeight = std::min(block_height, height - y); + for (u32 z = 0; z < depth; ++z) { + const u32 depth_offset = z * height * width * 4; + for (u32 y_index = 0; y_index < rows; ++y_index) { + auto decompress_stride = [data, width, height, depth, block_width, block_height, output, + rows, cols, z, depth_offset, y_index] { + const u32 y = y_index * block_height; + for (u32 x_index = 0; x_index < cols; ++x_index) { + const u32 block_index = (z * rows * cols) + (y_index * cols) + x_index; + const u32 x = x_index * block_width; - const std::span outRow = output.subspan(depth_offset + (y * width + x) * 4); - for (u32 jj = 0; jj < decompHeight; jj++) { - std::memcpy(outRow.data() + jj * width * 4, - uncompData.data() + jj * block_width, decompWidth * 4); + const std::span blockPtr{data.subspan(block_index * 16, 16)}; + + // Blocks can be at most 12x12 + std::array uncompData; + DecompressBlock(blockPtr, block_width, block_height, uncompData); + + u32 decompWidth = std::min(block_width, width - x); + u32 decompHeight = std::min(block_height, height - y); + + const std::span outRow = output.subspan(depth_offset + (y * width + x) * 4); + for (u32 h = 0; h < decompHeight; ++h) { + std::memcpy(outRow.data() + h * width * 4, + uncompData.data() + h * block_width, decompWidth * 4); + } } - ++block_index; - } + }; + workers.QueueWork(std::move(decompress_stride)); } - depth_offset += height * width * 4; + workers.WaitForRequests(); } } From 672e61d8022aaa178b66bd92f2038d260dc3d104 Mon Sep 17 00:00:00 2001 From: FengChen Date: Sat, 17 Sep 2022 00:28:56 +0800 Subject: [PATCH 77/78] 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(-) 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 From 49870baea4ccf47a2cc530fd66981805c6c12e5b Mon Sep 17 00:00:00 2001 From: Kyle K <190571+Docteh@users.noreply.github.com> Date: Fri, 16 Sep 2022 10:58:29 -0700 Subject: [PATCH 78/78] GIT: Modify .gitignore to ignore wildcard for build directories Helps if you have multiple build folders. There are other, dark ways to hide extra build folders from git, but this is better. See: https://github.com/citra-emu/citra/pull/6130 --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index cdf37962a..a5f7248c7 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ # SPDX-License-Identifier: GPL-2.0-or-later # Build directory -[Bb]uild/ +[Bb]uild*/ doc-build/ # Generated source files