From e17fc29e41c50ff08eea518fcc4a28aca5c0d29d Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Sat, 9 Jul 2022 12:25:03 +0200 Subject: [PATCH] added the skeleton for fudan card support --- client/CMakeLists.txt | 1 + client/Makefile | 3 +- client/src/cmdhf.c | 2 + client/src/cmdhf14a.c | 14 +- client/src/cmdhffudan.c | 420 ++++++++++++++++++++++++++++++++++++++++ client/src/cmdhffudan.h | 29 +++ client/src/fileutils.c | 35 ++++ client/src/fileutils.h | 1 + 8 files changed, 498 insertions(+), 7 deletions(-) create mode 100644 client/src/cmdhffudan.c create mode 100644 client/src/cmdhffudan.h diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 3d535f8ea..a34348413 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -275,6 +275,7 @@ set (TARGET_SOURCES ${PM3_ROOT}/client/src/cmdhfepa.c ${PM3_ROOT}/client/src/cmdhffelica.c ${PM3_ROOT}/client/src/cmdhffido.c + ${PM3_ROOT}/client/src/cmdhffudan.c ${PM3_ROOT}/client/src/cmdhfgallagher.c ${PM3_ROOT}/client/src/cmdhfcipurse.c ${PM3_ROOT}/client/src/cmdhficlass.c diff --git a/client/Makefile b/client/Makefile index 3f57cd1bd..b1d7bb671 100644 --- a/client/Makefile +++ b/client/Makefile @@ -365,7 +365,7 @@ endif CFLAGS ?= $(DEFCFLAGS) # We cannot just use CFLAGS+=... because it has impact on sub-makes if CFLAGS is defined in env: PM3CFLAGS = $(CFLAGS) -PM3CFLAGS += -I./src -I./include -I../include -I../common -I../common_fpga $(PM3INCLUDES) $(INCLUDES) +PM3CFLAGS += -g -I./src -I./include -I../include -I../common -I../common_fpga $(PM3INCLUDES) $(INCLUDES) # WIP Testing #PM3CFLAGS += -std=c11 -pedantic @@ -557,6 +557,7 @@ SRCS = mifare/aiddesfire.c \ cmdhfemrtd.c \ cmdhffelica.c \ cmdhffido.c \ + cmdhffudan.c \ cmdhfgallagher.c \ cmdhfksx6924.c \ cmdhfcipurse.c \ diff --git a/client/src/cmdhf.c b/client/src/cmdhf.c index e10524f4a..37560f785 100644 --- a/client/src/cmdhf.c +++ b/client/src/cmdhf.c @@ -49,6 +49,7 @@ #include "cmdhfwaveshare.h" // Waveshare #include "cmdhftexkom.h" // Texkom #include "cmdhfxerox.h" // Xerox +#include "cmdhffudan.h" // Fudan cards #include "cmdtrace.h" // trace list #include "ui.h" #include "proxgui.h" @@ -439,6 +440,7 @@ static command_t CommandTable[] = { {"emrtd", CmdHFeMRTD, AlwaysAvailable, "{ Machine Readable Travel Document... }"}, {"felica", CmdHFFelica, AlwaysAvailable, "{ ISO18092 / FeliCa RFIDs... }"}, {"fido", CmdHFFido, AlwaysAvailable, "{ FIDO and FIDO2 authenticators... }"}, + {"fudan", CmdHFFudan, AlwaysAvailable, "{ Fudan RFIDs... }"}, {"gallagher", CmdHFGallagher, AlwaysAvailable, "{ Gallagher DESFire RFIDs... }"}, {"ksx6924", CmdHFKSX6924, AlwaysAvailable, "{ KS X 6924 (T-Money, Snapper+) RFIDs }"}, {"jooki", CmdHF_Jooki, AlwaysAvailable, "{ Jooki RFIDs... }"}, diff --git a/client/src/cmdhf14a.c b/client/src/cmdhf14a.c index 42c19781a..3fe0840d6 100644 --- a/client/src/cmdhf14a.c +++ b/client/src/cmdhf14a.c @@ -18,9 +18,9 @@ #include "cmdhf14a.h" #include #include -#include "cmdparser.h" // command_t -#include "commonutil.h" // ARRAYLEN -#include "comms.h" // clearCommandBuffer +#include "cmdparser.h" // command_t +#include "commonutil.h" // ARRAYLEN +#include "comms.h" // clearCommandBuffer #include "cmdtrace.h" #include "cliparser.h" #include "cmdhfmf.h" @@ -29,9 +29,9 @@ #include "emv/emvcore.h" #include "ui.h" #include "crc16.h" -#include "util_posix.h" // msclock +#include "util_posix.h" // msclock #include "aidsearch.h" -#include "cmdhf.h" // handle HF plot +#include "cmdhf.h" // handle HF plot #include "cliparser.h" #include "protocols.h" // definitions of ISO14A/7816 protocol, MAGIC_GEN_1A #include "iso7816/apduinfo.h" // GetAPDUCodeDescription @@ -2301,7 +2301,8 @@ int infoHF14A(bool verbose, bool do_nack_test, bool do_aid_search) { PrintAndLogEx(HINT, "Hint: try " _YELLOW_("`emv search -s`")); if (isFUDAN) { - PrintAndLogEx(HINT, "Hint: try " _YELLOW_("`hf 14a raw`") " - since FUDAN is different"); + PrintAndLogEx(HINT, "Hint: try " _YELLOW_("`hf fudan dump`")); + /* PrintAndLogEx(HINT, " hf 14a raw -a -b 7 -k 26"); PrintAndLogEx(HINT, " hf 14a raw -k -c 3000"); PrintAndLogEx(HINT, " hf 14a raw -k -c 3001"); @@ -2311,6 +2312,7 @@ int infoHF14A(bool verbose, bool do_nack_test, bool do_aid_search) { PrintAndLogEx(HINT, " hf 14a raw -k -c 3005"); PrintAndLogEx(HINT, " hf 14a raw -k -c 3006"); PrintAndLogEx(HINT, " hf 14a raw -c 3007"); + */ } PrintAndLogEx(NORMAL, ""); diff --git a/client/src/cmdhffudan.c b/client/src/cmdhffudan.c new file mode 100644 index 000000000..e4847d16e --- /dev/null +++ b/client/src/cmdhffudan.c @@ -0,0 +1,420 @@ +//----------------------------------------------------------------------------- +// 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 proximity cards from ISO14443A / Fudan commands +//----------------------------------------------------------------------------- + +#include "cmdhffudan.h" + +#include +#include +#include +#include "cliparser.h" +#include "cmdparser.h" // command_t +#include "comms.h" +#include "cmdhf14a.h" +#include "cmddata.h" +#include "mifare.h" // xiso +#include "cmdhf.h" // +#include "fileutils.h" // saveFile +#include "ui.h" +#include "commonutil.h" // MemLeToUint2byte +#include "protocols.h" // ISO14 defines +#include "crc16.h" // compute_crc +#include "util_posix.h" // msclock + +#define FUDAN_BLOCK_READ_RETRY 3 +#define MAX_FUDAN_BLOCK_SIZE 4 +#define MAX_FUDAN_05_BLOCKS 8 // 16 +#define MAX_FUDAN_08_BLOCKS 64 + +#ifndef AddCrc14A +# define AddCrc14A(data, len) compute_crc(CRC_14443_A, (data), (len), (data)+(len), (data)+(len)+1) +#endif + + +// iceman: these types are quite unsure. +typedef enum { + FM11RF005M, + FM11RF008M, + FM11RF005SH, + FM11RF08SH, + FUDAN_NONE, +} fudan_type_t; + +static void fudan_print_blocks(uint16_t n, uint8_t *d) { + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(INFO, "----+-------------+-----------------"); + PrintAndLogEx(INFO, "blk | data | ascii"); + PrintAndLogEx(INFO, "----+-------------+-----------------"); + for (uint16_t b = 0; b < n; b++) { + PrintAndLogEx(INFO, "%3d | %s ", b, sprint_hex_ascii(d + (b * MAX_FUDAN_BLOCK_SIZE), MAX_FUDAN_BLOCK_SIZE)); + } + PrintAndLogEx(INFO, "----+-------------+-----------------"); + PrintAndLogEx(NORMAL, ""); +} + +static char* GenerateFilename(iso14a_card_select_t *card, const char *prefix, const char *suffix) { + if (card == NULL) { + return NULL; + } + char *fptr = calloc(sizeof(char) * (strlen(prefix) + strlen(suffix)) + sizeof(card->uid) * 2 + 1, sizeof(uint8_t)); + strcpy(fptr, prefix); + FillFileNameByUID(fptr, card->uid, suffix, card->uidlen); + return fptr; +} + +static fudan_type_t fudan_detected(iso14a_card_select_t *card) { + + if ((card->sak & 0x0A) == 0x0A) { + + uint8_t atqa = MemLeToUint2byte(card->atqa); + if ((atqa & 0x0003) == 0x0003) { + // Uses Shanghai algo + // printTag("FM11RF005SH (FUDAN Shanghai Metro)"); + return FM11RF005SH; + } else if ((atqa & 0x0005) == 0x0005) { + // printTag("FM11RF005M (FUDAN MIFARE Classic clone)"); + return FM11RF005M; + } else if ((atqa & 0x0008) == 0x0008) { + // printTag("FM11RF008M (FUDAN MIFARE Classic clone)"); + return FM11RF008M; + } + + } else if ((card->sak & 0x53) == 0x53) { + // printTag("FM11RF08SH (FUDAN)"); + return FM11RF08SH; + } + return FUDAN_NONE; +} + +static int fudan_get_type(iso14a_card_select_t *card, bool verbose) { + + 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"); + return PM3_ESOFT; + } + + memcpy(card, (iso14a_card_select_t *)resp.data.asBytes, sizeof(iso14a_card_select_t)); + + /* + 0: couldn't read + 1: OK, with ATS + 2: OK, no ATS + 3: proprietary Anticollision + */ + uint64_t select_status = resp.oldarg[0]; + + if (select_status == 0) { + PrintAndLogEx(DEBUG, "iso14443a card select failed"); + DropField(); + return PM3_ESOFT; + } + + if (select_status == 3) { + if (verbose) { + PrintAndLogEx(INFO, "Card doesn't support standard iso14443-3 anticollision"); + PrintAndLogEx(SUCCESS, "ATQA: %02X %02X", card->atqa[1], card->atqa[0]); + } + DropField(); + return PM3_ESOFT; + } + + PrintAndLogEx(SUCCESS, " UID: " _GREEN_("%s"), sprint_hex(card->uid, card->uidlen)); + if (verbose) { + PrintAndLogEx(SUCCESS, "ATQA: " _GREEN_("%02X %02X"), card->atqa[1], card->atqa[0]); + PrintAndLogEx(SUCCESS, " SAK: " _GREEN_("%02X [%" PRIu64 "]"), card->sak, select_status); + + if (card->ats_len >= 3) { // a valid ATS consists of at least the length byte (TL) and 2 CRC bytes + if (card->ats_len == card->ats[0] + 2) + PrintAndLogEx(SUCCESS, " ATS: " _GREEN_("%s"), sprint_hex(card->ats, card->ats[0])); + else { + PrintAndLogEx(SUCCESS, " ATS: [%d] " _GREEN_("%s"), card->ats_len, sprint_hex(card->ats, card->ats_len)); + } + } + } + return PM3_SUCCESS; +} + +int read_fudan_uid(bool loop, bool verbose) { + + do { + iso14a_card_select_t card; + + int res = fudan_get_type(&card, verbose); + + if (loop) { + if (res != PM3_SUCCESS) { + continue; + } + } else { + switch (res) { + case PM3_EFAILED: + case PM3_EINVARG: + return res; + case PM3_ETIMEOUT: + PrintAndLogEx(DEBUG, "command execution time out"); + return res; + case PM3_ESOFT: + PrintAndLogEx(DEBUG, "fudan card select failed"); + return PM3_ESOFT; + default: + break; + } + } + + + if (loop) { + res = handle_hf_plot(); + if (res != PM3_SUCCESS) { + break; + } + } + + // decoding code + if (loop == false) { + PrintAndLogEx(NORMAL, ""); + } + + } while (loop && kbd_enter_pressed() == false); + + + return PM3_SUCCESS; +} + +static int CmdHFFudanReader(const char *Cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "hf fudan reader", + "Read a fudan tag", + "hf fudan reader\n" + "hf fudan reader -@ -> continuous reader mode" + ); + + void *argtable[] = { + arg_param_begin, + arg_lit0("v", "verbose", "Verbose scan and output"), + arg_lit0("@", NULL, "optional - continuous reader mode"), + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, true); + + bool verbose = arg_get_lit(ctx, 1); + bool cm = arg_get_lit(ctx, 2); + + CLIParserFree(ctx); + + if (cm) { + PrintAndLogEx(INFO, "Press " _GREEN_("") " to exit"); + return read_fudan_uid(cm, verbose); + } + + DropField(); + return PM3_SUCCESS; +} + +static int CmdHFFudanDump(const char *Cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "hf fudan dump", + "Dump FUDAN tag to binary file\n" + "If no given, UID will be used as filename", + "hf fudan dump -f mydump --> dump using filename\n" + ); + + void *argtable[] = { + arg_param_begin, + arg_str0("f", "file", "", "filename of dump"), + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, true); + + int datafnlen = 0; + char dataFilename[FILE_PATH_SIZE] = {0}; + CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)dataFilename, FILE_PATH_SIZE, &datafnlen); + CLIParserFree(ctx); + + // Select card to get UID/UIDLEN/ATQA/SAK information + // leaves the field on + iso14a_card_select_t card; + int res = fudan_get_type(&card, false); + if (res != PM3_SUCCESS) { + PrintAndLogEx(FAILED, "failed to select a fudan card. Exiting..."); + DropField(); + return PM3_SUCCESS; + } + + // validations + fudan_type_t t = fudan_detected(&card); + if (t == FUDAN_NONE) { + PrintAndLogEx(FAILED, "failed to detect a fudan card. Exiting..."); + DropField(); + return PM3_SUCCESS; + } + + // detect card size + // 512b, 8kbits + uint8_t num_blocks = MAX_FUDAN_05_BLOCKS; + switch(t) { + case FM11RF008M: + num_blocks = MAX_FUDAN_08_BLOCKS; + break; + case FM11RF005SH: + case FM11RF005M: + case FM11RF08SH: + case FUDAN_NONE: + default: + break; + } + + uint8_t carddata[num_blocks * MAX_FUDAN_BLOCK_SIZE]; + +/* + PrintAndLogEx(HINT, " hf 14a raw -a -b 7 -k 26"); + PrintAndLogEx(HINT, " hf 14a raw -k -c 3000"); + PrintAndLogEx(HINT, " hf 14a raw -k -c 3001"); + PrintAndLogEx(HINT, " hf 14a raw -k -c 3002"); + PrintAndLogEx(HINT, " hf 14a raw -k -c 3003"); + PrintAndLogEx(HINT, " hf 14a raw -k -c 3004"); + PrintAndLogEx(HINT, " hf 14a raw -k -c 3005"); + PrintAndLogEx(HINT, " hf 14a raw -k -c 3006"); + PrintAndLogEx(HINT, " hf 14a raw -c 3007"); +*/ + + // + uint16_t flags = (ISO14A_NO_SELECT | ISO14A_NO_DISCONNECT | ISO14A_NO_RATS | ISO14A_RAW); + uint32_t argtimeout = 0; + uint32_t numbits = 0; + + PrintAndLogEx(SUCCESS, "." NOLF); + // dump memory + for (uint8_t b = 0; b < num_blocks; b++) { + + // read block + uint8_t cmd[4] = {ISO14443A_CMD_READBLOCK, b, 0x00, 0x00}; + AddCrc14A(cmd, 2); + + for (uint8_t tries = 0; tries < FUDAN_BLOCK_READ_RETRY; tries++) { + + clearCommandBuffer(); + PacketResponseNG resp; + SendCommandOLD(CMD_HF_ISO14443A_READER, flags, sizeof(cmd) | ((uint32_t)(numbits << 16)), argtimeout, cmd, sizeof(cmd)); + + if (WaitForResponseTimeout(CMD_ACK, &resp, 1500)) { + if (resp.status == PM3_SUCCESS) { + uint8_t *data = resp.data.asBytes; + memcpy(carddata + (b * MAX_FUDAN_BLOCK_SIZE), data, MAX_FUDAN_BLOCK_SIZE); + PrintAndLogEx(NORMAL, "." NOLF); + break; + } else { + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(FAILED, "could not read block %2d", b); + } + } else { + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(WARNING, "command execute timeout when trying to read block %2d", b); + } + } + + } + DropField(); + + PrintAndLogEx(SUCCESS, "\nSucceeded in dumping all blocks"); + + fudan_print_blocks(num_blocks, carddata); + + // create filename if none was given + if (strlen(dataFilename) < 1) { + char *fptr = GenerateFilename(&card, "hf-fudan-", "-dump"); + if (fptr == NULL) + return PM3_ESOFT; + + strcpy(dataFilename, fptr); + free(fptr); + } + + saveFile(dataFilename, ".bin", (uint8_t *)carddata, sizeof(carddata)); + saveFileEML(dataFilename, (uint8_t *)carddata, sizeof(carddata), MAX_FUDAN_BLOCK_SIZE); + + iso14a_mf_extdump_t xdump; + xdump.card_info = card; + xdump.dump = (uint8_t *)carddata; + xdump.dumplen = sizeof(carddata); + saveFileJSON(dataFilename, jsfFudan, (uint8_t *)&xdump, sizeof(xdump), NULL); + return PM3_SUCCESS; +} + +static int CmdHFFudanView(const char *Cmd) { + + CLIParserContext *ctx; + CLIParserInit(&ctx, "hf fudan view", + "Print a FUDAN dump file (bin/eml/json)", + "hf fudan view -f hf-fudan-01020304-dump.bin" + ); + void *argtable[] = { + arg_param_begin, + arg_str1("f", "file", "", "filename of dump"), + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, false); + int fnlen = 0; + char filename[FILE_PATH_SIZE]; + CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen); + CLIParserFree(ctx); + + // read dump file + uint8_t *dump = NULL; + size_t bytes_read = 0; + int res = pm3_load_dump(filename, (void **)&dump, &bytes_read, (MAX_FUDAN_BLOCK_SIZE * MAX_FUDAN_08_BLOCKS)); + if (res != PM3_SUCCESS) { + return res; + } + + uint16_t block_cnt = MIN(MAX_FUDAN_05_BLOCKS, (bytes_read / MAX_FUDAN_BLOCK_SIZE)); + + fudan_print_blocks(block_cnt, dump); + + free(dump); + return PM3_SUCCESS; +} + +static int CmdHelp(const char *Cmd); + +static command_t CommandTable[] = { + {"help", CmdHelp, AlwaysAvailable, "This help"}, + {"reader", CmdHFFudanReader, IfPm3Iso14443a, "Act like a fudan reader"}, + {"dump", CmdHFFudanDump, IfPm3Iso14443a, "Dump FUDAN tag to binary file"}, + //{"sim", CmdHFFudanSim, IfPm3Iso14443a, "Simulate a fudan tag"}, + //{"rdbl", CmdHFFudanRead, IfPm3Iso14443a, "Read a fudan tag"}, + {"view", CmdHFFudanView, AlwaysAvailable, "Display content from tag dump file"}, + //{"wrbl", CmdHFFudanWrite, IfPm3Iso14443a, "Write a fudan tag"}, + {NULL, NULL, 0, NULL} +}; + +static int CmdHelp(const char *Cmd) { + (void)Cmd; // Cmd is not used so far + CmdsHelp(CommandTable); + return PM3_SUCCESS; +} + +int CmdHFFudan(const char *Cmd) { + clearCommandBuffer(); + return CmdsParse(CommandTable, Cmd); +} diff --git a/client/src/cmdhffudan.h b/client/src/cmdhffudan.h new file mode 100644 index 000000000..afdd618bd --- /dev/null +++ b/client/src/cmdhffudan.h @@ -0,0 +1,29 @@ +//----------------------------------------------------------------------------- +// 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 proximity cards from ISO14443A / FUDAN commands +//----------------------------------------------------------------------------- + +#ifndef CMDHFFUDAN_H__ +#define CMDHFFUDAN_H__ + +#include "common.h" +#include "pm3_cmd.h" + +int CmdHFFudan(const char *Cmd); +int read_fudan_uid(bool loop, bool verbose); + +#endif + diff --git a/client/src/fileutils.c b/client/src/fileutils.c index 026193702..8e68ed03f 100644 --- a/client/src/fileutils.c +++ b/client/src/fileutils.c @@ -396,6 +396,19 @@ int saveFileJSONex(const char *preferredName, JSONFileType ftype, uint8_t *data, } break; } + case jsfFudan: { + iso14a_mf_extdump_t *xdump = (iso14a_mf_extdump_t *)(void *) data; + JsonSaveStr(root, "FileType", "fudan"); + JsonSaveBufAsHexCompact(root, "$.Card.UID", xdump->card_info.uid, xdump->card_info.uidlen); + JsonSaveBufAsHexCompact(root, "$.Card.ATQA", xdump->card_info.atqa, 2); + JsonSaveBufAsHexCompact(root, "$.Card.SAK", &(xdump->card_info.sak), 1); + for (size_t i = 0; i < (xdump->dumplen / 4); i++) { + char path[PATH_MAX_LENGTH] = {0}; + snprintf(path, sizeof(path), "$.blocks.%zu", i); + JsonSaveBufAsHexCompact(root, path, &xdump->dump[i * 4], 4); + } + break; + } case jsfMfuMemory: { JsonSaveStr(root, "FileType", "mfu"); @@ -1108,6 +1121,28 @@ int loadFileJSONex(const char *preferredName, void *data, size_t maxdatalen, siz *datalen = sptr; } + if (!strcmp(ctype, "fudan")) { + size_t sptr = 0; + for (int i = 0; i < 256; i++) { + if (sptr + 4 > maxdatalen) { + retval = PM3_EMALLOC; + goto out; + } + + char blocks[30] = {0}; + snprintf(blocks, sizeof(blocks), "$.blocks.%d", i); + + size_t len = 0; + JsonLoadBufAsHex(root, blocks, &udata[sptr], 4, &len); + if (!len) + break; + + sptr += len; + } + + *datalen = sptr; + } + if (!strcmp(ctype, "mfu")) { mfu_dump_t *mem = (mfu_dump_t *)udata; diff --git a/client/src/fileutils.h b/client/src/fileutils.h index c440c7c26..758dab84f 100644 --- a/client/src/fileutils.h +++ b/client/src/fileutils.h @@ -47,6 +47,7 @@ typedef enum { jsfEM4x69, jsfEM4x50, jsfFido, + jsfFudan, } JSONFileType; typedef enum {