diff --git a/CMakeLists.txt b/CMakeLists.txt index b4ca6745..90e2ffb9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -181,7 +181,7 @@ set(HEADER_FILES include/emulator.hpp include/helpers.hpp include/termcolor.hpp include/fs/romfs.hpp include/fs/ivfc.hpp include/discord_rpc.hpp include/services/http.hpp include/result/result_cfg.hpp include/applets/applet.hpp include/applets/mii_selector.hpp include/math_util.hpp include/services/soc.hpp include/services/news_u.hpp include/applets/software_keyboard.hpp include/applets/applet_manager.hpp include/fs/archive_user_save_data.hpp - include/services/amiibo_device.hpp + include/services/amiibo_device.hpp include/services/nfc_types.hpp include/swap.hpp ) cmrc_add_resource_library( diff --git a/include/services/amiibo_device.hpp b/include/services/amiibo_device.hpp index 0b9f21e1..a07d8ac1 100644 --- a/include/services/amiibo_device.hpp +++ b/include/services/amiibo_device.hpp @@ -1,15 +1,17 @@ #pragma once #include - #include "helpers.hpp" #include "io_file.hpp" +#include "nfc_types.hpp" class AmiiboDevice { + bool loaded = false; + bool encrypted = false; + public: static constexpr size_t tagSize = 0x21C; - - bool loaded = false; std::array raw; + void loadFromRaw(); void reset(); }; \ No newline at end of file diff --git a/include/services/nfc_types.hpp b/include/services/nfc_types.hpp new file mode 100644 index 00000000..fc0c323f --- /dev/null +++ b/include/services/nfc_types.hpp @@ -0,0 +1,411 @@ +// Copyright 2022 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include + +#include "helpers.hpp" +#include "swap.hpp" + +namespace Service::NFC { + static constexpr std::size_t amiiboNameLength = 0xA; + static constexpr std::size_t applicationIDVersionOffset = 0x1c; + static constexpr std::size_t counterLimit = 0xffff; + + enum class ServiceType : u32 { + User, + Debug, + System, + }; + + enum class CommunicationState : u8 { + Idle = 0, + SearchingForAdapter = 1, + Initialized = 2, + Active = 3, + }; + + enum class ConnectionState : u8 { + Success = 0, + NoAdapter = 1, + Lost = 2, + }; + + enum class DeviceState : u32 { + NotInitialized = 0, + Initialized = 1, + SearchingForTag = 2, + TagFound = 3, + TagRemoved = 4, + TagMounted = 5, + TagPartiallyMounted = 6, // Validate this one seems to have other name + }; + + enum class ModelType : u32 { + Amiibo, + }; + + enum class MountTarget : u32 { + None, + Rom, + Ram, + All, + }; + + enum class AmiiboType : u8 { + Figure, + Card, + Yarn, + }; + + enum class AmiiboSeries : u8 { + SuperSmashBros, + SuperMario, + ChibiRobo, + YoshiWoollyWorld, + Splatoon, + AnimalCrossing, + EightBitMario, + Skylanders, + Unknown8, + TheLegendOfZelda, + ShovelKnight, + Unknown11, + Kiby, + Pokemon, + MarioSportsSuperstars, + MonsterHunter, + BoxBoy, + Pikmin, + FireEmblem, + Metroid, + Others, + MegaMan, + Diablo, + }; + + struct ChecksummedMiiData { + u8 raw[0x60]; + }; + static_assert(sizeof(ChecksummedMiiData) == 0x60); + + enum class TagType : u32 { + None, + Type1, // ISO14443A RW 96-2k bytes 106kbit/s + Type2, // ISO14443A RW/RO 540 bytes 106kbit/s + Type3, // Sony Felica RW/RO 2k bytes 212kbit/s + Type4, // ISO14443A RW/RO 4k-32k bytes 424kbit/s + Type5, // ISO15693 RW/RO 540 bytes 106kbit/s + }; + + enum class PackedTagType : u8 { + None, + Type1, // ISO14443A RW 96-2k bytes 106kbit/s + Type2, // ISO14443A RW/RO 540 bytes 106kbit/s + Type3, // Sony Felica RW/RO 2k bytes 212kbit/s + Type4, // ISO14443A RW/RO 4k-32k bytes 424kbit/s + Type5, // ISO15693 RW/RO 540 bytes 106kbit/s + }; + + // Verify this enum. It might be completely wrong default protocol is 0x0 + enum class TagProtocol : u32 { + None, + TypeA = 1U << 0, // ISO14443A + TypeB = 1U << 1, // ISO14443B + TypeF = 1U << 2, // Sony Felica + Unknown1 = 1U << 3, + Unknown2 = 1U << 5, + All = 0xFFFFFFFFU, + }; + + // Verify this enum. It might be completely wrong default protocol is 0x0 + enum class PackedTagProtocol : u8 { + None, + TypeA = 1U << 0, // ISO14443A + TypeB = 1U << 1, // ISO14443B + TypeF = 1U << 2, // Sony Felica + Unknown1 = 1U << 3, + Unknown2 = 1U << 5, + All = 0xFF, + }; + + enum class AppAreaVersion : u8 { + Nintendo3DS = 0, + NintendoWiiU = 1, + Nintendo3DSv2 = 2, + NintendoSwitch = 3, + NotSet = 0xFF, + }; + + using UniqueSerialNumber = std::array; + using LockBytes = std::array; + using HashData = std::array; + using ApplicationArea = std::array; + using AmiiboName = std::array; + using DataBlock = std::array; + using KeyData = std::array; + + struct TagUuid { + UniqueSerialNumber uid; + u8 nintendo_id; + LockBytes lock_bytes; + }; + static_assert(sizeof(TagUuid) == 10, "TagUuid is an invalid size"); + + struct WriteDate { + u16 year; + u8 month; + u8 day; + }; + static_assert(sizeof(WriteDate) == 0x4, "WriteDate is an invalid size"); + + struct AmiiboDate { + u16 raw_date{}; + + u16 GetValue() const { return Common::swap16(raw_date); } + + u16 GetYear() const { return static_cast(((GetValue() & 0xFE00) >> 9) + 2000); } + u8 GetMonth() const { return static_cast((GetValue() & 0x01E0) >> 5); } + u8 GetDay() const { return static_cast(GetValue() & 0x001F); } + + WriteDate GetWriteDate() const { + if (!IsValidDate()) { + return { + .year = 2000, + .month = 1, + .day = 1, + }; + } + return { + .year = GetYear(), + .month = GetMonth(), + .day = GetDay(), + }; + } + + void SetYear(u16 year) { + const u16 year_converted = static_cast((year - 2000) << 9); + raw_date = Common::swap16((GetValue() & ~0xFE00) | year_converted); + } + void SetMonth(u8 month) { + const u16 month_converted = static_cast(month << 5); + raw_date = Common::swap16((GetValue() & ~0x01E0) | month_converted); + } + void SetDay(u8 day) { + const u16 day_converted = static_cast(day); + raw_date = Common::swap16((GetValue() & ~0x001F) | day_converted); + } + + bool IsValidDate() const { + const bool is_day_valid = GetDay() > 0 && GetDay() < 32; + const bool is_month_valid = GetMonth() > 0 && GetMonth() < 13; + const bool is_year_valid = GetYear() >= 2000; + return is_year_valid && is_month_valid && is_day_valid; + } + }; + static_assert(sizeof(AmiiboDate) == 2, "AmiiboDate is an invalid size"); + + struct Settings { + u8 raw{}; + }; + static_assert(sizeof(Settings) == 1, "AmiiboDate is an invalid size"); + + struct AmiiboSettings { + Settings settings; + u8 country_code_id; + u16_be crc_counter; // Incremented each time crc is changed + AmiiboDate init_date; + AmiiboDate write_date; + u32_be crc; + AmiiboName amiibo_name; // UTF-16 text + }; + static_assert(sizeof(AmiiboSettings) == 0x20, "AmiiboSettings is an invalid size"); + + struct AmiiboModelInfo { + u16 character_id; + u8 character_variant; + AmiiboType amiibo_type; + u16_be model_number; + AmiiboSeries series; + PackedTagType tag_type; + u32 pad; // Unknown + }; + static_assert(sizeof(AmiiboModelInfo) == 0xC, "AmiiboModelInfo is an invalid size"); + + struct NTAG215Password { + u32 PWD; // Password to allow write access + u16 PACK; // Password acknowledge reply + u16 RFUI; // Reserved for future use + }; + static_assert(sizeof(NTAG215Password) == 0x8, "NTAG215Password is an invalid size"); + +#pragma pack(1) + struct EncryptedAmiiboFile { + u8 constant_value; // Must be A5 + u16_be write_counter; // Number of times the amiibo has been written? + u8 amiibo_version; // Amiibo file version + AmiiboSettings settings; // Encrypted amiibo settings + HashData hmac_tag; // Hash + AmiiboModelInfo model_info; // Encrypted amiibo model info + HashData keygen_salt; // Salt + HashData hmac_data; // Hash + ChecksummedMiiData owner_mii; // Encrypted Mii data + u64_be application_id; // Encrypted Game id + u16_be application_write_counter; // Encrypted Counter + u32_be application_area_id; // Encrypted Game id + u8 application_id_byte; + u8 unknown; + u64 mii_extension; + std::array unknown2; + u32_be register_info_crc; + ApplicationArea application_area; // Encrypted Game data + }; + static_assert(sizeof(EncryptedAmiiboFile) == 0x1F8, "AmiiboFile is an invalid size"); + + struct NTAG215File { + LockBytes lock_bytes; // Tag UUID + u16 static_lock; // Set defined pages as read only + u32 compability_container; // Defines available memory + HashData hmac_data; // Hash + u8 constant_value; // Must be A5 + u16_be write_counter; // Number of times the amiibo has been written? + u8 amiibo_version; // Amiibo file version + AmiiboSettings settings; + ChecksummedMiiData owner_mii; // Mii data + u64_be application_id; // Game id + u16_be application_write_counter; // Counter + u32_be application_area_id; + u8 application_id_byte; + u8 unknown; + u64 mii_extension; + std::array unknown2; + u32_be register_info_crc; + ApplicationArea application_area; // Game data + HashData hmac_tag; // Hash + UniqueSerialNumber uid; // Unique serial number + u8 nintendo_id; // Tag UUID + AmiiboModelInfo model_info; + HashData keygen_salt; // Salt + u32 dynamic_lock; // Dynamic lock + u32 CFG0; // Defines memory protected by password + u32 CFG1; // Defines number of verification attempts + NTAG215Password password; // Password data + }; + static_assert(sizeof(NTAG215File) == 0x21C, "NTAG215File is an invalid size"); + static_assert(std::is_trivially_copyable_v, "NTAG215File must be trivially copyable."); +#pragma pack() + + struct EncryptedNTAG215File { + TagUuid uuid; // Unique serial number + u16 static_lock; // Set defined pages as read only + u32 compability_container; // Defines available memory + EncryptedAmiiboFile user_memory; // Writable data + u32 dynamic_lock; // Dynamic lock + u32 CFG0; // Defines memory protected by password + u32 CFG1; // Defines number of verification attempts + NTAG215Password password; // Password data + }; + static_assert(sizeof(EncryptedNTAG215File) == 0x21C, "EncryptedNTAG215File is an invalid size"); + static_assert(std::is_trivially_copyable_v, "EncryptedNTAG215File must be trivially copyable."); + + struct SerializableAmiiboFile { + union { + std::array raw; + NTAG215File file; + }; + }; + static_assert(sizeof(SerializableAmiiboFile) == 0x21C, "SerializableAmiiboFile is an invalid size"); + static_assert(std::is_trivially_copyable_v, "SerializableAmiiboFile must be trivially copyable."); + + struct SerializableEncryptedAmiiboFile { + union { + std::array raw; + EncryptedNTAG215File file; + }; + }; + static_assert(sizeof(SerializableEncryptedAmiiboFile) == 0x21C, "SerializableEncryptedAmiiboFile is an invalid size"); + static_assert(std::is_trivially_copyable_v, "SerializableEncryptedAmiiboFile must be trivially copyable."); + + struct TagInfo { + u16 uuid_length; + PackedTagProtocol protocol; + PackedTagType tag_type; + UniqueSerialNumber uuid; + std::array extra_data; + }; + static_assert(sizeof(TagInfo) == 0x2C, "TagInfo is an invalid size"); + + struct TagInfo2 { + u16 uuid_length; + u8 pad; + PackedTagType tag_type; + UniqueSerialNumber uuid; + std::array extra_data; + TagProtocol protocol; + std::array extra_data2; + }; + static_assert(sizeof(TagInfo2) == 0x60, "TagInfo2 is an invalid size"); + + struct CommonInfo { + WriteDate last_write_date; + u16 application_write_counter; + u16 character_id; + u8 character_variant; + AmiiboSeries series; + u16 model_number; + AmiiboType amiibo_type; + u8 version; + u16 application_area_size; + u8 pad[0x30]; + }; + static_assert(sizeof(CommonInfo) == 0x40, "CommonInfo is an invalid size"); + + struct ModelInfo { + u16 character_id; + u8 character_variant; + AmiiboSeries series; + u16 model_number; + AmiiboType amiibo_type; + u8 pad[0x2F]; + }; + static_assert(sizeof(ModelInfo) == 0x36, "ModelInfo is an invalid size"); + + struct RegisterInfo { + ChecksummedMiiData mii_data; + AmiiboName amiibo_name; + u16 pad; // Zero string terminator + u8 flags; + u8 font_region; + WriteDate creation_date; + u8 pad2[0x2C]; + }; + static_assert(sizeof(RegisterInfo) == 0xA8, "RegisterInfo is an invalid size"); + + struct RegisterInfoPrivate { + ChecksummedMiiData mii_data; + AmiiboName amiibo_name; + u16 pad; // Zero string terminator + u8 flags; + u8 font_region; + WriteDate creation_date; + u8 pad2[0x28]; + }; + static_assert(sizeof(RegisterInfoPrivate) == 0xA4, "RegisterInfoPrivate is an invalid size"); + static_assert(std::is_trivial_v, "RegisterInfoPrivate must be trivial."); + static_assert(std::is_trivially_copyable_v, "RegisterInfoPrivate must be trivially copyable."); + + struct AdminInfo { + u64_be application_id; + u32_be application_area_id; + u16 crc_counter; + u8 flags; + PackedTagType tag_type; + AppAreaVersion app_area_version; + u8 pad[0x7]; + u8 pad2[0x28]; + }; + static_assert(sizeof(AdminInfo) == 0x40, "AdminInfo is an invalid size"); + +} // namespace Service::NFC \ No newline at end of file diff --git a/third_party/cityhash/swap.hpp b/include/swap.hpp similarity index 100% rename from third_party/cityhash/swap.hpp rename to include/swap.hpp diff --git a/src/core/services/amiibo_device.cpp b/src/core/services/amiibo_device.cpp index 58a0e2c6..71cabf0e 100644 --- a/src/core/services/amiibo_device.cpp +++ b/src/core/services/amiibo_device.cpp @@ -1,3 +1,11 @@ #include "services/amiibo_device.hpp" -void AmiiboDevice::reset() { loaded = false; } \ No newline at end of file +void AmiiboDevice::reset() { + encrypted = false; + loaded = false; +} + +// Load amiibo information from our raw 540 byte array +void AmiiboDevice::loadFromRaw() { + +} \ No newline at end of file diff --git a/src/core/services/nfc.cpp b/src/core/services/nfc.cpp index 2ad20910..e9a61ef9 100644 --- a/src/core/services/nfc.cpp +++ b/src/core/services/nfc.cpp @@ -75,6 +75,8 @@ bool NFCService::loadAmiibo(const std::filesystem::path& path) { return false; } + device.loadFromRaw(); + if (tagOutOfRangeEvent.has_value()) { kernel.clearEvent(tagOutOfRangeEvent.value()); }