//-----------------------------------------------------------------------------
// Copyright (C) Colin Brigato, 2016
// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// See LICENSE.txt for the text of the license.
//-----------------------------------------------------------------------------
// main code for HF Mifare aka ColinRun by Colin Brigato
//-----------------------------------------------------------------------------

#include "standalone.h" // standalone definitions

#include "hf_colin.h"
#include "proxmark3_arm.h"
#include "appmain.h"
#include "fpgaloader.h"
#include "dbprint.h"
#include "ticks.h"
#include "util.h"
#include "commonutil.h"
#include "BigBuf.h"
#include "iso14443a.h"
#include "mifareutil.h"
#include "mifaresim.h"
#include "vtsend.h"
#include "spiffs.h"
#include "frozen.h"

#define MF1KSZ 1024
#define MF1KSZSIZE 64
#define AUTHENTICATION_TIMEOUT 848
#define HFCOLIN_LASTTAG_SYMLINK "hf_colin/lasttag.bin"
#define HFCOLIN_SCHEMAS_JSON "hf_colin/schemas.json"

/* Example jsonconfig file schemas.json : (array !)
[{
  "name": "UrmetCaptive",
  "trigger": "0x8829da9daf76",
  "keysA": [
    "0x8829da9daf76",
    "0x8829da9daf76",
    "0x8829da9daf76",
    "0x8829da9daf76",
    "0x8829da9daf76",
    "0x8829da9daf76",
    "0x8829da9daf76",
    "0x8829da9daf76",
    "0x8829da9daf76",
    "0x8829da9daf76",
    "0x8829da9daf76",
    "0x8829da9daf76",
    "0x8829da9daf76",
    "0x8829da9daf76",
    "0x8829da9daf76",
    "0x8829da9daf76"
  ],
  "keysB": [
    "0x8829da9daf76",
    "0x8829da9daf76",
    "0x8829da9daf76",
    "0x8829da9daf76",
    "0x8829da9daf76",
    "0x8829da9daf76",
    "0x8829da9daf76",
    "0x8829da9daf76",
    "0x8829da9daf76",
    "0x8829da9daf76",
    "0x8829da9daf76",
    "0x8829da9daf76",
    "0x8829da9daf76",
    "0x8829da9daf76",
    "0x8829da9daf76",
    "0x8829da9daf76"
  ]
},{
  "name": "Noralsy",
...

]

*/

static uint8_t colin_cjuid[10];
static uint32_t colin_cjcuid;
static iso14a_card_select_t colin_p_card;
static int colin_currline;
static int colin_currfline;
static int colin_curlline;
static int cjat91_saMifareChkKeys(uint8_t blockNo, uint8_t keyType, bool clearTrace, uint8_t keyCount, const uint8_t *datain, uint64_t *key);
static int e_MifareECardLoad(uint32_t numofsectors, uint8_t keytype);
static void saMifareMakeTag(void);
static int saMifareCSetBlock(uint32_t arg0, uint32_t arg1, uint32_t arg2, const uint8_t *datain);
static void WriteTagToFlash(uint32_t uid, size_t size);

// TODO : Implement fast read of KEYS like in RFIdea
// also http://ext.delaat.net/rp/2015-2016/p04/report.pdf

// Colin's VIGIKPWN sniff/simulate/clone repeat routine for HF Mifare

static const uint8_t colin_is_hex[] = {
    0, 0,  0,  0,  0,  0,  0,  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0,
    0, 0,  0,  0,  0,  0,  0,  0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0, 0, 0, 0, 0, 0,
    0, 11, 12, 13, 14, 15, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0,
    0, 11, 12, 13, 14, 15, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0,
    0, 0,  0,  0,  0,  0,  0,  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0,
    0, 0,  0,  0,  0,  0,  0,  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0,
    0, 0,  0,  0,  0,  0,  0,  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0,
    0, 0,  0,  0,  0,  0,  0,  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0
};

static uint64_t hex2i(const char *s) {
    uint64_t val = 0;
    if (s == NULL || s[0] == 0)
        return 0;
    if (s[1] == 'x')
        s += 2;
    else if (*s == 'x')
        s++;
    while (colin_is_hex[(uint8_t)*s])
        val = (val << 4) | (colin_is_hex[(uint8_t) * (s++)] - 1);
    return val;
}

/*char *noralsy2test =
    "{\"name\":\"noralsy2\",\"trigger\":\"0x414C41524F4E\",\"keysA\":[\"0x414C41524F4E\",\"0x414C41524F4E\","
    "\"0x414C41524F4E\","
    "\"0x414C41524F4E\",\"0x414C41524F4E\",\"0x414C41524F4E\",\"0x414C41524F4E\",\"0x414C41524F4E\","
    "\"0x414C41524F4E\",\"0x414C41524F4E\","
    "\"0x414C41524F4E\",\"0x414C41524F4E\",\"0x414C41524F4E\",\"0x414C41524F4E\",\"0x414C41524F4E\","
    "\"0x414C41524F4E\"],\"keysB\":["
    "\"0x424C41524F4E\",\"0x424C41524F4E\",\"0x424C41524F4E\",\"0x424C41524F4E\",\"0x424C41524F4E\","
    "\"0x424C41524F4E\",\"0x424C41524F4E\","
    "\"0x424C41524F4E\",\"0x424C41524F4E\",\"0x424C41524F4E\",\"0x424C41524F4E\",\"0x424C41524F4E\","
    "\"0x424C41524F4E\",\"0x424C41524F4E\","
    "\"0x424C41524F4E\",\"0x424C41524F4E\"]}";*/

