Add initial NTAG and NFC implementation

This commit is contained in:
Exzap 2024-05-22 04:45:10 +02:00 committed by GitHub
commit a059338890
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 3305 additions and 66 deletions

View File

@ -218,6 +218,8 @@ add_library(CemuCafe
HW/SI/SI.cpp HW/SI/SI.cpp
HW/SI/si.h HW/SI/si.h
HW/VI/VI.cpp 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/fsa_types.h
IOSU/fsa/iosu_fsa.cpp IOSU/fsa/iosu_fsa.cpp
IOSU/fsa/iosu_fsa.h IOSU/fsa/iosu_fsa.h
@ -378,6 +380,16 @@ add_library(CemuCafe
OS/libs/h264_avc/parser/H264Parser.h OS/libs/h264_avc/parser/H264Parser.h
OS/libs/mic/mic.cpp OS/libs/mic/mic.cpp
OS/libs/mic/mic.h 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/nlibcurl.cpp
OS/libs/nlibcurl/nlibcurlDebug.hpp OS/libs/nlibcurl/nlibcurlDebug.hpp
OS/libs/nlibcurl/nlibcurl.h OS/libs/nlibcurl/nlibcurl.h
@ -453,6 +465,8 @@ add_library(CemuCafe
OS/libs/nsyskbd/nsyskbd.h OS/libs/nsyskbd/nsyskbd.h
OS/libs/nsysnet/nsysnet.cpp OS/libs/nsysnet/nsysnet.cpp
OS/libs/nsysnet/nsysnet.h OS/libs/nsysnet/nsysnet.h
OS/libs/ntag/ntag.cpp
OS/libs/ntag/ntag.h
OS/libs/padscore/padscore.cpp OS/libs/padscore/padscore.cpp
OS/libs/padscore/padscore.h OS/libs/padscore/padscore.h
OS/libs/proc_ui/proc_ui.cpp OS/libs/proc_ui/proc_ui.cpp

View File

@ -35,6 +35,7 @@
#include "Cafe/IOSU/legacy/iosu_boss.h" #include "Cafe/IOSU/legacy/iosu_boss.h"
#include "Cafe/IOSU/legacy/iosu_nim.h" #include "Cafe/IOSU/legacy/iosu_nim.h"
#include "Cafe/IOSU/PDM/iosu_pdm.h" #include "Cafe/IOSU/PDM/iosu_pdm.h"
#include "Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.h"
// IOSU initializer functions // IOSU initializer functions
#include "Cafe/IOSU/kernel/iosu_kernel.h" #include "Cafe/IOSU/kernel/iosu_kernel.h"
@ -51,6 +52,8 @@
#include "Cafe/OS/libs/gx2/GX2.h" #include "Cafe/OS/libs/gx2/GX2.h"
#include "Cafe/OS/libs/gx2/GX2_Misc.h" #include "Cafe/OS/libs/gx2/GX2_Misc.h"
#include "Cafe/OS/libs/mic/mic.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_aoc/nn_aoc.h"
#include "Cafe/OS/libs/nn_pdm/nn_pdm.h" #include "Cafe/OS/libs/nn_pdm/nn_pdm.h"
#include "Cafe/OS/libs/nn_cmpt/nn_cmpt.h" #include "Cafe/OS/libs/nn_cmpt/nn_cmpt.h"
@ -533,6 +536,7 @@ namespace CafeSystem
iosu::acp::GetModule(), iosu::acp::GetModule(),
iosu::fpd::GetModule(), iosu::fpd::GetModule(),
iosu::pdm::GetModule(), iosu::pdm::GetModule(),
iosu::ccr_nfc::GetModule(),
}; };
// initialize all subsystems which are persistent and don't depend on a game running // initialize all subsystems which are persistent and don't depend on a game running
@ -587,6 +591,8 @@ namespace CafeSystem
H264::Initialize(); H264::Initialize();
snd_core::Initialize(); snd_core::Initialize();
mic::Initialize(); mic::Initialize();
nfc::Initialize();
ntag::Initialize();
// init hardware register interfaces // init hardware register interfaces
HW_SI::Initialize(); HW_SI::Initialize();
} }

View File

