From 8d5cde6eff64b6380f41233b55c94bfe24687fb7 Mon Sep 17 00:00:00 2001
From: Narr the Reg <juangerman-13@hotmail.com>
Date: Wed, 8 Mar 2023 22:15:36 -0600
Subject: [PATCH] service: nfp: Improve implementation

---
 .../hle/service/am/applets/applet_cabinet.cpp |   4 +-
 src/core/hle/service/nfp/amiibo_crypto.cpp    |  14 +-
 src/core/hle/service/nfp/nfp_device.cpp       | 210 +++++++++++++++---
 src/core/hle/service/nfp/nfp_device.h         |   9 +-
 src/core/hle/service/nfp/nfp_types.h          |  49 +++-
 5 files changed, 234 insertions(+), 52 deletions(-)

diff --git a/src/core/hle/service/am/applets/applet_cabinet.cpp b/src/core/hle/service/am/applets/applet_cabinet.cpp
index d0969b0f1..162687b29 100644
--- a/src/core/hle/service/am/applets/applet_cabinet.cpp
+++ b/src/core/hle/service/am/applets/applet_cabinet.cpp
@@ -119,7 +119,7 @@ void Cabinet::DisplayCompleted(bool apply_changes, std::string_view amiibo_name)
     case Service::NFP::CabinetMode::StartNicknameAndOwnerSettings: {
         Service::NFP::AmiiboName name{};
         std::memcpy(name.data(), amiibo_name.data(), std::min(amiibo_name.size(), name.size() - 1));
-        nfp_device->SetNicknameAndOwner(name);
+        nfp_device->SetRegisterInfoPrivate(name);
         break;
     }
     case Service::NFP::CabinetMode::StartGameDataEraser:
@@ -129,7 +129,7 @@ void Cabinet::DisplayCompleted(bool apply_changes, std::string_view amiibo_name)
         nfp_device->RestoreAmiibo();
         break;
     case Service::NFP::CabinetMode::StartFormatter:
-        nfp_device->DeleteAllData();
+        nfp_device->Format();
         break;
     default:
         UNIMPLEMENTED_MSG("Unknown CabinetMode={}", applet_input_common.applet_mode);
