//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
// High frequency MIFARE  Plus commands
//-----------------------------------------------------------------------------

#include "cmdhfmfp.h"
#include "cmdhfmfdes.h"
#include <string.h>
#include "cmdparser.h"    // command_t
#include "commonutil.h"  // ARRAYLEN
#include "comms.h"
#include "ui.h"
#include "util.h"
#include "cmdhf14a.h"
#include "mifare/mifare4.h"
#include "mifare/mad.h"
#include "nfc/ndef.h"
#include "cliparser.h"
#include "mifare/mifaredefault.h"
#include "util_posix.h"
#include "fileutils.h"
#include "protocols.h"
#include "crypto/libpcrypto.h"
#include "cmdhfmf.h"    // printblock, header
#include "cmdtrace.h"
#include "crypto/originality.h"

static const uint8_t mfp_default_key[16] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
static uint16_t mfp_card_adresses[] = {0x9000, 0x9001, 0x9002, 0x9003, 0x9004, 0x9006, 0x9007, 0xA000, 0xA001, 0xA080, 0xA081, 0xC000, 0xC001};

#define MFP_KEY_FILE_SIZE  14 + (2 * 64 * (AES_KEY_LEN + 1))
#define MFP_CHK_KEY_TRIES  (2)

static int CmdHelp(const char *Cmd);

/*
  The 7 MSBits (= n) code the storage size itself based on 2^n,
  the LSBit is set to '0' if the size is exactly 2^n
    and set to '1' if the storage size is between 2^n and 2^(n+1).
    For this version of DESFire the 7 MSBits are set to 0x0C (2^12 = 4096) and the LSBit is '0'.
*/
static char *getCardSizeStr(uint8_t fsize) {

    static char buf[40] = {0x00};
    char *retStr = buf;

    uint16_t usize = 1 << ((fsize >> 1) + 1);
    uint16_t lsize = 1 << (fsize >> 1);

    // is  LSB set?
    if (fsize & 1)
        snprintf(retStr, sizeof(buf), "0x%02X ( " _GREEN_("%d - %d bytes") " )", fsize, usize, lsize);
    else
        snprintf(retStr, sizeof(buf), "0x%02X ( " _GREEN_("%d bytes") " )", fsize, lsize);
    return buf;
}

static char *getProtocolStr(uint8_t id, bool hw) {

    static char buf[50] = {0x00};
    char *retStr = buf;

    if (id == 0x04) {
        snprintf(retStr, sizeof(buf), "0x%02X ( " _YELLOW_("ISO 14443-3 MIFARE, 14443-4") " )", id);
    } else if (id == 0x05) {
        if (hw)
            snprintf(retStr, sizeof(buf), "0x%02X ( " _YELLOW_("ISO 14443-2, 14443-3") " )", id);
        else
            snprintf(retStr, sizeof(buf), "0x%02X ( " _YELLOW_("ISO 14443-3, 14443-4") " )", id);
    } else {
        snprintf(retStr, sizeof(buf), "0x%02X ( " _YELLOW_("Unknown") " )", id);
    }
    return buf;
}

static char *getVersionStr(uint8_t type, uint8_t major, uint8_t minor) {

    static char buf[40] = {0x00};
    char *retStr = buf;

    if (type == 0x01 && major == 0x00)
        snprintf(retStr, sizeof(buf), "%x.%x ( " _GREEN_("DESFire MF3ICD40") " )", major, minor);
    else if (major == 0x10 && minor == 0x00)
        snprintf(retStr, sizeof(buf), "%x.%x ( " _GREEN_("NTAG413DNA") " )", major, minor);
    else if (type == 0x01 && major == 0x01 && minor == 0x00)
        snprintf(retStr, sizeof(buf), "%x.%x ( " _GREEN_("DESFire EV1") " )", major, minor);
    else if (type == 0x01 && major == 0x12 && minor == 0x00)
        snprintf(retStr, sizeof(buf), "%x.%x ( " _GREEN_("DESFire EV2") " )", major, minor);
    else if (type == 0x01 && major == 0x22 && minor == 0x00)
        snprintf(retStr, sizeof(buf), "%x.%x ( " _GREEN_("DESFire EV2 XL") " )", major, minor);
    else if (type == 0x01 && major == 0x42 && minor == 0x00)
        snprintf(retStr, sizeof(buf), "%x.%x ( " _GREEN_("DESFire EV2") " )", major, minor);
    else if (type == 0x01 && major == 0x33 && minor == 0x00)
        snprintf(retStr, sizeof(buf), "%x.%x ( " _GREEN_("DESFire EV3") " )", major, minor);
    else if (type == 0x01 && major == 0x30 && minor == 0x00)
        snprintf(retStr, sizeof(buf), "%x.%x ( " _GREEN_("DESFire Light") " )", major, minor);
    else if (type == 0x02 && major == 0x11 && minor == 0x00)
        snprintf(retStr, sizeof(buf), "%x.%x ( " _GREEN_("Plus EV1") " )", major, minor);
    else if (type == 0x02 && major == 0x22 && minor == 0x00)
        snprintf(retStr, sizeof(buf), "%x.%x ( " _GREEN_("Plus EV2") " )", major, minor);
    else
        snprintf(retStr, sizeof(buf), "%x.%x ( " _YELLOW_("Unknown") " )", major, minor);
    return buf;
}

static char *getTypeStr(uint8_t type) {

    static char buf[40] = {0x00};
    char *retStr = buf;

    switch (type) {
        case 0x01:
            snprintf(retStr, sizeof(buf), "0x%02X ( " _YELLOW_("DESFire") " )", type);
            break;
        case 0x02:
            snprintf(retStr, sizeof(buf), "0x%02X ( " _YELLOW_("Plus") " )", type);
            break;
        case 0x03:
            snprintf(retStr, sizeof(buf), "0x%02X ( " _YELLOW_("Ultralight") " )", type);
            break;
        case 0x04:
            snprintf(retStr, sizeof(buf), "0x%02X ( " _YELLOW_("NTAG") " )", type);
            break;
        case 0x81:
            snprintf(retStr, sizeof(buf), "0x%02X ( " _YELLOW_("Smartcard") " )", type);
            break;
        default:
            break;
    }
    return buf;
}

// --- GET SIGNATURE
static int plus_print_signature(uint8_t *uid, uint8_t uidlen, uint8_t *signature, int signature_len) {
    int index = originality_check_verify(uid, uidlen, signature, signature_len, PK_MFP);
    return originality_check_print(signature, signature_len, index);
}

static int get_plus_signature(uint8_t *signature, int *signature_len) {

    mfpSetVerboseMode(false);

    uint8_t data[59] = {0};
    int resplen = 0, retval = PM3_SUCCESS;
    MFPGetSignature(true, false, data, sizeof(data), &resplen);

    if (resplen == 59) {
        memcpy(signature, data + 1, 56);
        *signature_len = 56;
    } else {
        *signature_len = 0;
        retval = PM3_ESOFT;
    }

    return retval;
}

// GET VERSION
static int plus_print_version(uint8_t *version) {
    if ((version[14] == 0x00) && (version[15] == 0x04)) {
        PrintAndLogEx(SUCCESS, "UID: " _GREEN_("%s"), sprint_hex(version + 16, 4));
        PrintAndLogEx(SUCCESS, "Batch number: " _GREEN_("%s"), sprint_hex(version + 20, 5));
        PrintAndLogEx(SUCCESS, "Production date: week " _GREEN_("%02x") " / " _GREEN_("20%02x"), version[7 + 7 + 6 + 5], version[7 + 7 + 7 + 4 + 1]);
    } else {
        PrintAndLogEx(SUCCESS, "UID: " _GREEN_("%s"), sprint_hex(version + 14, 7));
        PrintAndLogEx(SUCCESS, "Batch number: " _GREEN_("%s"), sprint_hex(version + 21, 5));
        PrintAndLogEx(SUCCESS, "Production date: week " _GREEN_("%02x") " / " _GREEN_("20%02x"), version[7 + 7 + 7 + 5], version[7 + 7 + 7 + 5 + 1]);
    }
    PrintAndLogEx(NORMAL, "");
    PrintAndLogEx(INFO, "--- " _CYAN_("Hardware Information"));
    PrintAndLogEx(INFO, "          Raw : %s", sprint_hex(version, 7));
    PrintAndLogEx(INFO, "     Vendor Id: " _YELLOW_("%s"), getTagInfo(version[0]));
    PrintAndLogEx(INFO, "          Type: %s", getTypeStr(version[1]));
    PrintAndLogEx(INFO, "       Subtype: " _YELLOW_("0x%02X"), version[2]);
    PrintAndLogEx(INFO, "       Version: %s", getVersionStr(version[1], version[3], version[4]));
    PrintAndLogEx(INFO, "  Storage size: %s", getCardSizeStr(version[5]));
    PrintAndLogEx(INFO, "      Protocol: %s", getProtocolStr(version[6], true));
    PrintAndLogEx(NORMAL, "");
    PrintAndLogEx(INFO, "--- " _CYAN_("Software Information"));
    PrintAndLogEx(INFO, "          Raw : %s", sprint_hex(version + 7, 6));
    PrintAndLogEx(INFO, "     Vendor Id: " _YELLOW_("%s"), getTagInfo(version[7]));
    PrintAndLogEx(INFO, "          Type: %s", getTypeStr(version[8]));
    PrintAndLogEx(INFO, "       Subtype: " _YELLOW_("0x%02X"), version[9]);
    PrintAndLogEx(INFO, "       Version: " _YELLOW_("%d.%d"),  version[10], version[11]);
    PrintAndLogEx(INFO, "  Storage size: %s", getCardSizeStr(version[12]));
    PrintAndLogEx(INFO, "      Protocol: %s", getProtocolStr(version[13], false));
    return PM3_SUCCESS;
}

static int get_plus_version(uint8_t *version, int *version_len) {

    int resplen = 0, retval = PM3_SUCCESS;
    mfpSetVerboseMode(false);
    MFPGetVersion(true, false, version, *version_len, &resplen);

    *version_len = resplen;
    if (resplen != 28) {
        retval = PM3_ESOFT;
    }
    return retval;
}

static int mfp_read_card_id(iso14a_card_select_t *card, int *nxptype) {

    if (card == NULL) {
        return PM3_EINVARG;
    }

    clearCommandBuffer();
    SendCommandMIX(CMD_HF_ISO14443A_READER, ISO14A_CONNECT | ISO14A_NO_DISCONNECT, 0, 0, NULL, 0);
    PacketResponseNG resp;
    if (WaitForResponseTimeout(CMD_ACK, &resp, 2500) == false) {
        PrintAndLogEx(DEBUG, "iso14443a card select failed");
        DropField();
        return PM3_ERFTRANS;
    }

    memcpy(card, (iso14a_card_select_t *)resp.data.asBytes, sizeof(iso14a_card_select_t));

    if (nxptype) {
        uint64_t select_status = resp.oldarg[0];

        uint8_t ats_hist_pos = 0;
        if ((card->ats_len > 3) && (card->ats[0] > 1)) {
            ats_hist_pos = 2;
            ats_hist_pos += (card->ats[1] & 0x10) == 0x10;
            ats_hist_pos += (card->ats[1] & 0x20) == 0x20;
            ats_hist_pos += (card->ats[1] & 0x40) == 0x40;
        }

        version_hw_t version_hw = {0};
        // if 4b UID or NXP, try to get version
        int res = hf14a_getversion_data(card, select_status, &version_hw);
        DropField();

        bool version_hw_available = (res == PM3_SUCCESS);

        *nxptype = detect_nxp_card(card->sak
                                   , ((card->atqa[1] << 8) + card->atqa[0])
                                   , select_status
                                   , card->ats_len - ats_hist_pos
                                   , card->ats + ats_hist_pos
                                   , version_hw_available
                                   , &version_hw
                                  );
    }
    return PM3_SUCCESS;
}

static int mfp_load_keygen_keys(uint8_t **pkeyBlock, uint32_t *pkeycnt, uint8_t *uid) {

    // Handle dymanica keys

    return PM3_SUCCESS;
}

