diff options
Diffstat (limited to 'src/core')
| -rw-r--r-- | src/core/CMakeLists.txt | 9 | ||||
| -rw-r--r-- | src/core/core.cpp | 4 | ||||
| -rw-r--r-- | src/core/core_timing.cpp | 67 | ||||
| -rw-r--r-- | src/core/core_timing.h | 6 | ||||
| -rw-r--r-- | src/core/hle/result.h | 2 | ||||
| -rw-r--r-- | src/core/hle/service/am/applets/applet_mii_edit_types.h | 2 | ||||
| -rw-r--r-- | src/core/hle/service/audio/audout_u.cpp | 3 | ||||
| -rw-r--r-- | src/core/hle/service/audio/audren_u.cpp | 4 | ||||
| -rw-r--r-- | src/core/hle/service/mii/mii.cpp | 32 | ||||
| -rw-r--r-- | src/core/hle/service/mii/mii_manager.cpp | 89 | ||||
| -rw-r--r-- | src/core/hle/service/mii/mii_manager.h | 9 | ||||
| -rw-r--r-- | src/core/hle/service/mii/types.h | 138 | ||||
| -rw-r--r-- | src/core/hle/service/nfp/amiibo_crypto.cpp | 383 | ||||
| -rw-r--r-- | src/core/hle/service/nfp/amiibo_crypto.h | 98 | ||||
| -rw-r--r-- | src/core/hle/service/nfp/amiibo_types.h | 197 | ||||
| -rw-r--r-- | src/core/hle/service/nfp/nfp.cpp | 555 | ||||
| -rw-r--r-- | src/core/hle/service/nfp/nfp.h | 145 | ||||
| -rw-r--r-- | src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp | 7 | ||||
| -rw-r--r-- | src/core/hle/service/nvflinger/nvflinger.cpp | 42 | ||||
| -rw-r--r-- | src/core/hle/service/nvflinger/nvflinger.h | 5 |
20 files changed, 1447 insertions, 350 deletions
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 806e7ff6c..33cf470d5 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt | |||
| @@ -4,12 +4,6 @@ | |||
| 4 | add_library(core STATIC | 4 | add_library(core STATIC |
| 5 | arm/arm_interface.h | 5 | arm/arm_interface.h |
| 6 | arm/arm_interface.cpp | 6 | arm/arm_interface.cpp |
| 7 | arm/dynarmic/arm_dynarmic_32.cpp | ||
| 8 | arm/dynarmic/arm_dynarmic_32.h | ||
| 9 | arm/dynarmic/arm_dynarmic_64.cpp | ||
| 10 | arm/dynarmic/arm_dynarmic_64.h | ||
| 11 | arm/dynarmic/arm_dynarmic_cp15.cpp | ||
| 12 | arm/dynarmic/arm_dynarmic_cp15.h | ||
| 13 | arm/dynarmic/arm_exclusive_monitor.cpp | 7 | arm/dynarmic/arm_exclusive_monitor.cpp |
| 14 | arm/dynarmic/arm_exclusive_monitor.h | 8 | arm/dynarmic/arm_exclusive_monitor.h |
| 15 | arm/exclusive_monitor.cpp | 9 | arm/exclusive_monitor.cpp |
| @@ -525,6 +519,9 @@ add_library(core STATIC | |||
| 525 | hle/service/ncm/ncm.h | 519 | hle/service/ncm/ncm.h |
| 526 | hle/service/nfc/nfc.cpp | 520 | hle/service/nfc/nfc.cpp |
| 527 | hle/service/nfc/nfc.h | 521 | hle/service/nfc/nfc.h |
| 522 | hle/service/nfp/amiibo_crypto.cpp | ||
| 523 | hle/service/nfp/amiibo_crypto.h | ||
| 524 | hle/service/nfp/amiibo_types.h | ||
| 528 | hle/service/nfp/nfp.cpp | 525 | hle/service/nfp/nfp.cpp |
| 529 | hle/service/nfp/nfp.h | 526 | hle/service/nfp/nfp.h |
| 530 | hle/service/nfp/nfp_user.cpp | 527 | hle/service/nfp/nfp_user.cpp |
diff --git a/src/core/core.cpp b/src/core/core.cpp index e651ce100..121092868 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp | |||
| @@ -141,8 +141,6 @@ struct System::Impl { | |||
| 141 | core_timing.SyncPause(false); | 141 | core_timing.SyncPause(false); |
| 142 | is_paused = false; | 142 | is_paused = false; |
| 143 | 143 | ||
| 144 | audio_core->PauseSinks(false); | ||
| 145 | |||
| 146 | return status; | 144 | return status; |
| 147 | } | 145 | } |
| 148 | 146 | ||
| @@ -150,8 +148,6 @@ struct System::Impl { | |||
| 150 | std::unique_lock<std::mutex> lk(suspend_guard); | 148 | std::unique_lock<std::mutex> lk(suspend_guard); |
| 151 | status = SystemResultStatus::Success; | 149 | status = SystemResultStatus::Success; |
| 152 | 150 | ||
| 153 | audio_core->PauseSinks(true); | ||
| 154 | |||
| 155 | core_timing.SyncPause(true); | 151 | core_timing.SyncPause(true); |
| 156 | kernel.Suspend(true); | 152 | kernel.Suspend(true); |
| 157 | is_paused = true; | 153 | is_paused = true; |
diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp index 2dbb99c8b..f6c4567ba 100644 --- a/src/core/core_timing.cpp +++ b/src/core/core_timing.cpp | |||
| @@ -73,7 +73,6 @@ void CoreTiming::Shutdown() { | |||
| 73 | if (timer_thread) { | 73 | if (timer_thread) { |
| 74 | timer_thread->join(); | 74 | timer_thread->join(); |
| 75 | } | 75 | } |
| 76 | pause_callbacks.clear(); | ||
| 77 | ClearPendingEvents(); | 76 | ClearPendingEvents(); |
| 78 | timer_thread.reset(); | 77 | timer_thread.reset(); |
| 79 | has_started = false; | 78 | has_started = false; |
| @@ -86,10 +85,6 @@ void CoreTiming::Pause(bool is_paused) { | |||
| 86 | if (!is_paused) { | 85 | if (!is_paused) { |
| 87 | pause_end_time = GetGlobalTimeNs().count(); | 86 | pause_end_time = GetGlobalTimeNs().count(); |
| 88 | } | 87 | } |
| 89 | |||
| 90 | for (auto& cb : pause_callbacks) { | ||
| 91 | cb(is_paused); | ||
| 92 | } | ||
| 93 | } | 88 | } |
| 94 | 89 | ||
| 95 | void CoreTiming::SyncPause(bool is_paused) { | 90 | void CoreTiming::SyncPause(bool is_paused) { |
| @@ -110,10 +105,6 @@ void CoreTiming::SyncPause(bool is_paused) { | |||
| 110 | if (!is_paused) { | 105 | if (!is_paused) { |
| 111 | pause_end_time = GetGlobalTimeNs().count(); | 106 | pause_end_time = GetGlobalTimeNs().count(); |
| 112 | } | 107 | } |
| 113 | |||
| 114 | for (auto& cb : pause_callbacks) { | ||
| 115 | cb(is_paused); | ||
| 116 | } | ||
| 117 | } | 108 | } |
| 118 | 109 | ||
| 119 | bool CoreTiming::IsRunning() const { | 110 | bool CoreTiming::IsRunning() const { |
| @@ -143,13 +134,17 @@ void CoreTiming::ScheduleLoopingEvent(std::chrono::nanoseconds start_time, | |||
| 143 | std::chrono::nanoseconds resched_time, | 134 | std::chrono::nanoseconds resched_time, |
| 144 | const std::shared_ptr<EventType>& event_type, | 135 | const std::shared_ptr<EventType>& event_type, |
| 145 | std::uintptr_t user_data, bool absolute_time) { | 136 | std::uintptr_t user_data, bool absolute_time) { |
| 146 | std::scoped_lock scope{basic_lock}; | 137 | { |
| 147 | const auto next_time{absolute_time ? start_time : GetGlobalTimeNs() + start_time}; | 138 | std::scoped_lock scope{basic_lock}; |
| 139 | const auto next_time{absolute_time ? start_time : GetGlobalTimeNs() + start_time}; | ||
| 140 | |||
| 141 | event_queue.emplace_back( | ||
| 142 | Event{next_time.count(), event_fifo_id++, user_data, event_type, resched_time.count()}); | ||
| 148 | 143 | ||
| 149 | event_queue.emplace_back( | 144 | std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>()); |
| 150 | Event{next_time.count(), event_fifo_id++, user_data, event_type, resched_time.count()}); | 145 | } |
| 151 | 146 | ||
| 152 | std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>()); | 147 | event.Set(); |
| 153 | } | 148 | } |
| 154 | 149 | ||
| 155 | void CoreTiming::UnscheduleEvent(const std::shared_ptr<EventType>& event_type, | 150 | void CoreTiming::UnscheduleEvent(const std::shared_ptr<EventType>& event_type, |
| @@ -219,11 +214,6 @@ void CoreTiming::RemoveEvent(const std::shared_ptr<EventType>& event_type) { | |||
| 219 | } | 214 | } |
| 220 | } | 215 | } |
| 221 | 216 | ||
| 222 | void CoreTiming::RegisterPauseCallback(PauseCallback&& callback) { | ||
| 223 | std::scoped_lock lock{basic_lock}; | ||
| 224 | pause_callbacks.emplace_back(std::move(callback)); | ||
| 225 | } | ||
| 226 | |||
| 227 | std::optional<s64> CoreTiming::Advance() { | 217 | std::optional<s64> CoreTiming::Advance() { |
| 228 | std::scoped_lock lock{advance_lock, basic_lock}; | 218 | std::scoped_lock lock{advance_lock, basic_lock}; |
| 229 | global_timer = GetGlobalTimeNs().count(); | 219 | global_timer = GetGlobalTimeNs().count(); |
| @@ -243,17 +233,17 @@ std::optional<s64> CoreTiming::Advance() { | |||
| 243 | basic_lock.lock(); | 233 | basic_lock.lock(); |
| 244 | 234 | ||
| 245 | if (evt.reschedule_time != 0) { | 235 | if (evt.reschedule_time != 0) { |
| 236 | const auto next_schedule_time{new_schedule_time.has_value() | ||
| 237 | ? new_schedule_time.value().count() | ||
| 238 | : evt.reschedule_time}; | ||
| 239 | |||
| 246 | // If this event was scheduled into a pause, its time now is going to be way behind. | 240 | // If this event was scheduled into a pause, its time now is going to be way behind. |
| 247 | // Re-set this event to continue from the end of the pause. | 241 | // Re-set this event to continue from the end of the pause. |
| 248 | auto next_time{evt.time + evt.reschedule_time}; | 242 | auto next_time{evt.time + next_schedule_time}; |
| 249 | if (evt.time < pause_end_time) { | 243 | if (evt.time < pause_end_time) { |
| 250 | next_time = pause_end_time + evt.reschedule_time; | 244 | next_time = pause_end_time + next_schedule_time; |
| 251 | } | 245 | } |
| 252 | 246 | ||
| 253 | const auto next_schedule_time{new_schedule_time.has_value() | ||
| 254 | ? new_schedule_time.value().count() | ||
| 255 | : evt.reschedule_time}; | ||
| 256 | |||
| 257 | event_queue.emplace_back( | 247 | event_queue.emplace_back( |
| 258 | Event{next_time, event_fifo_id++, evt.user_data, evt.type, next_schedule_time}); | 248 | Event{next_time, event_fifo_id++, evt.user_data, evt.type, next_schedule_time}); |
| 259 | std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>()); | 249 | std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>()); |
| @@ -264,8 +254,7 @@ std::optional<s64> CoreTiming::Advance() { | |||
| 264 | } | 254 | } |
| 265 | 255 | ||
| 266 | if (!event_queue.empty()) { | 256 | if (!event_queue.empty()) { |
| 267 | const s64 next_time = event_queue.front().time - global_timer; | 257 | return event_queue.front().time; |
| 268 | return next_time; | ||
| 269 | } else { | 258 | } else { |
| 270 | return std::nullopt; | 259 | return std::nullopt; |
| 271 | } | 260 | } |
| @@ -278,11 +267,29 @@ void CoreTiming::ThreadLoop() { | |||
| 278 | paused_set = false; | 267 | paused_set = false; |
| 279 | const auto next_time = Advance(); | 268 | const auto next_time = Advance(); |
| 280 | if (next_time) { | 269 | if (next_time) { |
| 281 | if (*next_time > 0) { | 270 | // There are more events left in the queue, wait until the next event. |
| 282 | std::chrono::nanoseconds next_time_ns = std::chrono::nanoseconds(*next_time); | 271 | const auto wait_time = *next_time - GetGlobalTimeNs().count(); |
| 283 | event.WaitFor(next_time_ns); | 272 | if (wait_time > 0) { |
| 273 | // Assume a timer resolution of 1ms. | ||
| 274 | static constexpr s64 TimerResolutionNS = 1000000; | ||
| 275 | |||
| 276 | // Sleep in discrete intervals of the timer resolution, and spin the rest. | ||
| 277 | const auto sleep_time = wait_time - (wait_time % TimerResolutionNS); | ||
| 278 | if (sleep_time > 0) { | ||
| 279 | event.WaitFor(std::chrono::nanoseconds(sleep_time)); | ||
| 280 | } | ||
| 281 | |||
| 282 | while (!paused && !event.IsSet() && GetGlobalTimeNs().count() < *next_time) { | ||
| 283 | // Yield to reduce thread starvation. | ||
| 284 | std::this_thread::yield(); | ||
| 285 | } | ||
| 286 | |||
| 287 | if (event.IsSet()) { | ||
| 288 | event.Reset(); | ||
| 289 | } | ||
| 284 | } | 290 | } |
| 285 | } else { | 291 | } else { |
| 292 | // Queue is empty, wait until another event is scheduled and signals us to continue. | ||
| 286 | wait_set = true; | 293 | wait_set = true; |
| 287 | event.Wait(); | 294 | event.Wait(); |
| 288 | } | 295 | } |
diff --git a/src/core/core_timing.h b/src/core/core_timing.h index 6aa3ae923..3259397b2 100644 --- a/src/core/core_timing.h +++ b/src/core/core_timing.h | |||
| @@ -22,7 +22,6 @@ namespace Core::Timing { | |||
| 22 | /// A callback that may be scheduled for a particular core timing event. | 22 | /// A callback that may be scheduled for a particular core timing event. |
| 23 | using TimedCallback = std::function<std::optional<std::chrono::nanoseconds>( | 23 | using TimedCallback = std::function<std::optional<std::chrono::nanoseconds>( |
| 24 | std::uintptr_t user_data, s64 time, std::chrono::nanoseconds ns_late)>; | 24 | std::uintptr_t user_data, s64 time, std::chrono::nanoseconds ns_late)>; |
| 25 | using PauseCallback = std::function<void(bool paused)>; | ||
| 26 | 25 | ||
| 27 | /// Contains the characteristics of a particular event. | 26 | /// Contains the characteristics of a particular event. |
| 28 | struct EventType { | 27 | struct EventType { |
| @@ -134,9 +133,6 @@ public: | |||
| 134 | /// Checks for events manually and returns time in nanoseconds for next event, threadsafe. | 133 | /// Checks for events manually and returns time in nanoseconds for next event, threadsafe. |
| 135 | std::optional<s64> Advance(); | 134 | std::optional<s64> Advance(); |
| 136 | 135 | ||
| 137 | /// Register a callback function to be called when coretiming pauses. | ||
| 138 | void RegisterPauseCallback(PauseCallback&& callback); | ||
| 139 | |||
| 140 | private: | 136 | private: |
| 141 | struct Event; | 137 | struct Event; |
| 142 | 138 | ||
| @@ -176,8 +172,6 @@ private: | |||
| 176 | /// Cycle timing | 172 | /// Cycle timing |
| 177 | u64 ticks{}; | 173 | u64 ticks{}; |
| 178 | s64 downcount{}; | 174 | s64 downcount{}; |
| 179 | |||
| 180 | std::vector<PauseCallback> pause_callbacks{}; | ||
| 181 | }; | 175 | }; |
| 182 | 176 | ||
| 183 | /// Creates a core timing event with the given name and callback. | 177 | /// Creates a core timing event with the given name and callback. |
diff --git a/src/core/hle/result.h b/src/core/hle/result.h index 4de44cd06..47a1b829b 100644 --- a/src/core/hle/result.h +++ b/src/core/hle/result.h | |||
| @@ -117,6 +117,7 @@ union Result { | |||
| 117 | BitField<0, 9, ErrorModule> module; | 117 | BitField<0, 9, ErrorModule> module; |
| 118 | BitField<9, 13, u32> description; | 118 | BitField<9, 13, u32> description; |
| 119 | 119 | ||
| 120 | Result() = default; | ||
| 120 | constexpr explicit Result(u32 raw_) : raw(raw_) {} | 121 | constexpr explicit Result(u32 raw_) : raw(raw_) {} |
| 121 | 122 | ||
| 122 | constexpr Result(ErrorModule module_, u32 description_) | 123 | constexpr Result(ErrorModule module_, u32 description_) |
| @@ -130,6 +131,7 @@ union Result { | |||
| 130 | return !IsSuccess(); | 131 | return !IsSuccess(); |
| 131 | } | 132 | } |
| 132 | }; | 133 | }; |
| 134 | static_assert(std::is_trivial_v<Result>); | ||
| 133 | 135 | ||
| 134 | [[nodiscard]] constexpr bool operator==(const Result& a, const Result& b) { | 136 | [[nodiscard]] constexpr bool operator==(const Result& a, const Result& b) { |
| 135 | return a.raw == b.raw; | 137 | return a.raw == b.raw; |
diff --git a/src/core/hle/service/am/applets/applet_mii_edit_types.h b/src/core/hle/service/am/applets/applet_mii_edit_types.h index 1b145b696..4705d019f 100644 --- a/src/core/hle/service/am/applets/applet_mii_edit_types.h +++ b/src/core/hle/service/am/applets/applet_mii_edit_types.h | |||
| @@ -32,7 +32,7 @@ enum class MiiEditResult : u32 { | |||
| 32 | }; | 32 | }; |
| 33 | 33 | ||
| 34 | struct MiiEditCharInfo { | 34 | struct MiiEditCharInfo { |
| 35 | Service::Mii::MiiInfo mii_info{}; | 35 | Service::Mii::CharInfo mii_info{}; |
| 36 | }; | 36 | }; |
| 37 | static_assert(sizeof(MiiEditCharInfo) == 0x58, "MiiEditCharInfo has incorrect size."); | 37 | static_assert(sizeof(MiiEditCharInfo) == 0x58, "MiiEditCharInfo has incorrect size."); |
| 38 | 38 | ||
diff --git a/src/core/hle/service/audio/audout_u.cpp b/src/core/hle/service/audio/audout_u.cpp index a44dd842a..49c092301 100644 --- a/src/core/hle/service/audio/audout_u.cpp +++ b/src/core/hle/service/audio/audout_u.cpp | |||
| @@ -246,9 +246,8 @@ void AudOutU::ListAudioOuts(Kernel::HLERequestContext& ctx) { | |||
| 246 | const auto write_count = | 246 | const auto write_count = |
| 247 | static_cast<u32>(ctx.GetWriteBufferSize() / sizeof(AudioDevice::AudioDeviceName)); | 247 | static_cast<u32>(ctx.GetWriteBufferSize() / sizeof(AudioDevice::AudioDeviceName)); |
| 248 | std::vector<AudioDevice::AudioDeviceName> device_names{}; | 248 | std::vector<AudioDevice::AudioDeviceName> device_names{}; |
| 249 | std::string print_names{}; | ||
| 250 | if (write_count > 0) { | 249 | if (write_count > 0) { |
| 251 | device_names.push_back(AudioDevice::AudioDeviceName("DeviceOut")); | 250 | device_names.emplace_back("DeviceOut"); |
| 252 | LOG_DEBUG(Service_Audio, "called. \nName=DeviceOut"); | 251 | LOG_DEBUG(Service_Audio, "called. \nName=DeviceOut"); |
| 253 | } else { | 252 | } else { |
| 254 | LOG_DEBUG(Service_Audio, "called. Empty buffer passed in."); | 253 | LOG_DEBUG(Service_Audio, "called. Empty buffer passed in."); |
diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp index bc69117c6..6fb07c37d 100644 --- a/src/core/hle/service/audio/audren_u.cpp +++ b/src/core/hle/service/audio/audren_u.cpp | |||
| @@ -252,7 +252,7 @@ private: | |||
| 252 | 252 | ||
| 253 | std::vector<AudioDevice::AudioDeviceName> out_names{}; | 253 | std::vector<AudioDevice::AudioDeviceName> out_names{}; |
| 254 | 254 | ||
| 255 | u32 out_count = impl->ListAudioDeviceName(out_names, in_count); | 255 | const u32 out_count = impl->ListAudioDeviceName(out_names, in_count); |
| 256 | 256 | ||
| 257 | std::string out{}; | 257 | std::string out{}; |
| 258 | for (u32 i = 0; i < out_count; i++) { | 258 | for (u32 i = 0; i < out_count; i++) { |
| @@ -365,7 +365,7 @@ private: | |||
| 365 | 365 | ||
| 366 | std::vector<AudioDevice::AudioDeviceName> out_names{}; | 366 | std::vector<AudioDevice::AudioDeviceName> out_names{}; |
| 367 | 367 | ||
| 368 | u32 out_count = impl->ListAudioOutputDeviceName(out_names, in_count); | 368 | const u32 out_count = impl->ListAudioOutputDeviceName(out_names, in_count); |
| 369 | 369 | ||
| 370 | std::string out{}; | 370 | std::string out{}; |
| 371 | for (u32 i = 0; i < out_count; i++) { | 371 | for (u32 i = 0; i < out_count; i++) { |
diff --git a/src/core/hle/service/mii/mii.cpp b/src/core/hle/service/mii/mii.cpp index efb569993..390514fdc 100644 --- a/src/core/hle/service/mii/mii.cpp +++ b/src/core/hle/service/mii/mii.cpp | |||
| @@ -43,7 +43,7 @@ public: | |||
| 43 | {20, nullptr, "IsBrokenDatabaseWithClearFlag"}, | 43 | {20, nullptr, "IsBrokenDatabaseWithClearFlag"}, |
| 44 | {21, &IDatabaseService::GetIndex, "GetIndex"}, | 44 | {21, &IDatabaseService::GetIndex, "GetIndex"}, |
| 45 | {22, &IDatabaseService::SetInterfaceVersion, "SetInterfaceVersion"}, | 45 | {22, &IDatabaseService::SetInterfaceVersion, "SetInterfaceVersion"}, |
| 46 | {23, nullptr, "Convert"}, | 46 | {23, &IDatabaseService::Convert, "Convert"}, |
| 47 | {24, nullptr, "ConvertCoreDataToCharInfo"}, | 47 | {24, nullptr, "ConvertCoreDataToCharInfo"}, |
| 48 | {25, nullptr, "ConvertCharInfoToCoreData"}, | 48 | {25, nullptr, "ConvertCharInfoToCoreData"}, |
| 49 | {26, nullptr, "Append"}, | 49 | {26, nullptr, "Append"}, |
| @@ -130,7 +130,7 @@ private: | |||
| 130 | return; | 130 | return; |
| 131 | } | 131 | } |
| 132 | 132 | ||
| 133 | std::vector<MiiInfo> values; | 133 | std::vector<CharInfo> values; |
| 134 | for (const auto& element : *result) { | 134 | for (const auto& element : *result) { |
| 135 | values.emplace_back(element.info); | 135 | values.emplace_back(element.info); |
| 136 | } | 136 | } |
| @@ -144,7 +144,7 @@ private: | |||
| 144 | 144 | ||
| 145 | void UpdateLatest(Kernel::HLERequestContext& ctx) { | 145 | void UpdateLatest(Kernel::HLERequestContext& ctx) { |
| 146 | IPC::RequestParser rp{ctx}; | 146 | IPC::RequestParser rp{ctx}; |
| 147 | const auto info{rp.PopRaw<MiiInfo>()}; | 147 | const auto info{rp.PopRaw<CharInfo>()}; |
| 148 | const auto source_flag{rp.PopRaw<SourceFlag>()}; | 148 | const auto source_flag{rp.PopRaw<SourceFlag>()}; |
| 149 | 149 | ||
| 150 | LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag); | 150 | LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag); |
| @@ -156,9 +156,9 @@ private: | |||
| 156 | return; | 156 | return; |
| 157 | } | 157 | } |
| 158 | 158 | ||
| 159 | IPC::ResponseBuilder rb{ctx, 2 + sizeof(MiiInfo) / sizeof(u32)}; | 159 | IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; |
| 160 | rb.Push(ResultSuccess); | 160 | rb.Push(ResultSuccess); |
| 161 | rb.PushRaw<MiiInfo>(*result); | 161 | rb.PushRaw<CharInfo>(*result); |
| 162 | } | 162 | } |
| 163 | 163 | ||
| 164 | void BuildRandom(Kernel::HLERequestContext& ctx) { | 164 | void BuildRandom(Kernel::HLERequestContext& ctx) { |
| @@ -191,9 +191,9 @@ private: | |||
| 191 | return; | 191 | return; |
| 192 | } | 192 | } |
| 193 | 193 | ||
| 194 | IPC::ResponseBuilder rb{ctx, 2 + sizeof(MiiInfo) / sizeof(u32)}; | 194 | IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; |
| 195 | rb.Push(ResultSuccess); | 195 | rb.Push(ResultSuccess); |
| 196 | rb.PushRaw<MiiInfo>(manager.BuildRandom(age, gender, race)); | 196 | rb.PushRaw<CharInfo>(manager.BuildRandom(age, gender, race)); |
| 197 | } | 197 | } |
| 198 | 198 | ||
| 199 | void BuildDefault(Kernel::HLERequestContext& ctx) { | 199 | void BuildDefault(Kernel::HLERequestContext& ctx) { |
| @@ -210,14 +210,14 @@ private: | |||
| 210 | return; | 210 | return; |
| 211 | } | 211 | } |
| 212 | 212 | ||
| 213 | IPC::ResponseBuilder rb{ctx, 2 + sizeof(MiiInfo) / sizeof(u32)}; | 213 | IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; |
| 214 | rb.Push(ResultSuccess); | 214 | rb.Push(ResultSuccess); |
| 215 | rb.PushRaw<MiiInfo>(manager.BuildDefault(index)); | 215 | rb.PushRaw<CharInfo>(manager.BuildDefault(index)); |
| 216 | } | 216 | } |
| 217 | 217 | ||
| 218 | void GetIndex(Kernel::HLERequestContext& ctx) { | 218 | void GetIndex(Kernel::HLERequestContext& ctx) { |
| 219 | IPC::RequestParser rp{ctx}; | 219 | IPC::RequestParser rp{ctx}; |
| 220 | const auto info{rp.PopRaw<MiiInfo>()}; | 220 | const auto info{rp.PopRaw<CharInfo>()}; |
| 221 | 221 | ||
| 222 | LOG_DEBUG(Service_Mii, "called"); | 222 | LOG_DEBUG(Service_Mii, "called"); |
| 223 | 223 | ||
| @@ -239,6 +239,18 @@ private: | |||
| 239 | rb.Push(ResultSuccess); | 239 | rb.Push(ResultSuccess); |
| 240 | } | 240 | } |
| 241 | 241 | ||
| 242 | void Convert(Kernel::HLERequestContext& ctx) { | ||
| 243 | IPC::RequestParser rp{ctx}; | ||
| 244 | |||
| 245 | const auto mii_v3{rp.PopRaw<Ver3StoreData>()}; | ||
| 246 | |||
| 247 | LOG_INFO(Service_Mii, "called"); | ||
| 248 | |||
| 249 | IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; | ||
| 250 | rb.Push(ResultSuccess); | ||
| 251 | rb.PushRaw<CharInfo>(manager.ConvertV3ToCharInfo(mii_v3)); | ||
| 252 | } | ||
| 253 | |||
| 242 | constexpr bool IsInterfaceVersionSupported(u32 interface_version) const { | 254 | constexpr bool IsInterfaceVersionSupported(u32 interface_version) const { |
| 243 | return current_interface_version >= interface_version; | 255 | return current_interface_version >= interface_version; |
| 244 | } | 256 | } |
diff --git a/src/core/hle/service/mii/mii_manager.cpp b/src/core/hle/service/mii/mii_manager.cpp index 544c92a00..c484a9c8d 100644 --- a/src/core/hle/service/mii/mii_manager.cpp +++ b/src/core/hle/service/mii/mii_manager.cpp | |||
| @@ -42,7 +42,7 @@ std::array<T, DestArraySize> ResizeArray(const std::array<T, SourceArraySize>& i | |||
| 42 | return out; | 42 | return out; |
| 43 | } | 43 | } |
| 44 | 44 | ||
| 45 | MiiInfo ConvertStoreDataToInfo(const MiiStoreData& data) { | 45 | CharInfo ConvertStoreDataToInfo(const MiiStoreData& data) { |
| 46 | MiiStoreBitFields bf; | 46 | MiiStoreBitFields bf; |
| 47 | std::memcpy(&bf, data.data.data.data(), sizeof(MiiStoreBitFields)); | 47 | std::memcpy(&bf, data.data.data.data(), sizeof(MiiStoreBitFields)); |
| 48 | 48 | ||
| @@ -409,8 +409,8 @@ u32 MiiManager::GetCount(SourceFlag source_flag) const { | |||
| 409 | return static_cast<u32>(count); | 409 | return static_cast<u32>(count); |
| 410 | } | 410 | } |
| 411 | 411 | ||
| 412 | ResultVal<MiiInfo> MiiManager::UpdateLatest([[maybe_unused]] const MiiInfo& info, | 412 | ResultVal<CharInfo> MiiManager::UpdateLatest([[maybe_unused]] const CharInfo& info, |
| 413 | SourceFlag source_flag) { | 413 | SourceFlag source_flag) { |
| 414 | if ((source_flag & SourceFlag::Database) == SourceFlag::None) { | 414 | if ((source_flag & SourceFlag::Database) == SourceFlag::None) { |
| 415 | return ERROR_CANNOT_FIND_ENTRY; | 415 | return ERROR_CANNOT_FIND_ENTRY; |
| 416 | } | 416 | } |
| @@ -419,14 +419,91 @@ ResultVal<MiiInfo> MiiManager::UpdateLatest([[maybe_unused]] const MiiInfo& info | |||
| 419 | return ERROR_CANNOT_FIND_ENTRY; | 419 | return ERROR_CANNOT_FIND_ENTRY; |
| 420 | } | 420 | } |
| 421 | 421 | ||
| 422 | MiiInfo MiiManager::BuildRandom(Age age, Gender gender, Race race) { | 422 | CharInfo MiiManager::BuildRandom(Age age, Gender gender, Race race) { |
| 423 | return ConvertStoreDataToInfo(BuildRandomStoreData(age, gender, race, user_id)); | 423 | return ConvertStoreDataToInfo(BuildRandomStoreData(age, gender, race, user_id)); |
| 424 | } | 424 | } |
| 425 | 425 | ||
| 426 | MiiInfo MiiManager::BuildDefault(std::size_t index) { | 426 | CharInfo MiiManager::BuildDefault(std::size_t index) { |
| 427 | return ConvertStoreDataToInfo(BuildDefaultStoreData(RawData::DefaultMii.at(index), user_id)); | 427 | return ConvertStoreDataToInfo(BuildDefaultStoreData(RawData::DefaultMii.at(index), user_id)); |
| 428 | } | 428 | } |
| 429 | 429 | ||
| 430 | CharInfo MiiManager::ConvertV3ToCharInfo(Ver3StoreData mii_v3) const { | ||
| 431 | Service::Mii::MiiManager manager; | ||
| 432 | auto mii = manager.BuildDefault(0); | ||
| 433 | |||
| 434 | // Check if mii data exist | ||
| 435 | if (mii_v3.mii_name[0] == 0) { | ||
| 436 | return mii; | ||
| 437 | } | ||
| 438 | |||
| 439 | // TODO: We are ignoring a bunch of data from the mii_v3 | ||
| 440 | |||
| 441 | mii.gender = static_cast<u8>(mii_v3.mii_information.gender); | ||
| 442 | mii.favorite_color = static_cast<u8>(mii_v3.mii_information.favorite_color); | ||
| 443 | mii.height = mii_v3.height; | ||
| 444 | mii.build = mii_v3.build; | ||
| 445 | |||
| 446 | memset(mii.name.data(), 0, sizeof(mii.name)); | ||
| 447 | memcpy(mii.name.data(), mii_v3.mii_name.data(), sizeof(mii_v3.mii_name)); | ||
| 448 | mii.font_region = mii_v3.region_information.character_set; | ||
| 449 | |||
| 450 | mii.faceline_type = mii_v3.appearance_bits1.face_shape; | ||
| 451 | mii.faceline_color = mii_v3.appearance_bits1.skin_color; | ||
| 452 | mii.faceline_wrinkle = mii_v3.appearance_bits2.wrinkles; | ||
| 453 | mii.faceline_make = mii_v3.appearance_bits2.makeup; | ||
| 454 | |||
| 455 | mii.hair_type = mii_v3.hair_style; | ||
| 456 | mii.hair_color = mii_v3.appearance_bits3.hair_color; | ||
| 457 | mii.hair_flip = mii_v3.appearance_bits3.flip_hair; | ||
| 458 | |||
| 459 | mii.eye_type = static_cast<u8>(mii_v3.appearance_bits4.eye_type); | ||
| 460 | mii.eye_color = static_cast<u8>(mii_v3.appearance_bits4.eye_color); | ||
| 461 | mii.eye_scale = static_cast<u8>(mii_v3.appearance_bits4.eye_scale); | ||
| 462 | mii.eye_aspect = static_cast<u8>(mii_v3.appearance_bits4.eye_vertical_stretch); | ||
| 463 | mii.eye_rotate = static_cast<u8>(mii_v3.appearance_bits4.eye_rotation); | ||
| 464 | mii.eye_x = static_cast<u8>(mii_v3.appearance_bits4.eye_spacing); | ||
| 465 | mii.eye_y = static_cast<u8>(mii_v3.appearance_bits4.eye_y_position); | ||
| 466 | |||
| 467 | mii.eyebrow_type = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_style); | ||
| 468 | mii.eyebrow_color = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_color); | ||
| 469 | mii.eyebrow_scale = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_scale); | ||
| 470 | mii.eyebrow_aspect = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_yscale); | ||
| 471 | mii.eyebrow_rotate = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_rotation); | ||
| 472 | mii.eyebrow_x = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_spacing); | ||
| 473 | mii.eyebrow_y = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_y_position); | ||
| 474 | |||
| 475 | mii.nose_type = static_cast<u8>(mii_v3.appearance_bits6.nose_type); | ||
| 476 | mii.nose_scale = static_cast<u8>(mii_v3.appearance_bits6.nose_scale); | ||
| 477 | mii.nose_y = static_cast<u8>(mii_v3.appearance_bits6.nose_y_position); | ||
| 478 | |||
| 479 | mii.mouth_type = static_cast<u8>(mii_v3.appearance_bits7.mouth_type); | ||
| 480 | mii.mouth_color = static_cast<u8>(mii_v3.appearance_bits7.mouth_color); | ||
| 481 | mii.mouth_scale = static_cast<u8>(mii_v3.appearance_bits7.mouth_scale); | ||
| 482 | mii.mouth_aspect = static_cast<u8>(mii_v3.appearance_bits7.mouth_horizontal_stretch); | ||
| 483 | mii.mouth_y = static_cast<u8>(mii_v3.appearance_bits8.mouth_y_position); | ||
| 484 | |||
| 485 | mii.mustache_type = static_cast<u8>(mii_v3.appearance_bits8.mustache_type); | ||
| 486 | mii.mustache_scale = static_cast<u8>(mii_v3.appearance_bits9.mustache_scale); | ||
| 487 | mii.mustache_y = static_cast<u8>(mii_v3.appearance_bits9.mustache_y_position); | ||
| 488 | |||
| 489 | mii.beard_type = static_cast<u8>(mii_v3.appearance_bits9.bear_type); | ||
| 490 | mii.beard_color = static_cast<u8>(mii_v3.appearance_bits9.facial_hair_color); | ||
| 491 | |||
| 492 | mii.glasses_type = static_cast<u8>(mii_v3.appearance_bits10.glasses_type); | ||
| 493 | mii.glasses_color = static_cast<u8>(mii_v3.appearance_bits10.glasses_color); | ||
| 494 | mii.glasses_scale = static_cast<u8>(mii_v3.appearance_bits10.glasses_scale); | ||
| 495 | mii.glasses_y = static_cast<u8>(mii_v3.appearance_bits10.glasses_y_position); | ||
| 496 | |||
| 497 | mii.mole_type = static_cast<u8>(mii_v3.appearance_bits11.mole_enabled); | ||
| 498 | mii.mole_scale = static_cast<u8>(mii_v3.appearance_bits11.mole_scale); | ||
| 499 | mii.mole_x = static_cast<u8>(mii_v3.appearance_bits11.mole_x_position); | ||
| 500 | mii.mole_y = static_cast<u8>(mii_v3.appearance_bits11.mole_y_position); | ||
| 501 | |||
| 502 | // TODO: Validate mii data | ||
| 503 | |||
| 504 | return mii; | ||
| 505 | } | ||
| 506 | |||
| 430 | ResultVal<std::vector<MiiInfoElement>> MiiManager::GetDefault(SourceFlag source_flag) { | 507 | ResultVal<std::vector<MiiInfoElement>> MiiManager::GetDefault(SourceFlag source_flag) { |
| 431 | std::vector<MiiInfoElement> result; | 508 | std::vector<MiiInfoElement> result; |
| 432 | 509 | ||
| @@ -441,7 +518,7 @@ ResultVal<std::vector<MiiInfoElement>> MiiManager::GetDefault(SourceFlag source_ | |||
| 441 | return result; | 518 | return result; |
| 442 | } | 519 | } |
| 443 | 520 | ||
| 444 | Result MiiManager::GetIndex([[maybe_unused]] const MiiInfo& info, u32& index) { | 521 | Result MiiManager::GetIndex([[maybe_unused]] const CharInfo& info, u32& index) { |
| 445 | constexpr u32 INVALID_INDEX{0xFFFFFFFF}; | 522 | constexpr u32 INVALID_INDEX{0xFFFFFFFF}; |
| 446 | 523 | ||
| 447 | index = INVALID_INDEX; | 524 | index = INVALID_INDEX; |
diff --git a/src/core/hle/service/mii/mii_manager.h b/src/core/hle/service/mii/mii_manager.h index 6a286bd96..d847de0bd 100644 --- a/src/core/hle/service/mii/mii_manager.h +++ b/src/core/hle/service/mii/mii_manager.h | |||
| @@ -19,11 +19,12 @@ public: | |||
| 19 | bool CheckAndResetUpdateCounter(SourceFlag source_flag, u64& current_update_counter); | 19 | bool CheckAndResetUpdateCounter(SourceFlag source_flag, u64& current_update_counter); |
| 20 | bool IsFullDatabase() const; | 20 | bool IsFullDatabase() const; |
| 21 | u32 GetCount(SourceFlag source_flag) const; | 21 | u32 GetCount(SourceFlag source_flag) const; |
| 22 | ResultVal<MiiInfo> UpdateLatest(const MiiInfo& info, SourceFlag source_flag); | 22 | ResultVal<CharInfo> UpdateLatest(const CharInfo& info, SourceFlag source_flag); |
| 23 | MiiInfo BuildRandom(Age age, Gender gender, Race race); | 23 | CharInfo BuildRandom(Age age, Gender gender, Race race); |
| 24 | MiiInfo BuildDefault(std::size_t index); | 24 | CharInfo BuildDefault(std::size_t index); |
| 25 | CharInfo ConvertV3ToCharInfo(Ver3StoreData mii_v3) const; | ||
| 25 | ResultVal<std::vector<MiiInfoElement>> GetDefault(SourceFlag source_flag); | 26 | ResultVal<std::vector<MiiInfoElement>> GetDefault(SourceFlag source_flag); |
| 26 | Result GetIndex(const MiiInfo& info, u32& index); | 27 | Result GetIndex(const CharInfo& info, u32& index); |
| 27 | 28 | ||
| 28 | private: | 29 | private: |
| 29 | const Common::UUID user_id{}; | 30 | const Common::UUID user_id{}; |
diff --git a/src/core/hle/service/mii/types.h b/src/core/hle/service/mii/types.h index 45edbfeae..9e3247397 100644 --- a/src/core/hle/service/mii/types.h +++ b/src/core/hle/service/mii/types.h | |||
| @@ -86,7 +86,8 @@ enum class SourceFlag : u32 { | |||
| 86 | }; | 86 | }; |
| 87 | DECLARE_ENUM_FLAG_OPERATORS(SourceFlag); | 87 | DECLARE_ENUM_FLAG_OPERATORS(SourceFlag); |
| 88 | 88 | ||
| 89 | struct MiiInfo { | 89 | // nn::mii::CharInfo |
| 90 | struct CharInfo { | ||
| 90 | Common::UUID uuid; | 91 | Common::UUID uuid; |
| 91 | std::array<char16_t, 11> name; | 92 | std::array<char16_t, 11> name; |
| 92 | u8 font_region; | 93 | u8 font_region; |
| @@ -140,16 +141,16 @@ struct MiiInfo { | |||
| 140 | u8 mole_y; | 141 | u8 mole_y; |
| 141 | u8 padding; | 142 | u8 padding; |
| 142 | }; | 143 | }; |
| 143 | static_assert(sizeof(MiiInfo) == 0x58, "MiiInfo has incorrect size."); | 144 | static_assert(sizeof(CharInfo) == 0x58, "CharInfo has incorrect size."); |
| 144 | static_assert(std::has_unique_object_representations_v<MiiInfo>, | 145 | static_assert(std::has_unique_object_representations_v<CharInfo>, |
| 145 | "All bits of MiiInfo must contribute to its value."); | 146 | "All bits of CharInfo must contribute to its value."); |
| 146 | 147 | ||
| 147 | #pragma pack(push, 4) | 148 | #pragma pack(push, 4) |
| 148 | 149 | ||
| 149 | struct MiiInfoElement { | 150 | struct MiiInfoElement { |
| 150 | MiiInfoElement(const MiiInfo& info_, Source source_) : info{info_}, source{source_} {} | 151 | MiiInfoElement(const CharInfo& info_, Source source_) : info{info_}, source{source_} {} |
| 151 | 152 | ||
| 152 | MiiInfo info{}; | 153 | CharInfo info{}; |
| 153 | Source source{}; | 154 | Source source{}; |
| 154 | }; | 155 | }; |
| 155 | static_assert(sizeof(MiiInfoElement) == 0x5c, "MiiInfoElement has incorrect size."); | 156 | static_assert(sizeof(MiiInfoElement) == 0x5c, "MiiInfoElement has incorrect size."); |
| @@ -243,6 +244,131 @@ static_assert(sizeof(MiiStoreBitFields) == 0x1c, "MiiStoreBitFields has incorrec | |||
| 243 | static_assert(std::is_trivially_copyable_v<MiiStoreBitFields>, | 244 | static_assert(std::is_trivially_copyable_v<MiiStoreBitFields>, |
| 244 | "MiiStoreBitFields is not trivially copyable."); | 245 | "MiiStoreBitFields is not trivially copyable."); |
| 245 | 246 | ||
| 247 | // This is nn::mii::Ver3StoreData | ||
| 248 | // Based on citra HLE::Applets::MiiData and PretendoNetwork. | ||
| 249 | // https://github.com/citra-emu/citra/blob/master/src/core/hle/applets/mii_selector.h#L48 | ||
| 250 | // https://github.com/PretendoNetwork/mii-js/blob/master/mii.js#L299 | ||
| 251 | struct Ver3StoreData { | ||
| 252 | u8 version; | ||
| 253 | union { | ||
| 254 | u8 raw; | ||
| 255 | |||
| 256 | BitField<0, 1, u8> allow_copying; | ||
| 257 | BitField<1, 1, u8> profanity_flag; | ||
| 258 | BitField<2, 2, u8> region_lock; | ||
| 259 | BitField<4, 2, u8> character_set; | ||
| 260 | } region_information; | ||
| 261 | u16_be mii_id; | ||
| 262 | u64_be system_id; | ||
| 263 | u32_be specialness_and_creation_date; | ||
| 264 | std::array<u8, 0x6> creator_mac; | ||
| 265 | u16_be padding; | ||
| 266 | union { | ||
| 267 | u16 raw; | ||
| 268 | |||
| 269 | BitField<0, 1, u16> gender; | ||
| 270 | BitField<1, 4, u16> birth_month; | ||
| 271 | BitField<5, 5, u16> birth_day; | ||
| 272 | BitField<10, 4, u16> favorite_color; | ||
| 273 | BitField<14, 1, u16> favorite; | ||
| 274 | } mii_information; | ||
| 275 | std::array<char16_t, 0xA> mii_name; | ||
| 276 | u8 height; | ||
| 277 | u8 build; | ||
| 278 | union { | ||
| 279 | u8 raw; | ||
| 280 | |||
| 281 | BitField<0, 1, u8> disable_sharing; | ||
| 282 | BitField<1, 4, u8> face_shape; | ||
| 283 | BitField<5, 3, u8> skin_color; | ||
| 284 | } appearance_bits1; | ||
| 285 | union { | ||
| 286 | u8 raw; | ||
| 287 | |||
| 288 | BitField<0, 4, u8> wrinkles; | ||
| 289 | BitField<4, 4, u8> makeup; | ||
| 290 | } appearance_bits2; | ||
| 291 | u8 hair_style; | ||
| 292 | union { | ||
| 293 | u8 raw; | ||
| 294 | |||
| 295 | BitField<0, 3, u8> hair_color; | ||
| 296 | BitField<3, 1, u8> flip_hair; | ||
| 297 | } appearance_bits3; | ||
| 298 | union { | ||
| 299 | u32 raw; | ||
| 300 | |||
| 301 | BitField<0, 6, u32> eye_type; | ||
| 302 | BitField<6, 3, u32> eye_color; | ||
| 303 | BitField<9, 4, u32> eye_scale; | ||
| 304 | BitField<13, 3, u32> eye_vertical_stretch; | ||
| 305 | BitField<16, 5, u32> eye_rotation; | ||
| 306 | BitField<21, 4, u32> eye_spacing; | ||
| 307 | BitField<25, 5, u32> eye_y_position; | ||
| 308 | } appearance_bits4; | ||
| 309 | union { | ||
| 310 | u32 raw; | ||
| 311 | |||
| 312 | BitField<0, 5, u32> eyebrow_style; | ||
| 313 | BitField<5, 3, u32> eyebrow_color; | ||
| 314 | BitField<8, 4, u32> eyebrow_scale; | ||
| 315 | BitField<12, 3, u32> eyebrow_yscale; | ||
| 316 | BitField<16, 4, u32> eyebrow_rotation; | ||
| 317 | BitField<21, 4, u32> eyebrow_spacing; | ||
| 318 | BitField<25, 5, u32> eyebrow_y_position; | ||
| 319 | } appearance_bits5; | ||
| 320 | union { | ||
| 321 | u16 raw; | ||
| 322 | |||
| 323 | BitField<0, 5, u16> nose_type; | ||
| 324 | BitField<5, 4, u16> nose_scale; | ||
| 325 | BitField<9, 5, u16> nose_y_position; | ||
| 326 | } appearance_bits6; | ||
| 327 | union { | ||
| 328 | u16 raw; | ||
| 329 | |||
| 330 | BitField<0, 6, u16> mouth_type; | ||
| 331 | BitField<6, 3, u16> mouth_color; | ||
| 332 | BitField<9, 4, u16> mouth_scale; | ||
| 333 | BitField<13, 3, u16> mouth_horizontal_stretch; | ||
| 334 | } appearance_bits7; | ||
| 335 | union { | ||
| 336 | u8 raw; | ||
| 337 | |||
| 338 | BitField<0, 5, u8> mouth_y_position; | ||
| 339 | BitField<5, 3, u8> mustache_type; | ||
| 340 | } appearance_bits8; | ||
| 341 | u8 allow_copying; | ||
| 342 | union { | ||
| 343 | u16 raw; | ||
| 344 | |||
| 345 | BitField<0, 3, u16> bear_type; | ||
| 346 | BitField<3, 3, u16> facial_hair_color; | ||
| 347 | BitField<6, 4, u16> mustache_scale; | ||
| 348 | BitField<10, 5, u16> mustache_y_position; | ||
| 349 | } appearance_bits9; | ||
| 350 | union { | ||
| 351 | u16 raw; | ||
| 352 | |||
| 353 | BitField<0, 4, u16> glasses_type; | ||
| 354 | BitField<4, 3, u16> glasses_color; | ||
| 355 | BitField<7, 4, u16> glasses_scale; | ||
| 356 | BitField<11, 5, u16> glasses_y_position; | ||
| 357 | } appearance_bits10; | ||
| 358 | union { | ||
| 359 | u16 raw; | ||
| 360 | |||
| 361 | BitField<0, 1, u16> mole_enabled; | ||
| 362 | BitField<1, 4, u16> mole_scale; | ||
| 363 | BitField<5, 5, u16> mole_x_position; | ||
| 364 | BitField<10, 5, u16> mole_y_position; | ||
| 365 | } appearance_bits11; | ||
| 366 | |||
| 367 | std::array<u16_le, 0xA> author_name; | ||
| 368 | INSERT_PADDING_BYTES(0x4); | ||
| 369 | }; | ||
| 370 | static_assert(sizeof(Ver3StoreData) == 0x60, "Ver3StoreData is an invalid size"); | ||
| 371 | |||
| 246 | struct MiiStoreData { | 372 | struct MiiStoreData { |
| 247 | using Name = std::array<char16_t, 10>; | 373 | using Name = std::array<char16_t, 10>; |
| 248 | 374 | ||
diff --git a/src/core/hle/service/nfp/amiibo_crypto.cpp b/src/core/hle/service/nfp/amiibo_crypto.cpp new file mode 100644 index 000000000..31dd3a307 --- /dev/null +++ b/src/core/hle/service/nfp/amiibo_crypto.cpp | |||
| @@ -0,0 +1,383 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-3.0-or-later | ||
| 3 | |||
| 4 | // SPDX-FileCopyrightText: Copyright 2017 socram8888/amiitool | ||
| 5 | // SPDX-License-Identifier: MIT | ||
| 6 | |||
| 7 | #include <array> | ||
| 8 | #include <mbedtls/aes.h> | ||
| 9 | #include <mbedtls/hmac_drbg.h> | ||
| 10 | |||
| 11 | #include "common/fs/file.h" | ||
| 12 | #include "common/fs/path_util.h" | ||
| 13 | #include "common/logging/log.h" | ||
| 14 | #include "core/hle/service/mii/mii_manager.h" | ||
| 15 | #include "core/hle/service/nfp/amiibo_crypto.h" | ||
| 16 | |||
| 17 | namespace Service::NFP::AmiiboCrypto { | ||
| 18 | |||
| 19 | bool IsAmiiboValid(const EncryptedNTAG215File& ntag_file) { | ||
| 20 | const auto& amiibo_data = ntag_file.user_memory; | ||
| 21 | LOG_DEBUG(Service_NFP, "uuid_lock=0x{0:x}", ntag_file.static_lock); | ||
| 22 | LOG_DEBUG(Service_NFP, "compability_container=0x{0:x}", ntag_file.compability_container); | ||
| 23 | LOG_INFO(Service_NFP, "write_count={}", amiibo_data.write_counter); | ||
| 24 | |||
| 25 | LOG_INFO(Service_NFP, "character_id=0x{0:x}", amiibo_data.model_info.character_id); | ||
| 26 | LOG_INFO(Service_NFP, "character_variant={}", amiibo_data.model_info.character_variant); | ||
| 27 | LOG_INFO(Service_NFP, "amiibo_type={}", amiibo_data.model_info.amiibo_type); | ||
| 28 | LOG_INFO(Service_NFP, "model_number=0x{0:x}", amiibo_data.model_info.model_number); | ||
| 29 | LOG_INFO(Service_NFP, "series={}", amiibo_data.model_info.series); | ||
| 30 | LOG_DEBUG(Service_NFP, "fixed_value=0x{0:x}", amiibo_data.model_info.constant_value); | ||
| 31 | |||
| 32 | LOG_DEBUG(Service_NFP, "tag_dynamic_lock=0x{0:x}", ntag_file.dynamic_lock); | ||
| 33 | LOG_DEBUG(Service_NFP, "tag_CFG0=0x{0:x}", ntag_file.CFG0); | ||
| 34 | LOG_DEBUG(Service_NFP, "tag_CFG1=0x{0:x}", ntag_file.CFG1); | ||
| 35 | |||
| 36 | // Validate UUID | ||
| 37 | constexpr u8 CT = 0x88; // As defined in `ISO / IEC 14443 - 3` | ||
| 38 | if ((CT ^ ntag_file.uuid[0] ^ ntag_file.uuid[1] ^ ntag_file.uuid[2]) != ntag_file.uuid[3]) { | ||
| 39 | return false; | ||
| 40 | } | ||
| 41 | if ((ntag_file.uuid[4] ^ ntag_file.uuid[5] ^ ntag_file.uuid[6] ^ ntag_file.uuid[7]) != | ||
| 42 | ntag_file.uuid[8]) { | ||
| 43 | return false; | ||
| 44 | } | ||
| 45 | |||
| 46 | // Check against all know constants on an amiibo binary | ||
| 47 | if (ntag_file.static_lock != 0xE00F) { | ||
| 48 | return false; | ||
| 49 | } | ||
| 50 | if (ntag_file.compability_container != 0xEEFF10F1U) { | ||
| 51 | return false; | ||
| 52 | } | ||
| 53 | if (amiibo_data.constant_value != 0xA5) { | ||
| 54 | return false; | ||
| 55 | } | ||
| 56 | if (amiibo_data.model_info.constant_value != 0x02) { | ||
| 57 | return false; | ||
| 58 | } | ||
| 59 | // dynamic_lock value apparently is not constant | ||
| 60 | // ntag_file.dynamic_lock == 0x0F0001 | ||
| 61 | if (ntag_file.CFG0 != 0x04000000U) { | ||
| 62 | return false; | ||
| 63 | } | ||
| 64 | if (ntag_file.CFG1 != 0x5F) { | ||
| 65 | return false; | ||
| 66 | } | ||
| 67 | return true; | ||
| 68 | } | ||
| 69 | |||
| 70 | NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data) { | ||
| 71 | NTAG215File encoded_data{}; | ||
| 72 | |||
| 73 | memcpy(encoded_data.uuid2.data(), nfc_data.uuid.data() + 0x8, sizeof(encoded_data.uuid2)); | ||
| 74 | encoded_data.static_lock = nfc_data.static_lock; | ||
| 75 | encoded_data.compability_container = nfc_data.compability_container; | ||
| 76 | encoded_data.hmac_data = nfc_data.user_memory.hmac_data; | ||
| 77 | encoded_data.constant_value = nfc_data.user_memory.constant_value; | ||
| 78 | encoded_data.write_counter = nfc_data.user_memory.write_counter; | ||
| 79 | encoded_data.settings = nfc_data.user_memory.settings; | ||
| 80 | encoded_data.owner_mii = nfc_data.user_memory.owner_mii; | ||
| 81 | encoded_data.title_id = nfc_data.user_memory.title_id; | ||
| 82 | encoded_data.applicaton_write_counter = nfc_data.user_memory.applicaton_write_counter; | ||
| 83 | encoded_data.application_area_id = nfc_data.user_memory.application_area_id; | ||
| 84 | encoded_data.unknown = nfc_data.user_memory.unknown; | ||
| 85 | encoded_data.hash = nfc_data.user_memory.hash; | ||
| 86 | encoded_data.application_area = nfc_data.user_memory.application_area; | ||
| 87 | encoded_data.hmac_tag = nfc_data.user_memory.hmac_tag; | ||
| 88 | memcpy(encoded_data.uuid.data(), nfc_data.uuid.data(), sizeof(encoded_data.uuid)); | ||
| 89 | encoded_data.model_info = nfc_data.user_memory.model_info; | ||
| 90 | encoded_data.keygen_salt = nfc_data.user_memory.keygen_salt; | ||
| 91 | encoded_data.dynamic_lock = nfc_data.dynamic_lock; | ||
| 92 | encoded_data.CFG0 = nfc_data.CFG0; | ||
| 93 | encoded_data.CFG1 = nfc_data.CFG1; | ||
| 94 | encoded_data.password = nfc_data.password; | ||
| 95 | |||
| 96 | return encoded_data; | ||
| 97 | } | ||
| 98 | |||
| 99 | EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data) { | ||
| 100 | EncryptedNTAG215File nfc_data{}; | ||
| 101 | |||
| 102 | memcpy(nfc_data.uuid.data() + 0x8, encoded_data.uuid2.data(), sizeof(encoded_data.uuid2)); | ||
| 103 | memcpy(nfc_data.uuid.data(), encoded_data.uuid.data(), sizeof(encoded_data.uuid)); | ||
| 104 | nfc_data.static_lock = encoded_data.static_lock; | ||
| 105 | nfc_data.compability_container = encoded_data.compability_container; | ||
| 106 | nfc_data.user_memory.hmac_data = encoded_data.hmac_data; | ||
| 107 | nfc_data.user_memory.constant_value = encoded_data.constant_value; | ||
| 108 | nfc_data.user_memory.write_counter = encoded_data.write_counter; | ||
| 109 | nfc_data.user_memory.settings = encoded_data.settings; | ||
| 110 | nfc_data.user_memory.owner_mii = encoded_data.owner_mii; | ||
| 111 | nfc_data.user_memory.title_id = encoded_data.title_id; | ||
| 112 | nfc_data.user_memory.applicaton_write_counter = encoded_data.applicaton_write_counter; | ||
| 113 | nfc_data.user_memory.application_area_id = encoded_data.application_area_id; | ||
| 114 | nfc_data.user_memory.unknown = encoded_data.unknown; | ||
| 115 | nfc_data.user_memory.hash = encoded_data.hash; | ||
| 116 | nfc_data.user_memory.application_area = encoded_data.application_area; | ||
| 117 | nfc_data.user_memory.hmac_tag = encoded_data.hmac_tag; | ||
| 118 | nfc_data.user_memory.model_info = encoded_data.model_info; | ||
| 119 | nfc_data.user_memory.keygen_salt = encoded_data.keygen_salt; | ||
| 120 | nfc_data.dynamic_lock = encoded_data.dynamic_lock; | ||
| 121 | nfc_data.CFG0 = encoded_data.CFG0; | ||
| 122 | nfc_data.CFG1 = encoded_data.CFG1; | ||
| 123 | nfc_data.password = encoded_data.password; | ||
| 124 | |||
| 125 | return nfc_data; | ||
| 126 | } | ||
| 127 | |||
| 128 | u32 GetTagPassword(const TagUuid& uuid) { | ||
| 129 | // Verifiy that the generated password is correct | ||
| 130 | u32 password = 0xAA ^ (uuid[1] ^ uuid[3]); | ||
| 131 | password &= (0x55 ^ (uuid[2] ^ uuid[4])) << 8; | ||
| 132 | password &= (0xAA ^ (uuid[3] ^ uuid[5])) << 16; | ||
| 133 | password &= (0x55 ^ (uuid[4] ^ uuid[6])) << 24; | ||
| 134 | return password; | ||
| 135 | } | ||
| 136 | |||
| 137 | HashSeed GetSeed(const NTAG215File& data) { | ||
| 138 | HashSeed seed{ | ||
| 139 | .magic = data.write_counter, | ||
| 140 | .padding = {}, | ||
| 141 | .uuid1 = {}, | ||
| 142 | .uuid2 = {}, | ||
| 143 | .keygen_salt = data.keygen_salt, | ||
| 144 | }; | ||
| 145 | |||
| 146 | // Copy the first 8 bytes of uuid | ||
| 147 | memcpy(seed.uuid1.data(), data.uuid.data(), sizeof(seed.uuid1)); | ||
| 148 | memcpy(seed.uuid2.data(), data.uuid.data(), sizeof(seed.uuid2)); | ||
| 149 | |||
| 150 | return seed; | ||
| 151 | } | ||
| 152 | |||
| 153 | std::vector<u8> GenerateInternalKey(const InternalKey& key, const HashSeed& seed) { | ||
| 154 | const std::size_t seedPart1Len = sizeof(key.magic_bytes) - key.magic_length; | ||
| 155 | const std::size_t string_size = key.type_string.size(); | ||
| 156 | std::vector<u8> output(string_size + seedPart1Len); | ||
| 157 | |||
| 158 | // Copy whole type string | ||
| 159 | memccpy(output.data(), key.type_string.data(), '\0', string_size); | ||
| 160 | |||
| 161 | // Append (16 - magic_length) from the input seed | ||
| 162 | memcpy(output.data() + string_size, &seed, seedPart1Len); | ||
| 163 | |||
| 164 | // Append all bytes from magicBytes | ||
| 165 | output.insert(output.end(), key.magic_bytes.begin(), | ||
| 166 | key.magic_bytes.begin() + key.magic_length); | ||
| 167 | |||
| 168 | output.insert(output.end(), seed.uuid1.begin(), seed.uuid1.end()); | ||
| 169 | output.insert(output.end(), seed.uuid2.begin(), seed.uuid2.end()); | ||
| 170 | |||
| 171 | for (std::size_t i = 0; i < sizeof(seed.keygen_salt); i++) { | ||
| 172 | output.emplace_back(static_cast<u8>(seed.keygen_salt[i] ^ key.xor_pad[i])); | ||
| 173 | } | ||
| 174 | |||
| 175 | return output; | ||
| 176 | } | ||
| 177 | |||
| 178 | void CryptoInit(CryptoCtx& ctx, mbedtls_md_context_t& hmac_ctx, const HmacKey& hmac_key, | ||
| 179 | const std::vector<u8>& seed) { | ||
| 180 | |||
| 181 | // Initialize context | ||
| 182 | ctx.used = false; | ||
| 183 | ctx.counter = 0; | ||
| 184 | ctx.buffer_size = sizeof(ctx.counter) + seed.size(); | ||
| 185 | memcpy(ctx.buffer.data() + sizeof(u16), seed.data(), seed.size()); | ||
| 186 | |||
| 187 | // Initialize HMAC context | ||
| 188 | mbedtls_md_init(&hmac_ctx); | ||
| 189 | mbedtls_md_setup(&hmac_ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 1); | ||
| 190 | mbedtls_md_hmac_starts(&hmac_ctx, hmac_key.data(), hmac_key.size()); | ||
| 191 | } | ||
| 192 | |||
| 193 | void CryptoStep(CryptoCtx& ctx, mbedtls_md_context_t& hmac_ctx, DrgbOutput& output) { | ||
| 194 | // If used at least once, reinitialize the HMAC | ||
| 195 | if (ctx.used) { | ||
| 196 | mbedtls_md_hmac_reset(&hmac_ctx); | ||
| 197 | } | ||
| 198 | |||
| 199 | ctx.used = true; | ||
| 200 | |||
| 201 | // Store counter in big endian, and increment it | ||
| 202 | ctx.buffer[0] = static_cast<u8>(ctx.counter >> 8); | ||
| 203 | ctx.buffer[1] = static_cast<u8>(ctx.counter >> 0); | ||
| 204 | ctx.counter++; | ||
| 205 | |||
| 206 | // Do HMAC magic | ||
| 207 | mbedtls_md_hmac_update(&hmac_ctx, reinterpret_cast<const unsigned char*>(ctx.buffer.data()), | ||
| 208 | ctx.buffer_size); | ||
| 209 | mbedtls_md_hmac_finish(&hmac_ctx, output.data()); | ||
| 210 | } | ||
| 211 | |||
| 212 | DerivedKeys GenerateKey(const InternalKey& key, const NTAG215File& data) { | ||
| 213 | const auto seed = GetSeed(data); | ||
| 214 | |||
| 215 | // Generate internal seed | ||
| 216 | const std::vector<u8> internal_key = GenerateInternalKey(key, seed); | ||
| 217 | |||
| 218 | // Initialize context | ||
| 219 | CryptoCtx ctx{}; | ||
| 220 | mbedtls_md_context_t hmac_ctx; | ||
| 221 | CryptoInit(ctx, hmac_ctx, key.hmac_key, internal_key); | ||
| 222 | |||
| 223 | // Generate derived keys | ||
| 224 | DerivedKeys derived_keys{}; | ||
| 225 | std::array<DrgbOutput, 2> temp{}; | ||
| 226 | CryptoStep(ctx, hmac_ctx, temp[0]); | ||
| 227 | CryptoStep(ctx, hmac_ctx, temp[1]); | ||
| 228 | memcpy(&derived_keys, temp.data(), sizeof(DerivedKeys)); | ||
| 229 | |||
| 230 | // Cleanup context | ||
| 231 | mbedtls_md_free(&hmac_ctx); | ||
| 232 | |||
| 233 | return derived_keys; | ||
| 234 | } | ||
| 235 | |||
| 236 | void Cipher(const DerivedKeys& keys, const NTAG215File& in_data, NTAG215File& out_data) { | ||
| 237 | mbedtls_aes_context aes; | ||
| 238 | std::size_t nc_off = 0; | ||
| 239 | std::array<u8, sizeof(keys.aes_iv)> nonce_counter{}; | ||
| 240 | std::array<u8, sizeof(keys.aes_iv)> stream_block{}; | ||
| 241 | |||
| 242 | const auto aes_key_size = static_cast<u32>(keys.aes_key.size() * 8); | ||
| 243 | mbedtls_aes_setkey_enc(&aes, keys.aes_key.data(), aes_key_size); | ||
| 244 | memcpy(nonce_counter.data(), keys.aes_iv.data(), sizeof(keys.aes_iv)); | ||
| 245 | |||
| 246 | constexpr std::size_t encrypted_data_size = HMAC_TAG_START - SETTINGS_START; | ||
| 247 | mbedtls_aes_crypt_ctr(&aes, encrypted_data_size, &nc_off, nonce_counter.data(), | ||
| 248 | stream_block.data(), | ||
| 249 | reinterpret_cast<const unsigned char*>(&in_data.settings), | ||
| 250 | reinterpret_cast<unsigned char*>(&out_data.settings)); | ||
| 251 | |||
| 252 | // Copy the rest of the data directly | ||
| 253 | out_data.uuid2 = in_data.uuid2; | ||
| 254 | out_data.static_lock = in_data.static_lock; | ||
| 255 | out_data.compability_container = in_data.compability_container; | ||
| 256 | |||
| 257 | out_data.constant_value = in_data.constant_value; | ||
| 258 | out_data.write_counter = in_data.write_counter; | ||
| 259 | |||
| 260 | out_data.uuid = in_data.uuid; | ||
| 261 | out_data.model_info = in_data.model_info; | ||
| 262 | out_data.keygen_salt = in_data.keygen_salt; | ||
| 263 | out_data.dynamic_lock = in_data.dynamic_lock; | ||
| 264 | out_data.CFG0 = in_data.CFG0; | ||
| 265 | out_data.CFG1 = in_data.CFG1; | ||
| 266 | out_data.password = in_data.password; | ||
| 267 | } | ||
| 268 | |||
| 269 | bool LoadKeys(InternalKey& locked_secret, InternalKey& unfixed_info) { | ||
| 270 | const auto yuzu_keys_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::KeysDir); | ||
| 271 | |||
| 272 | const Common::FS::IOFile keys_file{yuzu_keys_dir / "key_retail.bin", | ||
| 273 | Common::FS::FileAccessMode::Read, | ||
| 274 | Common::FS::FileType::BinaryFile}; | ||
| 275 | |||
| 276 | if (!keys_file.IsOpen()) { | ||
| 277 | LOG_ERROR(Service_NFP, "No keys detected"); | ||
| 278 | return false; | ||
| 279 | } | ||
| 280 | |||
| 281 | if (keys_file.Read(unfixed_info) != 1) { | ||
| 282 | LOG_ERROR(Service_NFP, "Failed to read unfixed_info"); | ||
| 283 | return false; | ||
| 284 | } | ||
| 285 | if (keys_file.Read(locked_secret) != 1) { | ||
| 286 | LOG_ERROR(Service_NFP, "Failed to read locked-secret"); | ||
| 287 | return false; | ||
| 288 | } | ||
| 289 | |||
| 290 | return true; | ||
| 291 | } | ||
| 292 | |||
| 293 | bool DecodeAmiibo(const EncryptedNTAG215File& encrypted_tag_data, NTAG215File& tag_data) { | ||
| 294 | InternalKey locked_secret{}; | ||
| 295 | InternalKey unfixed_info{}; | ||
| 296 | |||
| 297 | if (!LoadKeys(locked_secret, unfixed_info)) { | ||
| 298 | return false; | ||
| 299 | } | ||
| 300 | |||
| 301 | // Generate keys | ||
| 302 | NTAG215File encoded_data = NfcDataToEncodedData(encrypted_tag_data); | ||
| 303 | const auto data_keys = GenerateKey(unfixed_info, encoded_data); | ||
| 304 | const auto tag_keys = GenerateKey(locked_secret, encoded_data); | ||
| 305 | |||
| 306 | // Decrypt | ||
| 307 | Cipher(data_keys, encoded_data, tag_data); | ||
| 308 | |||
| 309 | // Regenerate tag HMAC. Note: order matters, data HMAC depends on tag HMAC! | ||
| 310 | constexpr std::size_t input_length = DYNAMIC_LOCK_START - UUID_START; | ||
| 311 | mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), tag_keys.hmac_key.data(), | ||
| 312 | sizeof(HmacKey), reinterpret_cast<const unsigned char*>(&tag_data.uuid), | ||
| 313 | input_length, reinterpret_cast<unsigned char*>(&tag_data.hmac_tag)); | ||
| 314 | |||
| 315 | // Regenerate data HMAC | ||
| 316 | constexpr std::size_t input_length2 = DYNAMIC_LOCK_START - WRITE_COUNTER_START; | ||
| 317 | mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), data_keys.hmac_key.data(), | ||
| 318 | sizeof(HmacKey), | ||
| 319 | reinterpret_cast<const unsigned char*>(&tag_data.write_counter), input_length2, | ||
| 320 | reinterpret_cast<unsigned char*>(&tag_data.hmac_data)); | ||
| 321 | |||
| 322 | if (tag_data.hmac_data != encrypted_tag_data.user_memory.hmac_data) { | ||
| 323 | LOG_ERROR(Service_NFP, "hmac_data doesn't match"); | ||
| 324 | return false; | ||
| 325 | } | ||
| 326 | |||
| 327 | if (tag_data.hmac_tag != encrypted_tag_data.user_memory.hmac_tag) { | ||
| 328 | LOG_ERROR(Service_NFP, "hmac_tag doesn't match"); | ||
| 329 | return false; | ||
| 330 | } | ||
| 331 | |||
| 332 | return true; | ||
| 333 | } | ||
| 334 | |||
| 335 | bool EncodeAmiibo(const NTAG215File& tag_data, EncryptedNTAG215File& encrypted_tag_data) { | ||
| 336 | InternalKey locked_secret{}; | ||
| 337 | InternalKey unfixed_info{}; | ||
| 338 | |||
| 339 | if (!LoadKeys(locked_secret, unfixed_info)) { | ||
| 340 | return false; | ||
| 341 | } | ||
| 342 | |||
| 343 | // Generate keys | ||
| 344 | const auto data_keys = GenerateKey(unfixed_info, tag_data); | ||
| 345 | const auto tag_keys = GenerateKey(locked_secret, tag_data); | ||
| 346 | |||
| 347 | NTAG215File encoded_tag_data{}; | ||
| 348 | |||
| 349 | // Generate tag HMAC | ||
| 350 | constexpr std::size_t input_length = DYNAMIC_LOCK_START - UUID_START; | ||
| 351 | constexpr std::size_t input_length2 = HMAC_TAG_START - WRITE_COUNTER_START; | ||
| 352 | mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), tag_keys.hmac_key.data(), | ||
| 353 | sizeof(HmacKey), reinterpret_cast<const unsigned char*>(&tag_data.uuid), | ||
| 354 | input_length, reinterpret_cast<unsigned char*>(&encoded_tag_data.hmac_tag)); | ||
| 355 | |||
| 356 | // Init mbedtls HMAC context | ||
| 357 | mbedtls_md_context_t ctx; | ||
| 358 | mbedtls_md_init(&ctx); | ||
| 359 | mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 1); | ||
| 360 | |||
| 361 | // Generate data HMAC | ||
| 362 | mbedtls_md_hmac_starts(&ctx, data_keys.hmac_key.data(), sizeof(HmacKey)); | ||
| 363 | mbedtls_md_hmac_update(&ctx, reinterpret_cast<const unsigned char*>(&tag_data.write_counter), | ||
| 364 | input_length2); // Data | ||
| 365 | mbedtls_md_hmac_update(&ctx, reinterpret_cast<unsigned char*>(&encoded_tag_data.hmac_tag), | ||
| 366 | sizeof(HashData)); // Tag HMAC | ||
| 367 | mbedtls_md_hmac_update(&ctx, reinterpret_cast<const unsigned char*>(&tag_data.uuid), | ||
| 368 | input_length); | ||
| 369 | mbedtls_md_hmac_finish(&ctx, reinterpret_cast<unsigned char*>(&encoded_tag_data.hmac_data)); | ||
| 370 | |||
| 371 | // HMAC cleanup | ||
| 372 | mbedtls_md_free(&ctx); | ||
| 373 | |||
| 374 | // Encrypt | ||
| 375 | Cipher(data_keys, tag_data, encoded_tag_data); | ||
| 376 | |||
| 377 | // Convert back to hardware | ||
| 378 | encrypted_tag_data = EncodedDataToNfcData(encoded_tag_data); | ||
| 379 | |||
| 380 | return true; | ||
| 381 | } | ||
| 382 | |||
| 383 | } // namespace Service::NFP::AmiiboCrypto | ||
diff --git a/src/core/hle/service/nfp/amiibo_crypto.h b/src/core/hle/service/nfp/amiibo_crypto.h new file mode 100644 index 000000000..af7335912 --- /dev/null +++ b/src/core/hle/service/nfp/amiibo_crypto.h | |||
| @@ -0,0 +1,98 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-3.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <array> | ||
| 7 | |||
| 8 | #include "core/hle/service/nfp/amiibo_types.h" | ||
| 9 | |||
| 10 | struct mbedtls_md_context_t; | ||
| 11 | |||
| 12 | namespace Service::NFP::AmiiboCrypto { | ||
| 13 | // Byte locations in Service::NFP::NTAG215File | ||
| 14 | constexpr std::size_t HMAC_DATA_START = 0x8; | ||
| 15 | constexpr std::size_t SETTINGS_START = 0x2c; | ||
| 16 | constexpr std::size_t WRITE_COUNTER_START = 0x29; | ||
| 17 | constexpr std::size_t HMAC_TAG_START = 0x1B4; | ||
| 18 | constexpr std::size_t UUID_START = 0x1D4; | ||
| 19 | constexpr std::size_t DYNAMIC_LOCK_START = 0x208; | ||
| 20 | |||
| 21 | using HmacKey = std::array<u8, 0x10>; | ||
| 22 | using DrgbOutput = std::array<u8, 0x20>; | ||
| 23 | |||
| 24 | struct HashSeed { | ||
| 25 | u16 magic; | ||
| 26 | std::array<u8, 0xE> padding; | ||
| 27 | std::array<u8, 0x8> uuid1; | ||
| 28 | std::array<u8, 0x8> uuid2; | ||
| 29 | std::array<u8, 0x20> keygen_salt; | ||
| 30 | }; | ||
| 31 | static_assert(sizeof(HashSeed) == 0x40, "HashSeed is an invalid size"); | ||
| 32 | |||
| 33 | struct InternalKey { | ||
| 34 | HmacKey hmac_key; | ||
| 35 | std::array<char, 0xE> type_string; | ||
| 36 | u8 reserved; | ||
| 37 | u8 magic_length; | ||
| 38 | std::array<u8, 0x10> magic_bytes; | ||
| 39 | std::array<u8, 0x20> xor_pad; | ||
| 40 | }; | ||
| 41 | static_assert(sizeof(InternalKey) == 0x50, "InternalKey is an invalid size"); | ||
| 42 | static_assert(std::is_trivially_copyable_v<InternalKey>, "InternalKey must be trivially copyable."); | ||
| 43 | |||
| 44 | struct CryptoCtx { | ||
| 45 | std::array<char, 480> buffer; | ||
| 46 | bool used; | ||
| 47 | std::size_t buffer_size; | ||
| 48 | s16 counter; | ||
| 49 | }; | ||
| 50 | |||
| 51 | struct DerivedKeys { | ||
| 52 | std::array<u8, 0x10> aes_key; | ||
| 53 | std::array<u8, 0x10> aes_iv; | ||
| 54 | std::array<u8, 0x10> hmac_key; | ||
| 55 | }; | ||
| 56 | static_assert(sizeof(DerivedKeys) == 0x30, "DerivedKeys is an invalid size"); | ||
| 57 | |||
| 58 | /// Validates that the amiibo file is not corrupted | ||
| 59 | bool IsAmiiboValid(const EncryptedNTAG215File& ntag_file); | ||
| 60 | |||
| 61 | /// Converts from encrypted file format to encoded file format | ||
| 62 | NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data); | ||
| 63 | |||
| 64 | /// Converts from encoded file format to encrypted file format | ||
| 65 | EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data); | ||
| 66 | |||
| 67 | /// Returns password needed to allow write access to protected memory | ||
| 68 | u32 GetTagPassword(const TagUuid& uuid); | ||
| 69 | |||
| 70 | // Generates Seed needed for key derivation | ||
| 71 | HashSeed GetSeed(const NTAG215File& data); | ||
| 72 | |||
| 73 | // Middle step on the generation of derived keys | ||
| 74 | std::vector<u8> GenerateInternalKey(const InternalKey& key, const HashSeed& seed); | ||
| 75 | |||
| 76 | // Initializes mbedtls context | ||
| 77 | void CryptoInit(CryptoCtx& ctx, mbedtls_md_context_t& hmac_ctx, const HmacKey& hmac_key, | ||
| 78 | const std::vector<u8>& seed); | ||
| 79 | |||
| 80 | // Feeds data to mbedtls context to generate the derived key | ||
| 81 | void CryptoStep(CryptoCtx& ctx, mbedtls_md_context_t& hmac_ctx, DrgbOutput& output); | ||
| 82 | |||
| 83 | // Generates the derived key from amiibo data | ||
| 84 | DerivedKeys GenerateKey(const InternalKey& key, const NTAG215File& data); | ||
| 85 | |||
| 86 | // Encodes or decodes amiibo data | ||
| 87 | void Cipher(const DerivedKeys& keys, const NTAG215File& in_data, NTAG215File& out_data); | ||
| 88 | |||
| 89 | /// Loads both amiibo keys from key_retail.bin | ||
| 90 | bool LoadKeys(InternalKey& locked_secret, InternalKey& unfixed_info); | ||
| 91 | |||
| 92 | /// Decodes encripted amiibo data returns true if output is valid | ||
| 93 | bool DecodeAmiibo(const EncryptedNTAG215File& encrypted_tag_data, NTAG215File& tag_data); | ||
| 94 | |||
| 95 | /// Encodes plain amiibo data returns true if output is valid | ||
| 96 | bool EncodeAmiibo(const NTAG215File& tag_data, EncryptedNTAG215File& encrypted_tag_data); | ||
| 97 | |||
| 98 | } // namespace Service::NFP::AmiiboCrypto | ||
diff --git a/src/core/hle/service/nfp/amiibo_types.h b/src/core/hle/service/nfp/amiibo_types.h new file mode 100644 index 000000000..bf2de811a --- /dev/null +++ b/src/core/hle/service/nfp/amiibo_types.h | |||
| @@ -0,0 +1,197 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-3.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <array> | ||
| 7 | |||
| 8 | #include "core/hle/service/mii/types.h" | ||
| 9 | |||
| 10 | namespace Service::NFP { | ||
| 11 | static constexpr std::size_t amiibo_name_length = 0xA; | ||
| 12 | |||
| 13 | enum class ServiceType : u32 { | ||
| 14 | User, | ||
| 15 | Debug, | ||
| 16 | System, | ||
| 17 | }; | ||
| 18 | |||
| 19 | enum class State : u32 { | ||
| 20 | NonInitialized, | ||
| 21 | Initialized, | ||
| 22 | }; | ||
| 23 | |||
| 24 | enum class DeviceState : u32 { | ||
| 25 | Initialized, | ||
| 26 | SearchingForTag, | ||
| 27 | TagFound, | ||
| 28 | TagRemoved, | ||
| 29 | TagMounted, | ||
| 30 | Unaviable, | ||
| 31 | Finalized, | ||
| 32 | }; | ||
| 33 | |||
| 34 | enum class ModelType : u32 { | ||
| 35 | Amiibo, | ||
| 36 | }; | ||
| 37 | |||
| 38 | enum class MountTarget : u32 { | ||
| 39 | Rom, | ||
| 40 | Ram, | ||
| 41 | All, | ||
| 42 | }; | ||
| 43 | |||
| 44 | enum class AmiiboType : u8 { | ||
| 45 | Figure, | ||
| 46 | Card, | ||
| 47 | Yarn, | ||
| 48 | }; | ||
| 49 | |||
| 50 | enum class AmiiboSeries : u8 { | ||
| 51 | SuperSmashBros, | ||
| 52 | SuperMario, | ||
| 53 | ChibiRobo, | ||
| 54 | YoshiWoollyWorld, | ||
| 55 | Splatoon, | ||
| 56 | AnimalCrossing, | ||
| 57 | EightBitMario, | ||
| 58 | Skylanders, | ||
| 59 | Unknown8, | ||
| 60 | TheLegendOfZelda, | ||
| 61 | ShovelKnight, | ||
| 62 | Unknown11, | ||
| 63 | Kiby, | ||
| 64 | Pokemon, | ||
| 65 | MarioSportsSuperstars, | ||
| 66 | MonsterHunter, | ||
| 67 | BoxBoy, | ||
| 68 | Pikmin, | ||
| 69 | FireEmblem, | ||
| 70 | Metroid, | ||
| 71 | Others, | ||
| 72 | MegaMan, | ||
| 73 | Diablo, | ||
| 74 | }; | ||
| 75 | |||
| 76 | using TagUuid = std::array<u8, 10>; | ||
| 77 | using HashData = std::array<u8, 0x20>; | ||
| 78 | using ApplicationArea = std::array<u8, 0xD8>; | ||
| 79 | |||
| 80 | struct AmiiboDate { | ||
| 81 | u16 raw_date{}; | ||
| 82 | |||
| 83 | u16 GetYear() const { | ||
| 84 | return static_cast<u16>(((raw_date & 0xFE00) >> 9) + 2000); | ||
| 85 | } | ||
| 86 | u8 GetMonth() const { | ||
| 87 | return static_cast<u8>(((raw_date & 0x01E0) >> 5) - 1); | ||
| 88 | } | ||
| 89 | u8 GetDay() const { | ||
| 90 | return static_cast<u8>(raw_date & 0x001F); | ||
| 91 | } | ||
| 92 | }; | ||
| 93 | static_assert(sizeof(AmiiboDate) == 2, "AmiiboDate is an invalid size"); | ||
| 94 | |||
| 95 | struct Settings { | ||
| 96 | union { | ||
| 97 | u8 raw{}; | ||
| 98 | |||
| 99 | BitField<4, 1, u8> amiibo_initialized; | ||
| 100 | BitField<5, 1, u8> appdata_initialized; | ||
| 101 | }; | ||
| 102 | }; | ||
| 103 | static_assert(sizeof(Settings) == 1, "AmiiboDate is an invalid size"); | ||
| 104 | |||
| 105 | struct AmiiboSettings { | ||
| 106 | Settings settings; | ||
| 107 | u8 country_code_id; | ||
| 108 | u16_be crc_counter; // Incremented each time crc is changed | ||
| 109 | AmiiboDate init_date; | ||
| 110 | AmiiboDate write_date; | ||
| 111 | u32_be crc; | ||
| 112 | std::array<u16_be, amiibo_name_length> amiibo_name; // UTF-16 text | ||
| 113 | }; | ||
| 114 | static_assert(sizeof(AmiiboSettings) == 0x20, "AmiiboSettings is an invalid size"); | ||
| 115 | |||
| 116 | struct AmiiboModelInfo { | ||
| 117 | u16 character_id; | ||
| 118 | u8 character_variant; | ||
| 119 | AmiiboType amiibo_type; | ||
| 120 | u16 model_number; | ||
| 121 | AmiiboSeries series; | ||
| 122 | u8 constant_value; // Must be 02 | ||
| 123 | INSERT_PADDING_BYTES(0x4); // Unknown | ||
| 124 | }; | ||
| 125 | static_assert(sizeof(AmiiboModelInfo) == 0xC, "AmiiboModelInfo is an invalid size"); | ||
| 126 | |||
| 127 | struct NTAG215Password { | ||
| 128 | u32 PWD; // Password to allow write access | ||
| 129 | u16 PACK; // Password acknowledge reply | ||
| 130 | u16 RFUI; // Reserved for future use | ||
| 131 | }; | ||
| 132 | static_assert(sizeof(NTAG215Password) == 0x8, "NTAG215Password is an invalid size"); | ||
| 133 | |||
| 134 | #pragma pack(1) | ||
| 135 | struct EncryptedAmiiboFile { | ||
| 136 | u8 constant_value; // Must be A5 | ||
| 137 | u16 write_counter; // Number of times the amiibo has been written? | ||
| 138 | INSERT_PADDING_BYTES(0x1); // Unknown 1 | ||
| 139 | AmiiboSettings settings; // Encrypted amiibo settings | ||
| 140 | HashData hmac_tag; // Hash | ||
| 141 | AmiiboModelInfo model_info; // Encrypted amiibo model info | ||
| 142 | HashData keygen_salt; // Salt | ||
| 143 | HashData hmac_data; // Hash | ||
| 144 | Service::Mii::Ver3StoreData owner_mii; // Encrypted Mii data | ||
| 145 | u64_be title_id; // Encrypted Game id | ||
| 146 | u16_be applicaton_write_counter; // Encrypted Counter | ||
| 147 | u32_be application_area_id; // Encrypted Game id | ||
| 148 | std::array<u8, 0x2> unknown; | ||
| 149 | HashData hash; // Probably a SHA256-HMAC hash? | ||
| 150 | ApplicationArea application_area; // Encrypted Game data | ||
| 151 | }; | ||
| 152 | static_assert(sizeof(EncryptedAmiiboFile) == 0x1F8, "AmiiboFile is an invalid size"); | ||
| 153 | |||
| 154 | struct NTAG215File { | ||
| 155 | std::array<u8, 0x2> uuid2; | ||
| 156 | u16 static_lock; // Set defined pages as read only | ||
| 157 | u32 compability_container; // Defines available memory | ||
| 158 | HashData hmac_data; // Hash | ||
| 159 | u8 constant_value; // Must be A5 | ||
| 160 | u16 write_counter; // Number of times the amiibo has been written? | ||
| 161 | INSERT_PADDING_BYTES(0x1); // Unknown 1 | ||
| 162 | AmiiboSettings settings; | ||
| 163 | Service::Mii::Ver3StoreData owner_mii; // Encrypted Mii data | ||
| 164 | u64_be title_id; | ||
| 165 | u16_be applicaton_write_counter; // Encrypted Counter | ||
| 166 | u32_be application_area_id; | ||
| 167 | std::array<u8, 0x2> unknown; | ||
| 168 | HashData hash; // Probably a SHA256-HMAC hash? | ||
| 169 | ApplicationArea application_area; // Encrypted Game data | ||
| 170 | HashData hmac_tag; // Hash | ||
| 171 | std::array<u8, 0x8> uuid; | ||
| 172 | AmiiboModelInfo model_info; | ||
| 173 | HashData keygen_salt; // Salt | ||
| 174 | u32 dynamic_lock; // Dynamic lock | ||
| 175 | u32 CFG0; // Defines memory protected by password | ||
| 176 | u32 CFG1; // Defines number of verification attempts | ||
| 177 | NTAG215Password password; // Password data | ||
| 178 | }; | ||
| 179 | static_assert(sizeof(NTAG215File) == 0x21C, "NTAG215File is an invalid size"); | ||
| 180 | static_assert(std::is_trivially_copyable_v<NTAG215File>, "NTAG215File must be trivially copyable."); | ||
| 181 | #pragma pack() | ||
| 182 | |||
| 183 | struct EncryptedNTAG215File { | ||
| 184 | TagUuid uuid; // Unique serial number | ||
| 185 | u16 static_lock; // Set defined pages as read only | ||
| 186 | u32 compability_container; // Defines available memory | ||
| 187 | EncryptedAmiiboFile user_memory; // Writable data | ||
| 188 | u32 dynamic_lock; // Dynamic lock | ||
| 189 | u32 CFG0; // Defines memory protected by password | ||
| 190 | u32 CFG1; // Defines number of verification attempts | ||
| 191 | NTAG215Password password; // Password data | ||
| 192 | }; | ||
| 193 | static_assert(sizeof(EncryptedNTAG215File) == 0x21C, "EncryptedNTAG215File is an invalid size"); | ||
| 194 | static_assert(std::is_trivially_copyable_v<EncryptedNTAG215File>, | ||
| 195 | "EncryptedNTAG215File must be trivially copyable."); | ||
| 196 | |||
| 197 | } // namespace Service::NFP | ||
diff --git a/src/core/hle/service/nfp/nfp.cpp b/src/core/hle/service/nfp/nfp.cpp index 6c5b41dd1..e0ed3f771 100644 --- a/src/core/hle/service/nfp/nfp.cpp +++ b/src/core/hle/service/nfp/nfp.cpp | |||
| @@ -4,7 +4,10 @@ | |||
| 4 | #include <array> | 4 | #include <array> |
| 5 | #include <atomic> | 5 | #include <atomic> |
| 6 | 6 | ||
| 7 | #include "common/fs/file.h" | ||
| 8 | #include "common/fs/path_util.h" | ||
| 7 | #include "common/logging/log.h" | 9 | #include "common/logging/log.h" |
| 10 | #include "common/string_util.h" | ||
| 8 | #include "core/core.h" | 11 | #include "core/core.h" |
| 9 | #include "core/hid/emulated_controller.h" | 12 | #include "core/hid/emulated_controller.h" |
| 10 | #include "core/hid/hid_core.h" | 13 | #include "core/hid/hid_core.h" |
| @@ -12,6 +15,7 @@ | |||
| 12 | #include "core/hle/ipc_helpers.h" | 15 | #include "core/hle/ipc_helpers.h" |
| 13 | #include "core/hle/kernel/k_event.h" | 16 | #include "core/hle/kernel/k_event.h" |
| 14 | #include "core/hle/service/mii/mii_manager.h" | 17 | #include "core/hle/service/mii/mii_manager.h" |
| 18 | #include "core/hle/service/nfp/amiibo_crypto.h" | ||
| 15 | #include "core/hle/service/nfp/nfp.h" | 19 | #include "core/hle/service/nfp/nfp.h" |
| 16 | #include "core/hle/service/nfp/nfp_user.h" | 20 | #include "core/hle/service/nfp/nfp_user.h" |
| 17 | 21 | ||
| @@ -19,12 +23,14 @@ namespace Service::NFP { | |||
| 19 | namespace ErrCodes { | 23 | namespace ErrCodes { |
| 20 | constexpr Result DeviceNotFound(ErrorModule::NFP, 64); | 24 | constexpr Result DeviceNotFound(ErrorModule::NFP, 64); |
| 21 | constexpr Result WrongDeviceState(ErrorModule::NFP, 73); | 25 | constexpr Result WrongDeviceState(ErrorModule::NFP, 73); |
| 26 | constexpr Result NfcDisabled(ErrorModule::NFP, 80); | ||
| 27 | constexpr Result WriteAmiiboFailed(ErrorModule::NFP, 88); | ||
| 28 | constexpr Result TagRemoved(ErrorModule::NFP, 97); | ||
| 22 | constexpr Result ApplicationAreaIsNotInitialized(ErrorModule::NFP, 128); | 29 | constexpr Result ApplicationAreaIsNotInitialized(ErrorModule::NFP, 128); |
| 30 | constexpr Result WrongApplicationAreaId(ErrorModule::NFP, 152); | ||
| 23 | constexpr Result ApplicationAreaExist(ErrorModule::NFP, 168); | 31 | constexpr Result ApplicationAreaExist(ErrorModule::NFP, 168); |
| 24 | } // namespace ErrCodes | 32 | } // namespace ErrCodes |
| 25 | 33 | ||
| 26 | constexpr u32 ApplicationAreaSize = 0xD8; | ||
| 27 | |||
| 28 | IUser::IUser(Module::Interface& nfp_interface_, Core::System& system_) | 34 | IUser::IUser(Module::Interface& nfp_interface_, Core::System& system_) |
| 29 | : ServiceFramework{system_, "NFP::IUser"}, service_context{system_, service_name}, | 35 | : ServiceFramework{system_, "NFP::IUser"}, service_context{system_, service_name}, |
| 30 | nfp_interface{nfp_interface_} { | 36 | nfp_interface{nfp_interface_} { |
| @@ -39,7 +45,7 @@ IUser::IUser(Module::Interface& nfp_interface_, Core::System& system_) | |||
| 39 | {7, &IUser::OpenApplicationArea, "OpenApplicationArea"}, | 45 | {7, &IUser::OpenApplicationArea, "OpenApplicationArea"}, |
| 40 | {8, &IUser::GetApplicationArea, "GetApplicationArea"}, | 46 | {8, &IUser::GetApplicationArea, "GetApplicationArea"}, |
| 41 | {9, &IUser::SetApplicationArea, "SetApplicationArea"}, | 47 | {9, &IUser::SetApplicationArea, "SetApplicationArea"}, |
| 42 | {10, nullptr, "Flush"}, | 48 | {10, &IUser::Flush, "Flush"}, |
| 43 | {11, nullptr, "Restore"}, | 49 | {11, nullptr, "Restore"}, |
| 44 | {12, &IUser::CreateApplicationArea, "CreateApplicationArea"}, | 50 | {12, &IUser::CreateApplicationArea, "CreateApplicationArea"}, |
| 45 | {13, &IUser::GetTagInfo, "GetTagInfo"}, | 51 | {13, &IUser::GetTagInfo, "GetTagInfo"}, |
| @@ -53,7 +59,7 @@ IUser::IUser(Module::Interface& nfp_interface_, Core::System& system_) | |||
| 53 | {21, &IUser::GetNpadId, "GetNpadId"}, | 59 | {21, &IUser::GetNpadId, "GetNpadId"}, |
| 54 | {22, &IUser::GetApplicationAreaSize, "GetApplicationAreaSize"}, | 60 | {22, &IUser::GetApplicationAreaSize, "GetApplicationAreaSize"}, |
| 55 | {23, &IUser::AttachAvailabilityChangeEvent, "AttachAvailabilityChangeEvent"}, | 61 | {23, &IUser::AttachAvailabilityChangeEvent, "AttachAvailabilityChangeEvent"}, |
| 56 | {24, nullptr, "RecreateApplicationArea"}, | 62 | {24, &IUser::RecreateApplicationArea, "RecreateApplicationArea"}, |
| 57 | }; | 63 | }; |
| 58 | RegisterHandlers(functions); | 64 | RegisterHandlers(functions); |
| 59 | 65 | ||
| @@ -87,11 +93,23 @@ void IUser::Finalize(Kernel::HLERequestContext& ctx) { | |||
| 87 | void IUser::ListDevices(Kernel::HLERequestContext& ctx) { | 93 | void IUser::ListDevices(Kernel::HLERequestContext& ctx) { |
| 88 | LOG_INFO(Service_NFP, "called"); | 94 | LOG_INFO(Service_NFP, "called"); |
| 89 | 95 | ||
| 96 | if (state == State::NonInitialized) { | ||
| 97 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 98 | rb.Push(ErrCodes::NfcDisabled); | ||
| 99 | return; | ||
| 100 | } | ||
| 101 | |||
| 90 | std::vector<u64> devices; | 102 | std::vector<u64> devices; |
| 91 | 103 | ||
| 92 | // TODO(german77): Loop through all interfaces | 104 | // TODO(german77): Loop through all interfaces |
| 93 | devices.push_back(nfp_interface.GetHandle()); | 105 | devices.push_back(nfp_interface.GetHandle()); |
| 94 | 106 | ||
| 107 | if (devices.size() == 0) { | ||
| 108 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 109 | rb.Push(ErrCodes::DeviceNotFound); | ||
| 110 | return; | ||
| 111 | } | ||
| 112 | |||
| 95 | ctx.WriteBuffer(devices); | 113 | ctx.WriteBuffer(devices); |
| 96 | 114 | ||
| 97 | IPC::ResponseBuilder rb{ctx, 3}; | 115 | IPC::ResponseBuilder rb{ctx, 3}; |
| @@ -105,6 +123,12 @@ void IUser::StartDetection(Kernel::HLERequestContext& ctx) { | |||
| 105 | const auto nfp_protocol{rp.Pop<s32>()}; | 123 | const auto nfp_protocol{rp.Pop<s32>()}; |
| 106 | LOG_INFO(Service_NFP, "called, device_handle={}, nfp_protocol={}", device_handle, nfp_protocol); | 124 | LOG_INFO(Service_NFP, "called, device_handle={}, nfp_protocol={}", device_handle, nfp_protocol); |
| 107 | 125 | ||
| 126 | if (state == State::NonInitialized) { | ||
| 127 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 128 | rb.Push(ErrCodes::NfcDisabled); | ||
| 129 | return; | ||
| 130 | } | ||
| 131 | |||
| 108 | // TODO(german77): Loop through all interfaces | 132 | // TODO(german77): Loop through all interfaces |
| 109 | if (device_handle == nfp_interface.GetHandle()) { | 133 | if (device_handle == nfp_interface.GetHandle()) { |
| 110 | const auto result = nfp_interface.StartDetection(nfp_protocol); | 134 | const auto result = nfp_interface.StartDetection(nfp_protocol); |
| @@ -124,6 +148,12 @@ void IUser::StopDetection(Kernel::HLERequestContext& ctx) { | |||
| 124 | const auto device_handle{rp.Pop<u64>()}; | 148 | const auto device_handle{rp.Pop<u64>()}; |
| 125 | LOG_INFO(Service_NFP, "called, device_handle={}", device_handle); | 149 | LOG_INFO(Service_NFP, "called, device_handle={}", device_handle); |
| 126 | 150 | ||
| 151 | if (state == State::NonInitialized) { | ||
| 152 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 153 | rb.Push(ErrCodes::NfcDisabled); | ||
| 154 | return; | ||
| 155 | } | ||
| 156 | |||
| 127 | // TODO(german77): Loop through all interfaces | 157 | // TODO(german77): Loop through all interfaces |
| 128 | if (device_handle == nfp_interface.GetHandle()) { | 158 | if (device_handle == nfp_interface.GetHandle()) { |
| 129 | const auto result = nfp_interface.StopDetection(); | 159 | const auto result = nfp_interface.StopDetection(); |
| @@ -146,6 +176,12 @@ void IUser::Mount(Kernel::HLERequestContext& ctx) { | |||
| 146 | LOG_INFO(Service_NFP, "called, device_handle={}, model_type={}, mount_target={}", device_handle, | 176 | LOG_INFO(Service_NFP, "called, device_handle={}, model_type={}, mount_target={}", device_handle, |
| 147 | model_type, mount_target); | 177 | model_type, mount_target); |
| 148 | 178 | ||
| 179 | if (state == State::NonInitialized) { | ||
| 180 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 181 | rb.Push(ErrCodes::NfcDisabled); | ||
| 182 | return; | ||
| 183 | } | ||
| 184 | |||
| 149 | // TODO(german77): Loop through all interfaces | 185 | // TODO(german77): Loop through all interfaces |
| 150 | if (device_handle == nfp_interface.GetHandle()) { | 186 | if (device_handle == nfp_interface.GetHandle()) { |
| 151 | const auto result = nfp_interface.Mount(); | 187 | const auto result = nfp_interface.Mount(); |
| @@ -165,6 +201,12 @@ void IUser::Unmount(Kernel::HLERequestContext& ctx) { | |||
| 165 | const auto device_handle{rp.Pop<u64>()}; | 201 | const auto device_handle{rp.Pop<u64>()}; |
| 166 | LOG_INFO(Service_NFP, "called, device_handle={}", device_handle); | 202 | LOG_INFO(Service_NFP, "called, device_handle={}", device_handle); |
| 167 | 203 | ||
| 204 | if (state == State::NonInitialized) { | ||
| 205 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 206 | rb.Push(ErrCodes::NfcDisabled); | ||
| 207 | return; | ||
| 208 | } | ||
| 209 | |||
| 168 | // TODO(german77): Loop through all interfaces | 210 | // TODO(german77): Loop through all interfaces |
| 169 | if (device_handle == nfp_interface.GetHandle()) { | 211 | if (device_handle == nfp_interface.GetHandle()) { |
| 170 | const auto result = nfp_interface.Unmount(); | 212 | const auto result = nfp_interface.Unmount(); |
| @@ -186,6 +228,12 @@ void IUser::OpenApplicationArea(Kernel::HLERequestContext& ctx) { | |||
| 186 | LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}, access_id={}", device_handle, | 228 | LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}, access_id={}", device_handle, |
| 187 | access_id); | 229 | access_id); |
| 188 | 230 | ||
| 231 | if (state == State::NonInitialized) { | ||
| 232 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 233 | rb.Push(ErrCodes::NfcDisabled); | ||
| 234 | return; | ||
| 235 | } | ||
| 236 | |||
| 189 | // TODO(german77): Loop through all interfaces | 237 | // TODO(german77): Loop through all interfaces |
| 190 | if (device_handle == nfp_interface.GetHandle()) { | 238 | if (device_handle == nfp_interface.GetHandle()) { |
| 191 | const auto result = nfp_interface.OpenApplicationArea(access_id); | 239 | const auto result = nfp_interface.OpenApplicationArea(access_id); |
| @@ -205,9 +253,15 @@ void IUser::GetApplicationArea(Kernel::HLERequestContext& ctx) { | |||
| 205 | const auto device_handle{rp.Pop<u64>()}; | 253 | const auto device_handle{rp.Pop<u64>()}; |
| 206 | LOG_INFO(Service_NFP, "called, device_handle={}", device_handle); | 254 | LOG_INFO(Service_NFP, "called, device_handle={}", device_handle); |
| 207 | 255 | ||
| 256 | if (state == State::NonInitialized) { | ||
| 257 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 258 | rb.Push(ErrCodes::NfcDisabled); | ||
| 259 | return; | ||
| 260 | } | ||
| 261 | |||
| 208 | // TODO(german77): Loop through all interfaces | 262 | // TODO(german77): Loop through all interfaces |
| 209 | if (device_handle == nfp_interface.GetHandle()) { | 263 | if (device_handle == nfp_interface.GetHandle()) { |
| 210 | std::vector<u8> data{}; | 264 | ApplicationArea data{}; |
| 211 | const auto result = nfp_interface.GetApplicationArea(data); | 265 | const auto result = nfp_interface.GetApplicationArea(data); |
| 212 | ctx.WriteBuffer(data); | 266 | ctx.WriteBuffer(data); |
| 213 | IPC::ResponseBuilder rb{ctx, 3}; | 267 | IPC::ResponseBuilder rb{ctx, 3}; |
| @@ -229,6 +283,12 @@ void IUser::SetApplicationArea(Kernel::HLERequestContext& ctx) { | |||
| 229 | LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}, data_size={}", device_handle, | 283 | LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}, data_size={}", device_handle, |
| 230 | data.size()); | 284 | data.size()); |
| 231 | 285 | ||
| 286 | if (state == State::NonInitialized) { | ||
| 287 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 288 | rb.Push(ErrCodes::NfcDisabled); | ||
| 289 | return; | ||
| 290 | } | ||
| 291 | |||
| 232 | // TODO(german77): Loop through all interfaces | 292 | // TODO(german77): Loop through all interfaces |
| 233 | if (device_handle == nfp_interface.GetHandle()) { | 293 | if (device_handle == nfp_interface.GetHandle()) { |
| 234 | const auto result = nfp_interface.SetApplicationArea(data); | 294 | const auto result = nfp_interface.SetApplicationArea(data); |
| @@ -243,6 +303,31 @@ void IUser::SetApplicationArea(Kernel::HLERequestContext& ctx) { | |||
| 243 | rb.Push(ErrCodes::DeviceNotFound); | 303 | rb.Push(ErrCodes::DeviceNotFound); |
| 244 | } | 304 | } |
| 245 | 305 | ||
| 306 | void IUser::Flush(Kernel::HLERequestContext& ctx) { | ||
| 307 | IPC::RequestParser rp{ctx}; | ||
| 308 | const auto device_handle{rp.Pop<u64>()}; | ||
| 309 | LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}", device_handle); | ||
| 310 | |||
| 311 | if (state == State::NonInitialized) { | ||
| 312 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 313 | rb.Push(ErrCodes::NfcDisabled); | ||
| 314 | return; | ||
| 315 | } | ||
| 316 | |||
| 317 | // TODO(german77): Loop through all interfaces | ||
| 318 | if (device_handle == nfp_interface.GetHandle()) { | ||
| 319 | const auto result = nfp_interface.Flush(); | ||
| 320 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 321 | rb.Push(result); | ||
| 322 | return; | ||
| 323 | } | ||
| 324 | |||
| 325 | LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle); | ||
| 326 | |||
| 327 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 328 | rb.Push(ErrCodes::DeviceNotFound); | ||
| 329 | } | ||
| 330 | |||
| 246 | void IUser::CreateApplicationArea(Kernel::HLERequestContext& ctx) { | 331 | void IUser::CreateApplicationArea(Kernel::HLERequestContext& ctx) { |
| 247 | IPC::RequestParser rp{ctx}; | 332 | IPC::RequestParser rp{ctx}; |
| 248 | const auto device_handle{rp.Pop<u64>()}; | 333 | const auto device_handle{rp.Pop<u64>()}; |
| @@ -251,6 +336,12 @@ void IUser::CreateApplicationArea(Kernel::HLERequestContext& ctx) { | |||
| 251 | LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}, data_size={}, access_id={}", | 336 | LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}, data_size={}, access_id={}", |
| 252 | device_handle, access_id, data.size()); | 337 | device_handle, access_id, data.size()); |
| 253 | 338 | ||
| 339 | if (state == State::NonInitialized) { | ||
| 340 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 341 | rb.Push(ErrCodes::NfcDisabled); | ||
| 342 | return; | ||
| 343 | } | ||
| 344 | |||
| 254 | // TODO(german77): Loop through all interfaces | 345 | // TODO(german77): Loop through all interfaces |
| 255 | if (device_handle == nfp_interface.GetHandle()) { | 346 | if (device_handle == nfp_interface.GetHandle()) { |
| 256 | const auto result = nfp_interface.CreateApplicationArea(access_id, data); | 347 | const auto result = nfp_interface.CreateApplicationArea(access_id, data); |
| @@ -270,6 +361,12 @@ void IUser::GetTagInfo(Kernel::HLERequestContext& ctx) { | |||
| 270 | const auto device_handle{rp.Pop<u64>()}; | 361 | const auto device_handle{rp.Pop<u64>()}; |
| 271 | LOG_INFO(Service_NFP, "called, device_handle={}", device_handle); | 362 | LOG_INFO(Service_NFP, "called, device_handle={}", device_handle); |
| 272 | 363 | ||
| 364 | if (state == State::NonInitialized) { | ||
| 365 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 366 | rb.Push(ErrCodes::NfcDisabled); | ||
| 367 | return; | ||
| 368 | } | ||
| 369 | |||
| 273 | // TODO(german77): Loop through all interfaces | 370 | // TODO(german77): Loop through all interfaces |
| 274 | if (device_handle == nfp_interface.GetHandle()) { | 371 | if (device_handle == nfp_interface.GetHandle()) { |
| 275 | TagInfo tag_info{}; | 372 | TagInfo tag_info{}; |
| @@ -291,6 +388,12 @@ void IUser::GetRegisterInfo(Kernel::HLERequestContext& ctx) { | |||
| 291 | const auto device_handle{rp.Pop<u64>()}; | 388 | const auto device_handle{rp.Pop<u64>()}; |
| 292 | LOG_INFO(Service_NFP, "called, device_handle={}", device_handle); | 389 | LOG_INFO(Service_NFP, "called, device_handle={}", device_handle); |
| 293 | 390 | ||
| 391 | if (state == State::NonInitialized) { | ||
| 392 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 393 | rb.Push(ErrCodes::NfcDisabled); | ||
| 394 | return; | ||
| 395 | } | ||
| 396 | |||
| 294 | // TODO(german77): Loop through all interfaces | 397 | // TODO(german77): Loop through all interfaces |
| 295 | if (device_handle == nfp_interface.GetHandle()) { | 398 | if (device_handle == nfp_interface.GetHandle()) { |
| 296 | RegisterInfo register_info{}; | 399 | RegisterInfo register_info{}; |
| @@ -312,6 +415,12 @@ void IUser::GetCommonInfo(Kernel::HLERequestContext& ctx) { | |||
| 312 | const auto device_handle{rp.Pop<u64>()}; | 415 | const auto device_handle{rp.Pop<u64>()}; |
| 313 | LOG_INFO(Service_NFP, "called, device_handle={}", device_handle); | 416 | LOG_INFO(Service_NFP, "called, device_handle={}", device_handle); |
| 314 | 417 | ||
| 418 | if (state == State::NonInitialized) { | ||
| 419 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 420 | rb.Push(ErrCodes::NfcDisabled); | ||
| 421 | return; | ||
| 422 | } | ||
| 423 | |||
| 315 | // TODO(german77): Loop through all interfaces | 424 | // TODO(german77): Loop through all interfaces |
| 316 | if (device_handle == nfp_interface.GetHandle()) { | 425 | if (device_handle == nfp_interface.GetHandle()) { |
| 317 | CommonInfo common_info{}; | 426 | CommonInfo common_info{}; |
| @@ -333,6 +442,12 @@ void IUser::GetModelInfo(Kernel::HLERequestContext& ctx) { | |||
| 333 | const auto device_handle{rp.Pop<u64>()}; | 442 | const auto device_handle{rp.Pop<u64>()}; |
| 334 | LOG_INFO(Service_NFP, "called, device_handle={}", device_handle); | 443 | LOG_INFO(Service_NFP, "called, device_handle={}", device_handle); |
| 335 | 444 | ||
| 445 | if (state == State::NonInitialized) { | ||
| 446 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 447 | rb.Push(ErrCodes::NfcDisabled); | ||
| 448 | return; | ||
| 449 | } | ||
| 450 | |||
| 336 | // TODO(german77): Loop through all interfaces | 451 | // TODO(german77): Loop through all interfaces |
| 337 | if (device_handle == nfp_interface.GetHandle()) { | 452 | if (device_handle == nfp_interface.GetHandle()) { |
| 338 | ModelInfo model_info{}; | 453 | ModelInfo model_info{}; |
| @@ -354,6 +469,12 @@ void IUser::AttachActivateEvent(Kernel::HLERequestContext& ctx) { | |||
| 354 | const auto device_handle{rp.Pop<u64>()}; | 469 | const auto device_handle{rp.Pop<u64>()}; |
| 355 | LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle); | 470 | LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle); |
| 356 | 471 | ||
| 472 | if (state == State::NonInitialized) { | ||
| 473 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 474 | rb.Push(ErrCodes::NfcDisabled); | ||
| 475 | return; | ||
| 476 | } | ||
| 477 | |||
| 357 | // TODO(german77): Loop through all interfaces | 478 | // TODO(german77): Loop through all interfaces |
| 358 | if (device_handle == nfp_interface.GetHandle()) { | 479 | if (device_handle == nfp_interface.GetHandle()) { |
| 359 | IPC::ResponseBuilder rb{ctx, 2, 1}; | 480 | IPC::ResponseBuilder rb{ctx, 2, 1}; |
| @@ -373,6 +494,12 @@ void IUser::AttachDeactivateEvent(Kernel::HLERequestContext& ctx) { | |||
| 373 | const auto device_handle{rp.Pop<u64>()}; | 494 | const auto device_handle{rp.Pop<u64>()}; |
| 374 | LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle); | 495 | LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle); |
| 375 | 496 | ||
| 497 | if (state == State::NonInitialized) { | ||
| 498 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 499 | rb.Push(ErrCodes::NfcDisabled); | ||
| 500 | return; | ||
| 501 | } | ||
| 502 | |||
| 376 | // TODO(german77): Loop through all interfaces | 503 | // TODO(german77): Loop through all interfaces |
| 377 | if (device_handle == nfp_interface.GetHandle()) { | 504 | if (device_handle == nfp_interface.GetHandle()) { |
| 378 | IPC::ResponseBuilder rb{ctx, 2, 1}; | 505 | IPC::ResponseBuilder rb{ctx, 2, 1}; |
| @@ -419,6 +546,12 @@ void IUser::GetNpadId(Kernel::HLERequestContext& ctx) { | |||
| 419 | const auto device_handle{rp.Pop<u64>()}; | 546 | const auto device_handle{rp.Pop<u64>()}; |
| 420 | LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle); | 547 | LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle); |
| 421 | 548 | ||
| 549 | if (state == State::NonInitialized) { | ||
| 550 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 551 | rb.Push(ErrCodes::NfcDisabled); | ||
| 552 | return; | ||
| 553 | } | ||
| 554 | |||
| 422 | // TODO(german77): Loop through all interfaces | 555 | // TODO(german77): Loop through all interfaces |
| 423 | if (device_handle == nfp_interface.GetHandle()) { | 556 | if (device_handle == nfp_interface.GetHandle()) { |
| 424 | IPC::ResponseBuilder rb{ctx, 3}; | 557 | IPC::ResponseBuilder rb{ctx, 3}; |
| @@ -442,7 +575,7 @@ void IUser::GetApplicationAreaSize(Kernel::HLERequestContext& ctx) { | |||
| 442 | if (device_handle == nfp_interface.GetHandle()) { | 575 | if (device_handle == nfp_interface.GetHandle()) { |
| 443 | IPC::ResponseBuilder rb{ctx, 3}; | 576 | IPC::ResponseBuilder rb{ctx, 3}; |
| 444 | rb.Push(ResultSuccess); | 577 | rb.Push(ResultSuccess); |
| 445 | rb.Push(ApplicationAreaSize); | 578 | rb.Push(sizeof(ApplicationArea)); |
| 446 | return; | 579 | return; |
| 447 | } | 580 | } |
| 448 | 581 | ||
| @@ -455,11 +588,45 @@ void IUser::GetApplicationAreaSize(Kernel::HLERequestContext& ctx) { | |||
| 455 | void IUser::AttachAvailabilityChangeEvent(Kernel::HLERequestContext& ctx) { | 588 | void IUser::AttachAvailabilityChangeEvent(Kernel::HLERequestContext& ctx) { |
| 456 | LOG_DEBUG(Service_NFP, "(STUBBED) called"); | 589 | LOG_DEBUG(Service_NFP, "(STUBBED) called"); |
| 457 | 590 | ||
| 591 | if (state == State::NonInitialized) { | ||
| 592 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 593 | rb.Push(ErrCodes::NfcDisabled); | ||
| 594 | return; | ||
| 595 | } | ||
| 596 | |||
| 458 | IPC::ResponseBuilder rb{ctx, 2, 1}; | 597 | IPC::ResponseBuilder rb{ctx, 2, 1}; |
| 459 | rb.Push(ResultSuccess); | 598 | rb.Push(ResultSuccess); |
| 460 | rb.PushCopyObjects(availability_change_event->GetReadableEvent()); | 599 | rb.PushCopyObjects(availability_change_event->GetReadableEvent()); |
| 461 | } | 600 | } |
| 462 | 601 | ||
| 602 | void IUser::RecreateApplicationArea(Kernel::HLERequestContext& ctx) { | ||
| 603 | IPC::RequestParser rp{ctx}; | ||
| 604 | const auto device_handle{rp.Pop<u64>()}; | ||
| 605 | const auto access_id{rp.Pop<u32>()}; | ||
| 606 | const auto data{ctx.ReadBuffer()}; | ||
| 607 | LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}, data_size={}, access_id={}", | ||
| 608 | device_handle, access_id, data.size()); | ||
| 609 | |||
| 610 | if (state == State::NonInitialized) { | ||
| 611 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 612 | rb.Push(ErrCodes::NfcDisabled); | ||
| 613 | return; | ||
| 614 | } | ||
| 615 | |||
| 616 | // TODO(german77): Loop through all interfaces | ||
| 617 | if (device_handle == nfp_interface.GetHandle()) { | ||
| 618 | const auto result = nfp_interface.RecreateApplicationArea(access_id, data); | ||
| 619 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 620 | rb.Push(result); | ||
| 621 | return; | ||
| 622 | } | ||
| 623 | |||
| 624 | LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle); | ||
| 625 | |||
| 626 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 627 | rb.Push(ErrCodes::DeviceNotFound); | ||
| 628 | } | ||
| 629 | |||
| 463 | Module::Interface::Interface(std::shared_ptr<Module> module_, Core::System& system_, | 630 | Module::Interface::Interface(std::shared_ptr<Module> module_, Core::System& system_, |
| 464 | const char* name) | 631 | const char* name) |
| 465 | : ServiceFramework{system_, name}, module{std::move(module_)}, | 632 | : ServiceFramework{system_, name}, module{std::move(module_)}, |
| @@ -478,36 +645,42 @@ void Module::Interface::CreateUserInterface(Kernel::HLERequestContext& ctx) { | |||
| 478 | rb.PushIpcInterface<IUser>(*this, system); | 645 | rb.PushIpcInterface<IUser>(*this, system); |
| 479 | } | 646 | } |
| 480 | 647 | ||
| 481 | bool Module::Interface::LoadAmiibo(const std::vector<u8>& buffer) { | 648 | bool Module::Interface::LoadAmiiboFile(const std::string& filename) { |
| 482 | if (device_state != DeviceState::SearchingForTag) { | ||
| 483 | LOG_ERROR(Service_NFP, "Game is not looking for amiibos, current state {}", device_state); | ||
| 484 | return false; | ||
| 485 | } | ||
| 486 | |||
| 487 | constexpr auto tag_size = sizeof(NTAG215File); | ||
| 488 | constexpr auto tag_size_without_password = sizeof(NTAG215File) - sizeof(NTAG215Password); | 649 | constexpr auto tag_size_without_password = sizeof(NTAG215File) - sizeof(NTAG215Password); |
| 650 | const Common::FS::IOFile amiibo_file{filename, Common::FS::FileAccessMode::Read, | ||
| 651 | Common::FS::FileType::BinaryFile}; | ||
| 489 | 652 | ||
| 490 | std::vector<u8> amiibo_buffer = buffer; | 653 | if (!amiibo_file.IsOpen()) { |
| 654 | LOG_ERROR(Service_NFP, "Amiibo is already on use"); | ||
| 655 | return false; | ||
| 656 | } | ||
| 491 | 657 | ||
| 492 | if (amiibo_buffer.size() < tag_size_without_password) { | 658 | // Workaround for files with missing password data |
| 493 | LOG_ERROR(Service_NFP, "Wrong file size {}", buffer.size()); | 659 | std::array<u8, sizeof(EncryptedNTAG215File)> buffer{}; |
| 660 | if (amiibo_file.Read(buffer) < tag_size_without_password) { | ||
| 661 | LOG_ERROR(Service_NFP, "Failed to read amiibo file"); | ||
| 494 | return false; | 662 | return false; |
| 495 | } | 663 | } |
| 664 | memcpy(&encrypted_tag_data, buffer.data(), sizeof(EncryptedNTAG215File)); | ||
| 496 | 665 | ||
| 497 | // Ensure it has the correct size | 666 | if (!AmiiboCrypto::IsAmiiboValid(encrypted_tag_data)) { |
| 498 | if (amiibo_buffer.size() != tag_size) { | 667 | LOG_INFO(Service_NFP, "Invalid amiibo"); |
| 499 | amiibo_buffer.resize(tag_size, 0); | 668 | return false; |
| 500 | } | 669 | } |
| 501 | 670 | ||
| 502 | LOG_INFO(Service_NFP, "Amiibo detected"); | 671 | file_path = filename; |
| 503 | std::memcpy(&tag_data, buffer.data(), tag_size); | 672 | return true; |
| 673 | } | ||
| 504 | 674 | ||
| 505 | if (!IsAmiiboValid()) { | 675 | bool Module::Interface::LoadAmiibo(const std::string& filename) { |
| 676 | if (device_state != DeviceState::SearchingForTag) { | ||
| 677 | LOG_ERROR(Service_NFP, "Game is not looking for amiibos, current state {}", device_state); | ||
| 506 | return false; | 678 | return false; |
| 507 | } | 679 | } |
| 508 | 680 | ||
| 509 | // This value can't be dumped from a tag. Generate it | 681 | if (!LoadAmiiboFile(filename)) { |
| 510 | tag_data.password.PWD = GetTagPassword(tag_data.uuid); | 682 | return false; |
| 683 | } | ||
| 511 | 684 | ||
| 512 | device_state = DeviceState::TagFound; | 685 | device_state = DeviceState::TagFound; |
| 513 | activate_event->GetWritableEvent().Signal(); | 686 | activate_event->GetWritableEvent().Signal(); |
| @@ -517,55 +690,13 @@ bool Module::Interface::LoadAmiibo(const std::vector<u8>& buffer) { | |||
| 517 | void Module::Interface::CloseAmiibo() { | 690 | void Module::Interface::CloseAmiibo() { |
| 518 | LOG_INFO(Service_NFP, "Remove amiibo"); | 691 | LOG_INFO(Service_NFP, "Remove amiibo"); |
| 519 | device_state = DeviceState::TagRemoved; | 692 | device_state = DeviceState::TagRemoved; |
| 693 | is_data_decoded = false; | ||
| 520 | is_application_area_initialized = false; | 694 | is_application_area_initialized = false; |
| 521 | application_area_id = 0; | 695 | encrypted_tag_data = {}; |
| 522 | application_area_data.clear(); | 696 | tag_data = {}; |
| 523 | deactivate_event->GetWritableEvent().Signal(); | 697 | deactivate_event->GetWritableEvent().Signal(); |
| 524 | } | 698 | } |
| 525 | 699 | ||
| 526 | bool Module::Interface::IsAmiiboValid() const { | ||
| 527 | const auto& amiibo_data = tag_data.user_memory; | ||
| 528 | LOG_DEBUG(Service_NFP, "uuid_lock=0x{0:x}", tag_data.lock_bytes); | ||
| 529 | LOG_DEBUG(Service_NFP, "compability_container=0x{0:x}", tag_data.compability_container); | ||
| 530 | LOG_DEBUG(Service_NFP, "crypto_init=0x{0:x}", amiibo_data.crypto_init); | ||
| 531 | LOG_DEBUG(Service_NFP, "write_count={}", amiibo_data.write_count); | ||
| 532 | |||
| 533 | LOG_DEBUG(Service_NFP, "character_id=0x{0:x}", amiibo_data.model_info.character_id); | ||
| 534 | LOG_DEBUG(Service_NFP, "character_variant={}", amiibo_data.model_info.character_variant); | ||
| 535 | LOG_DEBUG(Service_NFP, "amiibo_type={}", amiibo_data.model_info.amiibo_type); | ||
| 536 | LOG_DEBUG(Service_NFP, "model_number=0x{0:x}", amiibo_data.model_info.model_number); | ||
| 537 | LOG_DEBUG(Service_NFP, "series={}", amiibo_data.model_info.series); | ||
| 538 | LOG_DEBUG(Service_NFP, "fixed_value=0x{0:x}", amiibo_data.model_info.fixed); | ||
| 539 | |||
| 540 | LOG_DEBUG(Service_NFP, "tag_dynamic_lock=0x{0:x}", tag_data.dynamic_lock); | ||
| 541 | LOG_DEBUG(Service_NFP, "tag_CFG0=0x{0:x}", tag_data.CFG0); | ||
| 542 | LOG_DEBUG(Service_NFP, "tag_CFG1=0x{0:x}", tag_data.CFG1); | ||
| 543 | |||
| 544 | // Check against all know constants on an amiibo binary | ||
| 545 | if (tag_data.lock_bytes != 0xE00F) { | ||
| 546 | return false; | ||
| 547 | } | ||
| 548 | if (tag_data.compability_container != 0xEEFF10F1U) { | ||
| 549 | return false; | ||
| 550 | } | ||
| 551 | if ((amiibo_data.crypto_init & 0xFF) != 0xA5) { | ||
| 552 | return false; | ||
| 553 | } | ||
| 554 | if (amiibo_data.model_info.fixed != 0x02) { | ||
| 555 | return false; | ||
| 556 | } | ||
| 557 | if ((tag_data.dynamic_lock & 0xFFFFFF) != 0x0F0001) { | ||
| 558 | return false; | ||
| 559 | } | ||
| 560 | if (tag_data.CFG0 != 0x04000000U) { | ||
| 561 | return false; | ||
| 562 | } | ||
| 563 | if (tag_data.CFG1 != 0x5F) { | ||
| 564 | return false; | ||
| 565 | } | ||
| 566 | return true; | ||
| 567 | } | ||
| 568 | |||
| 569 | Kernel::KReadableEvent& Module::Interface::GetActivateEvent() const { | 700 | Kernel::KReadableEvent& Module::Interface::GetActivateEvent() const { |
| 570 | return activate_event->GetReadableEvent(); | 701 | return activate_event->GetReadableEvent(); |
| 571 | } | 702 | } |
| @@ -576,13 +707,20 @@ Kernel::KReadableEvent& Module::Interface::GetDeactivateEvent() const { | |||
| 576 | 707 | ||
| 577 | void Module::Interface::Initialize() { | 708 | void Module::Interface::Initialize() { |
| 578 | device_state = DeviceState::Initialized; | 709 | device_state = DeviceState::Initialized; |
| 710 | is_data_decoded = false; | ||
| 711 | is_application_area_initialized = false; | ||
| 712 | encrypted_tag_data = {}; | ||
| 713 | tag_data = {}; | ||
| 579 | } | 714 | } |
| 580 | 715 | ||
| 581 | void Module::Interface::Finalize() { | 716 | void Module::Interface::Finalize() { |
| 717 | if (device_state == DeviceState::TagMounted) { | ||
| 718 | Unmount(); | ||
| 719 | } | ||
| 720 | if (device_state == DeviceState::SearchingForTag || device_state == DeviceState::TagRemoved) { | ||
| 721 | StopDetection(); | ||
| 722 | } | ||
| 582 | device_state = DeviceState::Unaviable; | 723 | device_state = DeviceState::Unaviable; |
| 583 | is_application_area_initialized = false; | ||
| 584 | application_area_id = 0; | ||
| 585 | application_area_data.clear(); | ||
| 586 | } | 724 | } |
| 587 | 725 | ||
| 588 | Result Module::Interface::StartDetection(s32 protocol_) { | 726 | Result Module::Interface::StartDetection(s32 protocol_) { |
| @@ -618,42 +756,102 @@ Result Module::Interface::StopDetection() { | |||
| 618 | return ErrCodes::WrongDeviceState; | 756 | return ErrCodes::WrongDeviceState; |
| 619 | } | 757 | } |
| 620 | 758 | ||
| 621 | Result Module::Interface::Mount() { | 759 | Result Module::Interface::Flush() { |
| 622 | if (device_state == DeviceState::TagFound) { | 760 | // Ignore write command if we can't encrypt the data |
| 623 | device_state = DeviceState::TagMounted; | 761 | if (!is_data_decoded) { |
| 624 | return ResultSuccess; | 762 | return ResultSuccess; |
| 625 | } | 763 | } |
| 626 | 764 | ||
| 627 | LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); | 765 | constexpr auto tag_size_without_password = sizeof(NTAG215File) - sizeof(NTAG215Password); |
| 628 | return ErrCodes::WrongDeviceState; | 766 | EncryptedNTAG215File tmp_encrypted_tag_data{}; |
| 767 | const Common::FS::IOFile amiibo_file{file_path, Common::FS::FileAccessMode::ReadWrite, | ||
| 768 | Common::FS::FileType::BinaryFile}; | ||
| 769 | |||
| 770 | if (!amiibo_file.IsOpen()) { | ||
| 771 | LOG_ERROR(Core, "Amiibo is already on use"); | ||
| 772 | return ErrCodes::WriteAmiiboFailed; | ||
| 773 | } | ||
| 774 | |||
| 775 | // Workaround for files with missing password data | ||
| 776 | std::array<u8, sizeof(EncryptedNTAG215File)> buffer{}; | ||
| 777 | if (amiibo_file.Read(buffer) < tag_size_without_password) { | ||
| 778 | LOG_ERROR(Core, "Failed to read amiibo file"); | ||
| 779 | return ErrCodes::WriteAmiiboFailed; | ||
| 780 | } | ||
| 781 | memcpy(&tmp_encrypted_tag_data, buffer.data(), sizeof(EncryptedNTAG215File)); | ||
| 782 | |||
| 783 | if (!AmiiboCrypto::IsAmiiboValid(tmp_encrypted_tag_data)) { | ||
| 784 | LOG_INFO(Service_NFP, "Invalid amiibo"); | ||
| 785 | return ErrCodes::WriteAmiiboFailed; | ||
| 786 | } | ||
| 787 | |||
| 788 | bool is_uuid_equal = memcmp(tmp_encrypted_tag_data.uuid.data(), tag_data.uuid.data(), 8) == 0; | ||
| 789 | bool is_character_equal = tmp_encrypted_tag_data.user_memory.model_info.character_id == | ||
| 790 | tag_data.model_info.character_id; | ||
| 791 | if (!is_uuid_equal || !is_character_equal) { | ||
| 792 | LOG_ERROR(Service_NFP, "Not the same amiibo"); | ||
| 793 | return ErrCodes::WriteAmiiboFailed; | ||
| 794 | } | ||
| 795 | |||
| 796 | if (!AmiiboCrypto::EncodeAmiibo(tag_data, encrypted_tag_data)) { | ||
| 797 | LOG_ERROR(Service_NFP, "Failed to encode data"); | ||
| 798 | return ErrCodes::WriteAmiiboFailed; | ||
| 799 | } | ||
| 800 | |||
| 801 | // Return to the start of the file | ||
| 802 | if (!amiibo_file.Seek(0)) { | ||
| 803 | LOG_ERROR(Service_NFP, "Error writting to file"); | ||
| 804 | return ErrCodes::WriteAmiiboFailed; | ||
| 805 | } | ||
| 806 | |||
| 807 | if (!amiibo_file.Write(encrypted_tag_data)) { | ||
| 808 | LOG_ERROR(Service_NFP, "Error writting to file"); | ||
| 809 | return ErrCodes::WriteAmiiboFailed; | ||
| 810 | } | ||
| 811 | |||
| 812 | return ResultSuccess; | ||
| 813 | } | ||
| 814 | |||
| 815 | Result Module::Interface::Mount() { | ||
| 816 | if (device_state != DeviceState::TagFound) { | ||
| 817 | LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); | ||
| 818 | return ErrCodes::WrongDeviceState; | ||
| 819 | } | ||
| 820 | |||
| 821 | is_data_decoded = AmiiboCrypto::DecodeAmiibo(encrypted_tag_data, tag_data); | ||
| 822 | LOG_INFO(Service_NFP, "Is amiibo decoded {}", is_data_decoded); | ||
| 823 | |||
| 824 | is_application_area_initialized = false; | ||
| 825 | device_state = DeviceState::TagMounted; | ||
| 826 | return ResultSuccess; | ||
| 629 | } | 827 | } |
| 630 | 828 | ||
| 631 | Result Module::Interface::Unmount() { | 829 | Result Module::Interface::Unmount() { |
| 632 | if (device_state == DeviceState::TagMounted) { | 830 | if (device_state != DeviceState::TagMounted) { |
| 633 | is_application_area_initialized = false; | 831 | LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); |
| 634 | application_area_id = 0; | 832 | return ErrCodes::WrongDeviceState; |
| 635 | application_area_data.clear(); | ||
| 636 | device_state = DeviceState::TagFound; | ||
| 637 | return ResultSuccess; | ||
| 638 | } | 833 | } |
| 639 | 834 | ||
| 640 | LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); | 835 | is_data_decoded = false; |
| 641 | return ErrCodes::WrongDeviceState; | 836 | is_application_area_initialized = false; |
| 837 | device_state = DeviceState::TagFound; | ||
| 838 | return ResultSuccess; | ||
| 642 | } | 839 | } |
| 643 | 840 | ||
| 644 | Result Module::Interface::GetTagInfo(TagInfo& tag_info) const { | 841 | Result Module::Interface::GetTagInfo(TagInfo& tag_info) const { |
| 645 | if (device_state == DeviceState::TagFound || device_state == DeviceState::TagMounted) { | 842 | if (device_state != DeviceState::TagFound && device_state != DeviceState::TagMounted) { |
| 646 | tag_info = { | 843 | LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); |
| 647 | .uuid = tag_data.uuid, | 844 | return ErrCodes::WrongDeviceState; |
| 648 | .uuid_length = static_cast<u8>(tag_data.uuid.size()), | ||
| 649 | .protocol = protocol, | ||
| 650 | .tag_type = static_cast<u32>(tag_data.user_memory.model_info.amiibo_type), | ||
| 651 | }; | ||
| 652 | return ResultSuccess; | ||
| 653 | } | 845 | } |
| 654 | 846 | ||
| 655 | LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); | 847 | tag_info = { |
| 656 | return ErrCodes::WrongDeviceState; | 848 | .uuid = encrypted_tag_data.uuid, |
| 849 | .uuid_length = static_cast<u8>(encrypted_tag_data.uuid.size()), | ||
| 850 | .protocol = protocol, | ||
| 851 | .tag_type = static_cast<u32>(encrypted_tag_data.user_memory.model_info.amiibo_type), | ||
| 852 | }; | ||
| 853 | |||
| 854 | return ResultSuccess; | ||
| 657 | } | 855 | } |
| 658 | 856 | ||
| 659 | Result Module::Interface::GetCommonInfo(CommonInfo& common_info) const { | 857 | Result Module::Interface::GetCommonInfo(CommonInfo& common_info) const { |
| @@ -662,14 +860,28 @@ Result Module::Interface::GetCommonInfo(CommonInfo& common_info) const { | |||
| 662 | return ErrCodes::WrongDeviceState; | 860 | return ErrCodes::WrongDeviceState; |
| 663 | } | 861 | } |
| 664 | 862 | ||
| 665 | // Read this data from the amiibo save file | 863 | if (is_data_decoded && tag_data.settings.settings.amiibo_initialized != 0) { |
| 864 | const auto& settings = tag_data.settings; | ||
| 865 | // TODO: Validate this data | ||
| 866 | common_info = { | ||
| 867 | .last_write_year = settings.write_date.GetYear(), | ||
| 868 | .last_write_month = settings.write_date.GetMonth(), | ||
| 869 | .last_write_day = settings.write_date.GetDay(), | ||
| 870 | .write_counter = settings.crc_counter, | ||
| 871 | .version = 1, | ||
| 872 | .application_area_size = sizeof(ApplicationArea), | ||
| 873 | }; | ||
| 874 | return ResultSuccess; | ||
| 875 | } | ||
| 876 | |||
| 877 | // Generate a generic answer | ||
| 666 | common_info = { | 878 | common_info = { |
| 667 | .last_write_year = 2022, | 879 | .last_write_year = 2022, |
| 668 | .last_write_month = 2, | 880 | .last_write_month = 2, |
| 669 | .last_write_day = 7, | 881 | .last_write_day = 7, |
| 670 | .write_counter = tag_data.user_memory.write_count, | 882 | .write_counter = 0, |
| 671 | .version = 1, | 883 | .version = 1, |
| 672 | .application_area_size = ApplicationAreaSize, | 884 | .application_area_size = sizeof(ApplicationArea), |
| 673 | }; | 885 | }; |
| 674 | return ResultSuccess; | 886 | return ResultSuccess; |
| 675 | } | 887 | } |
| @@ -680,26 +892,53 @@ Result Module::Interface::GetModelInfo(ModelInfo& model_info) const { | |||
| 680 | return ErrCodes::WrongDeviceState; | 892 | return ErrCodes::WrongDeviceState; |
| 681 | } | 893 | } |
| 682 | 894 | ||
| 683 | model_info = tag_data.user_memory.model_info; | 895 | const auto& model_info_data = encrypted_tag_data.user_memory.model_info; |
| 896 | model_info = { | ||
| 897 | .character_id = model_info_data.character_id, | ||
| 898 | .character_variant = model_info_data.character_variant, | ||
| 899 | .amiibo_type = model_info_data.amiibo_type, | ||
| 900 | .model_number = model_info_data.model_number, | ||
| 901 | .series = model_info_data.series, | ||
| 902 | .constant_value = model_info_data.constant_value, | ||
| 903 | }; | ||
| 684 | return ResultSuccess; | 904 | return ResultSuccess; |
| 685 | } | 905 | } |
| 686 | 906 | ||
| 687 | Result Module::Interface::GetRegisterInfo(RegisterInfo& register_info) const { | 907 | Result Module::Interface::GetRegisterInfo(RegisterInfo& register_info) const { |
| 688 | if (device_state != DeviceState::TagMounted) { | 908 | if (device_state != DeviceState::TagMounted) { |
| 689 | LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); | 909 | LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); |
| 910 | if (device_state == DeviceState::TagRemoved) { | ||
| 911 | return ErrCodes::TagRemoved; | ||
| 912 | } | ||
| 690 | return ErrCodes::WrongDeviceState; | 913 | return ErrCodes::WrongDeviceState; |
| 691 | } | 914 | } |
| 692 | 915 | ||
| 693 | Service::Mii::MiiManager manager; | 916 | Service::Mii::MiiManager manager; |
| 694 | 917 | ||
| 695 | // Read this data from the amiibo save file | 918 | if (is_data_decoded && tag_data.settings.settings.amiibo_initialized != 0) { |
| 919 | const auto& settings = tag_data.settings; | ||
| 920 | |||
| 921 | // TODO: Validate this data | ||
| 922 | register_info = { | ||
| 923 | .mii_char_info = manager.ConvertV3ToCharInfo(tag_data.owner_mii), | ||
| 924 | .first_write_year = settings.init_date.GetYear(), | ||
| 925 | .first_write_month = settings.init_date.GetMonth(), | ||
| 926 | .first_write_day = settings.init_date.GetDay(), | ||
| 927 | .amiibo_name = GetAmiiboName(settings), | ||
| 928 | .font_region = {}, | ||
| 929 | }; | ||
| 930 | |||
| 931 | return ResultSuccess; | ||
| 932 | } | ||
| 933 | |||
| 934 | // Generate a generic answer | ||
| 696 | register_info = { | 935 | register_info = { |
| 697 | .mii_char_info = manager.BuildDefault(0), | 936 | .mii_char_info = manager.BuildDefault(0), |
| 698 | .first_write_year = 2022, | 937 | .first_write_year = 2022, |
| 699 | .first_write_month = 2, | 938 | .first_write_month = 2, |
| 700 | .first_write_day = 7, | 939 | .first_write_day = 7, |
| 701 | .amiibo_name = {'Y', 'u', 'z', 'u', 'A', 'm', 'i', 'i', 'b', 'o', 0}, | 940 | .amiibo_name = {'Y', 'u', 'z', 'u', 'A', 'm', 'i', 'i', 'b', 'o', 0}, |
| 702 | .unknown = {}, | 941 | .font_region = {}, |
| 703 | }; | 942 | }; |
| 704 | return ResultSuccess; | 943 | return ResultSuccess; |
| 705 | } | 944 | } |
| @@ -707,31 +946,47 @@ Result Module::Interface::GetRegisterInfo(RegisterInfo& register_info) const { | |||
| 707 | Result Module::Interface::OpenApplicationArea(u32 access_id) { | 946 | Result Module::Interface::OpenApplicationArea(u32 access_id) { |
| 708 | if (device_state != DeviceState::TagMounted) { | 947 | if (device_state != DeviceState::TagMounted) { |
| 709 | LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); | 948 | LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); |
| 949 | if (device_state == DeviceState::TagRemoved) { | ||
| 950 | return ErrCodes::TagRemoved; | ||
| 951 | } | ||
| 710 | return ErrCodes::WrongDeviceState; | 952 | return ErrCodes::WrongDeviceState; |
| 711 | } | 953 | } |
| 712 | if (AmiiboApplicationDataExist(access_id)) { | 954 | |
| 713 | application_area_data = LoadAmiiboApplicationData(access_id); | 955 | // Fallback for lack of amiibo keys |
| 714 | application_area_id = access_id; | 956 | if (!is_data_decoded) { |
| 715 | is_application_area_initialized = true; | 957 | LOG_WARNING(Service_NFP, "Application area is not initialized"); |
| 958 | return ErrCodes::ApplicationAreaIsNotInitialized; | ||
| 716 | } | 959 | } |
| 717 | if (!is_application_area_initialized) { | 960 | |
| 961 | if (tag_data.settings.settings.appdata_initialized == 0) { | ||
| 718 | LOG_WARNING(Service_NFP, "Application area is not initialized"); | 962 | LOG_WARNING(Service_NFP, "Application area is not initialized"); |
| 719 | return ErrCodes::ApplicationAreaIsNotInitialized; | 963 | return ErrCodes::ApplicationAreaIsNotInitialized; |
| 720 | } | 964 | } |
| 965 | |||
| 966 | if (tag_data.application_area_id != access_id) { | ||
| 967 | LOG_WARNING(Service_NFP, "Wrong application area id"); | ||
| 968 | return ErrCodes::WrongApplicationAreaId; | ||
| 969 | } | ||
| 970 | |||
| 971 | is_application_area_initialized = true; | ||
| 721 | return ResultSuccess; | 972 | return ResultSuccess; |
| 722 | } | 973 | } |
| 723 | 974 | ||
| 724 | Result Module::Interface::GetApplicationArea(std::vector<u8>& data) const { | 975 | Result Module::Interface::GetApplicationArea(ApplicationArea& data) const { |
| 725 | if (device_state != DeviceState::TagMounted) { | 976 | if (device_state != DeviceState::TagMounted) { |
| 726 | LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); | 977 | LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); |
| 978 | if (device_state == DeviceState::TagRemoved) { | ||
| 979 | return ErrCodes::TagRemoved; | ||
| 980 | } | ||
| 727 | return ErrCodes::WrongDeviceState; | 981 | return ErrCodes::WrongDeviceState; |
| 728 | } | 982 | } |
| 983 | |||
| 729 | if (!is_application_area_initialized) { | 984 | if (!is_application_area_initialized) { |
| 730 | LOG_ERROR(Service_NFP, "Application area is not initialized"); | 985 | LOG_ERROR(Service_NFP, "Application area is not initialized"); |
| 731 | return ErrCodes::ApplicationAreaIsNotInitialized; | 986 | return ErrCodes::ApplicationAreaIsNotInitialized; |
| 732 | } | 987 | } |
| 733 | 988 | ||
| 734 | data = application_area_data; | 989 | data = tag_data.application_area; |
| 735 | 990 | ||
| 736 | return ResultSuccess; | 991 | return ResultSuccess; |
| 737 | } | 992 | } |
| @@ -739,46 +994,69 @@ Result Module::Interface::GetApplicationArea(std::vector<u8>& data) const { | |||
| 739 | Result Module::Interface::SetApplicationArea(const std::vector<u8>& data) { | 994 | Result Module::Interface::SetApplicationArea(const std::vector<u8>& data) { |
| 740 | if (device_state != DeviceState::TagMounted) { | 995 | if (device_state != DeviceState::TagMounted) { |
| 741 | LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); | 996 | LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); |
| 997 | if (device_state == DeviceState::TagRemoved) { | ||
| 998 | return ErrCodes::TagRemoved; | ||
| 999 | } | ||
| 742 | return ErrCodes::WrongDeviceState; | 1000 | return ErrCodes::WrongDeviceState; |
| 743 | } | 1001 | } |
| 1002 | |||
| 744 | if (!is_application_area_initialized) { | 1003 | if (!is_application_area_initialized) { |
| 745 | LOG_ERROR(Service_NFP, "Application area is not initialized"); | 1004 | LOG_ERROR(Service_NFP, "Application area is not initialized"); |
| 746 | return ErrCodes::ApplicationAreaIsNotInitialized; | 1005 | return ErrCodes::ApplicationAreaIsNotInitialized; |
| 747 | } | 1006 | } |
| 748 | application_area_data = data; | 1007 | |
| 749 | SaveAmiiboApplicationData(application_area_id, application_area_data); | 1008 | if (data.size() != sizeof(ApplicationArea)) { |
| 1009 | LOG_ERROR(Service_NFP, "Wrong data size {}", data.size()); | ||
| 1010 | return ResultUnknown; | ||
| 1011 | } | ||
| 1012 | |||
| 1013 | std::memcpy(&tag_data.application_area, data.data(), sizeof(ApplicationArea)); | ||
| 750 | return ResultSuccess; | 1014 | return ResultSuccess; |
| 751 | } | 1015 | } |
| 752 | 1016 | ||
| 753 | Result Module::Interface::CreateApplicationArea(u32 access_id, const std::vector<u8>& data) { | 1017 | Result Module::Interface::CreateApplicationArea(u32 access_id, const std::vector<u8>& data) { |
| 754 | if (device_state != DeviceState::TagMounted) { | 1018 | if (device_state != DeviceState::TagMounted) { |
| 755 | LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); | 1019 | LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); |
| 1020 | if (device_state == DeviceState::TagRemoved) { | ||
| 1021 | return ErrCodes::TagRemoved; | ||
| 1022 | } | ||
| 756 | return ErrCodes::WrongDeviceState; | 1023 | return ErrCodes::WrongDeviceState; |
| 757 | } | 1024 | } |
| 758 | if (AmiiboApplicationDataExist(access_id)) { | 1025 | |
| 1026 | if (tag_data.settings.settings.appdata_initialized != 0) { | ||
| 759 | LOG_ERROR(Service_NFP, "Application area already exist"); | 1027 | LOG_ERROR(Service_NFP, "Application area already exist"); |
| 760 | return ErrCodes::ApplicationAreaExist; | 1028 | return ErrCodes::ApplicationAreaExist; |
| 761 | } | 1029 | } |
| 762 | application_area_data = data; | 1030 | |
| 763 | application_area_id = access_id; | 1031 | if (data.size() != sizeof(ApplicationArea)) { |
| 764 | SaveAmiiboApplicationData(application_area_id, application_area_data); | 1032 | LOG_ERROR(Service_NFP, "Wrong data size {}", data.size()); |
| 1033 | return ResultUnknown; | ||
| 1034 | } | ||
| 1035 | |||
| 1036 | std::memcpy(&tag_data.application_area, data.data(), sizeof(ApplicationArea)); | ||
| 1037 | tag_data.application_area_id = access_id; | ||
| 1038 | |||
| 765 | return ResultSuccess; | 1039 | return ResultSuccess; |
| 766 | } | 1040 | } |
| 767 | 1041 | ||
| 768 | bool Module::Interface::AmiiboApplicationDataExist(u32 access_id) const { | 1042 | Result Module::Interface::RecreateApplicationArea(u32 access_id, const std::vector<u8>& data) { |
| 769 | // TODO(german77): Check if file exist | 1043 | if (device_state != DeviceState::TagMounted) { |
| 770 | return false; | 1044 | LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); |
| 771 | } | 1045 | if (device_state == DeviceState::TagRemoved) { |
| 1046 | return ErrCodes::TagRemoved; | ||
| 1047 | } | ||
| 1048 | return ErrCodes::WrongDeviceState; | ||
| 1049 | } | ||
| 772 | 1050 | ||
| 773 | std::vector<u8> Module::Interface::LoadAmiiboApplicationData(u32 access_id) const { | 1051 | if (data.size() != sizeof(ApplicationArea)) { |
| 774 | // TODO(german77): Read file | 1052 | LOG_ERROR(Service_NFP, "Wrong data size {}", data.size()); |
| 775 | std::vector<u8> data(ApplicationAreaSize); | 1053 | return ResultUnknown; |
| 776 | return data; | 1054 | } |
| 777 | } | 1055 | |
| 1056 | std::memcpy(&tag_data.application_area, data.data(), sizeof(ApplicationArea)); | ||
| 1057 | tag_data.application_area_id = access_id; | ||
| 778 | 1058 | ||
| 779 | void Module::Interface::SaveAmiiboApplicationData(u32 access_id, | 1059 | return ResultSuccess; |
| 780 | const std::vector<u8>& data) const { | ||
| 781 | // TODO(german77): Save file | ||
| 782 | } | 1060 | } |
| 783 | 1061 | ||
| 784 | u64 Module::Interface::GetHandle() const { | 1062 | u64 Module::Interface::GetHandle() const { |
| @@ -791,16 +1069,25 @@ DeviceState Module::Interface::GetCurrentState() const { | |||
| 791 | } | 1069 | } |
| 792 | 1070 | ||
| 793 | Core::HID::NpadIdType Module::Interface::GetNpadId() const { | 1071 | Core::HID::NpadIdType Module::Interface::GetNpadId() const { |
| 794 | return npad_id; | 1072 | // Return first connected npad id as a workaround for lack of a single nfc interface per |
| 1073 | // controller | ||
| 1074 | return system.HIDCore().GetFirstNpadId(); | ||
| 795 | } | 1075 | } |
| 796 | 1076 | ||
| 797 | u32 Module::Interface::GetTagPassword(const TagUuid& uuid) const { | 1077 | AmiiboName Module::Interface::GetAmiiboName(const AmiiboSettings& settings) const { |
| 798 | // Verifiy that the generated password is correct | 1078 | std::array<char16_t, amiibo_name_length> settings_amiibo_name{}; |
| 799 | u32 password = 0xAA ^ (uuid[1] ^ uuid[3]); | 1079 | AmiiboName amiibo_name{}; |
| 800 | password &= (0x55 ^ (uuid[2] ^ uuid[4])) << 8; | 1080 | |
| 801 | password &= (0xAA ^ (uuid[3] ^ uuid[5])) << 16; | 1081 | // Convert from big endian to little endian |
| 802 | password &= (0x55 ^ (uuid[4] ^ uuid[6])) << 24; | 1082 | for (std::size_t i = 0; i < amiibo_name_length; i++) { |
| 803 | return password; | 1083 | settings_amiibo_name[i] = static_cast<u16>(settings.amiibo_name[i]); |
| 1084 | } | ||
| 1085 | |||
| 1086 | // Convert from utf16 to utf8 | ||
| 1087 | const auto amiibo_name_utf8 = Common::UTF16ToUTF8(settings_amiibo_name.data()); | ||
| 1088 | memcpy(amiibo_name.data(), amiibo_name_utf8.data(), amiibo_name_utf8.size()); | ||
| 1089 | |||
| 1090 | return amiibo_name; | ||
| 804 | } | 1091 | } |
| 805 | 1092 | ||
| 806 | void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) { | 1093 | void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) { |
diff --git a/src/core/hle/service/nfp/nfp.h b/src/core/hle/service/nfp/nfp.h index 0fc808781..0de0b48e7 100644 --- a/src/core/hle/service/nfp/nfp.h +++ b/src/core/hle/service/nfp/nfp.h | |||
| @@ -9,6 +9,7 @@ | |||
| 9 | #include "common/common_funcs.h" | 9 | #include "common/common_funcs.h" |
| 10 | #include "core/hle/service/kernel_helpers.h" | 10 | #include "core/hle/service/kernel_helpers.h" |
| 11 | #include "core/hle/service/mii/types.h" | 11 | #include "core/hle/service/mii/types.h" |
| 12 | #include "core/hle/service/nfp/amiibo_types.h" | ||
| 12 | #include "core/hle/service/service.h" | 13 | #include "core/hle/service/service.h" |
| 13 | 14 | ||
| 14 | namespace Kernel { | 15 | namespace Kernel { |
| @@ -21,71 +22,7 @@ enum class NpadIdType : u32; | |||
| 21 | } // namespace Core::HID | 22 | } // namespace Core::HID |
| 22 | 23 | ||
| 23 | namespace Service::NFP { | 24 | namespace Service::NFP { |
| 24 | 25 | using AmiiboName = std::array<char, (amiibo_name_length * 4) + 1>; | |
| 25 | enum class ServiceType : u32 { | ||
| 26 | User, | ||
| 27 | Debug, | ||
| 28 | System, | ||
| 29 | }; | ||
| 30 | |||
| 31 | enum class State : u32 { | ||
| 32 | NonInitialized, | ||
| 33 | Initialized, | ||
| 34 | }; | ||
| 35 | |||
| 36 | enum class DeviceState : u32 { | ||
| 37 | Initialized, | ||
| 38 | SearchingForTag, | ||
| 39 | TagFound, | ||
| 40 | TagRemoved, | ||
| 41 | TagMounted, | ||
| 42 | Unaviable, | ||
| 43 | Finalized, | ||
| 44 | }; | ||
| 45 | |||
| 46 | enum class ModelType : u32 { | ||
| 47 | Amiibo, | ||
| 48 | }; | ||
| 49 | |||
| 50 | enum class MountTarget : u32 { | ||
| 51 | Rom, | ||
| 52 | Ram, | ||
| 53 | All, | ||
| 54 | }; | ||
| 55 | |||
| 56 | enum class AmiiboType : u8 { | ||
| 57 | Figure, | ||
| 58 | Card, | ||
| 59 | Yarn, | ||
| 60 | }; | ||
| 61 | |||
| 62 | enum class AmiiboSeries : u8 { | ||
| 63 | SuperSmashBros, | ||
| 64 | SuperMario, | ||
| 65 | ChibiRobo, | ||
| 66 | YoshiWoollyWorld, | ||
| 67 | Splatoon, | ||
| 68 | AnimalCrossing, | ||
| 69 | EightBitMario, | ||
| 70 | Skylanders, | ||
| 71 | Unknown8, | ||
| 72 | TheLegendOfZelda, | ||
| 73 | ShovelKnight, | ||
| 74 | Unknown11, | ||
| 75 | Kiby, | ||
| 76 | Pokemon, | ||
| 77 | MarioSportsSuperstars, | ||
| 78 | MonsterHunter, | ||
| 79 | BoxBoy, | ||
| 80 | Pikmin, | ||
| 81 | FireEmblem, | ||
| 82 | Metroid, | ||
| 83 | Others, | ||
| 84 | MegaMan, | ||
| 85 | Diablo | ||
| 86 | }; | ||
| 87 | |||
| 88 | using TagUuid = std::array<u8, 10>; | ||
| 89 | 26 | ||
| 90 | struct TagInfo { | 27 | struct TagInfo { |
| 91 | TagUuid uuid; | 28 | TagUuid uuid; |
| @@ -114,21 +51,19 @@ struct ModelInfo { | |||
| 114 | AmiiboType amiibo_type; | 51 | AmiiboType amiibo_type; |
| 115 | u16 model_number; | 52 | u16 model_number; |
| 116 | AmiiboSeries series; | 53 | AmiiboSeries series; |
| 117 | u8 fixed; // Must be 02 | 54 | u8 constant_value; // Must be 02 |
| 118 | INSERT_PADDING_BYTES(0x4); // Unknown | 55 | INSERT_PADDING_BYTES(0x38); // Unknown |
| 119 | INSERT_PADDING_BYTES(0x20); // Probably a SHA256-(HMAC?) hash | ||
| 120 | INSERT_PADDING_BYTES(0x14); // SHA256-HMAC | ||
| 121 | }; | 56 | }; |
| 122 | static_assert(sizeof(ModelInfo) == 0x40, "ModelInfo is an invalid size"); | 57 | static_assert(sizeof(ModelInfo) == 0x40, "ModelInfo is an invalid size"); |
| 123 | 58 | ||
| 124 | struct RegisterInfo { | 59 | struct RegisterInfo { |
| 125 | Service::Mii::MiiInfo mii_char_info; | 60 | Service::Mii::CharInfo mii_char_info; |
| 126 | u16 first_write_year; | 61 | u16 first_write_year; |
| 127 | u8 first_write_month; | 62 | u8 first_write_month; |
| 128 | u8 first_write_day; | 63 | u8 first_write_day; |
| 129 | std::array<u8, 11> amiibo_name; | 64 | AmiiboName amiibo_name; |
| 130 | u8 unknown; | 65 | u8 font_region; |
| 131 | INSERT_PADDING_BYTES(0x98); | 66 | INSERT_PADDING_BYTES(0x7A); |
| 132 | }; | 67 | }; |
| 133 | static_assert(sizeof(RegisterInfo) == 0x100, "RegisterInfo is an invalid size"); | 68 | static_assert(sizeof(RegisterInfo) == 0x100, "RegisterInfo is an invalid size"); |
| 134 | 69 | ||
| @@ -140,39 +75,9 @@ public: | |||
| 140 | const char* name); | 75 | const char* name); |
| 141 | ~Interface() override; | 76 | ~Interface() override; |
| 142 | 77 | ||
| 143 | struct EncryptedAmiiboFile { | ||
| 144 | u16 crypto_init; // Must be A5 XX | ||
| 145 | u16 write_count; // Number of times the amiibo has been written? | ||
| 146 | INSERT_PADDING_BYTES(0x20); // System crypts | ||
| 147 | INSERT_PADDING_BYTES(0x20); // SHA256-(HMAC?) hash | ||
| 148 | ModelInfo model_info; // This struct is bigger than documentation | ||
| 149 | INSERT_PADDING_BYTES(0xC); // SHA256-HMAC | ||
| 150 | INSERT_PADDING_BYTES(0x114); // section 1 encrypted buffer | ||
| 151 | INSERT_PADDING_BYTES(0x54); // section 2 encrypted buffer | ||
| 152 | }; | ||
| 153 | static_assert(sizeof(EncryptedAmiiboFile) == 0x1F8, "AmiiboFile is an invalid size"); | ||
| 154 | |||
| 155 | struct NTAG215Password { | ||
| 156 | u32 PWD; // Password to allow write access | ||
| 157 | u16 PACK; // Password acknowledge reply | ||
| 158 | u16 RFUI; // Reserved for future use | ||
| 159 | }; | ||
| 160 | static_assert(sizeof(NTAG215Password) == 0x8, "NTAG215Password is an invalid size"); | ||
| 161 | |||
| 162 | struct NTAG215File { | ||
| 163 | TagUuid uuid; // Unique serial number | ||
| 164 | u16 lock_bytes; // Set defined pages as read only | ||
| 165 | u32 compability_container; // Defines available memory | ||
| 166 | EncryptedAmiiboFile user_memory; // Writable data | ||
| 167 | u32 dynamic_lock; // Dynamic lock | ||
| 168 | u32 CFG0; // Defines memory protected by password | ||
| 169 | u32 CFG1; // Defines number of verification attempts | ||
| 170 | NTAG215Password password; // Password data | ||
| 171 | }; | ||
| 172 | static_assert(sizeof(NTAG215File) == 0x21C, "NTAG215File is an invalid size"); | ||
| 173 | |||
| 174 | void CreateUserInterface(Kernel::HLERequestContext& ctx); | 78 | void CreateUserInterface(Kernel::HLERequestContext& ctx); |
| 175 | bool LoadAmiibo(const std::vector<u8>& buffer); | 79 | bool LoadAmiibo(const std::string& filename); |
| 80 | bool LoadAmiiboFile(const std::string& filename); | ||
| 176 | void CloseAmiibo(); | 81 | void CloseAmiibo(); |
| 177 | 82 | ||
| 178 | void Initialize(); | 83 | void Initialize(); |
| @@ -182,6 +87,7 @@ public: | |||
| 182 | Result StopDetection(); | 87 | Result StopDetection(); |
| 183 | Result Mount(); | 88 | Result Mount(); |
| 184 | Result Unmount(); | 89 | Result Unmount(); |
| 90 | Result Flush(); | ||
| 185 | 91 | ||
| 186 | Result GetTagInfo(TagInfo& tag_info) const; | 92 | Result GetTagInfo(TagInfo& tag_info) const; |
| 187 | Result GetCommonInfo(CommonInfo& common_info) const; | 93 | Result GetCommonInfo(CommonInfo& common_info) const; |
| @@ -189,9 +95,10 @@ public: | |||
| 189 | Result GetRegisterInfo(RegisterInfo& register_info) const; | 95 | Result GetRegisterInfo(RegisterInfo& register_info) const; |
| 190 | 96 | ||
| 191 | Result OpenApplicationArea(u32 access_id); | 97 | Result OpenApplicationArea(u32 access_id); |
| 192 | Result GetApplicationArea(std::vector<u8>& data) const; | 98 | Result GetApplicationArea(ApplicationArea& data) const; |
| 193 | Result SetApplicationArea(const std::vector<u8>& data); | 99 | Result SetApplicationArea(const std::vector<u8>& data); |
| 194 | Result CreateApplicationArea(u32 access_id, const std::vector<u8>& data); | 100 | Result CreateApplicationArea(u32 access_id, const std::vector<u8>& data); |
| 101 | Result RecreateApplicationArea(u32 access_id, const std::vector<u8>& data); | ||
| 195 | 102 | ||
| 196 | u64 GetHandle() const; | 103 | u64 GetHandle() const; |
| 197 | DeviceState GetCurrentState() const; | 104 | DeviceState GetCurrentState() const; |
| @@ -204,27 +111,21 @@ public: | |||
| 204 | std::shared_ptr<Module> module; | 111 | std::shared_ptr<Module> module; |
| 205 | 112 | ||
| 206 | private: | 113 | private: |
| 207 | /// Validates that the amiibo file is not corrupted | 114 | AmiiboName GetAmiiboName(const AmiiboSettings& settings) const; |
| 208 | bool IsAmiiboValid() const; | ||
| 209 | |||
| 210 | bool AmiiboApplicationDataExist(u32 access_id) const; | ||
| 211 | std::vector<u8> LoadAmiiboApplicationData(u32 access_id) const; | ||
| 212 | void SaveAmiiboApplicationData(u32 access_id, const std::vector<u8>& data) const; | ||
| 213 | |||
| 214 | /// return password needed to allow write access to protected memory | ||
| 215 | u32 GetTagPassword(const TagUuid& uuid) const; | ||
| 216 | 115 | ||
| 217 | const Core::HID::NpadIdType npad_id; | 116 | const Core::HID::NpadIdType npad_id; |
| 218 | 117 | ||
| 219 | DeviceState device_state{DeviceState::Unaviable}; | 118 | bool is_data_decoded{}; |
| 220 | KernelHelpers::ServiceContext service_context; | 119 | bool is_application_area_initialized{}; |
| 120 | s32 protocol; | ||
| 121 | std::string file_path{}; | ||
| 221 | Kernel::KEvent* activate_event; | 122 | Kernel::KEvent* activate_event; |
| 222 | Kernel::KEvent* deactivate_event; | 123 | Kernel::KEvent* deactivate_event; |
| 124 | DeviceState device_state{DeviceState::Unaviable}; | ||
| 125 | KernelHelpers::ServiceContext service_context; | ||
| 126 | |||
| 223 | NTAG215File tag_data{}; | 127 | NTAG215File tag_data{}; |
| 224 | s32 protocol; | 128 | EncryptedNTAG215File encrypted_tag_data{}; |
| 225 | bool is_application_area_initialized{}; | ||
| 226 | u32 application_area_id; | ||
| 227 | std::vector<u8> application_area_data; | ||
| 228 | }; | 129 | }; |
| 229 | }; | 130 | }; |
| 230 | 131 | ||
| @@ -243,6 +144,7 @@ private: | |||
| 243 | void OpenApplicationArea(Kernel::HLERequestContext& ctx); | 144 | void OpenApplicationArea(Kernel::HLERequestContext& ctx); |
| 244 | void GetApplicationArea(Kernel::HLERequestContext& ctx); | 145 | void GetApplicationArea(Kernel::HLERequestContext& ctx); |
| 245 | void SetApplicationArea(Kernel::HLERequestContext& ctx); | 146 | void SetApplicationArea(Kernel::HLERequestContext& ctx); |
| 147 | void Flush(Kernel::HLERequestContext& ctx); | ||
| 246 | void CreateApplicationArea(Kernel::HLERequestContext& ctx); | 148 | void CreateApplicationArea(Kernel::HLERequestContext& ctx); |
| 247 | void GetTagInfo(Kernel::HLERequestContext& ctx); | 149 | void GetTagInfo(Kernel::HLERequestContext& ctx); |
| 248 | void GetRegisterInfo(Kernel::HLERequestContext& ctx); | 150 | void GetRegisterInfo(Kernel::HLERequestContext& ctx); |
| @@ -255,6 +157,7 @@ private: | |||
| 255 | void GetNpadId(Kernel::HLERequestContext& ctx); | 157 | void GetNpadId(Kernel::HLERequestContext& ctx); |
| 256 | void GetApplicationAreaSize(Kernel::HLERequestContext& ctx); | 158 | void GetApplicationAreaSize(Kernel::HLERequestContext& ctx); |
| 257 | void AttachAvailabilityChangeEvent(Kernel::HLERequestContext& ctx); | 159 | void AttachAvailabilityChangeEvent(Kernel::HLERequestContext& ctx); |
| 160 | void RecreateApplicationArea(Kernel::HLERequestContext& ctx); | ||
| 258 | 161 | ||
| 259 | KernelHelpers::ServiceContext service_context; | 162 | KernelHelpers::ServiceContext service_context; |
| 260 | 163 | ||
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp index 2a5128c60..a7385fce8 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp | |||
| @@ -1,6 +1,7 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | 1 | // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project |
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | 2 | // SPDX-License-Identifier: GPL-2.0-or-later |
| 3 | 3 | ||
| 4 | #include "audio_core/audio_core.h" | ||
| 4 | #include "common/assert.h" | 5 | #include "common/assert.h" |
| 5 | #include "common/logging/log.h" | 6 | #include "common/logging/log.h" |
| 6 | #include "core/core.h" | 7 | #include "core/core.h" |
| @@ -65,7 +66,10 @@ NvResult nvhost_nvdec::Ioctl3(DeviceFD fd, Ioctl command, const std::vector<u8>& | |||
| 65 | return NvResult::NotImplemented; | 66 | return NvResult::NotImplemented; |
| 66 | } | 67 | } |
| 67 | 68 | ||
| 68 | void nvhost_nvdec::OnOpen(DeviceFD fd) {} | 69 | void nvhost_nvdec::OnOpen(DeviceFD fd) { |
| 70 | LOG_INFO(Service_NVDRV, "NVDEC video stream started"); | ||
| 71 | system.AudioCore().SetNVDECActive(true); | ||
| 72 | } | ||
| 69 | 73 | ||
| 70 | void nvhost_nvdec::OnClose(DeviceFD fd) { | 74 | void nvhost_nvdec::OnClose(DeviceFD fd) { |
| 71 | LOG_INFO(Service_NVDRV, "NVDEC video stream ended"); | 75 | LOG_INFO(Service_NVDRV, "NVDEC video stream ended"); |
| @@ -73,6 +77,7 @@ void nvhost_nvdec::OnClose(DeviceFD fd) { | |||
| 73 | if (iter != fd_to_id.end()) { | 77 | if (iter != fd_to_id.end()) { |
| 74 | system.GPU().ClearCdmaInstance(iter->second); | 78 | system.GPU().ClearCdmaInstance(iter->second); |
| 75 | } | 79 | } |
| 80 | system.AudioCore().SetNVDECActive(false); | ||
| 76 | } | 81 | } |
| 77 | 82 | ||
| 78 | } // namespace Service::Nvidia::Devices | 83 | } // namespace Service::Nvidia::Devices |
diff --git a/src/core/hle/service/nvflinger/nvflinger.cpp b/src/core/hle/service/nvflinger/nvflinger.cpp index 5574269eb..9b382bf56 100644 --- a/src/core/hle/service/nvflinger/nvflinger.cpp +++ b/src/core/hle/service/nvflinger/nvflinger.cpp | |||
| @@ -38,20 +38,16 @@ void NVFlinger::SplitVSync(std::stop_token stop_token) { | |||
| 38 | 38 | ||
| 39 | Common::SetCurrentThreadName(name.c_str()); | 39 | Common::SetCurrentThreadName(name.c_str()); |
| 40 | Common::SetCurrentThreadPriority(Common::ThreadPriority::High); | 40 | Common::SetCurrentThreadPriority(Common::ThreadPriority::High); |
| 41 | s64 delay = 0; | 41 | |
| 42 | while (!stop_token.stop_requested()) { | 42 | while (!stop_token.stop_requested()) { |
| 43 | vsync_signal.wait(false); | ||
| 44 | vsync_signal.store(false); | ||
| 45 | |||
| 43 | guard->lock(); | 46 | guard->lock(); |
| 44 | const s64 time_start = system.CoreTiming().GetGlobalTimeNs().count(); | 47 | |
| 45 | Compose(); | 48 | Compose(); |
| 46 | const auto ticks = GetNextTicks(); | 49 | |
| 47 | const s64 time_end = system.CoreTiming().GetGlobalTimeNs().count(); | ||
| 48 | const s64 time_passed = time_end - time_start; | ||
| 49 | const s64 next_time = std::max<s64>(0, ticks - time_passed - delay); | ||
| 50 | guard->unlock(); | 50 | guard->unlock(); |
| 51 | if (next_time > 0) { | ||
| 52 | std::this_thread::sleep_for(std::chrono::nanoseconds{next_time}); | ||
| 53 | } | ||
| 54 | delay = (system.CoreTiming().GetGlobalTimeNs().count() - time_end) - next_time; | ||
| 55 | } | 51 | } |
| 56 | } | 52 | } |
| 57 | 53 | ||
| @@ -66,27 +62,41 @@ NVFlinger::NVFlinger(Core::System& system_, HosBinderDriverServer& hos_binder_dr | |||
| 66 | guard = std::make_shared<std::mutex>(); | 62 | guard = std::make_shared<std::mutex>(); |
| 67 | 63 | ||
| 68 | // Schedule the screen composition events | 64 | // Schedule the screen composition events |
| 69 | composition_event = Core::Timing::CreateEvent( | 65 | multi_composition_event = Core::Timing::CreateEvent( |
| 66 | "ScreenComposition", | ||
| 67 | [this](std::uintptr_t, s64 time, | ||
| 68 | std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> { | ||
| 69 | vsync_signal.store(true); | ||
| 70 | vsync_signal.notify_all(); | ||
| 71 | return std::chrono::nanoseconds(GetNextTicks()); | ||
| 72 | }); | ||
| 73 | |||
| 74 | single_composition_event = Core::Timing::CreateEvent( | ||
| 70 | "ScreenComposition", | 75 | "ScreenComposition", |
| 71 | [this](std::uintptr_t, s64 time, | 76 | [this](std::uintptr_t, s64 time, |
| 72 | std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> { | 77 | std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> { |
| 73 | const auto lock_guard = Lock(); | 78 | const auto lock_guard = Lock(); |
| 74 | Compose(); | 79 | Compose(); |
| 75 | 80 | ||
| 76 | return std::max(std::chrono::nanoseconds::zero(), | 81 | return std::chrono::nanoseconds(GetNextTicks()); |
| 77 | std::chrono::nanoseconds(GetNextTicks()) - ns_late); | ||
| 78 | }); | 82 | }); |
| 79 | 83 | ||
| 80 | if (system.IsMulticore()) { | 84 | if (system.IsMulticore()) { |
| 85 | system.CoreTiming().ScheduleLoopingEvent(frame_ns, frame_ns, multi_composition_event); | ||
| 81 | vsync_thread = std::jthread([this](std::stop_token token) { SplitVSync(token); }); | 86 | vsync_thread = std::jthread([this](std::stop_token token) { SplitVSync(token); }); |
| 82 | } else { | 87 | } else { |
| 83 | system.CoreTiming().ScheduleLoopingEvent(frame_ns, frame_ns, composition_event); | 88 | system.CoreTiming().ScheduleLoopingEvent(frame_ns, frame_ns, single_composition_event); |
| 84 | } | 89 | } |
| 85 | } | 90 | } |
| 86 | 91 | ||
| 87 | NVFlinger::~NVFlinger() { | 92 | NVFlinger::~NVFlinger() { |
| 88 | if (!system.IsMulticore()) { | 93 | if (system.IsMulticore()) { |
| 89 | system.CoreTiming().UnscheduleEvent(composition_event, 0); | 94 | system.CoreTiming().UnscheduleEvent(multi_composition_event, {}); |
| 95 | vsync_thread.request_stop(); | ||
| 96 | vsync_signal.store(true); | ||
| 97 | vsync_signal.notify_all(); | ||
| 98 | } else { | ||
| 99 | system.CoreTiming().UnscheduleEvent(single_composition_event, {}); | ||
| 90 | } | 100 | } |
| 91 | 101 | ||
| 92 | for (auto& display : displays) { | 102 | for (auto& display : displays) { |
diff --git a/src/core/hle/service/nvflinger/nvflinger.h b/src/core/hle/service/nvflinger/nvflinger.h index 4775597cc..044ac6ac8 100644 --- a/src/core/hle/service/nvflinger/nvflinger.h +++ b/src/core/hle/service/nvflinger/nvflinger.h | |||
| @@ -126,12 +126,15 @@ private: | |||
| 126 | u32 swap_interval = 1; | 126 | u32 swap_interval = 1; |
| 127 | 127 | ||
| 128 | /// Event that handles screen composition. | 128 | /// Event that handles screen composition. |
| 129 | std::shared_ptr<Core::Timing::EventType> composition_event; | 129 | std::shared_ptr<Core::Timing::EventType> multi_composition_event; |
| 130 | std::shared_ptr<Core::Timing::EventType> single_composition_event; | ||
| 130 | 131 | ||
| 131 | std::shared_ptr<std::mutex> guard; | 132 | std::shared_ptr<std::mutex> guard; |
| 132 | 133 | ||
| 133 | Core::System& system; | 134 | Core::System& system; |
| 134 | 135 | ||
| 136 | std::atomic<bool> vsync_signal; | ||
| 137 | |||
| 135 | std::jthread vsync_thread; | 138 | std::jthread vsync_thread; |
| 136 | 139 | ||
| 137 | KernelHelpers::ServiceContext service_context; | 140 | KernelHelpers::ServiceContext service_context; |