/*char *urmetcaptive2test =
    "{\"name\":\"urmetcaptive2\",\"trigger\":\"0x8829da9daf76\",\"keysA\":[\"0x8829da9daf76\",\"0x8829da9daf76\","
    "\"0x8829da9daf76\","
    "\"0x8829da9daf76\",\"0x8829da9daf76\",\"0x8829da9daf76\",\"0x8829da9daf76\",\"0x8829da9daf76\","
    "\"0x8829da9daf76\",\"0x8829da9daf76\","
    "\"0x8829da9daf76\",\"0x8829da9daf76\",\"0x8829da9daf76\",\"0x8829da9daf76\",\"0x8829da9daf76\","
    "\"0x8829da9daf76\"],\"keysB\":["
    "\"0x8829da9daf76\",\"0x8829da9daf76\",\"0x8829da9daf76\",\"0x8829da9daf76\",\"0x8829da9daf76\","
    "\"0x8829da9daf76\",\"0x8829da9daf76\","
    "\"0x8829da9daf76\",\"0x8829da9daf76\",\"0x8829da9daf76\",\"0x8829da9daf76\",\"0x8829da9daf76\","
    "\"0x8829da9daf76\",\"0x8829da9daf76\","
    "\"0x8829da9daf76\",\"0x8829da9daf76\"]}";*/

typedef struct {
    uint8_t name[32];
    uint64_t trigger;
    uint64_t keysA[16];
    uint64_t keysB[16];
} MFC1KSchema_t;

#define MAX_SCHEMAS 4

static void scan_keys(const char *str, int len, uint64_t *user_data) {
    struct json_token t;
    int i;
    char ks[32];
    for (i = 0; json_scanf_array_elem(str, len, "", i, &t) > 0; i++) {
        sprintf(ks, "%.*s", t.len, t.ptr);
        user_data[i] = hex2i(ks);
    }
}

static MFC1KSchema_t colin_Schemas[MAX_SCHEMAS];

/*MFC1KSchema_t Noralsy = {
    .name = "Noralsy",
    .trigger = 0x414c41524f4e,
    .keysA = {0x414c41524f4e, 0x414c41524f4e, 0x414c41524f4e, 0x414c41524f4e, 0x414c41524f4e, 0x414c41524f4e,
              0x414c41524f4e, 0x414c41524f4e, 0x414c41524f4e, 0x414c41524f4e, 0x414c41524f4e, 0x414c41524f4e,
              0x414c41524f4e, 0x414c41524f4e, 0x414c41524f4e, 0x414c41524f4e},
    .keysB = {0x424c41524f4e, 0x424c41524f4e, 0x424c41524f4e, 0x424c41524f4e, 0x424c41524f4e, 0x424c41524f4e,
              0x424c41524f4e, 0x424c41524f4e, 0x424c41524f4e, 0x424c41524f4e, 0x424c41524f4e, 0x424c41524f4e,
              0x424c41524f4e, 0x424c41524f4e, 0x424c41524f4e, 0x424c41524f4e}};

MFC1KSchema_t InfiHexact = {.name = "Infineon/Hexact",
                          .trigger = 0x484558414354,
                          .keysA = {0x484558414354, 0x484558414354, 0x484558414354, 0x484558414354, 0x484558414354,
                                    0x484558414354, 0x484558414354, 0x484558414354, 0x484558414354, 0x484558414354,
                                    0x484558414354, 0x484558414354, 0x484558414354, 0x484558414354, 0x484558414354,
                                    0x484558414354},
                          .keysB = {0xa22ae129c013, 0x49fae4e3849f, 0x38fcf33072e0, 0x8ad5517b4b18, 0x509359f131b1,
                                    0x6c78928e1317, 0xaa0720018738, 0xa6cac2886412, 0x62d0c424ed8e, 0xe64a986a5d94,
                                    0x8fa1d601d0a2, 0x89347350bd36, 0x66d2b7dc39ef, 0x6bc1e1ae547d, 0x22729a9bd40f}};
*/

/*MFC1KSchema_t UrmetCaptive = {
    .name = "Urmet Captive",
    .trigger = 0x8829da9daf76,
    .keysA = {0x8829da9daf76, 0x8829da9daf76, 0x8829da9daf76, 0x8829da9daf76, 0x8829da9daf76, 0x8829da9daf76,
              0x8829da9daf76, 0x8829da9daf76, 0x8829da9daf76, 0x8829da9daf76, 0x8829da9daf76, 0x8829da9daf76,
              0x8829da9daf76, 0x8829da9daf76, 0x8829da9daf76, 0x8829da9daf76},
    .keysB = {0x8829da9daf76, 0x8829da9daf76, 0x8829da9daf76, 0x8829da9daf76, 0x8829da9daf76, 0x8829da9daf76,
              0x8829da9daf76, 0x8829da9daf76, 0x8829da9daf76, 0x8829da9daf76, 0x8829da9daf76, 0x8829da9daf76,
              0x8829da9daf76, 0x8829da9daf76, 0x8829da9daf76, 0x8829da9daf76}};
*/

static int colin_total_schemas = 0;

static void add_schema(MFC1KSchema_t *p, MFC1KSchema_t a, int *schemas_counter) {
    if (*schemas_counter < MAX_SCHEMAS) {
        p[*schemas_counter] = a;
        *schemas_counter += 1;
    }
}
/*
static void delete_schema(MFC1KSchema_t *p, int *schemas_counter, int index) {
    if (*schemas_counter > 0 && index < *schemas_counter && index > -1) {
        int last_index = *schemas_counter - 1;
        for (int i = index; i < last_index; i++) {
            p[i] = p[i + 1];
        }
        *schemas_counter -= 1;
    }
}
*/
static void cjSetCursFRight(void) {
    vtsend_cursor_position(NULL, 98, (colin_currfline));
    colin_currfline++;
}

static void cjSetCursRight(void) {
    vtsend_cursor_position(NULL, 59, (colin_currline));
    colin_currline++;
}

static void cjSetCursLeft(void) {
    vtsend_cursor_position(NULL, 0, (colin_curlline));
    colin_curlline++;
}

static void cjTabulize(void) { DbprintfEx(FLAG_RAWPRINT, "\t\t\t"); }

static char *ReadSchemasFromSPIFFS(char *filename) {
    SpinOff(0);

    int changed = rdv40_spiffs_lazy_mount();
    uint32_t size = size_in_spiffs((char *)filename);
    uint8_t *mem = BigBuf_calloc(size);
    rdv40_spiffs_read_as_filetype((char *)filename, (uint8_t *)mem, size, RDV40_SPIFFS_SAFETY_SAFE);

    if (changed) {
        rdv40_spiffs_lazy_unmount();
    }
    SpinOff(0);
    return (char *)mem;
}