static int mfp_load_keys(uint8_t **pkeyBlock, uint32_t *pkeycnt, uint8_t *userkey, int userkeylen, const char *filename, int fnlen, uint8_t *uid, bool load_default) {
    // Handle Keys
    *pkeycnt = 0;
    *pkeyBlock = NULL;
    uint8_t *p;

    // Handle KDF uid based keys
    if (uid) {
        mfp_load_keygen_keys(pkeyBlock, pkeycnt, uid);
    }

    // Handle user supplied key
    // (it considers *pkeycnt and *pkeyBlock as possibly non-null so logic can be easily reordered)
    if (userkeylen >= AES_KEY_LEN) {
        int numKeys = userkeylen / AES_KEY_LEN;
        p = realloc(*pkeyBlock, (*pkeycnt + numKeys) * AES_KEY_LEN);
        if (p == NULL) {
            PrintAndLogEx(WARNING, "Failed to allocate memory");
            free(*pkeyBlock);
            return PM3_EMALLOC;
        }
        *pkeyBlock = p;

        memcpy(*pkeyBlock, userkey, numKeys * AES_KEY_LEN);

        for (int i = 0; i < numKeys; i++) {
            PrintAndLogEx(DEBUG, _YELLOW_("%2d") " - %s", i, sprint_hex_inrow(*pkeyBlock + i * AES_KEY_LEN, AES_KEY_LEN));
        }
        *pkeycnt += numKeys;
        PrintAndLogEx(SUCCESS, "loaded " _GREEN_("%d") " user keys", numKeys);
    }

    if (load_default) {
        // Handle default keys
        p = realloc(*pkeyBlock, (*pkeycnt + g_mifare_plus_default_keys_len) * AES_KEY_LEN);
        if (p == NULL) {
            PrintAndLogEx(WARNING, "Failed to allocate memory");
            free(*pkeyBlock);
            return PM3_EMALLOC;
        }
        *pkeyBlock = p;

        // Copy default keys to list
        size_t cnt = 0;
        for (cnt = 0; cnt < g_mifare_plus_default_keys_len; cnt++) {

            int len = hex_to_bytes(g_mifare_plus_default_keys[cnt], (uint8_t *)(*pkeyBlock + (*pkeycnt + cnt) * AES_KEY_LEN), AES_KEY_LEN);

            PrintAndLogEx(DEBUG, _YELLOW_("%2u") " - %s", *pkeycnt + cnt, sprint_hex_inrow(*pkeyBlock + (*pkeycnt + cnt) * AES_KEY_LEN, AES_KEY_LEN));
            if (len != AES_KEY_LEN) {
                break;
            }
        }
        *pkeycnt += cnt;
        PrintAndLogEx(SUCCESS, "loaded " _GREEN_("%zu") " hardcoded keys", cnt);
    }

    // Handle user supplied dictionary file
    if (fnlen > 0) {

        uint32_t loaded_numKeys = 0;
        uint8_t *dict_keys = NULL;

        int res = loadFileDICTIONARY_safe(filename, (void **) &dict_keys, AES_KEY_LEN, &loaded_numKeys);

        if (res != PM3_SUCCESS || loaded_numKeys == 0 || dict_keys == NULL) {
            PrintAndLogEx(FAILED, "An error occurred while loading the dictionary!");
            free(dict_keys);
            free(*pkeyBlock);
            return PM3_EFILE;

        } else {

            p = realloc(*pkeyBlock, (*pkeycnt + loaded_numKeys) * AES_KEY_LEN);
            if (p == NULL) {
                PrintAndLogEx(WARNING, "Failed to allocate memory");
                free(dict_keys);
                free(*pkeyBlock);
                return PM3_EMALLOC;
            }
            *pkeyBlock = p;

            memcpy(*pkeyBlock + *pkeycnt * AES_KEY_LEN, dict_keys, loaded_numKeys * AES_KEY_LEN);

            *pkeycnt += loaded_numKeys;

            free(dict_keys);
        }
    }
    return PM3_SUCCESS;
}


static int CmdHFMFPInfo(const char *Cmd) {
    CLIParserContext *ctx;
    CLIParserInit(&ctx, "hf mfp info",
                  "Get info from MIFARE Plus tags",
                  "hf mfp info");

    void *argtable[] = {
        arg_param_begin,
        arg_param_end
    };
    CLIExecWithReturn(ctx, Cmd, argtable, true);

    PrintAndLogEx(NORMAL, "");
    PrintAndLogEx(INFO, "--- " _CYAN_("Tag Information") " ---------------------------");

    // Mifare Plus info
    SendCommandMIX(CMD_HF_ISO14443A_READER, ISO14A_CONNECT, 0, 0, NULL, 0);
    PacketResponseNG resp;
    if (WaitForResponseTimeout(CMD_ACK, &resp, 2000) == false) {
        PrintAndLogEx(DEBUG, "iso14443a card select timeout");
        DropField();
        return false;
    }

    iso14a_card_select_t card;
    memcpy(&card, (iso14a_card_select_t *)resp.data.asBytes, sizeof(iso14a_card_select_t));

    uint64_t select_status = resp.oldarg[0]; // 0: couldn't read, 1: OK, with ATS, 2: OK, no ATS, 3: proprietary Anticollision

    bool Version4BUID = false;
    bool supportVersion = false;
    bool supportSignature = false;

    // version check
    uint8_t version[30] = {0};
    uint8_t uid4b[4] = {0};
    uint8_t uid7b[7] = {0};
    int version_len = sizeof(version);
    if (get_plus_version(version, &version_len) == PM3_SUCCESS) {
        plus_print_version(version);
        supportVersion = true;
        if ((version[14] == 0x00) && (version[15] == 0x04)) {
            Version4BUID = true;
            memcpy(uid4b, version + 16, 4);
        } else {
            memcpy(uid7b, version + 14, 7);
        }
    } else {
        // info about 14a part, historical bytes.
        infoHF14A(false, false, false);
    }

    // Signature originality check
    uint8_t signature[56] = {0};
    int signature_len = sizeof(signature);
    if (get_plus_signature(signature, &signature_len) == PM3_SUCCESS) {
        if (supportVersion) {
            if (Version4BUID) {
                plus_print_signature(uid4b, 4, signature, signature_len);
            } else {
                plus_print_signature(uid7b, 7, signature, signature_len);
            }
        } else {
            plus_print_signature(card.uid, card.uidlen, signature, signature_len);
        }
        supportSignature = true;
    }

    if (select_status == 1 || select_status == 2) {

        PrintAndLogEx(INFO, "--- " _CYAN_("Fingerprint"));

        bool isPlus = false;

        if (supportVersion) {

            int cardtype = getCardType(version[1], version[3], version[4]);
            switch (cardtype) {
                case PLUS_EV1: {
                    if (supportSignature) {
                        PrintAndLogEx(INFO, "Tech..... " _GREEN_("MIFARE Plus EV1"));
                    } else {
                        PrintAndLogEx(INFO, "Tech..... " _YELLOW_("MIFARE Plus SE/X"));
                    }
                    isPlus = true;
                    break;
                }
                case PLUS_EV2: {
                    if (supportSignature) {
                        PrintAndLogEx(INFO, "Tech..... " _GREEN_("MIFARE Plus EV2"));
                    } else {
                        PrintAndLogEx(INFO, "Tech..... " _YELLOW_("MIFARE Plus EV2 ???"));
                    }
                    isPlus = true;
                    break;
                }
                case DESFIRE_MF3ICD40:
                case DESFIRE_EV1:
                case DESFIRE_EV2:
                case DESFIRE_EV2_XL:
                case DESFIRE_EV3:
                case DESFIRE_LIGHT: {
                    PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("hf mfdes info") "` Card seems to be MIFARE DESFire");
                    PrintAndLogEx(NORMAL, "");
                    DropField();
                    return PM3_SUCCESS;
                }
                default: {
                    PrintAndLogEx(INFO, "Tech..... Unknown ( " _YELLOW_("%u") " )", cardtype);
                    break;
                }
            }
        }

        // MIFARE Type Identification Procedure
        // https://www.nxp.com/docs/en/application-note/AN10833.pdf
        uint16_t ATQA = card.atqa[0] + (card.atqa[1] << 8);

        if (ATQA & 0x0004) {
            PrintAndLogEx(INFO, "Size..... " _GREEN_("2K") " (%s UID)", (ATQA & 0x0040) ? "7" : "4");
            isPlus = true;
        }
        if (ATQA & 0x0002) {
            PrintAndLogEx(INFO, "Size..... " _GREEN_("4K") " (%s UID)", (ATQA & 0x0040) ? "7" : "4");
            isPlus = true;
        }

        uint8_t SLmode = 0xFF;
        if (isPlus) {
            if (card.sak == 0x08) {
                PrintAndLogEx(INFO, "SAK...... " _GREEN_("2K 7b UID"));
                if (select_status == 2) SLmode = 1;
            }
            if (card.sak == 0x18) {
                PrintAndLogEx(INFO, "SAK...... " _GREEN_("4K 7b UID"));
                if (select_status == 2) SLmode = 1;
            }
            if (card.sak == 0x10) {
                PrintAndLogEx(INFO, "SAK...... " _GREEN_("2K"));
                if (select_status == 2) SLmode = 2;
            }
            if (card.sak == 0x11) {
                PrintAndLogEx(INFO, "SAK...... " _GREEN_("4K"));
                if (select_status == 2) SLmode = 2;
            }
        }

        if (card.sak == 0x20) {
            if (card.ats_len > 0) {
                PrintAndLogEx(INFO, "SAK...... " _GREEN_("MIFARE Plus SL0/SL3") " or " _GREEN_("MIFARE DESFire"));
                SLmode = 3;
                // check SL0
                uint8_t data[128] = {0};
                int datalen = 0;
                // https://github.com/Proxmark/proxmark3/blob/master/client/luascripts/mifarePlus.lua#L161
                uint8_t cmd[3 + 16] = {0xa8, 0x90, 0x90, 0x00};
                int res = ExchangeRAW14a(cmd, sizeof(cmd), true, false, data, sizeof(data), &datalen, false);
                if (res != PM3_SUCCESS) {
                    PrintAndLogEx(INFO, "Identification failed");
                    PrintAndLogEx(NORMAL, "");
                    DropField();
                    return PM3_SUCCESS;
                }
                // DESFire answers 0x1C or 67 00
                // Plus answers 0x0B, 0x09, 0x06
                // 6D00 is "INS code not supported" in APDU
                if (
                    data[0] != 0x0B &&
                    data[0] != 0x09 &&
                    data[0] != 0x1C &&
                    data[0] != 0x67 &&
                    data[0] != 0x6D &&
                    data[0] != 0x6E) {

                    PrintAndLogEx(INFO, _RED_("Send copy to iceman of this command output!"));
                    PrintAndLogEx(INFO, "Data... %s", sprint_hex(data, datalen));
                }

                if ((memcmp(data, "\x67\x00", 2) == 0) ||   // wrong length
                        (memcmp(data, "\x1C\x83\x0C", 3) == 0)  // desfire answers
                   ) {
                    PrintAndLogEx(INFO, "Result... " _RED_("MIFARE DESFire"));
                    PrintAndLogEx(NORMAL, "");
                    DropField();
                    return PM3_SUCCESS;

//                } else if (memcmp(data, "\x68\x82", 2) == 0) {  // Secure message not supported
                } else if (memcmp(data, "\x6D\x00", 2) == 0) {
//                } else if (memcmp(data, "\x6E\x00", 2) == 0) {  // Class not supported
                    isPlus = false;
                } else {
                    PrintAndLogEx(INFO, "Result... " _GREEN_("MIFARE Plus SL0/SL3"));
                }

                if ((datalen > 1) && (data[0] == 0x09)) {
                    SLmode = 0;
                }
            }
        }


        if (isPlus) {
            // How do we detect SL0 / SL1 / SL2 / SL3 modes?!?
            PrintAndLogEx(INFO, "--- " _CYAN_("Security Level (SL)"));

            if (SLmode != 0xFF)
                PrintAndLogEx(SUCCESS, "SL mode... " _YELLOW_("SL%d"), SLmode);
            else
                PrintAndLogEx(WARNING, "SL mode... " _YELLOW_("unknown"));

            switch (SLmode) {
                case 0:
                    PrintAndLogEx(INFO, "SL 0: initial delivery configuration, used for card personalization");
                    break;
                case 1:
                    PrintAndLogEx(INFO, "SL 1: backwards functional compatibility mode (with MIFARE Classic 1K / 4K) with an optional AES authentication");
                    break;
                case 2:
                    PrintAndLogEx(INFO, "SL 2: 3-Pass Authentication based on AES followed by MIFARE CRYPTO1 authentication, communication secured by MIFARE CRYPTO1");
                    break;
                case 3:
                    PrintAndLogEx(INFO, "SL 3: 3-Pass authentication based on AES, data manipulation commands secured by AES encryption and an AES based MACing method.");
                    break;
                default:
                    break;
            }
        }
    } else {
        PrintAndLogEx(INFO, "MIFARE Plus info not available");
    }
    PrintAndLogEx(NORMAL, "");
    DropField();
    return PM3_SUCCESS;
}

