mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-30 21:30:04 +00:00 
			
		
		
		
	service: nfc: Implement amiibo encryption and appdata (#6340)
This commit is contained in:
		
							parent
							
								
									ca2d87e5e3
								
							
						
					
					
						commit
						3d0a3c2c45
					
				
					 16 changed files with 3016 additions and 323 deletions
				
			
		|  | @ -585,7 +585,7 @@ public final class NativeLibrary { | |||
|     /// Notifies that the activity is now in foreground and camera devices can now be reloaded | ||||
|     public static native void ReloadCameraDevices(); | ||||
| 
 | ||||
|     public static native boolean LoadAmiibo(byte[] bytes); | ||||
|     public static native boolean LoadAmiibo(String path); | ||||
| 
 | ||||
|     public static native void RemoveAmiibo(); | ||||
| 
 | ||||
|  |  | |||
|  | @ -570,15 +570,7 @@ public final class EmulationActivity extends AppCompatActivity { | |||
|     } | ||||
| 
 | ||||
|     private void onAmiiboSelected(String selectedFile) { | ||||
|         boolean success = false; | ||||
|         try { | ||||
|             Uri uri = Uri.parse(selectedFile); | ||||
|             DocumentFile file = DocumentFile.fromSingleUri(this, uri); | ||||
|             byte[] bytes = FileUtil.getBytesFromFile(this, file); | ||||
|             success = NativeLibrary.LoadAmiibo(bytes); | ||||
|         } catch (IOException e) { | ||||
|             e.printStackTrace(); | ||||
|         } | ||||
|         boolean success = NativeLibrary.LoadAmiibo(selectedFile); | ||||
| 
 | ||||
|         if (!success) { | ||||
|             new MaterialAlertDialogBuilder(this) | ||||
|  |  | |||
|  | @ -569,20 +569,16 @@ void Java_org_citra_citra_1emu_NativeLibrary_ReloadCameraDevices(JNIEnv* env, jc | |||
| } | ||||
| 
 | ||||
| jboolean Java_org_citra_citra_1emu_NativeLibrary_LoadAmiibo(JNIEnv* env, jclass clazz, | ||||
|                                                             jbyteArray bytes) { | ||||
|                                                             jstring j_file) { | ||||
|     std::string filepath = GetJString(env, j_file); | ||||
|     Core::System& system{Core::System::GetInstance()}; | ||||
|     Service::SM::ServiceManager& sm = system.ServiceManager(); | ||||
|     auto nfc = sm.GetService<Service::NFC::Module::Interface>("nfc:u"); | ||||
|     if (nfc == nullptr || env->GetArrayLength(bytes) != sizeof(Service::NFC::AmiiboData)) { | ||||
|     if (nfc == nullptr) { | ||||
|         return static_cast<jboolean>(false); | ||||
|     } | ||||
| 
 | ||||
|     Service::NFC::AmiiboData amiibo_data{}; | ||||
|     env->GetByteArrayRegion(bytes, 0, sizeof(Service::NFC::AmiiboData), | ||||
|                             reinterpret_cast<jbyte*>(&amiibo_data)); | ||||
| 
 | ||||
|     nfc->LoadAmiibo(amiibo_data); | ||||
|     return static_cast<jboolean>(true); | ||||
|     return static_cast<jboolean>(nfc->LoadAmiibo(filepath)); | ||||
| } | ||||
| 
 | ||||
| void Java_org_citra_citra_1emu_NativeLibrary_RemoveAmiibo(JNIEnv* env, jclass clazz) { | ||||
|  |  | |||
|  | @ -142,7 +142,7 @@ JNIEXPORT void JNICALL Java_org_citra_citra_1emu_NativeLibrary_ReloadCameraDevic | |||
|                                                                                    jclass clazz); | ||||
| 
 | ||||
| JNIEXPORT jboolean Java_org_citra_citra_1emu_NativeLibrary_LoadAmiibo(JNIEnv* env, jclass clazz, | ||||
|                                                                       jbyteArray bytes); | ||||
|                                                                       jstring j_file); | ||||
| 
 | ||||
| JNIEXPORT void Java_org_citra_citra_1emu_NativeLibrary_RemoveAmiibo(JNIEnv* env, jclass clazz); | ||||
| 
 | ||||
|  |  | |||
|  | @ -2017,6 +2017,25 @@ void GMainWindow::OnLoadAmiibo() { | |||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     Core::System& system{Core::System::GetInstance()}; | ||||
|     Service::SM::ServiceManager& sm = system.ServiceManager(); | ||||
|     auto nfc = sm.GetService<Service::NFC::Module::Interface>("nfc:u"); | ||||
|     if (nfc == nullptr) { | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     if (nfc->IsTagActive()) { | ||||
|         QMessageBox::warning(this, tr("Error opening amiibo data file"), | ||||
|                              tr("A tag is already in use.")); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     if (!nfc->IsSearchingForAmiibos()) { | ||||
|         QMessageBox::warning(this, tr("Error opening amiibo data file"), | ||||
|                              tr("Game is not looking for amiibos.")); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     const QString extensions{QStringLiteral("*.bin")}; | ||||
|     const QString file_filter = tr("Amiibo File (%1);; All Files (*.*)").arg(extensions); | ||||
|     const QString filename = QFileDialog::getOpenFileName(this, tr("Load Amiibo"), {}, file_filter); | ||||
|  | @ -2035,26 +2054,12 @@ void GMainWindow::LoadAmiibo(const QString& filename) { | |||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     QFile nfc_file{filename}; | ||||
|     if (!nfc_file.open(QIODevice::ReadOnly)) { | ||||
|         QMessageBox::warning(this, tr("Error opening Amiibo data file"), | ||||
|                              tr("Unable to open Amiibo file \"%1\" for reading.").arg(filename)); | ||||
|     if (!nfc->LoadAmiibo(filename.toStdString())) { | ||||
|         QMessageBox::warning(this, tr("Error opening amiibo data file"), | ||||
|                              tr("Unable to open amiibo file \"%1\" for reading.").arg(filename)); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     Service::NFC::AmiiboData amiibo_data{}; | ||||
|     const u64 read_size = | ||||
|         nfc_file.read(reinterpret_cast<char*>(&amiibo_data), sizeof(Service::NFC::AmiiboData)); | ||||
|     if (read_size != sizeof(Service::NFC::AmiiboData)) { | ||||
|         QMessageBox::warning(this, tr("Error reading Amiibo data file"), | ||||
|                              tr("Unable to fully read Amiibo data. Expected to read %1 bytes, but " | ||||
|                                 "was only able to read %2 bytes.") | ||||
|                                  .arg(sizeof(Service::NFC::AmiiboData)) | ||||
|                                  .arg(read_size)); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     nfc->LoadAmiibo(amiibo_data); | ||||
|     ui->action_Remove_Amiibo->setEnabled(true); | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -333,10 +333,16 @@ add_library(citra_core STATIC | |||
|     hle/service/news/news_s.h | ||||
|     hle/service/news/news_u.cpp | ||||
|     hle/service/news/news_u.h | ||||
|     hle/service/nfc/amiibo_crypto.cpp | ||||
|     hle/service/nfc/amiibo_crypto.h | ||||
|     hle/service/nfc/nfc.cpp | ||||
|     hle/service/nfc/nfc.h | ||||
|     hle/service/nfc/nfc_device.cpp | ||||
|     hle/service/nfc/nfc_device.h | ||||
|     hle/service/nfc/nfc_m.cpp | ||||
|     hle/service/nfc/nfc_m.h | ||||
|     hle/service/nfc/nfc_results.h | ||||
|     hle/service/nfc/nfc_types.h | ||||
|     hle/service/nfc/nfc_u.cpp | ||||
|     hle/service/nfc/nfc_u.h | ||||
|     hle/service/nim/nim.cpp | ||||
|  |  | |||
							
								
								
									
										374
									
								
								src/core/hle/service/nfc/amiibo_crypto.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										374
									
								
								src/core/hle/service/nfc/amiibo_crypto.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,374 @@ | |||
| // Copyright 2022 yuzu Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| // Copyright 2017 socram8888/amiitool
 | ||||
| // Licensed under MIT
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <array> | ||||
| #include <cryptopp/aes.h> | ||||
| #include <cryptopp/hmac.h> | ||||
| #include <cryptopp/modes.h> | ||||
| #include <cryptopp/sha.h> | ||||
| 
 | ||||
| #include "common/file_util.h" | ||||
| #include "common/logging/log.h" | ||||
| #include "core/hle/service/nfc/amiibo_crypto.h" | ||||
| 
 | ||||
| namespace Service::NFC::AmiiboCrypto { | ||||
| 
 | ||||
| bool IsAmiiboValid(const EncryptedNTAG215File& ntag_file) { | ||||
|     const auto& amiibo_data = ntag_file.user_memory; | ||||
|     LOG_DEBUG(Service_NFC, "uuid_lock=0x{0:x}", ntag_file.static_lock); | ||||
|     LOG_DEBUG(Service_NFC, "compability_container=0x{0:x}", ntag_file.compability_container); | ||||
|     LOG_DEBUG(Service_NFC, "write_count={}", static_cast<u16>(amiibo_data.write_counter)); | ||||
| 
 | ||||
|     LOG_DEBUG(Service_NFC, "character_id=0x{0:x}", amiibo_data.model_info.character_id); | ||||
|     LOG_DEBUG(Service_NFC, "character_variant={}", amiibo_data.model_info.character_variant); | ||||
|     LOG_DEBUG(Service_NFC, "amiibo_type={}", amiibo_data.model_info.amiibo_type); | ||||
|     LOG_DEBUG(Service_NFC, "model_number=0x{0:x}", | ||||
|               static_cast<u16>(amiibo_data.model_info.model_number)); | ||||
|     LOG_DEBUG(Service_NFC, "series={}", amiibo_data.model_info.series); | ||||
|     LOG_DEBUG(Service_NFC, "tag_type=0x{0:x}", amiibo_data.model_info.tag_type); | ||||
| 
 | ||||
|     LOG_DEBUG(Service_NFC, "tag_dynamic_lock=0x{0:x}", ntag_file.dynamic_lock); | ||||
|     LOG_DEBUG(Service_NFC, "tag_CFG0=0x{0:x}", ntag_file.CFG0); | ||||
|     LOG_DEBUG(Service_NFC, "tag_CFG1=0x{0:x}", ntag_file.CFG1); | ||||
| 
 | ||||
|     // Validate UUID
 | ||||
|     constexpr u8 CT = 0x88; // As defined in `ISO / IEC 14443 - 3`
 | ||||
|     if ((CT ^ ntag_file.uuid.uid[0] ^ ntag_file.uuid.uid[1] ^ ntag_file.uuid.uid[2]) != | ||||
|         ntag_file.uuid.uid[3]) { | ||||
|         return false; | ||||
|     } | ||||
|     if ((ntag_file.uuid.uid[4] ^ ntag_file.uuid.uid[5] ^ ntag_file.uuid.uid[6] ^ | ||||
|          ntag_file.uuid.nintendo_id) != ntag_file.uuid.lock_bytes[0]) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     // Check against all know constants on an amiibo binary
 | ||||
|     if (ntag_file.static_lock != 0xE00F) { | ||||
|         return false; | ||||
|     } | ||||
|     if (ntag_file.compability_container != 0xEEFF10F1U) { | ||||
|         return false; | ||||
|     } | ||||
|     if (amiibo_data.model_info.tag_type != PackedTagType::Type2) { | ||||
|         return false; | ||||
|     } | ||||
|     if ((ntag_file.dynamic_lock & 0xFFFFFF) != 0x0F0001U) { | ||||
|         return false; | ||||
|     } | ||||
|     if (ntag_file.CFG0 != 0x04000000U) { | ||||
|         return false; | ||||
|     } | ||||
|     if (ntag_file.CFG1 != 0x5F) { | ||||
|         return false; | ||||
|     } | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| bool IsAmiiboValid(const NTAG215File& ntag_file) { | ||||
|     return IsAmiiboValid(EncodedDataToNfcData(ntag_file)); | ||||
| } | ||||
| 
 | ||||
| NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data) { | ||||
|     NTAG215File encoded_data{}; | ||||
| 
 | ||||
|     encoded_data.uid = nfc_data.uuid.uid; | ||||
|     encoded_data.nintendo_id = nfc_data.uuid.nintendo_id; | ||||
|     encoded_data.static_lock = nfc_data.static_lock; | ||||
|     encoded_data.compability_container = nfc_data.compability_container; | ||||
|     encoded_data.hmac_data = nfc_data.user_memory.hmac_data; | ||||
|     encoded_data.constant_value = nfc_data.user_memory.constant_value; | ||||
|     encoded_data.write_counter = nfc_data.user_memory.write_counter; | ||||
|     encoded_data.amiibo_version = nfc_data.user_memory.amiibo_version; | ||||
|     encoded_data.settings = nfc_data.user_memory.settings; | ||||
|     encoded_data.owner_mii = nfc_data.user_memory.owner_mii; | ||||
|     encoded_data.padding = nfc_data.user_memory.padding; | ||||
|     encoded_data.owner_mii_aes_ccm = nfc_data.user_memory.owner_mii_aes_ccm; | ||||
|     encoded_data.application_id = nfc_data.user_memory.application_id; | ||||
|     encoded_data.application_write_counter = nfc_data.user_memory.application_write_counter; | ||||
|     encoded_data.application_area_id = nfc_data.user_memory.application_area_id; | ||||
|     encoded_data.application_id_byte = nfc_data.user_memory.application_id_byte; | ||||
|     encoded_data.unknown = nfc_data.user_memory.unknown; | ||||
|     encoded_data.mii_extension = nfc_data.user_memory.mii_extension; | ||||
|     encoded_data.unknown2 = nfc_data.user_memory.unknown2; | ||||
|     encoded_data.register_info_crc = nfc_data.user_memory.register_info_crc; | ||||
|     encoded_data.application_area = nfc_data.user_memory.application_area; | ||||
|     encoded_data.hmac_tag = nfc_data.user_memory.hmac_tag; | ||||
|     encoded_data.lock_bytes = nfc_data.uuid.lock_bytes; | ||||
|     encoded_data.model_info = nfc_data.user_memory.model_info; | ||||
|     encoded_data.keygen_salt = nfc_data.user_memory.keygen_salt; | ||||
|     encoded_data.dynamic_lock = nfc_data.dynamic_lock; | ||||
|     encoded_data.CFG0 = nfc_data.CFG0; | ||||
|     encoded_data.CFG1 = nfc_data.CFG1; | ||||
|     encoded_data.password = nfc_data.password; | ||||
| 
 | ||||
|     return encoded_data; | ||||
| } | ||||
| 
 | ||||
| EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data) { | ||||
|     EncryptedNTAG215File nfc_data{}; | ||||
| 
 | ||||
|     nfc_data.uuid.uid = encoded_data.uid; | ||||
|     nfc_data.uuid.nintendo_id = encoded_data.nintendo_id; | ||||
|     nfc_data.uuid.lock_bytes = encoded_data.lock_bytes; | ||||
|     nfc_data.static_lock = encoded_data.static_lock; | ||||
|     nfc_data.compability_container = encoded_data.compability_container; | ||||
|     nfc_data.user_memory.hmac_data = encoded_data.hmac_data; | ||||
|     nfc_data.user_memory.constant_value = encoded_data.constant_value; | ||||
|     nfc_data.user_memory.write_counter = encoded_data.write_counter; | ||||
|     nfc_data.user_memory.amiibo_version = encoded_data.amiibo_version; | ||||
|     nfc_data.user_memory.settings = encoded_data.settings; | ||||
|     nfc_data.user_memory.owner_mii = encoded_data.owner_mii; | ||||
|     nfc_data.user_memory.padding = encoded_data.padding; | ||||
|     nfc_data.user_memory.owner_mii_aes_ccm = encoded_data.owner_mii_aes_ccm; | ||||
|     nfc_data.user_memory.application_id = encoded_data.application_id; | ||||
|     nfc_data.user_memory.application_write_counter = encoded_data.application_write_counter; | ||||
|     nfc_data.user_memory.application_area_id = encoded_data.application_area_id; | ||||
|     nfc_data.user_memory.application_id_byte = encoded_data.application_id_byte; | ||||
|     nfc_data.user_memory.unknown = encoded_data.unknown; | ||||
|     nfc_data.user_memory.mii_extension = encoded_data.mii_extension; | ||||
|     nfc_data.user_memory.unknown2 = encoded_data.unknown2; | ||||
|     nfc_data.user_memory.register_info_crc = encoded_data.register_info_crc; | ||||
|     nfc_data.user_memory.application_area = encoded_data.application_area; | ||||
|     nfc_data.user_memory.hmac_tag = encoded_data.hmac_tag; | ||||
|     nfc_data.user_memory.model_info = encoded_data.model_info; | ||||
|     nfc_data.user_memory.keygen_salt = encoded_data.keygen_salt; | ||||
|     nfc_data.dynamic_lock = encoded_data.dynamic_lock; | ||||
|     nfc_data.CFG0 = encoded_data.CFG0; | ||||
|     nfc_data.CFG1 = encoded_data.CFG1; | ||||
|     nfc_data.password = encoded_data.password; | ||||
| 
 | ||||
|     return nfc_data; | ||||
| } | ||||
| 
 | ||||
| HashSeed GetSeed(const NTAG215File& data) { | ||||
|     HashSeed seed{ | ||||
|         .magic = data.write_counter, | ||||
|         .padding = {}, | ||||
|         .uid_1 = data.uid, | ||||
|         .nintendo_id_1 = data.nintendo_id, | ||||
|         .uid_2 = data.uid, | ||||
|         .nintendo_id_2 = data.nintendo_id, | ||||
|         .keygen_salt = data.keygen_salt, | ||||
|     }; | ||||
| 
 | ||||
|     return seed; | ||||
| } | ||||
| 
 | ||||
| std::vector<u8> GenerateInternalKey(const InternalKey& key, const HashSeed& seed) { | ||||
|     const std::size_t seed_part1_len = sizeof(key.magic_bytes) - key.magic_length; | ||||
|     const std::size_t string_size = key.type_string.size(); | ||||
|     std::vector<u8> output(string_size + seed_part1_len); | ||||
| 
 | ||||
|     // Copy whole type string
 | ||||
|     memccpy(output.data(), key.type_string.data(), '\0', string_size); | ||||
| 
 | ||||
|     // Append (16 - magic_length) from the input seed
 | ||||
|     memcpy(output.data() + string_size, &seed, seed_part1_len); | ||||
| 
 | ||||
|     // Append all bytes from magicBytes
 | ||||
|     output.insert(output.end(), key.magic_bytes.begin(), | ||||
|                   key.magic_bytes.begin() + key.magic_length); | ||||
| 
 | ||||
|     output.insert(output.end(), seed.uid_1.begin(), seed.uid_1.end()); | ||||
|     output.emplace_back(seed.nintendo_id_1); | ||||
|     output.insert(output.end(), seed.uid_2.begin(), seed.uid_2.end()); | ||||
|     output.emplace_back(seed.nintendo_id_2); | ||||
| 
 | ||||
|     for (std::size_t i = 0; i < sizeof(seed.keygen_salt); i++) { | ||||
|         output.emplace_back(static_cast<u8>(seed.keygen_salt[i] ^ key.xor_pad[i])); | ||||
|     } | ||||
| 
 | ||||
|     return output; | ||||
| } | ||||
| 
 | ||||
| void CryptoInit(CryptoCtx& ctx, CryptoPP::HMAC<CryptoPP::SHA256>& hmac_ctx, const HmacKey& hmac_key, | ||||
|                 const std::vector<u8>& seed) { | ||||
|     // Initialize context
 | ||||
|     ctx.used = false; | ||||
|     ctx.counter = 0; | ||||
|     ctx.buffer_size = sizeof(ctx.counter) + seed.size(); | ||||
|     memcpy(ctx.buffer.data() + sizeof(u16), seed.data(), seed.size()); | ||||
| 
 | ||||
|     // Initialize HMAC context
 | ||||
|     hmac_ctx.SetKey(hmac_key.data(), hmac_key.size()); | ||||
| } | ||||
| 
 | ||||
| void CryptoStep(CryptoCtx& ctx, CryptoPP::HMAC<CryptoPP::SHA256>& hmac_ctx, DrgbOutput& output) { | ||||
|     // If used at least once, reinitialize the HMAC
 | ||||
|     if (ctx.used) { | ||||
|         hmac_ctx.Restart(); | ||||
|     } | ||||
| 
 | ||||
|     ctx.used = true; | ||||
| 
 | ||||
|     // Store counter in big endian, and increment it
 | ||||
|     ctx.buffer[0] = static_cast<u8>(ctx.counter >> 8); | ||||
|     ctx.buffer[1] = static_cast<u8>(ctx.counter >> 0); | ||||
|     ctx.counter++; | ||||
| 
 | ||||
|     // Do HMAC magic
 | ||||
|     hmac_ctx.CalculateDigest( | ||||
|         output.data(), reinterpret_cast<const unsigned char*>(ctx.buffer.data()), ctx.buffer_size); | ||||
| } | ||||
| 
 | ||||
| DerivedKeys GenerateKey(const InternalKey& key, const NTAG215File& data) { | ||||
|     const auto seed = GetSeed(data); | ||||
| 
 | ||||
|     // Generate internal seed
 | ||||
|     const std::vector<u8> internal_key = GenerateInternalKey(key, seed); | ||||
| 
 | ||||
|     // Initialize context
 | ||||
|     CryptoCtx ctx{}; | ||||
|     CryptoPP::HMAC<CryptoPP::SHA256> hmac_ctx; | ||||
|     CryptoInit(ctx, hmac_ctx, key.hmac_key, internal_key); | ||||
| 
 | ||||
|     // Generate derived keys
 | ||||
|     DerivedKeys derived_keys{}; | ||||
|     std::array<DrgbOutput, 2> temp{}; | ||||
|     CryptoStep(ctx, hmac_ctx, temp[0]); | ||||
|     CryptoStep(ctx, hmac_ctx, temp[1]); | ||||
|     memcpy(&derived_keys, temp.data(), sizeof(DerivedKeys)); | ||||
| 
 | ||||
|     return derived_keys; | ||||
| } | ||||
| 
 | ||||
| void Cipher(const DerivedKeys& keys, const NTAG215File& in_data, NTAG215File& out_data) { | ||||
|     CryptoPP::CTR_Mode<CryptoPP::AES>::Decryption d2; | ||||
|     d2.SetKeyWithIV(keys.aes_key.data(), keys.aes_key.size(), keys.aes_iv.data(), | ||||
|                     keys.aes_iv.size()); | ||||
| 
 | ||||
|     constexpr std::size_t encrypted_data_size = HMAC_TAG_START - SETTINGS_START; | ||||
|     d2.ProcessData(reinterpret_cast<unsigned char*>(&out_data.settings), | ||||
|                    reinterpret_cast<const unsigned char*>(&in_data.settings), encrypted_data_size); | ||||
| 
 | ||||
|     // Copy the rest of the data directly
 | ||||
|     out_data.uid = in_data.uid; | ||||
|     out_data.nintendo_id = in_data.nintendo_id; | ||||
|     out_data.lock_bytes = in_data.lock_bytes; | ||||
|     out_data.static_lock = in_data.static_lock; | ||||
|     out_data.compability_container = in_data.compability_container; | ||||
| 
 | ||||
|     out_data.constant_value = in_data.constant_value; | ||||
|     out_data.write_counter = in_data.write_counter; | ||||
| 
 | ||||
|     out_data.model_info = in_data.model_info; | ||||
|     out_data.keygen_salt = in_data.keygen_salt; | ||||
|     out_data.dynamic_lock = in_data.dynamic_lock; | ||||
|     out_data.CFG0 = in_data.CFG0; | ||||
|     out_data.CFG1 = in_data.CFG1; | ||||
|     out_data.password = in_data.password; | ||||
| } | ||||
| 
 | ||||
| bool LoadKeys(InternalKey& locked_secret, InternalKey& unfixed_info) { | ||||
|     const auto citra_keys_dir = FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir); | ||||
|     auto keys_file = FileUtil::IOFile(citra_keys_dir + "key_retail.bin", "rb"); | ||||
| 
 | ||||
|     if (!keys_file.IsOpen()) { | ||||
|         LOG_ERROR(Service_NFC, "No keys detected"); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     if (keys_file.ReadBytes(&unfixed_info, sizeof(InternalKey)) != sizeof(InternalKey)) { | ||||
|         LOG_ERROR(Service_NFC, "Failed to read unfixed_info"); | ||||
|         return false; | ||||
|     } | ||||
|     if (keys_file.ReadBytes(&locked_secret, sizeof(InternalKey)) != sizeof(InternalKey)) { | ||||
|         LOG_ERROR(Service_NFC, "Failed to read locked-secret"); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| bool IsKeyAvailable() { | ||||
|     const auto citra_keys_dir = FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir); | ||||
|     return FileUtil::Exists(citra_keys_dir + "key_retail.bin"); | ||||
| } | ||||
| 
 | ||||
| bool DecodeAmiibo(const EncryptedNTAG215File& encrypted_tag_data, NTAG215File& tag_data) { | ||||
|     InternalKey locked_secret{}; | ||||
|     InternalKey unfixed_info{}; | ||||
| 
 | ||||
|     if (!LoadKeys(locked_secret, unfixed_info)) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     // Generate keys
 | ||||
|     NTAG215File encoded_data = NfcDataToEncodedData(encrypted_tag_data); | ||||
|     const auto data_keys = GenerateKey(unfixed_info, encoded_data); | ||||
|     const auto tag_keys = GenerateKey(locked_secret, encoded_data); | ||||
| 
 | ||||
|     // Decrypt
 | ||||
|     Cipher(data_keys, encoded_data, tag_data); | ||||
| 
 | ||||
|     // Regenerate tag HMAC. Note: order matters, data HMAC depends on tag HMAC!
 | ||||
|     constexpr std::size_t input_length = DYNAMIC_LOCK_START - UUID_START; | ||||
|     CryptoPP::HMAC<CryptoPP::SHA256> tag_hmac(tag_keys.hmac_key.data(), sizeof(HmacKey)); | ||||
|     tag_hmac.CalculateDigest(reinterpret_cast<unsigned char*>(&tag_data.hmac_tag), | ||||
|                              reinterpret_cast<const unsigned char*>(&tag_data.uid), input_length); | ||||
| 
 | ||||
|     // Regenerate data HMAC
 | ||||
|     constexpr std::size_t input_length2 = DYNAMIC_LOCK_START - WRITE_COUNTER_START; | ||||
|     CryptoPP::HMAC<CryptoPP::SHA256> data_hmac(data_keys.hmac_key.data(), sizeof(HmacKey)); | ||||
|     data_hmac.CalculateDigest(reinterpret_cast<unsigned char*>(&tag_data.hmac_data), | ||||
|                               reinterpret_cast<const unsigned char*>(&tag_data.write_counter), | ||||
|                               input_length2); | ||||
| 
 | ||||
|     if (tag_data.hmac_data != encrypted_tag_data.user_memory.hmac_data) { | ||||
|         LOG_ERROR(Service_NFC, "hmac_data doesn't match"); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     if (tag_data.hmac_tag != encrypted_tag_data.user_memory.hmac_tag) { | ||||
|         LOG_ERROR(Service_NFC, "hmac_tag doesn't match"); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| bool EncodeAmiibo(const NTAG215File& tag_data, EncryptedNTAG215File& encrypted_tag_data) { | ||||
|     InternalKey locked_secret{}; | ||||
|     InternalKey unfixed_info{}; | ||||
| 
 | ||||
|     if (!LoadKeys(locked_secret, unfixed_info)) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     // Generate keys
 | ||||
|     const auto data_keys = GenerateKey(unfixed_info, tag_data); | ||||
|     const auto tag_keys = GenerateKey(locked_secret, tag_data); | ||||
| 
 | ||||
|     NTAG215File encoded_tag_data{}; | ||||
| 
 | ||||
|     // Generate tag HMAC
 | ||||
|     constexpr std::size_t input_length = DYNAMIC_LOCK_START - UUID_START; | ||||
|     constexpr std::size_t input_length2 = HMAC_TAG_START - WRITE_COUNTER_START; | ||||
|     CryptoPP::HMAC<CryptoPP::SHA256> tag_hmac(tag_keys.hmac_key.data(), sizeof(HmacKey)); | ||||
|     tag_hmac.CalculateDigest(reinterpret_cast<unsigned char*>(&encoded_tag_data.hmac_tag), | ||||
|                              reinterpret_cast<const unsigned char*>(&tag_data.uid), input_length); | ||||
| 
 | ||||
|     // Generate data HMAC
 | ||||
|     CryptoPP::HMAC<CryptoPP::SHA256> data_hmac(data_keys.hmac_key.data(), sizeof(HmacKey)); | ||||
|     data_hmac.Update(reinterpret_cast<const unsigned char*>(&tag_data.write_counter), | ||||
|                      input_length2); | ||||
|     data_hmac.Update(reinterpret_cast<unsigned char*>(&encoded_tag_data.hmac_tag), | ||||
|                      sizeof(HashData)); | ||||
|     data_hmac.Update(reinterpret_cast<const unsigned char*>(&tag_data.uid), input_length); | ||||
|     data_hmac.Final(reinterpret_cast<unsigned char*>(&encoded_tag_data.hmac_data)); | ||||
| 
 | ||||
|     // Encrypt
 | ||||
|     Cipher(data_keys, tag_data, encoded_tag_data); | ||||
| 
 | ||||
|     // Convert back to hardware
 | ||||
|     encrypted_tag_data = EncodedDataToNfcData(encoded_tag_data); | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| } // namespace Service::NFC::AmiiboCrypto
 | ||||
							
								
								
									
										109
									
								
								src/core/hle/service/nfc/amiibo_crypto.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								src/core/hle/service/nfc/amiibo_crypto.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,109 @@ | |||
| // Copyright 2022 yuzu Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <array> | ||||
| #include <vector> | ||||
| 
 | ||||
| #include "core/hle/service/nfc/nfc_types.h" | ||||
| 
 | ||||
| namespace CryptoPP { | ||||
| class SHA256; | ||||
| template <class T> | ||||
| class HMAC; | ||||
| } // namespace CryptoPP
 | ||||
| 
 | ||||
| namespace Service::NFC::AmiiboCrypto { | ||||
| // Byte locations in Service::NFC::NTAG215File
 | ||||
| constexpr std::size_t HMAC_DATA_START = 0x8; | ||||
| constexpr std::size_t SETTINGS_START = 0x2c; | ||||
| constexpr std::size_t WRITE_COUNTER_START = 0x29; | ||||
| constexpr std::size_t HMAC_TAG_START = 0x1B4; | ||||
| constexpr std::size_t UUID_START = 0x1D4; | ||||
| constexpr std::size_t DYNAMIC_LOCK_START = 0x208; | ||||
| 
 | ||||
| using HmacKey = std::array<u8, 0x10>; | ||||
| using DrgbOutput = std::array<u8, 0x20>; | ||||
| 
 | ||||
| struct HashSeed { | ||||
|     u16_be magic; | ||||
|     std::array<u8, 0xE> padding; | ||||
|     UniqueSerialNumber uid_1; | ||||
|     u8 nintendo_id_1; | ||||
|     UniqueSerialNumber uid_2; | ||||
|     u8 nintendo_id_2; | ||||
|     std::array<u8, 0x20> keygen_salt; | ||||
| }; | ||||
| static_assert(sizeof(HashSeed) == 0x40, "HashSeed is an invalid size"); | ||||
| 
 | ||||
| struct InternalKey { | ||||
|     HmacKey hmac_key; | ||||
|     std::array<char, 0xE> type_string; | ||||
|     u8 reserved; | ||||
|     u8 magic_length; | ||||
|     std::array<u8, 0x10> magic_bytes; | ||||
|     std::array<u8, 0x20> xor_pad; | ||||
| }; | ||||
| static_assert(sizeof(InternalKey) == 0x50, "InternalKey is an invalid size"); | ||||
| static_assert(std::is_trivially_copyable_v<InternalKey>, "InternalKey must be trivially copyable."); | ||||
| 
 | ||||
| struct CryptoCtx { | ||||
|     std::array<char, 480> buffer; | ||||
|     bool used; | ||||
|     std::size_t buffer_size; | ||||
|     s16 counter; | ||||
| }; | ||||
| 
 | ||||
| struct DerivedKeys { | ||||
|     std::array<u8, 0x10> aes_key; | ||||
|     std::array<u8, 0x10> aes_iv; | ||||
|     std::array<u8, 0x10> hmac_key; | ||||
| }; | ||||
| static_assert(sizeof(DerivedKeys) == 0x30, "DerivedKeys is an invalid size"); | ||||
| 
 | ||||
| /// Validates that the amiibo file is not corrupted
 | ||||
| bool IsAmiiboValid(const EncryptedNTAG215File& ntag_file); | ||||
| 
 | ||||
| /// Validates that the amiibo file is not corrupted
 | ||||
| bool IsAmiiboValid(const NTAG215File& ntag_file); | ||||
| 
 | ||||
| /// Converts from encrypted file format to encoded file format
 | ||||
| NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data); | ||||
| 
 | ||||
| /// Converts from encoded file format to encrypted file format
 | ||||
| EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data); | ||||
| 
 | ||||
| // Generates Seed needed for key derivation
 | ||||
| HashSeed GetSeed(const NTAG215File& data); | ||||
| 
 | ||||
| // Middle step on the generation of derived keys
 | ||||
| std::vector<u8> GenerateInternalKey(const InternalKey& key, const HashSeed& seed); | ||||
| 
 | ||||
| // Initializes mbedtls context
 | ||||
| void CryptoInit(CryptoCtx& ctx, CryptoPP::HMAC<CryptoPP::SHA256>& hmac_ctx, const HmacKey& hmac_key, | ||||
|                 const std::vector<u8>& seed); | ||||
| 
 | ||||
| // Feeds data to mbedtls context to generate the derived key
 | ||||
| void CryptoStep(CryptoCtx& ctx, CryptoPP::HMAC<CryptoPP::SHA256>& hmac_ctx, DrgbOutput& output); | ||||
| 
 | ||||
| // Generates the derived key from amiibo data
 | ||||
| DerivedKeys GenerateKey(const InternalKey& key, const NTAG215File& data); | ||||
| 
 | ||||
| // Encodes or decodes amiibo data
 | ||||
| void Cipher(const DerivedKeys& keys, const NTAG215File& in_data, NTAG215File& out_data); | ||||
| 
 | ||||
| /// Loads both amiibo keys from key_retail.bin
 | ||||
| bool LoadKeys(InternalKey& locked_secret, InternalKey& unfixed_info); | ||||
| 
 | ||||
| /// Returns true if key_retail.bin exist
 | ||||
| bool IsKeyAvailable(); | ||||
| 
 | ||||
| /// Decodes encripted amiibo data returns true if output is valid
 | ||||
| bool DecodeAmiibo(const EncryptedNTAG215File& encrypted_tag_data, NTAG215File& tag_data); | ||||
| 
 | ||||
| /// Encodes plain amiibo data returns true if output is valid
 | ||||
| bool EncodeAmiibo(const NTAG215File& tag_data, EncryptedNTAG215File& encrypted_tag_data); | ||||
| 
 | ||||
| } // namespace Service::NFC::AmiiboCrypto
 | ||||
|  | @ -18,334 +18,662 @@ namespace Service::NFC { | |||
| 
 | ||||
| template <class Archive> | ||||
| void Module::serialize(Archive& ar, const unsigned int) { | ||||
|     ar& tag_in_range_event; | ||||
|     ar& tag_out_of_range_event; | ||||
|     ar& nfc_tag_state; | ||||
|     ar& nfc_status; | ||||
|     ar& amiibo_data; | ||||
|     ar& amiibo_in_range; | ||||
|     ar& nfc_mode; | ||||
|     ar& device; | ||||
| } | ||||
| SERIALIZE_IMPL(Module) | ||||
| 
 | ||||
| struct TagInfo { | ||||
|     u16_le id_offset_size; | ||||
|     u8 unk1; | ||||
|     u8 unk2; | ||||
|     std::array<u8, 7> uuid; | ||||
|     INSERT_PADDING_BYTES(0x20); | ||||
| }; | ||||
| static_assert(sizeof(TagInfo) == 0x2C, "TagInfo is an invalid size"); | ||||
| 
 | ||||
| struct AmiiboConfig { | ||||
|     u16_le lastwritedate_year; | ||||
|     u8 lastwritedate_month; | ||||
|     u8 lastwritedate_day; | ||||
|     u16_le write_counter; | ||||
|     std::array<u8, 3> characterID; | ||||
|     u8 series; | ||||
|     u16_le amiiboID; | ||||
|     u8 type; | ||||
|     u8 pagex4_byte3; | ||||
|     u16_le appdata_size; | ||||
|     INSERT_PADDING_BYTES(0x30); | ||||
| }; | ||||
| static_assert(sizeof(AmiiboConfig) == 0x40, "AmiiboConfig is an invalid size"); | ||||
| 
 | ||||
| struct IdentificationBlockReply { | ||||
|     u16_le char_id; | ||||
|     u8 char_variant; | ||||
|     u8 series; | ||||
|     u16_le model_number; | ||||
|     u8 figure_type; | ||||
|     INSERT_PADDING_BYTES(0x2F); | ||||
| }; | ||||
| static_assert(sizeof(IdentificationBlockReply) == 0x36, | ||||
|               "IdentificationBlockReply is an invalid size"); | ||||
| 
 | ||||
| void Module::Interface::Initialize(Kernel::HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp(ctx, 0x01, 1, 0); | ||||
|     u8 param = rp.Pop<u8>(); | ||||
|     const auto communication_mode = rp.PopEnum<CommunicationMode>(); | ||||
| 
 | ||||
|     LOG_INFO(Service_NFC, "called, communication_mode={}", communication_mode); | ||||
| 
 | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|     if (nfc->nfc_tag_state != TagState::NotInitialized) { | ||||
|         LOG_ERROR(Service_NFC, "Invalid TagState {}", nfc->nfc_tag_state); | ||||
|         rb.Push(ResultCode(ErrCodes::CommandInvalidForState, ErrorModule::NFC, | ||||
|                            ErrorSummary::InvalidState, ErrorLevel::Status)); | ||||
| 
 | ||||
|     if (nfc->nfc_mode != CommunicationMode::NotInitialized) { | ||||
|         rb.Push(ResultCommandInvalidForState); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     nfc->nfc_tag_state = TagState::NotScanning; | ||||
|     ResultCode result = RESULT_SUCCESS; | ||||
|     switch (communication_mode) { | ||||
|     case CommunicationMode::Ntag: | ||||
|     case CommunicationMode::Amiibo: | ||||
|         nfc->device->Initialize(); | ||||
|         break; | ||||
|     case CommunicationMode::TrainTag: | ||||
|         LOG_ERROR(Service_NFC, "CommunicationMode  {} not implemented", communication_mode); | ||||
|         break; | ||||
|     default: | ||||
|         result = ResultInvalidArgumentValue; | ||||
|         break; | ||||
|     } | ||||
| 
 | ||||
|     rb.Push(RESULT_SUCCESS); | ||||
|     LOG_WARNING(Service_NFC, "(STUBBED) called, param={}", param); | ||||
|     if (result.IsSuccess()) { | ||||
|         nfc->nfc_mode = communication_mode; | ||||
|     } | ||||
| 
 | ||||
|     rb.Push(result); | ||||
| } | ||||
| 
 | ||||
| void Module::Interface::Shutdown(Kernel::HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp(ctx, 0x02, 1, 0); | ||||
|     u8 param = rp.Pop<u8>(); | ||||
|     const auto communication_mode = rp.PopEnum<CommunicationMode>(); | ||||
| 
 | ||||
|     nfc->nfc_tag_state = TagState::NotInitialized; | ||||
|     LOG_INFO(Service_NFC, "called, communication_mode={}", communication_mode); | ||||
| 
 | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|     rb.Push(RESULT_SUCCESS); | ||||
|     LOG_WARNING(Service_NFC, "(STUBBED) called, param={}", param); | ||||
| 
 | ||||
|     if (nfc->nfc_mode != communication_mode) { | ||||
|         rb.Push(ResultCommandInvalidForState); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     ResultCode result = RESULT_SUCCESS; | ||||
|     switch (communication_mode) { | ||||
|     case CommunicationMode::Ntag: | ||||
|     case CommunicationMode::Amiibo: | ||||
|         nfc->device->Finalize(); | ||||
|         break; | ||||
|     case CommunicationMode::TrainTag: | ||||
|         LOG_ERROR(Service_NFC, "CommunicationMode  {} not implemented", communication_mode); | ||||
|         break; | ||||
|     default: | ||||
|         result = ResultInvalidArgumentValue; | ||||
|         break; | ||||
|     } | ||||
| 
 | ||||
|     if (result.IsSuccess()) { | ||||
|         nfc->nfc_mode = CommunicationMode::NotInitialized; | ||||
|     } | ||||
| 
 | ||||
|     rb.Push(result); | ||||
| } | ||||
| 
 | ||||
| void Module::Interface::StartCommunication(Kernel::HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp(ctx, 0x03, 0, 0); | ||||
| 
 | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|     rb.Push(RESULT_SUCCESS); | ||||
|     LOG_WARNING(Service_NFC, "(STUBBED) called"); | ||||
| 
 | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|     if (nfc->nfc_mode == CommunicationMode::TrainTag) { | ||||
|         LOG_ERROR(Service_NFC, "CommunicationMode  {} not implemented", nfc->nfc_mode); | ||||
|         rb.Push(RESULT_SUCCESS); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     // TODO: call start communication instead
 | ||||
|     const auto result = nfc->device->StartCommunication(); | ||||
|     rb.Push(result); | ||||
| } | ||||
| 
 | ||||
| void Module::Interface::StopCommunication(Kernel::HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp(ctx, 0x04, 0, 0); | ||||
| 
 | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|     rb.Push(RESULT_SUCCESS); | ||||
|     LOG_WARNING(Service_NFC, "(STUBBED) called"); | ||||
| 
 | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|     if (nfc->nfc_mode == CommunicationMode::TrainTag) { | ||||
|         LOG_ERROR(Service_NFC, "CommunicationMode  {} not implemented", nfc->nfc_mode); | ||||
|         rb.Push(RESULT_SUCCESS); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     // TODO: call stop communication instead
 | ||||
|     const auto result = nfc->device->StopCommunication(); | ||||
|     rb.Push(result); | ||||
| } | ||||
| 
 | ||||
| void Module::Interface::StartTagScanning(Kernel::HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp(ctx, 0x05, 1, 0); // 0x00050040
 | ||||
|     IPC::RequestParser rp(ctx, 0x05, 1, 0); | ||||
|     u16 in_val = rp.Pop<u16>(); | ||||
| 
 | ||||
|     LOG_INFO(Service_NFC, "called, in_val={:04x}", in_val); | ||||
| 
 | ||||
|     ResultCode result = RESULT_SUCCESS; | ||||
|     switch (nfc->nfc_mode) { | ||||
|     case CommunicationMode::Ntag: | ||||
|     case CommunicationMode::Amiibo: | ||||
|         // in_val probably correlates to the tag protocol to be detected
 | ||||
|         result = nfc->device->StartDetection(TagProtocol::All); | ||||
|         break; | ||||
|     default: | ||||
|         result = ResultInvalidArgumentValue; | ||||
|         break; | ||||
|     } | ||||
| 
 | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|     if (nfc->nfc_tag_state != TagState::NotScanning && | ||||
|         nfc->nfc_tag_state != TagState::TagOutOfRange) { | ||||
|         LOG_ERROR(Service_NFC, "Invalid TagState {}", nfc->nfc_tag_state); | ||||
|         rb.Push(ResultCode(ErrCodes::CommandInvalidForState, ErrorModule::NFC, | ||||
|                            ErrorSummary::InvalidState, ErrorLevel::Status)); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     nfc->nfc_tag_state = TagState::Scanning; | ||||
|     nfc->SyncTagState(); | ||||
| 
 | ||||
|     rb.Push(RESULT_SUCCESS); | ||||
|     LOG_WARNING(Service_NFC, "(STUBBED) called, in_val={:04x}", in_val); | ||||
| } | ||||
| 
 | ||||
| void Module::Interface::GetTagInfo(Kernel::HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp(ctx, 0x11, 0, 0); | ||||
| 
 | ||||
|     if (nfc->nfc_tag_state != TagState::TagInRange && | ||||
|         nfc->nfc_tag_state != TagState::TagDataLoaded && nfc->nfc_tag_state != TagState::Unknown6) { | ||||
|         LOG_ERROR(Service_NFC, "Invalid TagState {}", nfc->nfc_tag_state); | ||||
|         IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|         rb.Push(ResultCode(ErrCodes::CommandInvalidForState, ErrorModule::NFC, | ||||
|                            ErrorSummary::InvalidState, ErrorLevel::Status)); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     TagInfo tag_info{}; | ||||
|     tag_info.uuid = nfc->amiibo_data.uuid; | ||||
|     tag_info.id_offset_size = static_cast<u16>(tag_info.uuid.size()); | ||||
|     tag_info.unk1 = 0x0; | ||||
|     tag_info.unk2 = 0x2; | ||||
| 
 | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(12, 0); | ||||
|     rb.Push(RESULT_SUCCESS); | ||||
|     rb.PushRaw<TagInfo>(tag_info); | ||||
|     LOG_WARNING(Service_NFC, "(STUBBED) called"); | ||||
| } | ||||
| 
 | ||||
| void Module::Interface::GetAmiiboConfig(Kernel::HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp(ctx, 0x18, 0, 0); | ||||
| 
 | ||||
|     AmiiboConfig amiibo_config{}; | ||||
|     amiibo_config.lastwritedate_year = 2017; | ||||
|     amiibo_config.lastwritedate_month = 10; | ||||
|     amiibo_config.lastwritedate_day = 10; | ||||
|     amiibo_config.write_counter = 0x0; | ||||
|     std::memcpy(amiibo_config.characterID.data(), &nfc->amiibo_data.char_id, | ||||
|                 sizeof(nfc->amiibo_data.char_id)); | ||||
|     amiibo_config.series = nfc->amiibo_data.series; | ||||
|     amiibo_config.amiiboID = nfc->amiibo_data.model_number; | ||||
|     amiibo_config.type = nfc->amiibo_data.figure_type; | ||||
|     amiibo_config.pagex4_byte3 = 0x0; | ||||
|     amiibo_config.appdata_size = 0xD8; | ||||
| 
 | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(17, 0); | ||||
|     rb.Push(RESULT_SUCCESS); | ||||
|     rb.PushRaw<AmiiboConfig>(amiibo_config); | ||||
|     LOG_WARNING(Service_NFC, "(STUBBED) called"); | ||||
|     rb.Push(result); | ||||
| } | ||||
| 
 | ||||
| void Module::Interface::StopTagScanning(Kernel::HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp(ctx, 0x06, 0, 0); | ||||
| 
 | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|     if (nfc->nfc_tag_state == TagState::NotInitialized || | ||||
|         nfc->nfc_tag_state == TagState::NotScanning) { | ||||
|         LOG_ERROR(Service_NFC, "Invalid TagState {}", nfc->nfc_tag_state); | ||||
|         rb.Push(ResultCode(ErrCodes::CommandInvalidForState, ErrorModule::NFC, | ||||
|                            ErrorSummary::InvalidState, ErrorLevel::Status)); | ||||
|         return; | ||||
|     LOG_INFO(Service_NFC, "called"); | ||||
| 
 | ||||
|     ResultCode result = RESULT_SUCCESS; | ||||
|     switch (nfc->nfc_mode) { | ||||
|     case CommunicationMode::Ntag: | ||||
|     case CommunicationMode::Amiibo: | ||||
|         result = nfc->device->StopDetection(); | ||||
|         break; | ||||
|     case CommunicationMode::TrainTag: | ||||
|         LOG_ERROR(Service_NFC, "CommunicationMode  {} not implemented", nfc->nfc_mode); | ||||
|         break; | ||||
|     default: | ||||
|         result = ResultCommandInvalidForState; | ||||
|         break; | ||||
|     } | ||||
| 
 | ||||
|     nfc->nfc_tag_state = TagState::NotScanning; | ||||
| 
 | ||||
|     rb.Push(RESULT_SUCCESS); | ||||
|     LOG_DEBUG(Service_NFC, "called"); | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|     rb.Push(result); | ||||
| } | ||||
| 
 | ||||
| void Module::Interface::LoadAmiiboData(Kernel::HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp(ctx, 0x07, 0, 0); | ||||
| 
 | ||||
|     // TODO(FearlessTobi): Add state checking when this function gets properly implemented
 | ||||
|     LOG_INFO(Service_NFC, "called"); | ||||
| 
 | ||||
|     nfc->nfc_tag_state = TagState::TagDataLoaded; | ||||
|     ResultCode result = RESULT_SUCCESS; | ||||
|     switch (nfc->nfc_mode) { | ||||
|     case CommunicationMode::Ntag: | ||||
|         result = nfc->device->Mount(); | ||||
|         break; | ||||
|     case CommunicationMode::Amiibo: | ||||
|         result = nfc->device->MountAmiibo(); | ||||
|         break; | ||||
|     default: | ||||
|         result = ResultCommandInvalidForState; | ||||
|         break; | ||||
|     } | ||||
| 
 | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|     rb.Push(RESULT_SUCCESS); | ||||
|     LOG_WARNING(Service_NFC, "(STUBBED) called"); | ||||
|     rb.Push(result); | ||||
| } | ||||
| 
 | ||||
| void Module::Interface::ResetTagScanState(Kernel::HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp(ctx, 0x08, 0, 0); | ||||
| 
 | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|     if (nfc->nfc_tag_state != TagState::TagDataLoaded && nfc->nfc_tag_state != TagState::Unknown6) { | ||||
|         LOG_ERROR(Service_NFC, "Invalid TagState {}", nfc->nfc_tag_state); | ||||
|         rb.Push(ResultCode(ErrCodes::CommandInvalidForState, ErrorModule::NFC, | ||||
|                            ErrorSummary::InvalidState, ErrorLevel::Status)); | ||||
|         return; | ||||
|     LOG_INFO(Service_NFC, "called"); | ||||
| 
 | ||||
|     ResultCode result = RESULT_SUCCESS; | ||||
|     switch (nfc->nfc_mode) { | ||||
|     case CommunicationMode::Ntag: | ||||
|     case CommunicationMode::Amiibo: | ||||
|         result = nfc->device->ResetTagScanState(); | ||||
|         break; | ||||
|     default: | ||||
|         result = ResultCommandInvalidForState; | ||||
|         break; | ||||
|     } | ||||
| 
 | ||||
|     nfc->nfc_tag_state = TagState::TagInRange; | ||||
|     nfc->SyncTagState(); | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|     rb.Push(result); | ||||
| } | ||||
| 
 | ||||
|     rb.Push(RESULT_SUCCESS); | ||||
|     LOG_DEBUG(Service_NFC, "called"); | ||||
| void Module::Interface::UpdateStoredAmiiboData(Kernel::HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp(ctx, 0x09, 0, 0); | ||||
| 
 | ||||
|     LOG_INFO(Service_NFC, "called"); | ||||
| 
 | ||||
|     ResultCode result = RESULT_SUCCESS; | ||||
|     switch (nfc->nfc_mode) { | ||||
|     case CommunicationMode::Ntag: | ||||
|         LOG_ERROR(Service_NFC, "CommunicationMode  {} not implemented", nfc->nfc_mode); | ||||
|         break; | ||||
|     case CommunicationMode::Amiibo: | ||||
|         result = nfc->device->Flush(); | ||||
|         break; | ||||
|     default: | ||||
|         result = ResultCommandInvalidForState; | ||||
|         break; | ||||
|     } | ||||
| 
 | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|     rb.Push(result); | ||||
| } | ||||
| 
 | ||||
| void Module::Interface::GetTagInRangeEvent(Kernel::HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp(ctx, 0x0B, 0, 0); | ||||
| 
 | ||||
|     if (nfc->nfc_tag_state != TagState::NotScanning) { | ||||
|         LOG_ERROR(Service_NFC, "Invalid TagState {}", nfc->nfc_tag_state); | ||||
|     LOG_INFO(Service_NFC, "called"); | ||||
| 
 | ||||
|     if (nfc->nfc_mode == CommunicationMode::TrainTag) { | ||||
|         LOG_ERROR(Service_NFC, "CommunicationMode  {} not implemented", nfc->nfc_mode); | ||||
|         IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|         rb.Push(ResultCode(ErrCodes::CommandInvalidForState, ErrorModule::NFC, | ||||
|                            ErrorSummary::InvalidState, ErrorLevel::Status)); | ||||
|         rb.Push(ResultCommandInvalidForState); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); | ||||
|     rb.Push(RESULT_SUCCESS); | ||||
|     rb.PushCopyObjects(nfc->tag_in_range_event); | ||||
|     LOG_DEBUG(Service_NFC, "called"); | ||||
|     rb.PushCopyObjects(nfc->device->GetActivateEvent()); | ||||
| } | ||||
| 
 | ||||
| void Module::Interface::GetTagOutOfRangeEvent(Kernel::HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp(ctx, 0x0C, 0, 0); | ||||
| 
 | ||||
|     if (nfc->nfc_tag_state != TagState::NotScanning) { | ||||
|         LOG_ERROR(Service_NFC, "Invalid TagState {}", nfc->nfc_tag_state); | ||||
|     LOG_INFO(Service_NFC, "called"); | ||||
| 
 | ||||
|     if (nfc->nfc_mode == CommunicationMode::TrainTag) { | ||||
|         LOG_ERROR(Service_NFC, "CommunicationMode  {} not implemented", nfc->nfc_mode); | ||||
|         IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|         rb.Push(ResultCode(ErrCodes::CommandInvalidForState, ErrorModule::NFC, | ||||
|                            ErrorSummary::InvalidState, ErrorLevel::Status)); | ||||
|         rb.Push(ResultCommandInvalidForState); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); | ||||
|     rb.Push(RESULT_SUCCESS); | ||||
|     rb.PushCopyObjects(nfc->tag_out_of_range_event); | ||||
|     LOG_DEBUG(Service_NFC, "called"); | ||||
|     rb.PushCopyObjects(nfc->device->GetDeactivateEvent()); | ||||
| } | ||||
| 
 | ||||
| void Module::Interface::GetTagState(Kernel::HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp(ctx, 0x0D, 0, 0); | ||||
|     DeviceState state = DeviceState::NotInitialized; | ||||
| 
 | ||||
|     LOG_DEBUG(Service_NFC, "called"); | ||||
| 
 | ||||
|     if (nfc->nfc_mode == CommunicationMode::TrainTag) { | ||||
|         LOG_ERROR(Service_NFC, "CommunicationMode  {} not implemented", nfc->nfc_mode); | ||||
|     } else { | ||||
|         state = nfc->device->GetCurrentState(); | ||||
|     } | ||||
| 
 | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); | ||||
|     rb.Push(RESULT_SUCCESS); | ||||
|     rb.PushEnum(nfc->nfc_tag_state); | ||||
|     LOG_DEBUG(Service_NFC, "called"); | ||||
|     rb.PushEnum(state); | ||||
| } | ||||
| 
 | ||||
| void Module::Interface::CommunicationGetStatus(Kernel::HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp(ctx, 0x0F, 0, 0); | ||||
| 
 | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); | ||||
|     rb.Push(RESULT_SUCCESS); | ||||
|     rb.PushEnum(nfc->nfc_status); | ||||
|     LOG_DEBUG(Service_NFC, "(STUBBED) called"); | ||||
| } | ||||
|     LOG_DEBUG(Service_NFC, "called"); | ||||
| 
 | ||||
| void Module::Interface::Unknown0x1A(Kernel::HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp(ctx, 0x1A, 0, 0); | ||||
| 
 | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|     if (nfc->nfc_tag_state != TagState::TagInRange) { | ||||
|         LOG_ERROR(Service_NFC, "Invalid TagState {}", nfc->nfc_tag_state); | ||||
|         rb.Push(ResultCode(ErrCodes::CommandInvalidForState, ErrorModule::NFC, | ||||
|                            ErrorSummary::InvalidState, ErrorLevel::Status)); | ||||
|     if (nfc->nfc_mode == CommunicationMode::TrainTag) { | ||||
|         LOG_ERROR(Service_NFC, "CommunicationMode  {} not implemented", nfc->nfc_mode); | ||||
|         IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); | ||||
|         rb.Push(RESULT_SUCCESS); | ||||
|         rb.PushEnum(CommunicationState::Idle); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     nfc->nfc_tag_state = TagState::Unknown6; | ||||
|     CommunicationState status{}; | ||||
|     const auto result = nfc->device->GetCommunicationStatus(status); | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); | ||||
|     rb.Push(result); | ||||
|     rb.PushEnum(status); | ||||
| } | ||||
| 
 | ||||
| void Module::Interface::GetTagInfo2(Kernel::HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp(ctx, 0x10, 0, 0); | ||||
| 
 | ||||
|     LOG_INFO(Service_NFC, "called"); | ||||
| 
 | ||||
|     if (nfc->nfc_mode == CommunicationMode::TrainTag) { | ||||
|         LOG_ERROR(Service_NFC, "CommunicationMode  {} not implemented", nfc->nfc_mode); | ||||
|         IPC::RequestBuilder rb = rp.MakeBuilder(26, 0); | ||||
|         rb.Push(RESULT_SUCCESS); | ||||
|         rb.PushRaw<TagInfo2>({}); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     TagInfo2 tag_info{}; | ||||
|     const auto result = nfc->device->GetTagInfo2(tag_info); | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(26, 0); | ||||
|     rb.Push(result); | ||||
|     rb.PushRaw<TagInfo2>(tag_info); | ||||
| } | ||||
| 
 | ||||
| void Module::Interface::GetTagInfo(Kernel::HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp(ctx, 0x11, 0, 0); | ||||
| 
 | ||||
|     LOG_INFO(Service_NFC, "called"); | ||||
| 
 | ||||
|     if (nfc->nfc_mode == CommunicationMode::TrainTag) { | ||||
|         LOG_ERROR(Service_NFC, "CommunicationMode  {} not implemented", nfc->nfc_mode); | ||||
|         IPC::RequestBuilder rb = rp.MakeBuilder(12, 0); | ||||
|         rb.Push(RESULT_SUCCESS); | ||||
|         rb.PushRaw<TagInfo>({}); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     TagInfo tag_info{}; | ||||
|     const auto result = nfc->device->GetTagInfo(tag_info); | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(12, 0); | ||||
|     rb.Push(result); | ||||
|     rb.PushRaw<TagInfo>(tag_info); | ||||
| } | ||||
| 
 | ||||
| void Module::Interface::CommunicationGetResult(Kernel::HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp(ctx, 0x12, 0, 0); | ||||
| 
 | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); | ||||
|     rb.Push(RESULT_SUCCESS); | ||||
|     LOG_DEBUG(Service_NFC, "called"); | ||||
|     rb.Push(0); | ||||
|     LOG_WARNING(Service_NFC, "(STUBBED) called"); | ||||
| } | ||||
| 
 | ||||
| void Module::Interface::OpenAppData(Kernel::HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp(ctx, 0x13, 1, 0); | ||||
|     u32 access_id = rp.Pop<u32>(); | ||||
| 
 | ||||
|     LOG_INFO(Service_NFC, "called, access_id={}", access_id); | ||||
| 
 | ||||
|     if (nfc->nfc_mode != CommunicationMode::Amiibo) { | ||||
|         IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|         rb.Push(ResultCommandInvalidForState); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     const auto result = nfc->device->OpenApplicationArea(access_id); | ||||
| 
 | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|     rb.Push(result); | ||||
| } | ||||
| 
 | ||||
| void Module::Interface::InitializeWriteAppData(Kernel::HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp(ctx, 0x14, 18, 2); | ||||
|     u32 access_id = rp.Pop<u32>(); | ||||
|     [[maybe_unused]] u32 size = rp.Pop<u32>(); | ||||
|     std::vector<u8> buffer = rp.PopStaticBuffer(); | ||||
| 
 | ||||
|     LOG_CRITICAL(Service_NFC, "called, size={}", size); | ||||
| 
 | ||||
|     if (nfc->nfc_mode != CommunicationMode::Amiibo) { | ||||
|         IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|         rb.Push(ResultCommandInvalidForState); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     const auto result = nfc->device->CreateApplicationArea(access_id, buffer); | ||||
| 
 | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|     rb.Push(result); | ||||
| } | ||||
| 
 | ||||
| void Module::Interface::ReadAppData(Kernel::HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp(ctx, 0x15, 0, 0); | ||||
| 
 | ||||
|     LOG_INFO(Service_NFC, "called"); | ||||
| 
 | ||||
|     if (nfc->nfc_mode != CommunicationMode::Amiibo) { | ||||
|         IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|         rb.Push(ResultCommandInvalidForState); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     std::vector<u8> buffer(sizeof(ApplicationArea)); | ||||
|     const auto result = nfc->device->GetApplicationArea(buffer); | ||||
| 
 | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); | ||||
|     rb.Push(result); | ||||
|     rb.PushStaticBuffer(buffer, 0); | ||||
| } | ||||
| 
 | ||||
| void Module::Interface::WriteAppData(Kernel::HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp(ctx, 0x16, 12, 2); | ||||
|     [[maybe_unused]] u32 size = rp.Pop<u32>(); | ||||
|     std::vector<u8> tag_uuid_info = rp.PopStaticBuffer(); | ||||
|     std::vector<u8> buffer = rp.PopStaticBuffer(); | ||||
| 
 | ||||
|     LOG_CRITICAL(Service_NFC, "called, size={}", size); | ||||
| 
 | ||||
|     if (nfc->nfc_mode != CommunicationMode::Amiibo) { | ||||
|         IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|         rb.Push(ResultCommandInvalidForState); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     const auto result = nfc->device->SetApplicationArea(buffer); | ||||
| 
 | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|     rb.Push(result); | ||||
| } | ||||
| 
 | ||||
| void Module::Interface::GetRegisterInfo(Kernel::HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp(ctx, 0x17, 0, 0); | ||||
| 
 | ||||
|     LOG_INFO(Service_NFC, "called"); | ||||
| 
 | ||||
|     if (nfc->nfc_mode != CommunicationMode::Amiibo) { | ||||
|         IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|         rb.Push(ResultCommandInvalidForState); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     RegisterInfo settings_info{}; | ||||
|     const auto result = nfc->device->GetRegisterInfo(settings_info); | ||||
| 
 | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(43, 0); | ||||
|     rb.Push(result); | ||||
|     rb.PushRaw<RegisterInfo>(settings_info); | ||||
| } | ||||
| 
 | ||||
| void Module::Interface::GetCommonInfo(Kernel::HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp(ctx, 0x18, 0, 0); | ||||
| 
 | ||||
|     LOG_INFO(Service_NFC, "called"); | ||||
| 
 | ||||
|     if (nfc->nfc_mode != CommunicationMode::Amiibo) { | ||||
|         IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|         rb.Push(ResultCommandInvalidForState); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     CommonInfo amiibo_config{}; | ||||
|     const auto result = nfc->device->GetCommonInfo(amiibo_config); | ||||
| 
 | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(17, 0); | ||||
|     rb.Push(result); | ||||
|     rb.PushRaw<CommonInfo>(amiibo_config); | ||||
| } | ||||
| 
 | ||||
| void Module::Interface::GetAppDataInitStruct(Kernel::HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp(ctx, 0x19, 0, 0); | ||||
| 
 | ||||
|     LOG_INFO(Service_NFC, "called"); | ||||
| 
 | ||||
|     if (nfc->nfc_mode != CommunicationMode::Amiibo) { | ||||
|         IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|         rb.Push(ResultCommandInvalidForState); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     using InitialStruct = std::array<u8, 0x3c>; | ||||
|     InitialStruct empty{}; | ||||
| 
 | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(16, 0); | ||||
|     rb.Push(RESULT_SUCCESS); | ||||
|     rb.PushRaw<InitialStruct>(empty); | ||||
| } | ||||
| 
 | ||||
| void Module::Interface::LoadAmiiboPartially(Kernel::HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp(ctx, 0x1A, 0, 0); | ||||
| 
 | ||||
|     LOG_INFO(Service_NFC, "called"); | ||||
| 
 | ||||
|     ResultCode result = RESULT_SUCCESS; | ||||
|     switch (nfc->nfc_mode) { | ||||
|     case CommunicationMode::Ntag: | ||||
|         result = nfc->device->PartiallyMount(); | ||||
|         break; | ||||
|     case CommunicationMode::Amiibo: | ||||
|         result = nfc->device->PartiallyMountAmiibo(); | ||||
|         break; | ||||
|     default: | ||||
|         result = ResultCommandInvalidForState; | ||||
|         break; | ||||
|     } | ||||
| 
 | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|     rb.Push(result); | ||||
| } | ||||
| 
 | ||||
| void Module::Interface::GetIdentificationBlock(Kernel::HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp(ctx, 0x1B, 0, 0); | ||||
| 
 | ||||
|     if (nfc->nfc_tag_state != TagState::TagDataLoaded && nfc->nfc_tag_state != TagState::Unknown6) { | ||||
|         LOG_ERROR(Service_NFC, "Invalid TagState {}", nfc->nfc_tag_state); | ||||
|     LOG_INFO(Service_NFC, "called"); | ||||
| 
 | ||||
|     if (nfc->nfc_mode != CommunicationMode::Amiibo) { | ||||
|         IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|         rb.Push(ResultCode(ErrCodes::CommandInvalidForState, ErrorModule::NFC, | ||||
|                            ErrorSummary::InvalidState, ErrorLevel::Status)); | ||||
|         rb.Push(ResultCommandInvalidForState); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     IdentificationBlockReply identification_block_reply{}; | ||||
|     identification_block_reply.char_id = nfc->amiibo_data.char_id; | ||||
|     identification_block_reply.char_variant = nfc->amiibo_data.char_variant; | ||||
|     identification_block_reply.series = nfc->amiibo_data.series; | ||||
|     identification_block_reply.model_number = nfc->amiibo_data.model_number; | ||||
|     identification_block_reply.figure_type = nfc->amiibo_data.figure_type; | ||||
|     ModelInfo model_info{}; | ||||
|     const auto result = nfc->device->GetModelInfo(model_info); | ||||
| 
 | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(0x1F, 0); | ||||
|     rb.Push(result); | ||||
|     rb.PushRaw<ModelInfo>(model_info); | ||||
| } | ||||
| 
 | ||||
| void Module::Interface::Format(Kernel::HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp(ctx, 0x401, 3, 2); | ||||
|     [[maybe_unused]] u32 unknown1 = rp.Pop<u32>(); | ||||
|     [[maybe_unused]] u32 unknown2 = rp.Pop<u32>(); | ||||
|     [[maybe_unused]] u32 unknown3 = rp.Pop<u32>(); | ||||
|     [[maybe_unused]] std::vector<u8> buffer = rp.PopStaticBuffer(); | ||||
| 
 | ||||
|     const auto result = nfc->device->Format(); | ||||
| 
 | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|     rb.Push(result); | ||||
|     LOG_WARNING(Service_NFC, "(STUBBED) called"); | ||||
| } | ||||
| 
 | ||||
| void Module::Interface::GetAdminInfo(Kernel::HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp(ctx, 0x402, 0, 0); | ||||
| 
 | ||||
|     LOG_INFO(Service_NFC, "called"); | ||||
| 
 | ||||
|     if (nfc->nfc_mode != CommunicationMode::Amiibo) { | ||||
|         IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|         rb.Push(ResultCommandInvalidForState); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     AdminInfo admin_info{}; | ||||
|     const auto result = nfc->device->GetAdminInfo(admin_info); | ||||
| 
 | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(17, 0); | ||||
|     rb.Push(result); | ||||
|     rb.PushRaw<AdminInfo>(admin_info); | ||||
| } | ||||
| 
 | ||||
| void Module::Interface::GetEmptyRegisterInfo(Kernel::HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp(ctx, 0x403, 0, 0); | ||||
| 
 | ||||
|     LOG_INFO(Service_NFC, "called"); | ||||
| 
 | ||||
|     if (nfc->nfc_mode != CommunicationMode::Amiibo) { | ||||
|         IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|         rb.Push(ResultCommandInvalidForState); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(43, 0); | ||||
|     rb.Push(RESULT_SUCCESS); | ||||
|     rb.PushRaw<IdentificationBlockReply>(identification_block_reply); | ||||
|     LOG_DEBUG(Service_NFC, "called"); | ||||
|     rb.PushRaw<RegisterInfo>({}); | ||||
| } | ||||
| 
 | ||||
| void Module::Interface::SetRegisterInfo(Kernel::HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp(ctx, 0x404, 41, 0); | ||||
|     const auto register_info = rp.PopRaw<RegisterInfoPrivate>(); | ||||
| 
 | ||||
|     LOG_INFO(Service_NFC, "called"); | ||||
| 
 | ||||
|     if (nfc->nfc_mode != CommunicationMode::Amiibo) { | ||||
|         IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|         rb.Push(ResultCommandInvalidForState); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     const auto result = nfc->device->SetRegisterInfoPrivate(register_info); | ||||
| 
 | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|     rb.Push(result); | ||||
| } | ||||
| 
 | ||||
| void Module::Interface::DeleteRegisterInfo(Kernel::HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp(ctx, 0x405, 0, 0); | ||||
| 
 | ||||
|     LOG_INFO(Service_NFC, "called"); | ||||
| 
 | ||||
|     if (nfc->nfc_mode != CommunicationMode::Amiibo) { | ||||
|         IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|         rb.Push(ResultCommandInvalidForState); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     const auto result = nfc->device->DeleteRegisterInfo(); | ||||
| 
 | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|     rb.Push(result); | ||||
| } | ||||
| 
 | ||||
| void Module::Interface::DeleteApplicationArea(Kernel::HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp(ctx, 0x406, 0, 0); | ||||
| 
 | ||||
|     LOG_INFO(Service_NFC, "called"); | ||||
| 
 | ||||
|     if (nfc->nfc_mode != CommunicationMode::Amiibo) { | ||||
|         IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|         rb.Push(ResultCommandInvalidForState); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     const auto result = nfc->device->DeleteApplicationArea(); | ||||
| 
 | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|     rb.Push(result); | ||||
| } | ||||
| 
 | ||||
| void Module::Interface::ExistsApplicationArea(Kernel::HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp(ctx, 0x407, 0, 0); | ||||
| 
 | ||||
|     if (nfc->nfc_mode != CommunicationMode::Amiibo) { | ||||
|         IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|         rb.Push(ResultCommandInvalidForState); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     bool has_application_area = false; | ||||
|     const auto result = nfc->device->ApplicationAreaExist(has_application_area); | ||||
| 
 | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(0x2, 0); | ||||
|     rb.Push(result); | ||||
|     rb.Push(has_application_area); | ||||
|     LOG_INFO(Service_NFC, "called"); | ||||
| } | ||||
| 
 | ||||
| std::shared_ptr<Module> Module::Interface::GetModule() const { | ||||
|     return nfc; | ||||
| } | ||||
| 
 | ||||
| void Module::Interface::LoadAmiibo(const AmiiboData& amiibo_data) { | ||||
| bool Module::Interface::IsSearchingForAmiibos() { | ||||
|     std::lock_guard lock(HLE::g_hle_lock); | ||||
|     nfc->amiibo_data = amiibo_data; | ||||
|     nfc->amiibo_in_range = true; | ||||
|     nfc->SyncTagState(); | ||||
| 
 | ||||
|     const auto state = nfc->device->GetCurrentState(); | ||||
|     return state == DeviceState::SearchingForTag; | ||||
| } | ||||
| 
 | ||||
| bool Module::Interface::IsTagActive() { | ||||
|     std::lock_guard lock(HLE::g_hle_lock); | ||||
| 
 | ||||
|     const auto state = nfc->device->GetCurrentState(); | ||||
|     return state == DeviceState::TagFound || state == DeviceState::TagMounted || | ||||
|            state == DeviceState::TagPartiallyMounted; | ||||
| } | ||||
| bool Module::Interface::LoadAmiibo(const std::string& fullpath) { | ||||
|     std::lock_guard lock(HLE::g_hle_lock); | ||||
|     return nfc->device->LoadAmiibo(fullpath); | ||||
| } | ||||
| 
 | ||||
| void Module::Interface::RemoveAmiibo() { | ||||
|     std::lock_guard lock(HLE::g_hle_lock); | ||||
|     nfc->amiibo_in_range = false; | ||||
|     nfc->SyncTagState(); | ||||
| } | ||||
| 
 | ||||
| void Module::SyncTagState() { | ||||
|     if (amiibo_in_range && | ||||
|         (nfc_tag_state == TagState::TagOutOfRange || nfc_tag_state == TagState::Scanning)) { | ||||
|         // TODO (wwylele): Should TagOutOfRange->TagInRange transition only happen on the same tag
 | ||||
|         // detected on Scanning->TagInRange?
 | ||||
|         nfc_tag_state = TagState::TagInRange; | ||||
|         tag_in_range_event->Signal(); | ||||
|     } else if (!amiibo_in_range && | ||||
|                (nfc_tag_state == TagState::TagInRange || nfc_tag_state == TagState::TagDataLoaded || | ||||
|                 nfc_tag_state == TagState::Unknown6)) { | ||||
|         // TODO (wwylele): If a tag is removed during TagDataLoaded/Unknown6, should this event
 | ||||
|         // signals early?
 | ||||
|         nfc_tag_state = TagState::TagOutOfRange; | ||||
|         tag_out_of_range_event->Signal(); | ||||
|     } | ||||
|     nfc->device->UnloadAmiibo(); | ||||
| } | ||||
| 
 | ||||
| Module::Interface::Interface(std::shared_ptr<Module> nfc, const char* name, u32 max_session) | ||||
|  | @ -354,10 +682,7 @@ Module::Interface::Interface(std::shared_ptr<Module> nfc, const char* name, u32 | |||
| Module::Interface::~Interface() = default; | ||||
| 
 | ||||
| Module::Module(Core::System& system) { | ||||
|     tag_in_range_event = | ||||
|         system.Kernel().CreateEvent(Kernel::ResetType::OneShot, "NFC::tag_in_range_event"); | ||||
|     tag_out_of_range_event = | ||||
|         system.Kernel().CreateEvent(Kernel::ResetType::OneShot, "NFC::tag_out_range_event"); | ||||
|     device = std::make_shared<NfcDevice>(system); | ||||
| } | ||||
| 
 | ||||
| Module::~Module() = default; | ||||
|  |  | |||
|  | @ -8,6 +8,8 @@ | |||
| #include <memory> | ||||
| #include <boost/serialization/binary_object.hpp> | ||||
| #include "common/common_types.h" | ||||
| #include "core/hle/service/nfc/nfc_device.h" | ||||
| #include "core/hle/service/nfc/nfc_types.h" | ||||
| #include "core/hle/service/service.h" | ||||
| 
 | ||||
| namespace Core { | ||||
|  | @ -20,45 +22,11 @@ class Event; | |||
| 
 | ||||
| namespace Service::NFC { | ||||
| 
 | ||||
| namespace ErrCodes { | ||||
| enum { | ||||
|     CommandInvalidForState = 512, | ||||
| }; | ||||
| } // namespace ErrCodes
 | ||||
| 
 | ||||
| // TODO(FearlessTobi): Add more members to this struct
 | ||||
| struct AmiiboData { | ||||
|     std::array<u8, 7> uuid; | ||||
|     INSERT_PADDING_BYTES(0x4D); | ||||
|     u16_le char_id; | ||||
|     u8 char_variant; | ||||
|     u8 figure_type; | ||||
|     u16_be model_number; | ||||
|     u8 series; | ||||
|     INSERT_PADDING_BYTES(0x1C1); | ||||
| 
 | ||||
| private: | ||||
|     template <class Archive> | ||||
|     void serialize(Archive& ar, const unsigned int) { | ||||
|         ar& boost::serialization::make_binary_object(this, sizeof(AmiiboData)); | ||||
|     } | ||||
|     friend class boost::serialization::access; | ||||
| }; | ||||
| static_assert(sizeof(AmiiboData) == 0x21C, "AmiiboData is an invalid size"); | ||||
| 
 | ||||
| enum class TagState : u8 { | ||||
| enum class CommunicationMode : u8 { | ||||
|     NotInitialized = 0, | ||||
|     NotScanning = 1, | ||||
|     Scanning = 2, | ||||
|     TagInRange = 3, | ||||
|     TagOutOfRange = 4, | ||||
|     TagDataLoaded = 5, | ||||
|     Unknown6 = 6, | ||||
| }; | ||||
| 
 | ||||
| enum class CommunicationStatus : u8 { | ||||
|     AttemptInitialize = 1, | ||||
|     NfcInitialized = 2, | ||||
|     Ntag = 1, | ||||
|     Amiibo = 2, | ||||
|     TrainTag = 3, | ||||
| }; | ||||
| 
 | ||||
| class Module final { | ||||
|  | @ -73,7 +41,11 @@ public: | |||
| 
 | ||||
|         std::shared_ptr<Module> GetModule() const; | ||||
| 
 | ||||
|         void LoadAmiibo(const AmiiboData& amiibo_data); | ||||
|         bool IsSearchingForAmiibos(); | ||||
| 
 | ||||
|         bool IsTagActive(); | ||||
| 
 | ||||
|         bool LoadAmiibo(const std::string& fullpath); | ||||
| 
 | ||||
|         void RemoveAmiibo(); | ||||
| 
 | ||||
|  | @ -82,7 +54,7 @@ public: | |||
|          * NFC::Initialize service function | ||||
|          *  Inputs: | ||||
|          *      0 : Header code [0x00010040] | ||||
|          *      1 : (u8) unknown parameter. Can be either value 0x1 or 0x2 | ||||
|          *      1 : (u8) CommunicationMode. Can be either value 0x1, 0x2 or 0x3 | ||||
|          *  Outputs: | ||||
|          *      1 : Result of function, 0 on success, otherwise error code | ||||
|          */ | ||||
|  | @ -92,7 +64,7 @@ public: | |||
|          * NFC::Shutdown service function | ||||
|          *  Inputs: | ||||
|          *      0 : Header code [0x00020040] | ||||
|          *      1 : (u8) unknown parameter | ||||
|          *      1 : (u8) CommunicationMode. | ||||
|          *  Outputs: | ||||
|          *      1 : Result of function, 0 on success, otherwise error code | ||||
|          */ | ||||
|  | @ -153,6 +125,15 @@ public: | |||
|          */ | ||||
|         void ResetTagScanState(Kernel::HLERequestContext& ctx); | ||||
| 
 | ||||
|         /**
 | ||||
|          * NFC::UpdateStoredAmiiboData service function | ||||
|          *  Inputs: | ||||
|          *      0 : Header code [0x00090002] | ||||
|          *  Outputs: | ||||
|          *      1 : Result of function, 0 on success, otherwise error code | ||||
|          */ | ||||
|         void UpdateStoredAmiiboData(Kernel::HLERequestContext& ctx); | ||||
| 
 | ||||
|         /**
 | ||||
|          * NFC::GetTagInRangeEvent service function | ||||
|          *  Inputs: | ||||
|  | @ -195,6 +176,16 @@ public: | |||
|          */ | ||||
|         void CommunicationGetStatus(Kernel::HLERequestContext& ctx); | ||||
| 
 | ||||
|         /**
 | ||||
|          * NFC::GetTagInfo2 service function | ||||
|          *  Inputs: | ||||
|          *      0 : Header code [0x00100000] | ||||
|          *  Outputs: | ||||
|          *      1 : Result of function, 0 on success, otherwise error code | ||||
|          *   2-26 : 0x60-byte struct | ||||
|          */ | ||||
|         void GetTagInfo2(Kernel::HLERequestContext& ctx); | ||||
| 
 | ||||
|         /**
 | ||||
|          * NFC::GetTagInfo service function | ||||
|          *  Inputs: | ||||
|  | @ -206,23 +197,102 @@ public: | |||
|         void GetTagInfo(Kernel::HLERequestContext& ctx); | ||||
| 
 | ||||
|         /**
 | ||||
|          * NFC::GetAmiiboConfig service function | ||||
|          * NFC::GetTagInfo service function | ||||
|          *  Inputs: | ||||
|          *      0 : Header code [0x00120000] | ||||
|          *  Outputs: | ||||
|          *      1 : Result of function, 0 on success, otherwise error code | ||||
|          *      2 : Output NFC-adapter result-code | ||||
|          */ | ||||
|         void CommunicationGetResult(Kernel::HLERequestContext& ctx); | ||||
| 
 | ||||
|         /**
 | ||||
|          * NFC::OpenAppData service function | ||||
|          *  Inputs: | ||||
|          *      0 : Header code [0x00130040] | ||||
|          *      1 : (u32) App ID | ||||
|          *  Outputs: | ||||
|          *      1 : Result of function, 0 on success, otherwise error code | ||||
|          */ | ||||
|         void OpenAppData(Kernel::HLERequestContext& ctx); | ||||
| 
 | ||||
|         /**
 | ||||
|          * NFC::InitializeWriteAppData service function | ||||
|          *  Inputs: | ||||
|          *      0 : Header code [0x00140384] | ||||
|          *      1 : (u32) App ID | ||||
|          *      2 : Size | ||||
|          *   3-14 : 0x30-byte zeroed-out struct | ||||
|          *     15 : 0x20, PID translate-header for kernel | ||||
|          *     16 : PID written by kernel | ||||
|          *     17 : (Size << 14) | 2 | ||||
|          *     18 : Pointer to input buffer | ||||
|          *  Outputs: | ||||
|          *      1 : Result of function, 0 on success, otherwise error code | ||||
|          */ | ||||
|         void InitializeWriteAppData(Kernel::HLERequestContext& ctx); | ||||
| 
 | ||||
|         /**
 | ||||
|          * NFC::ReadAppData service function | ||||
|          *  Inputs: | ||||
|          *      0 : Header code [0x00150040] | ||||
|          *      1 : Size (unused? Hard-coded to be 0xD8) | ||||
|          *  Outputs: | ||||
|          *      1 : Result of function, 0 on success, otherwise error code | ||||
|          */ | ||||
|         void ReadAppData(Kernel::HLERequestContext& ctx); | ||||
| 
 | ||||
|         /**
 | ||||
|          * NFC::WriteAppData service function | ||||
|          *  Inputs: | ||||
|          *      0 : Header code [0x00160242] | ||||
|          *      1 : Size | ||||
|          *    2-9 : AmiiboWriteRequest struct (see above) | ||||
|          *     10 : (Size << 14) | 2 | ||||
|          *     11 : Pointer to input appdata buffer | ||||
|          *  Outputs: | ||||
|          *      1 : Result of function, 0 on success, otherwise error code | ||||
|          */ | ||||
|         void WriteAppData(Kernel::HLERequestContext& ctx); | ||||
| 
 | ||||
|         /**
 | ||||
|          * NFC::GetRegisterInfo service function | ||||
|          *  Inputs: | ||||
|          *      0 : Header code [0x00170000] | ||||
|          *  Outputs: | ||||
|          *      1 : Result of function, 0 on success, otherwise error code | ||||
|          *   2-43 : AmiiboSettings struct (see above) | ||||
|          */ | ||||
|         void GetRegisterInfo(Kernel::HLERequestContext& ctx); | ||||
| 
 | ||||
|         /**
 | ||||
|          * NFC::GetCommonInfo service function | ||||
|          *  Inputs: | ||||
|          *      0 : Header code [0x00180000] | ||||
|          *  Outputs: | ||||
|          *      1 : Result of function, 0 on success, otherwise error code | ||||
|          *   2-17 : 0x40-byte config struct | ||||
|          */ | ||||
|         void GetAmiiboConfig(Kernel::HLERequestContext& ctx); | ||||
|         void GetCommonInfo(Kernel::HLERequestContext& ctx); | ||||
| 
 | ||||
|         /**
 | ||||
|          * NFC::Unknown0x1A service function | ||||
|          * NFC::GetAppDataInitStruct service function | ||||
|          *  Inputs: | ||||
|          *      0 : Header code [0x00180000] | ||||
|          *  Outputs: | ||||
|          *      1 : Result of function, 0 on success, otherwise error code | ||||
|          *   2-16 : 0x3C-byte config struct | ||||
|          */ | ||||
|         void GetAppDataInitStruct(Kernel::HLERequestContext& ctx); | ||||
| 
 | ||||
|         /**
 | ||||
|          * NFC::LoadAmiiboPartially service function | ||||
|          *  Inputs: | ||||
|          *      0 : Header code [0x001A0000] | ||||
|          *  Outputs: | ||||
|          *      1 : Result of function, 0 on success, otherwise error code | ||||
|          */ | ||||
|         void Unknown0x1A(Kernel::HLERequestContext& ctx); | ||||
|         void LoadAmiiboPartially(Kernel::HLERequestContext& ctx); | ||||
| 
 | ||||
|         /**
 | ||||
|          * NFC::GetIdentificationBlock service function | ||||
|  | @ -234,21 +304,77 @@ public: | |||
|          */ | ||||
|         void GetIdentificationBlock(Kernel::HLERequestContext& ctx); | ||||
| 
 | ||||
|         /**
 | ||||
|          * NFC::Format service function | ||||
|          *  Inputs: | ||||
|          *      0 : Header code [0x040100C2] | ||||
|          *  Outputs: | ||||
|          *      1 : Result of function, 0 on success, otherwise error code | ||||
|          */ | ||||
|         void Format(Kernel::HLERequestContext& ctx); | ||||
| 
 | ||||
|         /**
 | ||||
|          * NFC::GetAdminInfo service function | ||||
|          *  Inputs: | ||||
|          *      0 : Header code [0x04020000] | ||||
|          *  Outputs: | ||||
|          *      1 : Result of function, 0 on success, otherwise error code | ||||
|          */ | ||||
|         void GetAdminInfo(Kernel::HLERequestContext& ctx); | ||||
| 
 | ||||
|         /**
 | ||||
|          * NFC::GetEmptyRegisterInfo service function | ||||
|          *  Inputs: | ||||
|          *      0 : Header code [0x04030000] | ||||
|          *  Outputs: | ||||
|          *      1 : Result of function, 0 on success, otherwise error code | ||||
|          */ | ||||
|         void GetEmptyRegisterInfo(Kernel::HLERequestContext& ctx); | ||||
| 
 | ||||
|         /**
 | ||||
|          * NFC::SetRegisterInfo service function | ||||
|          *  Inputs: | ||||
|          *      0 : Header code [0x04040A40] | ||||
|          *  Outputs: | ||||
|          *      1 : Result of function, 0 on success, otherwise error code | ||||
|          */ | ||||
|         void SetRegisterInfo(Kernel::HLERequestContext& ctx); | ||||
| 
 | ||||
|         /**
 | ||||
|          * NFC::DeleteRegisterInfo service function | ||||
|          *  Inputs: | ||||
|          *      0 : Header code [0x04050000] | ||||
|          *  Outputs: | ||||
|          *      1 : Result of function, 0 on success, otherwise error code | ||||
|          */ | ||||
|         void DeleteRegisterInfo(Kernel::HLERequestContext& ctx); | ||||
| 
 | ||||
|         /**
 | ||||
|          * NFC::DeleteApplicationArea service function | ||||
|          *  Inputs: | ||||
|          *      0 : Header code [0x04060000] | ||||
|          *  Outputs: | ||||
|          *      1 : Result of function, 0 on success, otherwise error code | ||||
|          */ | ||||
|         void DeleteApplicationArea(Kernel::HLERequestContext& ctx); | ||||
| 
 | ||||
|         /**
 | ||||
|          * NFC::ExistsApplicationArea service function | ||||
|          *  Inputs: | ||||
|          *      0 : Header code [0x04070000] | ||||
|          *  Outputs: | ||||
|          *      1 : Result of function, 0 on success, otherwise error code | ||||
|          */ | ||||
|         void ExistsApplicationArea(Kernel::HLERequestContext& ctx); | ||||
| 
 | ||||
|     protected: | ||||
|         std::shared_ptr<Module> nfc; | ||||
|     }; | ||||
| 
 | ||||
| private: | ||||
|     // Sync nfc_tag_state with amiibo_in_range and signal events on state change.
 | ||||
|     void SyncTagState(); | ||||
|     CommunicationMode nfc_mode = CommunicationMode::NotInitialized; | ||||
| 
 | ||||
|     std::shared_ptr<Kernel::Event> tag_in_range_event; | ||||
|     std::shared_ptr<Kernel::Event> tag_out_of_range_event; | ||||
|     TagState nfc_tag_state = TagState::NotInitialized; | ||||
|     CommunicationStatus nfc_status = CommunicationStatus::NfcInitialized; | ||||
| 
 | ||||
|     AmiiboData amiibo_data{}; | ||||
|     bool amiibo_in_range = false; | ||||
|     std::shared_ptr<NfcDevice> device = nullptr; | ||||
| 
 | ||||
|     template <class Archive> | ||||
|     void serialize(Archive& ar, const unsigned int); | ||||
|  |  | |||
							
								
								
									
										1110
									
								
								src/core/hle/service/nfc/nfc_device.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1110
									
								
								src/core/hle/service/nfc/nfc_device.cpp
									
										
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										111
									
								
								src/core/hle/service/nfc/nfc_device.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								src/core/hle/service/nfc/nfc_device.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,111 @@ | |||
| // Copyright 2022 yuzu Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <span> | ||||
| #include <vector> | ||||
| #include <boost/serialization/binary_object.hpp> | ||||
| 
 | ||||
| #include "common/common_types.h" | ||||
| #include "core/hle/service/nfc/nfc_results.h" | ||||
| #include "core/hle/service/nfc/nfc_types.h" | ||||
| #include "core/hle/service/service.h" | ||||
| 
 | ||||
| namespace Kernel { | ||||
| class KEvent; | ||||
| class KReadableEvent; | ||||
| } // namespace Kernel
 | ||||
| 
 | ||||
| namespace Service::NFC { | ||||
| class NfcDevice { | ||||
| public: | ||||
|     NfcDevice(Core::System& system); | ||||
|     ~NfcDevice(); | ||||
| 
 | ||||
|     bool LoadAmiibo(std::string filename); | ||||
|     void UnloadAmiibo(); | ||||
|     void CloseAmiibo(); | ||||
| 
 | ||||
|     void Initialize(); | ||||
|     void Finalize(); | ||||
| 
 | ||||
|     ResultCode StartCommunication(); | ||||
|     ResultCode StopCommunication(); | ||||
|     ResultCode StartDetection(TagProtocol allowed_protocol); | ||||
|     ResultCode StopDetection(); | ||||
|     ResultCode Mount(); | ||||
|     ResultCode MountAmiibo(); | ||||
|     ResultCode PartiallyMount(); | ||||
|     ResultCode PartiallyMountAmiibo(); | ||||
|     ResultCode ResetTagScanState(); | ||||
|     ResultCode Flush(); | ||||
| 
 | ||||
|     ResultCode GetTagInfo2(TagInfo2& tag_info) const; | ||||
|     ResultCode GetTagInfo(TagInfo& tag_info) const; | ||||
|     ResultCode GetCommonInfo(CommonInfo& common_info) const; | ||||
|     ResultCode GetModelInfo(ModelInfo& model_info) const; | ||||
|     ResultCode GetRegisterInfo(RegisterInfo& register_info) const; | ||||
|     ResultCode GetAdminInfo(AdminInfo& admin_info) const; | ||||
| 
 | ||||
|     ResultCode DeleteRegisterInfo(); | ||||
|     ResultCode SetRegisterInfoPrivate(const RegisterInfoPrivate& register_info); | ||||
|     ResultCode RestoreAmiibo(); | ||||
|     ResultCode Format(); | ||||
| 
 | ||||
|     ResultCode OpenApplicationArea(u32 access_id); | ||||
|     ResultCode GetApplicationAreaId(u32& application_area_id) const; | ||||
|     ResultCode GetApplicationArea(std::vector<u8>& data) const; | ||||
|     ResultCode SetApplicationArea(std::span<const u8> data); | ||||
|     ResultCode CreateApplicationArea(u32 access_id, std::span<const u8> data); | ||||
|     ResultCode RecreateApplicationArea(u32 access_id, std::span<const u8> data); | ||||
|     ResultCode DeleteApplicationArea(); | ||||
|     ResultCode ApplicationAreaExist(bool& has_application_area); | ||||
| 
 | ||||
|     constexpr u32 GetApplicationAreaSize() const; | ||||
|     DeviceState GetCurrentState() const; | ||||
|     ResultCode GetCommunicationStatus(CommunicationState& status) const; | ||||
|     ResultCode CheckConnectionState() const; | ||||
| 
 | ||||
|     std::shared_ptr<Kernel::Event> GetActivateEvent() const; | ||||
|     std::shared_ptr<Kernel::Event> GetDeactivateEvent() const; | ||||
| 
 | ||||
| private: | ||||
|     time_t GetCurrentTime() const; | ||||
|     void SetAmiiboName(AmiiboSettings& settings, const AmiiboName& amiibo_name); | ||||
|     AmiiboDate GetAmiiboDate() const; | ||||
|     u64 RemoveVersionByte(u64 application_id) const; | ||||
|     void UpdateSettingsCrc(); | ||||
|     void UpdateRegisterInfoCrc(); | ||||
| 
 | ||||
|     void BuildAmiiboWithoutKeys(); | ||||
| 
 | ||||
|     std::shared_ptr<Kernel::Event> tag_in_range_event = nullptr; | ||||
|     std::shared_ptr<Kernel::Event> tag_out_of_range_event = nullptr; | ||||
|     Core::TimingEventType* remove_amiibo_event = nullptr; | ||||
| 
 | ||||
|     bool is_initalized{}; | ||||
|     bool is_data_moddified{}; | ||||
|     bool is_app_area_open{}; | ||||
|     bool is_plain_amiibo{}; | ||||
|     bool is_write_protected{}; | ||||
|     bool is_tag_in_range{}; | ||||
|     TagProtocol allowed_protocols{}; | ||||
|     DeviceState device_state{DeviceState::NotInitialized}; | ||||
|     ConnectionState connection_state = ConnectionState::Success; | ||||
|     CommunicationState communication_state = CommunicationState::Idle; | ||||
| 
 | ||||
|     std::string amiibo_filename = ""; | ||||
| 
 | ||||
|     SerializableAmiiboFile tag{}; | ||||
|     SerializableEncryptedAmiiboFile encrypted_tag{}; | ||||
| 
 | ||||
|     template <class Archive> | ||||
|     void serialize(Archive& ar, const unsigned int); | ||||
|     friend class boost::serialization::access; | ||||
| }; | ||||
| 
 | ||||
| } // namespace Service::NFC
 | ||||
| 
 | ||||
| SERVICE_CONSTRUCT(Service::NFC::NfcDevice) | ||||
|  | @ -21,25 +21,33 @@ NFC_M::NFC_M(std::shared_ptr<Module> nfc) : Module::Interface(std::move(nfc), "n | |||
|         {IPC::MakeHeader(0x0006, 0, 0), &NFC_M::StopTagScanning, "StopTagScanning"}, | ||||
|         {IPC::MakeHeader(0x0007, 0, 0), &NFC_M::LoadAmiiboData, "LoadAmiiboData"}, | ||||
|         {IPC::MakeHeader(0x0008, 0, 0), &NFC_M::ResetTagScanState, "ResetTagScanState"}, | ||||
|         {IPC::MakeHeader(0x0009, 0, 2), nullptr, "UpdateStoredAmiiboData"}, | ||||
|         {IPC::MakeHeader(0x0009, 0, 2), &NFC_M::UpdateStoredAmiiboData, "UpdateStoredAmiiboData"}, | ||||
|         {IPC::MakeHeader(0x000A, 0, 0), nullptr, "Unknown0x0A"}, | ||||
|         {IPC::MakeHeader(0x000B, 0, 0), &NFC_M::GetTagInRangeEvent, "GetTagInRangeEvent"}, | ||||
|         {IPC::MakeHeader(0x000C, 0, 0), &NFC_M::GetTagOutOfRangeEvent, "GetTagOutOfRangeEvent"}, | ||||
|         {IPC::MakeHeader(0x000D, 0, 0), &NFC_M::GetTagState, "GetTagState"}, | ||||
|         {IPC::MakeHeader(0x000E, 0, 0), nullptr, "Unknown0x0E"}, | ||||
|         {IPC::MakeHeader(0x000F, 0, 0), &NFC_M::CommunicationGetStatus, "CommunicationGetStatus"}, | ||||
|         {IPC::MakeHeader(0x0010, 0, 0), nullptr, "GetTagInfo2"}, | ||||
|         {IPC::MakeHeader(0x0010, 0, 0), &NFC_M::GetTagInfo2, "GetTagInfo2"}, | ||||
|         {IPC::MakeHeader(0x0011, 0, 0), &NFC_M::GetTagInfo, "GetTagInfo"}, | ||||
|         {IPC::MakeHeader(0x0012, 0, 0), nullptr, "CommunicationGetResult"}, | ||||
|         {IPC::MakeHeader(0x0013, 1, 0), nullptr, "OpenAppData"}, | ||||
|         {IPC::MakeHeader(0x0014, 14, 4), nullptr, "InitializeWriteAppData"}, | ||||
|         {IPC::MakeHeader(0x0015, 1, 0), nullptr, "ReadAppData"}, | ||||
|         {IPC::MakeHeader(0x0016, 9, 2), nullptr, "WriteAppData"}, | ||||
|         {IPC::MakeHeader(0x0017, 0, 0), nullptr, "GetAmiiboSettings"}, | ||||
|         {IPC::MakeHeader(0x0018, 0, 0), &NFC_M::GetAmiiboConfig, "GetAmiiboConfig"}, | ||||
|         {IPC::MakeHeader(0x0019, 0, 0), nullptr, "GetAppDataInitStruct"}, | ||||
|         {IPC::MakeHeader(0x001A, 0, 0), &NFC_M::Unknown0x1A, "Unknown0x1A"}, | ||||
|         {IPC::MakeHeader(0x0012, 0, 0), &NFC_M::CommunicationGetResult, "CommunicationGetResult"}, | ||||
|         {IPC::MakeHeader(0x0013, 1, 0), &NFC_M::OpenAppData, "OpenAppData"}, | ||||
|         {IPC::MakeHeader(0x0014, 14, 4), &NFC_M::InitializeWriteAppData, "InitializeWriteAppData"}, | ||||
|         {IPC::MakeHeader(0x0015, 1, 0), &NFC_M::ReadAppData, "ReadAppData"}, | ||||
|         {IPC::MakeHeader(0x0016, 9, 2), &NFC_M::WriteAppData, "WriteAppData"}, | ||||
|         {IPC::MakeHeader(0x0017, 0, 0), &NFC_M::GetRegisterInfo, "GetRegisterInfo"}, | ||||
|         {IPC::MakeHeader(0x0018, 0, 0), &NFC_M::GetCommonInfo, "GetCommonInfo"}, | ||||
|         {IPC::MakeHeader(0x0019, 0, 0), &NFC_M::GetAppDataInitStruct, "GetAppDataInitStruct"}, | ||||
|         {IPC::MakeHeader(0x001A, 0, 0), &NFC_M::LoadAmiiboPartially, "LoadAmiiboPartially"}, | ||||
|         {IPC::MakeHeader(0x001B, 0, 0), &NFC_M::GetIdentificationBlock, "GetIdentificationBlock"}, | ||||
|         // nfc:m
 | ||||
|         {IPC::MakeHeader(0x0404, 41, 0), nullptr, "SetAmiiboSettings"} | ||||
|         {IPC::MakeHeader(0x0401, 3, 2), &NFC_M::Format, "Format"}, | ||||
|         {IPC::MakeHeader(0x0402, 0, 0), &NFC_M::GetAdminInfo, "GetAdminInfo"}, | ||||
|         {IPC::MakeHeader(0x0403, 0, 0), &NFC_M::GetEmptyRegisterInfo, "GetEmptyRegisterInfo"}, | ||||
|         {IPC::MakeHeader(0x0404, 41, 0), &NFC_M::SetRegisterInfo, "SetRegisterInfo"}, | ||||
|         {IPC::MakeHeader(0x0405, 0, 0), &NFC_M::DeleteRegisterInfo, "DeleteRegisterInfo"}, | ||||
|         {IPC::MakeHeader(0x0406, 0, 0), &NFC_M::DeleteApplicationArea, "DeleteApplicationArea"}, | ||||
|         {IPC::MakeHeader(0x0407, 0, 0), &NFC_M::ExistsApplicationArea, "ExistsApplicationArea"} | ||||
|         // clang-format on
 | ||||
|     }; | ||||
|     RegisterHandlers(functions); | ||||
|  |  | |||
							
								
								
									
										62
									
								
								src/core/hle/service/nfc/nfc_results.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								src/core/hle/service/nfc/nfc_results.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,62 @@ | |||
| // Copyright 2023 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include "core/hle/result.h" | ||||
| 
 | ||||
| namespace Service::NFC { | ||||
| 
 | ||||
| namespace ErrCodes { | ||||
| enum { | ||||
|     InvalidArgumentValue = 80, | ||||
|     InvalidArgument = 81, | ||||
| 
 | ||||
|     InvalidChecksum = 200, | ||||
|     WriteFailed = 328, | ||||
| 
 | ||||
|     CommandInvalidForState = 512, | ||||
|     NotAnAmiibo = 522, | ||||
|     CorruptedData = 536, | ||||
|     AppDataUninitialized = 544, | ||||
|     RegistrationUnitialized = 552, | ||||
|     ApplicationAreaExist = 560, | ||||
|     AppIdMismatch = 568, | ||||
| 
 | ||||
|     CommunicationLost = 608, | ||||
|     NoAdapterDetected = 616, | ||||
| }; | ||||
| } // namespace ErrCodes
 | ||||
| 
 | ||||
| constexpr ResultCode ResultInvalidArgumentValue(ErrCodes::InvalidArgumentValue, ErrorModule::NFC, | ||||
|                                                 ErrorSummary::InvalidArgument, ErrorLevel::Status); | ||||
| constexpr ResultCode ResultInvalidArgument(ErrCodes::InvalidArgument, ErrorModule::NFC, | ||||
|                                            ErrorSummary::InvalidArgument, ErrorLevel::Status); | ||||
| constexpr ResultCode ResultCommandInvalidForState(ErrCodes::CommandInvalidForState, | ||||
|                                                   ErrorModule::NFC, ErrorSummary::InvalidState, | ||||
|                                                   ErrorLevel::Status); | ||||
| constexpr ResultCode ResultNotAnAmiibo(ErrCodes::NotAnAmiibo, ErrorModule::NFC, | ||||
|                                        ErrorSummary::InvalidState, ErrorLevel::Status); | ||||
| constexpr ResultCode ResultCorruptedData(ErrCodes::CorruptedData, ErrorModule::NFC, | ||||
|                                          ErrorSummary::InvalidState, ErrorLevel::Status); | ||||
| constexpr ResultCode ResultWriteAmiiboFailed(ErrCodes::WriteFailed, ErrorModule::NFC, | ||||
|                                              ErrorSummary::InvalidState, ErrorLevel::Status); | ||||
| constexpr ResultCode ResultApplicationAreaIsNotInitialized(ErrCodes::AppDataUninitialized, | ||||
|                                                            ErrorModule::NFC, | ||||
|                                                            ErrorSummary::InvalidState, | ||||
|                                                            ErrorLevel::Status); | ||||
| constexpr ResultCode ResultRegistrationIsNotInitialized(ErrCodes::RegistrationUnitialized, | ||||
|                                                         ErrorModule::NFC, | ||||
|                                                         ErrorSummary::InvalidState, | ||||
|                                                         ErrorLevel::Status); | ||||
| constexpr ResultCode ResultApplicationAreaExist(ErrCodes::ApplicationAreaExist, ErrorModule::NFC, | ||||
|                                                 ErrorSummary::InvalidState, ErrorLevel::Status); | ||||
| constexpr ResultCode ResultWrongApplicationAreaId(ErrCodes::AppIdMismatch, ErrorModule::NFC, | ||||
|                                                   ErrorSummary::InvalidState, ErrorLevel::Status); | ||||
| constexpr ResultCode ResultCommunicationLost(ErrCodes::CommunicationLost, ErrorModule::NFC, | ||||
|                                              ErrorSummary::InvalidState, ErrorLevel::Status); | ||||
| constexpr ResultCode ResultNoAdapterDetected(ErrCodes::NoAdapterDetected, ErrorModule::NFC, | ||||
|                                              ErrorSummary::InvalidState, ErrorLevel::Status); | ||||
| 
 | ||||
| } // namespace Service::NFC
 | ||||
							
								
								
									
										460
									
								
								src/core/hle/service/nfc/nfc_types.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										460
									
								
								src/core/hle/service/nfc/nfc_types.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,460 @@ | |||
| // Copyright 2022 yuzu Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <array> | ||||
| 
 | ||||
| #include "common/bit_field.h" | ||||
| #include "common/common_types.h" | ||||
| #include "core/hle/applets/mii_selector.h" | ||||
| 
 | ||||
| namespace Service::NFC { | ||||
| static constexpr std::size_t amiibo_name_length = 0xA; | ||||
| static constexpr std::size_t application_id_version_offset = 0x1c; | ||||
| static constexpr std::size_t counter_limit = 0xffff; | ||||
| 
 | ||||
| enum class ServiceType : u32 { | ||||
|     User, | ||||
|     Debug, | ||||
|     System, | ||||
| }; | ||||
| 
 | ||||
| enum class CommunicationState : u8 { | ||||
|     Idle = 0, | ||||
|     SearchingForAdapter = 1, | ||||
|     Initialized = 2, | ||||
|     Active = 3, | ||||
| }; | ||||
| 
 | ||||
| enum class ConnectionState : u8 { | ||||
|     Success = 0, | ||||
|     NoAdapter = 1, | ||||
|     Lost = 2, | ||||
| }; | ||||
| 
 | ||||
| enum class DeviceState : u32 { | ||||
|     NotInitialized = 0, | ||||
|     Initialized = 1, | ||||
|     SearchingForTag = 2, | ||||
|     TagFound = 3, | ||||
|     TagRemoved = 4, | ||||
|     TagMounted = 5, | ||||
|     TagPartiallyMounted = 6, // Validate this one seems to have other name
 | ||||
| }; | ||||
| 
 | ||||
| enum class ModelType : u32 { | ||||
|     Amiibo, | ||||
| }; | ||||
| 
 | ||||
| enum class MountTarget : u32 { | ||||
|     None, | ||||
|     Rom, | ||||
|     Ram, | ||||
|     All, | ||||
| }; | ||||
| 
 | ||||
| enum class AmiiboType : u8 { | ||||
|     Figure, | ||||
|     Card, | ||||
|     Yarn, | ||||
| }; | ||||
| 
 | ||||
| enum class AmiiboSeries : u8 { | ||||
|     SuperSmashBros, | ||||
|     SuperMario, | ||||
|     ChibiRobo, | ||||
|     YoshiWoollyWorld, | ||||
|     Splatoon, | ||||
|     AnimalCrossing, | ||||
|     EightBitMario, | ||||
|     Skylanders, | ||||
|     Unknown8, | ||||
|     TheLegendOfZelda, | ||||
|     ShovelKnight, | ||||
|     Unknown11, | ||||
|     Kiby, | ||||
|     Pokemon, | ||||
|     MarioSportsSuperstars, | ||||
|     MonsterHunter, | ||||
|     BoxBoy, | ||||
|     Pikmin, | ||||
|     FireEmblem, | ||||
|     Metroid, | ||||
|     Others, | ||||
|     MegaMan, | ||||
|     Diablo, | ||||
| }; | ||||
| 
 | ||||
| enum class TagType : u32 { | ||||
|     None, | ||||
|     Type1, // ISO14443A RW 96-2k bytes 106kbit/s
 | ||||
|     Type2, // ISO14443A RW/RO 540 bytes 106kbit/s
 | ||||
|     Type3, // Sony Felica RW/RO 2k bytes 212kbit/s
 | ||||
|     Type4, // ISO14443A RW/RO 4k-32k bytes 424kbit/s
 | ||||
|     Type5, // ISO15693 RW/RO 540 bytes 106kbit/s
 | ||||
| }; | ||||
| 
 | ||||
| enum class PackedTagType : u8 { | ||||
|     None, | ||||
|     Type1, // ISO14443A RW 96-2k bytes 106kbit/s
 | ||||
|     Type2, // ISO14443A RW/RO 540 bytes 106kbit/s
 | ||||
|     Type3, // Sony Felica RW/RO 2k bytes 212kbit/s
 | ||||
|     Type4, // ISO14443A RW/RO 4k-32k bytes 424kbit/s
 | ||||
|     Type5, // ISO15693 RW/RO 540 bytes 106kbit/s
 | ||||
| }; | ||||
| 
 | ||||
| // Verify this enum. It might be completely wrong default protocol is 0x0
 | ||||
| enum class TagProtocol : u32 { | ||||
|     None, | ||||
|     TypeA = 1U << 0, // ISO14443A
 | ||||
|     TypeB = 1U << 1, // ISO14443B
 | ||||
|     TypeF = 1U << 2, // Sony Felica
 | ||||
|     Unknown1 = 1U << 3, | ||||
|     Unknown2 = 1U << 5, | ||||
|     All = 0xFFFFFFFFU, | ||||
| }; | ||||
| 
 | ||||
| // Verify this enum. It might be completely wrong default protocol is 0x0
 | ||||
| enum class PackedTagProtocol : u8 { | ||||
|     None, | ||||
|     TypeA = 1U << 0, // ISO14443A
 | ||||
|     TypeB = 1U << 1, // ISO14443B
 | ||||
|     TypeF = 1U << 2, // Sony Felica
 | ||||
|     Unknown1 = 1U << 3, | ||||
|     Unknown2 = 1U << 5, | ||||
|     All = 0xFF, | ||||
| }; | ||||
| 
 | ||||
| enum class AppAreaVersion : u8 { | ||||
|     Nintendo3DS = 0, | ||||
|     NintendoWiiU = 1, | ||||
|     Nintendo3DSv2 = 2, | ||||
|     NintendoSwitch = 3, | ||||
|     NotSet = 0xFF, | ||||
| }; | ||||
| 
 | ||||
| using UniqueSerialNumber = std::array<u8, 7>; | ||||
| using LockBytes = std::array<u8, 2>; | ||||
| using HashData = std::array<u8, 0x20>; | ||||
| using ApplicationArea = std::array<u8, 0xD8>; | ||||
| using AmiiboName = std::array<u16_be, amiibo_name_length>; | ||||
| using DataBlock = std::array<u8, 0x10>; | ||||
| using KeyData = std::array<u8, 0x6>; | ||||
| 
 | ||||
| struct TagUuid { | ||||
|     UniqueSerialNumber uid; | ||||
|     u8 nintendo_id; | ||||
|     LockBytes lock_bytes; | ||||
| 
 | ||||
|     template <class Archive> | ||||
|     void serialize(Archive& ar, const unsigned int) { | ||||
|         ar& uid; | ||||
|         ar& nintendo_id; | ||||
|         ar& lock_bytes; | ||||
|     } | ||||
|     friend class boost::serialization::access; | ||||
| }; | ||||
| static_assert(sizeof(TagUuid) == 10, "TagUuid is an invalid size"); | ||||
| 
 | ||||
| struct WriteDate { | ||||
|     u16 year; | ||||
|     u8 month; | ||||
|     u8 day; | ||||
| }; | ||||
| static_assert(sizeof(WriteDate) == 0x4, "WriteDate is an invalid size"); | ||||
| 
 | ||||
| struct AmiiboDate { | ||||
|     u16 raw_date{}; | ||||
| 
 | ||||
|     u16 GetValue() const { | ||||
|         return Common::swap16(raw_date); | ||||
|     } | ||||
| 
 | ||||
|     u16 GetYear() const { | ||||
|         return static_cast<u16>(((GetValue() & 0xFE00) >> 9) + 2000); | ||||
|     } | ||||
|     u8 GetMonth() const { | ||||
|         return static_cast<u8>((GetValue() & 0x01E0) >> 5); | ||||
|     } | ||||
|     u8 GetDay() const { | ||||
|         return static_cast<u8>(GetValue() & 0x001F); | ||||
|     } | ||||
| 
 | ||||
|     WriteDate GetWriteDate() const { | ||||
|         if (!IsValidDate()) { | ||||
|             return { | ||||
|                 .year = 2000, | ||||
|                 .month = 1, | ||||
|                 .day = 1, | ||||
|             }; | ||||
|         } | ||||
|         return { | ||||
|             .year = GetYear(), | ||||
|             .month = GetMonth(), | ||||
|             .day = GetDay(), | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     void SetYear(u16 year) { | ||||
|         const u16 year_converted = static_cast<u16>((year - 2000) << 9); | ||||
|         raw_date = Common::swap16((GetValue() & ~0xFE00) | year_converted); | ||||
|     } | ||||
|     void SetMonth(u8 month) { | ||||
|         const u16 month_converted = static_cast<u16>(month << 5); | ||||
|         raw_date = Common::swap16((GetValue() & ~0x01E0) | month_converted); | ||||
|     } | ||||
|     void SetDay(u8 day) { | ||||
|         const u16 day_converted = static_cast<u16>(day); | ||||
|         raw_date = Common::swap16((GetValue() & ~0x001F) | day_converted); | ||||
|     } | ||||
| 
 | ||||
|     bool IsValidDate() const { | ||||
|         const bool is_day_valid = GetDay() > 0 && GetDay() < 32; | ||||
|         const bool is_month_valid = GetMonth() > 0 && GetMonth() < 13; | ||||
|         const bool is_year_valid = GetYear() >= 2000; | ||||
|         return is_year_valid && is_month_valid && is_day_valid; | ||||
|     } | ||||
| }; | ||||
| static_assert(sizeof(AmiiboDate) == 2, "AmiiboDate is an invalid size"); | ||||
| 
 | ||||
| struct Settings { | ||||
|     union { | ||||
|         u8 raw{}; | ||||
| 
 | ||||
|         BitField<0, 4, u8> font_region; | ||||
|         BitField<4, 1, u8> amiibo_initialized; | ||||
|         BitField<5, 1, u8> appdata_initialized; | ||||
|     }; | ||||
| }; | ||||
| static_assert(sizeof(Settings) == 1, "AmiiboDate is an invalid size"); | ||||
| 
 | ||||
| struct AmiiboSettings { | ||||
|     Settings settings; | ||||
|     u8 country_code_id; | ||||
|     u16_be crc_counter; // Incremented each time crc is changed
 | ||||
|     AmiiboDate init_date; | ||||
|     AmiiboDate write_date; | ||||
|     u32_be crc; | ||||
|     AmiiboName amiibo_name; // UTF-16 text
 | ||||
| }; | ||||
| static_assert(sizeof(AmiiboSettings) == 0x20, "AmiiboSettings is an invalid size"); | ||||
| 
 | ||||
| struct AmiiboModelInfo { | ||||
|     u16 character_id; | ||||
|     u8 character_variant; | ||||
|     AmiiboType amiibo_type; | ||||
|     u16_be model_number; | ||||
|     AmiiboSeries series; | ||||
|     PackedTagType tag_type; | ||||
|     INSERT_PADDING_BYTES(0x4); // Unknown
 | ||||
| }; | ||||
| static_assert(sizeof(AmiiboModelInfo) == 0xC, "AmiiboModelInfo is an invalid size"); | ||||
| 
 | ||||
| struct NTAG215Password { | ||||
|     u32 PWD;  // Password to allow write access
 | ||||
|     u16 PACK; // Password acknowledge reply
 | ||||
|     u16 RFUI; // Reserved for future use
 | ||||
| 
 | ||||
|     template <class Archive> | ||||
|     void serialize(Archive& ar, const unsigned int) { | ||||
|         ar& PWD; | ||||
|         ar& PACK; | ||||
|         ar& RFUI; | ||||
|     } | ||||
|     friend class boost::serialization::access; | ||||
| }; | ||||
| static_assert(sizeof(NTAG215Password) == 0x8, "NTAG215Password is an invalid size"); | ||||
| 
 | ||||
| #pragma pack(1) | ||||
| struct EncryptedAmiiboFile { | ||||
|     u8 constant_value;                // Must be A5
 | ||||
|     u16_be write_counter;             // Number of times the amiibo has been written?
 | ||||
|     u8 amiibo_version;                // Amiibo file version
 | ||||
|     AmiiboSettings settings;          // Encrypted amiibo settings
 | ||||
|     HashData hmac_tag;                // Hash
 | ||||
|     AmiiboModelInfo model_info;       // Encrypted amiibo model info
 | ||||
|     HashData keygen_salt;             // Salt
 | ||||
|     HashData hmac_data;               // Hash
 | ||||
|     HLE::Applets::MiiData owner_mii;  // Encrypted Mii data
 | ||||
|     u16 padding;                      // Mii Padding
 | ||||
|     u16_be owner_mii_aes_ccm;         // Mii data AES-CCM MAC
 | ||||
|     u64_be application_id;            // Encrypted Game id
 | ||||
|     u16_be application_write_counter; // Encrypted Counter
 | ||||
|     u32_be application_area_id;       // Encrypted Game id
 | ||||
|     u8 application_id_byte; | ||||
|     u8 unknown; | ||||
|     u64 mii_extension; | ||||
|     std::array<u32, 0x5> unknown2; | ||||
|     u32_be register_info_crc; | ||||
|     ApplicationArea application_area; // Encrypted Game data
 | ||||
| }; | ||||
| static_assert(sizeof(EncryptedAmiiboFile) == 0x1F8, "AmiiboFile is an invalid size"); | ||||
| 
 | ||||
| struct NTAG215File { | ||||
|     LockBytes lock_bytes;      // Tag UUID
 | ||||
|     u16 static_lock;           // Set defined pages as read only
 | ||||
|     u32 compability_container; // Defines available memory
 | ||||
|     HashData hmac_data;        // Hash
 | ||||
|     u8 constant_value;         // Must be A5
 | ||||
|     u16_be write_counter;      // Number of times the amiibo has been written?
 | ||||
|     u8 amiibo_version;         // Amiibo file version
 | ||||
|     AmiiboSettings settings; | ||||
|     HLE::Applets::MiiData owner_mii;  // Mii data
 | ||||
|     u16 padding;                      // Mii Padding
 | ||||
|     u16_be owner_mii_aes_ccm;         // Mii data AES-CCM MAC
 | ||||
|     u64_be application_id;            // Game id
 | ||||
|     u16_be application_write_counter; // Counter
 | ||||
|     u32_be application_area_id; | ||||
|     u8 application_id_byte; | ||||
|     u8 unknown; | ||||
|     u64 mii_extension; | ||||
|     std::array<u32, 0x5> unknown2; | ||||
|     u32_be register_info_crc; | ||||
|     ApplicationArea application_area; // Game data
 | ||||
|     HashData hmac_tag;                // Hash
 | ||||
|     UniqueSerialNumber uid;           // Unique serial number
 | ||||
|     u8 nintendo_id;                   // Tag UUID
 | ||||
|     AmiiboModelInfo model_info; | ||||
|     HashData keygen_salt;     // Salt
 | ||||
|     u32 dynamic_lock;         // Dynamic lock
 | ||||
|     u32 CFG0;                 // Defines memory protected by password
 | ||||
|     u32 CFG1;                 // Defines number of verification attempts
 | ||||
|     NTAG215Password password; // Password data
 | ||||
| }; | ||||
| static_assert(sizeof(NTAG215File) == 0x21C, "NTAG215File is an invalid size"); | ||||
| static_assert(std::is_trivially_copyable_v<NTAG215File>, "NTAG215File must be trivially copyable."); | ||||
| #pragma pack() | ||||
| 
 | ||||
| struct EncryptedNTAG215File { | ||||
|     TagUuid uuid;                    // Unique serial number
 | ||||
|     u16 static_lock;                 // Set defined pages as read only
 | ||||
|     u32 compability_container;       // Defines available memory
 | ||||
|     EncryptedAmiiboFile user_memory; // Writable data
 | ||||
|     u32 dynamic_lock;                // Dynamic lock
 | ||||
|     u32 CFG0;                        // Defines memory protected by password
 | ||||
|     u32 CFG1;                        // Defines number of verification attempts
 | ||||
|     NTAG215Password password;        // Password data
 | ||||
| }; | ||||
| static_assert(sizeof(EncryptedNTAG215File) == 0x21C, "EncryptedNTAG215File is an invalid size"); | ||||
| static_assert(std::is_trivially_copyable_v<EncryptedNTAG215File>, | ||||
|               "EncryptedNTAG215File must be trivially copyable."); | ||||
| 
 | ||||
| struct SerializableAmiiboFile { | ||||
|     union { | ||||
|         std::array<u8, 0x21C> raw; | ||||
|         NTAG215File file; | ||||
|     }; | ||||
|     template <class Archive> | ||||
|     void serialize(Archive& ar, const unsigned int) { | ||||
|         ar& raw; | ||||
|     } | ||||
|     friend class boost::serialization::access; | ||||
| }; | ||||
| static_assert(sizeof(SerializableAmiiboFile) == 0x21C, "SerializableAmiiboFile is an invalid size"); | ||||
| static_assert(std::is_trivially_copyable_v<SerializableAmiiboFile>, | ||||
|               "SerializableAmiiboFile must be trivially copyable."); | ||||
| 
 | ||||
| struct SerializableEncryptedAmiiboFile { | ||||
|     union { | ||||
|         std::array<u8, 0x21C> raw; | ||||
|         EncryptedNTAG215File file; | ||||
|     }; | ||||
|     template <class Archive> | ||||
|     void serialize(Archive& ar, const unsigned int) { | ||||
|         ar& raw; | ||||
|     } | ||||
|     friend class boost::serialization::access; | ||||
| }; | ||||
| static_assert(sizeof(SerializableEncryptedAmiiboFile) == 0x21C, | ||||
|               "SerializableEncryptedAmiiboFile is an invalid size"); | ||||
| static_assert(std::is_trivially_copyable_v<SerializableEncryptedAmiiboFile>, | ||||
|               "SerializableEncryptedAmiiboFile must be trivially copyable."); | ||||
| 
 | ||||
| struct TagInfo { | ||||
|     u16 uuid_length; | ||||
|     PackedTagProtocol protocol; | ||||
|     PackedTagType tag_type; | ||||
|     UniqueSerialNumber uuid; | ||||
|     std::array<u8, 0x21> extra_data; | ||||
| }; | ||||
| static_assert(sizeof(TagInfo) == 0x2C, "TagInfo is an invalid size"); | ||||
| 
 | ||||
| struct TagInfo2 { | ||||
|     u16 uuid_length; | ||||
|     INSERT_PADDING_BYTES(0x1); | ||||
|     PackedTagType tag_type; | ||||
|     UniqueSerialNumber uuid; | ||||
|     std::array<u8, 0x21> extra_data; | ||||
|     TagProtocol protocol; | ||||
|     std::array<u8, 0x30> extra_data2; | ||||
| }; | ||||
| static_assert(sizeof(TagInfo2) == 0x60, "TagInfo2 is an invalid size"); | ||||
| 
 | ||||
| struct CommonInfo { | ||||
|     WriteDate last_write_date; | ||||
|     u16 application_write_counter; | ||||
|     u16 character_id; | ||||
|     u8 character_variant; | ||||
|     AmiiboSeries series; | ||||
|     u16 model_number; | ||||
|     AmiiboType amiibo_type; | ||||
|     u8 version; | ||||
|     u16 application_area_size; | ||||
|     INSERT_PADDING_BYTES(0x30); | ||||
| }; | ||||
| static_assert(sizeof(CommonInfo) == 0x40, "CommonInfo is an invalid size"); | ||||
| 
 | ||||
| struct ModelInfo { | ||||
|     u16 character_id; | ||||
|     u8 character_variant; | ||||
|     AmiiboSeries series; | ||||
|     u16 model_number; | ||||
|     AmiiboType amiibo_type; | ||||
|     INSERT_PADDING_BYTES(0x2F); | ||||
| }; | ||||
| static_assert(sizeof(ModelInfo) == 0x36, "ModelInfo is an invalid size"); | ||||
| 
 | ||||
| struct RegisterInfo { | ||||
|     HLE::Applets::MiiData mii_data; | ||||
|     INSERT_PADDING_BYTES(0x2); | ||||
|     u16_be owner_mii_aes_ccm; // Mii data AES-CCM MAC
 | ||||
|     AmiiboName amiibo_name; | ||||
|     INSERT_PADDING_BYTES(0x2); // Zero string terminator
 | ||||
|     u8 flags; | ||||
|     u8 font_region; | ||||
|     WriteDate creation_date; | ||||
|     INSERT_PADDING_BYTES(0x2C); | ||||
| }; | ||||
| static_assert(sizeof(RegisterInfo) == 0xA8, "RegisterInfo is an invalid size"); | ||||
| 
 | ||||
| struct RegisterInfoPrivate { | ||||
|     HLE::Applets::MiiData mii_data; | ||||
|     INSERT_PADDING_BYTES(0x2); | ||||
|     u16_be owner_mii_aes_ccm; // Mii data AES-CCM MAC
 | ||||
|     AmiiboName amiibo_name; | ||||
|     INSERT_PADDING_BYTES(0x2); // Zero string terminator
 | ||||
|     u8 flags; | ||||
|     u8 font_region; | ||||
|     WriteDate creation_date; | ||||
|     INSERT_PADDING_BYTES(0x28); | ||||
| }; | ||||
| static_assert(sizeof(RegisterInfoPrivate) == 0xA4, "RegisterInfoPrivate is an invalid size"); | ||||
| static_assert(std::is_trivial_v<RegisterInfoPrivate>, "RegisterInfoPrivate must be trivial."); | ||||
| static_assert(std::is_trivially_copyable_v<RegisterInfoPrivate>, | ||||
|               "RegisterInfoPrivate must be trivially copyable."); | ||||
| 
 | ||||
| struct AdminInfo { | ||||
|     u64_be application_id; | ||||
|     u32_be application_area_id; | ||||
|     u16 crc_counter; | ||||
|     u8 flags; | ||||
|     PackedTagType tag_type; | ||||
|     AppAreaVersion app_area_version; | ||||
|     INSERT_PADDING_BYTES(0x7); | ||||
|     INSERT_PADDING_BYTES(0x28); | ||||
| }; | ||||
| static_assert(sizeof(AdminInfo) == 0x40, "AdminInfo is an invalid size"); | ||||
| 
 | ||||
| } // namespace Service::NFC
 | ||||
|  | @ -20,23 +20,32 @@ NFC_U::NFC_U(std::shared_ptr<Module> nfc) : Module::Interface(std::move(nfc), "n | |||
|         {IPC::MakeHeader(0x0006, 0, 0), &NFC_U::StopTagScanning, "StopTagScanning"}, | ||||
|         {IPC::MakeHeader(0x0007, 0, 0), &NFC_U::LoadAmiiboData, "LoadAmiiboData"}, | ||||
|         {IPC::MakeHeader(0x0008, 0, 0), &NFC_U::ResetTagScanState, "ResetTagScanState"}, | ||||
|         {IPC::MakeHeader(0x0009, 0, 2), nullptr, "UpdateStoredAmiiboData"}, | ||||
|         {IPC::MakeHeader(0x0009, 0, 2), &NFC_U::UpdateStoredAmiiboData, "UpdateStoredAmiiboData"}, | ||||
|         {IPC::MakeHeader(0x000A, 0, 0), nullptr, "Unknown0x0A"}, | ||||
|         {IPC::MakeHeader(0x000B, 0, 0), &NFC_U::GetTagInRangeEvent, "GetTagInRangeEvent"}, | ||||
|         {IPC::MakeHeader(0x000C, 0, 0), &NFC_U::GetTagOutOfRangeEvent, "GetTagOutOfRangeEvent"}, | ||||
|         {IPC::MakeHeader(0x000D, 0, 0), &NFC_U::GetTagState, "GetTagState"}, | ||||
|         {IPC::MakeHeader(0x000E, 0, 0), nullptr, "Unknown0x0E"}, | ||||
|         {IPC::MakeHeader(0x000F, 0, 0), &NFC_U::CommunicationGetStatus, "CommunicationGetStatus"}, | ||||
|         {IPC::MakeHeader(0x0010, 0, 0), nullptr, "GetTagInfo2"}, | ||||
|         {IPC::MakeHeader(0x0010, 0, 0), &NFC_U::GetTagInfo2, "GetTagInfo2"}, | ||||
|         {IPC::MakeHeader(0x0011, 0, 0), &NFC_U::GetTagInfo, "GetTagInfo"}, | ||||
|         {IPC::MakeHeader(0x0012, 0, 0), nullptr, "CommunicationGetResult"}, | ||||
|         {IPC::MakeHeader(0x0013, 1, 0), nullptr, "OpenAppData"}, | ||||
|         {IPC::MakeHeader(0x0014, 14, 4), nullptr, "InitializeWriteAppData"}, | ||||
|         {IPC::MakeHeader(0x0015, 1, 0), nullptr, "ReadAppData"}, | ||||
|         {IPC::MakeHeader(0x0016, 9, 2), nullptr, "WriteAppData"}, | ||||
|         {IPC::MakeHeader(0x0017, 0, 0), nullptr, "GetAmiiboSettings"}, | ||||
|         {IPC::MakeHeader(0x0018, 0, 0), &NFC_U::GetAmiiboConfig, "GetAmiiboConfig"}, | ||||
|         {IPC::MakeHeader(0x0019, 0, 0), nullptr, "GetAppDataInitStruct"}, | ||||
|         {IPC::MakeHeader(0x001A, 0, 0), &NFC_U::Unknown0x1A, "Unknown0x1A"}, | ||||
|         {IPC::MakeHeader(0x0012, 0, 0), &NFC_U::CommunicationGetResult, "CommunicationGetResult"}, | ||||
|         {IPC::MakeHeader(0x0013, 1, 0), &NFC_U::OpenAppData, "OpenAppData"}, | ||||
|         {IPC::MakeHeader(0x0014, 14, 4), &NFC_U::InitializeWriteAppData, "InitializeWriteAppData"}, | ||||
|         {IPC::MakeHeader(0x0015, 1, 0), &NFC_U::ReadAppData, "ReadAppData"}, | ||||
|         {IPC::MakeHeader(0x0016, 9, 2), &NFC_U::WriteAppData, "WriteAppData"}, | ||||
|         {IPC::MakeHeader(0x0017, 0, 0), &NFC_U::GetRegisterInfo, "GetRegisterInfo"}, | ||||
|         {IPC::MakeHeader(0x0018, 0, 0), &NFC_U::GetCommonInfo, "GetCommonInfo"}, | ||||
|         {IPC::MakeHeader(0x0019, 0, 0), &NFC_U::GetAppDataInitStruct, "GetAppDataInitStruct"}, | ||||
|         {IPC::MakeHeader(0x001A, 0, 0), &NFC_U::LoadAmiiboPartially, "LoadAmiiboPartially"}, | ||||
|         {IPC::MakeHeader(0x001B, 0, 0), &NFC_U::GetIdentificationBlock, "GetIdentificationBlock"}, | ||||
|         {IPC::MakeHeader(0x001C, 0, 0), nullptr, "Unknown0x1C"}, | ||||
|         {IPC::MakeHeader(0x001D, 0, 0), nullptr, "Unknown0x1D"}, | ||||
|         {IPC::MakeHeader(0x001E, 0, 0), nullptr, "Unknown0x1E"}, | ||||
|         {IPC::MakeHeader(0x001F, 0, 0), nullptr, "Unknown0x1F"}, | ||||
|         {IPC::MakeHeader(0x0020, 0, 0), nullptr, "Unknown0x20"}, | ||||
|         {IPC::MakeHeader(0x0021, 0, 0), nullptr, "Unknown0x21"}, | ||||
|         {IPC::MakeHeader(0x0022, 0, 0), nullptr, "Unknown0x22"}, | ||||
|         // clang-format on
 | ||||
|     }; | ||||
|     RegisterHandlers(functions); | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue