mirror of
https://github.com/cemu-project/Cemu.git
synced 2024-11-29 04:24:17 +01:00
Add initial ntag and nfc implementation
This commit is contained in:
parent
84e78088fb
commit
1c6b209692
@ -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
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
406
src/Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.cpp
Normal file
406
src/Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.cpp
Normal 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_t inSize, void* outData, uint32_t outSize)
|
||||||
|
{
|
||||||
|
uint8_t 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_t nameSize, const uint8* inData, uint32_t inSize, uint8* outData, uint32_t 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_t buffer[0x50];
|
||||||
|
buffer[0] = 0;
|
||||||
|
buffer[1] = 0;
|
||||||
|
memcpy(buffer + 2, name, nameSize);
|
||||||
|
memcpy(buffer + nameSize + 2, inData, inSize);
|
||||||
|
|
||||||
|
uint16_t 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_t lockedSecretBuffer[0x40] = { 0 };
|
||||||
|
uint8_t unfixedInfosBuffer[0x40] = { 0 };
|
||||||
|
uint8_t 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_t 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_t 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_t 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
31
src/Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.h
Normal file
31
src/Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.h
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
139
src/Cafe/OS/libs/nfc/TLV.cpp
Normal file
139
src/Cafe/OS/libs/nfc/TLV.cpp
Normal 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_t 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_t 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 << std::uint8_t(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 << std::uint8_t(0xff);
|
||||||
|
stream << std::uint16_t(mValue.size());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
stream << std::uint8_t(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());
|
||||||
|
}
|
37
src/Cafe/OS/libs/nfc/TLV.h
Normal file
37
src/Cafe/OS/libs/nfc/TLV.h
Normal 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;
|
||||||
|
};
|
301
src/Cafe/OS/libs/nfc/TagV0.cpp
Normal file
301
src/Cafe/OS/libs/nfc/TagV0.cpp
Normal 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 std::uint8_t kLockbyteBlock0 = 0xe;
|
||||||
|
constexpr std::uint8_t kLockbytesStart0 = 0x0;
|
||||||
|
constexpr std::uint8_t kLockbytesEnd0 = 0x2;
|
||||||
|
constexpr std::uint8_t kLockbyteBlock1 = 0xf;
|
||||||
|
constexpr std::uint8_t kLockbytesStart1 = 0x2;
|
||||||
|
constexpr std::uint8_t kLockbytesEnd1 = 0x8;
|
||||||
|
|
||||||
|
constexpr std::uint8_t kNDEFMagicNumber = 0xe1;
|
||||||
|
|
||||||
|
// These blocks are not part of the locked area
|
||||||
|
constexpr bool IsBlockLockedOrReserved(std::uint8_t 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 (std::uint8_t 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)
|
||||||
|
{
|
||||||
|
std::uint8_t currentBlock = 0;
|
||||||
|
|
||||||
|
// Start by parsing the first set of lock bytes
|
||||||
|
for (std::uint8_t i = kLockbytesStart0; i < kLockbytesEnd0; i++)
|
||||||
|
{
|
||||||
|
std::uint8_t lockByte = std::uint8_t(data[kLockbyteBlock0 * sizeof(Block) + i]);
|
||||||
|
|
||||||
|
// Iterate over the individual bits in the lock byte
|
||||||
|
for (std::uint8_t 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 (std::uint8_t i = kLockbytesStart1; i < kLockbytesEnd1; i++) {
|
||||||
|
std::uint8_t lockByte = std::uint8_t(data[kLockbyteBlock1 * sizeof(Block) + i]);
|
||||||
|
|
||||||
|
// Iterate over the individual bits in the lock byte
|
||||||
|
for (std::uint8_t 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(std::uint8_t blockIdx) const
|
||||||
|
{
|
||||||
|
return mLockedBlocks.contains(blockIdx) || IsBlockLockedOrReserved(blockIdx);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TagV0::ParseDataArea(const std::span<const std::byte>& data, std::vector<std::byte>& dataArea)
|
||||||
|
{
|
||||||
|
for (std::uint8_t 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
|
||||||
|
std::uint8_t nmn = mCapabilityContainer[0];
|
||||||
|
if (nmn != kNDEFMagicNumber)
|
||||||
|
{
|
||||||
|
cemuLog_log(LogType::Force, "Error: CC: Invalid NDEF Magic Number");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Version Number
|
||||||
|
std::uint8_t vno = mCapabilityContainer[1];
|
||||||
|
if (vno >> 4 != 1)
|
||||||
|
{
|
||||||
|
cemuLog_log(LogType::Force, "Error: CC: Invalid Version Number");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tag memory size
|
||||||
|
std::uint8_t tms = mCapabilityContainer[2];
|
||||||
|
if (8u * (tms + 1) < kTagSize)
|
||||||
|
{
|
||||||
|
cemuLog_log(LogType::Force, "Error: CC: Incomplete tag memory size");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
39
src/Cafe/OS/libs/nfc/TagV0.h
Normal file
39
src/Cafe/OS/libs/nfc/TagV0.h
Normal 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(std::uint8_t blockIdx) const;
|
||||||
|
bool ParseDataArea(const std::span<const std::byte>& data, std::vector<std::byte>& dataArea);
|
||||||
|
bool ValidateCapabilityContainer();
|
||||||
|
|
||||||
|
std::map<std::uint8_t, Block> mLockedOrReservedBlocks;
|
||||||
|
std::map<std::uint8_t, Block> mLockedBlocks;
|
||||||
|
std::array<std::uint8_t, 0x4> mCapabilityContainer;
|
||||||
|
std::vector<TLV> mTLVs;
|
||||||
|
std::size_t mNdefTlvIdx;
|
||||||
|
std::vector<std::byte> mLockedArea;
|
||||||
|
};
|
277
src/Cafe/OS/libs/nfc/ndef.cpp
Normal file
277
src/Cafe/OS/libs/nfc/ndef.cpp
Normal file
@ -0,0 +1,277 @@
|
|||||||
|
#include "ndef.h"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
namespace ndef
|
||||||
|
{
|
||||||
|
|
||||||
|
Record::Record()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Record::~Record()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<Record> Record::FromStream(Stream& stream)
|
||||||
|
{
|
||||||
|
Record rec;
|
||||||
|
|
||||||
|
// Read record header
|
||||||
|
uint8_t recHdr;
|
||||||
|
stream >> recHdr;
|
||||||
|
rec.mFlags = recHdr & ~NDEF_TNF_MASK;
|
||||||
|
rec.mTNF = static_cast<TypeNameFormat>(recHdr & NDEF_TNF_MASK);
|
||||||
|
|
||||||
|
// Type length
|
||||||
|
uint8_t typeLen;
|
||||||
|
stream >> typeLen;
|
||||||
|
|
||||||
|
// Payload length;
|
||||||
|
uint32_t payloadLen;
|
||||||
|
if (recHdr & NDEF_SR)
|
||||||
|
{
|
||||||
|
uint8_t len;
|
||||||
|
stream >> len;
|
||||||
|
payloadLen = len;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
stream >> payloadLen;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some sane limits for the payload size
|
||||||
|
if (payloadLen > 2 * 1024 * 1024)
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ID length
|
||||||
|
uint8_t 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_t flags) const
|
||||||
|
{
|
||||||
|
std::vector<std::byte> bytes;
|
||||||
|
VectorStream stream(bytes, std::endian::big);
|
||||||
|
|
||||||
|
// Combine flags (clear message begin and end flags)
|
||||||
|
std::uint8_t finalFlags = mFlags & ~(NDEF_MB | NDEF_ME);
|
||||||
|
finalFlags |= flags;
|
||||||
|
|
||||||
|
// Write flags + tnf
|
||||||
|
stream << std::uint8_t(finalFlags | std::uint8_t(mTNF));
|
||||||
|
|
||||||
|
// Type length
|
||||||
|
stream << std::uint8_t(mType.size());
|
||||||
|
|
||||||
|
// Payload length
|
||||||
|
if (IsShort())
|
||||||
|
{
|
||||||
|
stream << std::uint8_t(mPayload.size());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
stream << std::uint32_t(mPayload.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ID length
|
||||||
|
if (mFlags & NDEF_IL)
|
||||||
|
{
|
||||||
|
stream << std::uint8_t(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++)
|
||||||
|
{
|
||||||
|
std::uint8_t 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
|
88
src/Cafe/OS/libs/nfc/ndef.h
Normal file
88
src/Cafe/OS/libs/nfc/ndef.h
Normal 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_t 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_t 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
|
596
src/Cafe/OS/libs/nfc/nfc.cpp
Normal file
596
src/Cafe/OS/libs/nfc/nfc.cpp
Normal file
@ -0,0 +1,596 @@
|
|||||||
|
#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"
|
||||||
|
|
||||||
|
// TODO move errors to header and allow ntag to convert them
|
||||||
|
|
||||||
|
#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_RAW 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> discoveryTimeout;
|
||||||
|
|
||||||
|
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 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
sint32 NFCShutdown(uint32 chan)
|
||||||
|
{
|
||||||
|
cemu_assert(chan < 2);
|
||||||
|
|
||||||
|
NFCContext* ctx = &gNFCContexts[chan];
|
||||||
|
|
||||||
|
__NFCClearContext(ctx);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NFCIsInit(uint32 chan)
|
||||||
|
{
|
||||||
|
cemu_assert(chan < 2);
|
||||||
|
|
||||||
|
return gNFCContexts[chan].isInitialized;
|
||||||
|
}
|
||||||
|
|
||||||
|
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_t, 0x200> data;
|
||||||
|
uint32 lockedDataSize = 0;
|
||||||
|
StackAllocator<uint8_t, 0x200> lockedData;
|
||||||
|
|
||||||
|
if (ctx->tag)
|
||||||
|
{
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
// Fill in uid
|
||||||
|
memcpy(uid.GetPointer(), ctx->tag->GetUIDBlock().data(), sizeof(NFCUid));
|
||||||
|
|
||||||
|
result = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = -0xBFE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = -0xBFE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear tag status after read
|
||||||
|
// TODO this is not really nice here
|
||||||
|
ctx->nfcStatus &= ~NFC_STATUS_HAS_TAG;
|
||||||
|
ctx->tag = {};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = -0x1DD;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
// TODO write to file
|
||||||
|
|
||||||
|
PPCCoreCallback(ctx->writeCallback, chan, 0, 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 = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = -0x9DD;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't actually send any commands/responses
|
||||||
|
uint32 responseSize = 0;
|
||||||
|
void* responseData = nullptr;
|
||||||
|
|
||||||
|
PPCCoreCallback(ctx->rawCallback, chan, result, responseSize, responseData, ctx->rawContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NFCProc(uint32 chan)
|
||||||
|
{
|
||||||
|
cemu_assert(chan < 2);
|
||||||
|
|
||||||
|
NFCContext* ctx = &gNFCContexts[chan];
|
||||||
|
|
||||||
|
if (!ctx->isInitialized)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (ctx->hasTag && ctx->state == NFC_STATE_IDLE)
|
||||||
|
{
|
||||||
|
if (ctx->tagDetectCallback)
|
||||||
|
{
|
||||||
|
PPCCoreCallback(ctx->tagDetectCallback, chan, false, ctx->tagDetectContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->hasTag = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (ctx->state)
|
||||||
|
{
|
||||||
|
case NFC_STATE_INITIALIZED:
|
||||||
|
ctx->state = NFC_STATE_IDLE;
|
||||||
|
break;
|
||||||
|
case NFC_STATE_IDLE:
|
||||||
|
break;
|
||||||
|
case NFC_STATE_READ:
|
||||||
|
// Do we have a tag or did the timeout expire?
|
||||||
|
if ((ctx->nfcStatus & NFC_STATUS_HAS_TAG) || ctx->discoveryTimeout < std::chrono::system_clock::now())
|
||||||
|
{
|
||||||
|
__NFCHandleRead(chan);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case NFC_STATE_WRITE:
|
||||||
|
__NFCHandleWrite(chan);
|
||||||
|
break;
|
||||||
|
case NFC_STATE_ABORT:
|
||||||
|
__NFCHandleAbort(chan);
|
||||||
|
break;
|
||||||
|
case NFC_STATE_RAW:
|
||||||
|
// Do we have a tag or did the timeout expire?
|
||||||
|
if ((ctx->nfcStatus & NFC_STATUS_HAS_TAG) || ctx->discoveryTimeout < std::chrono::system_clock::now())
|
||||||
|
{
|
||||||
|
__NFCHandleRaw(chan);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 -0xAE0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx->state == NFC_STATE_UNINITIALIZED)
|
||||||
|
{
|
||||||
|
return -0xADF;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->mode = mode;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 -0x6E0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx->state <= NFC_STATE_IDLE)
|
||||||
|
{
|
||||||
|
return -0x6DF;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->state = NFC_STATE_ABORT;
|
||||||
|
ctx->abortCallback = callback;
|
||||||
|
ctx->abortContext = context;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
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];
|
||||||
|
|
||||||
|
// TODO convert error
|
||||||
|
error = 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 result; // TODO convert 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 -0x9E0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only allow discovery
|
||||||
|
if (!startDiscovery)
|
||||||
|
{
|
||||||
|
return -0x9DC;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (NFCGetMode(chan) == NFC_MODE_ACTIVE && NFCSetMode(chan, NFC_MODE_IDLE) < 0)
|
||||||
|
{
|
||||||
|
return -0x9DC;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx->state != NFC_STATE_IDLE)
|
||||||
|
{
|
||||||
|
return -0x9DF;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->state = NFC_STATE_RAW;
|
||||||
|
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 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 -0x1E0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (NFCGetMode(chan) == NFC_MODE_ACTIVE && NFCSetMode(chan, NFC_MODE_IDLE) < 0)
|
||||||
|
{
|
||||||
|
return -0x1DC;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx->state != NFC_STATE_IDLE)
|
||||||
|
{
|
||||||
|
return -0x1DF;
|
||||||
|
}
|
||||||
|
|
||||||
|
cemuLog_log(LogType::NFC, "starting read");
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO uid filter?
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 -0x2e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (NFCGetMode(chan) == NFC_MODE_ACTIVE && NFCSetMode(chan, NFC_MODE_IDLE) < 0)
|
||||||
|
{
|
||||||
|
return -0x2dc;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx->state != NFC_STATE_IDLE)
|
||||||
|
{
|
||||||
|
return -0x1df;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO uid filter?
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
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_ERROR_NO_ACCESS;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->tag = TagV0::FromBytes(std::as_bytes(std::span(nfcData->data(), nfcData->size())));
|
||||||
|
if (!ctx->tag)
|
||||||
|
{
|
||||||
|
*nfcError = NFC_ERROR_INVALID_FILE_FORMAT;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->nfcStatus |= NFC_STATUS_HAS_TAG;
|
||||||
|
ctx->tagPath = filePath;
|
||||||
|
|
||||||
|
*nfcError = NFC_ERROR_NONE;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
62
src/Cafe/OS/libs/nfc/nfc.h
Normal file
62
src/Cafe/OS/libs/nfc/nfc.h
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// CEMU NFC error codes
|
||||||
|
#define NFC_ERROR_NONE (0)
|
||||||
|
#define NFC_ERROR_NO_ACCESS (1)
|
||||||
|
#define NFC_ERROR_INVALID_FILE_FORMAT (2)
|
||||||
|
|
||||||
|
#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);
|
||||||
|
}
|
201
src/Cafe/OS/libs/nfc/stream.cpp
Normal file
201
src/Cafe/OS/libs/nfc/stream.cpp
Normal 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)
|
||||||
|
{
|
||||||
|
std::uint8_t i;
|
||||||
|
*this >> i;
|
||||||
|
val = !!i;
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream& Stream::operator>>(float& val)
|
||||||
|
{
|
||||||
|
std::uint32_t i;
|
||||||
|
*this >> i;
|
||||||
|
val = std::bit_cast<float>(i);
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream& Stream::operator>>(double& val)
|
||||||
|
{
|
||||||
|
std::uint64_t i;
|
||||||
|
*this >> i;
|
||||||
|
val = std::bit_cast<double>(i);
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream& Stream::operator<<(bool val)
|
||||||
|
{
|
||||||
|
std::uint8_t i = val;
|
||||||
|
*this >> i;
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream& Stream::operator<<(float val)
|
||||||
|
{
|
||||||
|
std::uint32_t i = std::bit_cast<std::uint32_t>(val);
|
||||||
|
*this >> i;
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream& Stream::operator<<(double val)
|
||||||
|
{
|
||||||
|
std::uint64_t i = std::bit_cast<std::uint64_t>(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;
|
||||||
|
}
|
139
src/Cafe/OS/libs/nfc/stream.h
Normal file
139
src/Cafe/OS/libs/nfc/stream.h
Normal 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;
|
||||||
|
};
|
@ -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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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)
|
|
||||||
|
438
src/Cafe/OS/libs/ntag/ntag.cpp
Normal file
438
src/Cafe/OS/libs/ntag/ntag.cpp
Normal file
@ -0,0 +1,438 @@
|
|||||||
|
#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
|
||||||
|
{
|
||||||
|
|
||||||
|
};
|
||||||
|
NTAGWriteData gWriteData[2];
|
||||||
|
|
||||||
|
bool ccrNfcOpened = false;
|
||||||
|
IOSDevHandle gCcrNfcHandle;
|
||||||
|
|
||||||
|
NTAGFormatSettings gFormatSettings;
|
||||||
|
|
||||||
|
MPTR gDetectCallbacks[2];
|
||||||
|
MPTR gAbortCallbacks[2];
|
||||||
|
MPTR gReadCallbacks[2];
|
||||||
|
MPTR gWriteCallbacks[2];
|
||||||
|
|
||||||
|
sint32 __NTAGConvertNFCError(sint32 error)
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
sint32 NTAGInit(uint32 chan)
|
||||||
|
{
|
||||||
|
return NTAGInitEx(chan);
|
||||||
|
}
|
||||||
|
|
||||||
|
sint32 NTAGInitEx(uint32 chan)
|
||||||
|
{
|
||||||
|
sint32 result = nfc::NFCInitEx(chan, 1);
|
||||||
|
return __NTAGConvertNFCError(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 __NTAGConvertNFCError(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.indentifyCode = _swapEndianU32(formatSettings->indentifyCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
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, __NTAGConvertNFCError(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 __NTAGConvertNFCError(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, 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)
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
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));
|
||||||
|
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 = __NTAGConvertNFCError(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.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 __NTAGConvertNFCError(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
void __NTAGReadBeforeWriteCallback(PPCInterpreter_t* hCPU)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
|
||||||
|
gWriteCallbacks[chan] = callback;
|
||||||
|
|
||||||
|
nfc::NFCUid _uid{}, _uidMask{};
|
||||||
|
if (uid)
|
||||||
|
{
|
||||||
|
memcpy(&_uid, uid, sizeof(*uid));
|
||||||
|
}
|
||||||
|
memset(_uidMask.uid, 0xff, sizeof(_uidMask.uid));
|
||||||
|
|
||||||
|
// TODO save write data
|
||||||
|
|
||||||
|
// TODO we probably don't need to read first here
|
||||||
|
sint32 result = nfc::NFCRead(chan, timeout, &_uid, &_uidMask, RPLLoader_MakePPCCallable(__NTAGReadBeforeWriteCallback), context);
|
||||||
|
return __NTAGConvertNFCError(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
sint32 NTAGFormat(uint32 chan, uint32 timeout, nfc::NFCUid* uid, uint32 rwSize, void* rwData, MPTR callback, void* context)
|
||||||
|
{
|
||||||
|
cemu_assert(chan < 2);
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
94
src/Cafe/OS/libs/ntag/ntag.h
Normal file
94
src/Cafe/OS/libs/ntag/ntag.h
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "Cafe/OS/libs/nfc/nfc.h"
|
||||||
|
|
||||||
|
namespace ntag
|
||||||
|
{
|
||||||
|
struct NTAGFormatSettings
|
||||||
|
{
|
||||||
|
/* +0x00 */ uint8 version;
|
||||||
|
/* +0x04 */ uint32 makerCode;
|
||||||
|
/* +0x08 */ uint32 indentifyCode;
|
||||||
|
/* +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();
|
||||||
|
}
|
@ -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"},
|
||||||
|
@ -44,6 +44,9 @@ enum class LogType : sint32
|
|||||||
nlibcurl = 41,
|
nlibcurl = 41,
|
||||||
|
|
||||||
PRUDP = 40,
|
PRUDP = 40,
|
||||||
|
|
||||||
|
NFC = 41,
|
||||||
|
NTAG = 42,
|
||||||
};
|
};
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
|
@ -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();
|
||||||
@ -749,7 +749,7 @@ 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_ERROR_NO_ACCESS)
|
||||||
wxMessageBox(_("Cannot open file"));
|
wxMessageBox(_("Cannot open file"));
|
||||||
@ -772,7 +772,7 @@ 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_ERROR_NO_ACCESS)
|
||||||
wxMessageBox(_("Cannot open file"));
|
wxMessageBox(_("Cannot open file"));
|
||||||
@ -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));
|
||||||
|
Loading…
Reference in New Issue
Block a user