static void add_schemas_from_json_in_spiffs(char *filename) {

    const char *jsonfile = ReadSchemasFromSPIFFS((char *)filename);

    int i, len = strlen(jsonfile);
    struct json_token t;
    for (i = 0; json_scanf_array_elem(jsonfile, len, "", i, &t) > 0; i++) {
        char *tmpname;
        char *tmptrigger;
        MFC1KSchema_t tmpscheme;
        json_scanf(t.ptr, t.len, "{ name:%Q, trigger:%Q, keysA:%M, keysB:%M}", &tmpname, &tmptrigger, scan_keys,
                   &tmpscheme.keysA, scan_keys, &tmpscheme.keysB);
        memcpy(tmpscheme.name, tmpname, 32);
        tmpscheme.trigger = hex2i(tmptrigger);
        add_schema(colin_Schemas, tmpscheme, &colin_total_schemas);
        DbprintfEx(FLAG_NEWLINE, "Schema loaded : %s", tmpname);
        cjSetCursLeft();
    }
}

static void ReadLastTagFromFlash(void) {
    SpinOff(0);
    LED_A_ON();
    LED_B_ON();
    LED_C_ON();
    LED_D_ON();
    uint16_t len = 1024;
    size_t size = len;

    DbprintfEx(FLAG_NEWLINE, "Button HELD ! Using LAST Known TAG for Simulation...");
    cjSetCursLeft();

    uint8_t *mem = BigBuf_calloc(size);

    // this one will handle filetype (symlink or not) and resolving by itself
    rdv40_spiffs_read_as_filetype((char *)HFCOLIN_LASTTAG_SYMLINK, (uint8_t *)mem, len, RDV40_SPIFFS_SAFETY_SAFE);

    // copy 64blocks (16bytes) starting w block0, to emulator mem.
    emlSetMem_xt(mem, 0, 64, 16);

    DbprintfEx(FLAG_NEWLINE, "[OK] Last tag recovered from FLASHMEM set to emulator");
    cjSetCursLeft();
    SpinOff(0);
    return;
}

static void WriteTagToFlash(uint32_t uid, size_t size) {
    SpinOff(0);
    LED_A_ON();
    LED_B_ON();
    LED_C_ON();
    LED_D_ON();

    uint32_t len = size;
    uint8_t data[(size * (16 * 64)) / 1024];

    emlGetMem_xt(data, 0, (size * 64) / 1024, MIFARE_BLOCK_SIZE);

    char dest[SPIFFS_OBJ_NAME_LEN];
    uint8_t buid[4];
    num_to_bytes(uid, 4, buid);
    sprintf(dest, "hf_colin/mf_%02x%02x%02x%02x.bin", buid[0], buid[1], buid[2], buid[3]);

    // TODO : by using safe function for multiple writes we are both breaking cache mechanisms and making useless and
    // unoptimized mount operations we should manage at out level the mount status before and after the whole
    // standalone mode
    rdv40_spiffs_write((char *)dest, (uint8_t *)data, len, RDV40_SPIFFS_SAFETY_SAFE);
    // lastag will only contain filename/path to last written tag file so we don't loose time or space.
    rdv40_spiffs_make_symlink((char *)dest, (char *)HFCOLIN_LASTTAG_SYMLINK, RDV40_SPIFFS_SAFETY_SAFE);

    DbprintfEx(FLAG_NEWLINE, "[OK] TAG WRITTEN TO FLASH !");
    cjSetCursLeft();
    SpinOff(0);
    return;
}

void ModInfo(void) {
    DbpString("  HF Mifare ultra fast sniff/sim/clone - aka VIGIKPWN (Colin Brigato)");
}