static int CmdHFMFPWritePerso(const char *Cmd) {
    CLIParserContext *ctx;
    CLIParserInit(&ctx, "hf mfp wrp",
                  "Executes Write Perso command. Can be used in SL0 mode only.",
                  "Use this command to program AES keys, as well as personalize other data on the tag.\n"
                  "You can program:\n"
                  "* Address 00 [00-FF]: Memory blocks (as well as ACLs and Crypto1 keys)\n"
                  "* Address 40 [00-40]: AES sector keys\n"
                  "* Address 90 [00-04]: AES administrative keys\n"
                  "* Address A0 [00, 01, 80, 81]: Virtual Card keys\n"
                  "* Address B0 [00-03]: Configuration data (DO NOT TOUCH B003)\n"
                  "Examples:\n"
                  "hf mfp wrp --adr 4000 --data 000102030405060708090a0b0c0d0e0f  -> write key (00..0f) to key number 4000 \n"
                  "hf mfp wrp --adr 4000                                          -> write default key(0xff..0xff) to key number 4000\n"
                  "hf mfp wrp --adr b000 -d FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF      -> allow 255 commands without MAC in configuration block (B000)\n"
                  "hf mfp wrp --adr 0003 -d 1234561234567F078869B0B1B2B3B4B5      -> write crypto1 keys A: 123456123456 and B: B0B1B2B3B4B5 to block 3\n");

    void *argtable[] = {
        arg_param_begin,
        arg_lit0("v", "verbose", "Verbose output"),
        arg_str1("a", "adr",  "<hex>", "Address, 2 hex bytes"),
        arg_str0("d", "data", "<hex>", "Data, 16 hex bytes"),
        arg_param_end
    };
    CLIExecWithReturn(ctx, Cmd, argtable, true);

    bool verbose = arg_get_lit(ctx, 1);

    uint8_t addr[64] = {0};
    int addrLen = 0;
    CLIGetHexWithReturn(ctx, 2, addr, &addrLen);

    uint8_t datain[64] = {0};
    int datainLen = 0;
    CLIGetHexWithReturn(ctx, 3, datain, &datainLen);
    CLIParserFree(ctx);

    mfpSetVerboseMode(verbose);

    if (!datainLen) {
        memmove(datain, mfp_default_key, 16);
        datainLen = 16;
    }

    if (addrLen != 2) {
        PrintAndLogEx(ERR, "Address length must be 2 bytes. Got %d", addrLen);
        return PM3_EINVARG;
    }
    if (datainLen != 16) {
        PrintAndLogEx(ERR, "Data length must be 16 bytes. Got %d", datainLen);
        return PM3_EINVARG;
    }

    uint8_t data[250] = {0};
    int datalen = 0;

    int res = MFPWritePerso(addr, datain, true, false, data, sizeof(data), &datalen);
    if (res) {
        PrintAndLogEx(ERR, "Exchange error: %d", res);
        return res;
    }

    if (datalen != 3) {
        PrintAndLogEx(ERR, "Command must return 3 bytes. Got %d", datalen);
        return PM3_ESOFT;
    }

    if (data[0] != 0x90) {
        PrintAndLogEx(ERR, "Command error: %02x %s", data[0], mfpGetErrorDescription(data[0]));
        return PM3_ESOFT;
    }

    PrintAndLogEx(INFO, "Write ( " _GREEN_("ok") " )");
    return PM3_SUCCESS;
}

static int CmdHFMFPInitPerso(const char *Cmd) {
    CLIParserContext *ctx;
    CLIParserInit(&ctx, "hf mfp initp",
                  "Executes Write Perso command for all card's keys. Can be used in SL0 mode only.",
                  "hf mfp initp --key 000102030405060708090a0b0c0d0e0f  -> fill all the keys with key (00..0f)\n"
                  "hf mfp initp -vv                                     -> fill all the keys with default key(0xff..0xff) and show all the data exchange"
                 );

    void *argtable[] = {
        arg_param_begin,
        arg_litn("v",  "verbose", 0, 2, "Verbose output"),
        arg_str0("k", "key", "<hex>", "Key, 16 hex bytes"),
        arg_param_end
    };
    CLIExecWithReturn(ctx, Cmd, argtable, true);

    bool verbose = arg_get_lit(ctx, 1);
    bool verbose2 = arg_get_lit(ctx, 1) > 1;

    uint8_t key[256] = {0};
    int keylen = 0;
    CLIGetHexWithReturn(ctx, 2, key, &keylen);
    CLIParserFree(ctx);

    if (keylen && keylen != 16) {
        PrintAndLogEx(FAILED, "Key length must be 16 bytes. Got %d", keylen);
        return PM3_EINVARG;
    }

    if (keylen == 0) {
        memmove(key, mfp_default_key, sizeof(mfp_default_key));
    }

    uint8_t keyNum[2] = {0};
    uint8_t data[250] = {0};
    int datalen = 0;
    int res;

    mfpSetVerboseMode(verbose2);
    for (uint16_t sn = 0x4000; sn < 0x4050; sn++) {
        keyNum[0] = sn >> 8;
        keyNum[1] = sn & 0xff;
        res = MFPWritePerso(keyNum, key, (sn == 0x4000), true, data, sizeof(data), &datalen);
        if (!res && (datalen == 3) && data[0] == 0x09) {
            PrintAndLogEx(INFO, "2K card detected.");
            break;
        }
        if (res || (datalen != 3) || data[0] != 0x90) {
            PrintAndLogEx(ERR, "Write error on address %04x", sn);
            break;
        }
    }

    mfpSetVerboseMode(verbose);
    for (int i = 0; i < ARRAYLEN(mfp_card_adresses); i++) {
        keyNum[0] = mfp_card_adresses[i] >> 8;
        keyNum[1] = mfp_card_adresses[i] & 0xff;
        res = MFPWritePerso(keyNum, key, false, true, data, sizeof(data), &datalen);
        if (!res && (datalen == 3) && data[0] == 0x09) {
            PrintAndLogEx(WARNING, "Skipped[%04x]...", mfp_card_adresses[i]);
        } else {
            if (res || (datalen != 3) || data[0] != 0x90) {
                PrintAndLogEx(ERR, "Write error on address %04x", mfp_card_adresses[i]);
                break;
            }
        }
    }
    DropField();

    if (res)
        return res;

    PrintAndLogEx(INFO, "Done!");
    return PM3_SUCCESS;
}

static int CmdHFMFPCommitPerso(const char *Cmd) {
    CLIParserContext *ctx;
    CLIParserInit(&ctx, "hf mfp commitp",
                  "Executes Commit Perso command. Can be used in SL0 mode only.\n"
                  "OBS! This command will not be executed if \n"
                  "CardConfigKey, CardMasterKey and L3SwitchKey AES keys are not written.",
                  "hf mfp commitp\n"
                  //                "hf mfp commitp --sl 1"
                 );

    void *argtable[] = {
        arg_param_begin,
        arg_lit0("v",  "verbose", "Verbose output"),
//        arg_int0(NULL,  "sl", "<dec>", "SL mode"),
        arg_param_end
    };
    CLIExecWithReturn(ctx, Cmd, argtable, true);
    bool verbose = arg_get_lit(ctx, 1);
//    int slmode = arg_get_int(ctx, 2);
    CLIParserFree(ctx);

    mfpSetVerboseMode(verbose);

    uint8_t data[250] = {0};
    int datalen = 0;

    int res = MFPCommitPerso(true, false, data, sizeof(data), &datalen);
    if (res) {
        PrintAndLogEx(ERR, "Exchange error: %d", res);
        return res;
    }

    if (datalen != 3) {
        PrintAndLogEx(ERR, "Command must return 3 bytes. Got %d", datalen);
        return PM3_EINVARG;
    }

    if (data[0] != 0x90) {
        PrintAndLogEx(ERR, "Command error: %02x %s", data[0], mfpGetErrorDescription(data[0]));
        return PM3_EINVARG;
    }
    PrintAndLogEx(INFO, "Switched security level ( " _GREEN_("ok") " )");
    return PM3_SUCCESS;
}

static int CmdHFMFPAuth(const char *Cmd) {
    uint8_t keyn[250] = {0};
    int keynlen = 0;
    uint8_t key[250] = {0};
    int keylen = 0;

    CLIParserContext *ctx;
    CLIParserInit(&ctx, "hf mfp auth",
                  "Executes AES authentication command for MIFARE Plus card",
                  "hf mfp auth --ki 4000 --key 000102030405060708090a0b0c0d0e0f      -> executes authentication\n"
                  "hf mfp auth --ki 9003 --key FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF -v   -> executes authentication and shows all the system data"
                 );

    void *argtable[] = {
        arg_param_begin,
        arg_lit0("v",  "verbose", "Verbose output"),
        arg_str1(NULL, "ki", "<hex>", "Key number, 2 hex bytes"),
        arg_str1("k",  "key", "<hex>", "Key, 16 hex bytes"),
        arg_param_end
    };
    CLIExecWithReturn(ctx, Cmd, argtable, true);

    bool verbose = arg_get_lit(ctx, 1);
    CLIGetHexWithReturn(ctx, 2, keyn, &keynlen);
    CLIGetHexWithReturn(ctx, 3, key, &keylen);
    CLIParserFree(ctx);

    if (keynlen != 2) {
        PrintAndLogEx(ERR, "ERROR: <key number> must be 2 bytes. Got %d", keynlen);
        return PM3_EINVARG;
    }

    if (keylen != 16) {
        PrintAndLogEx(ERR, "ERROR: <key> must be 16 bytes. Got %d", keylen);
        return PM3_EINVARG;
    }

    return MifareAuth4(NULL, keyn, key, true, false, true, verbose, false);
}

int mfp_data_crypt(mf4Session_t *mf4session, uint8_t *dati, uint8_t *dato, bool rev) {
    uint8_t kenc[MFBLOCK_SIZE];
    memcpy(kenc, mf4session->Kenc, MFBLOCK_SIZE);

    uint8_t ti[4];
    memcpy(ti, mf4session->TI, 4);

    uint8_t ctr[1];
    uint8_t IV[MFBLOCK_SIZE] = { 0 };

    if (rev) {

        ctr[0] = (uint8_t)(mf4session->R_Ctr & 0xFF);

        for (int i = 0; i < 9; i += 4) {
            memcpy(&IV[i], ctr, 1);
        }

        memcpy(&IV[12], ti, 4); // For reads TI is LS

    } else {

        ctr[0] = (uint8_t)(mf4session->W_Ctr & 0xFF);

        for (int i = 3; i < MFBLOCK_SIZE; i += 4) {
            memcpy(&IV[i], ctr, 1);
        }

        memcpy(&IV[0], ti, 4); // For writes TI is MS
    }

    if (rev) {
        aes_decode(IV, kenc, dati, dato, MFBLOCK_SIZE);
    } else {
        aes_encode(IV, kenc, dati, dato, MFBLOCK_SIZE);
    }

    return PM3_SUCCESS;
}

