#include "Cafe/OS/common/OSCommon.h" #include "Cafe/HW/Espresso/PPCCallback.h" #include "Cafe/OS/libs/nn_common.h" #include "nn_nfp.h" #include "Cafe/OS/libs/coreinit/coreinit_Thread.h" #include "Common/FileStream.h" #include "Cafe/CafeSystem.h" std::recursive_mutex g_nfpMutex; void nnNfpLock() { g_nfpMutex.lock(); } bool nnNfpTryLock() { return g_nfpMutex.try_lock(); } void nnNfpUnlock() { g_nfpMutex.unlock(); } struct AmiiboInternal { /* +0x000 */ uint16 lockBytes; /* +0x002 */ uint16 staticLock; /* +0x004 */ uint32 cc; /* +0x008 */ uint8 dataHMAC[32]; /* +0x028 */ uint8 ukn_A5; // always 0xA5 /* +0x029 */ uint8 writeCounterHigh; /* +0x029 */ uint8 writeCounterLow; /* +0x02B */ uint8 unk02B; /* encrypted region starts here */ struct { /* +0x02C */ uint8 flags; /* +0x02D */ uint8 countryCode; /* +0x02E */ uint16be crcWriteCounter; /* +0x030 */ uint16be date1; /* +0x032 */ uint16be date2; /* +0x034 */ uint32be crc; /* +0x038 */ uint16be nickname[10]; /* +0x04C */ uint8 mii[0x60]; /* +0x0AC */ uint32be appDataTitleIdHigh; /* +0x0B0 */ uint32be appDataTitleIdLow; /* +0x0B4 */ uint16be appWriteCounter; /* +0x0B6 */ uint16be appDataIdHigh; /* +0x0B8 */ uint16be appDataIdLow; /* +0x0BA */ uint16be ukn0BA; /* +0x0BC */ uint32be ukn0BC; /* +0x0C0 */ uint32be ukn0C0; /* +0x0C4 */ uint32be ukn0C4; /* +0x0C8 */ uint32be ukn0C8; /* +0x0CC */ uint32be ukn0CC; /* +0x0D0 */ uint32be ukn0D0; /* +0x0D4 */ uint32be ukn0D4; /* +0x0D8 */ uint32be ukn0D8; uint32 getAppDataAppId() { uint32 appId = (uint32)appDataIdHigh << 16; appId |= (uint32)appDataIdLow; return appId; } void setAppDataAppId(uint32 appId) { appDataIdHigh = (uint16)(appId >> 16); appDataIdLow = (uint16)(appId & 0xFFFF); } }amiiboSettings; /* +0x0DC */ uint8 applicationData[0xD8]; /* encrypted region ends here */ /* +0x1B4 */ uint8 tagHMAC[32]; /* +0x1D4 */ uint8 ntagSerial[7]; /* +0x1DB */ uint8 nintendoId; struct { /* +0x1DC */ uint8 gameAndCharacterId[2]; /* +0x1DE */ uint8 characterVariation; /* +0x1DF */ uint8 amiiboFigureType; /* +0x1E0 */ uint8 amiiboModelNumber[2]; /* +0x1E2 */ uint8 amiiboSeries; /* +0x1E3 */ uint8 ukn_02; // always 0x02 ? /* +0x1E4 */ uint8 ukn5C[4]; }amiiboIdentificationBlock; /* +0x1E8 */ uint8 keygenSalt[32]; }; static_assert(sizeof(AmiiboInternal) == 0x208); static_assert(offsetof(AmiiboInternal, dataHMAC) == 0x8); static_assert(offsetof(AmiiboInternal, amiiboSettings.appDataIdHigh) == 0xB6); static_assert(offsetof(AmiiboInternal, amiiboSettings.ukn0D8) == 0xD8); static_assert(offsetof(AmiiboInternal, tagHMAC) == 0x1B4); union AmiiboRawNFCData { // each page is 4 bytes struct { uint8 page0[4]; uint8 page1[4]; uint8 page2[4]; uint8 page3[4]; uint8 page4[4]; uint8 page5[4]; uint8 page6[4]; uint8 page7[4]; uint8 page8[4]; uint8 page9[4]; uint8 page10[4]; uint8 page11[4]; uint8 page12[4]; uint8 page13[4]; uint8 page14[4]; uint8 page15[4]; uint8 page16[4]; }; struct { uint8 rawByte[16 * 4]; }; struct { /* +0x000 */ uint8 ntagSerial[7]; /* +0x007 */ uint8 nintendoId; /* +0x008 */ uint8 lockBytes[2]; /* +0x00A */ uint8 staticLock[2]; /* +0x00C */ uint8 cc[4]; // compatibility container /* +0x010 */ uint8 ukn_A5; // always 0xA5 /* +0x011 */ uint8 writeCounter[2]; /* +0x013 */ uint8 unk013; /* +0x014 */ uint8 encryptedSettings[32]; /* +0x034 */ uint8 tagHMAC[32]; // data hmac /* +0x054 */ struct { /* +0x54 */ uint8 gameAndCharacterId[2]; /* +0x56 */ uint8 characterVariation; /* +0x57 */ uint8 amiiboFigureType; /* +0x58 */ uint8 amiiboModelNumber[2]; /* +0x5A */ uint8 amiiboSeries; /* +0x5B */ uint8 ukn_02; // always 0x02 ? /* +0x5C */ uint8 ukn5C[4]; }amiiboIdentificationBlock; /* +0x060 */ uint8 keygenSalt[32]; /* +0x080 */ uint8 dataHMAC[32]; /* +0x0A0 */ uint8 encryptedMii[0x60]; /* +0x100 */ uint8 encryptedTitleId[8]; /* +0x108 */ uint8 encryptedApplicationWriteCounter[2]; /* +0x10A */ uint8 encryptedApplicationAreaId[4]; /* +0x10E */ uint8 ukn10E[2]; /* +0x110 */ uint8 unk110[32]; /* +0x130 */ uint8 encryptedApplicationArea[0xD8]; /* +0x208 */ uint8 dynamicLock[4]; /* +0x20C */ uint8 cfg0[4]; /* +0x210 */ uint8 cfg1[4]; }; }; static_assert(sizeof(AmiiboRawNFCData) == 532); struct AmiiboProcessedData { uint8 uidLength; uint8 uid[7]; }; struct { bool nfpIsInitialized; MPTR activateEvent; MPTR deactivateEvent; bool isDetecting; bool isMounted; bool isReadOnly; bool hasOpenApplicationArea; // set to true if application area was opened or created // currently active Amiibo bool hasActiveAmiibo; std::wstring amiiboPath; bool hasInvalidHMAC; uint32 amiiboTouchTime; AmiiboRawNFCData amiiboNFCData; // raw data AmiiboInternal amiiboInternal; // decrypted internal format AmiiboProcessedData amiiboProcessedData; }nfp_data = { 0 }; bool nnNfp_touchNfcTagFromFile(const wchar_t* filePath, uint32* nfcError); bool nnNfp_writeCurrentAmiibo(); #include "AmiiboCrypto.h" void nnNfpExport_SetActivateEvent(PPCInterpreter_t* hCPU) { // parameters: // r3 OSEvent_t* ppcDefineParamStructPtr(osEvent, coreinit::OSEvent, 0); ppcDefineParamMPTR(osEventMPTR, 0); debug_printf("nn_nfp.SetActivateEvent(0x%08x)\n", osEventMPTR); coreinit::OSInitEvent(osEvent, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_AUTO); nnNfpLock(); nfp_data.activateEvent = osEventMPTR; nnNfpUnlock(); osLib_returnFromFunction(hCPU, 0); // return value ukn } void nnNfpExport_SetDeactivateEvent(PPCInterpreter_t* hCPU) { ppcDefineParamStructPtr(osEvent, coreinit::OSEvent, 0); ppcDefineParamMPTR(osEventMPTR, 0); cemuLog_log(LogType::nn_nfp, "SetDeactivateEvent(0x{:08x})", osEventMPTR); coreinit::OSInitEvent(osEvent, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_AUTO); nnNfpLock(); nfp_data.deactivateEvent = osEventMPTR; nnNfpUnlock(); osLib_returnFromFunction(hCPU, 0); // return value ukn } void nnNfpExport_Initialize(PPCInterpreter_t* hCPU) { debug_printf("Nfp Initialize()\n"); nfp_data.nfpIsInitialized = true; nfp_data.isDetecting = false; nfp_data.hasActiveAmiibo = false; nfp_data.hasOpenApplicationArea = false; nfp_data.activateEvent = MPTR_NULL; nfp_data.deactivateEvent = MPTR_NULL; osLib_returnFromFunction(hCPU, 0); } void nnNfpExport_StartDetection(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::nn_nfp, "StartDetection()"); nnNfpLock(); nfp_data.isDetecting = true; nnNfpUnlock(); osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0)); } void nnNfpExport_StopDetection(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::nn_nfp, "StopDetection()"); nnNfpLock(); nfp_data.isDetecting = false; nnNfpUnlock(); osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0)); } #define NFP_TAG_MAX_LENGTH (10) typedef struct { /* +0x00 */ uint8 uidLength; /* +0x01 */ uint8 uid[0xA]; /* +0x0B */ uint8 unused0B[0x15]; /* +0x20 */ uint32 ukn20[4]; uint32 ukn30[4]; uint32 ukn40[4]; uint32 ukn50; }nfpTagInfo_t; static_assert(sizeof(nfpTagInfo_t) == 0x54, "nfpTagInfo_t has invalid size"); void nnNfpExport_GetTagInfo(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::nn_nfp, "GetTagInfo(0x{:08x})", hCPU->gpr[3]); ppcDefineParamStructPtr(tagInfo, nfpTagInfo_t, 0); nnNfpLock(); if (nfp_data.hasActiveAmiibo == false) { nnNfpUnlock(); osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, 0)); // todo: Return correct error code return; } memset(tagInfo, 0x00, sizeof(nfpTagInfo_t)); memcpy(tagInfo->uid, nfp_data.amiiboProcessedData.uid, nfp_data.amiiboProcessedData.uidLength); tagInfo->uidLength = (uint8)nfp_data.amiiboProcessedData.uidLength; // todo - remaining values nnNfpUnlock(); osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0)); } typedef struct { /* +0x00 */ uint8 uidLength; /* +0x01 */ uint8 uid[0xA]; /* +0x0B */ uint8 ukn0B; /* +0x0C */ uint8 ukn0C; /* +0x0D */ uint8 ukn0D; // more? }NFCTagInfoCallbackParam_t; uint32 NFCGetTagInfo(uint32 index, uint32 timeout, MPTR functionPtr, void* userParam) { cemuLog_log(LogType::nn_nfp, "NFCGetTagInfo({},{},0x{:08x},0x{:08x})", index, timeout, functionPtr, userParam?memory_getVirtualOffsetFromPointer(userParam):0); cemu_assert(index == 0); nnNfpLock(); StackAllocator _callbackParam; NFCTagInfoCallbackParam_t* callbackParam = _callbackParam.GetPointer(); memset(callbackParam, 0x00, sizeof(NFCTagInfoCallbackParam_t)); memcpy(callbackParam->uid, nfp_data.amiiboProcessedData.uid, nfp_data.amiiboProcessedData.uidLength); callbackParam->uidLength = (uint8)nfp_data.amiiboProcessedData.uidLength; PPCCoreCallback(functionPtr, index, 0, _callbackParam.GetPointer(), userParam); nnNfpUnlock(); return 0; // 0 -> success } void nnNfpExport_Mount(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::nn_nfp, "Mount()"); nnNfpLock(); if (nfp_data.hasActiveAmiibo == false) { nnNfpUnlock(); osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, 0)); // todo: Return correct error code return; } nfp_data.isMounted = true; nfp_data.isReadOnly = false; nfp_data.isDetecting = false; nnNfpUnlock(); osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0)); } void nnNfpExport_Unmount(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::nn_nfp, "Unmount()"); nfp_data.hasOpenApplicationArea = false; osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0)); } void nnNfpExport_MountRom(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::nn_nfp, "MountRom()"); nnNfpLock(); if (nfp_data.hasActiveAmiibo == false) { nnNfpUnlock(); osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, 0)); // todo: Return correct error code return; } nfp_data.isMounted = true; nfp_data.isReadOnly = true; nfp_data.isDetecting = false; nnNfpUnlock(); osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0)); } typedef struct { /* +0x00 */ uint8 characterId[3]; /* +0x03 */ uint8 amiiboSeries; /* +0x04 */ uint16be number; /* +0x06 */ uint8 nfpType; /* +0x07 */ uint8 unused[0x2F]; }nfpRomInfo_t; static_assert(offsetof(nfpRomInfo_t, amiiboSeries) == 0x3, "nfpRomInfo.seriesId has invalid offset"); static_assert(offsetof(nfpRomInfo_t, number) == 0x4, "nfpRomInfo.number has invalid offset"); static_assert(offsetof(nfpRomInfo_t, nfpType) == 0x6, "nfpRomInfo.nfpType has invalid offset"); static_assert(sizeof(nfpRomInfo_t) == 0x36, "nfpRomInfo_t has invalid size"); void nnNfpExport_GetNfpRomInfo(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::nn_nfp, "GetNfpRomInfo(0x{:08x})", hCPU->gpr[3]); ppcDefineParamStructPtr(romInfo, nfpRomInfo_t, 0); nnNfpLock(); if (nfp_data.hasActiveAmiibo == false) { nnNfpUnlock(); osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, 0)); // todo: Return correct error code return; } memset(romInfo, 0x00, sizeof(nfpRomInfo_t)); romInfo->characterId[0] = nfp_data.amiiboNFCData.amiiboIdentificationBlock.gameAndCharacterId[0]; romInfo->characterId[1] = nfp_data.amiiboNFCData.amiiboIdentificationBlock.gameAndCharacterId[1]; romInfo->characterId[2] = nfp_data.amiiboNFCData.amiiboIdentificationBlock.characterVariation; // guessed romInfo->amiiboSeries = nfp_data.amiiboNFCData.amiiboIdentificationBlock.amiiboSeries; // guessed romInfo->number = *(uint16be*)nfp_data.amiiboNFCData.amiiboIdentificationBlock.amiiboModelNumber; // guessed romInfo->nfpType = nfp_data.amiiboNFCData.amiiboIdentificationBlock.amiiboFigureType; // guessed nnNfpUnlock(); osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0)); } typedef struct { uint16be year; uint8 month; uint8 day; }nfpDate_t; typedef struct { /* +0x00 */ nfpDate_t date; /* +0x04 */ uint8 writeCount[2]; /* +0x06 */ uint8 characterId[3]; /* +0x09 */ uint8 amiiboSeries; /* +0x0A */ uint16be number; /* +0x0C */ uint8 nfpType; /* +0x0D */ uint8 nfpVersion; /* +0x0E */ uint16be applicationAreaSize; /* +0x10 */ uint8 unused[0x30]; }nfpCommonData_t; static_assert(sizeof(nfpCommonData_t) == 0x40, "nfpCommonData_t has invalid size"); static_assert(offsetof(nfpCommonData_t, writeCount) == 0x4, "nfpCommonData.writeCount has invalid offset"); static_assert(offsetof(nfpCommonData_t, amiiboSeries) == 0x9, "nfpCommonData.seriesId has invalid offset"); static_assert(offsetof(nfpCommonData_t, nfpType) == 0xC, "nfpCommonData.nfpType has invalid offset"); static_assert(offsetof(nfpCommonData_t, applicationAreaSize) == 0xE, "nfpCommonData.applicationAreaSize has invalid offset"); void nnNfpExport_GetNfpCommonInfo(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::nn_nfp, "GetNfpCommonInfo(0x{:08x})", hCPU->gpr[3]); ppcDefineParamStructPtr(commonInfo, nfpCommonData_t, 0); nnNfpLock(); if (nfp_data.hasActiveAmiibo == false) { nnNfpUnlock(); osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, 0)); // todo: Return correct error code return; } // tag info format is currently unknown, so we just set it to all zeros for now if (sizeof(nfpCommonData_t) != 0x40 ) assert_dbg(); memset(commonInfo, 0x00, sizeof(nfpCommonData_t)); forceLogDebug_printf("GetNfpCommonInfo(0x%08x)"); commonInfo->writeCount[0] = nfp_data.amiiboNFCData.writeCounter[0]; commonInfo->writeCount[1] = nfp_data.amiiboNFCData.writeCounter[1]; commonInfo->characterId[0] = nfp_data.amiiboNFCData.amiiboIdentificationBlock.gameAndCharacterId[0]; commonInfo->characterId[1] = nfp_data.amiiboNFCData.amiiboIdentificationBlock.gameAndCharacterId[1]; commonInfo->characterId[2] = nfp_data.amiiboNFCData.amiiboIdentificationBlock.characterVariation; commonInfo->number = *(uint16be*)nfp_data.amiiboNFCData.amiiboIdentificationBlock.amiiboModelNumber; commonInfo->amiiboSeries = nfp_data.amiiboNFCData.amiiboIdentificationBlock.amiiboSeries; commonInfo->nfpType = nfp_data.amiiboNFCData.amiiboIdentificationBlock.amiiboFigureType; // guessed commonInfo->applicationAreaSize = sizeof(nfp_data.amiiboInternal.applicationData); // not 100% sure if this is always set to the maximum nnNfpUnlock(); osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0)); } typedef struct { /* +0x00 */ uint8 ownerMii[0x60]; /* +0x60 */ uint8 nickname[0x14]; /* +0x74 */ uint16be ukn74; /* +0x76 */ uint8 ukn76; /* +0x77 */ uint8 _padding77; /* +0x78 */ nfpDate_t registerDate; /* +0x7C */ uint8 ukn7C[0x2C]; }nfpRegisterInfo_t; typedef struct { /* +0x00 */ uint8 ownerMii[0x60]; /* +0x60 */ uint8 nickname[0x14]; // maybe has more fields? }nfpRegisterInfoSet_t; void nnNfpExport_GetNfpRegisterInfo(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::nn_nfp, "GetNfpRegisterInfo(0x{:08x})", hCPU->gpr[3]); ppcDefineParamStructPtr(registerInfo, nfpRegisterInfo_t, 0); if(!registerInfo) { osLib_returnFromFunction(hCPU, 0xC1B03780); return; } memset(registerInfo, 0, sizeof(nfpRegisterInfo_t)); if ((nfp_data.amiiboInternal.amiiboSettings.flags & 0x10) != 0) { memcpy(registerInfo->ownerMii, nfp_data.amiiboInternal.amiiboSettings.mii, sizeof(nfp_data.amiiboInternal.amiiboSettings.mii)); memcpy(registerInfo->nickname, nfp_data.amiiboInternal.amiiboSettings.nickname, sizeof(nfp_data.amiiboInternal.amiiboSettings.nickname)); } // todo - missing fields osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0)); } void nnNfpExport_InitializeRegisterInfoSet(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::nn_nfp, "InitializeRegisterInfoSet(0x{:08x})", hCPU->gpr[3]); ppcDefineParamStructPtr(registerInfoSet, nfpRegisterInfoSet_t, 0); memset(registerInfoSet, 0, sizeof(nfpRegisterInfoSet_t)); osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0)); } void nnNfpExport_SetNfpRegisterInfo(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::nn_nfp, "SetNfpRegisterInfo(0x{:08x})", hCPU->gpr[3]); ppcDefineParamStructPtr(registerInfoSet, nfpRegisterInfoSet_t, 0); memcpy(nfp_data.amiiboInternal.amiiboSettings.mii, registerInfoSet->ownerMii, sizeof(nfp_data.amiiboInternal.amiiboSettings.mii)); memcpy(nfp_data.amiiboInternal.amiiboSettings.nickname, registerInfoSet->nickname, sizeof(nfp_data.amiiboInternal.amiiboSettings.nickname)); // todo - set register date and other values nfp_data.amiiboInternal.amiiboSettings.flags |= 0x10; // set registered bit osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0)); } void nnNfpExport_IsExistApplicationArea(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::nn_nfp, "IsExistApplicationArea()"); if (!nfp_data.hasActiveAmiibo || !nfp_data.isMounted) { osLib_returnFromFunction(hCPU, 0); return; } bool appAreaExists = (nfp_data.amiiboInternal.amiiboSettings.flags & 0x20) != 0; osLib_returnFromFunction(hCPU, appAreaExists ? 1 : 0); } void nnNfpExport_OpenApplicationArea(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::nn_nfp, "OpenApplicationArea(0x{:08x})", hCPU->gpr[3]); ppcDefineParamU32(appAreaId, 0); // note - this API doesn't fail if the application area has already been opened? if (!(nfp_data.amiiboInternal.amiiboSettings.flags & 0x20)) { // no application data set osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, NN_RESULT_NFP_CODE_NOAPPAREA)); return; } uint32 nfpAppAreaId = nfp_data.amiiboInternal.amiiboSettings.getAppDataAppId(); if (nfpAppAreaId != appAreaId) { osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, NN_RESULT_NFP_CODE_APPAREAIDMISMATCH)); return; } nfp_data.hasOpenApplicationArea = true; osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0)); } void nnNfpExport_ReadApplicationArea(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::nn_nfp, "ReadApplicationArea(0x{:08x}, 0x{:x})", hCPU->gpr[3], hCPU->gpr[4]); ppcDefineParamPtr(bufferPtr, uint8*, 0); ppcDefineParamU32(len, 1); if (!nfp_data.hasOpenApplicationArea) { osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, 0)); return; } len = std::min(len, (uint32)sizeof(nfp_data.amiiboInternal.applicationData)); memcpy(bufferPtr, nfp_data.amiiboInternal.applicationData, len); osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0)); } void nnNfpExport_WriteApplicationArea(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::nn_nfp, "WriteApplicationArea(0x{:08x}, 0x{:x}, 0x{:08x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]); ppcDefineParamPtr(bufferPtr, uint8*, 0); ppcDefineParamU32(len, 1); if (!nfp_data.hasOpenApplicationArea) { osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, 0)); return; } if (nfp_data.isReadOnly) { osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, 0)); return; } len = std::min(len, (uint32)sizeof(nfp_data.amiiboInternal.applicationData)); memcpy(nfp_data.amiiboInternal.applicationData, bufferPtr, len); // remaining data is filled with random bytes for (uint32 i = len; i < sizeof(nfp_data.amiiboInternal.applicationData); i++) nfp_data.amiiboInternal.applicationData[i] = rand() & 0xFF; nfp_data.amiiboInternal.amiiboSettings.appWriteCounter = nfp_data.amiiboInternal.amiiboSettings.appWriteCounter + 1; osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0)); } typedef struct { uint32be appAreaId; MEMPTR initialData; uint32be initialLen; // more? }NfpCreateInfo_t; void nnNfpExport_CreateApplicationArea(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::nn_nfp, "CreateApplicationArea(0x{:08x})", hCPU->gpr[3]); ppcDefineParamPtr(createInfo, NfpCreateInfo_t, 0); if (nfp_data.hasOpenApplicationArea || (nfp_data.amiiboInternal.amiiboSettings.flags&0x20)) { // cant create app area if it already exists osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, 0)); return; } if (nfp_data.isReadOnly) { osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, 0)); return; } void* writePtr = createInfo->initialData.GetPtr(); uint32 writeSize = createInfo->initialLen; if (writeSize > sizeof(nfp_data.amiiboInternal.applicationData)) { // requested write size larger than available space osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, 0)); return; } nfp_data.amiiboInternal.amiiboSettings.setAppDataAppId(createInfo->appAreaId); nfp_data.amiiboInternal.amiiboSettings.flags |= 0x20; // set application data exists bit nfp_data.amiiboInternal.amiiboSettings.appWriteCounter = nfp_data.amiiboInternal.amiiboSettings.appWriteCounter + 1; nfp_data.hasOpenApplicationArea = false; // write initial data to application area memcpy(nfp_data.amiiboInternal.applicationData, writePtr, writeSize); // remaining data is filled with random bytes for (uint32 i = writeSize; i < sizeof(nfp_data.amiiboInternal.applicationData); i++) nfp_data.amiiboInternal.applicationData[i] = rand() & 0xFF; // this API forces a flush (unsure, but without this data written by Smash doesn't stick) if (!nnNfp_writeCurrentAmiibo()) { cemuLog_log(LogType::Force, "Failed to write Amiibo file data when trying to remove appArea"); osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, 0)); return; } osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0)); } void nnNfpExport_DeleteApplicationArea(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::nn_nfp, "DeleteApplicationArea()"); if (nfp_data.isReadOnly) { osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, 0)); return; } if ((nfp_data.amiiboInternal.amiiboSettings.flags & 0x20) == 0) { osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, 0)); return; } nfp_data.amiiboInternal.amiiboSettings.setAppDataAppId(0); nfp_data.amiiboInternal.amiiboSettings.flags &= ~0x20; nfp_data.amiiboInternal.amiiboSettings.appWriteCounter = nfp_data.amiiboInternal.amiiboSettings.appWriteCounter + 1; // this API forces a flush if (!nnNfp_writeCurrentAmiibo()) { cemuLog_log(LogType::Force, "Failed to write Amiibo file data when trying to remove appArea"); osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, 0)); return; } osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0)); } void nnNfpExport_Flush(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::nn_nfp, "Flush()"); // write Amiibo data if (nfp_data.isReadOnly) { cemuLog_log(LogType::Force, "Cannot write to Amiibo when it is mounted in read-only mode"); osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, 0)); return; } if (!nnNfp_writeCurrentAmiibo()) { cemuLog_log(LogType::Force, "Failed to write Amiibo data"); osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, 0)); return; } osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0)); } typedef struct { /* +0x000 */ uint8 ukn00[0x10]; /* +0x010 */ uint32be mode; /* +0x014 */ uint8 tagInfo[0x54]; /* +0x068 */ uint8 isRegistered; // could be uint32be? /* +0x069 */ uint8 _padding69[3]; /* +0x06C */ uint8 registerInfo[0xA8]; /* +0x114 */ uint8 commonInfo[0x40]; /* +0x154 */ uint8 ukn154[0x20]; }AmiiboSettingsArgs_t; static_assert(sizeof(AmiiboSettingsArgs_t) == 0x174); static_assert(offsetof(AmiiboSettingsArgs_t, mode) == 0x10); static_assert(offsetof(AmiiboSettingsArgs_t, tagInfo) == 0x14); static_assert(offsetof(AmiiboSettingsArgs_t, isRegistered) == 0x68); static_assert(offsetof(AmiiboSettingsArgs_t, registerInfo) == 0x6C); static_assert(offsetof(AmiiboSettingsArgs_t, commonInfo) == 0x114); void nnNfpExport_GetAmiiboSettingsArgs(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::nn_nfp, "GetAmiiboSettingsArgs(0x{:08x})", hCPU->gpr[3]); ppcDefineParamStructPtr(settingsArg, AmiiboSettingsArgs_t, 0); memset(settingsArg, 0, sizeof(AmiiboSettingsArgs_t)); // modes: // 0 -> Register owner and nickname // 0x64 -> Launch normally settingsArg->mode = 0x64; osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0)); } void nnNfp_unloadAmiibo() { nnNfpLock(); nfp_data.isMounted = false; nfp_data.hasActiveAmiibo = false; nnNfpUnlock(); } bool nnNfp_touchNfcTagFromFile(const wchar_t* filePath, uint32* nfcError) { AmiiboRawNFCData rawData = { 0 }; auto nfcData = FileStream::LoadIntoMemory(filePath); if (!nfcData) { *nfcError = NFC_ERROR_NO_ACCESS; return false; } if (nfcData->size() < sizeof(AmiiboRawNFCData)) { *nfcError = NFC_ERROR_INVALID_FILE_FORMAT; return false; } memcpy(&rawData, nfcData->data(), sizeof(AmiiboRawNFCData)); // verify if the file is a valid ntag215/amiibo file if (rawData.dynamicLock[0] != 0x01 || rawData.dynamicLock[1] != 0x00 || rawData.dynamicLock[2] != 0x0F || rawData.dynamicLock[3] != 0xBD) { // Temporary workaround to fix corrupted files by old cemu versions rawData.dynamicLock[0] = 0x01; rawData.dynamicLock[1] = 0x00; rawData.dynamicLock[2] = 0x0F; rawData.dynamicLock[3] = 0xBD; // *nfcError = NFC_ERROR_INVALID_FILE_FORMAT; // return false; } if (rawData.cfg0[0] != 0x00 || rawData.cfg0[1] != 0x00 || rawData.cfg0[2] != 0x00 || rawData.cfg0[3] != 0x04) { *nfcError = NFC_ERROR_INVALID_FILE_FORMAT; return false; } if (rawData.cfg1[0] != 0x5F || rawData.cfg1[1] != 0x00 || rawData.cfg1[2] != 0x00 || rawData.cfg1[3] != 0x00) { *nfcError = NFC_ERROR_INVALID_FILE_FORMAT; return false; } if (rawData.staticLock[0] != 0x0F || rawData.staticLock[1] != 0xE0) { *nfcError = NFC_ERROR_INVALID_FILE_FORMAT; return false; } if (rawData.cc[0] != 0xF1 || rawData.cc[1] != 0x10 || rawData.cc[2] != 0xFF || rawData.cc[3] != 0xEE) { *nfcError = NFC_ERROR_INVALID_FILE_FORMAT; return false; } // process uid uint8 serialNumber[7]; serialNumber[0] = rawData.ntagSerial[0]; serialNumber[1] = rawData.ntagSerial[1]; serialNumber[2] = rawData.ntagSerial[2]; serialNumber[3] = rawData.ntagSerial[4]; serialNumber[4] = rawData.ntagSerial[5]; serialNumber[5] = rawData.ntagSerial[6]; serialNumber[6] = rawData.nintendoId; uint8 serialCheckByte0 = rawData.ntagSerial[3]; uint8 serialCheckByte1 = rawData.lockBytes[0]; uint8 bcc0 = serialNumber[0] ^ serialNumber[1] ^ serialNumber[2] ^ 0x88; uint8 bcc1 = serialNumber[3] ^ serialNumber[4] ^ serialNumber[5] ^ serialNumber[6]; if (serialCheckByte0 != bcc0 || serialCheckByte1 != bcc1) { cemuLog_log(LogType::Force, "nn_nfp: Mismatch in serial checksum of scanned NFC tag"); } nfp_data.amiiboProcessedData.uidLength = 7; memcpy(nfp_data.amiiboProcessedData.uid, serialNumber, 7); // signal activation event nnNfp_unloadAmiibo(); nnNfpLock(); memcpy(&nfp_data.amiiboNFCData, &rawData, sizeof(AmiiboRawNFCData)); // decrypt amiibo amiiboDecrypt(); nfp_data.amiiboPath = std::wstring(filePath); nfp_data.hasActiveAmiibo = true; if (nfp_data.activateEvent) { coreinit::OSEvent* osEvent = (coreinit::OSEvent*)memory_getPointerFromVirtualOffset(nfp_data.activateEvent); coreinit::OSSignalEvent(osEvent); } nfp_data.amiiboTouchTime = GetTickCount(); nnNfpUnlock(); *nfcError = NFC_ERROR_NO_ACCESS; return true; } bool nnNfp_writeCurrentAmiibo() { nnNfpLock(); if (!nfp_data.hasActiveAmiibo) { nnNfpUnlock(); return false; } uint16 writeCounter = nfp_data.amiiboInternal.writeCounterLow + (nfp_data.amiiboInternal.writeCounterHigh << 8); writeCounter++; nfp_data.amiiboInternal.writeCounterLow = writeCounter & 0xFF; nfp_data.amiiboInternal.writeCounterHigh = (writeCounter >> 8) & 0xFF; // open file for writing FileStream* fs = FileStream::openFile2(nfp_data.amiiboPath, true); if (!fs) { nnNfpUnlock(); return false; } // encrypt Amiibo and convert to NFC format AmiiboRawNFCData nfcData; amiiboEncrypt(&nfcData); // write to file fs->writeData(&nfcData, sizeof(AmiiboRawNFCData)); delete fs; nnNfpUnlock(); return true; } void nnNfp_update() { // lock-free check if amiibo is touching if (nfp_data.hasActiveAmiibo == false) return; if (!nnNfpTryLock()) return; // make sure amiibo is still touching after acquiring lock if (nfp_data.hasActiveAmiibo == false) return; uint32 amiiboElapsedTouchTime = GetTickCount() - nfp_data.amiiboTouchTime; if (amiiboElapsedTouchTime >= 1500) { nnNfp_unloadAmiibo(); } nnNfpUnlock(); if (nfp_data.deactivateEvent) { coreinit::OSEvent* osEvent = (coreinit::OSEvent*)memory_getPointerFromVirtualOffset(nfp_data.deactivateEvent); coreinit::OSSignalEvent(osEvent); } } void nnNfpExport_GetNfpState(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::nn_nfp, "GetNfpState()"); // workaround for Mario Party 10 eating CPU cycles in an infinite loop (maybe due to incorrect NFP detection handling?) uint64 titleId = CafeSystem::GetForegroundTitleId(); if (titleId == 0x0005000010162d00 || titleId == 0x0005000010162e00) { coreinit::OSSleepTicks(ESPRESSO_CORE_CLOCK / 100); // pause for 10ms } uint32 nfpState; if (nfp_data.nfpIsInitialized == false) { nfpState = NFP_STATE_NONE; } else { if (nfp_data.isMounted && nfp_data.hasActiveAmiibo) { if (nfp_data.isReadOnly) nfpState = NFP_STATE_RW_MOUNT_ROM; else nfpState = NFP_STATE_RW_MOUNT; } else if (nfp_data.isDetecting) { // todo: is this handled correctly? if (nfp_data.hasActiveAmiibo) { nfpState = NFP_STATE_RW_ACTIVE; } else nfpState = NFP_STATE_RW_SEARCH; } else { nfpState = NFP_STATE_INIT; } } // returns state of nfp library osLib_returnFromFunction(hCPU, nfpState); } namespace nn::nfp { uint32 GetErrorCode(uint32 result) { uint32 level = (result >> 0x1b) & 3; uint32 mask = 0x7f00000; if (level != 3) { mask = 0x1ff00000; } if (((result & mask) == 0x1b00000) && ((result & 0x80000000) != 0)) { mask = 0x3ff; if (level != 3) { mask = 0xfffff; } return ((result & mask) >> 7) + 1680000; } return 1680000; } void nnNfp_load() { osLib_addFunction("nn_nfp", "SetActivateEvent__Q2_2nn3nfpFP7OSEvent", nnNfpExport_SetActivateEvent); osLib_addFunction("nn_nfp", "SetDeactivateEvent__Q2_2nn3nfpFP7OSEvent", nnNfpExport_SetDeactivateEvent); osLib_addFunction("nn_nfp", "StartDetection__Q2_2nn3nfpFv", nnNfpExport_StartDetection); osLib_addFunction("nn_nfp", "StopDetection__Q2_2nn3nfpFv", nnNfpExport_StopDetection); osLib_addFunction("nn_nfp", "GetTagInfo__Q2_2nn3nfpFPQ3_2nn3nfp7TagInfo", nnNfpExport_GetTagInfo); osLib_addFunction("nn_nfp", "Mount__Q2_2nn3nfpFv", nnNfpExport_Mount); osLib_addFunction("nn_nfp", "MountRom__Q2_2nn3nfpFv", nnNfpExport_MountRom); osLib_addFunction("nn_nfp", "Unmount__Q2_2nn3nfpFv", nnNfpExport_Unmount); osLib_addFunction("nn_nfp", "GetNfpRomInfo__Q2_2nn3nfpFPQ3_2nn3nfp7RomInfo", nnNfpExport_GetNfpRomInfo); osLib_addFunction("nn_nfp", "GetNfpCommonInfo__Q2_2nn3nfpFPQ3_2nn3nfp10CommonInfo", nnNfpExport_GetNfpCommonInfo); osLib_addFunction("nn_nfp", "GetNfpRegisterInfo__Q2_2nn3nfpFPQ3_2nn3nfp12RegisterInfo", nnNfpExport_GetNfpRegisterInfo); osLib_addFunction("nn_nfp", "InitializeRegisterInfoSet__Q2_2nn3nfpFPQ3_2nn3nfp15RegisterInfoSet", nnNfpExport_InitializeRegisterInfoSet); osLib_addFunction("nn_nfp", "SetNfpRegisterInfo__Q2_2nn3nfpFRCQ3_2nn3nfp15RegisterInfoSet", nnNfpExport_SetNfpRegisterInfo); osLib_addFunction("nn_nfp", "IsExistApplicationArea__Q2_2nn3nfpFv", nnNfpExport_IsExistApplicationArea); osLib_addFunction("nn_nfp", "OpenApplicationArea__Q2_2nn3nfpFUi", nnNfpExport_OpenApplicationArea); osLib_addFunction("nn_nfp", "CreateApplicationArea__Q2_2nn3nfpFRCQ3_2nn3nfp25ApplicationAreaCreateInfo", nnNfpExport_CreateApplicationArea); osLib_addFunction("nn_nfp", "DeleteApplicationArea__Q2_2nn3nfpFv", nnNfpExport_DeleteApplicationArea); osLib_addFunction("nn_nfp", "ReadApplicationArea__Q2_2nn3nfpFPvUi", nnNfpExport_ReadApplicationArea); osLib_addFunction("nn_nfp", "WriteApplicationArea__Q2_2nn3nfpFPCvUiRCQ3_2nn3nfp5TagId", nnNfpExport_WriteApplicationArea); osLib_addFunction("nn_nfp", "Flush__Q2_2nn3nfpFv", nnNfpExport_Flush); osLib_addFunction("nn_nfp", "Initialize__Q2_2nn3nfpFv", nnNfpExport_Initialize); osLib_addFunction("nn_nfp", "GetNfpState__Q2_2nn3nfpFv", nnNfpExport_GetNfpState); osLib_addFunction("nn_nfp", "GetAmiiboSettingsArgs__Q2_2nn3nfpFPQ3_2nn3nfp18AmiiboSettingsArgs", nnNfpExport_GetAmiiboSettingsArgs); } void load() { nnNfp_load(); // legacy interface, update these to use cafeExportRegister / cafeExportRegisterFunc cafeExportRegisterFunc(nn::nfp::GetErrorCode, "nn_nfp", "GetErrorCode__Q2_2nn3nfpFRCQ2_2nn6Result", LogType::Placeholder); // NFC API cafeExportRegister("nn_nfp", NFCGetTagInfo, LogType::Placeholder); } }