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