static int CmdHFMFPRdbl(const char *Cmd) {
    CLIParserContext *ctx;
    CLIParserInit(&ctx, "hf mfp rdbl",
                  "Reads blocks from MIFARE Plus card",
                  "hf mfp rdbl --blk 0 --key 000102030405060708090a0b0c0d0e0f   -> executes authentication and read block 0 data\n"
                  "hf mfp rdbl --blk 1 -v                                       -> executes authentication and shows sector 1 data with default key 0xFF..0xFF"
                 );

    void *argtable[] = {
        arg_param_begin,
        arg_lit0("v",  "verbose", "Verbose output"),
        arg_int0("n",  "count", "<dec>", "Blocks count (def: 1)"),
        arg_lit0("b",  "keyb", "Use key B (def: keyA)"),
        arg_lit0("p", "plain", "Do not use encrypted communication mode between reader and card"),
        arg_lit0(NULL, "nmc", "Do not append MAC to command"),
        arg_lit0(NULL, "nmr", "Do not expect MAC in reply"),
        arg_int1(NULL, "blk", "<0..255>", "Block number"),
        arg_str0("k", "key", "<hex>", "Key, 16 hex bytes"),
        arg_param_end
    };
    CLIExecWithReturn(ctx, Cmd, argtable, false);

    bool verbose = arg_get_lit(ctx, 1);
    int blocksCount = arg_get_int_def(ctx, 2, 1);
    bool keyB = arg_get_lit(ctx, 3);
    bool plain = arg_get_lit(ctx, 4);
    bool nomaccmd = arg_get_lit(ctx, 5);
    bool nomacres = arg_get_lit(ctx, 6);
    uint32_t blockn = arg_get_int(ctx, 7);

    uint8_t keyn[2] = {0};
    uint8_t key[250] = {0};
    int keylen = 0;
    CLIGetHexWithReturn(ctx, 8, key, &keylen);
    CLIParserFree(ctx);

    mfpSetVerboseMode(verbose);

    if (keylen == 0) {
        memmove(key, mfp_default_key, 16);
        keylen = 16;
    }

    if (blockn > 255) {
        PrintAndLogEx(ERR, "<block number> must be in range [0..255]. got %d", blockn);
        return PM3_EINVARG;
    }

    if (keylen != 16) {
        PrintAndLogEx(ERR, "<key> must be 16 bytes. Got %d", keylen);
        return PM3_EINVARG;
    }

    // 3 blocks - wo iso14443-4 chaining
    if (blocksCount > 3) {
        PrintAndLogEx(ERR, "blocks count must be less than 3. Got %d", blocksCount);
        return PM3_EINVARG;
    }

    if (blocksCount > 1 && mfIsSectorTrailer(blockn)) {
        PrintAndLogEx(WARNING, "WARNING: trailer!");
    }

    uint8_t sectorNum = mfSectorNum(blockn & 0xff);
    uint16_t uKeyNum = 0x4000 + sectorNum * 2 + (keyB ? 1 : 0);
    keyn[0] = uKeyNum >> 8;
    keyn[1] = uKeyNum & 0xff;
    if (verbose) {
        PrintAndLogEx(INFO, "--block:%d sector[%u]:%02x key:%04x", blockn, mfNumBlocksPerSector(sectorNum), sectorNum, uKeyNum);
    }

    mf4Session_t mf4session;
    int res = MifareAuth4(&mf4session, keyn, key, true, true, true, verbose, false);
    if (res) {
        PrintAndLogEx(ERR, "Authentication error: %d", res);
        return res;
    }

    uint8_t data[250] = {0};
    int datalen = 0;
    uint8_t mac[8] = {0};
    res = MFPReadBlock(&mf4session, plain, nomaccmd, nomacres, blockn & 0xff, blocksCount, false, false, data, sizeof(data), &datalen, mac);
    if (res) {
        PrintAndLogEx(ERR, "Read error: %d", res);
        return res;
    }

    if (datalen && data[0] != 0x90) {
        PrintAndLogEx(ERR, "Card read error: %02x %s", data[0], mfpGetErrorDescription(data[0]));
        return PM3_ESOFT;
    }
    //PrintAndLogEx(INFO, "%i", 8 && (!macres || 0xff));
    if (datalen != 1 + blocksCount * 16 + (nomacres ? 0 : 8) + 2) {
        PrintAndLogEx(ERR, "Error return length: %d", datalen);
        return PM3_ESOFT;
    }

    if (plain == false) {
        mfp_data_crypt(&mf4session, &data[1], &data[1], true);
    }

    uint8_t sector = mfSectorNum(blockn);
    mf_print_sector_hdr(sector);

    int indx = blockn;
    for (int i = 0; i < blocksCount; i++)  {
        mf_print_block_one(indx, data + 1 + (i * MFBLOCK_SIZE), verbose);
        indx++;
    }

    if (memcmp(&data[(blocksCount * 16) + 1], mac, 8) && !nomacres) {
        PrintAndLogEx(WARNING, "WARNING: mac not equal...");
        PrintAndLogEx(WARNING, "MAC   card... " _YELLOW_("%s"), sprint_hex_inrow(&data[1 + (blocksCount * MFBLOCK_SIZE)], 8));
        PrintAndLogEx(WARNING, "MAC reader... " _YELLOW_("%s"), sprint_hex_inrow(mac, sizeof(mac)));
    } else if (!nomacres) {
        if (verbose) {
            PrintAndLogEx(INFO, "MAC... " _YELLOW_("%s"), sprint_hex_inrow(&data[1 + (blocksCount * MFBLOCK_SIZE)], 8));
        }
    }
    PrintAndLogEx(NORMAL, "");
    return PM3_SUCCESS;
}

static int CmdHFMFPRdsc(const char *Cmd) {
    CLIParserContext *ctx;
    CLIParserInit(&ctx, "hf mfp rdsc",
                  "Reads one sector from MIFARE Plus card",
                  "hf mfp rdsc -s 0 --key 000102030405060708090a0b0c0d0e0f   -> executes authentication and read sector 0 data\n"
                  "hf mfp rdsc -s 1 -v                                       -> executes authentication and shows sector 1 data with default key"
                 );

    void *argtable[] = {
        arg_param_begin,
        arg_lit0("v",  "verbose", "Verbose output"),
        arg_lit0("b",  "keyb",    "Use key B (def: keyA)"),
        arg_lit0("p", "plain", "Do not use encrypted communication mode between reader and card"),
        arg_lit0(NULL, "nmc", "Do not append MAC to command"),
        arg_lit0(NULL, "nmr", "Do not expect MAC in reply"),
        arg_int1("s",  "sn",      "<0..255>", "Sector number"),
        arg_str0("k",  "key",     "<hex>", "Key, 16 hex bytes"),
        arg_param_end
    };
    CLIExecWithReturn(ctx, Cmd, argtable, false);

    bool verbose = arg_get_lit(ctx, 1);
    bool keyB = arg_get_lit(ctx, 2);
    bool plain = arg_get_lit(ctx, 3);
    bool nomaccmd = arg_get_lit(ctx, 4);
    bool nomacres = arg_get_lit(ctx, 5);
    uint32_t sectorNum = arg_get_int(ctx, 6);
    uint8_t keyn[2] = {0};
    uint8_t key[250] = {0};
    int keylen = 0;
    CLIGetHexWithReturn(ctx, 7, key, &keylen);
    CLIParserFree(ctx);

    mfpSetVerboseMode(verbose);

    if (keylen == 0) {
        memmove(key, mfp_default_key, 16);
        keylen = 16;
    }

    if (sectorNum > 39) {
        PrintAndLogEx(ERR, "<sector number> must be in range [0..39]. Got %d", sectorNum);
        return PM3_EINVARG;
    }

    if (keylen != 16) {
        PrintAndLogEx(ERR, "<key> must be 16 bytes. Got %d", keylen);
        return PM3_EINVARG;
    }

    uint16_t uKeyNum = 0x4000 + sectorNum * 2 + (keyB ? 1 : 0);
    keyn[0] = uKeyNum >> 8;
    keyn[1] = uKeyNum & 0xff;
    if (verbose) {
        PrintAndLogEx(INFO, "--sector[%u]:%02x key:%04x", mfNumBlocksPerSector(sectorNum), sectorNum, uKeyNum);
    }

    mf4Session_t mf4session;
    int res = MifareAuth4(&mf4session, keyn, key, true, true, true, verbose, false);
    if (res) {
        PrintAndLogEx(ERR, "Authentication error: %d", res);
        return res;
    }

    uint8_t data[250] = {0};
    int datalen = 0;
    uint8_t mac[8] = {0};

    mf_print_sector_hdr(sectorNum);

    for (int blockno = mfFirstBlockOfSector(sectorNum); blockno < mfFirstBlockOfSector(sectorNum) + mfNumBlocksPerSector(sectorNum); blockno++) {

        res = MFPReadBlock(&mf4session, plain, nomaccmd, nomacres, blockno & 0xff, 1, false, true, data, sizeof(data), &datalen, mac);
        if (res) {
            PrintAndLogEx(ERR, "Read error: %d", res);
            DropField();
            return res;
        }

        if (datalen && data[0] != 0x90) {
            PrintAndLogEx(ERR, "Card read error: %02x %s", data[0], mfpGetErrorDescription(data[0]));
            DropField();
            return PM3_ESOFT;
        }

        if (datalen != 1 + MFBLOCK_SIZE + (nomacres ? 0 : 8) + 2) {
            PrintAndLogEx(ERR, "Error return length:%d", datalen);
            DropField();
            return PM3_ESOFT;
        }

        if (plain == false) {
            mfp_data_crypt(&mf4session, &data[1], &data[1], true);
        }

        mf_print_block_one(blockno, data + 1, verbose);

        if (memcmp(&data[1 + 16], mac, 8) && !nomacres) {
            PrintAndLogEx(WARNING, "WARNING: mac on block %d not equal...", blockno);
            PrintAndLogEx(WARNING, "MAC   card... " _YELLOW_("%s"), sprint_hex_inrow(&data[1 + MFBLOCK_SIZE], 8));
            PrintAndLogEx(WARNING, "MAC reader... " _YELLOW_("%s"), sprint_hex_inrow(mac, sizeof(mac)));
        } else if (!nomacres) {
            if (verbose) {
                PrintAndLogEx(INFO, "MAC... " _YELLOW_("%s"), sprint_hex_inrow(&data[1 + MFBLOCK_SIZE], 8));
            }
        }
    }
    PrintAndLogEx(NORMAL, "");
    DropField();
    return PM3_SUCCESS;
}

static int CmdHFMFPWrbl(const char *Cmd) {
    CLIParserContext *ctx;
    CLIParserInit(&ctx, "hf mfp wrbl",
                  "Writes one block to MIFARE Plus card",
                  "hf mfp wrbl --blk 1 -d ff0000000000000000000000000000ff --key 000102030405060708090a0b0c0d0e0f -> write block 1 data\n"
                  "hf mfp wrbl --blk 2 -d ff0000000000000000000000000000ff -v                                     -> write block 2 data with default key 0xFF..0xFF"
                 );

    void *argtable[] = {
        arg_param_begin,
        arg_lit0("v",  "verbose", "Verbose output"),
        arg_lit0("b",  "keyb",    "Use key B (def: keyA)"),
        arg_int1(NULL, "blk",     "<0..255>", "Block number"),
        arg_lit0("p", "plain", "Do not use encrypted transmission"),
        arg_lit0(NULL, "nmr", "Do not expect MAC in response"),
        arg_str1("d",  "data",    "<hex>", "Data, 16 hex bytes"),
        arg_str0("k",  "key",     "<hex>", "Key, 16 hex bytes"),
        arg_param_end
    };
    CLIExecWithReturn(ctx, Cmd, argtable, false);

    bool verbose = arg_get_lit(ctx, 1);
    bool keyB = arg_get_lit(ctx, 2);
    uint32_t blockNum = arg_get_int(ctx, 3);
    bool plain = arg_get_lit(ctx, 4);
    bool nomacres = arg_get_lit(ctx, 5);

    uint8_t datain[250] = {0};
    int datainlen = 0;
    CLIGetHexWithReturn(ctx, 6, datain, &datainlen);

    uint8_t key[250] = {0};
    int keylen = 0;
    CLIGetHexWithReturn(ctx, 7, key, &keylen);
    CLIParserFree(ctx);

    uint8_t keyn[2] = {0};

    mfpSetVerboseMode(verbose);

    if (!keylen) {
        memmove(key, mfp_default_key, 16);
        keylen = 16;
    }

    if (blockNum > 255) {
        PrintAndLogEx(ERR, "<block number> must be in range [0..255]. Got %d", blockNum);
        return PM3_EINVARG;
    }

    if (keylen != 16) {
        PrintAndLogEx(ERR, "<key> must be 16 bytes. Got %d", keylen);
        return PM3_EINVARG;
    }

    if (datainlen != 16) {
        PrintAndLogEx(ERR, "<data> must be 16 bytes. Got %d", datainlen);
        return PM3_EINVARG;
    }

    uint8_t sectorNum = mfSectorNum(blockNum & 0xff);
    uint16_t uKeyNum = 0x4000 + sectorNum * 2 + (keyB ? 1 : 0);
    keyn[0] = uKeyNum >> 8;
    keyn[1] = uKeyNum & 0xff;
    if (verbose) {
        PrintAndLogEx(INFO, "--block:%d sector[%u]:%02x key:%04x", blockNum & 0xff, mfNumBlocksPerSector(sectorNum), sectorNum, uKeyNum);
    }

    mf4Session_t mf4session;
    int res = MifareAuth4(&mf4session, keyn, key, true, true, true, verbose, false);
    if (res) {
        PrintAndLogEx(ERR, "Authentication error: %d", res);
        return res;
    }

    if (plain == false) {
        mfp_data_crypt(&mf4session, &datain[0], &datain[0], false);
    }

    uint8_t data[250] = {0};
    int datalen = 0;
    uint8_t mac[8] = {0};
    res = MFPWriteBlock(&mf4session, plain, nomacres, blockNum & 0xff, 0x00, datain, false, false, data, sizeof(data), &datalen, mac);
    if (res) {
        PrintAndLogEx(ERR, "Write error: %d", res);
        DropField();
        return res;
    }

    if (datalen != 3 && (datalen != 3 + (nomacres ? 0 : 8))) {
        PrintAndLogEx(ERR, "Error return length:%d", datalen);
        DropField();
        return PM3_ESOFT;
    }

    if (datalen && data[0] != 0x90) {
        PrintAndLogEx(ERR, "Card write error: %02x %s", data[0], mfpGetErrorDescription(data[0]));
        DropField();
        return PM3_ESOFT;
    }

    if (memcmp(&data[1], mac, 8) && !nomacres) {
        PrintAndLogEx(WARNING, "WARNING: mac not equal...");
        PrintAndLogEx(WARNING, "MAC   card: %s", sprint_hex(&data[1], 8));
        PrintAndLogEx(WARNING, "MAC reader: %s", sprint_hex(mac, 8));
    } else if (!nomacres) {
        if (verbose)
            PrintAndLogEx(INFO, "MAC: %s", sprint_hex(&data[1], 8));
    }

    DropField();
    PrintAndLogEx(INFO, "Write ( " _GREEN_("ok") " )");
    return PM3_SUCCESS;
}