@ -5,8 +5,28 @@ struct PPCCoreCallbackData_t
{ {
sint32 gprCount = 0; sint32 gprCount = 0;
sint32 floatCount = 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 // callback functions
inline uint32 PPCCoreCallback(MPTR function, const PPCCoreCallbackData_t& data) inline uint32 PPCCoreCallback(MPTR function, const PPCCoreCallbackData_t& data)
{ {
@ -16,23 +36,21 @@ inline uint32 PPCCoreCallback(MPTR function, const PPCCoreCallbackData_t& data)
template <typename T, typename... TArgs> template <typename T, typename... TArgs>
uint32 PPCCoreCallback(MPTR function, PPCCoreCallbackData_t& data, T currentArg, TArgs... args) uint32 PPCCoreCallback(MPTR function, PPCCoreCallbackData_t& data, T currentArg, TArgs... args)
{ {
cemu_assert_debug(data.gprCount <= 8); // TODO float arguments on stack
cemu_assert_debug(data.floatCount <= 8); cemu_assert_debug(data.floatCount < 8);
PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance(); PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance();
if constexpr (std::is_pointer_v<T>) if constexpr (std::is_pointer_v<T>)
{ {
hCPU->gpr[3 + data.gprCount] = MEMPTR(currentArg).GetMPTR(); _PPCCoreCallback_writeGPRArg(data, hCPU, MEMPTR(currentArg).GetMPTR());
data.gprCount++;
} }
else if constexpr (std::is_base_of_v<MEMPTRBase, std::remove_reference_t<T>>) else if constexpr (std::is_base_of_v<MEMPTRBase, std::remove_reference_t<T>>)
{ {
hCPU->gpr[3 + data.gprCount] = currentArg.GetMPTR(); _PPCCoreCallback_writeGPRArg(data, hCPU, currentArg.GetMPTR());
data.gprCount++;
} }
else if constexpr (std::is_reference_v<T>) else if constexpr (std::is_reference_v<T>)
{ {
hCPU->gpr[3 + data.gprCount] = MEMPTR(&currentArg).GetMPTR(); _PPCCoreCallback_writeGPRArg(data, hCPU, MEMPTR(&currentArg).GetMPTR());
data.gprCount++;
} }
else if constexpr(std::is_enum_v<T>) else if constexpr(std::is_enum_v<T>)
{ {
@ -53,8 +71,7 @@ uint32 PPCCoreCallback(MPTR function, PPCCoreCallbackData_t& data, T currentArg,
} }
else else
{ {
hCPU->gpr[3 + data.gprCount] = (uint32)currentArg; _PPCCoreCallback_writeGPRArg(data, hCPU, (uint32)currentArg);
data.gprCount++;
} }
return PPCCoreCallback(function, data, args...); return PPCCoreCallback(function, data, args...);

View File

@ -0,0 +1,406 @@
#include "iosu_ccr_nfc.h"
#include "Cafe/IOSU/kernel/iosu_kernel.h"
#include "util/crypto/aes128.h"
#include <openssl/evp.h>
#include <openssl/hmac.h>
namespace iosu
{
namespace ccr_nfc
{
IOSMsgQueueId sCCRNFCMsgQueue;
SysAllocator<iosu::kernel::IOSMessage, 0x20> 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<IPCCommandBody>(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<void>(cmd->args[1]);
uint32 sizeIn = cmd->args[2];
void* ptrOut = MEMPTR<void>(cmd->args[3]);
uint32 sizeOut = cmd->args[4];
if ((result = __CCRNFCValidateCryptData(static_cast<CCRNFCCryptData*>(ptrIn), sizeIn, true)) == 0 &&
(result = __CCRNFCValidateCryptData(static_cast<CCRNFCCryptData*>(ptrOut), sizeOut, false)) == 0)
{
// Initialize outData with inData
memcpy(ptrOut, ptrIn, sizeIn);
switch (requestId)
{
case 1: // encrypt
result = __CCRNFCCryptData(static_cast<CCRNFCCryptData*>(ptrIn), static_cast<CCRNFCCryptData*>(ptrOut), false);
break;
case 2: // decrypt
result = __CCRNFCCryptData(static_cast<CCRNFCCryptData*>(ptrIn), static_cast<CCRNFCCryptData*>(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<IOS_ERROR>(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<IOS_ERROR>(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;
}
}
}

View File

@ -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();
}
}

View File

@ -0,0 +1,139 @@
#include "TLV.h"
#include "stream.h"
#include <cassert>
TLV::TLV()
{
}
TLV::TLV(Tag tag, std::vector<std::byte> value)
: mTag(tag), mValue(std::move(value))
{
}
TLV::~TLV()
{
}
std::vector<TLV> TLV::FromBytes(const std::span<std::byte>& data)
{
bool hasTerminator = false;
std::vector<TLV> tlvs;
SpanStream stream(data, std::endian::big);
while (stream.GetRemaining() > 0 && !hasTerminator)
{
// Read the tag
uint8 byte;
stream >> byte;
Tag tag = static_cast<Tag>(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<std::byte>{});
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<std::byte> 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<std::byte> TLV::ToBytes() const
{
std::vector<std::byte> 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<std::byte>& TLV::GetValue() const
{
return mValue;
}
void TLV::SetTag(Tag tag)
{
mTag = tag;
}
void TLV::SetValue(const std::span<const std::byte>& value)
{
// Can only write max 16-bit lengths into TLV
cemu_assert(value.size() < 0x10000);
mValue.assign(value.begin(), value.end());
}

View File

@ -0,0 +1,37 @@
#pragma once
#include <cstdint>
#include <span>
#include <vector>
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<std::byte> value);
virtual ~TLV();
static std::vector<TLV> FromBytes(const std::span<std::byte>& data);
std::vector<std::byte> ToBytes() const;
Tag GetTag() const;
const std::vector<std::byte>& GetValue() const;
void SetTag(Tag tag);
void SetValue(const std::span<const std::byte>& value);
private:
Tag mTag;
std::vector<std::byte> mValue;
};

View File

@ -0,0 +1,301 @@
#include "TagV0.h"
#include "TLV.h"
#include <algorithm>
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> TagV0::FromBytes(const std::span<const std::byte>& 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<TagV0> tag = std::make_shared<TagV0>();
// 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<std::byte> 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<size_t>(-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<size_t>(-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<std::byte> TagV0::ToBytes() const
{
std::vector<std::byte> 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<std::byte> 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<std::byte>& TagV0::GetNDEFData() const
{
return mTLVs[mNdefTlvIdx].GetValue();
}
const std::vector<std::byte>& TagV0::GetLockedArea() const
{
return mLockedArea;
}
void TagV0::SetNDEFData(const std::span<const std::byte>& data)
{
// Update the ndef value
mTLVs[mNdefTlvIdx].SetValue(data);
}
bool TagV0::ParseLockedArea(const std::span<const std::byte>& 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<const std::byte>& data, std::vector<std::byte>& 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;
}

View File

@ -0,0 +1,39 @@
#pragma once
#include <memory>
#include <span>
#include <map>
#include "TLV.h"
class TagV0
{
public:
using Block = std::array<std::byte, 0x8>;
public:
TagV0();
virtual ~TagV0();
static std::shared_ptr<TagV0> FromBytes(const std::span<const std::byte>& data);
std::vector<std::byte> ToBytes() const;
const Block& GetUIDBlock() const;
const std::vector<std::byte>& GetNDEFData() const;
const std::vector<std::byte>& GetLockedArea() const;
void SetNDEFData(const std::span<const std::byte>& data);
private:
bool ParseLockedArea(const std::span<const std::byte>& data);
bool IsBlockLocked(uint8 blockIdx) const;
bool ParseDataArea(const std::span<const std::byte>& data, std::vector<std::byte>& dataArea);
bool ValidateCapabilityContainer();
std::map<uint8, Block> mLockedOrReservedBlocks;
std::map<uint8, Block> mLockedBlocks;
std::array<uint8, 0x4> mCapabilityContainer;
std::vector<TLV> mTLVs;
std::size_t mNdefTlvIdx;
std::vector<std::byte> mLockedArea;
};

View File

@ -0,0 +1,278 @@
#include "ndef.h"
#include <cassert>
namespace ndef
{
Record::Record()
: mFlags(0), mTNF(NDEF_TNF_EMPTY)
{
}
Record::~Record()
{
}
std::optional<Record> Record::FromStream(Stream& stream)
{
Record rec;
// Read record header
uint8 recHdr;
stream >> recHdr;
rec.mFlags = recHdr & ~NDEF_TNF_MASK;
rec.mTNF = static_cast<TypeNameFormat>(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<std::byte> Record::ToBytes(uint8 flags) const
{
std::vector<std::byte> 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<std::byte>& Record::GetID() const
{
return mID;
}
const std::vector<std::byte>& Record::GetType() const
{
return mType;
}
const std::vector<std::byte>& Record::GetPayload() const
{
return mPayload;
}
void Record::SetTNF(TypeNameFormat tnf)
{
mTNF = tnf;
}
void Record::SetID(const std::span<const std::byte>& 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<const std::byte>& type)
{
cemu_assert(type.size() < 0x100);
mType.assign(type.begin(), type.end());
}
void Record::SetPayload(const std::span<const std::byte>& 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> Message::FromBytes(const std::span<const std::byte>& data)
{
Message msg;
SpanStream stream(data, std::endian::big);
while (stream.GetRemaining() > 0)
{
std::optional<Record> 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<std::byte> Message::ToBytes() const
{
std::vector<std::byte> 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<std::byte> 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

View File

@ -0,0 +1,88 @@
#pragma once
#include <span>
#include <vector>
#include <optional>
#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<Record> FromStream(Stream& stream);
std::vector<std::byte> ToBytes(uint8 flags = 0) const;
TypeNameFormat GetTNF() const;
const std::vector<std::byte>& GetID() const;
const std::vector<std::byte>& GetType() const;
const std::vector<std::byte>& GetPayload() const;
void SetTNF(TypeNameFormat tnf);
void SetID(const std::span<const std::byte>& id);
void SetType(const std::span<const std::byte>& type);
void SetPayload(const std::span<const std::byte>& payload);
bool IsLast() const;
bool IsShort() const;
private:
uint8 mFlags;
TypeNameFormat mTNF;
std::vector<std::byte> mID;
std::vector<std::byte> mType;
std::vector<std::byte> mPayload;
};
class Message
{
public:
Message();
virtual ~Message();
static std::optional<Message> FromBytes(const std::span<const std::byte>& data);
std::vector<std::byte> 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<Record> mRecords;
};
} // namespace ndef

View File

@ -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<std::chrono::system_clock> touchTime;
std::chrono::time_point<std::chrono::system_clock> 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<NFCTagInfo> tagInfo;
fs::path tagPath;
std::shared_ptr<TagV0> 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<NFCUid> uid;
bool readOnly = false;
uint32 dataSize = 0;
StackAllocator<uint8, 0x200> data;
uint32 lockedDataSize = 0;
StackAllocator<uint8, 0x200> 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<std::chrono::system_clock>::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<std::chrono::system_clock>::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<std::byte*>(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<std::chrono::system_clock>::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;
}
}

View File

@ -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);
}

View File

@ -0,0 +1,201 @@
#include "stream.h"
#include <algorithm>
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<float>(i);
return *this;
}
Stream& Stream::operator>>(double& val)
{
uint64 i;
*this >> i;
val = std::bit_cast<double>(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<uint32>(val);
*this >> i;
return *this;
}
Stream& Stream::operator<<(double val)
{
uint64 i = std::bit_cast<uint64>(val);
*this >> i;
return *this;
}
void Stream::SetError(Error error)
{
mError = error;
}
bool Stream::NeedsSwap()
{
return mEndianness != std::endian::native;
}
VectorStream::VectorStream(std::vector<std::byte>& vector, std::endian endianness)
: Stream(endianness), mVector(vector), mPosition(0)
{
}
VectorStream::~VectorStream()
{
}
std::size_t VectorStream::Read(const std::span<std::byte>& 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<const std::byte>& 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<const std::byte> span, std::endian endianness)
: Stream(endianness), mSpan(std::move(span)), mPosition(0)
{
}
SpanStream::~SpanStream()
{
}
std::size_t SpanStream::Read(const std::span<std::byte>& 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<const std::byte>& 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;
}

View File

@ -0,0 +1,139 @@
#pragma once
#include <cstdint>
#include <vector>
#include <span>
#include <bit>
#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<std::byte>& data) = 0;
virtual std::size_t Write(const std::span<const std::byte>& 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<std::integral T>
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<std::integral T>
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<std::byte>& vector, std::endian endianness = std::endian::native);
virtual ~VectorStream();
virtual std::size_t Read(const std::span<std::byte>& data) override;
virtual std::size_t Write(const std::span<const std::byte>& 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<std::vector<std::byte>> mVector;
std::size_t mPosition;
};
class SpanStream : public Stream
{
public:
SpanStream(std::span<const std::byte> span, std::endian endianness = std::endian::native);
virtual ~SpanStream();
virtual std::size_t Read(const std::span<std::byte>& data) override;
virtual std::size_t Write(const std::span<const std::byte>& 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<const std::byte> mSpan;
std::size_t mPosition;
};

View File

@ -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)); 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<NFCTagInfoCallbackParam_t> _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) void nnNfpExport_Mount(PPCInterpreter_t* hCPU)
{ {
cemuLog_log(LogType::NN_NFP, "Mount()"); cemuLog_log(LogType::NN_NFP, "Mount()");
@ -769,6 +734,16 @@ void nnNfp_unloadAmiibo()
nnNfpUnlock(); 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) bool nnNfp_touchNfcTagFromFile(const fs::path& filePath, uint32* nfcError)
{ {
AmiiboRawNFCData rawData = { 0 }; AmiiboRawNFCData rawData = { 0 };
@ -960,6 +935,41 @@ void nnNfpExport_GetNfpState(PPCInterpreter_t* hCPU)
namespace nn::nfp 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<NFCTagInfoCallbackParam_t> _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 GetErrorCode(uint32 result)
{ {
uint32 level = (result >> 0x1b) & 3; uint32 level = (result >> 0x1b) & 3;
@ -1019,9 +1029,6 @@ namespace nn::nfp
nnNfp_load(); // legacy interface, update these to use cafeExportRegister / cafeExportRegisterFunc nnNfp_load(); // legacy interface, update these to use cafeExportRegister / cafeExportRegisterFunc
cafeExportRegisterFunc(nn::nfp::GetErrorCode, "nn_nfp", "GetErrorCode__Q2_2nn3nfpFRCQ2_2nn6Result", LogType::Placeholder); cafeExportRegisterFunc(nn::nfp::GetErrorCode, "nn_nfp", "GetErrorCode__Q2_2nn3nfpFRCQ2_2nn6Result", LogType::Placeholder);
// NFC API
cafeExportRegister("nn_nfp", NFCGetTagInfo, LogType::Placeholder);
} }
} }

View File

@ -2,12 +2,15 @@
namespace nn::nfp namespace nn::nfp
{ {
uint32 NFCGetTagInfo(uint32 index, uint32 timeout, MPTR functionPtr, void* userParam);
void load(); void load();
} }
void nnNfp_load(); void nnNfp_load();
void nnNfp_update(); void nnNfp_update();
bool nnNfp_isInitialized();
bool nnNfp_touchNfcTagFromFile(const fs::path& filePath, uint32* nfcError); bool nnNfp_touchNfcTagFromFile(const fs::path& filePath, uint32* nfcError);
#define NFP_STATE_NONE (0) #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_RW_MOUNT (5)
#define NFP_STATE_UNEXPECTED (6) #define NFP_STATE_UNEXPECTED (6)
#define NFP_STATE_RW_MOUNT_ROM (7) #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)

View File

@ -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<iosu::ccr_nfc::CCRNFCCryptData> 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<NTAGData> readResult;
StackAllocator<uint8, 0x1C8> rwData;
StackAllocator<uint8, 0x1C8> 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<iosu::ccr_nfc::CCRNFCCryptData> 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);
}
}

View File

@ -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();
}

View File

@ -51,6 +51,8 @@ const std::map<LogType, std::string> g_logging_window_mapping
{LogType::Socket, "Socket"}, {LogType::Socket, "Socket"},
{LogType::Save, "Save"}, {LogType::Save, "Save"},
{LogType::H264, "H264"}, {LogType::H264, "H264"},
{LogType::NFC, "NFC"},
{LogType::NTAG, "NTAG"},
{LogType::Patches, "Graphic pack patches"}, {LogType::Patches, "Graphic pack patches"},
{LogType::TextureCache, "Texture cache"}, {LogType::TextureCache, "Texture cache"},
{LogType::TextureReadback, "Texture readback"}, {LogType::TextureReadback, "Texture readback"},

View File

@ -44,6 +44,9 @@ enum class LogType : sint32
nlibcurl = 41, nlibcurl = 41,
PRUDP = 40, PRUDP = 40,
NFC = 41,
NTAG = 42,
}; };
template <> template <>

View File

@ -1,4 +1,5 @@
#include "Common/unix/FileStream_unix.h" #include "Common/unix/FileStream_unix.h"
#include <cstdarg>
fs::path findPathCI(const fs::path& path) fs::path findPathCI(const fs::path& path)
{ {

View File

@ -12,7 +12,7 @@
#include "audio/audioDebuggerWindow.h" #include "audio/audioDebuggerWindow.h"
#include "gui/canvas/OpenGLCanvas.h" #include "gui/canvas/OpenGLCanvas.h"
#include "gui/canvas/VulkanCanvas.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 "Cafe/OS/libs/swkbd/swkbd.h"
#include "gui/debugger/DebuggerWindow2.h" #include "gui/debugger/DebuggerWindow2.h"
#include "util/helpers/helpers.h" #include "util/helpers/helpers.h"
@ -261,7 +261,7 @@ public:
return false; return false;
uint32 nfcError; uint32 nfcError;
std::string path = filenames[0].utf8_string(); std::string path = filenames[0].utf8_string();
if (nnNfp_touchNfcTagFromFile(_utf8ToPath(path), &nfcError)) if (nfc::TouchTagFromFile(_utf8ToPath(path), &nfcError))
{ {
GetConfig().AddRecentNfcFile(path); GetConfig().AddRecentNfcFile(path);
m_window->UpdateNFCMenu(); m_window->UpdateNFCMenu();
@ -269,10 +269,10 @@ public:
} }
else else
{ {
if (nfcError == NFC_ERROR_NO_ACCESS) if (nfcError == NFC_TOUCH_TAG_ERROR_NO_ACCESS)
wxMessageBox(_("Cannot open file"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR); wxMessageBox(_("Cannot open file"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR);
else if (nfcError == NFC_ERROR_INVALID_FILE_FORMAT) else if (nfcError == NFC_TOUCH_TAG_ERROR_INVALID_FILE_FORMAT)
wxMessageBox(_("Not a valid NFC NTAG215 file"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR); wxMessageBox(_("Not a valid NFC file"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR);
return false; return false;
} }
} }
@ -749,12 +749,12 @@ void MainWindow::OnNFCMenu(wxCommandEvent& event)
return; return;
wxString wxStrFilePath = openFileDialog.GetPath(); wxString wxStrFilePath = openFileDialog.GetPath();
uint32 nfcError; 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")); wxMessageBox(_("Cannot open file"));
else if (nfcError == NFC_ERROR_INVALID_FILE_FORMAT) else if (nfcError == NFC_TOUCH_TAG_ERROR_INVALID_FILE_FORMAT)
wxMessageBox(_("Not a valid NFC NTAG215 file")); wxMessageBox(_("Not a valid NFC file"));
} }
else else
{ {
@ -772,12 +772,12 @@ void MainWindow::OnNFCMenu(wxCommandEvent& event)
if (!path.empty()) if (!path.empty())
{ {
uint32 nfcError = 0; 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")); wxMessageBox(_("Cannot open file"));
else if (nfcError == NFC_ERROR_INVALID_FILE_FORMAT) else if (nfcError == NFC_TOUCH_TAG_ERROR_INVALID_FILE_FORMAT)
wxMessageBox(_("Not a valid NFC NTAG215 file")); wxMessageBox(_("Not a valid NFC file"));
} }
else 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::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::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::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->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::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)); debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::TextureCache), _("&Texture cache warnings"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::TextureCache));