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