static int CmdHFMFPChKey(const char *Cmd) {
    CLIParserContext *ctx;
    CLIParserInit(&ctx, "hf mfp chkey",
                  "Change the keys on a Mifare Plus tag",
                  "This requires the key that can update the key that you are trying to update.\n"
                  "hf mfp chkey --ki 401f -d FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF --key A0A1A2A3A4A5A6A7A0A1A2A3A4A5A6A7 -> Change key B for Sector 15 from MAD to default\n"
                  "hf mfp chkey --ki 9000 -d 32F9351A1C02B35FF97E0CA943F814F6 --key FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF -> Change card master key to custom from default"
                 );

    void *argtable[] = {
        arg_param_begin,
        arg_lit0("v",  "verbose", "Verbose output"),
        arg_lit0(NULL, "nmr", "Do not expect MAC in response"),
        arg_str1(NULL, "ki", "<hex>", "Key Index, 2 hex bytes"),
        arg_str0("k", "key",      "<hex>", "Current sector key, 16 hex bytes"),
        arg_lit0("b", "typeb", "Sector key is key B"),
        arg_str1("d",  "data",    "<hex>", "New key, 16 hex bytes"),
        arg_param_end
    };
    CLIExecWithReturn(ctx, Cmd, argtable, false);

    bool verbose = arg_get_lit(ctx, 1);
    bool nomacres = arg_get_lit(ctx, 2);

    uint8_t keyn[250] = {0};

    uint8_t ki[250] = {0};
    int kilen = 0;
    CLIGetHexWithReturn(ctx, 3, ki, &kilen);

    uint8_t key[250] = {0};
    int keylen = 0;
    CLIGetHexWithReturn(ctx, 4, key, &keylen);

    bool usekeyb = arg_get_lit(ctx, 5);
    uint8_t datain[250] = {0};
    int datainlen = 0;
    CLIGetHexWithReturn(ctx, 6, datain, &datainlen);

    CLIParserFree(ctx);

    mfpSetVerboseMode(verbose);

    if (!keylen) {
        memmove(key, mfp_default_key, 16);
        keylen = 16;
    }

    if (keylen != 16) {
        PrintAndLogEx(ERR, "<key> must be 16 bytes. Got %d", keylen);
        return PM3_EINVARG;
    }

    if (datainlen != 16) {
        PrintAndLogEx(ERR, "<data> must be 16 bytes. Got %d", datainlen);
        return PM3_EINVARG;
    }

    mf4Session_t mf4session;

    keyn[0] = ki[0];

    if (ki[0] == 0x40) { // Only if we are working with sector keys

        if (usekeyb) {
            keyn[1] = (ki[1] % 2 == 0) ? ki[1] + 1 : ki[1]; // If we change using key B, check if KI is key A
        } else {
            keyn[1] = (ki[1] % 2 == 0) ? ki[1] : ki[1] - 1; // If we change using key A, check if KI is key A
        }

    } else {
        keyn[1] = ki[1];
    }

    if (verbose) {
        PrintAndLogEx(INFO, "--key index:", sprint_hex(keyn, 2));
    }

    int res = MifareAuth4(&mf4session, keyn, key, true, true, true, verbose, false);
    if (res) {
        PrintAndLogEx(ERR, "Authentication error: %d", res);
        return res;
    }

    mfp_data_crypt(&mf4session, &datain[0], &datain[0], false);

    uint8_t data[250] = {0};
    int datalen = 0;
    uint8_t mac[8] = {0};
    res = MFPWriteBlock(&mf4session, false, nomacres, ki[1], ki[0], datain, false, false, data, sizeof(data), &datalen, mac);
    if (res) {
        PrintAndLogEx(ERR, "Write error: %d", res);
        DropField();
        return res;
    }

    if (datalen != 3 && (datalen != 3 + (nomacres ? 0 : 8))) {
        PrintAndLogEx(ERR, "Error return length:%d", datalen);
        DropField();
        return PM3_ESOFT;
    }

    if (datalen && data[0] != 0x90) {
        PrintAndLogEx(ERR, "Card write error: %02x %s", data[0], mfpGetErrorDescription(data[0]));
        DropField();
        return PM3_ESOFT;
    }

    if (memcmp(&data[1], mac, 8) && !nomacres) {
        PrintAndLogEx(WARNING, "WARNING: mac not equal...");
        PrintAndLogEx(WARNING, "MAC   card: %s", sprint_hex(&data[1], 8));
        PrintAndLogEx(WARNING, "MAC reader: %s", sprint_hex(mac, 8));
    } else if (!nomacres) {
        if (verbose) {
            PrintAndLogEx(INFO, "MAC: %s", sprint_hex(&data[1], 8));
        }
    }

    DropField();
    PrintAndLogEx(INFO, "Key update ( " _GREEN_("ok") " )");
    return PM3_SUCCESS;
}

static int CmdHFMFPChConf(const char *Cmd) {
    CLIParserContext *ctx;
    CLIParserInit(&ctx, "hf mfp chconf",
                  "Change the configuration on a Mifare Plus tag. DANGER!",
                  "This requires Card Master Key (9000) or Card Configuration Key (9001).\n"
                  "Configuration block info can be found below.\n"
                  "* Block B000 (00; CMK): Max amount of commands without MAC (byte 0), as well as plain mode access (unknown).\n"
                  "* Block B001 (01; CCK): Installation identifier for Virtual Card. Please consult NXP for data.\n"
                  "* Block B002 (02; CCK): ATS data.\n"
                  "* Block B003 (03; CCK): Use Random ID in SL3, decide whether proximity check is mandatory.\n  * DO NOT WRITE THIS BLOCK UNDER ANY CIRCUMSTANCES! Risk of bricking.\n"
                  "More configuration tips to follow. Check JMY600 Series IC Card Module.\n"
                  "hf mfp chconf -c 00 -d 10ffffffffffffffffffffffffffffff --key A0A1A2A3A4A5A6A7A0A1A2A3A4A5A6A7 -> Allow 16 commands without MAC in a single transaction."
                 );

    void *argtable[] = {
        arg_param_begin,
        arg_lit0("v",  "verbose", "Verbose output"),
        arg_lit0(NULL, "nmr", "Do not expect MAC in response"),
        arg_int1("c", "conf", "<hex>", "Config block number, 0-3"),
        arg_str0("k", "key",      "<hex>", "Card key, 16 hex bytes"),
        arg_lit0(NULL, "cck", "Auth as Card Configuration key instead of Card Master Key"),
        arg_str1("d",  "data",    "<hex>", "New configuration data, 16 hex bytes"),
        arg_param_end
    };
    CLIExecWithReturn(ctx, Cmd, argtable, false);

    bool verbose = arg_get_lit(ctx, 1);
    bool nomacres = arg_get_lit(ctx, 2);

    uint8_t keyn[250] = {0};
    uint32_t blockNum = arg_get_int(ctx, 3);

    uint8_t key[250] = {0};
    int keylen = 0;
    CLIGetHexWithReturn(ctx, 4, key, &keylen);
    bool usecck = arg_get_lit(ctx, 5);

    uint8_t datain[250] = {0};
    int datainlen = 0;
    CLIGetHexWithReturn(ctx, 6, datain, &datainlen);

    CLIParserFree(ctx);

    mfpSetVerboseMode(verbose);

    if (keylen == 0) {
        memmove(key, mfp_default_key, 16);
        keylen = 16;
    }

    if (keylen != 16) {
        PrintAndLogEx(ERR, "<key> must be 16 bytes. Got %d", keylen);
        return PM3_EINVARG;
    }

    if (datainlen != 16) {
        PrintAndLogEx(ERR, "<data> must be 16 bytes. Got %d", datainlen);
        return PM3_EINVARG;
    }

    if (blockNum > 3) {
        PrintAndLogEx(ERR, "<config number> must be in range [0..3]. Got %d", blockNum);
        return PM3_EINVARG;
    }

    mf4Session_t mf4session;
    keyn[0] = 0x90;
    keyn[1] = usecck ? 0x01 : 0x00;

    if (verbose) {
        PrintAndLogEx(INFO, "--key index:", sprint_hex(keyn, 2));
    }

    int res = MifareAuth4(&mf4session, keyn, key, true, true, true, verbose, false);
    if (res) {
        PrintAndLogEx(ERR, "Authentication error: %d", res);
        return res;
    }

    mfp_data_crypt(&mf4session, &datain[0], &datain[0], false);

    uint8_t data[250] = {0};
    int datalen = 0;
    uint8_t mac[8] = {0};
    res = MFPWriteBlock(&mf4session, false, nomacres, blockNum & 0xff, 0xb0, datain, false, false, data, sizeof(data), &datalen, mac);
    if (res) {
        PrintAndLogEx(ERR, "Write error: %d", res);
        DropField();
        return res;
    }

    if (datalen != 3 && (datalen != 3 + (nomacres ? 0 : 8))) {
        PrintAndLogEx(ERR, "Error return length:%d", datalen);
        DropField();
        return PM3_ESOFT;
    }

    if (datalen && data[0] != 0x90) {
        PrintAndLogEx(ERR, "Card write error: %02x %s", data[0], mfpGetErrorDescription(data[0]));
        DropField();
        return PM3_ESOFT;
    }

    if (memcmp(&data[1], mac, 8) && !nomacres) {
        PrintAndLogEx(WARNING, "WARNING: mac not equal...");
        PrintAndLogEx(WARNING, "MAC   card: %s", sprint_hex(&data[1], 8));
        PrintAndLogEx(WARNING, "MAC reader: %s", sprint_hex(mac, 8));
    } else if (nomacres == false) {
        if (verbose) {
            PrintAndLogEx(INFO, "MAC: %s", sprint_hex(&data[1], 8));
        }
    }

    DropField();
    PrintAndLogEx(INFO, "Write config ( " _GREEN_("ok") " )");
    return PM3_SUCCESS;
}