void RunMod(void) {
    StandAloneMode();
    FpgaDownloadAndGo(FPGA_BITSTREAM_HF);
    Dbprintf(">>  HF Mifare ultra fast sniff/sim/clone  a.k.a VIGIKPWN Started  <<");

    // turn off all debugging.
    g_dbglevel = DBG_NONE;

    // add_schema(colin_Schemas, Noralsy, &colin_total_schemas);
    // add_schema(colin_Schemas, InfiHexact, &colin_total_schemas);
    // add_schema_from_json_in_spiffs((char *)HFCOLIN_URMETCAPTIVE_JSON);
    // add_schema(colin_Schemas, UrmetCaptive, &colin_total_schemas);

    colin_currline = 20;
    colin_curlline = 20;
    colin_currfline = 24;
    memset(colin_cjuid, 0, sizeof(colin_cjuid));
    colin_cjcuid = 0;
    uint8_t sectorsCnt = (MF1KSZ / MF1KSZSIZE);
    uint64_t key64;    // Defines current key
    uint8_t *keyBlock; // Where the keys will be held in memory.

    /* VIGIK EXPIRED DUMP FOR STUDY
    Sector 0
    121C7F730208040001FA33F5CB2D021D
    44001049164916491649000000000000
    00000000000000000000000000000000
    A0A1A2A3A4A579678800010203040506
    Sector 1
    0F000000000000000000000000000000
    AA0700002102080000740C110600AF13
    000000000000000001740C1108220000
    314B4947495679678800010203040506
    Sector 2
    24E572B923A3D243B402D60CAB576956
    216D6501FC8618B6C426762511AC2DEE
    25BF4CEC3618D0BAB3A6E9210D887746
    314B4947495679678800010203040506
    Sector 3
    0FBC41A5D95398E76A1B2029E8EA9735
    088BA2CE732653D0C1147596AFCF94D7
    77B4D91F0442182273A29DEAF7A2D095
    314B4947495679678800010203040506
    Sector 4
    4CEE715866E508CDBC95C640EC9D1E58
    E800457CF8B079414E1B45DD3E6C9317
    77B4D91F0442182273A29DEAF7A2D095
    314B4947495679678800010203040506
    010203040506  0
    Sector 5-0F
    00000000000000000000000000000000
    00000000000000000000000000000000
    00000000000000000000000000000000
    FFFFFFFFFFFFFF078069FFFFFFFFFFFF
    KEY A : 1KGIV ;
    ACCBITS : 796788[00]+VALUE
    */

// ----------------------------
//   Set of keys to be used.
//  This should cover ~98% of
//  French VIGIK system @2017
// ----------------------------

    const uint64_t mfKeys[] = {
        0xffffffffffff, // TRANSPORTS
        0x000000000000, // Blankkey
        0x484558414354, // INFINEONON A / 0F SEC B / INTRATONE / HEXACT...
        0x414c41524f4e, // ALARON NORALSY
        0x424c41524f4e, // BLARON NORALSY
        0x4a6352684677, // COMELIT A General Key  / 08 [2] 004
        0x536653644c65, // COMELIT B General Key  / 08 [2] 004
        0x8829da9daf76, // URMET CAPTIV IF A => ALL A/B / BTICINO
        0x314B49474956, // "1KIGIV" VIGIK'S SERVICE BADGE A KEY
        0xa0a1a2a3a4a5, // PUBLIC BLOC0 BTICINO MAD ACCESS
        0x021209197591, // BTCINO UNDETERMINED SPREAKD 0x01->0x13 key
        0x010203040506, // VIGIK's B Derivative
        0xb0b1b2b3b4b5, // NA DERIVATE B # 1
        0xaabbccddeeff, // NA DERIVATE B # 1
        0x4d3a99c351dd, // NA DERIVATE B # 1
        0x1a982c7e459a, // NA DERIVATE B # 1
        0xd3f7d3f7d3f7, // NA DERIVATE B # 1
        0x714c5c886e97, // NA DERIVATE B # 1
        0x587ee5f9350f, // NA DERIVATE B # 1
        0xa0478cc39091, // NA DERIVATE B # 1
        0x533cb6c723f6, // NA DERIVATE B # 1
        0x8fd0a4f256e9, // NA DERIVATE B # 1
        0xa22ae129c013, // INFINEON B 00
        0x49fae4e3849f, // INFINEON B 01
        0x38fcf33072e0, // INFINEON B 02
        0x8ad5517b4b18, // INFINEON B 03
        0x509359f131b1, // INFINEON B 04
        0x6c78928e1317, // INFINEON B 05
        0xaa0720018738, // INFINEON B 06
        0xa6cac2886412, // INFINEON B 07
        0x62d0c424ed8e, // INFINEON B 08
        0xe64a986a5d94, // INFINEON B 09
        0x8fa1d601d0a2, // INFINEON B 0A
        0x89347350bd36, // INFINEON B 0B
        0x66d2b7dc39ef, // INFINEON B 0C
        0x6bc1e1ae547d, // INFINEON B 0D
        0x22729a9bd40f  // INFINEON B 0E
    };

    // Can remember something like that in case of Bigbuf
    keyBlock = BigBuf_calloc(ARRAYLEN(mfKeys) * MF_KEY_LENGTH);
    int mfKeysCnt = ARRAYLEN(mfKeys);

    for (int mfKeyCounter = 0; mfKeyCounter < mfKeysCnt; mfKeyCounter++) {
        num_to_bytes(mfKeys[mfKeyCounter], MF_KEY_LENGTH, (uint8_t *)(keyBlock + (mfKeyCounter * MF_KEY_LENGTH)));
    }

    // TODO : remember why we actually had need to initialize this array in such specific case
    //   and why not a simple memset abuse to 0xffize the whole space in one go ?
    // uint8_t foundKey[2][40][6]; //= [ {0xff} ]; /* C99 abusal 6.7.8.21
    uint8_t foundKey[2][40][6];
    for (uint16_t i = 0; i < 2; i++) {
        for (uint16_t sectorNo = 0; sectorNo < sectorsCnt; sectorNo++) {
            foundKey[i][sectorNo][0] = 0xFF;
            foundKey[i][sectorNo][1] = 0xFF;
            foundKey[i][sectorNo][2] = 0xFF;
            foundKey[i][sectorNo][3] = 0xFF;
            foundKey[i][sectorNo][4] = 0xFF;
            foundKey[i][sectorNo][5] = 0xFF;
        }
    }

    int key = -1;
    bool err = 0;
    bool trapped = 0;
    bool allKeysFound = true;
    uint32_t size = mfKeysCnt;

    // banner:
    vtsend_reset(NULL);
    DbprintfEx(FLAG_NEWLINE, "\r\n%s", clearTerm);
    DbprintfEx(FLAG_NEWLINE, "%s%s%s", _XCYAN_, sub_banner, _XWHITE_);
    DbprintfEx(FLAG_NEWLINE, "%s>>%s C.J.B's MifareFastPwn Started\r\n", _XRED_, _XWHITE_);

    colin_currline = 20;
    colin_curlline = 20;
    colin_currfline = 24;
    cjSetCursLeft();

    add_schemas_from_json_in_spiffs((char *)HFCOLIN_SCHEMAS_JSON);

failtag:

    vtsend_cursor_position_save(NULL);
    vtsend_set_attribute(NULL, 1);
    vtsend_set_attribute(NULL, 5);
    DbprintfEx(FLAG_NEWLINE, "\t\t\t[ Waiting For Tag ]");
    vtsend_set_attribute(NULL, 0);

    iso14443a_setup(FPGA_HF_ISO14443A_READER_LISTEN);
    SpinOff(50);
    LED_A_ON();

    while (iso14443a_select_card(colin_cjuid, &colin_p_card, &colin_cjcuid, true, 0, true) == 0) {
        WDT_HIT();
        if (BUTTON_HELD(10) == BUTTON_HOLD) {
            WDT_HIT();
            DbprintfEx(FLAG_NEWLINE, "\t\t\t[    READING FLASH   ]");
            ReadLastTagFromFlash();
            goto readysim;
        }
        FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF);
        SpinDelay(500);
        LED_A_INV();
    }

    SpinOff(50);
    FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF);

    vtsend_cursor_position_restore(NULL);
    DbprintfEx(FLAG_NEWLINE, "\t\t\t%s[   GOT a Tag !   ]%s", _XGREEN_, _XWHITE_);
    cjSetCursLeft();
    DbprintfEx(FLAG_NEWLINE, "\t\t\t       `---> Breaking keys ---->");
    cjSetCursRight();

    DbprintfEx(FLAG_NEWLINE, "\t%sGOT TAG :%s %08x%s", _XRED_, _XCYAN_, colin_cjcuid, _XWHITE_);

    if (colin_cjcuid == 0) {
        cjSetCursLeft();
        DbprintfEx(FLAG_NEWLINE, "%s>>%s BUG: 0000_CJCUID! Retrying...", _XRED_, _XWHITE_);
        SpinErr(LED_A, 100, 8);
        goto failtag;
    }

    SpinOff(50);
    LED_B_ON();
    cjSetCursRight();
    DbprintfEx(FLAG_NEWLINE, "--------+--------------------+-------");
    cjSetCursRight();
    DbprintfEx(FLAG_NEWLINE, " SECTOR |        KEY         |  A/B  ");
    cjSetCursRight();
    DbprintfEx(FLAG_NEWLINE, "--------+--------------------+-------");

    uint32_t start_time = GetTickCount();
    uint32_t delta_time = 0;

    // ---------------------------------------------------------------------------
    // WE SHOULD FIND A WAY TO GET UID TO AVOID THIS "TESTRUN"
    // --------------------------------------------------------
    // + HERE IS TO BE THOUGHT AS ONLY A KEY SHOULD BE CHECK
    // `-+ THEN WE FILL EMULATOR WITH KEY
    // `-+ WHEN WE FILL EMULATOR CARD WITH A KEY
    // `-+ IF THERE IS ANY FAIL DURING ANY POINT, WE START BACK CHECKING B KEYS
    // `-+ THEN FILL EMULATOR WITH B KEEY
    // `-+ THEN EMULATOR WITH CARD WITH B KEY
    // `-+ IF IT HAS FAILED OF ANY OF SORT THEN WE ARE MARRON LIKE POMALO.
    // ----------------------------------------------------------------------------
    // AN EVEN BETTER IMPLEMENTATION IS TO CHECK EVERY KEY FOR SECTOR 0 KEY A
    // THEN IF FOUND CHECK THE SAME KEY FOR NEXT SECTOR ONLY KEY A
    // THEN IF FAIL CHECK EVERY SECTOR A KEY FOR EVERY OTHER KEY BUT NOT THE BLOCK
    // 0 KEY
    // THEN TRY TO READ B KEYS FROM KNOWN A KEYS
    // IF FAIL, CHECK SECTOR 0 B KEY WITH SECTOR 0 A KEY
    // THEN IF FOUND CHECK EVERY SECTOR FOR SAME B KEY
    // ELSE IF FAIL CHECK EVERY KEY FOR SECTOR 0 KEY B
    // THEN IF FOUND CHECK SAME KEY FOR ONLY NEXT SECTOR KEY B (PROBABLE A KEY IS
    // SAME FOR EVERY SECTOR AND B KEY IS SAME FOR EVERY SECTOR WITH JUST A vs B
    // DERIVATION
    // THEN IF B KEY IS NOT OF THIS SCHEME CHECK EVERY REMAINING B KEYED SECTOR
    // WITH EVERY REMAINING KEYS, BUT DISCARDING ANY DEFAULT TRANSPORT KEYS.
    // -----------------------------------------------------------------------------
    // also we could avoid first UID check for every block

    // then let's expose this optimal case of well known vigik schemes :
    for (uint8_t type = 0; type < 2 && !err && !trapped; type++) {
        for (int sec = 0; sec < sectorsCnt && !err && !trapped; ++sec) {
            key = cjat91_saMifareChkKeys(sec * 4, type, NULL, size, &keyBlock[0], &key64);

            if (key == -1) {
                err = 1;
                allKeysFound = false;
                // used in portable imlementation on microcontroller: it reports back the fail and open the
                // standalone lock reply_ng(CMD_CJB_FSMSTATE_MENU, NULL, 0);
                break;
            } else if (key == -2) {
                err = 1; // Can't select card.
                allKeysFound = false;
                // reply_old(CMD_CJB_FSMSTATE_MENU, 0, 0, 0, 0, 0);
                break;
            } else {
                /*  BRACE YOURSELF : AS LONG AS WE TRAP A KNOWN KEY, WE STOP CHECKING AND ENFORCE KNOWN SCHEMES */
                // uint8_t tosendkey[13];
                char tosendkey[13];
                num_to_bytes(key64, 6, foundKey[type][sec]);
                cjSetCursRight();
                DbprintfEx(FLAG_NEWLINE, "SEC: %02x ; KEY : %012" PRIx64 " ; TYP: %i", sec, key64, type);
                /*reply_old(CMD_CJB_INFORM_CLIENT_KEY, 12, sec, type, tosendkey, 12);*/

                for (int i = 0; i < colin_total_schemas; i++) {
                    if (key64 == colin_Schemas[i].trigger) {

                        cjSetCursLeft();
                        DbprintfEx(FLAG_NEWLINE, "%s>>>>>>>>>>>>!*STOP*!<<<<<<<<<<<<<<%s", _XRED_, _XWHITE_);
                        cjSetCursLeft();

                        DbprintfEx(FLAG_NEWLINE, "    .TAG SEEMS %sDETERMINISTIC%s.     ", _XGREEN_, _XWHITE_);
                        cjSetCursLeft();

                        DbprintfEx(FLAG_NEWLINE, "%sDetected: %s %s%s", _XORANGE_, _XCYAN_, colin_Schemas[i].name, _XWHITE_);
                        cjSetCursLeft();

                        DbprintfEx(FLAG_NEWLINE, "...%s[%sKey_derivation_schemeTest%s]%s...", _XYELLOW_, _XGREEN_,
                                   _XYELLOW_, _XGREEN_);
                        cjSetCursLeft();

                        DbprintfEx(FLAG_NEWLINE, "%s>>>>>>>>>>>>!*DONE*!<<<<<<<<<<<<<<%s", _XGREEN_, _XWHITE_);

                        uint16_t t = 0;
                        for (uint16_t s = 0; s < sectorsCnt; s++) {
                            num_to_bytes(colin_Schemas[i].keysA[s], 6, foundKey[t][s]);
                            sprintf(tosendkey, "%02x%02x%02x%02x%02x%02x", foundKey[t][s][0], foundKey[t][s][1],
                                    foundKey[t][s][2], foundKey[t][s][3], foundKey[t][s][4], foundKey[t][s][5]);
                            cjSetCursRight();
                            DbprintfEx(FLAG_NEWLINE, "SEC: %02x ; KEY : %s ; TYP: %d", s, tosendkey, t);
                        }
                        t = 1;
                        for (uint16_t s = 0; s < sectorsCnt; s++) {
                            num_to_bytes(colin_Schemas[i].keysB[s], 6, foundKey[t][s]);
                            sprintf(tosendkey, "%02x%02x%02x%02x%02x%02x", foundKey[t][s][0], foundKey[t][s][1],
                                    foundKey[t][s][2], foundKey[t][s][3], foundKey[t][s][4], foundKey[t][s][5]);
                            cjSetCursRight();
                            DbprintfEx(FLAG_NEWLINE, "SEC: %02x ; KEY : %s ; TYP: %d", s, tosendkey, t);
                        }
                        trapped = 1;
                        break;
                    }
                }
                /* etc etc for testing schemes quick schemes */
            }
        }
    }

    if (!allKeysFound) {
        cjSetCursLeft();
        cjTabulize();
        DbprintfEx(FLAG_NEWLINE, "%s[ FAIL ]%s\r\n->did not found all the keys :'(", _XRED_, _XWHITE_);
        cjSetCursLeft();
        SpinErr(LED_B, 100, 8);
        SpinOff(100);
        return;
    }

    // Settings keys to emulator
    emlClearMem();
    uint8_t mblock[16];
    for (uint8_t sectorNo = 0; sectorNo < sectorsCnt; sectorNo++) {
        emlGetMem_xt(mblock, FirstBlockOfSector(sectorNo) + NumBlocksPerSector(sectorNo) - 1, 1, MIFARE_BLOCK_SIZE);
        for (uint8_t t = 0; t < 2; t++) {
            memcpy(mblock + t * 10, foundKey[t][sectorNo], 6);
        }
        emlSetMem_xt(mblock, FirstBlockOfSector(sectorNo) + NumBlocksPerSector(sectorNo) - 1, 1, 16);
    }
    cjSetCursLeft();

    DbprintfEx(FLAG_NEWLINE, "%s>>%s Setting Keys->Emulator MEM...[%sOK%s]", _XYELLOW_, _XWHITE_, _XGREEN_, _XWHITE_);

    // filling TAG to emulator
    int filled;
    cjSetCursLeft();

    DbprintfEx(FLAG_NEWLINE, "%s>>%s Filling Emulator <- from A keys...", _XYELLOW_, _XWHITE_);
    filled = e_MifareECardLoad(sectorsCnt, 0);
    if (filled != PM3_SUCCESS) {
        cjSetCursLeft();

        DbprintfEx(FLAG_NEWLINE, "%s>>%s W_FAILURE ! %sTrying fallback B keys....", _XRED_, _XORANGE_, _XWHITE_);

        // no trace, no dbg
        filled = e_MifareECardLoad(sectorsCnt, 1);
        if (filled != PM3_SUCCESS) {
            cjSetCursLeft();
            DbprintfEx(FLAG_NEWLINE, "FATAL:EML_FALLBACKFILL_B");
            SpinErr(LED_C, 100, 8);
            SpinOff(100);
            return;
        }
    }

    delta_time = GetTickCountDelta(start_time);
    cjSetCursLeft();

    DbprintfEx(FLAG_NEWLINE, "%s>>%s Time for VIGIK break :%s%dms%s", _XGREEN_, _XWHITE_, _XYELLOW_, delta_time,
               _XWHITE_);

    vtsend_cursor_position_save(NULL);
    vtsend_set_attribute(NULL, 1);
    vtsend_set_attribute(NULL, 5);
    cjTabulize();
    DbprintfEx(FLAG_NEWLINE, "[    WRITING FLASH   ]");
    cjSetCursLeft();
    cjSetCursLeft();

    WriteTagToFlash(colin_cjcuid, 1024);

