|
#include <wups.h> |
|
#include <string.h> |
|
#include <stdlib.h> |
|
#include <nfc/nfc.h> |
|
#include <coreinit/alarm.h> |
|
#include <coreinit/thread.h> |
|
#include <coreinit/time.h> |
|
#include <coreinit/title.h> |
|
#include <coreinit/debug.h> |
|
#include <coreinit/mcp.h> |
|
#include <sysapp/title.h> |
|
#include <sysapp/launch.h> |
|
#include <nn/acp.h> |
|
|
|
#if 0 |
|
#include <nsysccr/cdc.h> |
|
#endif |
|
|
|
#include <notifications/notifications.h> |
|
|
|
WUPS_PLUGIN_NAME("NfcLaunch"); |
|
WUPS_PLUGIN_DESCRIPTION("Launch titles using NFC tags"); |
|
WUPS_PLUGIN_VERSION("v1.0"); |
|
WUPS_PLUGIN_AUTHOR("GaryOderNichts"); |
|
WUPS_PLUGIN_LICENSE("GPLv3"); |
|
|
|
OSAlarm gProcAlarm; |
|
OSAlarm gSearchAlarm; |
|
|
|
static bool LaunchTitle(uint64_t titleId) |
|
{ |
|
if (!SYSCheckTitleExists(titleId)) { |
|
OSReport("Title does not exist\n"); |
|
return false; |
|
} |
|
|
|
MCPTitleListType titleInfo; |
|
int32_t handle = MCP_Open(); |
|
MCPError err = MCP_GetTitleInfo(handle, titleId, &titleInfo); |
|
MCP_Close(handle); |
|
if (err != 0) { |
|
OSReport("Failed to get title info\n"); |
|
return false; |
|
} |
|
|
|
char metaDir[256]; |
|
if (ACPGetTitleMetaDir(titleId, metaDir, sizeof(metaDir) - 1) != ACP_RESULT_SUCCESS) { |
|
OSReport("Failed to get title meta dir\n"); |
|
return false; |
|
} |
|
|
|
ACPAssignTitlePatch(&titleInfo); |
|
_SYSLaunchTitleByPathFromLauncher(titleInfo.path, strlen(titleInfo.path)); |
|
return true; |
|
} |
|
|
|
static uint8_t *TLV_Parse(uint8_t *tlv, uint8_t *outTag, uint16_t *outLen) |
|
{ |
|
uint8_t tag = *tlv++; |
|
uint16_t len = *tlv++; |
|
|
|
// If the length is 0xFF, 2 bytes with length follow |
|
if (len == 0xFF) { |
|
len = ((uint16_t)tlv[0] << 8) | tlv[1]; |
|
tlv += 2; |
|
} |
|
|
|
if (outTag) { |
|
*outTag = tag; |
|
} |
|
|
|
if (outLen) { |
|
*outLen = len; |
|
} |
|
|
|
// Return pointer to value |
|
return tlv; |
|
} |
|
|
|
static uint8_t *NDEF_GetType(uint8_t *rec, uint8_t *outTNF, uint8_t *outTypeLen) |
|
{ |
|
uint8_t recHdr = *rec++; |
|
uint8_t typeLen = *rec++; |
|
|
|
// Skip payload length |
|
if (recHdr & 0x10) { |
|
rec += 1; |
|
} else { |
|
rec += 4; |
|
} |
|
|
|
// Skip ID length, if it exists |
|
if (recHdr & 0x08) { |
|
rec += 1; |
|
} |
|
|
|
if (outTNF) { |
|
*outTNF = recHdr & 0x07; |
|
} |
|
|
|
if (outTypeLen) { |
|
*outTypeLen = typeLen; |
|
} |
|
|
|
// Return pointer to type field |
|
return rec; |
|
} |
|
|
|
static uint8_t *NDEF_GetPayload(uint8_t *rec, uint32_t* outPayloadLen) |
|
{ |
|
uint8_t recHdr = *rec++; |
|
uint8_t typeLen = *rec++; |
|
|
|
// Payload length can be 1 or 4 bytes long, depending on the SR bit |
|
uint32_t payloadLen; |
|
if (recHdr & 0x10) { |
|
payloadLen = *rec++; |
|
} else { |
|
payloadLen = ((uint32_t)rec[0] << 24) | ((uint32_t)rec[1] << 16) | ((uint32_t)rec[2] << 8) | rec[3]; |
|
rec += 4; |
|
} |
|
|
|
// Read ID length, if it exists |
|
uint8_t idLen; |
|
if (recHdr & 0x08) { |
|
idLen = *rec++; |
|
} else { |
|
idLen = 0; |
|
} |
|
|
|
if (outPayloadLen) { |
|
*outPayloadLen = payloadLen; |
|
} |
|
|
|
// Return pointer to payload |
|
return rec + idLen + typeLen; |
|
} |
|
|
|
static void NdefReadCallback(VPADChan chan, NFCError error, uint32_t responseSize, void *responseData, void *userContext) |
|
{ |
|
if (error != 0) { |
|
OSReport("Failed to read NDEF message\n"); |
|
return; |
|
} |
|
|
|
// Parse the TLV again to get the V field (NDEF record) |
|
uint8_t *rec = TLV_Parse((uint8_t *)responseData, NULL, NULL); |
|
|
|
// Get the NDEF record type |
|
uint8_t tnf, typeLen; |
|
uint8_t *type = NDEF_GetType(rec, &tnf, &typeLen); |
|
|
|
// We only support well-known types |
|
if (tnf != 0x01) { |
|
OSReport("Unsupported TNF\n"); |
|
return; |
|
} |
|
|
|
// Make sure this is a URI |
|
if ((typeLen != 0x01) || (*type != 'U')) { |
|
OSReport("Unsupported type\n"); |
|
return; |
|
} |
|
|
|
// Get the NDEF payload |
|
uint32_t payloadLen; |
|
uint8_t *payload = NDEF_GetPayload(rec, &payloadLen); |
|
|
|
// We can now parse the payload |
|
uint8_t identifier = *payload++; |
|
|
|
if (identifier != 0) { |
|
OSReport("Unsupported identifier\n"); |
|
return; |
|
} |
|
|
|
if (memcmp(payload, "wiiu://", 7) != 0) { |
|
OSReport("Unsupported prefix\n"); |
|
return; |
|
} |
|
|
|
// Append the rest of the payloa |
|
char titleIdStr[64]; |
|
if (payloadLen - 7 - 1 > 64) { |
|
OSReport("Payload too long\n"); |
|
return; |
|
} |
|
strncpy(titleIdStr, (char *)payload + 7, payloadLen - 7 - 1); |
|
|
|
uint64_t tid = strtoull(titleIdStr, NULL, 16); |
|
|
|
OSReport("Launching: %s %llx\n", titleIdStr, tid); |
|
|
|
if (NotificationModule_InitLibrary() == NOTIFICATION_MODULE_RESULT_SUCCESS) { |
|
NotificationModule_AddInfoNotification("NfcLaunch: Starting title..."); |
|
} |
|
|
|
if (!LaunchTitle(tid)) { |
|
if (NotificationModule_InitLibrary() == NOTIFICATION_MODULE_RESULT_SUCCESS) { |
|
NotificationModule_AddErrorNotification("NfcLaunch: Failed to start title"); |
|
} |
|
} |
|
} |
|
|
|
void NdefDetectionCallback(VPADChan chan, NFCError error, uint32_t responseSize, void *responseData, void *userContext) |
|
{ |
|
if (error != 0 || responseSize < 16) { |
|
OSReport("Failed to read capability container: %x %u\n", error, responseSize); |
|
return; |
|
} |
|
|
|
// Verify that the CC contains the NFC defined magic and contains a supported version |
|
uint8_t *cc = (uint8_t *)responseData; |
|
if (cc[0] != 0xE1 || cc[1] >> 4 != 0x1) { |
|
OSReport("Tag not supported\n"); |
|
return; |
|
} |
|
|
|
// Parse TLV |
|
uint8_t tag; |
|
uint16_t length; |
|
TLV_Parse((uint8_t *)responseData + 4, &tag, &length); |
|
|
|
// Check the tag field for the NDEF type |
|
if (tag != 0x03) { |
|
OSReport("Tag does not contain an NDEF message\n"); |
|
return; |
|
} |
|
|
|
uint8_t blockCount = (length + 4) / 4; |
|
|
|
// Read the full NDEF data using the NTAG FAST_READ command |
|
uint8_t command[3]; |
|
command[0] = 0x3A; // Command (FAST_READ) |
|
command[1] = 0x04; // Start Block (04) |
|
command[2] = 0x04 + blockCount; // End Block (04 + block count) |
|
NFCError err = NFCSendRawData(VPAD_CHAN_0, FALSE, 500, 500, |
|
sizeof(command), blockCount * 4, command, |
|
NdefReadCallback, NULL); |
|
|
|
if (err != 0) { |
|
OSReport("NFCSendRawData(1) failed: %x\n", err); |
|
} |
|
} |
|
|
|
void GetTagInfoCallback(VPADChan chan, NFCError error, NFCTagInfo *tagInfo, void *userContext) |
|
{ |
|
if (error != 0) { |
|
return; |
|
} |
|
|
|
OSReport("Found tag!\n"); |
|
OSReport(" UID: %02x:%02x:%02x:%02x:%02x:%02x:%02x\n", |
|
tagInfo->uid[0], tagInfo->uid[1], tagInfo->uid[2], tagInfo->uid[3], |
|
tagInfo->uid[4], tagInfo->uid[5], tagInfo->uid[6]); |
|
OSReport(" Protocol: %d Technology: %d\n", tagInfo->protocol, tagInfo->technology); |
|
|
|
if (tagInfo->protocol == NFC_PROTOCOL_T2T) { |
|
// Read the first 16-bytes from the tag for NDEF detection |
|
uint8_t command[2]; |
|
command[0] = 0x30; // Command (READ) |
|
command[1] = 0x03; // Block Number (03 = Capability Container) |
|
NFCError err = NFCSendRawDataWithPrePollingEx(VPAD_CHAN_0, TRUE, 500, 500, |
|
sizeof(command), 16, command, |
|
NdefDetectionCallback, NULL); |
|
if (err != 0) { |
|
OSReport("NFCSendRawData(0) failed: %x\n", err); |
|
} |
|
} |
|
} |
|
|
|
void SearchCallback(OSAlarm *, OSContext *) |
|
{ |
|
if (!NFCIsInit(VPAD_CHAN_0)) { |
|
NFCInit(VPAD_CHAN_0); |
|
return; |
|
} |
|
|
|
// Start searching for tags by getting their info |
|
NFCGetTagInfo(VPAD_CHAN_0, 500, GetTagInfoCallback, NULL); |
|
} |
|
|
|
void ProcCallback(OSAlarm *, OSContext *) |
|
{ |
|
NFCProc(VPAD_CHAN_0); |
|
} |
|
|
|
bool IsWiiUMenu() |
|
{ |
|
return OSGetTitleID() == _SYSGetSystemApplicationTitleId(SYSTEM_APP_ID_WII_U_MENU); |
|
} |
|
|
|
bool IsHAS() |
|
{ |
|
return OSGetTitleID() == _SYSGetSystemApplicationTitleId(SYSTEM_APP_ID_HEALTH_AND_SAFETY); |
|
} |
|
|
|
INITIALIZE_PLUGIN() |
|
{ |
|
} |
|
|
|
DEINITIALIZE_PLUGIN() |
|
{ |
|
} |
|
|
|
ON_APPLICATION_START() |
|
{ |
|
if (!IsWiiUMenu()) { |
|
#if 0 |
|
if (!IsHAS()) { |
|
CCRCDCSysConsoleShutdownInd(CCR_CDC_DESTINATION_DRC0); |
|
} |
|
#endif |
|
return; |
|
} |
|
|
|
#if 0 |
|
CCRCDCWowlWakeDrcArg arg; |
|
arg.state = CCR_CDC_WAKE_STATE_ACTIVE; |
|
CCRCDCWowlWakeDrc(&arg); |
|
#endif |
|
|
|
NFCInit(VPAD_CHAN_0); |
|
|
|
OSCreateAlarm(&gProcAlarm); |
|
OSSetPeriodicAlarm(&gProcAlarm, 0, OSMillisecondsToTicks(15), ProcCallback); |
|
|
|
OSCreateAlarm(&gSearchAlarm); |
|
OSSetPeriodicAlarm(&gSearchAlarm, 0, OSSecondsToTicks(2), SearchCallback); |
|
} |
|
|
|
ON_APPLICATION_ENDS() |
|
{ |
|
if (!IsWiiUMenu()) { |
|
return; |
|
} |
|
|
|
OSCancelAlarm(&gProcAlarm); |
|
OSCancelAlarm(&gSearchAlarm); |
|
} |