static int plus_key_check(uint8_t start_sector, uint8_t end_sector, uint8_t startKeyAB, uint8_t endKeyAB,
                          uint8_t *keys, size_t keycount, uint8_t foundKeys[2][64][AES_KEY_LEN + 1],
                          bool verbose, bool newline) {

    if (newline) {
        PrintAndLogEx(INFO, "." NOLF);
    }

    // sector number from 0
    for (uint8_t sector = start_sector; sector <= end_sector; sector++) {

        // 0-keyA 1-keyB
        for (uint8_t keyAB = startKeyAB; keyAB <= endKeyAB; keyAB++) {

            // skip already found keys
            if (foundKeys[keyAB][sector][0]) {
                continue;
            }

            int res;
            bool selectCard = true;

            // reset current key pointer after each loop
            uint8_t *currkey = keys;

            // main cycle with key check
            for (int i = 0; i < keycount; i++) {

                // allow client abort every iteration
                if (kbd_enter_pressed()) {
                    PrintAndLogEx(WARNING, "\naborted via keyboard!\n");
                    DropField();
                    return PM3_EOPABORTED;
                }

                if (i % 10 == 0) {
                    if (verbose == false) {
                        PrintAndLogEx(NORMAL, "." NOLF);
                    }
                }

                uint16_t uKeyNum = 0x4000 + sector * 2 + keyAB;
                uint8_t keyn[2] = { uKeyNum >> 8, uKeyNum & 0xff};

                // authentication loop with retries
                for (int retry = 0; retry < MFP_CHK_KEY_TRIES; retry++) {

                    res = MifareAuth4(NULL, keyn, currkey, selectCard, true, false, false, true);
                    if (res == PM3_SUCCESS || res == PM3_EWRONGANSWER) {
                        break;
                    }

                    if (verbose) {
                        PrintAndLogEx(WARNING, "\nretried[%d]...", retry);
                    } else {
                        PrintAndLogEx(NORMAL, "R" NOLF);
                    }

                    DropField();
                    selectCard = true;
                    msleep(100);
                }

                // key for [sector,keyAB] found
                if (res == PM3_SUCCESS) {

                    if (verbose) {
                        PrintAndLogEx(INFO, "Found key for sector " _YELLOW_("%d") " key "_YELLOW_("%s") " [ " _GREEN_("%s") " ]", sector, (keyAB == 0) ? "A" : "B", sprint_hex_inrow(currkey, AES_KEY_LEN));
                    } else {
                        PrintAndLogEx(NORMAL, _GREEN_("+") NOLF);
                    }

                    foundKeys[keyAB][sector][0] = 0x01;
                    memcpy(&foundKeys[keyAB][sector][1], currkey, AES_KEY_LEN);

                    DropField();
                    selectCard = true;
//                    msleep(50);

                    // recursive test of a successful key
                    if (keycount > 1) {
                        plus_key_check(start_sector, end_sector, startKeyAB, endKeyAB, currkey, 1, foundKeys, verbose, false);
                    }

                    // break out from keylist check loop,
                    break;
                }

                if (verbose) {
                    PrintAndLogEx(WARNING, "\nsector %02d key %d [%s] res: %d", sector, keyAB, sprint_hex_inrow(currkey, AES_KEY_LEN), res);
                }

                // RES can be:
                // PM3_ERFTRANS     -7
                // PM3_EWRONGANSWER -16
                if (res == PM3_ERFTRANS) {

                    if (verbose) {
                        PrintAndLogEx(ERR, "\nExchange error. Aborted.");
                    } else {
                        PrintAndLogEx(NORMAL, "E" NOLF);
                    }

                    DropField();
                    return PM3_ECARDEXCHANGE;
                }

                selectCard = false;

                // set pointer to next key
                currkey += AES_KEY_LEN;
            }
        }
    }

    DropField();
    return PM3_SUCCESS;
}

static void Fill2bPattern(uint8_t keyList[MAX_AES_KEYS_LIST_LEN][AES_KEY_LEN], uint32_t *n, uint32_t *startPattern) {

    uint32_t cnt = 0;
    for (uint32_t pt = *startPattern; pt < 0x10000; pt++) {

        for (uint8_t i = 0; i < AES_KEY_LEN; i += 2) {
            keyList[*n][i] = (pt >> 8) & 0xff;
            keyList[*n][i + 1] = pt & 0xff;
        }

        PrintAndLogEx(DEBUG, _YELLOW_("%4d") " - %s", *n, sprint_hex_inrow(keyList[*n], AES_KEY_LEN));

        // increase number of keys
        (*n)++;
        cnt++;

        *startPattern = pt;

        if (*n == MAX_AES_KEYS_LIST_LEN) {
            break;
        }
    }

    PrintAndLogEx(SUCCESS, "loaded " _GREEN_("%d") " pattern2 keys", cnt);

    (*startPattern)++;
}

static int CmdHFMFPChk(const char *Cmd) {

    CLIParserContext *ctx;
    CLIParserInit(&ctx, "hf mfp chk",
                  "Checks keys on MIFARE Plus card",
                  "hf mfp chk -k 000102030405060708090a0b0c0d0e0f  -> check key on sector 0 as key A and B\n"
                  "hf mfp chk -s 2 -a                              -> check default key list on sector 2, only key A\n"
                  "hf mfp chk -f mfp_default_keys -s 0 -e 6        -> check keys from dictionary against sectors 0-6\n"
                  "hf mfp chk --pattern1b --dump                   -> check all 1-byte keys pattern and save found keys to file\n"
                  "hf mfp chk --pattern2b --startp2b FA00          -> check all 2-byte keys pattern. Start from key FA00FA00...FA00"
                 );

    void *argtable[] = {
        arg_param_begin,
        arg_lit0("a",  "keya",      "Check only key A (def: check all keys)"),
        arg_lit0("b",  "keyb",      "Check only key B (def: check all keys)"),
        arg_int0("s",  "startsec",  "<0..255>", "Start sector number"),
        arg_int0("e",  "endsec",    "<0..255>", "End sector number"),
        arg_str0("k",  "key",       "<hex>", "Key for checking (HEX 16 bytes)"),
        arg_str0("f", "file", "<fn>", "Dictionary file with default keys"),
        arg_lit0(NULL, "pattern1b", "Check all 1-byte combinations of key (0000...0000, 0101...0101, 0202...0202, ...)"),
        arg_lit0(NULL, "pattern2b", "Check all 2-byte combinations of key (0000...0000, 0001...0001, 0002...0002, ...)"),
        arg_str0(NULL, "startp2b",  "<pattern>", "Start key (2-byte HEX) for 2-byte search (use with `--pattern2b`)"),
        arg_lit0(NULL, "dump",      "Dump found keys to JSON file"),
        arg_lit0(NULL, "no-default",  "Skip check default keys"),
        arg_lit0("v",  "verbose",   "Verbose output"),
        arg_param_end
    };
    CLIExecWithReturn(ctx, Cmd, argtable, true);

    bool keyA = arg_get_lit(ctx, 1);
    bool keyB = arg_get_lit(ctx, 2);

    uint8_t startSector = arg_get_int_def(ctx, 3, 0);
    uint8_t endSector = arg_get_int_def(ctx, 4, 0);

    uint32_t keyListLen = 0;
    uint8_t keyList[MAX_AES_KEYS_LIST_LEN][AES_KEY_LEN] = {{0}};
    uint8_t foundKeys[2][64][AES_KEY_LEN + 1] = {{{0}}};

    int vkeylen = 0;
    uint8_t vkey[AES_KEY_LEN] = {0};
    CLIGetHexWithReturn(ctx, 5, vkey, &vkeylen);

    int fnlen = 0;
    char filename[FILE_PATH_SIZE] = {0};
    CLIParamStrToBuf(arg_get_str(ctx, 6), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);

    bool pattern1b = arg_get_lit(ctx, 7);
    bool pattern2b = arg_get_lit(ctx, 8);

    int vpatternlen = 0;
    uint8_t vpattern[2];
    CLIGetHexWithReturn(ctx, 9, vpattern, &vpatternlen);

    bool create_dumpfile = arg_get_lit(ctx, 10);
    bool load_default = ! arg_get_lit(ctx, 11);
    bool verbose = arg_get_lit(ctx, 12);

    CLIParserFree(ctx);

    // sanity checks
    if (vkeylen && (vkeylen != AES_KEY_LEN)) {
        PrintAndLogEx(ERR, "Specified key must have 16 bytes. Got %d", vkeylen);
        return PM3_EINVARG;
    }

    if (pattern1b && pattern2b) {
        PrintAndLogEx(ERR, "Pattern search mode must be 2-byte or 1-byte only");
        return PM3_EINVARG;
    }

    if (fnlen && (pattern1b || pattern2b)) {
        PrintAndLogEx(ERR, "Pattern search mode and dictionary mode can't be used in one command");
        return PM3_EINVARG;
    }

    if (vpatternlen && pattern2b == false) {
        PrintAndLogEx(WARNING, "Pattern entered, but search mode not is 2-byte search");
        return PM3_EINVARG;
    }

    if (vpatternlen > 2) {
        PrintAndLogEx(ERR, "Pattern must be 2-bytes. Got %d", vpatternlen);
        return PM3_EINVARG;
    }

    uint32_t startPattern = (vpattern[0] << 8) + vpattern[1];

    // read card UID
    iso14a_card_select_t card;
    int nxptype = MTNONE;
    int res = mfp_read_card_id(&card, &nxptype);
    if (res != PM3_SUCCESS) {
        return res;
    }

    uint8_t startKeyAB = 0;
    uint8_t endKeyAB = 1;
    if (keyA && (keyB == false)) {
        endKeyAB = 0;
    }

    if ((keyA == false) && keyB) {
        startKeyAB = 1;
    }

    if (endSector < startSector) {
        endSector = startSector;
    }

    // Print generic information
    PrintAndLogEx(NORMAL, "");
    PrintAndLogEx(INFO, "--- " _CYAN_("Check keys") " ----------");
    PrintAndLogEx(INFO, "Start sector... %u", startSector);
    PrintAndLogEx(INFO, "End sector..... %u", endSector);

    char keytypestr[6] = {0};
    if (keyA == false && keyB == false) {
        strcat(keytypestr, "AB");
    }
    if (keyA) {
        strcat(keytypestr, "A");
    }
    if (keyB) {
        strcat(keytypestr, "B");
    }
    PrintAndLogEx(INFO, "Key type....... " _YELLOW_("%s"), keytypestr);
    PrintAndLogEx(NORMAL, "");

    //
    // Key creation section
    //
    // 1-byte pattern search mode
    if (pattern1b) {

        for (int i = 0; i < 0x100; i++) {
            memset(keyList[i], i, 16);
            PrintAndLogEx(DEBUG, _YELLOW_("%3d") " - %s", i, sprint_hex_inrow(keyList[i], AES_KEY_LEN));
        }

        keyListLen = 0x100;
        PrintAndLogEx(SUCCESS, "loaded " _GREEN_("%d") " pattern1b keys", 0x100);
    }

    // 2-byte pattern search mode
    if (pattern2b) {
        Fill2bPattern(keyList, &keyListLen, &startPattern);
    }

    // dictionary mode
    uint8_t *key_block = NULL;
    uint32_t keycnt = 0;

    int ret = mfp_load_keys(&key_block, &keycnt, vkey, vkeylen, filename, fnlen, card.uid, load_default);
    if (ret != PM3_SUCCESS) {
        return ret;
    }

    PrintAndLogEx(INFO, "Start check for keys...");

    // time
    uint64_t t1 = msclock();

    res = plus_key_check(startSector, endSector, startKeyAB, endKeyAB, key_block, keycnt, foundKeys, verbose, true);
    if (res == PM3_EOPABORTED) {
        t1 = msclock() - t1;
        PrintAndLogEx(INFO, "\ntime in checkkeys " _YELLOW_("%.0f") " seconds\n", (float)t1 / 1000.0);
        return res;
    }

    PrintAndLogEx(NORMAL, "");
    PrintAndLogEx(INFO, "Start check for pattern based keys...");
    while (keyListLen) {

        res = plus_key_check(startSector, endSector, startKeyAB, endKeyAB, (uint8_t *)keyList, keyListLen, foundKeys, verbose, true);
        if (res == PM3_EOPABORTED) {
            break;
        }

        if (pattern2b && startPattern < 0x10000) {
            keyListLen = 0;
            PrintAndLogEx(NORMAL, "");
            Fill2bPattern(keyList, &keyListLen, &startPattern);
            continue;
        }
        break;
    }

    t1 = msclock() - t1;
    PrintAndLogEx(INFO, "\ntime in checkkeys " _YELLOW_("%.0f") " seconds\n", (float)t1 / 1000.0);

    // print result
    char strA[46 + 1] = {0};
    char strB[46 + 1] = {0};

    bool has_ndef_key = false;
    bool printedHeader = false;
    for (uint8_t s = startSector; s <= endSector; s++) {

        if ((memcmp(&foundKeys[0][s][1], g_mifarep_ndef_key, AES_KEY_LEN) == 0) ||
                (memcmp(&foundKeys[1][s][1], g_mifarep_ndef_key, AES_KEY_LEN) == 0)) {
            has_ndef_key = true;
        }

        if (printedHeader == false) {
            PrintAndLogEx(NORMAL, "");
            PrintAndLogEx(INFO, "-----+----------------------------------+----------------------------------");
            PrintAndLogEx(INFO, " Sec | key A                            | key B");
            PrintAndLogEx(INFO, "-----+----------------------------------+----------------------------------");
            printedHeader = true;
        }

        if (foundKeys[0][s][0]) {
            snprintf(strA, sizeof(strA), _GREEN_("%s"), sprint_hex_inrow(&foundKeys[0][s][1], AES_KEY_LEN));
        } else {
            snprintf(strA, sizeof(strA), _RED_("%s"), "--------------------------------");
        }

        if (foundKeys[1][s][0]) {
            snprintf(strB, sizeof(strB), _GREEN_("%s"), sprint_hex_inrow(&foundKeys[1][s][1], AES_KEY_LEN));
        } else {
            snprintf(strB, sizeof(strB), _RED_("%s"), "--------------------------------");
        }

        PrintAndLogEx(INFO, " " _YELLOW_("%03d") " | %s | %s", s, strA, strB);
    }

    if (printedHeader == false) {
        PrintAndLogEx(INFO, "No keys found");
    } else {
        PrintAndLogEx(INFO, "-----+----------------------------------+----------------------------------");
    }
    PrintAndLogEx(NORMAL, "");

    // save keys to json
    if (create_dumpfile && printedHeader) {

        size_t keys_len = (2 * 64 * (AES_KEY_LEN + 1));

        uint8_t data[10 + 1 + 2 + 1 + 256 + keys_len];
        memset(data, 0, sizeof(data));

        memcpy(data, card.uid, card.uidlen);
        data[10] = card.sak;
        data[11] = card.atqa[1];
        data[12] = card.atqa[0];
        data[13] = card.ats_len;
        memcpy(&data[14], card.ats, card.ats_len);

        char *fptr = calloc(sizeof(char) * (strlen("hf-mfp-") + strlen("-key")) + card.uidlen * 2 + 1,  sizeof(uint8_t));
        if (fptr == NULL) {
            PrintAndLogEx(WARNING, "Failed to allocate memory");
            return PM3_EMALLOC;
        }
        strcpy(fptr, "hf-mfp-");

        FillFileNameByUID(fptr, card.uid, "-key", card.uidlen);

        // length: UID(10b)+SAK(1b)+ATQA(2b)+ATSlen(1b)+ATS(atslen)+foundKeys[2][64][AES_KEY_LEN + 1]
        memcpy(&data[14 + card.ats_len], foundKeys, keys_len);
        // 64 here is for how many "rows" there is in the data array.  A bit confusing
        saveFileJSON(fptr, jsfMfPlusKeys, data, 64, NULL);
        free(fptr);
    }

    // MAD detection
    if ((memcmp(&foundKeys[0][0][1], g_mifarep_mad_key, AES_KEY_LEN) == 0)) {
        PrintAndLogEx(HINT, "Hint: MAD key detected. Try " _YELLOW_("`hf mfp mad`") " for more details");
    }

    // NDEF detection
    if (has_ndef_key) {
        PrintAndLogEx(HINT, "Hint: NDEF key detected. Try " _YELLOW_("`hf mfp ndefread -h`") " for more details");
    }
    PrintAndLogEx(NORMAL, "");
    return PM3_SUCCESS;
}