readysim:
    cjSetCursLeft();

    DbprintfEx(FLAG_NEWLINE, "-> We launch Emulation ->");
    cjSetCursLeft();

    DbprintfEx(FLAG_NEWLINE, "%s!> HOLD ON : %s When you'll click, simm will stop", _XRED_, _XWHITE_);
    cjSetCursLeft();

    DbprintfEx(FLAG_NEWLINE,
               "Then %s immediately %s we'll try to %s dump our emulator state%s \r\nin a %s chinese tag%s", _XRED_,
               _XWHITE_, _XYELLOW_, _XWHITE_, _XCYAN_, _XWHITE_);
    cjSetCursLeft();
    cjSetCursLeft();

    cjTabulize();

    DbprintfEx(FLAG_NEWLINE, "[    SIMULATION   ]");
    vtsend_set_attribute(NULL, 0);

    SpinOff(100);
    LED_C_ON();

    uint16_t flags = 0;
//    FLAG_SET_UID_IN_DATA(flags, colin_p_card.uidlen);
    FLAG_SET_UID_IN_EMUL(flags);
    FLAG_SET_MF_SIZE(flags, MIFARE_1K_MAX_BYTES);
    DbprintfEx(FLAG_NEWLINE, "\n\n\n\n\n\n\n\nn\n\nn\n\n\nflags: %d (0x%02x)", flags, flags);
    cjSetCursLeft();
    SpinOff(1000);
    Mifare1ksim(flags, 0, colin_cjuid, 0, 0);
    LED_C_OFF();
    SpinOff(50);
    vtsend_cursor_position_restore(NULL);
    DbprintfEx(FLAG_NEWLINE, "[   SIMUL ENDED   ]%s", _XGREEN_, _XWHITE_);
    cjSetCursLeft();

    DbprintfEx(FLAG_NEWLINE, "<- We're out of Emulation");
    // END SIM

    cjSetCursLeft();

    DbprintfEx(FLAG_NEWLINE, "-> Trying a clone !");
    saMifareMakeTag();
    cjSetCursLeft();
    vtsend_cursor_position_restore(NULL);
    DbprintfEx(FLAG_NEWLINE, "%s[ CLONED? ]", _XCYAN_);

    DbprintfEx(FLAG_NEWLINE, "-> End Cloning.");
    WDT_HIT();

    // Debunk...
    cjSetCursLeft();
    cjTabulize();
    vtsend_set_attribute(NULL, 0);
    vtsend_set_attribute(NULL, 7);
    DbprintfEx(FLAG_NEWLINE, "- [ LA FIN ] -\r\n%s`-> You can take shell back :) ...", _XWHITE_);
    cjSetCursLeft();
    vtsend_set_attribute(NULL, 0);
    SpinErr(LED_D, 100, 16);
    SpinDown(75);
    SpinOff(100);
    return;
}

