micetools/src/micetools/dll/devices/nfc/felica.c

424 lines
17 KiB
C

#include "../../../lib/mice/spad.h"
#include "../_devices.h"
#include "../ser_tn32msec.h"
#include "bng.h"
#include "nfc.h"
static const BYTE PMmTiming[6] = { 0x00, 0x00, 0x00, 0x01, 0x43, 0x00 };
#define DFC_BNG_EDY_0 0x8C
#define DFC_BNG_EDY_1 0xA2
#define DFC_BNG_LITE_0 0x00
#define DFC_BNG_LITE_1 0x3A
#define DFC_KNM_LITES_0 0x00
#define DFC_KNM_LITES_1 0x68
#define DFC_BNE_LITES_0 0x00
#define DFC_BNE_LITES_1 0x2A
#define DFC_SEGA_LITES_0 0x00
#define DFC_SEGA_LITES_1 0x78
#define DFC_TEST_0 0x00
#define DFC_TEST_1 0x00
static const BYTE ID_Arbitrary[6] = { 0x05, 0x73, 0x02, 0x01, 0x02, 0x00 };
inline static void _FelicaDumpChars(LPBYTE lpBytes, BYTE nBytes) {
printf("|");
for (int i = 0; i < nBytes; i++) {
BYTE chr = lpBytes[i];
if (' ' < chr && chr <= '~')
printf("%c", chr);
else
printf(".");
}
printf("|");
}
static void _FelicaDumpBlock(PNFC_CARD lpCard, BYTE block, BYTE nBytes, LPCSTR lpName) {
PFelicaBlock_t lpBlock;
if (block < 0xf)
lpBlock = &lpCard->m_FeliCaData.m_UserBlocks[block];
else
lpBlock = &lpCard->m_FeliCaData.m_SystemBlocks[block - 0x80];
printf("[%02X] ", block);
for (int i = 0; i < 8; i++) {
if (i < nBytes)
printf("%02X ", lpBlock->bytes[i]);
else
printf("-- ");
}
if (lpName)
printf("(%s)", lpName);
else
_FelicaDumpChars(lpBlock->bytes, 8);
printf("\n ");
for (int i = 8; i < 16; i++) {
if (i < nBytes)
printf("%02X ", lpBlock->bytes[i]);
else
printf("-- ");
}
if (!lpName) _FelicaDumpChars(&(lpBlock->bytes[8]), 8);
printf("\n");
}
void FelicaDumpCard(PNFC_CARD lpCard) {
for (BYTE block = 0; block < 15; block++) {
_FelicaDumpBlock(lpCard, block, 16, NULL);
}
_FelicaDumpBlock(lpCard, 0x80, 16, "RC");
_FelicaDumpBlock(lpCard, 0x81, 8, "MAC");
_FelicaDumpBlock(lpCard, 0x82, 16, "ID");
_FelicaDumpBlock(lpCard, 0x83, 16, "D_ID");
_FelicaDumpBlock(lpCard, 0x84, 2, "SER_C");
_FelicaDumpBlock(lpCard, 0x85, 2, "SYS_C");
_FelicaDumpBlock(lpCard, 0x86, 2, "CKV");
_FelicaDumpBlock(lpCard, 0x87, 16, "CK");
_FelicaDumpBlock(lpCard, 0x88, 13, "MC");
}
void FelicaPopulate(PNFC_CARD lpCard, PFELICA_ID lpFelicaId, PACCESS_CODE lpAccessCode) {
ZeroMemory(&lpCard->m_FeliCaData, sizeof lpCard->m_FeliCaData);
lpCard->m_Header.m_Type = CardType_FeliCa;
lpCard->m_Header.m_IdLen = sizeof(lpCard->m_FeliCa);
memcpy(&lpCard->m_FeliCa.m_IDm, lpFelicaId, sizeof *lpFelicaId);
// TODO: Toggle between suica and AIC mode.
lpCard->m_FeliCaData.m_EmuMode = FelicaEmulationMode_AIC;
lpCard->m_FeliCaData.m_AICVendor = FelicaAICVendor_Konami;
lpCard->m_FeliCa.m_PMm[0] = 0x00;
switch (lpCard->m_FeliCaData.m_EmuMode) {
case FelicaEmulationMode_Suica:
lpCard->m_FeliCa.m_PMm[1] = FelicaChipType_Mobile_Ver10;
lpCard->m_FeliCaData.m_SystemBlock_SYS_C.m_SYS_C = _byteswap_ushort(FelicaSystem_Suica);
// DFC shouldn't matter for Suica
lpCard->m_FeliCaData.m_SystemBlock_ID.m_DFC[0] = DFC_TEST_0;
lpCard->m_FeliCaData.m_SystemBlock_ID.m_DFC[1] = DFC_TEST_1;
break;
case FelicaEmulationMode_AIC:
lpCard->m_FeliCaData.m_SystemBlock_SYS_C.m_SYS_C =
_byteswap_ushort(FelicaSystem_FelicaLite);
/**
* The DFC code is used to identify the card manufacturer. Each
* manufacturer uses a different derivation algorithm for FeliCa ID
* to Access Code conversion, which AiMeDB is able to validate
* based on this information.
*
* This validation is performed as part of
* AiMe_DB_com_get_AiMeAccountS, added in AiMeDB version 4.0.0
*
* To the best of my knowledge this was added long after RingEdge
* was a dead platform, so isn't something we will need to concern
* ourselves with here.
*
* Maimai itself only does a check for if this is
* FelicaAICVendor_BandaiNamco_LiteS, in which case special
* handling is required.
*
* TODO: Right now things seem to test bad if it's _not_ a BNE card??
*/
switch (lpCard->m_FeliCaData.m_AICVendor) {
case FelicaAICVendor_BandaiNamcoGames:
lpCard->m_FeliCaData.m_SystemBlock_ID.m_DFC[0] = DFC_BNG_LITE_0;
lpCard->m_FeliCaData.m_SystemBlock_ID.m_DFC[1] = DFC_BNG_LITE_1;
break;
case FelicaAICVendor_BandaiNamcoEntertainment: {
lpCard->m_FeliCaData.m_SystemBlock_ID.m_DFC[0] = DFC_BNE_LITES_0;
lpCard->m_FeliCaData.m_SystemBlock_ID.m_DFC[1] = DFC_BNE_LITES_1;
BYTE block0D[8] = {
// TODO: Work backwards and figure out where the key is coming from
0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, /*sum*/ 0x00,
};
NbgiWriteBNESignedBlock(lpCard, 0x0D, 0, block0D);
break;
}
case FelicaAICVendor_SEGA:
lpCard->m_FeliCaData.m_SystemBlock_ID.m_DFC[0] = DFC_SEGA_LITES_0;
lpCard->m_FeliCaData.m_SystemBlock_ID.m_DFC[1] = DFC_SEGA_LITES_1;
break;
case FelicaAICVendor_Konami:
lpCard->m_FeliCaData.m_SystemBlock_ID.m_DFC[0] = DFC_KNM_LITES_0;
lpCard->m_FeliCaData.m_SystemBlock_ID.m_DFC[1] = DFC_KNM_LITES_1;
break;
default:
lpCard->m_FeliCaData.m_SystemBlock_ID.m_DFC[0] = DFC_TEST_0;
lpCard->m_FeliCaData.m_SystemBlock_ID.m_DFC[1] = DFC_TEST_1;
break;
}
// Write the card IC type into the PMm
switch (lpCard->m_FeliCaData.m_AICVendor) {
case FelicaAICVendor_BandaiNamcoGames:
lpCard->m_FeliCa.m_PMm[1] = FelicaChipType_Lite;
break;
case FelicaAICVendor_BandaiNamcoEntertainment:
case FelicaAICVendor_SEGA:
case FelicaAICVendor_Konami:
default:
lpCard->m_FeliCa.m_PMm[1] = FelicaChipType_LiteS;
break;
}
break;
case FelicaEmulationMode_BNG:
default:
lpCard->m_FeliCa.m_PMm[1] = FelicaChipType_STD_RC_S915;
lpCard->m_FeliCaData.m_SystemBlock_SYS_C.m_SYS_C = _byteswap_ushort(FelicaSystem_NDEF);
lpCard->m_FeliCaData.m_SystemBlock_ID.m_DFC[0] = DFC_BNG_EDY_0;
lpCard->m_FeliCaData.m_SystemBlock_ID.m_DFC[1] = DFC_BNG_EDY_1;
break;
}
memcpy(&(lpCard->m_FeliCa.m_PMm[2]), PMmTiming, sizeof PMmTiming);
if (lpAccessCode) {
BYTE block0[16];
memcpy(&(block0[6]), lpAccessCode->m_AC, 10);
Spad0Encrypt(block0, block0);
memcpy(lpCard->m_FeliCaData.m_UserBlocks[0].bytes, block0, 16);
}
memset(lpCard->m_FeliCaData.m_UserBlocks[0x0e].bytes, 0xFF, 16);
memcpy(&lpCard->m_FeliCaData.m_SystemBlock_ID.m_FelicaId, lpFelicaId, sizeof *lpFelicaId);
memcpy(lpCard->m_FeliCaData.m_SystemBlock_ID.m_Arbitrary, ID_Arbitrary, sizeof ID_Arbitrary);
memcpy(&lpCard->m_FeliCaData.m_SystemBlock_D_ID.m_FelicaId, lpFelicaId, sizeof *lpFelicaId);
memcpy(lpCard->m_FeliCaData.m_SystemBlock_D_ID.m_PMm, lpCard->m_FeliCa.m_PMm,
sizeof lpCard->m_FeliCa.m_PMm);
#if 0
FelicaDumpCard(lpCard);
#endif
}
void FelicaPoll(PFelicaEncapRequest lpReq, PFelicaEncapResponse lpResponse, PNFC_CARD lpCard) {
FelicaPollRequest request = lpReq->m_Poll.m_Request;
lpResponse->m_FelicaLen = FelicaRespSize(lpResponse->m_Poll);
lpResponse->m_FelicaCommand = FelicaCommand_Poll_Response;
memcpy(&lpResponse->m_IDm, &lpReq->m_IDm, sizeof lpReq->m_IDm);
memcpy(lpResponse->m_Poll.m_PMm, lpCard->m_FeliCa.m_PMm, sizeof lpCard->m_FeliCa.m_PMm);
switch (request) {
case FelicaPollRequest_SystemCode:
lpResponse->m_Poll.m_Value = lpCard->m_FeliCaData.m_SystemBlock_SYS_C.m_SYS_C;
break;
default:
log_error(plfAime, "Unhnaldled poll request: %02x", request);
lpResponse->m_FelicaLen -= 2;
break;
}
}
void FelicaReadNoEnc(PFelicaEncapRequest lpReq, PFelicaEncapResponse lpResponse, PNFC_CARD lpCard) {
BYTE readIndex = 0;
BYTE serviceN = lpReq->m_ReadNoEnc.m_Rest[readIndex++];
for (BYTE i = 0; i < serviceN; i++) {
WORD service = *(LPWORD)(&lpReq->m_ReadNoEnc.m_Rest[readIndex++]);
readIndex++;
}
BYTE blockCount = lpReq->m_ReadNoEnc.m_Rest[readIndex++];
lpResponse->m_FelicaLen = FelicaRespSize(lpResponse->m_ReadNoEnc) + 16 * blockCount;
lpResponse->m_FelicaCommand = FelicaCommand_ReadNoEnc_Response;
memcpy(&lpResponse->m_IDm, &lpReq->m_ReadNoEnc.m_IDm, sizeof lpReq->m_ReadNoEnc.m_IDm);
lpResponse->m_ReadNoEnc.m_Status1 = FelicaStatus1_Success;
lpResponse->m_ReadNoEnc.m_Status2 = FelicaStatus2_Success;
lpResponse->m_ReadNoEnc.m_BlockCount = blockCount;
BYTE writeIndex = 0;
for (BYTE i = 0; i < blockCount; i++) {
BYTE blHeader = lpReq->m_ReadNoEnc.m_Rest[readIndex++];
BOOL isExtended = !(blHeader & 0x80);
BYTE serviceCodeOrder = blHeader & 0x0f;
BYTE accessMode = (blHeader & 0x70) >> 4;
WORD blockNumber;
if (isExtended) {
blockNumber = *(LPWORD)(&lpReq->m_ReadNoEnc.m_Rest[readIndex++]);
readIndex++;
} else {
blockNumber = lpReq->m_ReadNoEnc.m_Rest[readIndex++];
}
if (blockNumber < 0xf) {
memcpy(&lpResponse->m_ReadNoEnc.m_BlockData[writeIndex],
lpCard->m_FeliCaData.m_UserBlocks[blockNumber].bytes, 16);
log_warning(plfAime, "Felica read: s:%x block:%04x", serviceCodeOrder, blockNumber);
writeIndex += 16;
} else if (blockNumber >= 0x80 && blockNumber < 0x89) {
if (blockNumber - 0x80 == FelicaSystemBlock_MAC) {
log_info(plfAime, "MAC challenge requested");
DWORD64 ID[2] = { 0 };
memcpy(ID, &lpCard->m_FeliCaData.m_SystemBlock_ID, 16);
KEY_PAIR CK, SK;
DWORD64* RC = (DWORD64*)&lpCard->m_FeliCaData.m_SystemBlock_RC.m_RC;
FelicaMacGenerateCK(ID, CK);
FelicaMacGenerateSK(RC, CK, SK);
DWORD64 mac = FelicaMacGenerateMAC(ID, SK, RC);
mac = _byteswap_uint64(mac);
memcpy(&lpResponse->m_ReadNoEnc.m_BlockData[writeIndex], &mac, 8);
memset(&lpResponse->m_ReadNoEnc.m_BlockData[writeIndex + 8], 0, 8);
log_warning(plfAime, "Felica read: s:%x block:%04x", serviceCodeOrder, blockNumber);
writeIndex += 16;
} else {
memcpy(&lpResponse->m_ReadNoEnc.m_BlockData[writeIndex],
lpCard->m_FeliCaData.m_SystemBlocks[blockNumber - 0x80].bytes, 16);
log_warning(plfAime, "Felica read: s:%x block:%04x", serviceCodeOrder, blockNumber);
writeIndex += 16;
}
} else {
// Invalid block, but we're already committed to the success state because I'm lazy,
// so just write a bunch of nulls.
memset(&lpResponse->m_ReadNoEnc.m_BlockData[writeIndex], 0, 16);
log_warning(plfAime, "Felica read: s:%x block:%04x", serviceCodeOrder, blockNumber);
writeIndex += 16;
}
}
}
void FelicaWriteNoEnc(PFelicaEncapRequest lpReq, PFelicaEncapResponse lpResponse,
PNFC_CARD lpCard) {
BYTE readIndex = 0;
BYTE serviceN = lpReq->m_WriteNoEnc.m_Rest[readIndex++];
for (BYTE i = 0; i < serviceN; i++) {
WORD service = *(LPWORD)(&lpReq->m_WriteNoEnc.m_Rest[readIndex++]);
readIndex++;
}
BYTE blockCount = lpReq->m_WriteNoEnc.m_Rest[readIndex++];
for (BYTE i = 0; i < blockCount; i++) {
BYTE blHeader = lpReq->m_WriteNoEnc.m_Rest[readIndex++];
BOOL isExtended = !(blHeader & 0x80);
BYTE serviceCodeOrder = blHeader & 0x0f;
BYTE accessMode = (blHeader & 0x70) >> 4;
WORD blockNumber;
if (isExtended) {
blockNumber = *(LPWORD)(&lpReq->m_WriteNoEnc.m_Rest[readIndex++]);
readIndex++;
} else {
blockNumber = lpReq->m_WriteNoEnc.m_Rest[readIndex++];
}
log_warning(plfAime, "Felica write: s:%x block:%04x", serviceCodeOrder, blockNumber);
// TODO: Access controls!
if (blockNumber < 0xf) {
memcpy(lpCard->m_FeliCaData.m_UserBlocks[blockNumber].bytes,
&lpReq->m_WriteNoEnc.m_Rest[readIndex], 16);
readIndex += 16;
} else if (blockNumber >= 0x80 && blockNumber < 0x89) {
memcpy(lpCard->m_FeliCaData.m_SystemBlocks[blockNumber - 0x80].bytes,
&lpReq->m_WriteNoEnc.m_Rest[readIndex], 16);
readIndex += 16;
} else {
// Invalid block, but I'd rather just steamroll through it for the sake of
// compatability, so don't error.
readIndex += 16;
}
}
lpResponse->m_FelicaLen = FelicaRespSize(lpResponse->m_WriteNoEnc);
lpResponse->m_FelicaCommand = FelicaCommand_WriteNoEnc_Response;
memcpy(&lpResponse->m_IDm, &lpReq->m_WriteNoEnc.m_IDm, sizeof lpReq->m_WriteNoEnc.m_IDm);
lpResponse->m_WriteNoEnc.m_Status1 = FelicaStatus1_Success;
lpResponse->m_WriteNoEnc.m_Status2 = FelicaStatus2_Success;
}
void FelicaSystemCode(PFelicaEncapRequest lpReq, PFelicaEncapResponse lpResponse,
PNFC_CARD lpCard) {
lpResponse->m_FelicaLen = FelicaRespSize(lpResponse->m_SystemCode);
lpResponse->m_FelicaCommand = FelicaCommand_RequestSystemCode_Response;
memcpy(&lpResponse->m_IDm, &lpReq->m_SystemCode.m_IDm, sizeof lpReq->m_SystemCode.m_IDm);
lpResponse->m_SystemCode.m_SystemsCount = 1;
lpResponse->m_SystemCode.m_SystemCode = lpCard->m_FeliCaData.m_SystemBlock_SYS_C.m_SYS_C;
}
void FelicaMobileActive2(PFelicaEncapRequest lpReq, PFelicaEncapResponse lpResponse,
PNFC_CARD lpCard) {
lpResponse->m_FelicaLen = FelicaRespSize(lpResponse->m_Active2);
lpResponse->m_FelicaCommand = FelicaCommand_MobileActive2_Response;
switch (lpReq->m_Active2.m_Val) {
case 0: // Alive check
lpResponse->m_Active2.m_Data = 0;
break;
case 1: // OS Version
switch (lpCard->m_FeliCa.m_PMm[1]) {
case FelicaChipType_Mobile_Ver10:
lpResponse->m_Active2.m_Data = 1;
break;
case FelicaChipType_Mobile_Ver20:
lpResponse->m_Active2.m_Data = 0;
break;
default:
// I have no idea what other chips return
lpResponse->m_Active2.m_Data = 0;
break;
}
break;
case 2:
lpResponse->m_Active2.m_Data = 0xff;
break;
}
}
void FelicaEncap(com_device_t* dev, comio_recv_head_t* req, LPBYTE lpPacket, PNFC_CARD lpCard) {
if (lpCard->m_Header.m_Type != CardType_FeliCa) {
log_error(plfAime, "Reader %02x requested a FeliCa encap on a non-FeliCa card!", req->dst);
comio_reply(dev, req, TN32Error_CardError, 0, NULL);
return;
}
PFelicaEncapRequest lpReq = (PFelicaEncapRequest)lpPacket;
FelicaEncapResponse fResponse;
FelicaCommand felicaCmd = lpReq->m_FelicaCommand;
fResponse.m_FelicaCommand = felicaCmd + 1;
fResponse.m_FelicaLen = 0;
/**
* Suica devices will:
* - FelicaCommand_Poll
* - FelicaCommand_RequestSystemCode
* - FelicaCommand_MobileActive2
*
* Others (maimai does not appear to disambiguate?) will:
* - FelicaCommand_Poll
* - FelicaCommand_ReadNoEnc (block 82)
* - FelicaCommand_WriteNoEnc (block 80)
* - FelicaCommand_ReadNoEnc (block 82,21)
* - FelicaCommand_ReadNoEnc (block 0D)
*/
log_game(plfAime, "FeliCa command: %02x", felicaCmd);
switch (felicaCmd) {
case FelicaCommand_Poll:
FelicaPoll(lpReq, &fResponse, lpCard);
break;
case FelicaCommand_ReadNoEnc:
FelicaReadNoEnc(lpReq, &fResponse, lpCard);
break;
case FelicaCommand_WriteNoEnc:
FelicaWriteNoEnc(lpReq, &fResponse, lpCard);
break;
case FelicaCommand_RequestSystemCode:
FelicaSystemCode(lpReq, &fResponse, lpCard);
break;
case FelicaCommand_MobileActive2:
FelicaMobileActive2(lpReq, &fResponse, lpCard);
break;
default:
log_error(plfAime, "Unknown FeliCa command: %02x", felicaCmd);
break;
}
comio_reply(dev, req, TN32Error_OK, fResponse.m_FelicaLen, &fResponse);
}