static int CmdHFMFPDump(const char *Cmd) {
    CLIParserContext *ctx;
    CLIParserInit(&ctx, "hf mfp dump",
                  "Dump MIFARE Plus tag to file (bin/json)\n"
                  "If no <name> given, UID will be used as filename",
                  "hf mfp dump\n"
                  "hf mfp dump --keys hf-mf-066C8B78-key.bin --> MIFARE Plus with keys from specified file\n");

    void *argtable[] = {
        arg_param_begin,
        arg_str0("f",  "file", "<fn>", "Specify a filename for dump file"),
        arg_str0("k",  "keys", "<fn>", "Specify a filename for keys file"),
//        arg_lit0(NULL, "ns", "no save to file"),
//        arg_lit0("v",  "verbose",   "Verbose output"),
        arg_param_end
    };
    CLIExecWithReturn(ctx, Cmd, argtable, true);

    int datafnlen = 0;
    char data_fn[FILE_PATH_SIZE] = {0};
    CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)data_fn, FILE_PATH_SIZE, &datafnlen);

    int keyfnlen = 0;
    char key_fn[FILE_PATH_SIZE] = {0};
    CLIParamStrToBuf(arg_get_str(ctx, 2), (uint8_t *)key_fn, FILE_PATH_SIZE, &keyfnlen);

//    bool nosave = arg_get_lit(ctx, 3);
//    bool verbose = arg_get_lit(ctx, 4);
    CLIParserFree(ctx);

    PrintAndLogEx(INFO, " To be implemented, feel free to contribute!");
    return PM3_ENOTIMPL;

    /*
        mfpSetVerboseMode(verbose);

        // read card
        uint8_t *mem = calloc(MIFARE_4K_MAXBLOCK * MFBLOCK_SIZE, sizeof(uint8_t));
        if (mem == NULL) {
            PrintAndLogEx(WARNING, "Failed to allocate memory");
            return PM3_EMALLOC;
        }


    //        iso14a_card_select_t card ;
    //        int res = mfp_read_tag(&card, mem, key_fn);
    //        if (res != PM3_SUCCESS) {
    //            free(mem);
    //            return res;
    //        }


        // Skip saving card data to file
        if (nosave) {
            PrintAndLogEx(INFO, "Called with no save option");
            free(mem);
            return PM3_SUCCESS;
        }

            // Save to file
    //        if (strlen(data_fn) < 1) {
    //            char *fptr = calloc(sizeof(char) * (strlen("hf-mfp-") + strlen("-dump")) + card.uidlen * 2 + 1,  sizeof(uint8_t));
    //            strcpy(fptr, "hf-mfp-");
    //            FillFileNameByUID(fptr, card.uid, "-dump", card.uidlen);
    //            strcpy(data_fn, fptr);
    //            free(fptr);
    //        }

    //        pm3_save_mf_dump(filename, dump, MIFARE_4K_MAX_BYTES, jsfCardMemory);

        free(mem);
        return PM3_SUCCESS;
    */
}

static int CmdHFMFPMAD(const char *Cmd) {

    CLIParserContext *ctx;
    CLIParserInit(&ctx, "hf mfp mad",
                  "Checks and prints MIFARE Application Directory (MAD)",
                  "hf mfp mad\n"
                  "hf mfp mad --aid e103 -k d3f7d3f7d3f7d3f7d3f7d3f7d3f7d3f7  -> read and print NDEF data from MAD aid"
                 );

    void *argtable[] = {
        arg_param_begin,
        arg_lit0("v",  "verbose",  "Verbose output"),
        arg_str0(NULL, "aid",      "<hex>", "Print all sectors with aid"),
        arg_str0("k",  "key",      "<hex>", "Key for printing sectors"),
        arg_lit0("b",  "keyb",     "Use key B for access printing sectors (def: key A)"),
        arg_lit0(NULL, "be",       "(optional: BigEndian)"),
        arg_lit0(NULL, "dch",      "Decode Card Holder information"),
        arg_lit0(NULL, "override", "override failed crc check"),
        arg_param_end
    };
    CLIExecWithReturn(ctx, Cmd, argtable, true);

    bool verbose = arg_get_lit(ctx, 1);
    uint8_t aid[2] = {0};
    int aidlen;
    CLIGetHexWithReturn(ctx, 2, aid, &aidlen);
    uint8_t key[16] = {0};
    int keylen;
    CLIGetHexWithReturn(ctx, 3, key, &keylen);
    bool keyB = arg_get_lit(ctx, 4);
    bool swapmad = arg_get_lit(ctx, 5);
    bool decodeholder = arg_get_lit(ctx, 6);
    bool override = arg_get_lit(ctx, 7);

    CLIParserFree(ctx);

    if (aidlen != 2 && !decodeholder && keylen > 0) {
        PrintAndLogEx(WARNING, "Using default MAD keys instead");
    }

    uint8_t sector0[16 * 4] = {0};
    uint8_t sector16[16 * 4] = {0};

    if (mfpReadSector(MF_MAD1_SECTOR, MF_KEY_A, (uint8_t *)g_mifarep_mad_key, sector0, verbose)) {
        PrintAndLogEx(NORMAL, "");
        PrintAndLogEx(ERR, "error, read sector 0. card doesn't have MAD or doesn't have MAD on default keys");
        return PM3_ESOFT;
    }

    MADPrintHeader();

    if (verbose) {
        PrintAndLogEx(SUCCESS, "Raw:");
        for (int i = 0; i < 4; i ++)
            PrintAndLogEx(INFO, "[%d] %s", i, sprint_hex(&sector0[i * 16], 16));
    }

    bool haveMAD2 = false;
    MAD1DecodeAndPrint(sector0, swapmad, verbose, &haveMAD2);

    if (haveMAD2) {
        if (mfpReadSector(MF_MAD2_SECTOR, MF_KEY_A, (uint8_t *)g_mifarep_mad_key, sector16, verbose)) {
            PrintAndLogEx(NORMAL, "");
            PrintAndLogEx(ERR, "error, read sector " _YELLOW_("0x10") ". Card doesn't have MAD or doesn't have MAD on default keys");
            return PM3_ESOFT;
        }

        MAD2DecodeAndPrint(sector16, swapmad, verbose);
    }

    if (aidlen == 2 || decodeholder) {
        uint16_t mad[7 + 8 + 8 + 8 + 8] = {0};
        size_t madlen = 0;
        if (MADDecode(sector0, sector16, mad, &madlen, swapmad, override)) {
            PrintAndLogEx(ERR, "can't decode MAD");
            return PM3_EWRONGANSWER;
        }

        // copy default NDEF key
        uint8_t akey[16] = {0};
        memcpy(akey, g_mifarep_ndef_key, 16);

        // user specified key
        if (keylen == 16) {
            memcpy(akey, key, 16);
        }

        uint16_t aaid = 0x0004;
        if (aidlen == 2) {
            aaid = (aid[0] << 8) + aid[1];
            PrintAndLogEx(NORMAL, "");
            PrintAndLogEx(INFO, "-------------- " _CYAN_("AID 0x%04x") " ---------------", aaid);

            for (int i = 0; i < madlen; i++) {
                if (aaid == mad[i]) {
                    uint8_t vsector[16 * 4] = {0};
                    if (mfpReadSector(i + 1, keyB ? MF_KEY_B : MF_KEY_A, akey, vsector, false)) {
                        PrintAndLogEx(NORMAL, "");
                        PrintAndLogEx(ERR, "error, read sector %d error", i + 1);
                        return PM3_ESOFT;
                    }

                    for (int j = 0; j < (verbose ? 4 : 3); j ++)
                        PrintAndLogEx(NORMAL, " [%03d] %s", (i + 1) * 4 + j, sprint_hex(&vsector[j * 16], 16));
                }
            }
        }

        if (decodeholder) {

            PrintAndLogEx(NORMAL, "");
            PrintAndLogEx(INFO, "-------- " _CYAN_("Card Holder Info 0x%04x") " --------", aaid);

            uint8_t data[4096] = {0};
            int datalen = 0;

            for (int i = 0; i < madlen; i++) {
                if (aaid == mad[i]) {

                    uint8_t vsector[16 * 4] = {0};
                    if (mf_read_sector(i + 1, keyB ? MF_KEY_B : MF_KEY_A, akey, vsector)) {
                        PrintAndLogEx(NORMAL, "");
                        PrintAndLogEx(ERR, "error, read sector %d", i + 1);
                        return PM3_ESOFT;
                    }

                    memcpy(&data[datalen], vsector, 16 * 3);
                    datalen += 16 * 3;
                }
            }

            if (!datalen) {
                PrintAndLogEx(WARNING, "no Card Holder Info data");
                return PM3_SUCCESS;
            }
            MADCardHolderInfoDecode(data, datalen, verbose);
        }
    }
    return PM3_SUCCESS;
}