/* Abusive microgain on original MifareECardLoad :
 * - *datain used as error return
 * - tracing is falsed
 */
static int e_MifareECardLoad(uint32_t numofsectors, uint8_t keytype) {
    uint8_t numSectors = numofsectors;
    uint8_t keyType = keytype;

    struct Crypto1State mpcs = {0, 0};
    struct Crypto1State *pcs;
    pcs = &mpcs;

    uint8_t dataoutbuf[16];
    uint8_t dataoutbuf2[16];

    iso14443a_setup(FPGA_HF_ISO14443A_READER_LISTEN);
    clear_trace();
    set_tracing(false);

    bool isOK = true;

    if (iso14443a_select_card(colin_cjuid, &colin_p_card, &colin_cjcuid, true, 0, true) == 0) {
        isOK = false;
    }

    for (uint8_t s = 0; isOK && s < numSectors; s++) {
        uint64_t ui64Key = emlGetKey(s, keyType);
        if (s == 0) {
            if (isOK && mifare_classic_auth(pcs, colin_cjcuid, FirstBlockOfSector(s), keyType, ui64Key, AUTH_FIRST)) {
                break;
            }
        } else {
            if (isOK && mifare_classic_auth(pcs, colin_cjcuid, FirstBlockOfSector(s), keyType, ui64Key, AUTH_NESTED)) {
                isOK = false;
                break;
            }
        }

        for (uint8_t blockNo = 0; isOK && blockNo < NumBlocksPerSector(s); blockNo++) {
            if (isOK && mifare_classic_readblock(pcs, FirstBlockOfSector(s) + blockNo, dataoutbuf)) {
                isOK = false;
                break;
            };
            if (isOK) {
                if (blockNo < NumBlocksPerSector(s) - 1) {
                    emlSetMem_xt(dataoutbuf, FirstBlockOfSector(s) + blockNo, 1, 16);
                } else {
                    // sector trailer, keep the keys, set only the AC
                    emlGetMem_xt(dataoutbuf2, FirstBlockOfSector(s) + blockNo, 1, MIFARE_BLOCK_SIZE);
                    memcpy(&dataoutbuf2[6], &dataoutbuf[6], 4);
                    emlSetMem_xt(dataoutbuf2, FirstBlockOfSector(s) + blockNo, 1, 16);
                }
            }
        }
    }

    int res = mifare_classic_halt(pcs);
    (void)res;

    crypto1_deinit(pcs);
    FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF);
    return (isOK) ? PM3_SUCCESS : PM3_EUNDEF;
}

