#include "bng.h"

#include <stdlib.h>

#include "../../../lib/mice/blowfish.h"
#include "../../../lib/mice/des.h"

// Are these SEGA-specific or used for all FeliCa cards?
// Commentary: The MAC validation code in bnacard can take a key as an argument
//             and uses the below key if NULL is passed (which it is).
//             The NbgiAccessCode library names this `ucFK` which doesn't feel
//             like a name someone would pick out of the blue.
DWORD64 CK_GEN_KEY[3] = { 0x1223491caf37206d, 0x5f3f1d16badd58a2, 0xc2f2ea946a755ee7 };

#pragma pack(push, 1)
typedef struct BNG_SIGNED_BLOCK {
    WORD m_BNAVersion;
    BYTE m_Name[6];
    DWORD m_BFLeft;
    DWORD m_BFRight;
} BNG_SIGNED_BLOCK, *PBNG_SIGNED_BLOCK;
#pragma pack(pop)

typedef struct {
    char m_Name[8];
    BYTE m_Key[56];
    BYTE Unk40[72];
} BLOWFISH_KEY;

static BLOWFISH_KEY DataKeyNBGIC[3] = {
    {
        .m_Name = { 'N', 'B', 'G', 'I', 'C', '0', '\0', '\0' },
        .m_Key = { 0x6b, 0xf9, 0xe0, 0x22, 0x4b, 0x14, 0xa9, 0x51, 0xd4, 0x3d, 0xb6, 0x2d,
                   0x2a, 0x99, 0x47, 0xd8, 0xb2, 0x78, 0x80, 0x35, 0x8e, 0x71, 0x16, 0xeb,
                   0xa0, 0x00, 0x01, 0x3e, 0x8c, 0xfe, 0xf6, 0x09, 0x02, 0xa9, 0x59, 0x5d,
                   0x4c, 0x25, 0xfe, 0xff, 0x74, 0x3f, 0xc8, 0xd8, 0xe0, 0x75, 0x6a, 0xe6,
                   0x18, 0xef, 0x8b, 0x72, 0x12, 0xde, 0x5a, 0x4b },
        .Unk40 = { 0x97, 0xba, 0xa5, 0x1b, 0x46, 0x04, 0x87, 0x43, 0x9f, 0x21, 0x5b, 0xe9,
                   0xe1, 0xd0, 0x98, 0x72, 0x27, 0x91, 0x31, 0xba, 0x8a, 0x82, 0x8f, 0xab,
                   0x50, 0x7f, 0x44, 0x7f, 0x8b, 0xc5, 0xe3, 0x4a, 0xc3, 0x32, 0xb7, 0xcb,
                   0xd8, 0x4f, 0xb1, 0x4c, 0x45, 0x21, 0x20, 0xe2, 0xa0, 0x71, 0x38, 0x4d,
                   0xe6, 0x95, 0x89, 0xc1, 0x5b, 0x04, 0x14, 0xc6, 0x9d, 0xcd, 0x03, 0x1c,
                   0xd0, 0xa8, 0x15, 0x3a, 0xf4, 0x6d, 0x3d, 0xea, 0x84, 0xec, 0xcd, 0x40 },
    },
    {
        .m_Name = { 'N', 'B', 'G', 'I', 'C', '1', '\0', '\0' },
        .m_Key = { 0x5e, 0x6b, 0xb1, 0x92, 0xc0, 0x52, 0x2f, 0x4c, 0xa0, 0x29, 0x6a, 0x1c,
                   0x98, 0x62, 0xfb, 0xeb, 0xac, 0xf5, 0xe1, 0xd7, 0x66, 0xe6, 0x34, 0x69,
                   0x40, 0x47, 0x5b, 0x96, 0xfe, 0x17, 0x89, 0x3d, 0x18, 0xae, 0x98, 0xf8,
                   0xbd, 0x6d, 0xfa, 0x12, 0xd6, 0xcd, 0xf5, 0x81, 0x7b, 0x78, 0xd6, 0x60,
                   0x71, 0x8f, 0x7a, 0x28, 0xca, 0x3c, 0xfd, 0x2f },
        .Unk40 = { 0xa6, 0x29, 0x78, 0x80, 0x63, 0xae, 0xa3, 0xa7, 0x84, 0xdd, 0x24, 0x60,
                   0x62, 0x86, 0x10, 0x97, 0x80, 0x75, 0xb4, 0x3d, 0x44, 0x81, 0xc9, 0x9f,
                   0xf4, 0x4a, 0xf7, 0xfd, 0xe4, 0xc8, 0x96, 0xcb, 0x9c, 0x2f, 0x3b, 0x6d,
                   0x95, 0xfe, 0x49, 0x54, 0x9d, 0x36, 0xea, 0x58, 0xcb, 0xf4, 0x0e, 0x06,
                   0x42, 0xfa, 0x6f, 0xb5, 0x1b, 0xc7, 0x88, 0x50, 0xea, 0x7b, 0x5f, 0x60,
                   0x56, 0xdb, 0x6f, 0x03, 0xd7, 0x84, 0x75, 0x4e, 0x07, 0x9d, 0xbc, 0x05 },
    },
    {
        .m_Name = { 'N', 'B', 'G', 'I', 'C', '2', '\0', '\0' },
        .m_Key = { 0xde, 0x15, 0x00, 0x6f, 0x45, 0xe2, 0x73, 0x66, 0xaa, 0x74, 0x8f, 0x98,
                   0x14, 0x6e, 0x05, 0x7b, 0x52, 0x18, 0x29, 0xd5, 0x0a, 0x65, 0xa9, 0x0c,
                   0xfe, 0xca, 0xd3, 0x8e, 0x04, 0xcf, 0x3e, 0x72, 0x31, 0xfb, 0x76, 0x50,
                   0xfb, 0x8c, 0x67, 0x09, 0x95, 0x08, 0xff, 0xf0, 0xc2, 0x97, 0x31, 0x5f,
                   0xde, 0x6c, 0x54, 0x32, 0xec, 0x8e, 0x2c, 0x6c },
        .Unk40 = { 0xc0, 0x6c, 0xa9, 0x7d, 0xd1, 0x10, 0x35, 0xc2, 0xa8, 0x6d, 0x52, 0xfc,
                   0x0a, 0x8c, 0x1a, 0xf7, 0xd1, 0x61, 0xea, 0x40, 0x49, 0x86, 0x7f, 0x37,
                   0x3f, 0xd1, 0x84, 0x2e, 0x98, 0xd9, 0x6d, 0x2c, 0x5e, 0x80, 0xe0, 0xe1,
                   0x92, 0x7c, 0xf2, 0x09, 0xef, 0x11, 0x44, 0x29, 0xe9, 0x4b, 0xb9, 0x3a,
                   0x87, 0x27, 0x5c, 0xa8, 0x05, 0xca, 0xfc, 0xcd, 0x3f, 0x21, 0x91, 0x17,
                   0xf5, 0x05, 0x6b, 0x82, 0x6b, 0x47, 0xdc, 0x06, 0x75, 0x1f, 0x04, 0x30 },
    },
};