static int CmdHFMFPNDEFFormat(const char *Cmd) {
    CLIParserContext *ctx;
    CLIParserInit(&ctx, "hf mfp ndefformat",
                  "format MIFARE Plus Tag as a NFC tag with Data Exchange Format (NDEF)\n"
                  "If no <name> given, UID will be used as filename. \n"
                  "It will try default keys and MAD keys to detect if tag is already formatted in order to write.\n"
                  "\n"
                  "If not, it will try finding a key file based on your UID.  ie, if you ran autopwn before",
                  "hf mfp ndefformat\n"
                  "hf mfp ndefformat --keys hf-mf-01020304-key.bin -->  with keys from specified file\n"
                 );

    void *argtable[] = {
        arg_param_begin,
        arg_str0("k", "keys", "<fn>", "filename of keys"),
        arg_param_end
    };
    CLIExecWithReturn(ctx, Cmd, argtable, true);

    int keyfnlen = 0;
    char keyFilename[FILE_PATH_SIZE] = {0};
    CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)keyFilename, FILE_PATH_SIZE, &keyfnlen);

    CLIParserFree(ctx);

    PrintAndLogEx(SUCCESS, "Not implemented yet. Feel free to contribute!");
    PrintAndLogEx(NORMAL, "");
    return PM3_SUCCESS;
}

int CmdHFMFPNDEFRead(const char *Cmd) {

    CLIParserContext *ctx;
    CLIParserInit(&ctx, "hf mfp ndefread",
                  "Prints NFC Data Exchange Format (NDEF)",
                  "hf mfp ndefread \n"
                  "hf mfp ndefread -vv                                            -> shows NDEF parsed and raw data\n"
                  "hf mfp ndefread -f myfilename                                  -> save raw NDEF to file\n"
                  "hf mfp ndefread --aid e103 -k d3f7d3f7d3f7d3f7d3f7d3f7d3f7d3f7 -> shows NDEF data with custom AID and key\n"
                 );

    void *argtable[] = {
        arg_param_begin,
        arg_litn("v",  "verbose",  0, 2, "verbose output"),
        arg_str0(NULL, "aid",      "<aid>", "replace default aid for NDEF"),
        arg_str0("k",  "key",      "<key>", "replace default key for NDEF"),
        arg_lit0("b",  "keyb",     "use key B for access sectors (by default: key A)"),
        arg_str0("f",  "file", "<fn>", "save raw NDEF to file"),
        arg_lit0(NULL, "override", "override failed crc check"),
        arg_param_end
    };
    CLIExecWithReturn(ctx, Cmd, argtable, true);

    bool verbose = arg_get_lit(ctx, 1);
    bool verbose2 = arg_get_lit(ctx, 1) > 1;
    uint8_t aid[2] = {0};
    int aidlen;
    CLIGetHexWithReturn(ctx, 2, aid, &aidlen);
    uint8_t key[16] = {0};
    int keylen;
    CLIGetHexWithReturn(ctx, 3, key, &keylen);
    bool keyB = arg_get_lit(ctx, 4);

    int fnlen = 0;
    char filename[FILE_PATH_SIZE] = {0};
    CLIParamStrToBuf(arg_get_str(ctx, 5), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);

    bool override = arg_get_lit(ctx, 6);
    CLIParserFree(ctx);

    uint16_t ndefAID = 0xe103;
    if (aidlen == 2)
        ndefAID = (aid[0] << 8) + aid[1];

    uint8_t ndefkey[16] = {0};
    memcpy(ndefkey, g_mifarep_ndef_key, 16);
    if (keylen == 16) {
        memcpy(ndefkey, key, 16);
    }

    uint8_t sector0[MIFARE_1K_MAXBLOCK] = {0};
    uint8_t sector16[MIFARE_1K_MAXBLOCK] = {0};
    uint8_t data[MIFARE_4K_MAX_BYTES] = {0};
    int datalen = 0;

    if (verbose)
        PrintAndLogEx(INFO, "reading MAD v1 sector");

    if (mfpReadSector(MF_MAD1_SECTOR, MF_KEY_A, (uint8_t *)g_mifarep_mad_key, sector0, verbose)) {
        PrintAndLogEx(ERR, "error, read sector 0. card doesn't have MAD or doesn't have MAD on default keys");
        PrintAndLogEx(HINT, "Hint: Try " _YELLOW_("`hf mfp ndefread -k `") " with your custom key");
        return PM3_ESOFT;
    }

    bool haveMAD2 = false;
    int res = MADCheck(sector0, NULL, verbose, &haveMAD2);
    if (res != PM3_SUCCESS) {
        PrintAndLogEx(ERR, "MAD error %d", res);
        return res;
    }

    if (haveMAD2) {

        if (verbose)
            PrintAndLogEx(INFO, "reading MAD v2 sector");

        if (mfpReadSector(MF_MAD2_SECTOR, MF_KEY_A, (uint8_t *)g_mifarep_mad_key, sector16, verbose)) {
            PrintAndLogEx(ERR, "error, read sector 0x10. card doesn't have MAD or doesn't have MAD on default keys");
            PrintAndLogEx(HINT, "Hint: Try " _YELLOW_("`hf mfp ndefread -k `") " with your custom key");
            return PM3_ESOFT;
        }
    }

    uint16_t mad[7 + 8 + 8 + 8 + 8] = {0};
    size_t madlen = 0;
    res = MADDecode(sector0, (haveMAD2 ? sector16 : NULL), mad, &madlen, false, override);
    if (res != PM3_SUCCESS) {
        PrintAndLogEx(ERR, "can't decode MAD");
        return res;
    }

    PrintAndLogEx(INFO, "reading data from tag");
    for (int i = 0; i < madlen; i++) {
        if (ndefAID == mad[i]) {
            uint8_t vsector[MIFARE_1K_MAXBLOCK] = {0};
            if (mfpReadSector(i + 1, keyB ? MF_KEY_B : MF_KEY_A, ndefkey, vsector, false)) {
                PrintAndLogEx(ERR, "error, reading sector %d", i + 1);
                return PM3_ESOFT;
            }

            memcpy(&data[datalen], vsector, 16 * 3);
            datalen += 16 * 3;

            PrintAndLogEx(INPLACE, "%d", i);
        }
    }
    PrintAndLogEx(NORMAL, "");

    if (datalen == 0) {
        PrintAndLogEx(ERR, "no NDEF data");
        return PM3_SUCCESS;
    }

    if (verbose2) {
        PrintAndLogEx(NORMAL, "");
        PrintAndLogEx(INFO, "--- " _CYAN_("MF Plus NDEF raw") " ----------------");
        print_buffer(data, datalen, 1);
    }

    res = NDEFDecodeAndPrint(data, datalen, verbose);
    if (res != PM3_SUCCESS) {
        PrintAndLogEx(INFO, "Trying to parse NDEF records w/o NDEF header");
        res = NDEFRecordsDecodeAndPrint(data, datalen, verbose);
    }

    // get total NDEF length before save. If fails, we save it all
    size_t n = 0;
    if (NDEFGetTotalLength(data, datalen, &n) != PM3_SUCCESS)
        n = datalen;

    pm3_save_dump(filename, data, n, jsfNDEF);

    if (verbose == false) {
        PrintAndLogEx(HINT, "Hint: Try " _YELLOW_("`hf mfp ndefread -v`") " for more details");
    } else {
        if (verbose2 == false) {
            PrintAndLogEx(HINT, "Hint: Try " _YELLOW_("`hf mfp ndefread -vv`") " for more details");
        }
    }
    return PM3_SUCCESS;
}

static int CmdHFMFPNDEFWrite(const char *Cmd) {
    CLIParserContext *ctx;
    CLIParserInit(&ctx, "hf mfp ndefwrite",
                  "Write raw NDEF hex bytes to tag. This commands assumes tag already been NFC/NDEF formatted.\n",
                  "hf mfp ndefwrite -d 0300FE      -> write empty record to tag\n"
                  "hf mfp ndefwrite -f myfilename\n"
                  "hf mfp ndefwrite -d 033fd1023a53709101195405656e2d55534963656d616e2054776974746572206c696e6b5101195502747769747465722e636f6d2f686572726d616e6e31303031\n"
                 );

    void *argtable[] = {
        arg_param_begin,
        arg_str0("d", NULL, "<hex>", "raw NDEF hex bytes"),
        arg_str0("f", "file", "<fn>", "write raw NDEF file to tag"),
        arg_lit0("p", NULL, "fix NDEF record headers / terminator block if missing"),
        arg_lit0("v", "verbose", "verbose output"),
        arg_param_end
    };
    CLIExecWithReturn(ctx, Cmd, argtable, false);

    uint8_t raw[4096] = {0};
    int rawlen;
    CLIGetHexWithReturn(ctx, 1, raw, &rawlen);

    int fnlen = 0;
    char filename[FILE_PATH_SIZE] = {0};
    CLIParamStrToBuf(arg_get_str(ctx, 2), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);

    bool fix_msg = arg_get_lit(ctx, 3);
    bool verbose = arg_get_lit(ctx, 4);
    CLIParserFree(ctx);

    if (fix_msg) {
        PrintAndLogEx(NORMAL, "called with fix NDEF message param");
    }

    if (verbose) {
        PrintAndLogEx(NORMAL, "");
    }
    PrintAndLogEx(SUCCESS, "Not implemented yet. Feel free to contribute!");
    PrintAndLogEx(NORMAL, "");
    return PM3_SUCCESS;
}

static int CmdHFMFPList(const char *Cmd) {
    return CmdTraceListAlias(Cmd, "hf mfp", "mfp -c");
}

static command_t CommandTable[] = {
    {"help",        CmdHelp,                 AlwaysAvailable, "This help"},
    {"list",        CmdHFMFPList,            AlwaysAvailable, "List MIFARE Plus history"},
    {"-----------", CmdHelp,                 IfPm3Iso14443a,  "------------------- " _CYAN_("operations") " ---------------------"},
    {"auth",        CmdHFMFPAuth,            IfPm3Iso14443a,  "Authentication"},
    {"chk",         CmdHFMFPChk,             IfPm3Iso14443a,  "Check keys"},
    {"dump",        CmdHFMFPDump,            IfPm3Iso14443a,  "Dump MIFARE Plus tag to binary file"},
    {"info",        CmdHFMFPInfo,            IfPm3Iso14443a,  "Tag information"},
    {"mad",         CmdHFMFPMAD,             IfPm3Iso14443a,  "Check and print MAD"},
    {"rdbl",        CmdHFMFPRdbl,            IfPm3Iso14443a,  "Read blocks from card"},
    {"rdsc",        CmdHFMFPRdsc,            IfPm3Iso14443a,  "Read sectors from card"},
    {"wrbl",        CmdHFMFPWrbl,            IfPm3Iso14443a,  "Write block to card"},
    {"chkey",   CmdHFMFPChKey,      IfPm3Iso14443a,  "Change key on card"},
    {"chconf",    CmdHFMFPChConf,     IfPm3Iso14443a,  "Change config on card"},
    {"-----------", CmdHelp,                 IfPm3Iso14443a,  "---------------- " _CYAN_("personalization") " -------------------"},
    {"commitp",     CmdHFMFPCommitPerso,     IfPm3Iso14443a,  "Configure security layer (SL1/SL3 mode)"},
    {"initp",       CmdHFMFPInitPerso,       IfPm3Iso14443a,  "Fill all the card's keys in SL0 mode"},
    {"wrp",         CmdHFMFPWritePerso,      IfPm3Iso14443a,  "Write Perso command"},
    {"-----------", CmdHelp,                 IfPm3Iso14443a,  "---------------------- " _CYAN_("ndef") " ------------------------"},
    {"ndefformat",  CmdHFMFPNDEFFormat,      IfPm3Iso14443a,  "Format MIFARE Plus Tag as NFC Tag"},
    {"ndefread",    CmdHFMFPNDEFRead,        IfPm3Iso14443a,  "Read and print NDEF records from card"},
    {"ndefwrite",   CmdHFMFPNDEFWrite,       IfPm3Iso14443a,  "Write NDEF records to card"},
    {NULL, NULL, 0, NULL}
};

static int CmdHelp(const char *Cmd) {
    (void)Cmd; // Cmd is not used so far
    CmdsHelp(CommandTable);
    return PM3_SUCCESS;
}

int CmdHFMFP(const char *Cmd) {
    clearCommandBuffer();
    return CmdsParse(CommandTable, Cmd);
}