/* the chk function is a piwi'ed(tm) check that will try all keys for
a particular sector. also no tracing no dbg */
static int cjat91_saMifareChkKeys(uint8_t blockNo, uint8_t keyType, bool clearTrace,
                                  uint8_t keyCount, const uint8_t *datain, uint64_t *key) {
    iso14443a_setup(FPGA_HF_ISO14443A_READER_LISTEN);
    set_tracing(false);

    struct Crypto1State mpcs = {0, 0};
    struct Crypto1State *pcs;
    pcs = &mpcs;

    int retval = -1;

    for (uint8_t i = 0; i < keyCount; i++) {

        /* no need for anticollision. just verify tag is still here */
        if (iso14443a_select_card(colin_cjuid, &colin_p_card, &colin_cjcuid, true, 0, true) == 0) {
            cjSetCursLeft();
            DbprintfEx(FLAG_NEWLINE, "%sFATAL%s : E_MF_LOSTTAG", _XRED_, _XWHITE_);
            break;
        }

        uint64_t ui64Key = bytes_to_num(datain + i * 6, 6);
        if (mifare_classic_auth(pcs, colin_cjcuid, blockNo, keyType, ui64Key, AUTH_FIRST)) {
            uint8_t dummy_answer = 0;
            ReaderTransmit(&dummy_answer, 1, NULL);
            // wait for the card to become ready again
            SpinDelayUs(AUTHENTICATION_TIMEOUT);
            continue;
        }
        *key = ui64Key;
        retval = i;
        break;
    }
    crypto1_deinit(pcs);
    FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF);
    return retval;
}