void NbgiWriteBNESignedBlock(PNFC_CARD lpCard, BYTE block, BYTE nKey, LPBYTE lpData) {
    if (nKey > 2) return;
    PBNG_SIGNED_BLOCK lpBlock = (PBNG_SIGNED_BLOCK)&lpCard->m_FeliCaData.m_UserBlocks[block].bytes;
    lpBlock->m_BNAVersion = 0x200;
    memcpy(lpBlock->m_Name, DataKeyNBGIC[nKey].m_Name, 6);

    lpData[7] = lpData[0] ^ lpData[1] ^ lpData[2] ^ lpData[3] ^ lpData[4] ^ lpData[5] ^ lpData[6];

    DWORD dataLeft = ((LPDWORD)lpData)[0];
    DWORD dataRight = ((LPDWORD)lpData)[1];
    BlowfishSetKey(DataKeyNBGIC[nKey].m_Key, _countof(DataKeyNBGIC[nKey].m_Key));
    BlowfishEncrypt(&dataLeft, &dataRight);
    lpBlock->m_BFLeft = dataLeft;
    lpBlock->m_BFRight = dataRight;
}

static inline DWORD64 _FelicaMacCKNested(DWORD64 data, DWORD64 key) {
    data = Mice3Des_3K(data, CK_GEN_KEY);
    data ^= key;
    data = Mice3Des_3K(data, CK_GEN_KEY);
    return data;
}

void FelicaMacGenerateCK(DWORD64* ID, KEY_PAIR lpKeys) {
    DWORD64 data = Mice3Des_3K(0x0, CK_GEN_KEY);

    BOOL will_overflow = ((LPBYTE)&data)[0] & 0x80;

    // Binary rotate left 1. While `<<= 1` would be lovely here, the carries
    // are expected to be performed (incorrectly) to the right.
    for (BYTE i = 0; i < 8; i++) {
        if (i != 0 && ((LPBYTE)&data)[i] & 0x80) ((LPBYTE)&data)[i - 1] |= 1;
        ((LPBYTE)&data)[i] <<= 1;
    }
    if (will_overflow) ((LPBYTE)&data)[7] ^= 0x1b;

    DWORD64 idKey = data ^ ID[1];
    lpKeys[0] = _byteswap_uint64(_FelicaMacCKNested(ID[0], idKey));
    ((LPBYTE)&ID[0])[0] ^= 0x80;
    lpKeys[1] = _byteswap_uint64(_FelicaMacCKNested(ID[0], idKey));
    ((LPBYTE)&ID[0])[0] ^= 0x80;  // Undo our modification above
}
void FelicaMacGenerateSK(DWORD64* random, KEY_PAIR ck, KEY_PAIR lpSk) {
    lpSk[0] = Mice3Des_2K(_byteswap_uint64(random[0]), ck);
    lpSk[1] = lpSk[0] ^ _byteswap_uint64(random[1]);
    lpSk[1] = Mice3Des_2K(lpSk[1], ck);
}
DWORD64 FelicaMacGenerateMAC(DWORD64* data, KEY_PAIR sk, DWORD64* random) {
    DWORD64 mac = data[0] ^ random[0];
    mac = Mice3Des_2K(_byteswap_uint64(mac), sk);
    mac = mac ^ _byteswap_uint64(data[1]);
    return Mice3Des_2K(mac, sk);
}