diff --git a/src/core/hle/service/nfp/amiibo_crypto.cpp b/src/core/hle/service/nfp/amiibo_crypto.cpp
index ffb2f959c..ddf04b1d7 100644
--- a/src/core/hle/service/nfp/amiibo_crypto.cpp
+++ b/src/core/hle/service/nfp/amiibo_crypto.cpp
@@ -80,13 +80,16 @@ NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data) {
     encoded_data.hmac_data = nfc_data.user_memory.hmac_data;
     encoded_data.constant_value = nfc_data.user_memory.constant_value;
     encoded_data.write_counter = nfc_data.user_memory.write_counter;
+    encoded_data.amiibo_version = nfc_data.user_memory.amiibo_version;
     encoded_data.settings = nfc_data.user_memory.settings;
     encoded_data.owner_mii = nfc_data.user_memory.owner_mii;
-    encoded_data.title_id = nfc_data.user_memory.title_id;
-    encoded_data.applicaton_write_counter = nfc_data.user_memory.applicaton_write_counter;
+    encoded_data.application_id = nfc_data.user_memory.application_id;
+    encoded_data.application_write_counter = nfc_data.user_memory.application_write_counter;
     encoded_data.application_area_id = nfc_data.user_memory.application_area_id;
+    encoded_data.application_id_byte = nfc_data.user_memory.application_id_byte;
     encoded_data.unknown = nfc_data.user_memory.unknown;
     encoded_data.unknown2 = nfc_data.user_memory.unknown2;
+    encoded_data.application_area_crc = nfc_data.user_memory.application_area_crc;
     encoded_data.application_area = nfc_data.user_memory.application_area;
     encoded_data.hmac_tag = nfc_data.user_memory.hmac_tag;
     encoded_data.lock_bytes = nfc_data.uuid.lock_bytes;
@@ -111,13 +114,16 @@ EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data) {
     nfc_data.user_memory.hmac_data = encoded_data.hmac_data;
     nfc_data.user_memory.constant_value = encoded_data.constant_value;
     nfc_data.user_memory.write_counter = encoded_data.write_counter;
+    nfc_data.user_memory.amiibo_version = encoded_data.amiibo_version;
     nfc_data.user_memory.settings = encoded_data.settings;
     nfc_data.user_memory.owner_mii = encoded_data.owner_mii;
-    nfc_data.user_memory.title_id = encoded_data.title_id;
-    nfc_data.user_memory.applicaton_write_counter = encoded_data.applicaton_write_counter;
+    nfc_data.user_memory.application_id = encoded_data.application_id;
+    nfc_data.user_memory.application_write_counter = encoded_data.application_write_counter;
     nfc_data.user_memory.application_area_id = encoded_data.application_area_id;
+    nfc_data.user_memory.application_id_byte = encoded_data.application_id_byte;
     nfc_data.user_memory.unknown = encoded_data.unknown;
     nfc_data.user_memory.unknown2 = encoded_data.unknown2;
+    nfc_data.user_memory.application_area_crc = encoded_data.application_area_crc;
     nfc_data.user_memory.application_area = encoded_data.application_area;
     nfc_data.user_memory.hmac_tag = encoded_data.hmac_tag;
     nfc_data.user_memory.model_info = encoded_data.model_info;
diff --git a/src/core/hle/service/nfp/nfp_device.cpp b/src/core/hle/service/nfp/nfp_device.cpp
index 1bdc42741..ddff90d6a 100644
--- a/src/core/hle/service/nfp/nfp_device.cpp
+++ b/src/core/hle/service/nfp/nfp_device.cpp
@@ -174,8 +174,8 @@ Result NfpDevice::StopDetection() {
 
     if (device_state == DeviceState::TagFound || device_state == DeviceState::TagMounted) {
         CloseAmiibo();
-        return ResultSuccess;
     }
+
     if (device_state == DeviceState::SearchingForTag || device_state == DeviceState::TagRemoved) {
         device_state = DeviceState::Initialized;
         return ResultSuccess;
@@ -204,9 +204,7 @@ Result NfpDevice::Flush() {
     const auto& current_date = GetAmiiboDate(current_posix_time);
     if (settings.write_date.raw_date != current_date.raw_date) {
         settings.write_date = current_date;
-        settings.crc_counter++;
-        // TODO: Find how to calculate the crc check
-        // settings.crc = CalculateCRC(settings);
+        UpdateSettingsCrc();
     }
 
     tag_data.write_counter++;
@@ -318,7 +316,7 @@ Result NfpDevice::GetCommonInfo(CommonInfo& common_info) const {
     common_info = {
         .last_write_date = settings.write_date.GetWriteDate(),
         .write_counter = tag_data.write_counter,
-        .version = 0,
+        .version = tag_data.amiibo_version,
         .application_area_size = sizeof(ApplicationArea),
     };
     return ResultSuccess;
@@ -370,13 +368,95 @@ Result NfpDevice::GetRegisterInfo(RegisterInfo& register_info) const {
         .mii_char_info = manager.ConvertV3ToCharInfo(tag_data.owner_mii),
         .creation_date = settings.init_date.GetWriteDate(),
         .amiibo_name = GetAmiiboName(settings),
-        .font_region = {},
+        .font_region = settings.settings.font_region,
     };
 
     return ResultSuccess;
 }
 
-Result NfpDevice::SetNicknameAndOwner(const AmiiboName& amiibo_name) {
+Result NfpDevice::GetAdminInfo(AdminInfo& admin_info) const {
+    if (device_state != DeviceState::TagMounted) {
+        LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
+        if (device_state == DeviceState::TagRemoved) {
+            return TagRemoved;
+        }
+        return WrongDeviceState;
+    }
+
+    if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
+        LOG_ERROR(Service_NFC, "Amiibo is read only", device_state);
+        return WrongDeviceState;
+    }
+
+    u8 flags = static_cast<u8>(tag_data.settings.settings.raw >> 0x4);
+    if (tag_data.settings.settings.amiibo_initialized == 0) {
+        flags = flags & 0xfe;
+    }
+
+    u64 application_id = 0;
+    u32 application_area_id = 0;
+    AppAreaVersion app_area_version = AppAreaVersion::NotSet;
+    if (tag_data.settings.settings.appdata_initialized != 0) {
+        application_id = tag_data.application_id;
+        app_area_version =
+            static_cast<AppAreaVersion>(application_id >> application_id_version_offset & 0xf);
+
+        // Restore application id to original value
+        if (application_id >> 0x38 != 0) {
+            const u8 application_byte = tag_data.application_id_byte & 0xf;
+            application_id = RemoveVersionByte(application_id) |
+                             (static_cast<u64>(application_byte) << application_id_version_offset);
+        }
+
+        application_area_id = tag_data.application_area_id;
+    }
+
+    // TODO: Validate this data
+    admin_info = {
+        .application_id = application_id,
+        .application_area_id = application_area_id,
+        .crc_change_counter = tag_data.settings.crc_counter,
+        .flags = flags,
+        .tag_type = PackedTagType::Type2,
+        .app_area_version = app_area_version,
+    };
+
+    return ResultSuccess;
+}
+
+Result NfpDevice::DeleteRegisterInfo() {
+    if (device_state != DeviceState::TagMounted) {
+        LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
+        if (device_state == DeviceState::TagRemoved) {
+            return TagRemoved;
+        }
+        return WrongDeviceState;
+    }
+
+    if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
+        LOG_ERROR(Service_NFC, "Amiibo is read only", device_state);
+        return WrongDeviceState;
+    }
+
+    if (tag_data.settings.settings.amiibo_initialized == 0) {
+        return RegistrationIsNotInitialized;
+    }
+
+    Common::TinyMT rng{};
+    rng.GenerateRandomBytes(&tag_data.owner_mii, sizeof(tag_data.owner_mii));
+    rng.GenerateRandomBytes(&tag_data.settings.amiibo_name, sizeof(tag_data.settings.amiibo_name));
+    rng.GenerateRandomBytes(&tag_data.unknown, sizeof(u8));
+    rng.GenerateRandomBytes(&tag_data.unknown2[0], sizeof(u32));
+    rng.GenerateRandomBytes(&tag_data.unknown2[1], sizeof(u32));
+    rng.GenerateRandomBytes(&tag_data.application_area_crc, sizeof(u32));
+    rng.GenerateRandomBytes(&tag_data.settings.init_date, sizeof(u32));
+    tag_data.settings.settings.font_region.Assign(0);
+    tag_data.settings.settings.amiibo_initialized.Assign(0);
+
+    return Flush();
+}
+
+Result NfpDevice::SetRegisterInfoPrivate(const AmiiboName& amiibo_name) {
     if (device_state != DeviceState::TagMounted) {
         LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
         if (device_state == DeviceState::TagRemoved) {
@@ -393,16 +473,23 @@ Result NfpDevice::SetNicknameAndOwner(const AmiiboName& amiibo_name) {
     Service::Mii::MiiManager manager;
     auto& settings = tag_data.settings;
 
-    settings.init_date = GetAmiiboDate(current_posix_time);
-    settings.write_date = GetAmiiboDate(current_posix_time);
-    settings.crc_counter++;
-    // TODO: Find how to calculate the crc check
-    // settings.crc = CalculateCRC(settings);
+    if (tag_data.settings.settings.amiibo_initialized == 0) {
+        settings.init_date = GetAmiiboDate(current_posix_time);
+        settings.write_date.raw_date = 0;
+    }
 
     SetAmiiboName(settings, amiibo_name);
     tag_data.owner_mii = manager.ConvertCharInfoToV3(manager.BuildDefault(0));
+    tag_data.unknown = 0;
+    tag_data.unknown2[6] = 0;
+    settings.country_code_id = 0;
+    settings.settings.font_region.Assign(0);
     settings.settings.amiibo_initialized.Assign(1);
 
+    // TODO: this is a mix of tag.file input
+    std::array<u8, 0x7e> unknown_input{};
+    tag_data.application_area_crc = CalculateCrc(unknown_input);
+
     return Flush();
 }
 
@@ -425,24 +512,18 @@ Result NfpDevice::RestoreAmiibo() {
     return ResultSuccess;
 }
 
-Result NfpDevice::DeleteAllData() {
-    const auto result = DeleteApplicationArea();
-    if (result.IsError()) {
-        return result;
+Result NfpDevice::Format() {
+    auto result1 = DeleteApplicationArea();
+    auto result2 = DeleteRegisterInfo();
+
+    if (result1.IsError()) {
+        return result1;
     }
 
-    if (device_state != DeviceState::TagMounted) {
-        LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
-        if (device_state == DeviceState::TagRemoved) {
-            return TagRemoved;
-        }
-        return WrongDeviceState;
+    if (result2.IsError()) {
+        return result2;
     }
 
-    Common::TinyMT rng{};
-    rng.GenerateRandomBytes(&tag_data.owner_mii, sizeof(tag_data.owner_mii));
-    tag_data.settings.settings.amiibo_initialized.Assign(0);
-
     return Flush();
 }
 
@@ -569,7 +650,10 @@ Result NfpDevice::SetApplicationArea(std::span<const u8> data) {
     rng.GenerateRandomBytes(tag_data.application_area.data() + data.size(),
                             sizeof(ApplicationArea) - data.size());
 
-    tag_data.applicaton_write_counter++;
+    if (tag_data.application_write_counter != counter_limit) {
+        tag_data.application_write_counter++;
+    }
+
     is_data_moddified = true;
 
     return ResultSuccess;
@@ -617,14 +701,25 @@ Result NfpDevice::RecreateApplicationArea(u32 access_id, std::span<const u8> dat
     rng.GenerateRandomBytes(tag_data.application_area.data() + data.size(),
                             sizeof(ApplicationArea) - data.size());
 
-    // TODO: Investigate why the title id needs to be moddified
-    tag_data.title_id = system.GetApplicationProcessProgramID();
-    tag_data.title_id = tag_data.title_id | 0x30000000ULL;
+    if (tag_data.application_write_counter != counter_limit) {
+        tag_data.application_write_counter++;
+    }
+
+    const u64 application_id = system.GetApplicationProcessProgramID();
+
+    tag_data.application_id_byte =
+        static_cast<u8>(application_id >> application_id_version_offset & 0xf);
+    tag_data.application_id =
+        RemoveVersionByte(application_id) |
+        (static_cast<u64>(AppAreaVersion::NintendoSwitch) << application_id_version_offset);
     tag_data.settings.settings.appdata_initialized.Assign(1);
     tag_data.application_area_id = access_id;
-    tag_data.applicaton_write_counter++;
     tag_data.unknown = {};
 
+    // TODO: this is a mix of tag_data input
+    std::array<u8, 0x7e> unknown_input{};
+    tag_data.application_area_crc = CalculateCrc(unknown_input);
+
     return Flush();
 }
 
@@ -642,12 +737,20 @@ Result NfpDevice::DeleteApplicationArea() {
         return WrongDeviceState;
     }
 
+    if (tag_data.settings.settings.appdata_initialized == 0) {
+        return ApplicationAreaIsNotInitialized;
+    }
+
+    if (tag_data.application_write_counter != counter_limit) {
+        tag_data.application_write_counter++;
+    }
+
     Common::TinyMT rng{};
     rng.GenerateRandomBytes(tag_data.application_area.data(), sizeof(ApplicationArea));
-    rng.GenerateRandomBytes(&tag_data.title_id, sizeof(u64));
+    rng.GenerateRandomBytes(&tag_data.application_id, sizeof(u64));
     rng.GenerateRandomBytes(&tag_data.application_area_id, sizeof(u32));
+    rng.GenerateRandomBytes(&tag_data.application_id_byte, sizeof(u8));
     tag_data.settings.settings.appdata_initialized.Assign(0);
-    tag_data.applicaton_write_counter++;
     tag_data.unknown = {};
 
     return Flush();
@@ -719,4 +822,45 @@ AmiiboDate NfpDevice::GetAmiiboDate(s64 posix_time) const {
     return amiibo_date;
 }
 
+u64 NfpDevice::RemoveVersionByte(u64 application_id) const {
+    return application_id & ~(0xfULL << application_id_version_offset);
+}
+
+void NfpDevice::UpdateSettingsCrc() {
+    auto& settings = tag_data.settings;
+
+    if (settings.crc_counter != counter_limit) {
+        settings.crc_counter++;
+    }
+
+    // TODO: this reads data from a global, find what it is
+    std::array<u8, 8> unknown_input{};
+    settings.crc = CalculateCrc(unknown_input);
+}
+
+u32 NfpDevice::CalculateCrc(std::span<const u8> data) {
+    constexpr u32 magic = 0xedb88320;
+    u32 crc = 0xffffffff;
+
+    if (data.size() == 0) {
+        return 0;
+    }
+
+    for (u8 input : data) {
+        u32 temp = (crc ^ input) >> 1;
+        if (((crc ^ input) & 1) != 0) {
+            temp = temp ^ magic;
+        }
+
+        for (std::size_t step = 0; step < 7; ++step) {
+            crc = temp >> 1;
+            if ((temp & 1) != 0) {
+                crc = temp >> 1 ^ magic;
+            }
+        }
+    }
+
+    return ~crc;
+}
+
 } // namespace Service::NFP
diff --git a/src/core/hle/service/nfp/nfp_device.h b/src/core/hle/service/nfp/nfp_device.h
index b6a46f2ac..06386401d 100644
--- a/src/core/hle/service/nfp/nfp_device.h
+++ b/src/core/hle/service/nfp/nfp_device.h
@@ -47,10 +47,12 @@ public:
     Result GetCommonInfo(CommonInfo& common_info) const;
     Result GetModelInfo(ModelInfo& model_info) const;
     Result GetRegisterInfo(RegisterInfo& register_info) const;
+    Result GetAdminInfo(AdminInfo& admin_info) const;
 
-    Result SetNicknameAndOwner(const AmiiboName& amiibo_name);
+    Result DeleteRegisterInfo();
+    Result SetRegisterInfoPrivate(const AmiiboName& amiibo_name);
     Result RestoreAmiibo();
-    Result DeleteAllData();
+    Result Format();
 
     Result OpenApplicationArea(u32 access_id);
     Result GetApplicationAreaId(u32& application_area_id) const;
@@ -76,6 +78,9 @@ private:
     AmiiboName GetAmiiboName(const AmiiboSettings& settings) const;
     void SetAmiiboName(AmiiboSettings& settings, const AmiiboName& amiibo_name);
     AmiiboDate GetAmiiboDate(s64 posix_time) const;
+    u64 RemoveVersionByte(u64 application_id) const;
+    void UpdateSettingsCrc();
+    u32 CalculateCrc(std::span<const u8>);
 
     bool is_controller_set{};
     int callback_key;
diff --git a/src/core/hle/service/nfp/nfp_types.h b/src/core/hle/service/nfp/nfp_types.h
index fc228c2b2..142343d6e 100644
--- a/src/core/hle/service/nfp/nfp_types.h
+++ b/src/core/hle/service/nfp/nfp_types.h
@@ -10,6 +10,8 @@
 
 namespace Service::NFP {
 static constexpr std::size_t amiibo_name_length = 0xA;
+static constexpr std::size_t application_id_version_offset = 0x1c;
+static constexpr std::size_t counter_limit = 0xffff;
 
 enum class ServiceType : u32 {
     User,
@@ -99,6 +101,14 @@ enum class TagProtocol : u32 {
     All = 0xFFFFFFFFU,
 };
 
+enum class AppAreaVersion : u8 {
+    Nintendo3DS = 0,
+    NintendoWiiU = 1,
+    Nintendo3DSv2 = 2,
+    NintendoSwitch = 3,
+    NotSet = 0xFF,
+};
+
 enum class CabinetMode : u8 {
     StartNicknameAndOwnerSettings,
     StartGameDataEraser,
@@ -197,6 +207,7 @@ struct Settings {
     union {
         u8 raw{};
 
+        BitField<0, 4, u8> font_region;
         BitField<4, 1, u8> amiibo_initialized;
         BitField<5, 1, u8> appdata_initialized;
     };
@@ -236,18 +247,20 @@ static_assert(sizeof(NTAG215Password) == 0x8, "NTAG215Password is an invalid siz
 struct EncryptedAmiiboFile {
     u8 constant_value;                     // Must be A5
     u16_be write_counter;                  // Number of times the amiibo has been written?
-    INSERT_PADDING_BYTES(0x1);             // Unknown 1
+    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
     Service::Mii::Ver3StoreData owner_mii; // Encrypted Mii data
-    u64_be title_id;                       // Encrypted Game id
-    u16_be applicaton_write_counter;       // Encrypted Counter
+    u64_be application_id;                 // Encrypted Game id
+    u16_be application_write_counter;      // Encrypted Counter
     u32_be application_area_id;            // Encrypted Game id
-    std::array<u8, 0x2> unknown;
-    std::array<u32, 0x8> unknown2;
+    u8 application_id_byte;
+    u8 unknown;
+    std::array<u32, 0x7> unknown2;
+    u32_be application_area_crc;
     ApplicationArea application_area; // Encrypted Game data
 };
 static_assert(sizeof(EncryptedAmiiboFile) == 0x1F8, "AmiiboFile is an invalid size");
@@ -259,14 +272,16 @@ struct NTAG215File {
     HashData hmac_data;        // Hash
     u8 constant_value;         // Must be A5
     u16_be write_counter;      // Number of times the amiibo has been written?
-    INSERT_PADDING_BYTES(0x1); // Unknown 1
+    u8 amiibo_version;         // Amiibo file version
     AmiiboSettings settings;
-    Service::Mii::Ver3StoreData owner_mii; // Encrypted Mii data
-    u64_be title_id;
-    u16_be applicaton_write_counter; // Encrypted Counter
+    Service::Mii::Ver3StoreData owner_mii; // Mii data
+    u64_be application_id;                 // Game id
+    u16_be application_write_counter;      // Counter
     u32_be application_area_id;
-    std::array<u8, 0x2> unknown;
-    std::array<u32, 0x8> unknown2;
+    u8 application_id_byte;
+    u8 unknown;
+    std::array<u32, 0x7> unknown2;
+    u32_be application_area_crc;
     ApplicationArea application_area; // Encrypted Game data
     HashData hmac_tag;                // Hash
     UniqueSerialNumber uid;           // Unique serial number
@@ -336,6 +351,18 @@ struct RegisterInfo {
 };
 static_assert(sizeof(RegisterInfo) == 0x100, "RegisterInfo is an invalid size");
 
+struct AdminInfo {
+    u64 application_id;
+    u32 application_area_id;
+    u16 crc_change_counter;
+    u8 flags;
+    PackedTagType tag_type;
+    AppAreaVersion app_area_version;
+    INSERT_PADDING_BYTES(0x7);
+    INSERT_PADDING_BYTES(0x28);
+};
+static_assert(sizeof(AdminInfo) == 0x40, "AdminInfo is an invalid size");
+
 struct SectorKey {
     MifareCmd command;
     u8 unknown; // Usually 1