diff --git a/src/Cafe/CMakeLists.txt b/src/Cafe/CMakeLists.txt index 851854fc..b5090dcf 100644 --- a/src/Cafe/CMakeLists.txt +++ b/src/Cafe/CMakeLists.txt @@ -218,6 +218,8 @@ add_library(CemuCafe HW/SI/SI.cpp HW/SI/si.h HW/VI/VI.cpp + IOSU/ccr_nfc/iosu_ccr_nfc.cpp + IOSU/ccr_nfc/iosu_ccr_nfc.h IOSU/fsa/fsa_types.h IOSU/fsa/iosu_fsa.cpp IOSU/fsa/iosu_fsa.h @@ -378,6 +380,16 @@ add_library(CemuCafe OS/libs/h264_avc/parser/H264Parser.h OS/libs/mic/mic.cpp OS/libs/mic/mic.h + OS/libs/nfc/ndef.cpp + OS/libs/nfc/ndef.h + OS/libs/nfc/nfc.cpp + OS/libs/nfc/nfc.h + OS/libs/nfc/stream.cpp + OS/libs/nfc/stream.h + OS/libs/nfc/TagV0.cpp + OS/libs/nfc/TagV0.h + OS/libs/nfc/TLV.cpp + OS/libs/nfc/TLV.h OS/libs/nlibcurl/nlibcurl.cpp OS/libs/nlibcurl/nlibcurlDebug.hpp OS/libs/nlibcurl/nlibcurl.h @@ -453,6 +465,8 @@ add_library(CemuCafe OS/libs/nsyskbd/nsyskbd.h OS/libs/nsysnet/nsysnet.cpp OS/libs/nsysnet/nsysnet.h + OS/libs/ntag/ntag.cpp + OS/libs/ntag/ntag.h OS/libs/padscore/padscore.cpp OS/libs/padscore/padscore.h OS/libs/proc_ui/proc_ui.cpp diff --git a/src/Cafe/CafeSystem.cpp b/src/Cafe/CafeSystem.cpp index 3c62a686..958a5a57 100644 --- a/src/Cafe/CafeSystem.cpp +++ b/src/Cafe/CafeSystem.cpp @@ -35,6 +35,7 @@ #include "Cafe/IOSU/legacy/iosu_boss.h" #include "Cafe/IOSU/legacy/iosu_nim.h" #include "Cafe/IOSU/PDM/iosu_pdm.h" +#include "Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.h" // IOSU initializer functions #include "Cafe/IOSU/kernel/iosu_kernel.h" @@ -51,6 +52,8 @@ #include "Cafe/OS/libs/gx2/GX2.h" #include "Cafe/OS/libs/gx2/GX2_Misc.h" #include "Cafe/OS/libs/mic/mic.h" +#include "Cafe/OS/libs/nfc/nfc.h" +#include "Cafe/OS/libs/ntag/ntag.h" #include "Cafe/OS/libs/nn_aoc/nn_aoc.h" #include "Cafe/OS/libs/nn_pdm/nn_pdm.h" #include "Cafe/OS/libs/nn_cmpt/nn_cmpt.h" @@ -533,6 +536,7 @@ namespace CafeSystem iosu::acp::GetModule(), iosu::fpd::GetModule(), iosu::pdm::GetModule(), + iosu::ccr_nfc::GetModule(), }; // initialize all subsystems which are persistent and don't depend on a game running @@ -587,6 +591,8 @@ namespace CafeSystem H264::Initialize(); snd_core::Initialize(); mic::Initialize(); + nfc::Initialize(); + ntag::Initialize(); // init hardware register interfaces HW_SI::Initialize(); } diff --git a/src/Cafe/HW/Espresso/PPCCallback.h b/src/Cafe/HW/Espresso/PPCCallback.h index 19fcd4d1..3d5393b1 100644 --- a/src/Cafe/HW/Espresso/PPCCallback.h +++ b/src/Cafe/HW/Espresso/PPCCallback.h @@ -5,8 +5,28 @@ struct PPCCoreCallbackData_t { sint32 gprCount = 0; sint32 floatCount = 0; + sint32 stackCount = 0; }; +inline void _PPCCoreCallback_writeGPRArg(PPCCoreCallbackData_t& data, PPCInterpreter_t* hCPU, uint32 value) +{ + if (data.gprCount < 8) + { + hCPU->gpr[3 + data.gprCount] = value; + data.gprCount++; + } + else + { + uint32 stackOffset = 8 + data.stackCount * 4; + + // PPCCore_executeCallbackInternal does -16*4 to save the current stack area + stackOffset -= 16 * 4; + + memory_writeU32(hCPU->gpr[1] + stackOffset, value); + data.stackCount++; + } +} + // callback functions inline uint32 PPCCoreCallback(MPTR function, const PPCCoreCallbackData_t& data) { @@ -16,23 +36,21 @@ inline uint32 PPCCoreCallback(MPTR function, const PPCCoreCallbackData_t& data) template uint32 PPCCoreCallback(MPTR function, PPCCoreCallbackData_t& data, T currentArg, TArgs... args) { - cemu_assert_debug(data.gprCount <= 8); - cemu_assert_debug(data.floatCount <= 8); + // TODO float arguments on stack + cemu_assert_debug(data.floatCount < 8); + PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance(); if constexpr (std::is_pointer_v) { - hCPU->gpr[3 + data.gprCount] = MEMPTR(currentArg).GetMPTR(); - data.gprCount++; + _PPCCoreCallback_writeGPRArg(data, hCPU, MEMPTR(currentArg).GetMPTR()); } else if constexpr (std::is_base_of_v>) { - hCPU->gpr[3 + data.gprCount] = currentArg.GetMPTR(); - data.gprCount++; + _PPCCoreCallback_writeGPRArg(data, hCPU, currentArg.GetMPTR()); } else if constexpr (std::is_reference_v) { - hCPU->gpr[3 + data.gprCount] = MEMPTR(¤tArg).GetMPTR(); - data.gprCount++; + _PPCCoreCallback_writeGPRArg(data, hCPU, MEMPTR(¤tArg).GetMPTR()); } else if constexpr(std::is_enum_v) { @@ -53,8 +71,7 @@ uint32 PPCCoreCallback(MPTR function, PPCCoreCallbackData_t& data, T currentArg, } else { - hCPU->gpr[3 + data.gprCount] = (uint32)currentArg; - data.gprCount++; + _PPCCoreCallback_writeGPRArg(data, hCPU, (uint32)currentArg); } return PPCCoreCallback(function, data, args...); diff --git a/src/Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.cpp b/src/Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.cpp new file mode 100644 index 00000000..1ceb16dc --- /dev/null +++ b/src/Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.cpp @@ -0,0 +1,406 @@ +#include "iosu_ccr_nfc.h" +#include "Cafe/IOSU/kernel/iosu_kernel.h" +#include "util/crypto/aes128.h" +#include +#include + +namespace iosu +{ + namespace ccr_nfc + { + IOSMsgQueueId sCCRNFCMsgQueue; + SysAllocator sCCRNFCMsgQueueMsgBuffer; + std::thread sCCRNFCThread; + + constexpr uint8 sNfcKey[] = { 0xC1, 0x2B, 0x07, 0x10, 0xD7, 0x2C, 0xEB, 0x5D, 0x43, 0x49, 0xB7, 0x43, 0xE3, 0xCA, 0xD2, 0x24 }; + constexpr uint8 sNfcKeyIV[] = { 0x4F, 0xD3, 0x9A, 0x6E, 0x79, 0xFC, 0xEA, 0xAD, 0x99, 0x90, 0x4D, 0xB8, 0xEE, 0x38, 0xE9, 0xDB }; + + constexpr uint8 sUnfixedInfosMagicBytes[] = { 0x00, 0x00, 0xDB, 0x4B, 0x9E, 0x3F, 0x45, 0x27, 0x8F, 0x39, 0x7E, 0xFF, 0x9B, 0x4F, 0xB9, 0x93 }; + constexpr uint8 sLockedSecretMagicBytes[] = { 0xFD, 0xC8, 0xA0, 0x76, 0x94, 0xB8, 0x9E, 0x4C, 0x47, 0xD3, 0x7D, 0xE8, 0xCE, 0x5C, 0x74, 0xC1 }; + constexpr uint8 sUnfixedInfosString[] = { 0x75, 0x6E, 0x66, 0x69, 0x78, 0x65, 0x64, 0x20, 0x69, 0x6E, 0x66, 0x6F, 0x73, 0x00, 0x00, 0x00 }; + constexpr uint8 sLockedSecretString[] = { 0x6C, 0x6F, 0x63, 0x6B, 0x65, 0x64, 0x20, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x00, 0x00, 0x00 }; + + constexpr uint8 sLockedSecretHmacKey[] = { 0x7F, 0x75, 0x2D, 0x28, 0x73, 0xA2, 0x00, 0x17, 0xFE, 0xF8, 0x5C, 0x05, 0x75, 0x90, 0x4B, 0x6D }; + constexpr uint8 sUnfixedInfosHmacKey[] = { 0x1D, 0x16, 0x4B, 0x37, 0x5B, 0x72, 0xA5, 0x57, 0x28, 0xB9, 0x1D, 0x64, 0xB6, 0xA3, 0xC2, 0x05 }; + + uint8 sLockedSecretInternalKey[0x10]; + uint8 sLockedSecretInternalNonce[0x10]; + uint8 sLockedSecretInternalHmacKey[0x10]; + + uint8 sUnfixedInfosInternalKey[0x10]; + uint8 sUnfixedInfosInternalNonce[0x10]; + uint8 sUnfixedInfosInternalHmacKey[0x10]; + + sint32 __CCRNFCValidateCryptData(CCRNFCCryptData* data, uint32 size, bool validateOffsets) + { + if (!data) + { + return CCR_NFC_ERROR; + } + + if (size != sizeof(CCRNFCCryptData)) + { + return CCR_NFC_ERROR; + } + + if (!validateOffsets) + { + return 0; + } + + // Make sure all offsets are within bounds + if (data->version == 0) + { + if (data->unfixedInfosHmacOffset < 0x1C9 && data->unfixedInfosOffset < 0x1C9 && + data->lockedSecretHmacOffset < 0x1C9 && data->lockedSecretOffset < 0x1C9 && + data->lockedSecretSize < 0x1C9 && data->unfixedInfosSize < 0x1C9) + { + return 0; + } + } + else if (data->version == 2) + { + if (data->unfixedInfosHmacOffset < 0x21D && data->unfixedInfosOffset < 0x21D && + data->lockedSecretHmacOffset < 0x21D && data->lockedSecretOffset < 0x21D && + data->lockedSecretSize < 0x21D && data->unfixedInfosSize < 0x21D) + { + return 0; + } + } + + return CCR_NFC_ERROR; + } + + sint32 CCRNFCAESCTRCrypt(const uint8* key, const void* ivNonce, const void* inData, uint32 inSize, void* outData, uint32 outSize) + { + uint8 tmpIv[0x10]; + memcpy(tmpIv, ivNonce, sizeof(tmpIv)); + + memcpy(outData, inData, inSize); + AES128CTR_transform((uint8*)outData, outSize, (uint8*)key, tmpIv); + return 0; + } + + sint32 __CCRNFCGenerateKey(const uint8* hmacKey, uint32 hmacKeySize, const uint8* name, uint32 nameSize, const uint8* inData, uint32 inSize, uint8* outData, uint32 outSize) + { + if (nameSize != 0xe || outSize != 0x40) + { + return CCR_NFC_ERROR; + } + + // Create a buffer containing 2 counter bytes, the key name, and the key data + uint8 buffer[0x50]; + buffer[0] = 0; + buffer[1] = 0; + memcpy(buffer + 2, name, nameSize); + memcpy(buffer + nameSize + 2, inData, inSize); + + uint16 counter = 0; + while (outSize > 0) + { + // Set counter bytes and increment counter + buffer[0] = (counter >> 8) & 0xFF; + buffer[1] = counter & 0xFF; + counter++; + + uint32 dataSize = outSize; + if (!HMAC(EVP_sha256(), hmacKey, hmacKeySize, buffer, sizeof(buffer), outData, &dataSize)) + { + return CCR_NFC_ERROR; + } + + outSize -= 0x20; + outData += 0x20; + } + + return 0; + } + + sint32 __CCRNFCGenerateInternalKeys(const CCRNFCCryptData* in, const uint8* keyGenSalt) + { + uint8 lockedSecretBuffer[0x40] = { 0 }; + uint8 unfixedInfosBuffer[0x40] = { 0 }; + uint8 outBuffer[0x40] = { 0 }; + + // Fill the locked secret buffer + memcpy(lockedSecretBuffer, sLockedSecretMagicBytes, sizeof(sLockedSecretMagicBytes)); + if (in->version == 0) + { + // For Version 0 this is the 16-byte Format Info + memcpy(lockedSecretBuffer + 0x10, in->data + in->uuidOffset, 0x10); + } + else if (in->version == 2) + { + // For Version 2 this is 2 times the 7-byte UID + 1 check byte + memcpy(lockedSecretBuffer + 0x10, in->data + in->uuidOffset, 8); + memcpy(lockedSecretBuffer + 0x18, in->data + in->uuidOffset, 8); + } + else + { + return CCR_NFC_ERROR; + } + // Append key generation salt + memcpy(lockedSecretBuffer + 0x20, keyGenSalt, 0x20); + + // Generate the key output + sint32 res = __CCRNFCGenerateKey(sLockedSecretHmacKey, sizeof(sLockedSecretHmacKey), sLockedSecretString, 0xe, lockedSecretBuffer, sizeof(lockedSecretBuffer), outBuffer, sizeof(outBuffer)); + if (res != 0) + { + return res; + } + + // Unpack the key buffer + memcpy(sLockedSecretInternalKey, outBuffer, 0x10); + memcpy(sLockedSecretInternalNonce, outBuffer + 0x10, 0x10); + memcpy(sLockedSecretInternalHmacKey, outBuffer + 0x20, 0x10); + + // Fill the unfixed infos buffer + memcpy(unfixedInfosBuffer, in->data + in->seedOffset, 2); + memcpy(unfixedInfosBuffer + 2, sUnfixedInfosMagicBytes + 2, 0xe); + if (in->version == 0) + { + // For Version 0 this is the 16-byte Format Info + memcpy(unfixedInfosBuffer + 0x10, in->data + in->uuidOffset, 0x10); + } + else if (in->version == 2) + { + // For Version 2 this is 2 times the 7-byte UID + 1 check byte + memcpy(unfixedInfosBuffer + 0x10, in->data + in->uuidOffset, 8); + memcpy(unfixedInfosBuffer + 0x18, in->data + in->uuidOffset, 8); + } + else + { + return CCR_NFC_ERROR; + } + // Append key generation salt + memcpy(unfixedInfosBuffer + 0x20, keyGenSalt, 0x20); + + // Generate the key output + res = __CCRNFCGenerateKey(sUnfixedInfosHmacKey, sizeof(sUnfixedInfosHmacKey), sUnfixedInfosString, 0xe, unfixedInfosBuffer, sizeof(unfixedInfosBuffer), outBuffer, sizeof(outBuffer)); + if (res != 0) + { + return res; + } + + // Unpack the key buffer + memcpy(sUnfixedInfosInternalKey, outBuffer, 0x10); + memcpy(sUnfixedInfosInternalNonce, outBuffer + 0x10, 0x10); + memcpy(sUnfixedInfosInternalHmacKey, outBuffer + 0x20, 0x10); + + return 0; + } + + sint32 __CCRNFCCryptData(const CCRNFCCryptData* in, CCRNFCCryptData* out, bool decrypt) + { + // Decrypt key generation salt + uint8 keyGenSalt[0x20]; + sint32 res = CCRNFCAESCTRCrypt(sNfcKey, sNfcKeyIV, in->data + in->keyGenSaltOffset, 0x20, keyGenSalt, sizeof(keyGenSalt)); + if (res != 0) + { + return res; + } + + // Prepare internal keys + res = __CCRNFCGenerateInternalKeys(in, keyGenSalt); + if (res != 0) + { + return res; + } + + if (decrypt) + { + // Only version 0 tags have an encrypted locked secret area + if (in->version == 0) + { + res = CCRNFCAESCTRCrypt(sLockedSecretInternalKey, sLockedSecretInternalNonce, in->data + in->lockedSecretOffset, in->lockedSecretSize, out->data + in->lockedSecretOffset, in->lockedSecretSize); + if (res != 0) + { + return res; + } + } + + // Decrypt unfxied infos + res = CCRNFCAESCTRCrypt(sUnfixedInfosInternalKey, sUnfixedInfosInternalNonce, in->data + in->unfixedInfosOffset, in->unfixedInfosSize, out->data + in->unfixedInfosOffset, in->unfixedInfosSize); + if (res != 0) + { + return res; + } + + // Verify HMACs + uint8 hmacBuffer[0x20]; + uint32 hmacLen = sizeof(hmacBuffer); + + if (!HMAC(EVP_sha256(), sLockedSecretInternalHmacKey, sizeof(sLockedSecretInternalHmacKey), out->data + in->lockedSecretHmacOffset + 0x20, (in->dataSize - in->lockedSecretHmacOffset) - 0x20, hmacBuffer, &hmacLen)) + { + return CCR_NFC_ERROR; + } + + if (memcmp(in->data + in->lockedSecretHmacOffset, hmacBuffer, 0x20) != 0) + { + return CCR_NFC_INVALID_LOCKED_SECRET; + } + + if (in->version == 0) + { + hmacLen = sizeof(hmacBuffer); + res = HMAC(EVP_sha256(), sUnfixedInfosInternalHmacKey, sizeof(sUnfixedInfosInternalHmacKey), out->data + in->unfixedInfosHmacOffset + 0x20, (in->dataSize - in->unfixedInfosHmacOffset) - 0x20, hmacBuffer, &hmacLen) ? 0 : CCR_NFC_ERROR; + } + else + { + hmacLen = sizeof(hmacBuffer); + res = HMAC(EVP_sha256(), sUnfixedInfosInternalHmacKey, sizeof(sUnfixedInfosInternalHmacKey), out->data + in->unfixedInfosHmacOffset + 0x21, (in->dataSize - in->unfixedInfosHmacOffset) - 0x21, hmacBuffer, &hmacLen) ? 0 : CCR_NFC_ERROR; + } + + if (memcmp(in->data + in->unfixedInfosHmacOffset, hmacBuffer, 0x20) != 0) + { + return CCR_NFC_INVALID_UNFIXED_INFOS; + } + } + else + { + uint8 hmacBuffer[0x20]; + uint32 hmacLen = sizeof(hmacBuffer); + + if (!HMAC(EVP_sha256(), sLockedSecretInternalHmacKey, sizeof(sLockedSecretInternalHmacKey), out->data + in->lockedSecretHmacOffset + 0x20, (in->dataSize - in->lockedSecretHmacOffset) - 0x20, hmacBuffer, &hmacLen)) + { + return CCR_NFC_ERROR; + } + + if (memcmp(in->data + in->lockedSecretHmacOffset, hmacBuffer, 0x20) != 0) + { + return CCR_NFC_INVALID_LOCKED_SECRET; + } + + // Only version 0 tags have an encrypted locked secret area + if (in->version == 0) + { + uint32 hmacLen = 0x20; + if (!HMAC(EVP_sha256(), sUnfixedInfosInternalHmacKey, sizeof(sUnfixedInfosInternalHmacKey), out->data + in->unfixedInfosHmacOffset + 0x20, (in->dataSize - in->unfixedInfosHmacOffset) - 0x20, out->data + in->unfixedInfosHmacOffset, &hmacLen)) + { + return CCR_NFC_ERROR; + } + + res = CCRNFCAESCTRCrypt(sLockedSecretInternalKey, sLockedSecretInternalNonce, in->data + in->lockedSecretOffset, in->lockedSecretSize, out->data + in->lockedSecretOffset, in->lockedSecretSize); + if (res != 0) + { + return res; + } + } + else + { + uint32 hmacLen = 0x20; + if (!HMAC(EVP_sha256(), sUnfixedInfosInternalHmacKey, sizeof(sUnfixedInfosInternalHmacKey), out->data + in->unfixedInfosHmacOffset + 0x21, (in->dataSize - in->unfixedInfosHmacOffset) - 0x21, out->data + in->unfixedInfosHmacOffset, &hmacLen)) + { + return CCR_NFC_ERROR; + } + } + + res = CCRNFCAESCTRCrypt(sUnfixedInfosInternalKey, sUnfixedInfosInternalNonce, in->data + in->unfixedInfosOffset, in->unfixedInfosSize, out->data + in->unfixedInfosOffset, in->unfixedInfosSize); + if (res != 0) + { + return res; + } + } + + return res; + } + + void CCRNFCThread() + { + iosu::kernel::IOSMessage msg; + while (true) + { + IOS_ERROR error = iosu::kernel::IOS_ReceiveMessage(sCCRNFCMsgQueue, &msg, 0); + cemu_assert(!IOS_ResultIsError(error)); + + // Check for system exit + if (msg == 0xf00dd00d) + { + return; + } + + IPCCommandBody* cmd = MEMPTR(msg).GetPtr(); + if (cmd->cmdId == IPCCommandId::IOS_OPEN) + { + iosu::kernel::IOS_ResourceReply(cmd, IOS_ERROR_OK); + } + else if (cmd->cmdId == IPCCommandId::IOS_CLOSE) + { + iosu::kernel::IOS_ResourceReply(cmd, IOS_ERROR_OK); + } + else if (cmd->cmdId == IPCCommandId::IOS_IOCTL) + { + sint32 result; + uint32 requestId = cmd->args[0]; + void* ptrIn = MEMPTR(cmd->args[1]); + uint32 sizeIn = cmd->args[2]; + void* ptrOut = MEMPTR(cmd->args[3]); + uint32 sizeOut = cmd->args[4]; + + if ((result = __CCRNFCValidateCryptData(static_cast(ptrIn), sizeIn, true)) == 0 && + (result = __CCRNFCValidateCryptData(static_cast(ptrOut), sizeOut, false)) == 0) + { + // Initialize outData with inData + memcpy(ptrOut, ptrIn, sizeIn); + + switch (requestId) + { + case 1: // encrypt + result = __CCRNFCCryptData(static_cast(ptrIn), static_cast(ptrOut), false); + break; + case 2: // decrypt + result = __CCRNFCCryptData(static_cast(ptrIn), static_cast(ptrOut), true); + break; + default: + cemuLog_log(LogType::Force, "/dev/ccr_nfc: Unsupported IOCTL requestId"); + cemu_assert_suspicious(); + result = IOS_ERROR_INVALID; + break; + } + } + + iosu::kernel::IOS_ResourceReply(cmd, static_cast(result)); + } + else + { + cemuLog_log(LogType::Force, "/dev/ccr_nfc: Unsupported IPC cmdId"); + cemu_assert_suspicious(); + iosu::kernel::IOS_ResourceReply(cmd, IOS_ERROR_INVALID); + } + } + } + + class : public ::IOSUModule + { + void SystemLaunch() override + { + sCCRNFCMsgQueue = iosu::kernel::IOS_CreateMessageQueue(sCCRNFCMsgQueueMsgBuffer.GetPtr(), sCCRNFCMsgQueueMsgBuffer.GetCount()); + cemu_assert(!IOS_ResultIsError(static_cast(sCCRNFCMsgQueue))); + + IOS_ERROR error = iosu::kernel::IOS_RegisterResourceManager("/dev/ccr_nfc", sCCRNFCMsgQueue); + cemu_assert(!IOS_ResultIsError(error)); + + sCCRNFCThread = std::thread(CCRNFCThread); + } + + void SystemExit() override + { + if (sCCRNFCMsgQueue < 0) + { + return; + } + + iosu::kernel::IOS_SendMessage(sCCRNFCMsgQueue, 0xf00dd00d, 0); + sCCRNFCThread.join(); + + iosu::kernel::IOS_DestroyMessageQueue(sCCRNFCMsgQueue); + sCCRNFCMsgQueue = -1; + } + } sIOSUModuleCCRNFC; + + IOSUModule* GetModule() + { + return &sIOSUModuleCCRNFC; + } + } +} diff --git a/src/Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.h b/src/Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.h new file mode 100644 index 00000000..ae99d645 --- /dev/null +++ b/src/Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.h @@ -0,0 +1,31 @@ +#pragma once +#include "Cafe/IOSU/iosu_types_common.h" + +#define CCR_NFC_ERROR (-0x2F001E) +#define CCR_NFC_INVALID_LOCKED_SECRET (-0x2F0029) +#define CCR_NFC_INVALID_UNFIXED_INFOS (-0x2F002A) + +namespace iosu +{ + namespace ccr_nfc + { + struct CCRNFCCryptData + { + uint32 version; + uint32 dataSize; + uint32 seedOffset; + uint32 keyGenSaltOffset; + uint32 uuidOffset; + uint32 unfixedInfosOffset; + uint32 unfixedInfosSize; + uint32 lockedSecretOffset; + uint32 lockedSecretSize; + uint32 unfixedInfosHmacOffset; + uint32 lockedSecretHmacOffset; + uint8 data[540]; + }; + static_assert(sizeof(CCRNFCCryptData) == 0x248); + + IOSUModule* GetModule(); + } +} diff --git a/src/Cafe/OS/libs/nfc/TLV.cpp b/src/Cafe/OS/libs/nfc/TLV.cpp new file mode 100644 index 00000000..2650858d --- /dev/null +++ b/src/Cafe/OS/libs/nfc/TLV.cpp @@ -0,0 +1,139 @@ +#include "TLV.h" +#include "stream.h" + +#include + +TLV::TLV() +{ +} + +TLV::TLV(Tag tag, std::vector value) + : mTag(tag), mValue(std::move(value)) +{ +} + +TLV::~TLV() +{ +} + +std::vector TLV::FromBytes(const std::span& data) +{ + bool hasTerminator = false; + std::vector tlvs; + SpanStream stream(data, std::endian::big); + + while (stream.GetRemaining() > 0 && !hasTerminator) + { + // Read the tag + uint8 byte; + stream >> byte; + Tag tag = static_cast(byte); + + switch (tag) + { + case TLV::TAG_NULL: + // Don't need to do anything for NULL tags + break; + + case TLV::TAG_TERMINATOR: + tlvs.emplace_back(tag, std::vector{}); + hasTerminator = true; + break; + + default: + { + // Read the length + uint16 length; + stream >> byte; + length = byte; + + // If the length is 0xff, 2 bytes with length follow + if (length == 0xff) { + stream >> length; + } + + std::vector value; + value.resize(length); + stream.Read(value); + + tlvs.emplace_back(tag, value); + break; + } + } + + if (stream.GetError() != Stream::ERROR_OK) + { + cemuLog_log(LogType::Force, "Error: TLV parsing read past end of stream"); + // Clear tlvs to prevent further havoc while parsing ndef data + tlvs.clear(); + break; + } + } + + // This seems to be okay, at least NTAGs don't add a terminator tag + // if (!hasTerminator) + // { + // cemuLog_log(LogType::Force, "Warning: TLV parsing reached end of stream without terminator tag"); + // } + + return tlvs; +} + +std::vector TLV::ToBytes() const +{ + std::vector bytes; + VectorStream stream(bytes, std::endian::big); + + // Write tag + stream << uint8(mTag); + + switch (mTag) + { + case TLV::TAG_NULL: + case TLV::TAG_TERMINATOR: + // Nothing to do here + break; + + default: + { + // Write length (decide if as a 8-bit or 16-bit value) + if (mValue.size() >= 0xff) + { + stream << uint8(0xff); + stream << uint16(mValue.size()); + } + else + { + stream << uint8(mValue.size()); + } + + // Write value + stream.Write(mValue); + } + } + + return bytes; +} + +TLV::Tag TLV::GetTag() const +{ + return mTag; +} + +const std::vector& TLV::GetValue() const +{ + return mValue; +} + +void TLV::SetTag(Tag tag) +{ + mTag = tag; +} + +void TLV::SetValue(const std::span& value) +{ + // Can only write max 16-bit lengths into TLV + cemu_assert(value.size() < 0x10000); + + mValue.assign(value.begin(), value.end()); +} diff --git a/src/Cafe/OS/libs/nfc/TLV.h b/src/Cafe/OS/libs/nfc/TLV.h new file mode 100644 index 00000000..f582128f --- /dev/null +++ b/src/Cafe/OS/libs/nfc/TLV.h @@ -0,0 +1,37 @@ +#pragma once + +#include +#include +#include + +class TLV +{ +public: + enum Tag + { + TAG_NULL = 0x00, + TAG_LOCK_CTRL = 0x01, + TAG_MEM_CTRL = 0x02, + TAG_NDEF = 0x03, + TAG_PROPRIETARY = 0xFD, + TAG_TERMINATOR = 0xFE, + }; + +public: + TLV(); + TLV(Tag tag, std::vector value); + virtual ~TLV(); + + static std::vector FromBytes(const std::span& data); + std::vector ToBytes() const; + + Tag GetTag() const; + const std::vector& GetValue() const; + + void SetTag(Tag tag); + void SetValue(const std::span& value); + +private: + Tag mTag; + std::vector mValue; +}; diff --git a/src/Cafe/OS/libs/nfc/TagV0.cpp b/src/Cafe/OS/libs/nfc/TagV0.cpp new file mode 100644 index 00000000..41b5c7a0 --- /dev/null +++ b/src/Cafe/OS/libs/nfc/TagV0.cpp @@ -0,0 +1,301 @@ +#include "TagV0.h" +#include "TLV.h" + +#include + +namespace +{ + +constexpr std::size_t kTagSize = 512u; +constexpr std::size_t kMaxBlockCount = kTagSize / sizeof(TagV0::Block); + +constexpr uint8 kLockbyteBlock0 = 0xe; +constexpr uint8 kLockbytesStart0 = 0x0; +constexpr uint8 kLockbytesEnd0 = 0x2; +constexpr uint8 kLockbyteBlock1 = 0xf; +constexpr uint8 kLockbytesStart1 = 0x2; +constexpr uint8 kLockbytesEnd1 = 0x8; + +constexpr uint8 kNDEFMagicNumber = 0xe1; + +// These blocks are not part of the locked area +constexpr bool IsBlockLockedOrReserved(uint8 blockIdx) +{ + // Block 0 is the UID + if (blockIdx == 0x0) + { + return true; + } + + // Block 0xd is reserved + if (blockIdx == 0xd) + { + return true; + } + + // Block 0xe and 0xf contains lock / reserved bytes + if (blockIdx == 0xe || blockIdx == 0xf) + { + return true; + } + + return false; +} + +} // namespace + +TagV0::TagV0() +{ +} + +TagV0::~TagV0() +{ +} + +std::shared_ptr TagV0::FromBytes(const std::span& data) +{ + // Version 0 tags need at least 512 bytes + if (data.size() != kTagSize) + { + cemuLog_log(LogType::Force, "Error: Version 0 tags should be {} bytes in size", kTagSize); + return {}; + } + + std::shared_ptr tag = std::make_shared(); + + // Parse the locked area before continuing + if (!tag->ParseLockedArea(data)) + { + cemuLog_log(LogType::Force, "Error: Failed to parse locked area"); + return {}; + } + + // Now that the locked area is known, parse the data area + std::vector dataArea; + if (!tag->ParseDataArea(data, dataArea)) + { + cemuLog_log(LogType::Force, "Error: Failed to parse data area"); + return {}; + } + + // The first few bytes in the dataArea make up the capability container + std::copy_n(dataArea.begin(), tag->mCapabilityContainer.size(), std::as_writable_bytes(std::span(tag->mCapabilityContainer)).begin()); + if (!tag->ValidateCapabilityContainer()) + { + cemuLog_log(LogType::Force, "Error: Failed to validate capability container"); + return {}; + } + + // The rest of the dataArea contains the TLVs + tag->mTLVs = TLV::FromBytes(std::span(dataArea).subspan(tag->mCapabilityContainer.size())); + if (tag->mTLVs.empty()) + { + cemuLog_log(LogType::Force, "Error: Tag contains no TLVs"); + return {}; + } + + // Look for the NDEF tlv + tag->mNdefTlvIdx = static_cast(-1); + for (std::size_t i = 0; i < tag->mTLVs.size(); i++) + { + if (tag->mTLVs[i].GetTag() == TLV::TAG_NDEF) + { + tag->mNdefTlvIdx = i; + break; + } + } + + if (tag->mNdefTlvIdx == static_cast(-1)) + { + cemuLog_log(LogType::Force, "Error: Tag contains no NDEF TLV"); + return {}; + } + + // Append locked data + for (const auto& [key, value] : tag->mLockedBlocks) + { + tag->mLockedArea.insert(tag->mLockedArea.end(), value.begin(), value.end()); + } + + return tag; +} + +std::vector TagV0::ToBytes() const +{ + std::vector bytes(kTagSize); + + // Insert locked or reserved blocks + for (const auto& [key, value] : mLockedOrReservedBlocks) + { + std::copy(value.begin(), value.end(), bytes.begin() + key * sizeof(Block)); + } + + // Insert locked area + auto lockedDataIterator = mLockedArea.begin(); + for (const auto& [key, value] : mLockedBlocks) + { + std::copy_n(lockedDataIterator, sizeof(Block), bytes.begin() + key * sizeof(Block)); + lockedDataIterator += sizeof(Block); + } + + // Pack the dataArea into a linear buffer + std::vector dataArea; + const auto ccBytes = std::as_bytes(std::span(mCapabilityContainer)); + dataArea.insert(dataArea.end(), ccBytes.begin(), ccBytes.end()); + for (const TLV& tlv : mTLVs) + { + const auto tlvBytes = tlv.ToBytes(); + dataArea.insert(dataArea.end(), tlvBytes.begin(), tlvBytes.end()); + } + + // Make sure the dataArea is block size aligned + dataArea.resize((dataArea.size() + (sizeof(Block)-1)) & ~(sizeof(Block)-1)); + + // The rest will be the data area + auto dataIterator = dataArea.begin(); + for (uint8 currentBlock = 0; currentBlock < kMaxBlockCount; currentBlock++) + { + // All blocks which aren't locked make up the dataArea + if (!IsBlockLocked(currentBlock)) + { + std::copy_n(dataIterator, sizeof(Block), bytes.begin() + currentBlock * sizeof(Block)); + dataIterator += sizeof(Block); + } + } + + return bytes; +} + +const TagV0::Block& TagV0::GetUIDBlock() const +{ + return mLockedOrReservedBlocks.at(0); +} + +const std::vector& TagV0::GetNDEFData() const +{ + return mTLVs[mNdefTlvIdx].GetValue(); +} + +const std::vector& TagV0::GetLockedArea() const +{ + return mLockedArea; +} + +void TagV0::SetNDEFData(const std::span& data) +{ + // Update the ndef value + mTLVs[mNdefTlvIdx].SetValue(data); +} + +bool TagV0::ParseLockedArea(const std::span& data) +{ + uint8 currentBlock = 0; + + // Start by parsing the first set of lock bytes + for (uint8 i = kLockbytesStart0; i < kLockbytesEnd0; i++) + { + uint8 lockByte = uint8(data[kLockbyteBlock0 * sizeof(Block) + i]); + + // Iterate over the individual bits in the lock byte + for (uint8 j = 0; j < 8; j++) + { + // Is block locked? + if (lockByte & (1u << j)) + { + Block blk; + std::copy_n(data.begin() + currentBlock * sizeof(Block), sizeof(Block), blk.begin()); + + // The lock bytes themselves are not part of the locked area + if (!IsBlockLockedOrReserved(currentBlock)) + { + mLockedBlocks.emplace(currentBlock, blk); + } + else + { + mLockedOrReservedBlocks.emplace(currentBlock, blk); + } + } + + currentBlock++; + } + } + + // Parse the second set of lock bytes + for (uint8 i = kLockbytesStart1; i < kLockbytesEnd1; i++) { + uint8 lockByte = uint8(data[kLockbyteBlock1 * sizeof(Block) + i]); + + // Iterate over the individual bits in the lock byte + for (uint8 j = 0; j < 8; j++) + { + // Is block locked? + if (lockByte & (1u << j)) + { + Block blk; + std::copy_n(data.begin() + currentBlock * sizeof(Block), sizeof(Block), blk.begin()); + + // The lock bytes themselves are not part of the locked area + if (!IsBlockLockedOrReserved(currentBlock)) + { + mLockedBlocks.emplace(currentBlock, blk); + } + else + { + mLockedOrReservedBlocks.emplace(currentBlock, blk); + } + } + + currentBlock++; + } + } + + return true; +} + +bool TagV0::IsBlockLocked(uint8 blockIdx) const +{ + return mLockedBlocks.contains(blockIdx) || IsBlockLockedOrReserved(blockIdx); +} + +bool TagV0::ParseDataArea(const std::span& data, std::vector& dataArea) +{ + for (uint8 currentBlock = 0; currentBlock < kMaxBlockCount; currentBlock++) + { + // All blocks which aren't locked make up the dataArea + if (!IsBlockLocked(currentBlock)) + { + auto blockOffset = data.begin() + sizeof(Block) * currentBlock; + dataArea.insert(dataArea.end(), blockOffset, blockOffset + sizeof(Block)); + } + } + + return true; +} + +bool TagV0::ValidateCapabilityContainer() +{ + // NDEF Magic Number + uint8 nmn = mCapabilityContainer[0]; + if (nmn != kNDEFMagicNumber) + { + cemuLog_log(LogType::Force, "Error: CC: Invalid NDEF Magic Number"); + return false; + } + + // Version Number + uint8 vno = mCapabilityContainer[1]; + if (vno >> 4 != 1) + { + cemuLog_log(LogType::Force, "Error: CC: Invalid Version Number"); + return false; + } + + // Tag memory size + uint8 tms = mCapabilityContainer[2]; + if (8u * (tms + 1) < kTagSize) + { + cemuLog_log(LogType::Force, "Error: CC: Incomplete tag memory size"); + return false; + } + + return true; +} diff --git a/src/Cafe/OS/libs/nfc/TagV0.h b/src/Cafe/OS/libs/nfc/TagV0.h new file mode 100644 index 00000000..72c321b6 --- /dev/null +++ b/src/Cafe/OS/libs/nfc/TagV0.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include +#include + +#include "TLV.h" + +class TagV0 +{ +public: + using Block = std::array; + +public: + TagV0(); + virtual ~TagV0(); + + static std::shared_ptr FromBytes(const std::span& data); + std::vector ToBytes() const; + + const Block& GetUIDBlock() const; + const std::vector& GetNDEFData() const; + const std::vector& GetLockedArea() const; + + void SetNDEFData(const std::span& data); + +private: + bool ParseLockedArea(const std::span& data); + bool IsBlockLocked(uint8 blockIdx) const; + bool ParseDataArea(const std::span& data, std::vector& dataArea); + bool ValidateCapabilityContainer(); + + std::map mLockedOrReservedBlocks; + std::map mLockedBlocks; + std::array mCapabilityContainer; + std::vector mTLVs; + std::size_t mNdefTlvIdx; + std::vector mLockedArea; +}; diff --git a/src/Cafe/OS/libs/nfc/ndef.cpp b/src/Cafe/OS/libs/nfc/ndef.cpp new file mode 100644 index 00000000..60be5811 --- /dev/null +++ b/src/Cafe/OS/libs/nfc/ndef.cpp @@ -0,0 +1,278 @@ +#include "ndef.h" + +#include + +namespace ndef +{ + + Record::Record() + : mFlags(0), mTNF(NDEF_TNF_EMPTY) + { + } + + Record::~Record() + { + } + + std::optional Record::FromStream(Stream& stream) + { + Record rec; + + // Read record header + uint8 recHdr; + stream >> recHdr; + rec.mFlags = recHdr & ~NDEF_TNF_MASK; + rec.mTNF = static_cast(recHdr & NDEF_TNF_MASK); + + // Type length + uint8 typeLen; + stream >> typeLen; + + // Payload length; + uint32 payloadLen; + if (recHdr & NDEF_SR) + { + uint8 len; + stream >> len; + payloadLen = len; + } + else + { + stream >> payloadLen; + } + + // Some sane limits for the payload size + if (payloadLen > 2 * 1024 * 1024) + { + return {}; + } + + // ID length + uint8 idLen = 0; + if (recHdr & NDEF_IL) + { + stream >> idLen; + } + + // Make sure we didn't read past the end of the stream yet + if (stream.GetError() != Stream::ERROR_OK) + { + return {}; + } + + // Type + rec.mType.resize(typeLen); + stream.Read(rec.mType); + + // ID + rec.mID.resize(idLen); + stream.Read(rec.mID); + + // Payload + rec.mPayload.resize(payloadLen); + stream.Read(rec.mPayload); + + // Make sure we didn't read past the end of the stream again + if (stream.GetError() != Stream::ERROR_OK) + { + return {}; + } + + return rec; + } + + std::vector Record::ToBytes(uint8 flags) const + { + std::vector bytes; + VectorStream stream(bytes, std::endian::big); + + // Combine flags (clear message begin and end flags) + uint8 finalFlags = mFlags & ~(NDEF_MB | NDEF_ME); + finalFlags |= flags; + + // Write flags + tnf + stream << uint8(finalFlags | uint8(mTNF)); + + // Type length + stream << uint8(mType.size()); + + // Payload length + if (IsShort()) + { + stream << uint8(mPayload.size()); + } + else + { + stream << uint32(mPayload.size()); + } + + // ID length + if (mFlags & NDEF_IL) + { + stream << uint8(mID.size()); + } + + // Type + stream.Write(mType); + + // ID + stream.Write(mID); + + // Payload + stream.Write(mPayload); + + return bytes; + } + + Record::TypeNameFormat Record::GetTNF() const + { + return mTNF; + } + + const std::vector& Record::GetID() const + { + return mID; + } + + const std::vector& Record::GetType() const + { + return mType; + } + + const std::vector& Record::GetPayload() const + { + return mPayload; + } + + void Record::SetTNF(TypeNameFormat tnf) + { + mTNF = tnf; + } + + void Record::SetID(const std::span& id) + { + cemu_assert(id.size() < 0x100); + + if (id.size() > 0) + { + mFlags |= NDEF_IL; + } + else + { + mFlags &= ~NDEF_IL; + } + + mID.assign(id.begin(), id.end()); + } + + void Record::SetType(const std::span& type) + { + cemu_assert(type.size() < 0x100); + + mType.assign(type.begin(), type.end()); + } + + void Record::SetPayload(const std::span& payload) + { + // Update short record flag + if (payload.size() < 0xff) + { + mFlags |= NDEF_SR; + } + else + { + mFlags &= ~NDEF_SR; + } + + mPayload.assign(payload.begin(), payload.end()); + } + + bool Record::IsLast() const + { + return mFlags & NDEF_ME; + } + + bool Record::IsShort() const + { + return mFlags & NDEF_SR; + } + + Message::Message() + { + } + + Message::~Message() + { + } + + std::optional Message::FromBytes(const std::span& data) + { + Message msg; + SpanStream stream(data, std::endian::big); + + while (stream.GetRemaining() > 0) + { + std::optional rec = Record::FromStream(stream); + if (!rec) + { + cemuLog_log(LogType::Force, "Warning: Failed to parse NDEF Record #{}." + "Ignoring the remaining {} bytes in NDEF message", msg.mRecords.size(), stream.GetRemaining()); + break; + } + + msg.mRecords.emplace_back(*rec); + + if ((*rec).IsLast() && stream.GetRemaining() > 0) + { + cemuLog_log(LogType::Force, "Warning: Ignoring {} bytes in NDEF message", stream.GetRemaining()); + break; + } + } + + if (msg.mRecords.empty()) + { + return {}; + } + + if (!msg.mRecords.back().IsLast()) + { + cemuLog_log(LogType::Force, "Error: NDEF message missing end record"); + return {}; + } + + return msg; + } + + std::vector Message::ToBytes() const + { + std::vector bytes; + + for (std::size_t i = 0; i < mRecords.size(); i++) + { + uint8 flags = 0; + + // Add message begin flag to first record + if (i == 0) + { + flags |= Record::NDEF_MB; + } + + // Add message end flag to last record + if (i == mRecords.size() - 1) + { + flags |= Record::NDEF_ME; + } + + std::vector recordBytes = mRecords[i].ToBytes(flags); + bytes.insert(bytes.end(), recordBytes.begin(), recordBytes.end()); + } + + return bytes; + } + + void Message::append(const Record& r) + { + mRecords.push_back(r); + } + +} // namespace ndef diff --git a/src/Cafe/OS/libs/nfc/ndef.h b/src/Cafe/OS/libs/nfc/ndef.h new file mode 100644 index 00000000..398feb54 --- /dev/null +++ b/src/Cafe/OS/libs/nfc/ndef.h @@ -0,0 +1,88 @@ +#pragma once + +#include +#include +#include + +#include "stream.h" + +namespace ndef +{ + + class Record + { + public: + enum HeaderFlag + { + NDEF_IL = 0x08, + NDEF_SR = 0x10, + NDEF_CF = 0x20, + NDEF_ME = 0x40, + NDEF_MB = 0x80, + NDEF_TNF_MASK = 0x07, + }; + + enum TypeNameFormat + { + NDEF_TNF_EMPTY = 0, + NDEF_TNF_WKT = 1, + NDEF_TNF_MEDIA = 2, + NDEF_TNF_URI = 3, + NDEF_TNF_EXT = 4, + NDEF_TNF_UNKNOWN = 5, + NDEF_TNF_UNCHANGED = 6, + NDEF_TNF_RESERVED = 7, + }; + + public: + Record(); + virtual ~Record(); + + static std::optional FromStream(Stream& stream); + std::vector ToBytes(uint8 flags = 0) const; + + TypeNameFormat GetTNF() const; + const std::vector& GetID() const; + const std::vector& GetType() const; + const std::vector& GetPayload() const; + + void SetTNF(TypeNameFormat tnf); + void SetID(const std::span& id); + void SetType(const std::span& type); + void SetPayload(const std::span& payload); + + bool IsLast() const; + bool IsShort() const; + + private: + uint8 mFlags; + TypeNameFormat mTNF; + std::vector mID; + std::vector mType; + std::vector mPayload; + }; + + class Message + { + public: + Message(); + virtual ~Message(); + + static std::optional FromBytes(const std::span& data); + std::vector ToBytes() const; + + Record& operator[](int i) { return mRecords[i]; } + const Record& operator[](int i) const { return mRecords[i]; } + + void append(const Record& r); + + auto begin() { return mRecords.begin(); } + auto end() { return mRecords.end(); } + auto begin() const { return mRecords.begin(); } + auto end() const { return mRecords.end(); } + + private: + std::vector mRecords; + }; + +} // namespace ndef diff --git a/src/Cafe/OS/libs/nfc/nfc.cpp b/src/Cafe/OS/libs/nfc/nfc.cpp new file mode 100644 index 00000000..fcb1d8d0 --- /dev/null +++ b/src/Cafe/OS/libs/nfc/nfc.cpp @@ -0,0 +1,690 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/OS/RPL/rpl.h" +#include "Cafe/OS/libs/nfc/nfc.h" +#include "Cafe/OS/libs/nn_nfp/nn_nfp.h" +#include "Common/FileStream.h" + +#include "TagV0.h" +#include "ndef.h" + +#define NFC_MODE_INVALID -1 +#define NFC_MODE_IDLE 0 +#define NFC_MODE_ACTIVE 1 + +#define NFC_STATE_UNINITIALIZED 0x0 +#define NFC_STATE_INITIALIZED 0x1 +#define NFC_STATE_IDLE 0x2 +#define NFC_STATE_READ 0x3 +#define NFC_STATE_WRITE 0x4 +#define NFC_STATE_ABORT 0x5 +#define NFC_STATE_FORMAT 0x6 +#define NFC_STATE_SET_READ_ONLY 0x7 +#define NFC_STATE_TAG_PRESENT 0x8 +#define NFC_STATE_DETECT 0x9 +#define NFC_STATE_SEND_RAW_DATA 0xA + +#define NFC_STATUS_COMMAND_COMPLETE 0x1 +#define NFC_STATUS_READY 0x2 +#define NFC_STATUS_HAS_TAG 0x4 + +namespace nfc +{ + struct NFCContext + { + bool isInitialized; + uint32 state; + sint32 mode; + bool hasTag; + + uint32 nfcStatus; + std::chrono::time_point touchTime; + std::chrono::time_point discoveryTimeout; + struct { + NFCUid uid; + NFCUid mask; + } filter; + + MPTR tagDetectCallback; + void* tagDetectContext; + MPTR abortCallback; + void* abortContext; + MPTR rawCallback; + void* rawContext; + MPTR readCallback; + void* readContext; + MPTR writeCallback; + void* writeContext; + MPTR getTagInfoCallback; + + SysAllocator tagInfo; + + fs::path tagPath; + std::shared_ptr tag; + + ndef::Message writeMessage; + }; + NFCContext gNFCContexts[2]; + + sint32 NFCInit(uint32 chan) + { + return NFCInitEx(chan, 0); + } + + void __NFCClearContext(NFCContext* context) + { + context->isInitialized = false; + context->state = NFC_STATE_UNINITIALIZED; + context->mode = NFC_MODE_IDLE; + context->hasTag = false; + + context->nfcStatus = NFC_STATUS_READY; + context->discoveryTimeout = {}; + + context->tagDetectCallback = MPTR_NULL; + context->tagDetectContext = nullptr; + context->abortCallback = MPTR_NULL; + context->abortContext = nullptr; + context->rawCallback = MPTR_NULL; + context->rawContext = nullptr; + context->readCallback = MPTR_NULL; + context->readContext = nullptr; + context->writeCallback = MPTR_NULL; + context->writeContext = nullptr; + + context->tagPath = ""; + context->tag = {}; + } + + sint32 NFCInitEx(uint32 chan, uint32 powerMode) + { + cemu_assert(chan < 2); + + NFCContext* ctx = &gNFCContexts[chan]; + + __NFCClearContext(ctx); + ctx->isInitialized = true; + ctx->state = NFC_STATE_INITIALIZED; + + return NFC_RESULT_SUCCESS; + } + + sint32 NFCShutdown(uint32 chan) + { + cemu_assert(chan < 2); + + NFCContext* ctx = &gNFCContexts[chan]; + + __NFCClearContext(ctx); + + return NFC_RESULT_SUCCESS; + } + + bool NFCIsInit(uint32 chan) + { + cemu_assert(chan < 2); + + return gNFCContexts[chan].isInitialized; + } + + bool __NFCCompareUid(NFCUid* uid, NFCUid* filterUid, NFCUid* filterMask) + { + for (int i = 0; i < sizeof(uid->uid); i++) + { + if ((uid->uid[i] & filterMask->uid[i]) != filterUid->uid[i]) + { + return false; + } + } + + return true; + } + + void __NFCHandleRead(uint32 chan) + { + NFCContext* ctx = &gNFCContexts[chan]; + + ctx->state = NFC_STATE_IDLE; + + sint32 result; + StackAllocator uid; + bool readOnly = false; + uint32 dataSize = 0; + StackAllocator data; + uint32 lockedDataSize = 0; + StackAllocator lockedData; + + if (ctx->tag) + { + // Compare UID + memcpy(uid.GetPointer(), ctx->tag->GetUIDBlock().data(), sizeof(NFCUid)); + if (__NFCCompareUid(uid.GetPointer(), &ctx->filter.uid, &ctx->filter.mask)) + { + // Try to parse ndef message + auto ndefMsg = ndef::Message::FromBytes(ctx->tag->GetNDEFData()); + if (ndefMsg) + { + // Look for the unknown TNF which contains the data we care about + for (const auto& rec : *ndefMsg) + { + if (rec.GetTNF() == ndef::Record::NDEF_TNF_UNKNOWN) + { + dataSize = rec.GetPayload().size(); + cemu_assert(dataSize < 0x200); + memcpy(data.GetPointer(), rec.GetPayload().data(), dataSize); + break; + } + } + + if (dataSize) + { + // Get locked data + lockedDataSize = ctx->tag->GetLockedArea().size(); + memcpy(lockedData.GetPointer(), ctx->tag->GetLockedArea().data(), lockedDataSize); + + result = NFC_RESULT_SUCCESS; + } + else + { + result = NFC_MAKE_RESULT(NFC_RESULT_BASE_TAG_PARSE, NFC_RESULT_INVALID_TAG); + } + } + else + { + result = NFC_MAKE_RESULT(NFC_RESULT_BASE_TAG_PARSE, NFC_RESULT_INVALID_TAG); + } + } + else + { + result = NFC_MAKE_RESULT(NFC_RESULT_BASE_READ, NFC_RESULT_UID_MISMATCH); + } + } + else + { + result = NFC_MAKE_RESULT(NFC_RESULT_BASE_READ, NFC_RESULT_NO_TAG); + } + + PPCCoreCallback(ctx->readCallback, chan, result, uid.GetPointer(), readOnly, dataSize, data.GetPointer(), lockedDataSize, lockedData.GetPointer(), ctx->readContext); + } + + void __NFCHandleWrite(uint32 chan) + { + NFCContext* ctx = &gNFCContexts[chan]; + + ctx->state = NFC_STATE_IDLE; + + sint32 result; + + if (ctx->tag) + { + NFCUid uid; + memcpy(&uid, ctx->tag->GetUIDBlock().data(), sizeof(NFCUid)); + if (__NFCCompareUid(&uid, &ctx->filter.uid, &ctx->filter.mask)) + { + // Update tag NDEF data + ctx->tag->SetNDEFData(ctx->writeMessage.ToBytes()); + + // open file for writing + FileStream* fs = FileStream::openFile2(ctx->tagPath, true); + if (!fs) + { + result = NFC_MAKE_RESULT(NFC_RESULT_BASE_WRITE, 0x22); + } + else + { + auto tagBytes = ctx->tag->ToBytes(); + fs->writeData(tagBytes.data(), tagBytes.size()); + delete fs; + + result = NFC_RESULT_SUCCESS; + } + } + else + { + result = NFC_MAKE_RESULT(NFC_RESULT_BASE_WRITE, NFC_RESULT_UID_MISMATCH); + } + } + else + { + result = NFC_MAKE_RESULT(NFC_RESULT_BASE_WRITE, NFC_RESULT_NO_TAG); + } + + PPCCoreCallback(ctx->writeCallback, chan, result, ctx->writeContext); + } + + void __NFCHandleAbort(uint32 chan) + { + NFCContext* ctx = &gNFCContexts[chan]; + + ctx->state = NFC_STATE_IDLE; + + PPCCoreCallback(ctx->abortCallback, chan, 0, ctx->abortContext); + } + + void __NFCHandleRaw(uint32 chan) + { + NFCContext* ctx = &gNFCContexts[chan]; + + ctx->state = NFC_STATE_IDLE; + + sint32 result; + if (ctx->nfcStatus & NFC_STATUS_HAS_TAG) + { + result = NFC_RESULT_SUCCESS; + } + else + { + result = NFC_MAKE_RESULT(NFC_RESULT_BASE_SEND_RAW_DATA, NFC_RESULT_NO_TAG); + } + + // We don't actually send any commands/responses + uint32 responseSize = 0; + void* responseData = nullptr; + + PPCCoreCallback(ctx->rawCallback, chan, result, responseSize, responseData, ctx->rawContext); + } + + bool __NFCShouldHandleState(NFCContext* ctx) + { + // Always handle abort + if (ctx->state == NFC_STATE_ABORT) + { + return true; + } + + // Do we have a tag? + if (ctx->nfcStatus & NFC_STATUS_HAS_TAG) + { + return true; + } + + // Did the timeout expire? + if (ctx->discoveryTimeout < std::chrono::system_clock::now()) + { + return true; + } + + return false; + } + + void NFCProc(uint32 chan) + { + cemu_assert(chan < 2); + + NFCContext* ctx = &gNFCContexts[chan]; + + if (!ctx->isInitialized) + { + return; + } + + if (ctx->state == NFC_STATE_INITIALIZED) + { + ctx->state = NFC_STATE_IDLE; + } + + // Check if the detect callback should be called + if (ctx->nfcStatus & NFC_STATUS_HAS_TAG) + { + if (!ctx->hasTag && ctx->state > NFC_STATE_IDLE && ctx->state != NFC_STATE_ABORT) + { + if (ctx->tagDetectCallback) + { + PPCCoreCallback(ctx->tagDetectCallback, chan, true, ctx->tagDetectContext); + } + + ctx->hasTag = true; + } + + // Check if the tag should be removed again + if (ctx->touchTime + std::chrono::seconds(2) < std::chrono::system_clock::now()) + { + ctx->nfcStatus &= ~NFC_STATUS_HAS_TAG; + ctx->tag = {}; + ctx->tagPath = ""; + } + } + else + { + if (ctx->hasTag && ctx->state == NFC_STATE_IDLE) + { + if (ctx->tagDetectCallback) + { + PPCCoreCallback(ctx->tagDetectCallback, chan, false, ctx->tagDetectContext); + } + + ctx->hasTag = false; + } + } + + if (__NFCShouldHandleState(ctx)) + { + switch (ctx->state) + { + case NFC_STATE_READ: + __NFCHandleRead(chan); + break; + case NFC_STATE_WRITE: + __NFCHandleWrite(chan); + break; + case NFC_STATE_ABORT: + __NFCHandleAbort(chan); + break; + case NFC_STATE_SEND_RAW_DATA: + __NFCHandleRaw(chan); + break; + default: + break; + } + + // Return back to idle mode + ctx->mode = NFC_MODE_IDLE; + } + } + + sint32 NFCGetMode(uint32 chan) + { + cemu_assert(chan < 2); + + NFCContext* ctx = &gNFCContexts[chan]; + + if (!NFCIsInit(chan) || ctx->state == NFC_STATE_UNINITIALIZED) + { + return NFC_MODE_INVALID; + } + + return ctx->mode; + } + + sint32 NFCSetMode(uint32 chan, sint32 mode) + { + cemu_assert(chan < 2); + + NFCContext* ctx = &gNFCContexts[chan]; + + if (!NFCIsInit(chan)) + { + return NFC_MAKE_RESULT(NFC_RESULT_BASE_SET_MODE, NFC_RESULT_UNINITIALIZED); + } + + if (ctx->state == NFC_STATE_UNINITIALIZED) + { + return NFC_MAKE_RESULT(NFC_RESULT_BASE_SET_MODE, NFC_RESULT_INVALID_STATE); + } + + ctx->mode = mode; + + return NFC_RESULT_SUCCESS; + } + + void NFCSetTagDetectCallback(uint32 chan, MPTR callback, void* context) + { + cemu_assert(chan < 2); + + NFCContext* ctx = &gNFCContexts[chan]; + ctx->tagDetectCallback = callback; + ctx->tagDetectContext = context; + } + + sint32 NFCAbort(uint32 chan, MPTR callback, void* context) + { + cemu_assert(chan < 2); + + NFCContext* ctx = &gNFCContexts[chan]; + + if (!NFCIsInit(chan)) + { + return NFC_MAKE_RESULT(NFC_RESULT_BASE_ABORT, NFC_RESULT_UNINITIALIZED); + } + + if (ctx->state <= NFC_STATE_IDLE) + { + return NFC_MAKE_RESULT(NFC_RESULT_BASE_ABORT, NFC_RESULT_INVALID_STATE); + } + + ctx->state = NFC_STATE_ABORT; + ctx->abortCallback = callback; + ctx->abortContext = context; + + return NFC_RESULT_SUCCESS; + } + + sint32 __NFCConvertGetTagInfoResult(sint32 result) + { + if (result == NFC_MAKE_RESULT(NFC_RESULT_BASE_SEND_RAW_DATA, NFC_RESULT_NO_TAG)) + { + return NFC_MAKE_RESULT(NFC_RESULT_BASE_GET_TAG_INFO, NFC_RESULT_TAG_INFO_TIMEOUT); + } + + // TODO convert the rest of the results + return result; + } + + void __NFCGetTagInfoCallback(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(chan, 0); + ppcDefineParamS32(error, 1); + ppcDefineParamU32(responseSize, 2); + ppcDefineParamPtr(responseData, void, 3); + ppcDefineParamPtr(context, void, 4); + + NFCContext* ctx = &gNFCContexts[chan]; + + error = __NFCConvertGetTagInfoResult(error); + if (error == 0 && ctx->tag) + { + // this is usually parsed from response data + ctx->tagInfo->uidSize = sizeof(NFCUid); + memcpy(ctx->tagInfo->uid, ctx->tag->GetUIDBlock().data(), ctx->tagInfo->uidSize); + ctx->tagInfo->technology = NFC_TECHNOLOGY_A; + ctx->tagInfo->protocol = NFC_PROTOCOL_T1T; + } + + PPCCoreCallback(ctx->getTagInfoCallback, chan, error, ctx->tagInfo.GetPtr(), context); + osLib_returnFromFunction(hCPU, 0); + } + + sint32 NFCGetTagInfo(uint32 chan, uint32 discoveryTimeout, MPTR callback, void* context) + { + cemu_assert(chan < 2); + + // Forward this request to nn_nfp, if the title initialized it + // TODO integrate nn_nfp/ntag/nfc + if (nnNfp_isInitialized()) + { + return nn::nfp::NFCGetTagInfo(chan, discoveryTimeout, callback, context); + } + + NFCContext* ctx = &gNFCContexts[chan]; + + ctx->getTagInfoCallback = callback; + + sint32 result = NFCSendRawData(chan, true, discoveryTimeout, 1000U, 0, 0, nullptr, RPLLoader_MakePPCCallable(__NFCGetTagInfoCallback), context); + return __NFCConvertGetTagInfoResult(result); + } + + sint32 NFCSendRawData(uint32 chan, bool startDiscovery, uint32 discoveryTimeout, uint32 commandTimeout, uint32 commandSize, uint32 responseSize, void* commandData, MPTR callback, void* context) + { + cemu_assert(chan < 2); + + NFCContext* ctx = &gNFCContexts[chan]; + + if (!NFCIsInit(chan)) + { + return NFC_MAKE_RESULT(NFC_RESULT_BASE_SEND_RAW_DATA, NFC_RESULT_UNINITIALIZED); + } + + // Only allow discovery + if (!startDiscovery) + { + return NFC_MAKE_RESULT(NFC_RESULT_BASE_SEND_RAW_DATA, NFC_RESULT_INVALID_MODE); + } + + if (NFCGetMode(chan) == NFC_MODE_ACTIVE && NFCSetMode(chan, NFC_MODE_IDLE) < 0) + { + return NFC_MAKE_RESULT(NFC_RESULT_BASE_SEND_RAW_DATA, NFC_RESULT_INVALID_MODE); + } + + if (ctx->state != NFC_STATE_IDLE) + { + return NFC_MAKE_RESULT(NFC_RESULT_BASE_SEND_RAW_DATA, NFC_RESULT_INVALID_STATE); + } + + ctx->state = NFC_STATE_SEND_RAW_DATA; + ctx->rawCallback = callback; + ctx->rawContext = context; + + // If the discoveryTimeout is 0, no timeout + if (discoveryTimeout == 0) + { + ctx->discoveryTimeout = std::chrono::time_point::max(); + } + else + { + ctx->discoveryTimeout = std::chrono::system_clock::now() + std::chrono::milliseconds(discoveryTimeout); + } + + return NFC_RESULT_SUCCESS; + } + + sint32 NFCRead(uint32 chan, uint32 discoveryTimeout, NFCUid* uid, NFCUid* uidMask, MPTR callback, void* context) + { + cemu_assert(chan < 2); + + NFCContext* ctx = &gNFCContexts[chan]; + + if (!NFCIsInit(chan)) + { + return NFC_MAKE_RESULT(NFC_RESULT_BASE_READ, NFC_RESULT_UNINITIALIZED); + } + + if (NFCGetMode(chan) == NFC_MODE_ACTIVE && NFCSetMode(chan, NFC_MODE_IDLE) < 0) + { + return NFC_MAKE_RESULT(NFC_RESULT_BASE_READ, NFC_RESULT_INVALID_MODE); + } + + if (ctx->state != NFC_STATE_IDLE) + { + return NFC_MAKE_RESULT(NFC_RESULT_BASE_READ, NFC_RESULT_INVALID_STATE); + } + + ctx->state = NFC_STATE_READ; + ctx->readCallback = callback; + ctx->readContext = context; + + // If the discoveryTimeout is 0, no timeout + if (discoveryTimeout == 0) + { + ctx->discoveryTimeout = std::chrono::time_point::max(); + } + else + { + ctx->discoveryTimeout = std::chrono::system_clock::now() + std::chrono::milliseconds(discoveryTimeout); + } + + memcpy(&ctx->filter.uid, uid, sizeof(*uid)); + memcpy(&ctx->filter.mask, uidMask, sizeof(*uidMask)); + + return NFC_RESULT_SUCCESS; + } + + sint32 NFCWrite(uint32 chan, uint32 discoveryTimeout, NFCUid* uid, NFCUid* uidMask, uint32 size, void* data, MPTR callback, void* context) + { + cemu_assert(chan < 2); + + NFCContext* ctx = &gNFCContexts[chan]; + + if (!NFCIsInit(chan)) + { + return NFC_MAKE_RESULT(NFC_RESULT_BASE_WRITE, NFC_RESULT_UNINITIALIZED); + } + + if (NFCGetMode(chan) == NFC_MODE_ACTIVE && NFCSetMode(chan, NFC_MODE_IDLE) < 0) + { + return NFC_MAKE_RESULT(NFC_RESULT_BASE_WRITE, NFC_RESULT_INVALID_MODE); + } + + if (ctx->state != NFC_STATE_IDLE) + { + return NFC_MAKE_RESULT(NFC_RESULT_BASE_WRITE, NFC_RESULT_INVALID_STATE); + } + + // Create unknown record which contains the rw area + ndef::Record rec; + rec.SetTNF(ndef::Record::NDEF_TNF_UNKNOWN); + rec.SetPayload(std::span(reinterpret_cast(data), size)); + + // Create ndef message which contains the record + ndef::Message msg; + msg.append(rec); + ctx->writeMessage = msg; + + ctx->state = NFC_STATE_WRITE; + ctx->writeCallback = callback; + ctx->writeContext = context; + + // If the discoveryTimeout is 0, no timeout + if (discoveryTimeout == 0) + { + ctx->discoveryTimeout = std::chrono::time_point::max(); + } + else + { + ctx->discoveryTimeout = std::chrono::system_clock::now() + std::chrono::milliseconds(discoveryTimeout); + } + + memcpy(&ctx->filter.uid, uid, sizeof(*uid)); + memcpy(&ctx->filter.mask, uidMask, sizeof(*uidMask)); + + return NFC_RESULT_SUCCESS; + } + + void Initialize() + { + cafeExportRegister("nfc", NFCInit, LogType::NFC); + cafeExportRegister("nfc", NFCInitEx, LogType::NFC); + cafeExportRegister("nfc", NFCShutdown, LogType::NFC); + cafeExportRegister("nfc", NFCIsInit, LogType::NFC); + cafeExportRegister("nfc", NFCProc, LogType::NFC); + cafeExportRegister("nfc", NFCGetMode, LogType::NFC); + cafeExportRegister("nfc", NFCSetMode, LogType::NFC); + cafeExportRegister("nfc", NFCSetTagDetectCallback, LogType::NFC); + cafeExportRegister("nfc", NFCGetTagInfo, LogType::NFC); + cafeExportRegister("nfc", NFCSendRawData, LogType::NFC); + cafeExportRegister("nfc", NFCAbort, LogType::NFC); + cafeExportRegister("nfc", NFCRead, LogType::NFC); + cafeExportRegister("nfc", NFCWrite, LogType::NFC); + } + + bool TouchTagFromFile(const fs::path& filePath, uint32* nfcError) + { + // Forward this request to nn_nfp, if the title initialized it + // TODO integrate nn_nfp/ntag/nfc + if (nnNfp_isInitialized()) + { + return nnNfp_touchNfcTagFromFile(filePath, nfcError); + } + + NFCContext* ctx = &gNFCContexts[0]; + + auto nfcData = FileStream::LoadIntoMemory(filePath); + if (!nfcData) + { + *nfcError = NFC_TOUCH_TAG_ERROR_NO_ACCESS; + return false; + } + + ctx->tag = TagV0::FromBytes(std::as_bytes(std::span(nfcData->data(), nfcData->size()))); + if (!ctx->tag) + { + *nfcError = NFC_TOUCH_TAG_ERROR_INVALID_FILE_FORMAT; + return false; + } + + ctx->nfcStatus |= NFC_STATUS_HAS_TAG; + ctx->tagPath = filePath; + ctx->touchTime = std::chrono::system_clock::now(); + + *nfcError = NFC_TOUCH_TAG_ERROR_NONE; + return true; + } +} diff --git a/src/Cafe/OS/libs/nfc/nfc.h b/src/Cafe/OS/libs/nfc/nfc.h new file mode 100644 index 00000000..ea959cd1 --- /dev/null +++ b/src/Cafe/OS/libs/nfc/nfc.h @@ -0,0 +1,92 @@ +#pragma once + +// CEMU NFC error codes +#define NFC_TOUCH_TAG_ERROR_NONE (0) +#define NFC_TOUCH_TAG_ERROR_NO_ACCESS (1) +#define NFC_TOUCH_TAG_ERROR_INVALID_FILE_FORMAT (2) + +// NFC result base +#define NFC_RESULT_BASE_INIT (-0x100) +#define NFC_RESULT_BASE_READ (-0x200) +#define NFC_RESULT_BASE_WRITE (-0x300) +#define NFC_RESULT_BASE_FORMAT (-0x400) +#define NFC_RESULT_BASE_SET_READ_ONLY (-0x500) +#define NFC_RESULT_BASE_IS_TAG_PRESENT (-0x600) +#define NFC_RESULT_BASE_ABORT (-0x700) +#define NFC_RESULT_BASE_SHUTDOWN (-0x800) +#define NFC_RESULT_BASE_DETECT (-0x900) +#define NFC_RESULT_BASE_SEND_RAW_DATA (-0xA00) +#define NFC_RESULT_BASE_SET_MODE (-0xB00) +#define NFC_RESULT_BASE_TAG_PARSE (-0xC00) +#define NFC_RESULT_BASE_GET_TAG_INFO (-0x1400) + +// NFC result status +#define NFC_RESULT_NO_TAG (0x01) +#define NFC_RESULT_INVALID_TAG (0x02) +#define NFC_RESULT_UID_MISMATCH (0x0A) +#define NFC_RESULT_UNINITIALIZED (0x20) +#define NFC_RESULT_INVALID_STATE (0x21) +#define NFC_RESULT_INVALID_MODE (0x24) +#define NFC_RESULT_TAG_INFO_TIMEOUT (0x7A) + +// Result macros +#define NFC_RESULT_SUCCESS (0) +#define NFC_RESULT_BASE_MASK (0xFFFFFF00) +#define NFC_RESULT_MASK (0x000000FF) +#define NFC_MAKE_RESULT(base, result) ((base) | (result)) + +#define NFC_PROTOCOL_T1T 0x1 +#define NFC_PROTOCOL_T2T 0x2 + +#define NFC_TECHNOLOGY_A 0x0 +#define NFC_TECHNOLOGY_B 0x1 +#define NFC_TECHNOLOGY_F 0x2 + +namespace nfc +{ + struct NFCUid + { + /* +0x00 */ uint8 uid[7]; + }; + static_assert(sizeof(NFCUid) == 0x7); + + struct NFCTagInfo + { + /* +0x00 */ uint8 uidSize; + /* +0x01 */ uint8 uid[10]; + /* +0x0B */ uint8 technology; + /* +0x0C */ uint8 protocol; + /* +0x0D */ uint8 reserved[0x20]; + }; + static_assert(sizeof(NFCTagInfo) == 0x2D); + + sint32 NFCInit(uint32 chan); + + sint32 NFCInitEx(uint32 chan, uint32 powerMode); + + sint32 NFCShutdown(uint32 chan); + + bool NFCIsInit(uint32 chan); + + void NFCProc(uint32 chan); + + sint32 NFCGetMode(uint32 chan); + + sint32 NFCSetMode(uint32 chan, sint32 mode); + + void NFCSetTagDetectCallback(uint32 chan, MPTR callback, void* context); + + sint32 NFCGetTagInfo(uint32 chan, uint32 discoveryTimeout, MPTR callback, void* context); + + sint32 NFCSendRawData(uint32 chan, bool startDiscovery, uint32 discoveryTimeout, uint32 commandTimeout, uint32 commandSize, uint32 responseSize, void* commandData, MPTR callback, void* context); + + sint32 NFCAbort(uint32 chan, MPTR callback, void* context); + + sint32 NFCRead(uint32 chan, uint32 discoveryTimeout, NFCUid* uid, NFCUid* uidMask, MPTR callback, void* context); + + sint32 NFCWrite(uint32 chan, uint32 discoveryTimeout, NFCUid* uid, NFCUid* uidMask, uint32 size, void* data, MPTR callback, void* context); + + void Initialize(); + + bool TouchTagFromFile(const fs::path& filePath, uint32* nfcError); +} diff --git a/src/Cafe/OS/libs/nfc/stream.cpp b/src/Cafe/OS/libs/nfc/stream.cpp new file mode 100644 index 00000000..dd6de7ad --- /dev/null +++ b/src/Cafe/OS/libs/nfc/stream.cpp @@ -0,0 +1,201 @@ +#include "stream.h" + +#include + +Stream::Stream(std::endian endianness) + : mError(ERROR_OK), mEndianness(endianness) +{ +} + +Stream::~Stream() +{ +} + +Stream::Error Stream::GetError() const +{ + return mError; +} + +void Stream::SetEndianness(std::endian endianness) +{ + mEndianness = endianness; +} + +std::endian Stream::GetEndianness() const +{ + return mEndianness; +} + +Stream& Stream::operator>>(bool& val) +{ + uint8 i; + *this >> i; + val = !!i; + + return *this; +} + +Stream& Stream::operator>>(float& val) +{ + uint32 i; + *this >> i; + val = std::bit_cast(i); + + return *this; +} + +Stream& Stream::operator>>(double& val) +{ + uint64 i; + *this >> i; + val = std::bit_cast(i); + + return *this; +} + +Stream& Stream::operator<<(bool val) +{ + uint8 i = val; + *this >> i; + + return *this; +} + +Stream& Stream::operator<<(float val) +{ + uint32 i = std::bit_cast(val); + *this >> i; + + return *this; +} + +Stream& Stream::operator<<(double val) +{ + uint64 i = std::bit_cast(val); + *this >> i; + + return *this; +} + +void Stream::SetError(Error error) +{ + mError = error; +} + +bool Stream::NeedsSwap() +{ + return mEndianness != std::endian::native; +} + +VectorStream::VectorStream(std::vector& vector, std::endian endianness) + : Stream(endianness), mVector(vector), mPosition(0) +{ +} + +VectorStream::~VectorStream() +{ +} + +std::size_t VectorStream::Read(const std::span& data) +{ + if (data.size() > GetRemaining()) + { + SetError(ERROR_READ_FAILED); + std::fill(data.begin(), data.end(), std::byte(0)); + return 0; + } + + std::copy_n(mVector.get().begin() + mPosition, data.size(), data.begin()); + mPosition += data.size(); + return data.size(); +} + +std::size_t VectorStream::Write(const std::span& data) +{ + // Resize vector if not enough bytes remain + if (mPosition + data.size() > mVector.get().size()) + { + mVector.get().resize(mPosition + data.size()); + } + + std::copy(data.begin(), data.end(), mVector.get().begin() + mPosition); + mPosition += data.size(); + return data.size(); +} + +bool VectorStream::SetPosition(std::size_t position) +{ + if (position >= mVector.get().size()) + { + return false; + } + + mPosition = position; + return true; +} + +std::size_t VectorStream::GetPosition() const +{ + return mPosition; +} + +std::size_t VectorStream::GetRemaining() const +{ + return mVector.get().size() - mPosition; +} + +SpanStream::SpanStream(std::span span, std::endian endianness) + : Stream(endianness), mSpan(std::move(span)), mPosition(0) +{ +} + +SpanStream::~SpanStream() +{ +} + +std::size_t SpanStream::Read(const std::span& data) +{ + if (data.size() > GetRemaining()) + { + SetError(ERROR_READ_FAILED); + std::fill(data.begin(), data.end(), std::byte(0)); + return 0; + } + + std::copy_n(mSpan.begin() + mPosition, data.size(), data.begin()); + mPosition += data.size(); + return data.size(); +} + +std::size_t SpanStream::Write(const std::span& data) +{ + // Cannot write to const span + SetError(ERROR_WRITE_FAILED); + return 0; +} + +bool SpanStream::SetPosition(std::size_t position) +{ + if (position >= mSpan.size()) + { + return false; + } + + mPosition = position; + return true; +} + +std::size_t SpanStream::GetPosition() const +{ + return mPosition; +} + +std::size_t SpanStream::GetRemaining() const +{ + if (mPosition > mSpan.size()) + { + return 0; + } + + return mSpan.size() - mPosition; +} diff --git a/src/Cafe/OS/libs/nfc/stream.h b/src/Cafe/OS/libs/nfc/stream.h new file mode 100644 index 00000000..e666b480 --- /dev/null +++ b/src/Cafe/OS/libs/nfc/stream.h @@ -0,0 +1,139 @@ +#pragma once + +#include +#include +#include +#include + +#include "Common/precompiled.h" + +class Stream +{ +public: + enum Error + { + ERROR_OK, + ERROR_READ_FAILED, + ERROR_WRITE_FAILED, + }; + +public: + Stream(std::endian endianness = std::endian::native); + virtual ~Stream(); + + Error GetError() const; + + void SetEndianness(std::endian endianness); + std::endian GetEndianness() const; + + virtual std::size_t Read(const std::span& data) = 0; + virtual std::size_t Write(const std::span& data) = 0; + + virtual bool SetPosition(std::size_t position) = 0; + virtual std::size_t GetPosition() const = 0; + + virtual std::size_t GetRemaining() const = 0; + + // Stream read operators + template + Stream& operator>>(T& val) + { + val = 0; + if (Read(std::as_writable_bytes(std::span(std::addressof(val), 1))) == sizeof(val)) + { + if (NeedsSwap()) + { + if (sizeof(T) == 2) + { + val = _swapEndianU16(val); + } + else if (sizeof(T) == 4) + { + val = _swapEndianU32(val); + } + else if (sizeof(T) == 8) + { + val = _swapEndianU64(val); + } + } + } + + return *this; + } + Stream& operator>>(bool& val); + Stream& operator>>(float& val); + Stream& operator>>(double& val); + + // Stream write operators + template + Stream& operator<<(T val) + { + if (NeedsSwap()) + { + if (sizeof(T) == 2) + { + val = _swapEndianU16(val); + } + else if (sizeof(T) == 4) + { + val = _swapEndianU32(val); + } + else if (sizeof(T) == 8) + { + val = _swapEndianU64(val); + } + } + + Write(std::as_bytes(std::span(std::addressof(val), 1))); + return *this; + } + Stream& operator<<(bool val); + Stream& operator<<(float val); + Stream& operator<<(double val); + +protected: + void SetError(Error error); + + bool NeedsSwap(); + + Error mError; + std::endian mEndianness; +}; + +class VectorStream : public Stream +{ +public: + VectorStream(std::vector& vector, std::endian endianness = std::endian::native); + virtual ~VectorStream(); + + virtual std::size_t Read(const std::span& data) override; + virtual std::size_t Write(const std::span& data) override; + + virtual bool SetPosition(std::size_t position) override; + virtual std::size_t GetPosition() const override; + + virtual std::size_t GetRemaining() const override; + +private: + std::reference_wrapper> mVector; + std::size_t mPosition; +}; + +class SpanStream : public Stream +{ +public: + SpanStream(std::span span, std::endian endianness = std::endian::native); + virtual ~SpanStream(); + + virtual std::size_t Read(const std::span& data) override; + virtual std::size_t Write(const std::span& data) override; + + virtual bool SetPosition(std::size_t position) override; + virtual std::size_t GetPosition() const override; + + virtual std::size_t GetRemaining() const override; + +private: + std::span mSpan; + std::size_t mPosition; +}; diff --git a/src/Cafe/OS/libs/nn_nfp/nn_nfp.cpp b/src/Cafe/OS/libs/nn_nfp/nn_nfp.cpp index ad2ea203..10d9e7cb 100644 --- a/src/Cafe/OS/libs/nn_nfp/nn_nfp.cpp +++ b/src/Cafe/OS/libs/nn_nfp/nn_nfp.cpp @@ -293,41 +293,6 @@ void nnNfpExport_GetTagInfo(PPCInterpreter_t* hCPU) osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0)); } -typedef struct -{ - /* +0x00 */ uint8 uidLength; - /* +0x01 */ uint8 uid[0xA]; - /* +0x0B */ uint8 ukn0B; - /* +0x0C */ uint8 ukn0C; - /* +0x0D */ uint8 ukn0D; - // more? -}NFCTagInfoCallbackParam_t; - -uint32 NFCGetTagInfo(uint32 index, uint32 timeout, MPTR functionPtr, void* userParam) -{ - cemuLog_log(LogType::NN_NFP, "NFCGetTagInfo({},{},0x{:08x},0x{:08x})", index, timeout, functionPtr, userParam ? memory_getVirtualOffsetFromPointer(userParam) : 0); - - - cemu_assert(index == 0); - - nnNfpLock(); - - StackAllocator _callbackParam; - NFCTagInfoCallbackParam_t* callbackParam = _callbackParam.GetPointer(); - - memset(callbackParam, 0x00, sizeof(NFCTagInfoCallbackParam_t)); - - memcpy(callbackParam->uid, nfp_data.amiiboProcessedData.uid, nfp_data.amiiboProcessedData.uidLength); - callbackParam->uidLength = (uint8)nfp_data.amiiboProcessedData.uidLength; - - PPCCoreCallback(functionPtr, index, 0, _callbackParam.GetPointer(), userParam); - - nnNfpUnlock(); - - - return 0; // 0 -> success -} - void nnNfpExport_Mount(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::NN_NFP, "Mount()"); @@ -769,6 +734,16 @@ void nnNfp_unloadAmiibo() nnNfpUnlock(); } +bool nnNfp_isInitialized() +{ + return nfp_data.nfpIsInitialized; +} + +// CEMU NFC error codes +#define NFC_ERROR_NONE (0) +#define NFC_ERROR_NO_ACCESS (1) +#define NFC_ERROR_INVALID_FILE_FORMAT (2) + bool nnNfp_touchNfcTagFromFile(const fs::path& filePath, uint32* nfcError) { AmiiboRawNFCData rawData = { 0 }; @@ -960,6 +935,41 @@ void nnNfpExport_GetNfpState(PPCInterpreter_t* hCPU) namespace nn::nfp { + typedef struct + { + /* +0x00 */ uint8 uidLength; + /* +0x01 */ uint8 uid[0xA]; + /* +0x0B */ uint8 ukn0B; + /* +0x0C */ uint8 ukn0C; + /* +0x0D */ uint8 ukn0D; + // more? + }NFCTagInfoCallbackParam_t; + + uint32 NFCGetTagInfo(uint32 index, uint32 timeout, MPTR functionPtr, void* userParam) + { + cemuLog_log(LogType::NN_NFP, "NFCGetTagInfo({},{},0x{:08x},0x{:08x})", index, timeout, functionPtr, userParam ? memory_getVirtualOffsetFromPointer(userParam) : 0); + + + cemu_assert(index == 0); + + nnNfpLock(); + + StackAllocator _callbackParam; + NFCTagInfoCallbackParam_t* callbackParam = _callbackParam.GetPointer(); + + memset(callbackParam, 0x00, sizeof(NFCTagInfoCallbackParam_t)); + + memcpy(callbackParam->uid, nfp_data.amiiboProcessedData.uid, nfp_data.amiiboProcessedData.uidLength); + callbackParam->uidLength = (uint8)nfp_data.amiiboProcessedData.uidLength; + + PPCCoreCallback(functionPtr, index, 0, _callbackParam.GetPointer(), userParam); + + nnNfpUnlock(); + + + return 0; // 0 -> success + } + uint32 GetErrorCode(uint32 result) { uint32 level = (result >> 0x1b) & 3; @@ -1019,9 +1029,6 @@ namespace nn::nfp nnNfp_load(); // legacy interface, update these to use cafeExportRegister / cafeExportRegisterFunc cafeExportRegisterFunc(nn::nfp::GetErrorCode, "nn_nfp", "GetErrorCode__Q2_2nn3nfpFRCQ2_2nn6Result", LogType::Placeholder); - - // NFC API - cafeExportRegister("nn_nfp", NFCGetTagInfo, LogType::Placeholder); } } diff --git a/src/Cafe/OS/libs/nn_nfp/nn_nfp.h b/src/Cafe/OS/libs/nn_nfp/nn_nfp.h index e8a1c55f..25b36cc9 100644 --- a/src/Cafe/OS/libs/nn_nfp/nn_nfp.h +++ b/src/Cafe/OS/libs/nn_nfp/nn_nfp.h @@ -2,12 +2,15 @@ namespace nn::nfp { + uint32 NFCGetTagInfo(uint32 index, uint32 timeout, MPTR functionPtr, void* userParam); + void load(); } void nnNfp_load(); void nnNfp_update(); +bool nnNfp_isInitialized(); bool nnNfp_touchNfcTagFromFile(const fs::path& filePath, uint32* nfcError); #define NFP_STATE_NONE (0) @@ -18,8 +21,3 @@ bool nnNfp_touchNfcTagFromFile(const fs::path& filePath, uint32* nfcError); #define NFP_STATE_RW_MOUNT (5) #define NFP_STATE_UNEXPECTED (6) #define NFP_STATE_RW_MOUNT_ROM (7) - -// CEMU NFC error codes -#define NFC_ERROR_NONE (0) -#define NFC_ERROR_NO_ACCESS (1) -#define NFC_ERROR_INVALID_FILE_FORMAT (2) diff --git a/src/Cafe/OS/libs/ntag/ntag.cpp b/src/Cafe/OS/libs/ntag/ntag.cpp new file mode 100644 index 00000000..24617791 --- /dev/null +++ b/src/Cafe/OS/libs/ntag/ntag.cpp @@ -0,0 +1,647 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/OS/RPL/rpl.h" +#include "Cafe/OS/libs/ntag/ntag.h" +#include "Cafe/OS/libs/nfc/nfc.h" +#include "Cafe/OS/libs/coreinit/coreinit_IPC.h" +#include "Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.h" + +namespace ntag +{ + struct NTAGWriteData + { + uint16 size; + uint8 data[0x1C8]; + nfc::NFCUid uid; + nfc::NFCUid uidMask; + }; + NTAGWriteData gWriteData[2]; + + bool ccrNfcOpened = false; + IOSDevHandle gCcrNfcHandle; + + NTAGFormatSettings gFormatSettings; + + MPTR gDetectCallbacks[2]; + MPTR gAbortCallbacks[2]; + MPTR gReadCallbacks[2]; + MPTR gWriteCallbacks[2]; + + sint32 __NTAGConvertNFCResult(sint32 result) + { + if (result == NFC_RESULT_SUCCESS) + { + return NTAG_RESULT_SUCCESS; + } + + switch (result & NFC_RESULT_MASK) + { + case NFC_RESULT_UNINITIALIZED: + return NTAG_RESULT_UNINITIALIZED; + case NFC_RESULT_INVALID_STATE: + return NTAG_RESULT_INVALID_STATE; + case NFC_RESULT_NO_TAG: + return NTAG_RESULT_NO_TAG; + case NFC_RESULT_UID_MISMATCH: + return NTAG_RESULT_UID_MISMATCH; + } + + // TODO convert more errors + return NTAG_RESULT_INVALID; + } + + sint32 NTAGInit(uint32 chan) + { + return NTAGInitEx(chan); + } + + sint32 NTAGInitEx(uint32 chan) + { + sint32 result = nfc::NFCInitEx(chan, 1); + return __NTAGConvertNFCResult(result); + } + + sint32 NTAGShutdown(uint32 chan) + { + sint32 result = nfc::NFCShutdown(chan); + + if (ccrNfcOpened) + { + coreinit::IOS_Close(gCcrNfcHandle); + ccrNfcOpened = false; + } + + gDetectCallbacks[chan] = MPTR_NULL; + gAbortCallbacks[chan] = MPTR_NULL; + gReadCallbacks[chan] = MPTR_NULL; + gWriteCallbacks[chan] = MPTR_NULL; + + return __NTAGConvertNFCResult(result); + } + + bool NTAGIsInit(uint32 chan) + { + return nfc::NFCIsInit(chan); + } + + void NTAGProc(uint32 chan) + { + nfc::NFCProc(chan); + } + + void NTAGSetFormatSettings(NTAGFormatSettings* formatSettings) + { + gFormatSettings.version = formatSettings->version; + gFormatSettings.makerCode = _swapEndianU32(formatSettings->makerCode); + gFormatSettings.identifyCode = _swapEndianU32(formatSettings->identifyCode); + } + + void __NTAGDetectCallback(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(chan, 0); + ppcDefineParamU32(hasTag, 1); + ppcDefineParamPtr(context, void, 2); + + cemuLog_log(LogType::NTAG, "__NTAGDetectCallback: {} {} {}", chan, hasTag, context); + + PPCCoreCallback(gDetectCallbacks[chan], chan, hasTag, context); + + osLib_returnFromFunction(hCPU, 0); + } + + void NTAGSetTagDetectCallback(uint32 chan, MPTR callback, void* context) + { + cemu_assert(chan < 2); + + gDetectCallbacks[chan] = callback; + nfc::NFCSetTagDetectCallback(chan, RPLLoader_MakePPCCallable(__NTAGDetectCallback), context); + } + + void __NTAGAbortCallback(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(chan, 0); + ppcDefineParamS32(error, 1); + ppcDefineParamPtr(context, void, 2); + + PPCCoreCallback(gAbortCallbacks[chan], chan, __NTAGConvertNFCResult(error), context); + + osLib_returnFromFunction(hCPU, 0); + } + + sint32 NTAGAbort(uint32 chan, MPTR callback, void* context) + { + cemu_assert(chan < 2); + + // TODO is it normal that Rumble U calls this? + + gAbortCallbacks[chan] = callback; + sint32 result = nfc::NFCAbort(chan, RPLLoader_MakePPCCallable(__NTAGAbortCallback), context); + return __NTAGConvertNFCResult(result); + } + + bool __NTAGRawDataToNfcData(iosu::ccr_nfc::CCRNFCCryptData* raw, iosu::ccr_nfc::CCRNFCCryptData* nfc) + { + memcpy(nfc, raw, sizeof(iosu::ccr_nfc::CCRNFCCryptData)); + + if (raw->version == 0) + { + nfc->version = 0; + nfc->dataSize = 0x1C8; + nfc->seedOffset = 0x25; + nfc->keyGenSaltOffset = 0x1A8; + nfc->uuidOffset = 0x198; + nfc->unfixedInfosOffset = 0x28; + nfc->unfixedInfosSize = 0x120; + nfc->lockedSecretOffset = 0x168; + nfc->lockedSecretSize = 0x30; + nfc->unfixedInfosHmacOffset = 0; + nfc->lockedSecretHmacOffset = 0x148; + } + else if (raw->version == 2) + { + nfc->version = 2; + nfc->dataSize = 0x208; + nfc->seedOffset = 0x29; + nfc->keyGenSaltOffset = 0x1E8; + nfc->uuidOffset = 0x1D4; + nfc->unfixedInfosOffset = 0x2C; + nfc->unfixedInfosSize = 0x188; + nfc->lockedSecretOffset = 0x1DC; + nfc->lockedSecretSize = 0; + nfc->unfixedInfosHmacOffset = 0x8; + nfc->lockedSecretHmacOffset = 0x1B4; + + memcpy(nfc->data + 0x1d4, raw->data, 0x8); + memcpy(nfc->data, raw->data + 0x8, 0x8); + memcpy(nfc->data + 0x28, raw->data + 0x10, 0x4); + memcpy(nfc->data + nfc->unfixedInfosOffset, raw->data + 0x14, 0x20); + memcpy(nfc->data + nfc->lockedSecretHmacOffset, raw->data + 0x34, 0x20); + memcpy(nfc->data + nfc->lockedSecretOffset, raw->data + 0x54, 0xC); + memcpy(nfc->data + nfc->keyGenSaltOffset, raw->data + 0x60, 0x20); + memcpy(nfc->data + nfc->unfixedInfosHmacOffset, raw->data + 0x80, 0x20); + memcpy(nfc->data + nfc->unfixedInfosOffset + 0x20, raw->data + 0xa0, 0x168); + memcpy(nfc->data + 0x208, raw->data + 0x208, 0x14); + } + else + { + return false; + } + + return true; + } + + bool __NTAGNfcDataToRawData(iosu::ccr_nfc::CCRNFCCryptData* nfc, iosu::ccr_nfc::CCRNFCCryptData* raw) + { + memcpy(raw, nfc, sizeof(iosu::ccr_nfc::CCRNFCCryptData)); + + if (nfc->version == 0) + { + raw->version = 0; + raw->dataSize = 0x1C8; + raw->seedOffset = 0x25; + raw->keyGenSaltOffset = 0x1A8; + raw->uuidOffset = 0x198; + raw->unfixedInfosOffset = 0x28; + raw->unfixedInfosSize = 0x120; + raw->lockedSecretOffset = 0x168; + raw->lockedSecretSize = 0x30; + raw->unfixedInfosHmacOffset = 0; + raw->lockedSecretHmacOffset = 0x148; + } + else if (nfc->version == 2) + { + raw->version = 2; + raw->dataSize = 0x208; + raw->seedOffset = 0x11; + raw->keyGenSaltOffset = 0x60; + raw->uuidOffset = 0; + raw->unfixedInfosOffset = 0x14; + raw->unfixedInfosSize = 0x188; + raw->lockedSecretOffset = 0x54; + raw->lockedSecretSize = 0xC; + raw->unfixedInfosHmacOffset = 0x80; + raw->lockedSecretHmacOffset = 0x34; + + memcpy(raw->data + 0x8, nfc->data, 0x8); + memcpy(raw->data + raw->unfixedInfosHmacOffset, nfc->data + 0x8, 0x20); + memcpy(raw->data + 0x10, nfc->data + 0x28, 0x4); + memcpy(raw->data + raw->unfixedInfosOffset, nfc->data + 0x2C, 0x20); + memcpy(raw->data + 0xa0, nfc->data + 0x4C, 0x168); + memcpy(raw->data + raw->lockedSecretHmacOffset, nfc->data + 0x1B4, 0x20); + memcpy(raw->data + raw->uuidOffset, nfc->data + 0x1D4, 0x8); + memcpy(raw->data + raw->lockedSecretOffset, nfc->data + 0x1DC, 0xC); + memcpy(raw->data + raw->keyGenSaltOffset, nfc->data + 0x1E8, 0x20); + memcpy(raw->data + 0x208, nfc->data + 0x208, 0x14); + } + else + { + return false; + } + + return true; + } + + sint32 __NTAGDecryptData(void* decryptedData, const void* rawData) + { + StackAllocator nfcRawData, nfcInData, nfcOutData; + + if (!ccrNfcOpened) + { + gCcrNfcHandle = coreinit::IOS_Open("/dev/ccr_nfc", 0); + } + + // Prepare nfc buffer + nfcRawData->version = 0; + memcpy(nfcRawData->data, rawData, 0x1C8); + __NTAGRawDataToNfcData(nfcRawData.GetPointer(), nfcInData.GetPointer()); + + // Decrypt + sint32 result = coreinit::IOS_Ioctl(gCcrNfcHandle, 2, nfcInData.GetPointer(), sizeof(iosu::ccr_nfc::CCRNFCCryptData), nfcOutData.GetPointer(), sizeof(iosu::ccr_nfc::CCRNFCCryptData)); + + // Unpack nfc buffer + __NTAGNfcDataToRawData(nfcOutData.GetPointer(), nfcRawData.GetPointer()); + memcpy(decryptedData, nfcRawData->data, 0x1C8); + + // Convert result + if (result == CCR_NFC_INVALID_UNFIXED_INFOS) + { + return -0x2708; + } + else if (result == CCR_NFC_INVALID_LOCKED_SECRET) + { + return -0x2707; + } + + return result; + } + + sint32 __NTAGValidateHeaders(NTAGNoftHeader* noftHeader, NTAGInfoHeader* infoHeader, NTAGAreaHeader* rwHeader, NTAGAreaHeader* roHeader) + { + if (infoHeader->formatVersion != gFormatSettings.version || noftHeader->version != 0x1) + { + cemuLog_log(LogType::Force, "Invalid format version"); + return -0x2710; + } + + if (_swapEndianU32(noftHeader->magic) != 0x4E4F4654 /* 'NOFT' */ || + _swapEndianU16(rwHeader->magic) != 0x5257 /* 'RW' */ || + _swapEndianU16(roHeader->magic) != 0x524F /* 'RO' */) + { + cemuLog_log(LogType::Force, "Invalid header magic"); + return -0x270F; + } + + if (_swapEndianU32(rwHeader->makerCode) != gFormatSettings.makerCode || + _swapEndianU32(roHeader->makerCode) != gFormatSettings.makerCode) + { + cemuLog_log(LogType::Force, "Invalid maker code"); + return -0x270E; + } + + if (infoHeader->formatVersion != 0 && + (_swapEndianU32(rwHeader->identifyCode) != gFormatSettings.identifyCode || + _swapEndianU32(roHeader->identifyCode) != gFormatSettings.identifyCode)) + { + cemuLog_log(LogType::Force, "Invalid identify code"); + return -0x2709; + } + + if (_swapEndianU16(rwHeader->size) + _swapEndianU16(roHeader->size) != 0x130) + { + cemuLog_log(LogType::Force, "Invalid data size"); + return -0x270D; + } + + return 0; + } + + sint32 __NTAGParseHeaders(const uint8* data, NTAGNoftHeader* noftHeader, NTAGInfoHeader* infoHeader, NTAGAreaHeader* rwHeader, NTAGAreaHeader* roHeader) + { + memcpy(noftHeader, data + 0x20, sizeof(NTAGNoftHeader)); + memcpy(infoHeader, data + 0x198, sizeof(NTAGInfoHeader)); + + cemu_assert(_swapEndianU16(infoHeader->rwHeaderOffset) + sizeof(NTAGAreaHeader) < 0x200); + cemu_assert(_swapEndianU16(infoHeader->roHeaderOffset) + sizeof(NTAGAreaHeader) < 0x200); + + memcpy(rwHeader, data + _swapEndianU16(infoHeader->rwHeaderOffset), sizeof(NTAGAreaHeader)); + memcpy(roHeader, data + _swapEndianU16(infoHeader->roHeaderOffset), sizeof(NTAGAreaHeader)); + + return __NTAGValidateHeaders(noftHeader, infoHeader, rwHeader, roHeader); + } + + sint32 __NTAGParseData(void* rawData, void* rwData, void* roData, nfc::NFCUid* uid, uint32 lockedDataSize, NTAGNoftHeader* noftHeader, NTAGInfoHeader* infoHeader, NTAGAreaHeader* rwHeader, NTAGAreaHeader* roHeader) + { + uint8 decryptedData[0x1C8]; + sint32 result = __NTAGDecryptData(decryptedData, rawData); + if (result < 0) + { + return result; + } + + result = __NTAGParseHeaders(decryptedData, noftHeader, infoHeader, rwHeader, roHeader); + if (result < 0) + { + return result; + } + + if (_swapEndianU16(roHeader->size) + 0x70 != lockedDataSize) + { + cemuLog_log(LogType::Force, "Invalid locked area size"); + return -0x270C; + } + + if (memcmp(infoHeader->uid.uid, uid->uid, sizeof(nfc::NFCUid)) != 0) + { + cemuLog_log(LogType::Force, "UID mismatch"); + return -0x270B; + } + + cemu_assert(_swapEndianU16(rwHeader->offset) + _swapEndianU16(rwHeader->size) < 0x200); + cemu_assert(_swapEndianU16(roHeader->offset) + _swapEndianU16(roHeader->size) < 0x200); + + memcpy(rwData, decryptedData + _swapEndianU16(rwHeader->offset), _swapEndianU16(rwHeader->size)); + memcpy(roData, decryptedData + _swapEndianU16(roHeader->offset), _swapEndianU16(roHeader->size)); + + return 0; + } + + void __NTAGReadCallback(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(chan, 0); + ppcDefineParamS32(error, 1); + ppcDefineParamPtr(uid, nfc::NFCUid, 2); + ppcDefineParamU32(readOnly, 3); + ppcDefineParamU32(dataSize, 4); + ppcDefineParamPtr(data, void, 5); + ppcDefineParamU32(lockedDataSize, 6); + ppcDefineParamPtr(lockedData, void, 7); + ppcDefineParamPtr(context, void, 8); + + uint8 rawData[0x1C8]{}; + StackAllocator readResult; + StackAllocator rwData; + StackAllocator roData; + NTAGNoftHeader noftHeader; + NTAGInfoHeader infoHeader; + NTAGAreaHeader rwHeader; + NTAGAreaHeader roHeader; + + readResult->readOnly = readOnly; + + error = __NTAGConvertNFCResult(error); + if (error == 0) + { + memset(rwData.GetPointer(), 0, 0x1C8); + memset(roData.GetPointer(), 0, 0x1C8); + + // Copy raw and locked data into a contigous buffer + memcpy(rawData, data, dataSize); + memcpy(rawData + dataSize, lockedData, lockedDataSize); + + error = __NTAGParseData(rawData, rwData.GetPointer(), roData.GetPointer(), uid, lockedDataSize, &noftHeader, &infoHeader, &rwHeader, &roHeader); + if (error == 0) + { + memcpy(readResult->uid.uid, uid->uid, sizeof(uid->uid)); + readResult->rwInfo.data = _swapEndianU32(rwData.GetMPTR()); + readResult->roInfo.data = _swapEndianU32(roData.GetMPTR()); + readResult->rwInfo.makerCode = rwHeader.makerCode; + readResult->rwInfo.size = rwHeader.size; + readResult->roInfo.makerCode = roHeader.makerCode; + readResult->rwInfo.identifyCode = rwHeader.identifyCode; + readResult->roInfo.identifyCode = roHeader.identifyCode; + readResult->formatVersion = infoHeader.formatVersion; + readResult->roInfo.size = roHeader.size; + + cemuLog_log(LogType::NTAG, "__NTAGReadCallback: {} {} {}", chan, error, context); + + PPCCoreCallback(gReadCallbacks[chan], chan, 0, readResult.GetPointer(), context); + osLib_returnFromFunction(hCPU, 0); + return; + } + } + + if (uid) + { + memcpy(readResult->uid.uid, uid->uid, sizeof(uid->uid)); + } + readResult->roInfo.size = 0; + readResult->rwInfo.size = 0; + readResult->roInfo.data = MPTR_NULL; + readResult->formatVersion = 0; + readResult->rwInfo.data = MPTR_NULL; + cemuLog_log(LogType::NTAG, "__NTAGReadCallback: {} {} {}", chan, error, context); + PPCCoreCallback(gReadCallbacks[chan], chan, error, readResult.GetPointer(), context); + osLib_returnFromFunction(hCPU, 0); + } + + sint32 NTAGRead(uint32 chan, uint32 timeout, nfc::NFCUid* uid, nfc::NFCUid* uidMask, MPTR callback, void* context) + { + cemu_assert(chan < 2); + + gReadCallbacks[chan] = callback; + + nfc::NFCUid _uid{}, _uidMask{}; + if (uid && uidMask) + { + memcpy(&_uid, uid, sizeof(*uid)); + memcpy(&_uidMask, uidMask, sizeof(*uidMask)); + } + + sint32 result = nfc::NFCRead(chan, timeout, &_uid, &_uidMask, RPLLoader_MakePPCCallable(__NTAGReadCallback), context); + return __NTAGConvertNFCResult(result); + } + + sint32 __NTAGEncryptData(void* encryptedData, const void* rawData) + { + StackAllocator nfcRawData, nfcInData, nfcOutData; + + if (!ccrNfcOpened) + { + gCcrNfcHandle = coreinit::IOS_Open("/dev/ccr_nfc", 0); + } + + // Prepare nfc buffer + nfcRawData->version = 0; + memcpy(nfcRawData->data, rawData, 0x1C8); + __NTAGRawDataToNfcData(nfcRawData.GetPointer(), nfcInData.GetPointer()); + + // Encrypt + sint32 result = coreinit::IOS_Ioctl(gCcrNfcHandle, 1, nfcInData.GetPointer(), sizeof(iosu::ccr_nfc::CCRNFCCryptData), nfcOutData.GetPointer(), sizeof(iosu::ccr_nfc::CCRNFCCryptData)); + + // Unpack nfc buffer + __NTAGNfcDataToRawData(nfcOutData.GetPointer(), nfcRawData.GetPointer()); + memcpy(encryptedData, nfcRawData->data, 0x1C8); + + return result; + } + + sint32 __NTAGPrepareWriteData(void* outBuffer, uint32 dataSize, const void* data, const void* tagData, NTAGNoftHeader* noftHeader, NTAGAreaHeader* rwHeader) + { + uint8 decryptedBuffer[0x1C8]; + uint8 encryptedBuffer[0x1C8]; + + memcpy(decryptedBuffer, tagData, 0x1C8); + + // Fill the rest of the rw area with random data + if (dataSize < _swapEndianU16(rwHeader->size)) + { + uint8 randomBuffer[0x1C8]; + for (int i = 0; i < sizeof(randomBuffer); i++) + { + randomBuffer[i] = rand() & 0xFF; + } + + memcpy(decryptedBuffer + _swapEndianU16(rwHeader->offset) + dataSize, randomBuffer, _swapEndianU16(rwHeader->size) - dataSize); + } + + // Make sure the data fits into the rw area + if (_swapEndianU16(rwHeader->size) < dataSize) + { + return -0x270D; + } + + // Update write count (check for overflow) + if ((_swapEndianU16(noftHeader->writeCount) & 0x7fff) == 0x7fff) + { + noftHeader->writeCount = _swapEndianU16(_swapEndianU16(noftHeader->writeCount) & 0x8000); + } + else + { + noftHeader->writeCount = _swapEndianU16(_swapEndianU16(noftHeader->writeCount) + 1); + } + + memcpy(decryptedBuffer + 0x20, noftHeader, sizeof(noftHeader)); + memcpy(decryptedBuffer + _swapEndianU16(rwHeader->offset), data, dataSize); + + // Encrypt + sint32 result = __NTAGEncryptData(encryptedBuffer, decryptedBuffer); + if (result < 0) + { + return result; + } + + memcpy(outBuffer, encryptedBuffer, _swapEndianU16(rwHeader->size) + 0x28); + return 0; + } + + void __NTAGWriteCallback(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(chan, 0); + ppcDefineParamS32(error, 1); + ppcDefineParamPtr(context, void, 2); + + PPCCoreCallback(gWriteCallbacks[chan], chan, __NTAGConvertNFCResult(error), context); + + osLib_returnFromFunction(hCPU, 0); + } + + void __NTAGReadBeforeWriteCallback(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(chan, 0); + ppcDefineParamS32(error, 1); + ppcDefineParamPtr(uid, nfc::NFCUid, 2); + ppcDefineParamU32(readOnly, 3); + ppcDefineParamU32(dataSize, 4); + ppcDefineParamPtr(data, void, 5); + ppcDefineParamU32(lockedDataSize, 6); + ppcDefineParamPtr(lockedData, void, 7); + ppcDefineParamPtr(context, void, 8); + + uint8 rawData[0x1C8]{}; + uint8 rwData[0x1C8]{}; + uint8 roData[0x1C8]{}; + NTAGNoftHeader noftHeader; + NTAGInfoHeader infoHeader; + NTAGAreaHeader rwHeader; + NTAGAreaHeader roHeader; + uint8 writeBuffer[0x1C8]{}; + + error = __NTAGConvertNFCResult(error); + if (error == 0) + { + // Copy raw and locked data into a contigous buffer + memcpy(rawData, data, dataSize); + memcpy(rawData + dataSize, lockedData, lockedDataSize); + + error = __NTAGParseData(rawData, rwData, roData, uid, lockedDataSize, &noftHeader, &infoHeader, &rwHeader, &roHeader); + if (error < 0) + { + cemuLog_log(LogType::Force, "Failed to parse data before write"); + PPCCoreCallback(gWriteCallbacks[chan], chan, -0x3E3, context); + osLib_returnFromFunction(hCPU, 0); + return; + } + + // Prepare data + memcpy(rawData + _swapEndianU16(infoHeader.rwHeaderOffset), &rwHeader, sizeof(rwHeader)); + memcpy(rawData + _swapEndianU16(infoHeader.roHeaderOffset), &roHeader, sizeof(roHeader)); + memcpy(rawData + _swapEndianU16(roHeader.offset), roData, _swapEndianU16(roHeader.size)); + error = __NTAGPrepareWriteData(writeBuffer, gWriteData[chan].size, gWriteData[chan].data, rawData, &noftHeader, &rwHeader); + if (error < 0) + { + cemuLog_log(LogType::Force, "Failed to prepare write data"); + PPCCoreCallback(gWriteCallbacks[chan], chan, -0x3E3, context); + osLib_returnFromFunction(hCPU, 0); + return; + } + + // Write data to tag + error = nfc::NFCWrite(chan, 200, &gWriteData[chan].uid, &gWriteData[chan].uidMask, + _swapEndianU16(rwHeader.size) + 0x28, writeBuffer, RPLLoader_MakePPCCallable(__NTAGWriteCallback), context); + if (error >= 0) + { + osLib_returnFromFunction(hCPU, 0); + return; + } + + error = __NTAGConvertNFCResult(error); + } + + PPCCoreCallback(gWriteCallbacks[chan], chan, error, context); + osLib_returnFromFunction(hCPU, 0); + } + + sint32 NTAGWrite(uint32 chan, uint32 timeout, nfc::NFCUid* uid, uint32 rwSize, void* rwData, MPTR callback, void* context) + { + cemu_assert(chan < 2); + cemu_assert(rwSize < 0x1C8); + + gWriteCallbacks[chan] = callback; + + if (uid) + { + memcpy(&gWriteData[chan].uid, uid, sizeof(nfc::NFCUid)); + } + memset(&gWriteData[chan].uidMask, 0xff, sizeof(nfc::NFCUid)); + + gWriteData[chan].size = rwSize; + memcpy(gWriteData[chan].data, rwData, rwSize); + + sint32 result = nfc::NFCRead(chan, timeout, &gWriteData[chan].uid, &gWriteData[chan].uidMask, RPLLoader_MakePPCCallable(__NTAGReadBeforeWriteCallback), context); + return __NTAGConvertNFCResult(result); + } + + sint32 NTAGFormat(uint32 chan, uint32 timeout, nfc::NFCUid* uid, uint32 rwSize, void* rwData, MPTR callback, void* context) + { + cemu_assert(chan < 2); + + // TODO + cemu_assert_debug(false); + + return NTAG_RESULT_INVALID; + } + + void Initialize() + { + cafeExportRegister("ntag", NTAGInit, LogType::NTAG); + cafeExportRegister("ntag", NTAGInitEx, LogType::NTAG); + cafeExportRegister("ntag", NTAGShutdown, LogType::NTAG); + cafeExportRegister("ntag", NTAGIsInit, LogType::Placeholder); // disabled logging, since this gets spammed + cafeExportRegister("ntag", NTAGProc, LogType::Placeholder); // disabled logging, since this gets spammed + cafeExportRegister("ntag", NTAGSetFormatSettings, LogType::NTAG); + cafeExportRegister("ntag", NTAGSetTagDetectCallback, LogType::NTAG); + cafeExportRegister("ntag", NTAGAbort, LogType::NTAG); + cafeExportRegister("ntag", NTAGRead, LogType::NTAG); + cafeExportRegister("ntag", NTAGWrite, LogType::NTAG); + cafeExportRegister("ntag", NTAGFormat, LogType::NTAG); + } +} diff --git a/src/Cafe/OS/libs/ntag/ntag.h b/src/Cafe/OS/libs/ntag/ntag.h new file mode 100644 index 00000000..68f1801b --- /dev/null +++ b/src/Cafe/OS/libs/ntag/ntag.h @@ -0,0 +1,101 @@ +#pragma once +#include "Cafe/OS/libs/nfc/nfc.h" + +#define NTAG_RESULT_SUCCESS (0) +#define NTAG_RESULT_UNINITIALIZED (-0x3E7) +#define NTAG_RESULT_INVALID_STATE (-0x3E6) +#define NTAG_RESULT_NO_TAG (-0x3E5) +#define NTAG_RESULT_INVALID (-0x3E1) +#define NTAG_RESULT_UID_MISMATCH (-0x3DB) + +namespace ntag +{ + struct NTAGFormatSettings + { + /* +0x00 */ uint8 version; + /* +0x04 */ uint32 makerCode; + /* +0x08 */ uint32 identifyCode; + /* +0x0C */ uint8 reserved[0x1C]; + }; + static_assert(sizeof(NTAGFormatSettings) == 0x28); + +#pragma pack(1) + struct NTAGNoftHeader + { + /* +0x00 */ uint32 magic; + /* +0x04 */ uint8 version; + /* +0x05 */ uint16 writeCount; + /* +0x07 */ uint8 unknown; + }; + static_assert(sizeof(NTAGNoftHeader) == 0x8); +#pragma pack() + + struct NTAGInfoHeader + { + /* +0x00 */ uint16 rwHeaderOffset; + /* +0x02 */ uint16 rwSize; + /* +0x04 */ uint16 roHeaderOffset; + /* +0x06 */ uint16 roSize; + /* +0x08 */ nfc::NFCUid uid; + /* +0x0F */ uint8 formatVersion; + }; + static_assert(sizeof(NTAGInfoHeader) == 0x10); + + struct NTAGAreaHeader + { + /* +0x00 */ uint16 magic; + /* +0x02 */ uint16 offset; + /* +0x04 */ uint16 size; + /* +0x06 */ uint16 padding; + /* +0x08 */ uint32 makerCode; + /* +0x0C */ uint32 identifyCode; + }; + static_assert(sizeof(NTAGAreaHeader) == 0x10); + + struct NTAGAreaInfo + { + /* +0x00 */ MPTR data; + /* +0x04 */ uint16 size; + /* +0x06 */ uint16 padding; + /* +0x08 */ uint32 makerCode; + /* +0x0C */ uint32 identifyCode; + /* +0x10 */ uint8 reserved[0x20]; + }; + static_assert(sizeof(NTAGAreaInfo) == 0x30); + + struct NTAGData + { + /* +0x00 */ nfc::NFCUid uid; + /* +0x07 */ uint8 readOnly; + /* +0x08 */ uint8 formatVersion; + /* +0x09 */ uint8 padding[3]; + /* +0x0C */ NTAGAreaInfo rwInfo; + /* +0x3C */ NTAGAreaInfo roInfo; + /* +0x6C */ uint8 reserved[0x20]; + }; + static_assert(sizeof(NTAGData) == 0x8C); + + sint32 NTAGInit(uint32 chan); + + sint32 NTAGInitEx(uint32 chan); + + sint32 NTAGShutdown(uint32 chan); + + bool NTAGIsInit(uint32 chan); + + void NTAGProc(uint32 chan); + + void NTAGSetFormatSettings(NTAGFormatSettings* formatSettings); + + void NTAGSetTagDetectCallback(uint32 chan, MPTR callback, void* context); + + sint32 NTAGAbort(uint32 chan, MPTR callback, void* context); + + sint32 NTAGRead(uint32 chan, uint32 timeout, nfc::NFCUid* uid, nfc::NFCUid* uidMask, MPTR callback, void* context); + + sint32 NTAGWrite(uint32 chan, uint32 timeout, nfc::NFCUid* uid, uint32 rwSize, void* rwData, MPTR callback, void* context); + + sint32 NTAGFormat(uint32 chan, uint32 timeout, nfc::NFCUid* uid, uint32 rwSize, void* rwData, MPTR callback, void* context); + + void Initialize(); +} diff --git a/src/Cemu/Logging/CemuLogging.cpp b/src/Cemu/Logging/CemuLogging.cpp index 058ab07a..e49ece94 100644 --- a/src/Cemu/Logging/CemuLogging.cpp +++ b/src/Cemu/Logging/CemuLogging.cpp @@ -51,6 +51,8 @@ const std::map g_logging_window_mapping {LogType::Socket, "Socket"}, {LogType::Save, "Save"}, {LogType::H264, "H264"}, + {LogType::NFC, "NFC"}, + {LogType::NTAG, "NTAG"}, {LogType::Patches, "Graphic pack patches"}, {LogType::TextureCache, "Texture cache"}, {LogType::TextureReadback, "Texture readback"}, diff --git a/src/Cemu/Logging/CemuLogging.h b/src/Cemu/Logging/CemuLogging.h index 8fbb318c..5fd652b3 100644 --- a/src/Cemu/Logging/CemuLogging.h +++ b/src/Cemu/Logging/CemuLogging.h @@ -44,6 +44,9 @@ enum class LogType : sint32 nlibcurl = 41, PRUDP = 40, + + NFC = 41, + NTAG = 42, }; template <> diff --git a/src/Common/unix/FileStream_unix.cpp b/src/Common/unix/FileStream_unix.cpp index 2dba17b7..4bc9b526 100644 --- a/src/Common/unix/FileStream_unix.cpp +++ b/src/Common/unix/FileStream_unix.cpp @@ -1,4 +1,5 @@ #include "Common/unix/FileStream_unix.h" +#include fs::path findPathCI(const fs::path& path) { diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 097d506e..33e2cdc1 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -12,7 +12,7 @@ #include "audio/audioDebuggerWindow.h" #include "gui/canvas/OpenGLCanvas.h" #include "gui/canvas/VulkanCanvas.h" -#include "Cafe/OS/libs/nn_nfp/nn_nfp.h" +#include "Cafe/OS/libs/nfc/nfc.h" #include "Cafe/OS/libs/swkbd/swkbd.h" #include "gui/debugger/DebuggerWindow2.h" #include "util/helpers/helpers.h" @@ -261,7 +261,7 @@ public: return false; uint32 nfcError; std::string path = filenames[0].utf8_string(); - if (nnNfp_touchNfcTagFromFile(_utf8ToPath(path), &nfcError)) + if (nfc::TouchTagFromFile(_utf8ToPath(path), &nfcError)) { GetConfig().AddRecentNfcFile(path); m_window->UpdateNFCMenu(); @@ -269,10 +269,10 @@ public: } else { - if (nfcError == NFC_ERROR_NO_ACCESS) + if (nfcError == NFC_TOUCH_TAG_ERROR_NO_ACCESS) wxMessageBox(_("Cannot open file"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR); - else if (nfcError == NFC_ERROR_INVALID_FILE_FORMAT) - wxMessageBox(_("Not a valid NFC NTAG215 file"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR); + else if (nfcError == NFC_TOUCH_TAG_ERROR_INVALID_FILE_FORMAT) + wxMessageBox(_("Not a valid NFC file"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR); return false; } } @@ -749,12 +749,12 @@ void MainWindow::OnNFCMenu(wxCommandEvent& event) return; wxString wxStrFilePath = openFileDialog.GetPath(); uint32 nfcError; - if (nnNfp_touchNfcTagFromFile(_utf8ToPath(wxStrFilePath.utf8_string()), &nfcError) == false) + if (nfc::TouchTagFromFile(_utf8ToPath(wxStrFilePath.utf8_string()), &nfcError) == false) { - if (nfcError == NFC_ERROR_NO_ACCESS) + if (nfcError == NFC_TOUCH_TAG_ERROR_NO_ACCESS) wxMessageBox(_("Cannot open file")); - else if (nfcError == NFC_ERROR_INVALID_FILE_FORMAT) - wxMessageBox(_("Not a valid NFC NTAG215 file")); + else if (nfcError == NFC_TOUCH_TAG_ERROR_INVALID_FILE_FORMAT) + wxMessageBox(_("Not a valid NFC file")); } else { @@ -772,12 +772,12 @@ void MainWindow::OnNFCMenu(wxCommandEvent& event) if (!path.empty()) { uint32 nfcError = 0; - if (nnNfp_touchNfcTagFromFile(_utf8ToPath(path), &nfcError) == false) + if (nfc::TouchTagFromFile(_utf8ToPath(path), &nfcError) == false) { - if (nfcError == NFC_ERROR_NO_ACCESS) + if (nfcError == NFC_TOUCH_TAG_ERROR_NO_ACCESS) wxMessageBox(_("Cannot open file")); - else if (nfcError == NFC_ERROR_INVALID_FILE_FORMAT) - wxMessageBox(_("Not a valid NFC NTAG215 file")); + else if (nfcError == NFC_TOUCH_TAG_ERROR_INVALID_FILE_FORMAT) + wxMessageBox(_("Not a valid NFC file")); } else { @@ -2210,6 +2210,8 @@ void MainWindow::RecreateMenu() debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::Socket), _("&Socket API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::Socket)); debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::Save), _("&Save API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::Save)); debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::H264), _("&H264 API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::H264)); + debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::NFC), _("&NFC API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::NFC)); + debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::NTAG), _("&NTAG API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::NTAG)); debugLoggingMenu->AppendSeparator(); debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::Patches), _("&Graphic pack patches"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::Patches)); debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::TextureCache), _("&Texture cache warnings"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::TextureCache));