static void saMifareMakeTag(void) {
    uint8_t cfail = 0;
    cjSetCursLeft();
    cjTabulize();
    vtsend_cursor_position_save(NULL);
    vtsend_set_attribute(NULL, 1);
    DbprintfEx(FLAG_NEWLINE, "[ CLONING ]");
    vtsend_set_attribute(NULL, 0);

    cjSetCursFRight();

    DbprintfEx(FLAG_NEWLINE, ">> Write to Special:");
    int flags = 0;
    for (int blockNum = 0; blockNum < 16 * 4; blockNum++) {
        uint8_t mblock[16];
        emlGetMem_xt(mblock, blockNum, 1, MIFARE_BLOCK_SIZE);
        // switch on field and send magic sequence
        if (blockNum == 0)
            flags = 0x08 + 0x02;

        // just write
        if (blockNum == 1)
            flags = 0;

        // Done. Magic Halt and switch off field.
        if (blockNum == 16 * 4 - 1)
            flags = 0x04 + 0x10;

        if (saMifareCSetBlock(0, flags & 0xFE, blockNum, mblock)) {
            cjSetCursFRight();
            if (colin_currfline > 53) {
                colin_currfline = 54;
            }
            DbprintfEx(FLAG_NEWLINE, "Block :%02x %sOK%s", blockNum, _XGREEN_, _XWHITE_);
            continue;
        } else {
            cjSetCursLeft();
            cjSetCursLeft();
            DbprintfEx(FLAG_NEWLINE, "`--> %sFAIL%s : CHN_FAIL_BLK_%02x_NOK", _XRED_, _XWHITE_, blockNum);
            cjSetCursFRight();
            DbprintfEx(FLAG_NEWLINE, "%s>>>>%s STOP AT %02x", _XRED_, _XWHITE_, blockNum);
            cfail++;
            break;
        }
        cjSetCursFRight();
        DbprintfEx(FLAG_NEWLINE, "%s>>>>>>>> END <<<<<<<<%s", _XYELLOW_, _XWHITE_);
    }

    if (cfail == 0) {
        SpinUp(50);
        SpinUp(50);
        SpinUp(50);
    }
}

// TODO : make this work either for a Gen1a or for a block 0 direct write all transparently
//-----------------------------------------------------------------------------
// Matt's StandAlone mod.
// Work with "magic Chinese" card (email him: ouyangweidaxian@live.cn)
//-----------------------------------------------------------------------------
static int saMifareCSetBlock(uint32_t arg0, uint32_t arg1, uint32_t arg2, const uint8_t *datain) {
    // params
    uint8_t needWipe = arg0;
    // bit 0 - need get UID
    // bit 1 - need wupC
    // bit 2 - need HALT after sequence
    // bit 3 - need init FPGA and field before sequence
    // bit 4 - need reset FPGA and LED
    uint8_t workFlags = arg1;
    uint8_t blockNo = arg2;

    // card commands
    uint8_t wupC1[] = {0x40};
    uint8_t wupC2[] = {0x43};
    uint8_t wipeC[] = {0x41};

    // variables
    uint8_t isOK = 0;
    uint8_t d_block[18] = {0x00};

    uint8_t receivedAnswer[MAX_MIFARE_FRAME_SIZE];
    uint8_t receivedAnswerPar[MAX_MIFARE_PARITY_SIZE];

    // reset FPGA and LED
    if (workFlags & 0x08) {
        iso14443a_setup(FPGA_HF_ISO14443A_READER_LISTEN);
        //  clear_trace();
        set_tracing(FALSE);
    }

    while (true) {
        cjSetCursLeft();

        // get UID from chip
        if (workFlags & 0x01) {
            if (iso14443a_select_card(colin_cjuid, &colin_p_card, &colin_cjcuid, true, 0, true) == 0) {
                DbprintfEx(FLAG_NEWLINE, "Can't select card");
                break;
            };

            if (mifare_classic_halt(NULL)) {
                DbprintfEx(FLAG_NEWLINE, "Halt error");
                break;
            };
        };

        // reset chip
        if (needWipe) {
            ReaderTransmitBitsPar(wupC1, 7, 0, NULL);
            if ((ReaderReceive(receivedAnswer, sizeof(receivedAnswer), receivedAnswerPar) == 0) || (receivedAnswer[0] != 0x0a)) {
                DbprintfEx(FLAG_NEWLINE, "wupC1 error");
                break;
            };

            ReaderTransmit(wipeC, sizeof(wipeC), NULL);
            if ((ReaderReceive(receivedAnswer, sizeof(receivedAnswer), receivedAnswerPar) == 0) || (receivedAnswer[0] != 0x0a)) {
                DbprintfEx(FLAG_NEWLINE, "wipeC error");
                break;
            };

            if (mifare_classic_halt(NULL)) {
                DbprintfEx(FLAG_NEWLINE, "Halt error");
                break;
            };
        };

        // chaud
        // write block
        if (workFlags & 0x02) {
            ReaderTransmitBitsPar(wupC1, 7, 0, NULL);
            if ((ReaderReceive(receivedAnswer, sizeof(receivedAnswer), receivedAnswerPar) == 0) || (receivedAnswer[0] != 0x0a)) {
                DbprintfEx(FLAG_NEWLINE, "wupC1 error");
                break;
            };

            ReaderTransmit(wupC2, sizeof(wupC2), NULL);
            if ((ReaderReceive(receivedAnswer, sizeof(receivedAnswer), receivedAnswerPar) == 0) || (receivedAnswer[0] != 0x0a)) {
                DbprintfEx(FLAG_NEWLINE, "wupC2 errorv");
                break;
            };
        }

        if ((mifare_sendcmd_short(NULL, CRYPT_NONE, 0xA0, blockNo, receivedAnswer, sizeof(receivedAnswer), receivedAnswerPar, NULL) != 1) ||
                (receivedAnswer[0] != 0x0a)) {
            DbprintfEx(FLAG_NEWLINE, "write block send command error");
            break;
        };

        memcpy(d_block, datain, 16);
        AddCrc14A(d_block, 16);
        ReaderTransmit(d_block, sizeof(d_block), NULL);
        if ((ReaderReceive(receivedAnswer, sizeof(receivedAnswer), receivedAnswerPar) != 1) || (receivedAnswer[0] != 0x0a)) {
            DbprintfEx(FLAG_NEWLINE, "write block send data error");
            break;
        };

        if (workFlags & 0x04) {
            if (mifare_classic_halt(NULL)) {
                cjSetCursFRight();

                DbprintfEx(FLAG_NEWLINE, "Halt error");
                break;
            };
        }

        isOK = 1;
        break;
    }

    if ((workFlags & 0x10) || (!isOK)) {
        FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF);
    }

    return isOK;
}
