Просмотр исходного кода

[FL-2605] NFC new design (#1364)

* nfc: add new read scene

* lib: refactore nfc library

* mifare desfire: add read card fuction

* lib nfc: add auto read worker

* nfc: add supported cards

* nfc: add mifare classic read success scene

* nfc: add troyka support

* submodule: update protobuf

* nfc: mifare classic keys cache

* nfc: rework mifare classic key cache

* Correct spelling

* nfc: add user dictionary

* nfc: introduce block read map in fff

* nfc: rework dict attack

* nfc: improve dict attack

* nfc: rework mifare classic format

* nfc: rework MFC read with Reader

* nfc: add gui for MFC read success scene

* nfc: fix dict attack view gui

* nfc: add retry and exit confirm scenes

* nfc: add retry and exit scenes navigation

* nfc: check user dictionary

* nfc: remove unused scenes

* nfc: rename functions in nfc worker

* nfc: rename mf_classic_dict_attack -> dict_attack

* nfc: change scenes names
* nfc: remove scene tick events
* nfc: rework dict calls with buffer streams
* nfc: fix notifications
* nfc: fix mf desfire navigation
* nfc: remove notification from mf classic read success
* nfc: fix read sectors calculation
* nfc: add fallback for unknown card
* nfc: show file name while emulating
* nfc: fix build
* nfc: fix memory leak
* nfc: fix desfire read
* nfc: add no dict found navigation
* nfc: add read views
* nfc: update card fix
* nfc: fix access bytes save
* nfc: add exit and retry confirm to mf ultralight read success
* nfc: introduce detect reader
* nfc: change record open arg to macros
* nfc: fix start from archive

Co-authored-by: Astra <astra@astrra.space>
Co-authored-by: あく <alleteam@gmail.com>
gornekich 3 лет назад
Родитель
Сommit
9c59bcd776
89 измененных файлов с 2733 добавлено и 1990 удалено
  1. 1 1
      .github/CODEOWNERS
  2. 1 0
      applications/nfc/helpers/nfc_custom_event.h
  3. 13 13
      applications/nfc/helpers/nfc_generators.c
  4. 0 53
      applications/nfc/helpers/nfc_mf_classic_dict.c
  5. 0 15
      applications/nfc/helpers/nfc_mf_classic_dict.h
  6. 9 15
      applications/nfc/nfc.c
  7. 3 2
      applications/nfc/nfc_cli.c
  8. 6 5
      applications/nfc/nfc_i.h
  9. 0 708
      applications/nfc/nfc_worker.c
  10. 0 57
      applications/nfc/nfc_worker_i.h
  11. 0 90
      applications/nfc/scenes/nfc_scene_card_menu.c
  12. 26 27
      applications/nfc/scenes/nfc_scene_config.h
  13. 143 0
      applications/nfc/scenes/nfc_scene_detect_reader.c
  14. 1 1
      applications/nfc/scenes/nfc_scene_device_info.c
  15. 2 5
      applications/nfc/scenes/nfc_scene_dict_not_found.c
  16. 4 5
      applications/nfc/scenes/nfc_scene_emulate_uid.c
  17. 10 26
      applications/nfc/scenes/nfc_scene_emv_read_success.c
  18. 47 0
      applications/nfc/scenes/nfc_scene_exit_confirm.c
  19. 48 0
      applications/nfc/scenes/nfc_scene_extra_actions.c
  20. 137 0
      applications/nfc/scenes/nfc_scene_mf_classic_dict_attack.c
  21. 15 14
      applications/nfc/scenes/nfc_scene_mf_classic_emulate.c
  22. 59 0
      applications/nfc/scenes/nfc_scene_mf_classic_keys.c
  23. 57 0
      applications/nfc/scenes/nfc_scene_mf_classic_keys_add.c
  24. 11 11
      applications/nfc/scenes/nfc_scene_mf_classic_menu.c
  25. 104 0
      applications/nfc/scenes/nfc_scene_mf_classic_read_success.c
  26. 15 21
      applications/nfc/scenes/nfc_scene_mf_desfire_app.c
  27. 15 17
      applications/nfc/scenes/nfc_scene_mf_desfire_data.c
  28. 7 8
      applications/nfc/scenes/nfc_scene_mf_desfire_menu.c
  29. 22 23
      applications/nfc/scenes/nfc_scene_mf_desfire_read_success.c
  30. 15 14
      applications/nfc/scenes/nfc_scene_mf_ultralight_emulate.c
  31. 11 11
      applications/nfc/scenes/nfc_scene_mf_ultralight_menu.c
  32. 14 14
      applications/nfc/scenes/nfc_scene_mf_ultralight_read_success.c
  33. 108 0
      applications/nfc/scenes/nfc_scene_read.c
  34. 0 51
      applications/nfc/scenes/nfc_scene_read_card.c
  35. 5 15
      applications/nfc/scenes/nfc_scene_read_card_success.c
  36. 0 58
      applications/nfc/scenes/nfc_scene_read_emv_app.c
  37. 0 83
      applications/nfc/scenes/nfc_scene_read_emv_app_success.c
  38. 0 59
      applications/nfc/scenes/nfc_scene_read_emv_data.c
  39. 0 96
      applications/nfc/scenes/nfc_scene_read_mifare_classic.c
  40. 0 56
      applications/nfc/scenes/nfc_scene_read_mifare_desfire.c
  41. 0 54
      applications/nfc/scenes/nfc_scene_read_mifare_ul.c
  42. 47 0
      applications/nfc/scenes/nfc_scene_retry_confirm.c
  43. 0 49
      applications/nfc/scenes/nfc_scene_run_emv_app_confirm.c
  44. 2 6
      applications/nfc/scenes/nfc_scene_save_success.c
  45. 2 2
      applications/nfc/scenes/nfc_scene_saved_menu.c
  46. 0 82
      applications/nfc/scenes/nfc_scene_scripts_menu.c
  47. 12 11
      applications/nfc/scenes/nfc_scene_start.c
  48. 90 86
      applications/nfc/views/dict_attack.c
  49. 14 11
      applications/nfc/views/dict_attack.h
  50. 1 1
      applications/unit_tests/nfc/nfc_test.c
  51. BIN
      assets/icons/NFC/NFC_manual.png
  52. BIN
      assets/icons/NFC/Reader_detect.png
  53. 1 0
      firmware.scons
  54. 1 1
      firmware/targets/furi_hal_include/furi_hal_nfc.h
  55. 4 0
      furi/core/common_defines.h
  56. 1 0
      lib/SConscript
  57. 0 2
      lib/misc.scons
  58. 16 0
      lib/nfc/SConscript
  59. 148 0
      lib/nfc/helpers/mf_classic_dict.c
  60. 28 0
      lib/nfc/helpers/mf_classic_dict.h
  61. 0 0
      lib/nfc/helpers/nfc_debug_pcap.c
  62. 0 0
      lib/nfc/helpers/nfc_debug_pcap.h
  63. 255 18
      lib/nfc/nfc_device.c
  64. 7 4
      lib/nfc/nfc_device.h
  65. 0 0
      lib/nfc/nfc_types.c
  66. 0 0
      lib/nfc/nfc_types.h
  67. 510 0
      lib/nfc/nfc_worker.c
  68. 23 13
      lib/nfc/nfc_worker.h
  69. 48 0
      lib/nfc/nfc_worker_i.h
  70. 12 0
      lib/nfc/parsers/nfc_supported_card.c
  71. 27 0
      lib/nfc/parsers/nfc_supported_card.h
  72. 70 0
      lib/nfc/parsers/troyka_parser.c
  73. 9 0
      lib/nfc/parsers/troyka_parser.h
  74. 0 0
      lib/nfc/protocols/crypto1.c
  75. 0 0
      lib/nfc/protocols/crypto1.h
  76. 0 0
      lib/nfc/protocols/emv.c
  77. 0 0
      lib/nfc/protocols/emv.h
  78. 269 56
      lib/nfc/protocols/mifare_classic.c
  79. 51 20
      lib/nfc/protocols/mifare_classic.h
  80. 0 0
      lib/nfc/protocols/mifare_common.c
  81. 0 0
      lib/nfc/protocols/mifare_common.h
  82. 172 0
      lib/nfc/protocols/mifare_desfire.c
  83. 4 0
      lib/nfc/protocols/mifare_desfire.h
  84. 0 0
      lib/nfc/protocols/mifare_ultralight.c
  85. 0 0
      lib/nfc/protocols/mifare_ultralight.h
  86. 0 0
      lib/nfc/protocols/nfc_util.c
  87. 0 0
      lib/nfc/protocols/nfc_util.h
  88. 0 0
      lib/nfc/protocols/nfca.c
  89. 0 0
      lib/nfc/protocols/nfca.h

+ 1 - 1
.github/CODEOWNERS

@@ -76,7 +76,7 @@
 /lib/microtar/ @skotopes @DrZlo13 @hedger
 /lib/mlib/ @skotopes @DrZlo13 @hedger
 /lib/nanopb/ @skotopes @DrZlo13 @hedger
-/lib/nfc_protocols/ @skotopes @DrZlo13 @hedger @gornekich
+/lib/nfc/ @skotopes @DrZlo13 @hedger @gornekich
 /lib/one_wire/ @skotopes @DrZlo13 @hedger
 /lib/qrcode/ @skotopes @DrZlo13 @hedger
 /lib/subghz/ @skotopes @DrZlo13 @hedger @Skorpionm

+ 1 - 0
applications/nfc/helpers/nfc_custom_event.h

@@ -9,5 +9,6 @@ enum NfcCustomEvent {
     NfcCustomEventByteInputDone,
     NfcCustomEventTextInputDone,
     NfcCustomEventDictAttackDone,
+    NfcCustomEventDictAttackSkip,
     NfcCustomEventRpcLoad,
 };

+ 13 - 13
applications/nfc/helpers/nfc_generators.c

@@ -267,67 +267,67 @@ static void nfc_generate_ntag_i2c_plus_2k(NfcDeviceData* data) {
 static const NfcGenerator mf_ul_generator = {
     .name = "Mifare Ultralight",
     .generator_func = nfc_generate_mf_ul_orig,
-    .next_scene = NfcSceneMifareUlMenu};
+    .next_scene = NfcSceneMfUltralightMenu};
 
 static const NfcGenerator mf_ul_11_generator = {
     .name = "Mifare Ultralight EV1 11",
     .generator_func = nfc_generate_mf_ul_11,
-    .next_scene = NfcSceneMifareUlMenu};
+    .next_scene = NfcSceneMfUltralightMenu};
 
 static const NfcGenerator mf_ul_h11_generator = {
     .name = "Mifare Ultralight EV1 H11",
     .generator_func = nfc_generate_mf_ul_h11,
-    .next_scene = NfcSceneMifareUlMenu};
+    .next_scene = NfcSceneMfUltralightMenu};
 
 static const NfcGenerator mf_ul_21_generator = {
     .name = "Mifare Ultralight EV1 21",
     .generator_func = nfc_generate_mf_ul_21,
-    .next_scene = NfcSceneMifareUlMenu};
+    .next_scene = NfcSceneMfUltralightMenu};
 
 static const NfcGenerator mf_ul_h21_generator = {
     .name = "Mifare Ultralight EV1 H21",
     .generator_func = nfc_generate_mf_ul_h21,
-    .next_scene = NfcSceneMifareUlMenu};
+    .next_scene = NfcSceneMfUltralightMenu};
 
 static const NfcGenerator ntag203_generator = {
     .name = "NTAG203",
     .generator_func = nfc_generate_mf_ul_ntag203,
-    .next_scene = NfcSceneMifareUlMenu};
+    .next_scene = NfcSceneMfUltralightMenu};
 
 static const NfcGenerator ntag213_generator = {
     .name = "NTAG213",
     .generator_func = nfc_generate_ntag213,
-    .next_scene = NfcSceneMifareUlMenu};
+    .next_scene = NfcSceneMfUltralightMenu};
 
 static const NfcGenerator ntag215_generator = {
     .name = "NTAG215",
     .generator_func = nfc_generate_ntag215,
-    .next_scene = NfcSceneMifareUlMenu};
+    .next_scene = NfcSceneMfUltralightMenu};
 
 static const NfcGenerator ntag216_generator = {
     .name = "NTAG216",
     .generator_func = nfc_generate_ntag216,
-    .next_scene = NfcSceneMifareUlMenu};
+    .next_scene = NfcSceneMfUltralightMenu};
 
 static const NfcGenerator ntag_i2c_1k_generator = {
     .name = "NTAG I2C 1k",
     .generator_func = nfc_generate_ntag_i2c_1k,
-    .next_scene = NfcSceneMifareUlMenu};
+    .next_scene = NfcSceneMfUltralightMenu};
 
 static const NfcGenerator ntag_i2c_2k_generator = {
     .name = "NTAG I2C 2k",
     .generator_func = nfc_generate_ntag_i2c_2k,
-    .next_scene = NfcSceneMifareUlMenu};
+    .next_scene = NfcSceneMfUltralightMenu};
 
 static const NfcGenerator ntag_i2c_plus_1k_generator = {
     .name = "NTAG I2C Plus 1k",
     .generator_func = nfc_generate_ntag_i2c_plus_1k,
-    .next_scene = NfcSceneMifareUlMenu};
+    .next_scene = NfcSceneMfUltralightMenu};
 
 static const NfcGenerator ntag_i2c_plus_2k_generator = {
     .name = "NTAG I2C Plus 2k",
     .generator_func = nfc_generate_ntag_i2c_plus_2k,
-    .next_scene = NfcSceneMifareUlMenu};
+    .next_scene = NfcSceneMfUltralightMenu};
 
 const NfcGenerator* const nfc_generators[] = {
     &mf_ul_generator,

+ 0 - 53
applications/nfc/helpers/nfc_mf_classic_dict.c

@@ -1,53 +0,0 @@
-#include "nfc_mf_classic_dict.h"
-
-#include <flipper_format/flipper_format.h>
-#include <lib/toolbox/args.h>
-
-#define NFC_MF_CLASSIC_DICT_PATH EXT_PATH("nfc/assets/mf_classic_dict.nfc")
-
-#define NFC_MF_CLASSIC_KEY_LEN (13)
-
-bool nfc_mf_classic_dict_check_presence(Storage* storage) {
-    furi_assert(storage);
-    return storage_common_stat(storage, NFC_MF_CLASSIC_DICT_PATH, NULL) == FSE_OK;
-}
-
-bool nfc_mf_classic_dict_open_file(Stream* stream) {
-    furi_assert(stream);
-    return file_stream_open(stream, NFC_MF_CLASSIC_DICT_PATH, FSAM_READ, FSOM_OPEN_EXISTING);
-}
-
-void nfc_mf_classic_dict_close_file(Stream* stream) {
-    furi_assert(stream);
-    file_stream_close(stream);
-}
-
-bool nfc_mf_classic_dict_get_next_key(Stream* stream, uint64_t* key) {
-    furi_assert(stream);
-    furi_assert(key);
-    uint8_t key_byte_tmp = 0;
-    string_t next_line;
-    string_init(next_line);
-    *key = 0;
-
-    bool next_key_read = false;
-    while(!next_key_read) {
-        if(!stream_read_line(stream, next_line)) break;
-        if(string_get_char(next_line, 0) == '#') continue;
-        if(string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN) continue;
-        for(uint8_t i = 0; i < 12; i += 2) {
-            args_char_to_hex(
-                string_get_char(next_line, i), string_get_char(next_line, i + 1), &key_byte_tmp);
-            *key |= (uint64_t)key_byte_tmp << 8 * (5 - i / 2);
-        }
-        next_key_read = true;
-    }
-
-    string_clear(next_line);
-    return next_key_read;
-}
-
-void nfc_mf_classic_dict_reset(Stream* stream) {
-    furi_assert(stream);
-    stream_rewind(stream);
-}

+ 0 - 15
applications/nfc/helpers/nfc_mf_classic_dict.h

@@ -1,15 +0,0 @@
-#pragma once
-
-#include <stdbool.h>
-#include <storage/storage.h>
-#include <lib/toolbox/stream/file_stream.h>
-
-bool nfc_mf_classic_dict_check_presence(Storage* storage);
-
-bool nfc_mf_classic_dict_open_file(Stream* stream);
-
-void nfc_mf_classic_dict_close_file(Stream* stream);
-
-bool nfc_mf_classic_dict_get_next_key(Stream* stream, uint64_t* key);
-
-void nfc_mf_classic_dict_reset(Stream* stream);

+ 9 - 15
applications/nfc/nfc.c

@@ -13,12 +13,6 @@ bool nfc_back_event_callback(void* context) {
     return scene_manager_handle_back_event(nfc->scene_manager);
 }
 
-void nfc_tick_event_callback(void* context) {
-    furi_assert(context);
-    Nfc* nfc = context;
-    scene_manager_handle_tick_event(nfc->scene_manager);
-}
-
 void nfc_rpc_exit_callback(Nfc* nfc) {
     if(nfc->rpc_state == NfcRpcStateEmulating) {
         // Stop worker
@@ -36,11 +30,12 @@ void nfc_rpc_exit_callback(Nfc* nfc) {
     }
 }
 
-static void nfc_rpc_emulate_callback(NfcWorkerEvent event, void* context) {
+static bool nfc_rpc_emulate_callback(NfcWorkerEvent event, void* context) {
     UNUSED(event);
     Nfc* nfc = context;
 
     nfc->rpc_state = NfcRpcStateEmulated;
+    return true;
 }
 
 static bool nfc_rpc_command_callback(RpcAppSystemEvent event, const char* arg, void* context) {
@@ -67,20 +62,20 @@ static bool nfc_rpc_command_callback(RpcAppSystemEvent event, const char* arg, v
                 if(nfc->dev->format == NfcDeviceSaveFormatMifareUl) {
                     nfc_worker_start(
                         nfc->worker,
-                        NfcWorkerStateEmulateMifareUltralight,
+                        NfcWorkerStateMfUltralightEmulate,
                         &nfc->dev->dev_data,
                         nfc_rpc_emulate_callback,
                         nfc);
                 } else if(nfc->dev->format == NfcDeviceSaveFormatMifareClassic) {
                     nfc_worker_start(
                         nfc->worker,
-                        NfcWorkerStateEmulateMifareClassic,
+                        NfcWorkerStateMfClassicEmulate,
                         &nfc->dev->dev_data,
                         nfc_rpc_emulate_callback,
                         nfc);
                 } else {
                     nfc_worker_start(
-                        nfc->worker, NfcWorkerStateEmulate, &nfc->dev->dev_data, NULL, nfc);
+                        nfc->worker, NfcWorkerStateUidEmulate, &nfc->dev->dev_data, NULL, nfc);
                 }
                 nfc->rpc_state = NfcRpcStateEmulating;
                 view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventRpcLoad);
@@ -102,7 +97,6 @@ Nfc* nfc_alloc() {
     view_dispatcher_set_event_callback_context(nfc->view_dispatcher, nfc);
     view_dispatcher_set_custom_event_callback(nfc->view_dispatcher, nfc_custom_event_callback);
     view_dispatcher_set_navigation_event_callback(nfc->view_dispatcher, nfc_back_event_callback);
-    view_dispatcher_set_tick_event_callback(nfc->view_dispatcher, nfc_tick_event_callback, 100);
 
     // Nfc device
     nfc->dev = nfc_device_alloc();
@@ -155,7 +149,7 @@ Nfc* nfc_alloc() {
     view_dispatcher_add_view(
         nfc->view_dispatcher, NfcViewBankCard, bank_card_get_view(nfc->bank_card));
 
-    // Dict Attack
+    // Mifare Classic Dict Attack
     nfc->dict_attack = dict_attack_alloc();
     view_dispatcher_add_view(
         nfc->view_dispatcher, NfcViewDictAttack, dict_attack_get_view(nfc->dict_attack));
@@ -209,7 +203,7 @@ void nfc_free(Nfc* nfc) {
     view_dispatcher_remove_view(nfc->view_dispatcher, NfcViewBankCard);
     bank_card_free(nfc->bank_card);
 
-    // Dict Attack
+    // Mifare Classic Dict Attack
     view_dispatcher_remove_view(nfc->view_dispatcher, NfcViewDictAttack);
     dict_attack_free(nfc->dict_attack);
 
@@ -301,9 +295,9 @@ int32_t nfc_app(void* p) {
                 nfc->view_dispatcher, nfc->gui, ViewDispatcherTypeFullscreen);
             if(nfc_device_load(nfc->dev, p, true)) {
                 if(nfc->dev->format == NfcDeviceSaveFormatMifareUl) {
-                    scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateMifareUl);
+                    scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightEmulate);
                 } else if(nfc->dev->format == NfcDeviceSaveFormatMifareClassic) {
-                    scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateMifareClassic);
+                    scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicEmulate);
                 } else {
                     scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateUid);
                 }

+ 3 - 2
applications/nfc/nfc_cli.c

@@ -1,9 +1,10 @@
 #include <furi.h>
 #include <furi_hal.h>
 #include <cli/cli.h>
-#include <toolbox/args.h>
+#include <lib/toolbox/args.h>
 
-#include "nfc_types.h"
+#include <lib/nfc/nfc_types.h>
+#include <lib/nfc/nfc_device.h>
 
 static void nfc_cli_print_usage() {
     printf("Usage:\r\n");

+ 6 - 5
applications/nfc/nfc_i.h

@@ -1,9 +1,6 @@
 #pragma once
 
 #include "nfc.h"
-#include "nfc_types.h"
-#include "nfc_worker.h"
-#include "nfc_device.h"
 
 #include <furi.h>
 #include <furi_hal.h>
@@ -24,6 +21,11 @@
 #include <gui/modules/text_box.h>
 #include <gui/modules/widget.h>
 
+#include <lib/nfc/nfc_types.h>
+#include <lib/nfc/nfc_worker.h>
+#include <lib/nfc/nfc_device.h>
+#include <lib/nfc/helpers/mf_classic_dict.h>
+
 #include "views/bank_card.h"
 #include "views/dict_attack.h"
 
@@ -32,8 +34,6 @@
 
 #include "rpc/rpc_app.h"
 
-#define NFC_SEND_NOTIFICATION_FALSE (0UL)
-#define NFC_SEND_NOTIFICATION_TRUE (1UL)
 #define NFC_TEXT_STORE_SIZE 128
 
 typedef enum {
@@ -56,6 +56,7 @@ struct Nfc {
 
     char text_store[NFC_TEXT_STORE_SIZE + 1];
     string_t text_box_store;
+    uint8_t byte_input_store[6];
 
     void* rpc_ctx;
     NfcRpcState rpc_state;

+ 0 - 708
applications/nfc/nfc_worker.c

@@ -1,708 +0,0 @@
-#include "nfc_worker_i.h"
-#include <furi_hal.h>
-
-#include <platform.h>
-
-#define TAG "NfcWorker"
-
-/***************************** NFC Worker API *******************************/
-
-NfcWorker* nfc_worker_alloc() {
-    NfcWorker* nfc_worker = malloc(sizeof(NfcWorker));
-
-    // Worker thread attributes
-    nfc_worker->thread = furi_thread_alloc();
-    furi_thread_set_name(nfc_worker->thread, "NfcWorker");
-    furi_thread_set_stack_size(nfc_worker->thread, 8192);
-    furi_thread_set_callback(nfc_worker->thread, nfc_worker_task);
-    furi_thread_set_context(nfc_worker->thread, nfc_worker);
-
-    nfc_worker->callback = NULL;
-    nfc_worker->context = NULL;
-    nfc_worker->storage = furi_record_open(RECORD_STORAGE);
-
-    // Initialize rfal
-    while(furi_hal_nfc_is_busy()) {
-        furi_delay_ms(10);
-    }
-    nfc_worker_change_state(nfc_worker, NfcWorkerStateReady);
-
-    if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
-        nfc_worker->debug_pcap_worker = nfc_debug_pcap_alloc(nfc_worker->storage);
-    }
-
-    return nfc_worker;
-}
-
-void nfc_worker_free(NfcWorker* nfc_worker) {
-    furi_assert(nfc_worker);
-
-    furi_thread_free(nfc_worker->thread);
-
-    furi_record_close(RECORD_STORAGE);
-
-    if(nfc_worker->debug_pcap_worker) nfc_debug_pcap_free(nfc_worker->debug_pcap_worker);
-
-    free(nfc_worker);
-}
-
-NfcWorkerState nfc_worker_get_state(NfcWorker* nfc_worker) {
-    return nfc_worker->state;
-}
-
-void nfc_worker_start(
-    NfcWorker* nfc_worker,
-    NfcWorkerState state,
-    NfcDeviceData* dev_data,
-    NfcWorkerCallback callback,
-    void* context) {
-    furi_assert(nfc_worker);
-    furi_assert(dev_data);
-    while(furi_hal_nfc_is_busy()) {
-        furi_delay_ms(10);
-    }
-
-    nfc_worker->callback = callback;
-    nfc_worker->context = context;
-    nfc_worker->dev_data = dev_data;
-    nfc_worker_change_state(nfc_worker, state);
-    furi_thread_start(nfc_worker->thread);
-}
-
-void nfc_worker_stop(NfcWorker* nfc_worker) {
-    furi_assert(nfc_worker);
-    if(nfc_worker->state == NfcWorkerStateBroken || nfc_worker->state == NfcWorkerStateReady) {
-        return;
-    }
-    furi_hal_nfc_stop();
-    nfc_worker_change_state(nfc_worker, NfcWorkerStateStop);
-    furi_thread_join(nfc_worker->thread);
-}
-
-void nfc_worker_change_state(NfcWorker* nfc_worker, NfcWorkerState state) {
-    nfc_worker->state = state;
-}
-
-/***************************** NFC Worker Thread *******************************/
-
-int32_t nfc_worker_task(void* context) {
-    NfcWorker* nfc_worker = context;
-
-    furi_hal_nfc_exit_sleep();
-
-    if(nfc_worker->state == NfcWorkerStateDetect) {
-        nfc_worker_detect(nfc_worker);
-    } else if(nfc_worker->state == NfcWorkerStateEmulate) {
-        nfc_worker_emulate(nfc_worker);
-    } else if(nfc_worker->state == NfcWorkerStateReadEMVApp) {
-        nfc_worker_read_emv_app(nfc_worker);
-    } else if(nfc_worker->state == NfcWorkerStateReadEMVData) {
-        nfc_worker_read_emv(nfc_worker);
-    } else if(nfc_worker->state == NfcWorkerStateEmulateApdu) {
-        nfc_worker_emulate_apdu(nfc_worker);
-    } else if(nfc_worker->state == NfcWorkerStateReadMifareUltralight) {
-        nfc_worker_read_mifare_ultralight(nfc_worker);
-    } else if(nfc_worker->state == NfcWorkerStateEmulateMifareUltralight) {
-        nfc_worker_emulate_mifare_ul(nfc_worker);
-    } else if(nfc_worker->state == NfcWorkerStateReadMifareClassic) {
-        nfc_worker_mifare_classic_dict_attack(nfc_worker);
-    } else if(nfc_worker->state == NfcWorkerStateEmulateMifareClassic) {
-        nfc_worker_emulate_mifare_classic(nfc_worker);
-    } else if(nfc_worker->state == NfcWorkerStateReadMifareDesfire) {
-        nfc_worker_read_mifare_desfire(nfc_worker);
-    }
-    furi_hal_nfc_sleep();
-    nfc_worker_change_state(nfc_worker, NfcWorkerStateReady);
-
-    return 0;
-}
-
-void nfc_worker_detect(NfcWorker* nfc_worker) {
-    nfc_device_data_clear(nfc_worker->dev_data);
-    NfcDeviceData* dev_data = nfc_worker->dev_data;
-    FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data;
-
-    while(nfc_worker->state == NfcWorkerStateDetect) {
-        if(furi_hal_nfc_detect(nfc_data, 1000)) {
-            // Process first found device
-            if(nfc_data->type == FuriHalNfcTypeA) {
-                if(mf_ul_check_card_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak)) {
-                    dev_data->protocol = NfcDeviceProtocolMifareUl;
-                } else if(mf_classic_check_card_type(
-                              nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak)) {
-                    dev_data->protocol = NfcDeviceProtocolMifareClassic;
-                } else if(mf_df_check_card_type(
-                              nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak)) {
-                    dev_data->protocol = NfcDeviceProtocolMifareDesfire;
-                } else if(nfc_data->interface == FuriHalNfcInterfaceIsoDep) {
-                    dev_data->protocol = NfcDeviceProtocolEMV;
-                } else {
-                    dev_data->protocol = NfcDeviceProtocolUnknown;
-                }
-            }
-
-            // Notify caller and exit
-            if(nfc_worker->callback) {
-                nfc_worker->callback(NfcWorkerEventSuccess, nfc_worker->context);
-            }
-            break;
-        }
-        furi_hal_nfc_sleep();
-        furi_delay_ms(100);
-    }
-}
-
-void nfc_worker_emulate(NfcWorker* nfc_worker) {
-    FuriHalNfcTxRxContext tx_rx = {};
-    nfc_debug_pcap_prepare_tx_rx(nfc_worker->debug_pcap_worker, &tx_rx, true);
-    FuriHalNfcDevData* data = &nfc_worker->dev_data->nfc_data;
-    NfcReaderRequestData* reader_data = &nfc_worker->dev_data->reader_data;
-
-    while(nfc_worker->state == NfcWorkerStateEmulate) {
-        if(furi_hal_nfc_listen(data->uid, data->uid_len, data->atqa, data->sak, true, 100)) {
-            if(furi_hal_nfc_tx_rx(&tx_rx, 100)) {
-                reader_data->size = tx_rx.rx_bits / 8;
-                if(reader_data->size > 0) {
-                    memcpy(reader_data->data, tx_rx.rx_data, reader_data->size);
-                    if(nfc_worker->callback) {
-                        nfc_worker->callback(NfcWorkerEventSuccess, nfc_worker->context);
-                    }
-                }
-            } else {
-                FURI_LOG_E(TAG, "Failed to get reader commands");
-            }
-        }
-    }
-}
-
-void nfc_worker_read_emv_app(NfcWorker* nfc_worker) {
-    FuriHalNfcTxRxContext tx_rx = {};
-    nfc_debug_pcap_prepare_tx_rx(nfc_worker->debug_pcap_worker, &tx_rx, false);
-    EmvApplication emv_app = {};
-    NfcDeviceData* result = nfc_worker->dev_data;
-    FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data;
-    nfc_device_data_clear(result);
-
-    while(nfc_worker->state == NfcWorkerStateReadEMVApp) {
-        if(furi_hal_nfc_detect(nfc_data, 1000)) {
-            // Card was found. Check that it supports EMV
-            if(nfc_data->interface == FuriHalNfcInterfaceIsoDep) {
-                result->protocol = NfcDeviceProtocolEMV;
-                if(emv_search_application(&tx_rx, &emv_app)) {
-                    // Notify caller and exit
-                    result->emv_data.aid_len = emv_app.aid_len;
-                    memcpy(result->emv_data.aid, emv_app.aid, emv_app.aid_len);
-                    if(nfc_worker->callback) {
-                        nfc_worker->callback(NfcWorkerEventSuccess, nfc_worker->context);
-                    }
-                }
-            } else {
-                FURI_LOG_W(TAG, "Card doesn't support EMV");
-            }
-        } else {
-            FURI_LOG_D(TAG, "Can't find any cards");
-        }
-        furi_hal_nfc_sleep();
-        furi_delay_ms(20);
-    }
-}
-
-void nfc_worker_read_emv(NfcWorker* nfc_worker) {
-    FuriHalNfcTxRxContext tx_rx = {};
-    nfc_debug_pcap_prepare_tx_rx(nfc_worker->debug_pcap_worker, &tx_rx, false);
-    EmvApplication emv_app = {};
-    NfcDeviceData* result = nfc_worker->dev_data;
-    FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data;
-    nfc_device_data_clear(result);
-
-    while(nfc_worker->state == NfcWorkerStateReadEMVData) {
-        if(furi_hal_nfc_detect(nfc_data, 1000)) {
-            // Card was found. Check that it supports EMV
-            if(nfc_data->interface == FuriHalNfcInterfaceIsoDep) {
-                result->protocol = NfcDeviceProtocolEMV;
-                if(emv_read_bank_card(&tx_rx, &emv_app)) {
-                    result->emv_data.number_len = emv_app.card_number_len;
-                    memcpy(
-                        result->emv_data.number, emv_app.card_number, result->emv_data.number_len);
-                    result->emv_data.aid_len = emv_app.aid_len;
-                    memcpy(result->emv_data.aid, emv_app.aid, emv_app.aid_len);
-                    if(emv_app.name_found) {
-                        memcpy(result->emv_data.name, emv_app.name, sizeof(emv_app.name));
-                    }
-                    if(emv_app.exp_month) {
-                        result->emv_data.exp_mon = emv_app.exp_month;
-                        result->emv_data.exp_year = emv_app.exp_year;
-                    }
-                    if(emv_app.country_code) {
-                        result->emv_data.country_code = emv_app.country_code;
-                    }
-                    if(emv_app.currency_code) {
-                        result->emv_data.currency_code = emv_app.currency_code;
-                    }
-                    // Notify caller and exit
-                    if(nfc_worker->callback) {
-                        nfc_worker->callback(NfcWorkerEventSuccess, nfc_worker->context);
-                    }
-                    break;
-                }
-            } else {
-                FURI_LOG_W(TAG, "Card doesn't support EMV");
-            }
-        } else {
-            FURI_LOG_D(TAG, "Can't find any cards");
-        }
-        furi_hal_nfc_sleep();
-        furi_delay_ms(20);
-    }
-}
-
-void nfc_worker_emulate_apdu(NfcWorker* nfc_worker) {
-    FuriHalNfcTxRxContext tx_rx = {};
-    nfc_debug_pcap_prepare_tx_rx(nfc_worker->debug_pcap_worker, &tx_rx, true);
-    FuriHalNfcDevData params = {
-        .uid = {0xCF, 0x72, 0xd4, 0x40},
-        .uid_len = 4,
-        .atqa = {0x00, 0x04},
-        .sak = 0x20,
-        .type = FuriHalNfcTypeA,
-    };
-
-    while(nfc_worker->state == NfcWorkerStateEmulateApdu) {
-        if(furi_hal_nfc_listen(params.uid, params.uid_len, params.atqa, params.sak, false, 300)) {
-            FURI_LOG_D(TAG, "POS terminal detected");
-            if(emv_card_emulation(&tx_rx)) {
-                FURI_LOG_D(TAG, "EMV card emulated");
-            }
-        } else {
-            FURI_LOG_D(TAG, "Can't find reader");
-        }
-        furi_hal_nfc_sleep();
-        furi_delay_ms(20);
-    }
-}
-
-void nfc_worker_read_mifare_ultralight(NfcWorker* nfc_worker) {
-    FuriHalNfcTxRxContext tx_rx = {};
-    nfc_debug_pcap_prepare_tx_rx(nfc_worker->debug_pcap_worker, &tx_rx, false);
-    MfUltralightReader reader = {};
-    MfUltralightData data = {};
-    NfcDeviceData* result = nfc_worker->dev_data;
-    FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data;
-
-    while(nfc_worker->state == NfcWorkerStateReadMifareUltralight) {
-        if(furi_hal_nfc_detect(nfc_data, 300)) {
-            if(nfc_data->type == FuriHalNfcTypeA &&
-               mf_ul_check_card_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak)) {
-                FURI_LOG_D(TAG, "Found Mifare Ultralight tag. Start reading");
-                if(mf_ul_read_card(&tx_rx, &reader, &data)) {
-                    result->protocol = NfcDeviceProtocolMifareUl;
-                    result->mf_ul_data = data;
-                    // Notify caller and exit
-                    if(nfc_worker->callback) {
-                        nfc_worker->callback(NfcWorkerEventSuccess, nfc_worker->context);
-                    }
-                    break;
-                } else {
-                    FURI_LOG_D(TAG, "Failed reading Mifare Ultralight");
-                }
-            } else {
-                FURI_LOG_W(TAG, "Tag is not Mifare Ultralight");
-            }
-        } else {
-            FURI_LOG_D(TAG, "Can't find any tags");
-        }
-        furi_hal_nfc_sleep();
-        furi_delay_ms(100);
-    }
-}
-
-void nfc_worker_emulate_mifare_ul(NfcWorker* nfc_worker) {
-    FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data;
-    MfUltralightEmulator emulator = {};
-    mf_ul_prepare_emulation(&emulator, &nfc_worker->dev_data->mf_ul_data);
-    while(nfc_worker->state == NfcWorkerStateEmulateMifareUltralight) {
-        mf_ul_reset_emulation(&emulator, true);
-        furi_hal_nfc_emulate_nfca(
-            nfc_data->uid,
-            nfc_data->uid_len,
-            nfc_data->atqa,
-            nfc_data->sak,
-            mf_ul_prepare_emulation_response,
-            &emulator,
-            5000);
-        // Check if data was modified
-        if(emulator.data_changed) {
-            nfc_worker->dev_data->mf_ul_data = emulator.data;
-            if(nfc_worker->callback) {
-                nfc_worker->callback(NfcWorkerEventSuccess, nfc_worker->context);
-            }
-            emulator.data_changed = false;
-        }
-    }
-}
-
-void nfc_worker_mifare_classic_dict_attack(NfcWorker* nfc_worker) {
-    furi_assert(nfc_worker->callback);
-    FuriHalNfcTxRxContext tx_rx_ctx = {};
-    nfc_debug_pcap_prepare_tx_rx(nfc_worker->debug_pcap_worker, &tx_rx_ctx, false);
-    MfClassicAuthContext auth_ctx = {};
-    MfClassicReader reader = {};
-    uint64_t curr_key = 0;
-    uint16_t curr_sector = 0;
-    uint8_t total_sectors = 0;
-    NfcWorkerEvent event;
-    FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data;
-
-    // Open dictionary
-    nfc_worker->dict_stream = file_stream_alloc(nfc_worker->storage);
-    if(!nfc_mf_classic_dict_open_file(nfc_worker->dict_stream)) {
-        event = NfcWorkerEventNoDictFound;
-        nfc_worker->callback(event, nfc_worker->context);
-        nfc_mf_classic_dict_close_file(nfc_worker->dict_stream);
-        stream_free(nfc_worker->dict_stream);
-        return;
-    }
-
-    // Detect Mifare Classic card
-    while(nfc_worker->state == NfcWorkerStateReadMifareClassic) {
-        if(furi_hal_nfc_detect(nfc_data, 300)) {
-            if(mf_classic_get_type(
-                   nfc_data->uid,
-                   nfc_data->uid_len,
-                   nfc_data->atqa[0],
-                   nfc_data->atqa[1],
-                   nfc_data->sak,
-                   &reader)) {
-                total_sectors = mf_classic_get_total_sectors_num(&reader);
-                if(reader.type == MfClassicType1k) {
-                    event = NfcWorkerEventDetectedClassic1k;
-                } else {
-                    event = NfcWorkerEventDetectedClassic4k;
-                }
-                nfc_worker->callback(event, nfc_worker->context);
-                break;
-            }
-        } else {
-            event = NfcWorkerEventNoCardDetected;
-            nfc_worker->callback(event, nfc_worker->context);
-        }
-    }
-
-    if(nfc_worker->state == NfcWorkerStateReadMifareClassic) {
-        bool card_removed_notified = false;
-        bool card_found_notified = false;
-        // Seek for mifare classic keys
-        for(curr_sector = 0; curr_sector < total_sectors; curr_sector++) {
-            FURI_LOG_I(TAG, "Sector: %d ...", curr_sector);
-            event = NfcWorkerEventNewSector;
-            nfc_worker->callback(event, nfc_worker->context);
-            mf_classic_auth_init_context(&auth_ctx, reader.cuid, curr_sector);
-            bool sector_key_found = false;
-            while(nfc_mf_classic_dict_get_next_key(nfc_worker->dict_stream, &curr_key)) {
-                furi_hal_nfc_sleep();
-                if(furi_hal_nfc_activate_nfca(300, &reader.cuid)) {
-                    if(!card_found_notified) {
-                        if(reader.type == MfClassicType1k) {
-                            event = NfcWorkerEventDetectedClassic1k;
-                        } else {
-                            event = NfcWorkerEventDetectedClassic4k;
-                        }
-                        nfc_worker->callback(event, nfc_worker->context);
-                        card_found_notified = true;
-                        card_removed_notified = false;
-                    }
-                    FURI_LOG_D(
-                        TAG,
-                        "Try to auth to sector %d with key %04lx%08lx",
-                        curr_sector,
-                        (uint32_t)(curr_key >> 32),
-                        (uint32_t)curr_key);
-                    if(mf_classic_auth_attempt(&tx_rx_ctx, &auth_ctx, curr_key)) {
-                        sector_key_found = true;
-                        if((auth_ctx.key_a != MF_CLASSIC_NO_KEY) &&
-                           (auth_ctx.key_b != MF_CLASSIC_NO_KEY))
-                            break;
-                    }
-                } else {
-                    // Notify that no tag is availalble
-                    FURI_LOG_D(TAG, "Can't find tags");
-                    if(!card_removed_notified) {
-                        event = NfcWorkerEventNoCardDetected;
-                        nfc_worker->callback(event, nfc_worker->context);
-                        card_removed_notified = true;
-                        card_found_notified = false;
-                    }
-                }
-                if(nfc_worker->state != NfcWorkerStateReadMifareClassic) break;
-                furi_delay_tick(1);
-            }
-            if(nfc_worker->state != NfcWorkerStateReadMifareClassic) break;
-            if(sector_key_found) {
-                // Notify that keys were found
-                if(auth_ctx.key_a != MF_CLASSIC_NO_KEY) {
-                    FURI_LOG_I(
-                        TAG,
-                        "Sector %d key A: %04lx%08lx",
-                        curr_sector,
-                        (uint32_t)(auth_ctx.key_a >> 32),
-                        (uint32_t)auth_ctx.key_a);
-                    event = NfcWorkerEventFoundKeyA;
-                    nfc_worker->callback(event, nfc_worker->context);
-                }
-                if(auth_ctx.key_b != MF_CLASSIC_NO_KEY) {
-                    FURI_LOG_I(
-                        TAG,
-                        "Sector %d key B: %04lx%08lx",
-                        curr_sector,
-                        (uint32_t)(auth_ctx.key_b >> 32),
-                        (uint32_t)auth_ctx.key_b);
-                    event = NfcWorkerEventFoundKeyB;
-                    nfc_worker->callback(event, nfc_worker->context);
-                }
-                // Add sectors to read sequence
-                mf_classic_reader_add_sector(&reader, curr_sector, auth_ctx.key_a, auth_ctx.key_b);
-            }
-            nfc_mf_classic_dict_reset(nfc_worker->dict_stream);
-        }
-    }
-
-    if(nfc_worker->state == NfcWorkerStateReadMifareClassic) {
-        FURI_LOG_I(TAG, "Found keys to %d sectors. Start reading sectors", reader.sectors_to_read);
-        uint8_t sectors_read =
-            mf_classic_read_card(&tx_rx_ctx, &reader, &nfc_worker->dev_data->mf_classic_data);
-        if(sectors_read) {
-            event = NfcWorkerEventSuccess;
-            nfc_worker->dev_data->protocol = NfcDeviceProtocolMifareClassic;
-            FURI_LOG_I(TAG, "Successfully read %d sectors", sectors_read);
-        } else {
-            event = NfcWorkerEventFail;
-            FURI_LOG_W(TAG, "Failed to read any sector");
-        }
-        nfc_worker->callback(event, nfc_worker->context);
-    }
-
-    nfc_mf_classic_dict_close_file(nfc_worker->dict_stream);
-    stream_free(nfc_worker->dict_stream);
-}
-
-void nfc_worker_emulate_mifare_classic(NfcWorker* nfc_worker) {
-    FuriHalNfcTxRxContext tx_rx = {};
-    nfc_debug_pcap_prepare_tx_rx(nfc_worker->debug_pcap_worker, &tx_rx, true);
-    FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data;
-    MfClassicEmulator emulator = {
-        .cuid = nfc_util_bytes2num(&nfc_data->uid[nfc_data->uid_len - 4], 4),
-        .data = nfc_worker->dev_data->mf_classic_data,
-        .data_changed = false,
-    };
-    NfcaSignal* nfca_signal = nfca_signal_alloc();
-    tx_rx.nfca_signal = nfca_signal;
-
-    rfal_platform_spi_acquire();
-
-    furi_hal_nfc_listen_start(nfc_data);
-    while(nfc_worker->state == NfcWorkerStateEmulateMifareClassic) {
-        if(furi_hal_nfc_listen_rx(&tx_rx, 300)) {
-            mf_classic_emulator(&emulator, &tx_rx);
-        }
-    }
-    if(emulator.data_changed) {
-        nfc_worker->dev_data->mf_classic_data = emulator.data;
-        if(nfc_worker->callback) {
-            nfc_worker->callback(NfcWorkerEventSuccess, nfc_worker->context);
-        }
-        emulator.data_changed = false;
-    }
-
-    nfca_signal_free(nfca_signal);
-
-    rfal_platform_spi_release();
-}
-
-void nfc_worker_read_mifare_desfire(NfcWorker* nfc_worker) {
-    FuriHalNfcTxRxContext tx_rx = {};
-    nfc_debug_pcap_prepare_tx_rx(nfc_worker->debug_pcap_worker, &tx_rx, false);
-    NfcDeviceData* result = nfc_worker->dev_data;
-    nfc_device_data_clear(result);
-    MifareDesfireData* data = &result->mf_df_data;
-    FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data;
-
-    while(nfc_worker->state == NfcWorkerStateReadMifareDesfire) {
-        furi_hal_nfc_sleep();
-        if(!furi_hal_nfc_detect(nfc_data, 300)) {
-            furi_delay_ms(100);
-            continue;
-        }
-        memset(data, 0, sizeof(MifareDesfireData));
-        if(nfc_data->type != FuriHalNfcTypeA ||
-           !mf_df_check_card_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak)) {
-            FURI_LOG_D(TAG, "Tag is not DESFire");
-            furi_delay_ms(100);
-            continue;
-        }
-
-        FURI_LOG_D(TAG, "Found DESFire tag");
-
-        result->protocol = NfcDeviceProtocolMifareDesfire;
-
-        // Get DESFire version
-        tx_rx.tx_bits = 8 * mf_df_prepare_get_version(tx_rx.tx_data);
-        if(!furi_hal_nfc_tx_rx_full(&tx_rx)) {
-            FURI_LOG_W(TAG, "Bad exchange getting version");
-            continue;
-        }
-        if(!mf_df_parse_get_version_response(tx_rx.rx_data, tx_rx.rx_bits / 8, &data->version)) {
-            FURI_LOG_W(TAG, "Bad DESFire GET_VERSION response");
-            continue;
-        }
-
-        tx_rx.tx_bits = 8 * mf_df_prepare_get_free_memory(tx_rx.tx_data);
-        if(furi_hal_nfc_tx_rx_full(&tx_rx)) {
-            data->free_memory = malloc(sizeof(MifareDesfireFreeMemory));
-            memset(data->free_memory, 0, sizeof(MifareDesfireFreeMemory));
-            if(!mf_df_parse_get_free_memory_response(
-                   tx_rx.rx_data, tx_rx.rx_bits / 8, data->free_memory)) {
-                FURI_LOG_D(TAG, "Bad DESFire GET_FREE_MEMORY response (normal for pre-EV1 cards)");
-                free(data->free_memory);
-                data->free_memory = NULL;
-            }
-        }
-
-        tx_rx.tx_bits = 8 * mf_df_prepare_get_key_settings(tx_rx.tx_data);
-        if(!furi_hal_nfc_tx_rx_full(&tx_rx)) {
-            FURI_LOG_D(TAG, "Bad exchange getting key settings");
-        } else {
-            data->master_key_settings = malloc(sizeof(MifareDesfireKeySettings));
-            memset(data->master_key_settings, 0, sizeof(MifareDesfireKeySettings));
-            if(!mf_df_parse_get_key_settings_response(
-                   tx_rx.rx_data, tx_rx.rx_bits / 8, data->master_key_settings)) {
-                FURI_LOG_W(TAG, "Bad DESFire GET_KEY_SETTINGS response");
-                free(data->master_key_settings);
-                data->master_key_settings = NULL;
-            } else {
-                MifareDesfireKeyVersion** key_version_head =
-                    &data->master_key_settings->key_version_head;
-                for(uint8_t key_id = 0; key_id < data->master_key_settings->max_keys; key_id++) {
-                    tx_rx.tx_bits = 8 * mf_df_prepare_get_key_version(tx_rx.tx_data, key_id);
-                    if(!furi_hal_nfc_tx_rx_full(&tx_rx)) {
-                        FURI_LOG_W(TAG, "Bad exchange getting key version");
-                        continue;
-                    }
-                    MifareDesfireKeyVersion* key_version = malloc(sizeof(MifareDesfireKeyVersion));
-                    memset(key_version, 0, sizeof(MifareDesfireKeyVersion));
-                    key_version->id = key_id;
-                    if(!mf_df_parse_get_key_version_response(
-                           tx_rx.rx_data, tx_rx.rx_bits / 8, key_version)) {
-                        FURI_LOG_W(TAG, "Bad DESFire GET_KEY_VERSION response");
-                        free(key_version);
-                        continue;
-                    }
-                    *key_version_head = key_version;
-                    key_version_head = &key_version->next;
-                }
-            }
-        }
-
-        tx_rx.tx_bits = 8 * mf_df_prepare_get_application_ids(tx_rx.tx_data);
-        if(!furi_hal_nfc_tx_rx_full(&tx_rx)) {
-            FURI_LOG_W(TAG, "Bad exchange getting application IDs");
-        } else {
-            if(!mf_df_parse_get_application_ids_response(
-                   tx_rx.rx_data, tx_rx.rx_bits / 8, &data->app_head)) {
-                FURI_LOG_W(TAG, "Bad DESFire GET_APPLICATION_IDS response");
-            }
-        }
-
-        for(MifareDesfireApplication* app = data->app_head; app; app = app->next) {
-            tx_rx.tx_bits = 8 * mf_df_prepare_select_application(tx_rx.tx_data, app->id);
-            if(!furi_hal_nfc_tx_rx_full(&tx_rx) ||
-               !mf_df_parse_select_application_response(tx_rx.rx_data, tx_rx.rx_bits / 8)) {
-                FURI_LOG_W(TAG, "Bad exchange selecting application");
-                continue;
-            }
-            tx_rx.tx_bits = 8 * mf_df_prepare_get_key_settings(tx_rx.tx_data);
-            if(!furi_hal_nfc_tx_rx_full(&tx_rx)) {
-                FURI_LOG_W(TAG, "Bad exchange getting key settings");
-            } else {
-                app->key_settings = malloc(sizeof(MifareDesfireKeySettings));
-                memset(app->key_settings, 0, sizeof(MifareDesfireKeySettings));
-                if(!mf_df_parse_get_key_settings_response(
-                       tx_rx.rx_data, tx_rx.rx_bits / 8, app->key_settings)) {
-                    FURI_LOG_W(TAG, "Bad DESFire GET_KEY_SETTINGS response");
-                    free(app->key_settings);
-                    app->key_settings = NULL;
-                    continue;
-                }
-
-                MifareDesfireKeyVersion** key_version_head = &app->key_settings->key_version_head;
-                for(uint8_t key_id = 0; key_id < app->key_settings->max_keys; key_id++) {
-                    tx_rx.tx_bits = 8 * mf_df_prepare_get_key_version(tx_rx.tx_data, key_id);
-                    if(!furi_hal_nfc_tx_rx_full(&tx_rx)) {
-                        FURI_LOG_W(TAG, "Bad exchange getting key version");
-                        continue;
-                    }
-                    MifareDesfireKeyVersion* key_version = malloc(sizeof(MifareDesfireKeyVersion));
-                    memset(key_version, 0, sizeof(MifareDesfireKeyVersion));
-                    key_version->id = key_id;
-                    if(!mf_df_parse_get_key_version_response(
-                           tx_rx.rx_data, tx_rx.rx_bits / 8, key_version)) {
-                        FURI_LOG_W(TAG, "Bad DESFire GET_KEY_VERSION response");
-                        free(key_version);
-                        continue;
-                    }
-                    *key_version_head = key_version;
-                    key_version_head = &key_version->next;
-                }
-            }
-
-            tx_rx.tx_bits = 8 * mf_df_prepare_get_file_ids(tx_rx.tx_data);
-            if(!furi_hal_nfc_tx_rx_full(&tx_rx)) {
-                FURI_LOG_W(TAG, "Bad exchange getting file IDs");
-            } else {
-                if(!mf_df_parse_get_file_ids_response(
-                       tx_rx.rx_data, tx_rx.rx_bits / 8, &app->file_head)) {
-                    FURI_LOG_W(TAG, "Bad DESFire GET_FILE_IDS response");
-                }
-            }
-
-            for(MifareDesfireFile* file = app->file_head; file; file = file->next) {
-                tx_rx.tx_bits = 8 * mf_df_prepare_get_file_settings(tx_rx.tx_data, file->id);
-                if(!furi_hal_nfc_tx_rx_full(&tx_rx)) {
-                    FURI_LOG_W(TAG, "Bad exchange getting file settings");
-                    continue;
-                }
-                if(!mf_df_parse_get_file_settings_response(
-                       tx_rx.rx_data, tx_rx.rx_bits / 8, file)) {
-                    FURI_LOG_W(TAG, "Bad DESFire GET_FILE_SETTINGS response");
-                    continue;
-                }
-                switch(file->type) {
-                case MifareDesfireFileTypeStandard:
-                case MifareDesfireFileTypeBackup:
-                    tx_rx.tx_bits = 8 * mf_df_prepare_read_data(tx_rx.tx_data, file->id, 0, 0);
-                    break;
-                case MifareDesfireFileTypeValue:
-                    tx_rx.tx_bits = 8 * mf_df_prepare_get_value(tx_rx.tx_data, file->id);
-                    break;
-                case MifareDesfireFileTypeLinearRecord:
-                case MifareDesfireFileTypeCyclicRecord:
-                    tx_rx.tx_bits = 8 * mf_df_prepare_read_records(tx_rx.tx_data, file->id, 0, 0);
-                    break;
-                }
-                if(!furi_hal_nfc_tx_rx_full(&tx_rx)) {
-                    FURI_LOG_W(TAG, "Bad exchange reading file %d", file->id);
-                    continue;
-                }
-                if(!mf_df_parse_read_data_response(tx_rx.rx_data, tx_rx.rx_bits / 8, file)) {
-                    FURI_LOG_W(TAG, "Bad response reading file %d", file->id);
-                    continue;
-                }
-            }
-        }
-
-        // Notify caller and exit
-        if(nfc_worker->callback) {
-            nfc_worker->callback(NfcWorkerEventSuccess, nfc_worker->context);
-        }
-        break;
-    }
-}

+ 0 - 57
applications/nfc/nfc_worker_i.h

@@ -1,57 +0,0 @@
-#pragma once
-
-#include "nfc_worker.h"
-#include "nfc_i.h"
-
-#include <furi.h>
-#include <lib/toolbox/stream/file_stream.h>
-
-#include <lib/nfc_protocols/nfc_util.h>
-#include <lib/nfc_protocols/emv.h>
-#include <lib/nfc_protocols/mifare_common.h>
-#include <lib/nfc_protocols/mifare_ultralight.h>
-#include <lib/nfc_protocols/mifare_classic.h>
-#include <lib/nfc_protocols/mifare_desfire.h>
-#include <lib/nfc_protocols/nfca.h>
-
-#include "helpers/nfc_mf_classic_dict.h"
-#include "helpers/nfc_debug_pcap.h"
-
-struct NfcWorker {
-    FuriThread* thread;
-    Storage* storage;
-    Stream* dict_stream;
-
-    NfcDeviceData* dev_data;
-
-    NfcWorkerCallback callback;
-    void* context;
-
-    NfcWorkerState state;
-
-    NfcDebugPcapWorker* debug_pcap_worker;
-};
-
-void nfc_worker_change_state(NfcWorker* nfc_worker, NfcWorkerState state);
-
-int32_t nfc_worker_task(void* context);
-
-void nfc_worker_read_emv_app(NfcWorker* nfc_worker);
-
-void nfc_worker_read_emv(NfcWorker* nfc_worker);
-
-void nfc_worker_emulate_apdu(NfcWorker* nfc_worker);
-
-void nfc_worker_detect(NfcWorker* nfc_worker);
-
-void nfc_worker_emulate(NfcWorker* nfc_worker);
-
-void nfc_worker_read_mifare_ultralight(NfcWorker* nfc_worker);
-
-void nfc_worker_mifare_classic_dict_attack(NfcWorker* nfc_worker);
-
-void nfc_worker_read_mifare_desfire(NfcWorker* nfc_worker);
-
-void nfc_worker_emulate_mifare_ul(NfcWorker* nfc_worker);
-
-void nfc_worker_emulate_mifare_classic(NfcWorker* nfc_worker);

+ 0 - 90
applications/nfc/scenes/nfc_scene_card_menu.c

@@ -1,90 +0,0 @@
-#include "../nfc_i.h"
-
-enum SubmenuIndex {
-    SubmenuIndexRunApp,
-    SubmenuIndexChooseScript,
-    SubmenuIndexEmulate,
-    SubmenuIndexSave,
-};
-
-void nfc_scene_card_menu_submenu_callback(void* context, uint32_t index) {
-    Nfc* nfc = context;
-
-    view_dispatcher_send_custom_event(nfc->view_dispatcher, index);
-}
-
-void nfc_scene_card_menu_on_enter(void* context) {
-    Nfc* nfc = context;
-    Submenu* submenu = nfc->submenu;
-
-    if(nfc->dev->dev_data.protocol > NfcDeviceProtocolUnknown) {
-        submenu_add_item(
-            submenu,
-            "Run Compatible App",
-            SubmenuIndexRunApp,
-            nfc_scene_card_menu_submenu_callback,
-            nfc);
-    }
-    submenu_add_item(
-        submenu,
-        "Additional reading scripts",
-        SubmenuIndexChooseScript,
-        nfc_scene_card_menu_submenu_callback,
-        nfc);
-    submenu_add_item(
-        submenu, "Emulate UID", SubmenuIndexEmulate, nfc_scene_card_menu_submenu_callback, nfc);
-    submenu_add_item(
-        submenu, "Save UID", SubmenuIndexSave, nfc_scene_card_menu_submenu_callback, nfc);
-    submenu_set_selected_item(
-        nfc->submenu, scene_manager_get_scene_state(nfc->scene_manager, NfcSceneCardMenu));
-
-    view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu);
-}
-
-bool nfc_scene_card_menu_on_event(void* context, SceneManagerEvent event) {
-    Nfc* nfc = context;
-    bool consumed = false;
-
-    if(event.type == SceneManagerEventTypeCustom) {
-        if(event.event == SubmenuIndexRunApp) {
-            scene_manager_set_scene_state(
-                nfc->scene_manager, NfcSceneCardMenu, SubmenuIndexRunApp);
-            if(nfc->dev->dev_data.protocol == NfcDeviceProtocolMifareUl) {
-                scene_manager_next_scene(nfc->scene_manager, NfcSceneReadMifareUl);
-            } else if(nfc->dev->dev_data.protocol == NfcDeviceProtocolMifareDesfire) {
-                scene_manager_next_scene(nfc->scene_manager, NfcSceneReadMifareDesfire);
-            } else if(nfc->dev->dev_data.protocol == NfcDeviceProtocolEMV) {
-                scene_manager_next_scene(nfc->scene_manager, NfcSceneReadEmvApp);
-            } else if(nfc->dev->dev_data.protocol == NfcDeviceProtocolMifareClassic) {
-                scene_manager_next_scene(nfc->scene_manager, NfcSceneReadMifareClassic);
-            }
-            consumed = true;
-        } else if(event.event == SubmenuIndexChooseScript) {
-            scene_manager_set_scene_state(
-                nfc->scene_manager, NfcSceneCardMenu, SubmenuIndexChooseScript);
-            scene_manager_next_scene(nfc->scene_manager, NfcSceneScriptsMenu);
-            consumed = true;
-        } else if(event.event == SubmenuIndexEmulate) {
-            scene_manager_set_scene_state(
-                nfc->scene_manager, NfcSceneCardMenu, SubmenuIndexEmulate);
-            scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateUid);
-            consumed = true;
-        } else if(event.event == SubmenuIndexSave) {
-            scene_manager_set_scene_state(nfc->scene_manager, NfcSceneCardMenu, SubmenuIndexSave);
-            nfc->dev->format = NfcDeviceSaveFormatUid;
-            scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveName);
-            consumed = true;
-        }
-    } else if(event.type == SceneManagerEventTypeBack) {
-        consumed =
-            scene_manager_search_and_switch_to_previous_scene(nfc->scene_manager, NfcSceneStart);
-    }
-
-    return consumed;
-}
-
-void nfc_scene_card_menu_on_exit(void* context) {
-    Nfc* nfc = context;
-
-    submenu_reset(nfc->submenu);
-}

+ 26 - 27
applications/nfc/scenes/nfc_scene_config.h

@@ -1,41 +1,40 @@
 ADD_SCENE(nfc, start, Start)
-ADD_SCENE(nfc, read_card, ReadCard)
-ADD_SCENE(nfc, read_card_success, ReadCardSuccess)
-ADD_SCENE(nfc, card_menu, CardMenu)
-ADD_SCENE(nfc, emulate_uid, EmulateUid)
-ADD_SCENE(nfc, save_name, SaveName)
-ADD_SCENE(nfc, save_success, SaveSuccess)
-ADD_SCENE(nfc, file_select, FileSelect)
+ADD_SCENE(nfc, read, Read)
 ADD_SCENE(nfc, saved_menu, SavedMenu)
+ADD_SCENE(nfc, extra_actions, ExtraActions)
 ADD_SCENE(nfc, set_type, SetType)
 ADD_SCENE(nfc, set_sak, SetSak)
 ADD_SCENE(nfc, set_atqa, SetAtqua)
 ADD_SCENE(nfc, set_uid, SetUid)
-ADD_SCENE(nfc, scripts_menu, ScriptsMenu)
-ADD_SCENE(nfc, read_mifare_ul, ReadMifareUl)
-ADD_SCENE(nfc, read_mifare_ul_success, ReadMifareUlSuccess)
-ADD_SCENE(nfc, mifare_ul_menu, MifareUlMenu)
-ADD_SCENE(nfc, emulate_mifare_ul, EmulateMifareUl)
-ADD_SCENE(nfc, read_emv_app, ReadEmvApp)
-ADD_SCENE(nfc, read_emv_app_success, ReadEmvAppSuccess)
-ADD_SCENE(nfc, read_mifare_desfire, ReadMifareDesfire)
-ADD_SCENE(nfc, read_mifare_desfire_success, ReadMifareDesfireSuccess)
-ADD_SCENE(nfc, mifare_desfire_menu, MifareDesfireMenu)
-ADD_SCENE(nfc, mifare_desfire_data, MifareDesfireData)
-ADD_SCENE(nfc, mifare_desfire_app, MifareDesfireApp)
+ADD_SCENE(nfc, generate_info, GenerateInfo)
+ADD_SCENE(nfc, read_card_success, ReadCardSuccess)
+ADD_SCENE(nfc, save_name, SaveName)
+ADD_SCENE(nfc, save_success, SaveSuccess)
+ADD_SCENE(nfc, file_select, FileSelect)
+ADD_SCENE(nfc, emulate_uid, EmulateUid)
+ADD_SCENE(nfc, mf_ultralight_read_success, MfUltralightReadSuccess)
+ADD_SCENE(nfc, mf_ultralight_menu, MfUltralightMenu)
+ADD_SCENE(nfc, mf_ultralight_emulate, MfUltralightEmulate)
+ADD_SCENE(nfc, mf_desfire_read_success, MfDesfireReadSuccess)
+ADD_SCENE(nfc, mf_desfire_menu, MfDesfireMenu)
+ADD_SCENE(nfc, mf_desfire_data, MfDesfireData)
+ADD_SCENE(nfc, mf_desfire_app, MfDesfireApp)
+ADD_SCENE(nfc, mf_classic_read_success, MfClassicReadSuccess)
+ADD_SCENE(nfc, mf_classic_menu, MfClassicMenu)
+ADD_SCENE(nfc, mf_classic_emulate, MfClassicEmulate)
+ADD_SCENE(nfc, mf_classic_keys, MfClassicKeys)
+ADD_SCENE(nfc, mf_classic_keys_add, MfClassicKeysAdd)
+ADD_SCENE(nfc, mf_classic_dict_attack, MfClassicDictAttack)
+ADD_SCENE(nfc, emv_read_success, EmvReadSuccess)
+ADD_SCENE(nfc, emulate_apdu_sequence, EmulateApduSequence)
 ADD_SCENE(nfc, device_info, DeviceInfo)
 ADD_SCENE(nfc, delete, Delete)
 ADD_SCENE(nfc, delete_success, DeleteSuccess)
-ADD_SCENE(nfc, run_emv_app_confirm, RunEmvAppConfirm)
-ADD_SCENE(nfc, read_emv_data, ReadEmvData)
-ADD_SCENE(nfc, read_emv_data_success, ReadEmvDataSuccess)
-ADD_SCENE(nfc, emulate_apdu_sequence, EmulateApduSequence)
 ADD_SCENE(nfc, restore_original, RestoreOriginal)
 ADD_SCENE(nfc, debug, Debug)
 ADD_SCENE(nfc, field, Field)
-ADD_SCENE(nfc, read_mifare_classic, ReadMifareClassic)
-ADD_SCENE(nfc, emulate_mifare_classic, EmulateMifareClassic)
-ADD_SCENE(nfc, mifare_classic_menu, MifareClassicMenu)
 ADD_SCENE(nfc, dict_not_found, DictNotFound)
 ADD_SCENE(nfc, rpc, Rpc)
-ADD_SCENE(nfc, generate_info, GenerateInfo)
+ADD_SCENE(nfc, exit_confirm, ExitConfirm)
+ADD_SCENE(nfc, retry_confirm, RetryConfirm)
+ADD_SCENE(nfc, detect_reader, DetectReader)

+ 143 - 0
applications/nfc/scenes/nfc_scene_detect_reader.c

@@ -0,0 +1,143 @@
+#include "../nfc_i.h"
+#include <dolphin/dolphin.h>
+
+#define NFC_SCENE_DETECT_READER_LOG_SIZE_MAX (200)
+
+enum {
+    NfcSceneDetectReaderStateWidget,
+    NfcSceneDetectReaderStateTextBox,
+};
+
+bool nfc_detect_reader_worker_callback(NfcWorkerEvent event, void* context) {
+    UNUSED(event);
+    furi_assert(context);
+    Nfc* nfc = context;
+    view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventWorkerExit);
+    return true;
+}
+
+void nfc_scene_detect_reader_widget_callback(GuiButtonType result, InputType type, void* context) {
+    furi_assert(context);
+    Nfc* nfc = context;
+    if(type == InputTypeShort) {
+        view_dispatcher_send_custom_event(nfc->view_dispatcher, result);
+    }
+}
+
+void nfc_detect_reader_textbox_callback(void* context) {
+    furi_assert(context);
+    Nfc* nfc = context;
+    view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit);
+}
+
+// Add widget with device name or inform that data received
+static void nfc_scene_detect_reader_widget_config(Nfc* nfc, bool data_received) {
+    Widget* widget = nfc->widget;
+    widget_reset(widget);
+
+    widget_add_icon_element(widget, 0, 14, &I_Reader_detect);
+    widget_add_string_element(
+        widget, 64, 3, AlignCenter, AlignTop, FontSecondary, "Hold near reader");
+    widget_add_string_element(widget, 55, 22, AlignLeft, AlignTop, FontPrimary, "Emulating...");
+
+    if(data_received) {
+        widget_add_button_element(
+            widget, GuiButtonTypeCenter, "Log", nfc_scene_detect_reader_widget_callback, nfc);
+    }
+}
+
+void nfc_scene_detect_reader_on_enter(void* context) {
+    Nfc* nfc = context;
+    DOLPHIN_DEED(DolphinDeedNfcEmulate);
+    FuriHalNfcDevData nfc_params = {
+        .uid = {0x36, 0x9C, 0xe7, 0xb1, 0x0A, 0xC1, 0x34},
+        .uid_len = 7,
+        .atqa = {0x44, 0x00},
+        .sak = 0x08,
+        .type = FuriHalNfcTypeA,
+    };
+    nfc->dev->dev_data.nfc_data = nfc_params;
+
+    // Setup Widget
+    nfc_scene_detect_reader_widget_config(nfc, false);
+    // Setup TextBox
+    TextBox* text_box = nfc->text_box;
+    text_box_set_font(text_box, TextBoxFontHex);
+    text_box_set_focus(text_box, TextBoxFocusEnd);
+    string_reset(nfc->text_box_store);
+
+    // Set Widget state and view
+    scene_manager_set_scene_state(
+        nfc->scene_manager, NfcSceneDetectReader, NfcSceneDetectReaderStateWidget);
+    view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget);
+    // Start worker
+    memset(&nfc->dev->dev_data.reader_data, 0, sizeof(NfcReaderRequestData));
+    nfc_worker_start(
+        nfc->worker,
+        NfcWorkerStateUidEmulate,
+        &nfc->dev->dev_data,
+        nfc_detect_reader_worker_callback,
+        nfc);
+
+    nfc_blink_start(nfc);
+}
+
+bool nfc_scene_detect_reader_on_event(void* context, SceneManagerEvent event) {
+    Nfc* nfc = context;
+    NfcReaderRequestData* reader_data = &nfc->dev->dev_data.reader_data;
+    uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneDetectReader);
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == NfcCustomEventWorkerExit) {
+            // Add data button to widget if data is received for the first time
+            if(!string_size(nfc->text_box_store)) {
+                nfc_scene_detect_reader_widget_config(nfc, true);
+            }
+            // Update TextBox data
+            if(string_size(nfc->text_box_store) < NFC_SCENE_DETECT_READER_LOG_SIZE_MAX) {
+                string_cat_printf(nfc->text_box_store, "R:");
+                for(uint16_t i = 0; i < reader_data->size; i++) {
+                    string_cat_printf(nfc->text_box_store, " %02X", reader_data->data[i]);
+                }
+                string_push_back(nfc->text_box_store, '\n');
+                text_box_set_text(nfc->text_box, string_get_cstr(nfc->text_box_store));
+            }
+            memset(reader_data, 0, sizeof(NfcReaderRequestData));
+            consumed = true;
+        } else if(event.event == GuiButtonTypeCenter && state == NfcSceneDetectReaderStateWidget) {
+            view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextBox);
+            scene_manager_set_scene_state(
+                nfc->scene_manager, NfcSceneDetectReader, NfcSceneDetectReaderStateTextBox);
+            consumed = true;
+        } else if(event.event == NfcCustomEventViewExit && state == NfcSceneDetectReaderStateTextBox) {
+            view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget);
+            scene_manager_set_scene_state(
+                nfc->scene_manager, NfcSceneDetectReader, NfcSceneDetectReaderStateWidget);
+            consumed = true;
+        }
+    } else if(event.type == SceneManagerEventTypeBack) {
+        if(state == NfcSceneDetectReaderStateTextBox) {
+            view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget);
+            scene_manager_set_scene_state(
+                nfc->scene_manager, NfcSceneDetectReader, NfcSceneDetectReaderStateWidget);
+            consumed = true;
+        }
+    }
+
+    return consumed;
+}
+
+void nfc_scene_detect_reader_on_exit(void* context) {
+    Nfc* nfc = context;
+
+    // Stop worker
+    nfc_worker_stop(nfc->worker);
+
+    // Clear view
+    widget_reset(nfc->widget);
+    text_box_reset(nfc->text_box);
+    string_reset(nfc->text_box_store);
+
+    nfc_blink_stop(nfc);
+}

+ 1 - 1
applications/nfc/scenes/nfc_scene_device_info.c

@@ -190,7 +190,7 @@ bool nfc_scene_device_info_on_event(void* context, SceneManagerEvent event) {
                 view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewBankCard);
                 consumed = true;
             } else if(nfc->dev->format == NfcDeviceSaveFormatMifareDesfire) {
-                scene_manager_next_scene(nfc->scene_manager, NfcSceneMifareDesfireData);
+                scene_manager_next_scene(nfc->scene_manager, NfcSceneMfDesfireData);
                 consumed = true;
             }
         } else if(state == NfcSceneDeviceInfoData && event.event == NfcCustomEventViewExit) {

+ 2 - 5
applications/nfc/scenes/nfc_scene_dict_not_found.c

@@ -31,12 +31,9 @@ bool nfc_scene_dict_not_found_on_event(void* context, SceneManagerEvent event) {
 
     if(event.type == SceneManagerEventTypeCustom) {
         if(event.event == NfcCustomEventViewExit) {
-            if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneScriptsMenu)) {
+            if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneExtraActions)) {
                 consumed = scene_manager_search_and_switch_to_previous_scene(
-                    nfc->scene_manager, NfcSceneScriptsMenu);
-            } else if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneCardMenu)) {
-                consumed = scene_manager_search_and_switch_to_previous_scene(
-                    nfc->scene_manager, NfcSceneCardMenu);
+                    nfc->scene_manager, NfcSceneExtraActions);
             } else {
                 consumed = scene_manager_search_and_switch_to_previous_scene(
                     nfc->scene_manager, NfcSceneStart);

+ 4 - 5
applications/nfc/scenes/nfc_scene_emulate_uid.c

@@ -8,11 +8,12 @@ enum {
     NfcSceneEmulateUidStateTextBox,
 };
 
-void nfc_emulate_uid_worker_callback(NfcWorkerEvent event, void* context) {
+bool nfc_emulate_uid_worker_callback(NfcWorkerEvent event, void* context) {
     UNUSED(event);
     furi_assert(context);
     Nfc* nfc = context;
     view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventWorkerExit);
+    return true;
 }
 
 void nfc_scene_emulate_uid_widget_callback(GuiButtonType result, InputType type, void* context) {
@@ -76,7 +77,7 @@ void nfc_scene_emulate_uid_on_enter(void* context) {
     memset(&nfc->dev->dev_data.reader_data, 0, sizeof(NfcReaderRequestData));
     nfc_worker_start(
         nfc->worker,
-        NfcWorkerStateEmulate,
+        NfcWorkerStateUidEmulate,
         &nfc->dev->dev_data,
         nfc_emulate_uid_worker_callback,
         nfc);
@@ -90,9 +91,7 @@ bool nfc_scene_emulate_uid_on_event(void* context, SceneManagerEvent event) {
     uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneEmulateUid);
     bool consumed = false;
 
-    if(event.type == SceneManagerEventTypeTick) {
-        consumed = true;
-    } else if(event.type == SceneManagerEventTypeCustom) {
+    if(event.type == SceneManagerEventTypeCustom) {
         if(event.event == NfcCustomEventWorkerExit) {
             // Add data button to widget if data is received for the first time
             if(!string_size(nfc->text_box_store)) {

+ 10 - 26
applications/nfc/scenes/nfc_scene_read_emv_data_success.c → applications/nfc/scenes/nfc_scene_emv_read_success.c

@@ -2,7 +2,7 @@
 #include "../helpers/nfc_emv_parser.h"
 #include <dolphin/dolphin.h>
 
-void nfc_scene_read_emv_data_success_widget_callback(
+void nfc_scene_emv_read_success_widget_callback(
     GuiButtonType result,
     InputType type,
     void* context) {
@@ -12,7 +12,7 @@ void nfc_scene_read_emv_data_success_widget_callback(
     }
 }
 
-void nfc_scene_read_emv_data_success_on_enter(void* context) {
+void nfc_scene_emv_read_success_on_enter(void* context) {
     Nfc* nfc = context;
     EmvData* emv_data = &nfc->dev->dev_data.emv_data;
     FuriHalNfcDevData* nfc_data = &nfc->dev->dev_data.nfc_data;
@@ -23,17 +23,9 @@ void nfc_scene_read_emv_data_success_on_enter(void* context) {
     widget_add_frame_element(nfc->widget, 0, 0, 128, 64, 6);
     // Add buttons
     widget_add_button_element(
-        nfc->widget,
-        GuiButtonTypeLeft,
-        "Back",
-        nfc_scene_read_emv_data_success_widget_callback,
-        nfc);
+        nfc->widget, GuiButtonTypeLeft, "Retry", nfc_scene_emv_read_success_widget_callback, nfc);
     widget_add_button_element(
-        nfc->widget,
-        GuiButtonTypeRight,
-        "Save",
-        nfc_scene_read_emv_data_success_widget_callback,
-        nfc);
+        nfc->widget, GuiButtonTypeRight, "Save", nfc_scene_emv_read_success_widget_callback, nfc);
     // Add card name
     widget_add_string_element(
         nfc->widget, 64, 3, AlignCenter, AlignTop, FontSecondary, nfc->dev->dev_data.emv_data.name);
@@ -103,25 +95,17 @@ void nfc_scene_read_emv_data_success_on_enter(void* context) {
         widget_add_string_element(nfc->widget, 7, 32, AlignLeft, AlignTop, FontSecondary, exp_str);
     }
 
-    // Send notification
-    if(scene_manager_get_scene_state(nfc->scene_manager, NfcSceneReadEmvDataSuccess) ==
-       NFC_SEND_NOTIFICATION_TRUE) {
-        notification_message(nfc->notifications, &sequence_success);
-        scene_manager_set_scene_state(
-            nfc->scene_manager, NfcSceneReadEmvDataSuccess, NFC_SEND_NOTIFICATION_FALSE);
-    }
-
     view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget);
 }
 
-bool nfc_scene_read_emv_data_success_on_event(void* context, SceneManagerEvent event) {
+bool nfc_scene_emv_read_success_on_event(void* context, SceneManagerEvent event) {
     Nfc* nfc = context;
     bool consumed = false;
 
     if(event.type == SceneManagerEventTypeCustom) {
         if(event.event == GuiButtonTypeLeft) {
-            consumed = scene_manager_search_and_switch_to_previous_scene(
-                nfc->scene_manager, NfcSceneReadEmvAppSuccess);
+            scene_manager_next_scene(nfc->scene_manager, NfcSceneRetryConfirm);
+            consumed = true;
         } else if(event.event == GuiButtonTypeRight) {
             // Clear device name
             nfc_device_set_name(nfc->dev, "");
@@ -130,13 +114,13 @@ bool nfc_scene_read_emv_data_success_on_event(void* context, SceneManagerEvent e
             consumed = true;
         }
     } else if(event.type == SceneManagerEventTypeBack) {
-        consumed = scene_manager_search_and_switch_to_previous_scene(
-            nfc->scene_manager, NfcSceneReadEmvAppSuccess);
+        scene_manager_next_scene(nfc->scene_manager, NfcSceneExitConfirm);
+        consumed = true;
     }
     return consumed;
 }
 
-void nfc_scene_read_emv_data_success_on_exit(void* context) {
+void nfc_scene_emv_read_success_on_exit(void* context) {
     Nfc* nfc = context;
 
     // Clear view

+ 47 - 0
applications/nfc/scenes/nfc_scene_exit_confirm.c

@@ -0,0 +1,47 @@
+#include "../nfc_i.h"
+
+void nfc_scene_exit_confirm_dialog_callback(DialogExResult result, void* context) {
+    Nfc* nfc = context;
+
+    view_dispatcher_send_custom_event(nfc->view_dispatcher, result);
+}
+
+void nfc_scene_exit_confirm_on_enter(void* context) {
+    Nfc* nfc = context;
+    DialogEx* dialog_ex = nfc->dialog_ex;
+
+    dialog_ex_set_left_button_text(dialog_ex, "Exit");
+    dialog_ex_set_right_button_text(dialog_ex, "Stay");
+    dialog_ex_set_header(dialog_ex, "Exit to NFC menu?", 64, 11, AlignCenter, AlignTop);
+    dialog_ex_set_text(
+        dialog_ex, "All unsaved data\nwill be lost.", 64, 25, AlignCenter, AlignTop);
+    dialog_ex_set_context(dialog_ex, nfc);
+    dialog_ex_set_result_callback(dialog_ex, nfc_scene_exit_confirm_dialog_callback);
+
+    view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewDialogEx);
+}
+
+bool nfc_scene_exit_confirm_on_event(void* context, SceneManagerEvent event) {
+    Nfc* nfc = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == DialogExResultRight) {
+            consumed = scene_manager_previous_scene(nfc->scene_manager);
+        } else if(event.event == DialogExResultLeft) {
+            consumed = scene_manager_search_and_switch_to_previous_scene(
+                nfc->scene_manager, NfcSceneStart);
+        }
+    } else if(event.type == SceneManagerEventTypeBack) {
+        consumed = true;
+    }
+
+    return consumed;
+}
+
+void nfc_scene_exit_confirm_on_exit(void* context) {
+    Nfc* nfc = context;
+
+    // Clean view
+    dialog_ex_reset(nfc->dialog_ex);
+}

+ 48 - 0
applications/nfc/scenes/nfc_scene_extra_actions.c

@@ -0,0 +1,48 @@
+#include "../nfc_i.h"
+
+enum SubmenuIndex {
+    SubmenuIndexMfClassicKeys,
+};
+
+void nfc_scene_extra_actions_submenu_callback(void* context, uint32_t index) {
+    Nfc* nfc = context;
+
+    view_dispatcher_send_custom_event(nfc->view_dispatcher, index);
+}
+
+void nfc_scene_extra_actions_on_enter(void* context) {
+    Nfc* nfc = context;
+    Submenu* submenu = nfc->submenu;
+
+    submenu_add_item(
+        submenu,
+        "Mf Classic Keys",
+        SubmenuIndexMfClassicKeys,
+        nfc_scene_extra_actions_submenu_callback,
+        nfc);
+    view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu);
+}
+
+bool nfc_scene_extra_actions_on_event(void* context, SceneManagerEvent event) {
+    Nfc* nfc = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == SubmenuIndexMfClassicKeys) {
+            if(mf_classic_dict_check_presence(MfClassicDictTypeFlipper)) {
+                scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicKeys);
+            } else {
+                scene_manager_next_scene(nfc->scene_manager, NfcSceneDictNotFound);
+            }
+            consumed = true;
+        }
+        scene_manager_set_scene_state(nfc->scene_manager, NfcSceneExtraActions, event.event);
+    }
+    return consumed;
+}
+
+void nfc_scene_extra_actions_on_exit(void* context) {
+    Nfc* nfc = context;
+
+    submenu_reset(nfc->submenu);
+}

+ 137 - 0
applications/nfc/scenes/nfc_scene_mf_classic_dict_attack.c

@@ -0,0 +1,137 @@
+#include "../nfc_i.h"
+
+typedef enum {
+    DictAttackStateIdle,
+    DictAttackStateUserDictInProgress,
+    DictAttackStateFlipperDictInProgress,
+} DictAttackState;
+
+bool nfc_dict_attack_worker_callback(NfcWorkerEvent event, void* context) {
+    furi_assert(context);
+    Nfc* nfc = context;
+    view_dispatcher_send_custom_event(nfc->view_dispatcher, event);
+    return true;
+}
+
+void nfc_dict_attack_dict_attack_result_callback(void* context) {
+    furi_assert(context);
+    Nfc* nfc = context;
+    view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventDictAttackSkip);
+}
+
+static void nfc_scene_mf_classic_dict_attack_update_view(Nfc* nfc) {
+    MfClassicData* data = &nfc->dev->dev_data.mf_classic_data;
+    uint8_t sectors_read = 0;
+    uint8_t keys_found = 0;
+
+    // Calculate found keys and read sectors
+    mf_classic_get_read_sectors_and_keys(data, &sectors_read, &keys_found);
+    dict_attack_set_keys_found(nfc->dict_attack, keys_found);
+    dict_attack_set_sector_read(nfc->dict_attack, sectors_read);
+}
+
+static void nfc_scene_mf_classic_dict_attack_prepare_view(Nfc* nfc, DictAttackState state) {
+    MfClassicData* data = &nfc->dev->dev_data.mf_classic_data;
+    NfcWorkerState worker_state = NfcWorkerStateReady;
+
+    // Identify scene state
+    if(state == DictAttackStateIdle) {
+        if(mf_classic_dict_check_presence(MfClassicDictTypeUser)) {
+            state = DictAttackStateUserDictInProgress;
+        } else {
+            state = DictAttackStateFlipperDictInProgress;
+        }
+    } else if(state == DictAttackStateUserDictInProgress) {
+        state = DictAttackStateFlipperDictInProgress;
+    }
+
+    // Setup view
+    if(state == DictAttackStateUserDictInProgress) {
+        worker_state = NfcWorkerStateMfClassicUserDictAttack;
+        dict_attack_set_header(nfc->dict_attack, "Mf Classic User Dict.");
+    } else if(state == DictAttackStateFlipperDictInProgress) {
+        worker_state = NfcWorkerStateMfClassicFlipperDictAttack;
+        dict_attack_set_header(nfc->dict_attack, "Mf Classic Flipper Dict.");
+    }
+    scene_manager_set_scene_state(nfc->scene_manager, NfcSceneMfClassicDictAttack, state);
+    dict_attack_set_callback(nfc->dict_attack, nfc_dict_attack_dict_attack_result_callback, nfc);
+    dict_attack_set_current_sector(nfc->dict_attack, 0);
+    dict_attack_set_card_detected(nfc->dict_attack, data->type);
+    nfc_scene_mf_classic_dict_attack_update_view(nfc);
+    nfc_worker_start(
+        nfc->worker, worker_state, &nfc->dev->dev_data, nfc_dict_attack_worker_callback, nfc);
+}
+
+void nfc_scene_mf_classic_dict_attack_on_enter(void* context) {
+    Nfc* nfc = context;
+    nfc_scene_mf_classic_dict_attack_prepare_view(nfc, DictAttackStateIdle);
+    view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewDictAttack);
+    nfc_blink_start(nfc);
+}
+
+bool nfc_scene_mf_classic_dict_attack_on_event(void* context, SceneManagerEvent event) {
+    Nfc* nfc = context;
+    MfClassicData* data = &nfc->dev->dev_data.mf_classic_data;
+    bool consumed = false;
+
+    uint32_t state =
+        scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfClassicDictAttack);
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == NfcWorkerEventSuccess) {
+            if(state == DictAttackStateUserDictInProgress) {
+                nfc_worker_stop(nfc->worker);
+                nfc_scene_mf_classic_dict_attack_prepare_view(nfc, state);
+                consumed = true;
+            } else {
+                notification_message(nfc->notifications, &sequence_success);
+                scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicReadSuccess);
+                consumed = true;
+            }
+        } else if(event.event == NfcWorkerEventAborted) {
+            if(state == DictAttackStateUserDictInProgress) {
+                nfc_scene_mf_classic_dict_attack_prepare_view(nfc, state);
+                consumed = true;
+            } else {
+                notification_message(nfc->notifications, &sequence_success);
+                scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicReadSuccess);
+                consumed = true;
+            }
+        } else if(event.event == NfcWorkerEventCardDetected) {
+            dict_attack_set_card_detected(nfc->dict_attack, data->type);
+            consumed = true;
+        } else if(event.event == NfcWorkerEventNoCardDetected) {
+            dict_attack_set_card_removed(nfc->dict_attack);
+            consumed = true;
+        } else if(event.event == NfcWorkerEventFoundKeyA) {
+            dict_attack_inc_keys_found(nfc->dict_attack);
+            consumed = true;
+        } else if(event.event == NfcWorkerEventFoundKeyB) {
+            dict_attack_inc_keys_found(nfc->dict_attack);
+            consumed = true;
+        } else if(event.event == NfcWorkerEventNewSector) {
+            nfc_scene_mf_classic_dict_attack_update_view(nfc);
+            dict_attack_inc_current_sector(nfc->dict_attack);
+            consumed = true;
+        } else if(event.event == NfcCustomEventDictAttackSkip) {
+            if(state == DictAttackStateUserDictInProgress) {
+                nfc_worker_stop(nfc->worker);
+                consumed = true;
+            } else if(state == DictAttackStateFlipperDictInProgress) {
+                nfc_worker_stop(nfc->worker);
+                consumed = true;
+            }
+        }
+    } else if(event.type == SceneManagerEventTypeBack) {
+        scene_manager_next_scene(nfc->scene_manager, NfcSceneExitConfirm);
+        consumed = true;
+    }
+    return consumed;
+}
+
+void nfc_scene_mf_classic_dict_attack_on_exit(void* context) {
+    Nfc* nfc = context;
+    // Stop worker
+    nfc_worker_stop(nfc->worker);
+    dict_attack_reset(nfc->dict_attack);
+    nfc_blink_stop(nfc);
+}

+ 15 - 14
applications/nfc/scenes/nfc_scene_emulate_mifare_classic.c → applications/nfc/scenes/nfc_scene_mf_classic_emulate.c

@@ -4,51 +4,52 @@
 #define NFC_MF_CLASSIC_DATA_NOT_CHANGED (0UL)
 #define NFC_MF_CLASSIC_DATA_CHANGED (1UL)
 
-void nfc_emulate_mifare_classic_worker_callback(NfcWorkerEvent event, void* context) {
+bool nfc_mf_classic_emulate_worker_callback(NfcWorkerEvent event, void* context) {
     UNUSED(event);
     Nfc* nfc = context;
 
     scene_manager_set_scene_state(
-        nfc->scene_manager, NfcSceneEmulateMifareClassic, NFC_MF_CLASSIC_DATA_CHANGED);
+        nfc->scene_manager, NfcSceneMfClassicEmulate, NFC_MF_CLASSIC_DATA_CHANGED);
+    return true;
 }
 
-void nfc_scene_emulate_mifare_classic_on_enter(void* context) {
+void nfc_scene_mf_classic_emulate_on_enter(void* context) {
     Nfc* nfc = context;
     DOLPHIN_DEED(DolphinDeedNfcEmulate);
 
     // Setup view
     Popup* popup = nfc->popup;
     if(strcmp(nfc->dev->dev_name, "")) {
-        nfc_text_store_set(nfc, "%s", nfc->dev->dev_name);
+        nfc_text_store_set(nfc, "Emulating\n%s", nfc->dev->dev_name);
+    } else {
+        nfc_text_store_set(nfc, "Emulating\nMf Classic", nfc->dev->dev_name);
     }
     popup_set_icon(popup, 0, 3, &I_RFIDDolphinSend_97x61);
-    popup_set_header(popup, "Emulating\nMf Classic", 56, 31, AlignLeft, AlignTop);
+    popup_set_header(popup, nfc->text_store, 56, 31, AlignLeft, AlignTop);
 
     // Setup and start worker
     view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup);
     nfc_worker_start(
         nfc->worker,
-        NfcWorkerStateEmulateMifareClassic,
+        NfcWorkerStateMfClassicEmulate,
         &nfc->dev->dev_data,
-        nfc_emulate_mifare_classic_worker_callback,
+        nfc_mf_classic_emulate_worker_callback,
         nfc);
     nfc_blink_start(nfc);
 }
 
-bool nfc_scene_emulate_mifare_classic_on_event(void* context, SceneManagerEvent event) {
+bool nfc_scene_mf_classic_emulate_on_event(void* context, SceneManagerEvent event) {
     Nfc* nfc = context;
     bool consumed = false;
 
-    if(event.type == SceneManagerEventTypeTick) {
-        consumed = true;
-    } else if(event.type == SceneManagerEventTypeBack) {
+    if(event.type == SceneManagerEventTypeBack) {
         // Stop worker
         nfc_worker_stop(nfc->worker);
         // Check if data changed and save in shadow file
-        if(scene_manager_get_scene_state(nfc->scene_manager, NfcSceneEmulateMifareClassic) ==
+        if(scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfClassicEmulate) ==
            NFC_MF_CLASSIC_DATA_CHANGED) {
             scene_manager_set_scene_state(
-                nfc->scene_manager, NfcSceneEmulateMifareClassic, NFC_MF_CLASSIC_DATA_NOT_CHANGED);
+                nfc->scene_manager, NfcSceneMfClassicEmulate, NFC_MF_CLASSIC_DATA_NOT_CHANGED);
             nfc_device_save_shadow(nfc->dev, nfc->dev->dev_name);
         }
         consumed = false;
@@ -56,7 +57,7 @@ bool nfc_scene_emulate_mifare_classic_on_event(void* context, SceneManagerEvent
     return consumed;
 }
 
-void nfc_scene_emulate_mifare_classic_on_exit(void* context) {
+void nfc_scene_mf_classic_emulate_on_exit(void* context) {
     Nfc* nfc = context;
 
     // Clear view

+ 59 - 0
applications/nfc/scenes/nfc_scene_mf_classic_keys.c

@@ -0,0 +1,59 @@
+#include "../nfc_i.h"
+
+void nfc_scene_mf_classic_keys_widget_callback(GuiButtonType result, InputType type, void* context) {
+    Nfc* nfc = context;
+    if(type == InputTypeShort) {
+        view_dispatcher_send_custom_event(nfc->view_dispatcher, result);
+    }
+}
+
+void nfc_scene_mf_classic_keys_on_enter(void* context) {
+    Nfc* nfc = context;
+
+    // Load flipper dict keys total
+    uint32_t flipper_dict_keys_total = 0;
+    MfClassicDict* dict = mf_classic_dict_alloc(MfClassicDictTypeFlipper);
+    if(dict) {
+        flipper_dict_keys_total = mf_classic_dict_get_total_keys(dict);
+        mf_classic_dict_free(dict);
+    }
+    // Load user dict keys total
+    uint32_t user_dict_keys_total = 0;
+    dict = mf_classic_dict_alloc(MfClassicDictTypeUser);
+    if(dict) {
+        user_dict_keys_total = mf_classic_dict_get_total_keys(dict);
+        mf_classic_dict_free(dict);
+    }
+
+    widget_add_string_element(
+        nfc->widget, 0, 0, AlignLeft, AlignTop, FontPrimary, "MF Classic Keys");
+    char temp_str[32];
+    snprintf(temp_str, sizeof(temp_str), "Flipper dict: %ld", flipper_dict_keys_total);
+    widget_add_string_element(nfc->widget, 0, 20, AlignLeft, AlignTop, FontSecondary, temp_str);
+    snprintf(temp_str, sizeof(temp_str), "User dict: %ld", user_dict_keys_total);
+    widget_add_string_element(nfc->widget, 0, 32, AlignLeft, AlignTop, FontSecondary, temp_str);
+    widget_add_button_element(
+        nfc->widget, GuiButtonTypeCenter, "Add", nfc_scene_mf_classic_keys_widget_callback, nfc);
+
+    view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget);
+}
+
+bool nfc_scene_mf_classic_keys_on_event(void* context, SceneManagerEvent event) {
+    Nfc* nfc = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == GuiButtonTypeCenter) {
+            scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicKeysAdd);
+            consumed = true;
+        }
+    }
+
+    return consumed;
+}
+
+void nfc_scene_mf_classic_keys_on_exit(void* context) {
+    Nfc* nfc = context;
+
+    widget_reset(nfc->widget);
+}

+ 57 - 0
applications/nfc/scenes/nfc_scene_mf_classic_keys_add.c

@@ -0,0 +1,57 @@
+#include "../nfc_i.h"
+
+void nfc_scene_mf_classic_keys_add_byte_input_callback(void* context) {
+    Nfc* nfc = context;
+
+    view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventByteInputDone);
+}
+
+void nfc_scene_mf_classic_keys_add_on_enter(void* context) {
+    Nfc* nfc = context;
+
+    // Setup view
+    ByteInput* byte_input = nfc->byte_input;
+    byte_input_set_header_text(byte_input, "Enter the key in hex");
+    byte_input_set_result_callback(
+        byte_input,
+        nfc_scene_mf_classic_keys_add_byte_input_callback,
+        NULL,
+        nfc,
+        nfc->byte_input_store,
+        6);
+    view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewByteInput);
+}
+
+bool nfc_scene_mf_classic_keys_add_on_event(void* context, SceneManagerEvent event) {
+    Nfc* nfc = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == NfcCustomEventByteInputDone) {
+            // Add key to dict
+            bool key_added = false;
+            MfClassicDict* dict = mf_classic_dict_alloc(MfClassicDictTypeUser);
+            if(dict) {
+                if(mf_classic_dict_add_key(dict, nfc->byte_input_store)) {
+                    key_added = true;
+                }
+            }
+            if(key_added) {
+                scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveSuccess);
+            } else {
+                scene_manager_next_scene(nfc->scene_manager, NfcSceneDictNotFound);
+            }
+            mf_classic_dict_free(dict);
+            consumed = true;
+        }
+    }
+    return consumed;
+}
+
+void nfc_scene_mf_classic_keys_add_on_exit(void* context) {
+    Nfc* nfc = context;
+
+    // Clear view
+    byte_input_set_result_callback(nfc->byte_input, NULL, NULL, NULL, NULL, 0);
+    byte_input_set_header_text(nfc->byte_input, "");
+}

+ 11 - 11
applications/nfc/scenes/nfc_scene_mifare_ul_menu.c → applications/nfc/scenes/nfc_scene_mf_classic_menu.c

@@ -5,43 +5,43 @@ enum SubmenuIndex {
     SubmenuIndexEmulate,
 };
 
-void nfc_scene_mifare_ul_menu_submenu_callback(void* context, uint32_t index) {
+void nfc_scene_mf_classic_menu_submenu_callback(void* context, uint32_t index) {
     Nfc* nfc = context;
 
     view_dispatcher_send_custom_event(nfc->view_dispatcher, index);
 }
 
-void nfc_scene_mifare_ul_menu_on_enter(void* context) {
+void nfc_scene_mf_classic_menu_on_enter(void* context) {
     Nfc* nfc = context;
     Submenu* submenu = nfc->submenu;
 
     submenu_add_item(
-        submenu, "Save", SubmenuIndexSave, nfc_scene_mifare_ul_menu_submenu_callback, nfc);
+        submenu, "Save", SubmenuIndexSave, nfc_scene_mf_classic_menu_submenu_callback, nfc);
     submenu_add_item(
-        submenu, "Emulate", SubmenuIndexEmulate, nfc_scene_mifare_ul_menu_submenu_callback, nfc);
+        submenu, "Emulate", SubmenuIndexEmulate, nfc_scene_mf_classic_menu_submenu_callback, nfc);
     submenu_set_selected_item(
-        nfc->submenu, scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMifareUlMenu));
+        nfc->submenu, scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfClassicMenu));
 
     view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu);
 }
 
-bool nfc_scene_mifare_ul_menu_on_event(void* context, SceneManagerEvent event) {
+bool nfc_scene_mf_classic_menu_on_event(void* context, SceneManagerEvent event) {
     Nfc* nfc = context;
     bool consumed = false;
 
     if(event.type == SceneManagerEventTypeCustom) {
         if(event.event == SubmenuIndexSave) {
             scene_manager_set_scene_state(
-                nfc->scene_manager, NfcSceneMifareUlMenu, SubmenuIndexSave);
-            nfc->dev->format = NfcDeviceSaveFormatMifareUl;
+                nfc->scene_manager, NfcSceneMfClassicMenu, SubmenuIndexSave);
+            nfc->dev->format = NfcDeviceSaveFormatMifareClassic;
             // Clear device name
             nfc_device_set_name(nfc->dev, "");
             scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveName);
             consumed = true;
         } else if(event.event == SubmenuIndexEmulate) {
             scene_manager_set_scene_state(
-                nfc->scene_manager, NfcSceneMifareUlMenu, SubmenuIndexEmulate);
-            scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateMifareUl);
+                nfc->scene_manager, NfcSceneMfClassicMenu, SubmenuIndexEmulate);
+            scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicEmulate);
             consumed = true;
         }
     } else if(event.type == SceneManagerEventTypeBack) {
@@ -52,7 +52,7 @@ bool nfc_scene_mifare_ul_menu_on_event(void* context, SceneManagerEvent event) {
     return consumed;
 }
 
-void nfc_scene_mifare_ul_menu_on_exit(void* context) {
+void nfc_scene_mf_classic_menu_on_exit(void* context) {
     Nfc* nfc = context;
 
     // Clear view

+ 104 - 0
applications/nfc/scenes/nfc_scene_mf_classic_read_success.c

@@ -0,0 +1,104 @@
+#include "../nfc_i.h"
+#include <dolphin/dolphin.h>
+
+void nfc_scene_mf_classic_read_success_widget_callback(
+    GuiButtonType result,
+    InputType type,
+    void* context) {
+    furi_assert(context);
+    Nfc* nfc = context;
+
+    if(type == InputTypeShort) {
+        view_dispatcher_send_custom_event(nfc->view_dispatcher, result);
+    }
+}
+
+void nfc_scene_mf_classic_read_success_on_enter(void* context) {
+    Nfc* nfc = context;
+    NfcDeviceData* dev_data = &nfc->dev->dev_data;
+    MfClassicData* mf_data = &dev_data->mf_classic_data;
+    string_t str_tmp;
+    string_init(str_tmp);
+
+    DOLPHIN_DEED(DolphinDeedNfcReadSuccess);
+
+    // Setup view
+    Widget* widget = nfc->widget;
+    widget_add_button_element(
+        widget, GuiButtonTypeLeft, "Retry", nfc_scene_mf_classic_read_success_widget_callback, nfc);
+    widget_add_button_element(
+        widget, GuiButtonTypeRight, "More", nfc_scene_mf_classic_read_success_widget_callback, nfc);
+
+    if(string_size(nfc->dev->dev_data.parsed_data)) {
+        widget_add_text_box_element(
+            nfc->widget,
+            0,
+            0,
+            128,
+            32,
+            AlignLeft,
+            AlignTop,
+            string_get_cstr(nfc->dev->dev_data.parsed_data),
+            true);
+    } else {
+        widget_add_string_element(
+            widget,
+            0,
+            0,
+            AlignLeft,
+            AlignTop,
+            FontSecondary,
+            mf_classic_get_type_str(mf_data->type));
+        widget_add_string_element(
+            widget, 0, 11, AlignLeft, AlignTop, FontSecondary, "ISO 14443-3 (Type A)");
+        string_printf(str_tmp, "UID:");
+        for(size_t i = 0; i < dev_data->nfc_data.uid_len; i++) {
+            string_cat_printf(str_tmp, " %02X", dev_data->nfc_data.uid[i]);
+        }
+        widget_add_string_element(
+            widget, 0, 22, AlignLeft, AlignTop, FontSecondary, string_get_cstr(str_tmp));
+        uint8_t sectors_total = mf_classic_get_total_sectors_num(mf_data->type);
+        uint8_t keys_total = sectors_total * 2;
+        uint8_t keys_found = 0;
+        uint8_t sectors_read = 0;
+        mf_classic_get_read_sectors_and_keys(mf_data, &sectors_read, &keys_found);
+        string_printf(str_tmp, "Keys Found: %d/%d", keys_found, keys_total);
+        widget_add_string_element(
+            widget, 0, 33, AlignLeft, AlignTop, FontSecondary, string_get_cstr(str_tmp));
+        string_printf(str_tmp, "Sectors Read: %d/%d", sectors_read, sectors_total);
+        widget_add_string_element(
+            widget, 0, 44, AlignLeft, AlignTop, FontSecondary, string_get_cstr(str_tmp));
+    }
+
+    string_clear(str_tmp);
+    view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget);
+}
+
+bool nfc_scene_mf_classic_read_success_on_event(void* context, SceneManagerEvent event) {
+    Nfc* nfc = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == GuiButtonTypeLeft) {
+            scene_manager_next_scene(nfc->scene_manager, NfcSceneRetryConfirm);
+            consumed = true;
+        } else if(event.event == GuiButtonTypeRight) {
+            // Clear device name
+            nfc_device_set_name(nfc->dev, "");
+            scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicMenu);
+            consumed = true;
+        }
+    } else if(event.type == SceneManagerEventTypeBack) {
+        scene_manager_next_scene(nfc->scene_manager, NfcSceneExitConfirm);
+        consumed = true;
+    }
+
+    return consumed;
+}
+
+void nfc_scene_mf_classic_read_success_on_exit(void* context) {
+    Nfc* nfc = context;
+
+    // Clear view
+    widget_reset(nfc->widget);
+}

+ 15 - 21
applications/nfc/scenes/nfc_scene_mifare_desfire_app.c → applications/nfc/scenes/nfc_scene_mf_desfire_app.c

@@ -1,15 +1,15 @@
 #include "../nfc_i.h"
 
-#define TAG "NfcSceneMifareDesfireApp"
+#define TAG "NfcSceneMfDesfireApp"
 
 enum SubmenuIndex {
     SubmenuIndexAppInfo,
     SubmenuIndexDynamic, // dynamic indexes start here
 };
 
-MifareDesfireApplication* nfc_scene_mifare_desfire_app_get_app(Nfc* nfc) {
-    uint32_t app_idx =
-        scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMifareDesfireApp) >> 1;
+MifareDesfireApplication* nfc_scene_mf_desfire_app_get_app(Nfc* nfc) {
+    uint32_t app_idx = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfDesfireApp) >>
+                       1;
     MifareDesfireApplication* app = nfc->dev->dev_data.mf_df_data.app_head;
     for(uint32_t i = 0; i < app_idx && app; i++) {
         app = app->next;
@@ -17,16 +17,16 @@ MifareDesfireApplication* nfc_scene_mifare_desfire_app_get_app(Nfc* nfc) {
     return app;
 }
 
-void nfc_scene_mifare_desfire_app_submenu_callback(void* context, uint32_t index) {
+void nfc_scene_mf_desfire_app_submenu_callback(void* context, uint32_t index) {
     Nfc* nfc = context;
 
     view_dispatcher_send_custom_event(nfc->view_dispatcher, index);
 }
 
-void nfc_scene_mifare_desfire_app_on_enter(void* context) {
+void nfc_scene_mf_desfire_app_on_enter(void* context) {
     Nfc* nfc = context;
     Submenu* submenu = nfc->submenu;
-    MifareDesfireApplication* app = nfc_scene_mifare_desfire_app_get_app(nfc);
+    MifareDesfireApplication* app = nfc_scene_mf_desfire_app_get_app(nfc);
     if(!app) {
         popup_set_icon(nfc->popup, 5, 5, &I_WarningDolphin_45x42);
         popup_set_header(nfc->popup, "Internal Error!", 55, 12, AlignLeft, AlignBottom);
@@ -45,11 +45,7 @@ void nfc_scene_mifare_desfire_app_on_enter(void* context) {
     text_box_set_font(nfc->text_box, TextBoxFontHex);
 
     submenu_add_item(
-        submenu,
-        "App info",
-        SubmenuIndexAppInfo,
-        nfc_scene_mifare_desfire_app_submenu_callback,
-        nfc);
+        submenu, "App info", SubmenuIndexAppInfo, nfc_scene_mf_desfire_app_submenu_callback, nfc);
 
     uint16_t cap = NFC_TEXT_STORE_SIZE;
     char* buf = nfc->text_store;
@@ -65,20 +61,19 @@ void nfc_scene_mifare_desfire_app_on_enter(void* context) {
         char* label = buf;
         cap -= size + 1;
         buf += size + 1;
-        submenu_add_item(
-            submenu, label, idx++, nfc_scene_mifare_desfire_app_submenu_callback, nfc);
+        submenu_add_item(submenu, label, idx++, nfc_scene_mf_desfire_app_submenu_callback, nfc);
     }
 
     view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu);
 }
 
-bool nfc_scene_mifare_desfire_app_on_event(void* context, SceneManagerEvent event) {
+bool nfc_scene_mf_desfire_app_on_event(void* context, SceneManagerEvent event) {
     Nfc* nfc = context;
     bool consumed = false;
-    uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMifareDesfireApp);
+    uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfDesfireApp);
 
     if(event.type == SceneManagerEventTypeCustom) {
-        MifareDesfireApplication* app = nfc_scene_mifare_desfire_app_get_app(nfc);
+        MifareDesfireApplication* app = nfc_scene_mf_desfire_app_get_app(nfc);
         TextBox* text_box = nfc->text_box;
         string_reset(nfc->text_box_store);
         if(event.event == SubmenuIndexAppInfo) {
@@ -95,14 +90,13 @@ bool nfc_scene_mifare_desfire_app_on_event(void* context, SceneManagerEvent even
             mf_df_cat_file(file, nfc->text_box_store);
         }
         text_box_set_text(text_box, string_get_cstr(nfc->text_box_store));
-        scene_manager_set_scene_state(nfc->scene_manager, NfcSceneMifareDesfireApp, state | 1);
+        scene_manager_set_scene_state(nfc->scene_manager, NfcSceneMfDesfireApp, state | 1);
         view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextBox);
         consumed = true;
     } else if(event.type == SceneManagerEventTypeBack) {
         if(state & 1) {
             view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu);
-            scene_manager_set_scene_state(
-                nfc->scene_manager, NfcSceneMifareDesfireApp, state & ~1);
+            scene_manager_set_scene_state(nfc->scene_manager, NfcSceneMfDesfireApp, state & ~1);
             consumed = true;
         }
     }
@@ -110,7 +104,7 @@ bool nfc_scene_mifare_desfire_app_on_event(void* context, SceneManagerEvent even
     return consumed;
 }
 
-void nfc_scene_mifare_desfire_app_on_exit(void* context) {
+void nfc_scene_mf_desfire_app_on_exit(void* context) {
     Nfc* nfc = context;
 
     // Clear views

+ 15 - 17
applications/nfc/scenes/nfc_scene_mifare_desfire_data.c → applications/nfc/scenes/nfc_scene_mf_desfire_data.c

@@ -1,6 +1,6 @@
 #include "../nfc_i.h"
 
-#define TAG "NfcSceneMifareDesfireData"
+#define TAG "NfcSceneMfDesfireData"
 
 enum {
     MifareDesfireDataStateMenu,
@@ -12,16 +12,16 @@ enum SubmenuIndex {
     SubmenuIndexDynamic, // dynamic indexes start here
 };
 
-void nfc_scene_mifare_desfire_data_submenu_callback(void* context, uint32_t index) {
+void nfc_scene_mf_desfire_data_submenu_callback(void* context, uint32_t index) {
     Nfc* nfc = (Nfc*)context;
 
     view_dispatcher_send_custom_event(nfc->view_dispatcher, index);
 }
 
-void nfc_scene_mifare_desfire_data_on_enter(void* context) {
+void nfc_scene_mf_desfire_data_on_enter(void* context) {
     Nfc* nfc = context;
     Submenu* submenu = nfc->submenu;
-    uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMifareDesfireData);
+    uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfDesfireData);
     MifareDesfireData* data = &nfc->dev->dev_data.mf_df_data;
 
     text_box_set_font(nfc->text_box, TextBoxFontHex);
@@ -30,7 +30,7 @@ void nfc_scene_mifare_desfire_data_on_enter(void* context) {
         submenu,
         "Card info",
         SubmenuIndexCardInfo,
-        nfc_scene_mifare_desfire_data_submenu_callback,
+        nfc_scene_mf_desfire_data_submenu_callback,
         nfc);
 
     uint16_t cap = NFC_TEXT_STORE_SIZE;
@@ -46,24 +46,23 @@ void nfc_scene_mifare_desfire_data_on_enter(void* context) {
         char* label = buf;
         cap -= size + 1;
         buf += size + 1;
-        submenu_add_item(
-            submenu, label, idx++, nfc_scene_mifare_desfire_data_submenu_callback, nfc);
+        submenu_add_item(submenu, label, idx++, nfc_scene_mf_desfire_data_submenu_callback, nfc);
     }
 
     if(state >= MifareDesfireDataStateItem) {
         submenu_set_selected_item(
             nfc->submenu, state - MifareDesfireDataStateItem + SubmenuIndexDynamic);
         scene_manager_set_scene_state(
-            nfc->scene_manager, NfcSceneMifareDesfireData, MifareDesfireDataStateMenu);
+            nfc->scene_manager, NfcSceneMfDesfireData, MifareDesfireDataStateMenu);
     }
 
     view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu);
 }
 
-bool nfc_scene_mifare_desfire_data_on_event(void* context, SceneManagerEvent event) {
+bool nfc_scene_mf_desfire_data_on_event(void* context, SceneManagerEvent event) {
     Nfc* nfc = context;
     bool consumed = false;
-    uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMifareDesfireData);
+    uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfDesfireData);
     MifareDesfireData* data = &nfc->dev->dev_data.mf_df_data;
 
     if(event.type == SceneManagerEventTypeCustom) {
@@ -75,23 +74,22 @@ bool nfc_scene_mifare_desfire_data_on_event(void* context, SceneManagerEvent eve
             view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextBox);
             scene_manager_set_scene_state(
                 nfc->scene_manager,
-                NfcSceneMifareDesfireData,
+                NfcSceneMfDesfireData,
                 MifareDesfireDataStateItem + SubmenuIndexCardInfo);
             consumed = true;
         } else {
             uint16_t index = event.event - SubmenuIndexDynamic;
             scene_manager_set_scene_state(
-                nfc->scene_manager, NfcSceneMifareDesfireData, MifareDesfireDataStateItem + index);
-            scene_manager_set_scene_state(
-                nfc->scene_manager, NfcSceneMifareDesfireApp, index << 1);
-            scene_manager_next_scene(nfc->scene_manager, NfcSceneMifareDesfireApp);
+                nfc->scene_manager, NfcSceneMfDesfireData, MifareDesfireDataStateItem + index);
+            scene_manager_set_scene_state(nfc->scene_manager, NfcSceneMfDesfireApp, index << 1);
+            scene_manager_next_scene(nfc->scene_manager, NfcSceneMfDesfireApp);
             consumed = true;
         }
     } else if(event.type == SceneManagerEventTypeBack) {
         if(state >= MifareDesfireDataStateItem) {
             view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu);
             scene_manager_set_scene_state(
-                nfc->scene_manager, NfcSceneMifareDesfireData, MifareDesfireDataStateMenu);
+                nfc->scene_manager, NfcSceneMfDesfireData, MifareDesfireDataStateMenu);
             consumed = true;
         }
     }
@@ -99,7 +97,7 @@ bool nfc_scene_mifare_desfire_data_on_event(void* context, SceneManagerEvent eve
     return consumed;
 }
 
-void nfc_scene_mifare_desfire_data_on_exit(void* context) {
+void nfc_scene_mf_desfire_data_on_exit(void* context) {
     Nfc* nfc = context;
 
     // Clear views

+ 7 - 8
applications/nfc/scenes/nfc_scene_mifare_desfire_menu.c → applications/nfc/scenes/nfc_scene_mf_desfire_menu.c

@@ -4,33 +4,32 @@ enum SubmenuIndex {
     SubmenuIndexSave,
 };
 
-void nfc_scene_mifare_desfire_menu_submenu_callback(void* context, uint32_t index) {
+void nfc_scene_mf_desfire_menu_submenu_callback(void* context, uint32_t index) {
     Nfc* nfc = context;
 
     view_dispatcher_send_custom_event(nfc->view_dispatcher, index);
 }
 
-void nfc_scene_mifare_desfire_menu_on_enter(void* context) {
+void nfc_scene_mf_desfire_menu_on_enter(void* context) {
     Nfc* nfc = context;
     Submenu* submenu = nfc->submenu;
 
     submenu_add_item(
-        submenu, "Save", SubmenuIndexSave, nfc_scene_mifare_desfire_menu_submenu_callback, nfc);
+        submenu, "Save", SubmenuIndexSave, nfc_scene_mf_desfire_menu_submenu_callback, nfc);
     submenu_set_selected_item(
-        nfc->submenu,
-        scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMifareDesfireMenu));
+        nfc->submenu, scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfDesfireMenu));
 
     view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu);
 }
 
-bool nfc_scene_mifare_desfire_menu_on_event(void* context, SceneManagerEvent event) {
+bool nfc_scene_mf_desfire_menu_on_event(void* context, SceneManagerEvent event) {
     Nfc* nfc = context;
     bool consumed = false;
 
     if(event.type == SceneManagerEventTypeCustom) {
         if(event.event == SubmenuIndexSave) {
             scene_manager_set_scene_state(
-                nfc->scene_manager, NfcSceneMifareDesfireMenu, SubmenuIndexSave);
+                nfc->scene_manager, NfcSceneMfDesfireMenu, SubmenuIndexSave);
             nfc->dev->format = NfcDeviceSaveFormatMifareDesfire;
             // Clear device name
             nfc_device_set_name(nfc->dev, "");
@@ -42,7 +41,7 @@ bool nfc_scene_mifare_desfire_menu_on_event(void* context, SceneManagerEvent eve
     return consumed;
 }
 
-void nfc_scene_mifare_desfire_menu_on_exit(void* context) {
+void nfc_scene_mf_desfire_menu_on_exit(void* context) {
     Nfc* nfc = context;
 
     // Clear view

+ 22 - 23
applications/nfc/scenes/nfc_scene_read_mifare_desfire_success.c → applications/nfc/scenes/nfc_scene_mf_desfire_read_success.c

@@ -4,22 +4,22 @@
 #define NFC_SCENE_READ_SUCCESS_SHIFT "              "
 
 enum {
-    ReadMifareDesfireSuccessStateShowUID,
-    ReadMifareDesfireSuccessStateShowData,
+    MfDesfireReadSuccessStateShowUID,
+    MfDesfireReadSuccessStateShowData,
 };
 
-void nfc_scene_read_mifare_desfire_success_dialog_callback(DialogExResult result, void* context) {
+void nfc_scene_mf_desfire_read_success_dialog_callback(DialogExResult result, void* context) {
     Nfc* nfc = context;
 
     view_dispatcher_send_custom_event(nfc->view_dispatcher, result);
 }
 
-void nfc_scene_read_mifare_desfire_success_on_enter(void* context) {
+void nfc_scene_mf_desfire_read_success_on_enter(void* context) {
     Nfc* nfc = context;
 
     MifareDesfireData* data = &nfc->dev->dev_data.mf_df_data;
     DialogEx* dialog_ex = nfc->dialog_ex;
-    dialog_ex_set_left_button_text(dialog_ex, "Back");
+    dialog_ex_set_left_button_text(dialog_ex, "Retry");
     dialog_ex_set_center_button_text(dialog_ex, "Data");
     dialog_ex_set_right_button_text(dialog_ex, "More");
     dialog_ex_set_icon(dialog_ex, 8, 16, &I_Medium_chip_22x21);
@@ -55,41 +55,40 @@ void nfc_scene_read_mifare_desfire_success_on_enter(void* context) {
         n_files == 1 ? "" : "s");
     dialog_ex_set_text(dialog_ex, nfc->text_store, 8, 6, AlignLeft, AlignTop);
     dialog_ex_set_context(dialog_ex, nfc);
-    dialog_ex_set_result_callback(
-        dialog_ex, nfc_scene_read_mifare_desfire_success_dialog_callback);
+    dialog_ex_set_result_callback(dialog_ex, nfc_scene_mf_desfire_read_success_dialog_callback);
 
     scene_manager_set_scene_state(
-        nfc->scene_manager,
-        NfcSceneReadMifareDesfireSuccess,
-        ReadMifareDesfireSuccessStateShowUID);
+        nfc->scene_manager, NfcSceneMfDesfireReadSuccess, MfDesfireReadSuccessStateShowUID);
     view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewDialogEx);
 }
 
-bool nfc_scene_read_mifare_desfire_success_on_event(void* context, SceneManagerEvent event) {
+bool nfc_scene_mf_desfire_read_success_on_event(void* context, SceneManagerEvent event) {
     Nfc* nfc = context;
     bool consumed = false;
     uint32_t state =
-        scene_manager_get_scene_state(nfc->scene_manager, NfcSceneReadMifareDesfireSuccess);
+        scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfDesfireReadSuccess);
 
     if(event.type == SceneManagerEventTypeCustom) {
-        if(state == ReadMifareDesfireSuccessStateShowUID && event.event == DialogExResultLeft) {
-            scene_manager_previous_scene(nfc->scene_manager);
+        if(state == MfDesfireReadSuccessStateShowUID && event.event == DialogExResultLeft) {
+            scene_manager_next_scene(nfc->scene_manager, NfcSceneRetryConfirm);
             consumed = true;
-        } else if(
-            state == ReadMifareDesfireSuccessStateShowUID && event.event == DialogExResultCenter) {
-            scene_manager_next_scene(nfc->scene_manager, NfcSceneMifareDesfireData);
+        } else if(state == MfDesfireReadSuccessStateShowUID && event.event == DialogExResultCenter) {
+            scene_manager_next_scene(nfc->scene_manager, NfcSceneMfDesfireData);
             consumed = true;
-        } else if(state == ReadMifareDesfireSuccessStateShowUID && event.event == DialogExResultRight) {
-            scene_manager_next_scene(nfc->scene_manager, NfcSceneMifareDesfireMenu);
+        } else if(state == MfDesfireReadSuccessStateShowUID && event.event == DialogExResultRight) {
+            scene_manager_next_scene(nfc->scene_manager, NfcSceneMfDesfireMenu);
             consumed = true;
         }
     } else if(event.type == SceneManagerEventTypeBack) {
-        if(state == ReadMifareDesfireSuccessStateShowData) {
+        if(state == MfDesfireReadSuccessStateShowData) {
             view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewDialogEx);
             scene_manager_set_scene_state(
                 nfc->scene_manager,
-                NfcSceneReadMifareDesfireSuccess,
-                ReadMifareDesfireSuccessStateShowUID);
+                NfcSceneMfDesfireReadSuccess,
+                MfDesfireReadSuccessStateShowUID);
+            consumed = true;
+        } else {
+            scene_manager_next_scene(nfc->scene_manager, NfcSceneExitConfirm);
             consumed = true;
         }
     }
@@ -97,7 +96,7 @@ bool nfc_scene_read_mifare_desfire_success_on_event(void* context, SceneManagerE
     return consumed;
 }
 
-void nfc_scene_read_mifare_desfire_success_on_exit(void* context) {
+void nfc_scene_mf_desfire_read_success_on_exit(void* context) {
     Nfc* nfc = context;
 
     // Clean dialog

+ 15 - 14
applications/nfc/scenes/nfc_scene_emulate_mifare_ul.c → applications/nfc/scenes/nfc_scene_mf_ultralight_emulate.c

@@ -4,51 +4,52 @@
 #define NFC_MF_UL_DATA_NOT_CHANGED (0UL)
 #define NFC_MF_UL_DATA_CHANGED (1UL)
 
-void nfc_emulate_mifare_ul_worker_callback(NfcWorkerEvent event, void* context) {
+bool nfc_mf_ultralight_emulate_worker_callback(NfcWorkerEvent event, void* context) {
     UNUSED(event);
     Nfc* nfc = context;
 
     scene_manager_set_scene_state(
-        nfc->scene_manager, NfcSceneEmulateMifareUl, NFC_MF_UL_DATA_CHANGED);
+        nfc->scene_manager, NfcSceneMfUltralightEmulate, NFC_MF_UL_DATA_CHANGED);
+    return true;
 }
 
-void nfc_scene_emulate_mifare_ul_on_enter(void* context) {
+void nfc_scene_mf_ultralight_emulate_on_enter(void* context) {
     Nfc* nfc = context;
     DOLPHIN_DEED(DolphinDeedNfcEmulate);
 
     // Setup view
     Popup* popup = nfc->popup;
     if(strcmp(nfc->dev->dev_name, "")) {
-        nfc_text_store_set(nfc, "%s", nfc->dev->dev_name);
+        nfc_text_store_set(nfc, "Emulating\n%s", nfc->dev->dev_name);
+    } else {
+        nfc_text_store_set(nfc, "Emulating\nMf Ultralight", nfc->dev->dev_name);
     }
     popup_set_icon(popup, 0, 3, &I_RFIDDolphinSend_97x61);
-    popup_set_header(popup, "Emulating\nMf Ultralight", 56, 31, AlignLeft, AlignTop);
+    popup_set_header(popup, nfc->text_store, 56, 31, AlignLeft, AlignTop);
 
     // Setup and start worker
     view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup);
     nfc_worker_start(
         nfc->worker,
-        NfcWorkerStateEmulateMifareUltralight,
+        NfcWorkerStateMfUltralightEmulate,
         &nfc->dev->dev_data,
-        nfc_emulate_mifare_ul_worker_callback,
+        nfc_mf_ultralight_emulate_worker_callback,
         nfc);
     nfc_blink_start(nfc);
 }
 
-bool nfc_scene_emulate_mifare_ul_on_event(void* context, SceneManagerEvent event) {
+bool nfc_scene_mf_ultralight_emulate_on_event(void* context, SceneManagerEvent event) {
     Nfc* nfc = context;
     bool consumed = false;
 
-    if(event.type == SceneManagerEventTypeTick) {
-        consumed = true;
-    } else if(event.type == SceneManagerEventTypeBack) {
+    if(event.type == SceneManagerEventTypeBack) {
         // Stop worker
         nfc_worker_stop(nfc->worker);
         // Check if data changed and save in shadow file
-        if(scene_manager_get_scene_state(nfc->scene_manager, NfcSceneEmulateMifareUl) ==
+        if(scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfUltralightEmulate) ==
            NFC_MF_UL_DATA_CHANGED) {
             scene_manager_set_scene_state(
-                nfc->scene_manager, NfcSceneEmulateMifareUl, NFC_MF_UL_DATA_NOT_CHANGED);
+                nfc->scene_manager, NfcSceneMfUltralightEmulate, NFC_MF_UL_DATA_NOT_CHANGED);
             nfc_device_save_shadow(nfc->dev, nfc->dev->dev_name);
         }
         consumed = false;
@@ -56,7 +57,7 @@ bool nfc_scene_emulate_mifare_ul_on_event(void* context, SceneManagerEvent event
     return consumed;
 }
 
-void nfc_scene_emulate_mifare_ul_on_exit(void* context) {
+void nfc_scene_mf_ultralight_emulate_on_exit(void* context) {
     Nfc* nfc = context;
 
     // Clear view

+ 11 - 11
applications/nfc/scenes/nfc_scene_mifare_classic_menu.c → applications/nfc/scenes/nfc_scene_mf_ultralight_menu.c

@@ -5,47 +5,47 @@ enum SubmenuIndex {
     SubmenuIndexEmulate,
 };
 
-void nfc_scene_mifare_classic_menu_submenu_callback(void* context, uint32_t index) {
+void nfc_scene_mf_ultralight_menu_submenu_callback(void* context, uint32_t index) {
     Nfc* nfc = context;
 
     view_dispatcher_send_custom_event(nfc->view_dispatcher, index);
 }
 
-void nfc_scene_mifare_classic_menu_on_enter(void* context) {
+void nfc_scene_mf_ultralight_menu_on_enter(void* context) {
     Nfc* nfc = context;
     Submenu* submenu = nfc->submenu;
 
     submenu_add_item(
-        submenu, "Save", SubmenuIndexSave, nfc_scene_mifare_classic_menu_submenu_callback, nfc);
+        submenu, "Save", SubmenuIndexSave, nfc_scene_mf_ultralight_menu_submenu_callback, nfc);
     submenu_add_item(
         submenu,
         "Emulate",
         SubmenuIndexEmulate,
-        nfc_scene_mifare_classic_menu_submenu_callback,
+        nfc_scene_mf_ultralight_menu_submenu_callback,
         nfc);
     submenu_set_selected_item(
-        nfc->submenu, scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMifareUlMenu));
+        nfc->submenu, scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfUltralightMenu));
 
     view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu);
 }
 
-bool nfc_scene_mifare_classic_menu_on_event(void* context, SceneManagerEvent event) {
+bool nfc_scene_mf_ultralight_menu_on_event(void* context, SceneManagerEvent event) {
     Nfc* nfc = context;
     bool consumed = false;
 
     if(event.type == SceneManagerEventTypeCustom) {
         if(event.event == SubmenuIndexSave) {
             scene_manager_set_scene_state(
-                nfc->scene_manager, NfcSceneMifareUlMenu, SubmenuIndexSave);
-            nfc->dev->format = NfcDeviceSaveFormatMifareClassic;
+                nfc->scene_manager, NfcSceneMfUltralightMenu, SubmenuIndexSave);
+            nfc->dev->format = NfcDeviceSaveFormatMifareUl;
             // Clear device name
             nfc_device_set_name(nfc->dev, "");
             scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveName);
             consumed = true;
         } else if(event.event == SubmenuIndexEmulate) {
             scene_manager_set_scene_state(
-                nfc->scene_manager, NfcSceneMifareUlMenu, SubmenuIndexEmulate);
-            scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateMifareClassic);
+                nfc->scene_manager, NfcSceneMfUltralightMenu, SubmenuIndexEmulate);
+            scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightEmulate);
             consumed = true;
         }
     } else if(event.type == SceneManagerEventTypeBack) {
@@ -56,7 +56,7 @@ bool nfc_scene_mifare_classic_menu_on_event(void* context, SceneManagerEvent eve
     return consumed;
 }
 
-void nfc_scene_mifare_classic_menu_on_exit(void* context) {
+void nfc_scene_mf_ultralight_menu_on_exit(void* context) {
     Nfc* nfc = context;
 
     // Clear view

+ 14 - 14
applications/nfc/scenes/nfc_scene_read_mifare_ul_success.c → applications/nfc/scenes/nfc_scene_mf_ultralight_read_success.c

@@ -8,19 +8,16 @@ enum {
     ReadMifareUlStateShowData,
 };
 
-void nfc_scene_read_mifare_ul_success_dialog_callback(DialogExResult result, void* context) {
+void nfc_scene_mf_ultralight_read_success_dialog_callback(DialogExResult result, void* context) {
     Nfc* nfc = context;
 
     view_dispatcher_send_custom_event(nfc->view_dispatcher, result);
 }
 
-void nfc_scene_read_mifare_ul_success_on_enter(void* context) {
+void nfc_scene_mf_ultralight_read_success_on_enter(void* context) {
     Nfc* nfc = context;
     DOLPHIN_DEED(DolphinDeedNfcReadSuccess);
 
-    // Send notification
-    notification_message(nfc->notifications, &sequence_success);
-
     // Setup dialog view
     FuriHalNfcDevData* data = &nfc->dev->dev_data.nfc_data;
     MfUltralightData* mf_ul_data = &nfc->dev->dev_data.mf_ul_data;
@@ -48,7 +45,7 @@ void nfc_scene_read_mifare_ul_success_on_enter(void* context) {
         data->uid[6]);
     dialog_ex_set_text(dialog_ex, nfc->text_store, 8, 16, AlignLeft, AlignTop);
     dialog_ex_set_context(dialog_ex, nfc);
-    dialog_ex_set_result_callback(dialog_ex, nfc_scene_read_mifare_ul_success_dialog_callback);
+    dialog_ex_set_result_callback(dialog_ex, nfc_scene_mf_ultralight_read_success_dialog_callback);
 
     // Setup TextBox view
     TextBox* text_box = nfc->text_box;
@@ -63,34 +60,37 @@ void nfc_scene_read_mifare_ul_success_on_enter(void* context) {
     text_box_set_text(text_box, string_get_cstr(nfc->text_box_store));
 
     scene_manager_set_scene_state(
-        nfc->scene_manager, NfcSceneReadMifareUlSuccess, ReadMifareUlStateShowUID);
+        nfc->scene_manager, NfcSceneMfUltralightReadSuccess, ReadMifareUlStateShowUID);
     view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewDialogEx);
 }
 
-bool nfc_scene_read_mifare_ul_success_on_event(void* context, SceneManagerEvent event) {
+bool nfc_scene_mf_ultralight_read_success_on_event(void* context, SceneManagerEvent event) {
     Nfc* nfc = context;
     bool consumed = false;
     uint32_t state =
-        scene_manager_get_scene_state(nfc->scene_manager, NfcSceneReadMifareUlSuccess);
+        scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfUltralightReadSuccess);
 
     if(event.type == SceneManagerEventTypeCustom) {
         if(state == ReadMifareUlStateShowUID && event.event == DialogExResultLeft) {
-            scene_manager_previous_scene(nfc->scene_manager);
+            scene_manager_next_scene(nfc->scene_manager, NfcSceneRetryConfirm);
             consumed = true;
         } else if(state == ReadMifareUlStateShowUID && event.event == DialogExResultRight) {
-            scene_manager_next_scene(nfc->scene_manager, NfcSceneMifareUlMenu);
+            scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightMenu);
             consumed = true;
         } else if(state == ReadMifareUlStateShowUID && event.event == DialogExResultCenter) {
             view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextBox);
             scene_manager_set_scene_state(
-                nfc->scene_manager, NfcSceneReadMifareUlSuccess, ReadMifareUlStateShowData);
+                nfc->scene_manager, NfcSceneMfUltralightReadSuccess, ReadMifareUlStateShowData);
             consumed = true;
         }
     } else if(event.type == SceneManagerEventTypeBack) {
         if(state == ReadMifareUlStateShowData) {
             view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewDialogEx);
             scene_manager_set_scene_state(
-                nfc->scene_manager, NfcSceneReadMifareUlSuccess, ReadMifareUlStateShowUID);
+                nfc->scene_manager, NfcSceneMfUltralightReadSuccess, ReadMifareUlStateShowUID);
+            consumed = true;
+        } else {
+            scene_manager_next_scene(nfc->scene_manager, NfcSceneExitConfirm);
             consumed = true;
         }
     }
@@ -98,7 +98,7 @@ bool nfc_scene_read_mifare_ul_success_on_event(void* context, SceneManagerEvent
     return consumed;
 }
 
-void nfc_scene_read_mifare_ul_success_on_exit(void* context) {
+void nfc_scene_mf_ultralight_read_success_on_exit(void* context) {
     Nfc* nfc = context;
 
     // Clean views

+ 108 - 0
applications/nfc/scenes/nfc_scene_read.c

@@ -0,0 +1,108 @@
+#include "../nfc_i.h"
+#include <dolphin/dolphin.h>
+
+typedef enum {
+    NfcSceneReadStateIdle,
+    NfcSceneReadStateDetecting,
+    NfcSceneReadStateReading,
+} NfcSceneReadState;
+
+bool nfc_scene_read_worker_callback(NfcWorkerEvent event, void* context) {
+    Nfc* nfc = context;
+    bool consumed = false;
+    if(event == NfcWorkerEventReadMfClassicLoadKeyCache) {
+        consumed = nfc_device_load_key_cache(nfc->dev);
+    } else {
+        view_dispatcher_send_custom_event(nfc->view_dispatcher, event);
+        consumed = true;
+    }
+    return consumed;
+}
+
+void nfc_scene_read_set_state(Nfc* nfc, NfcSceneReadState state) {
+    uint32_t curr_state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneRead);
+    if(curr_state != state) {
+        if(state == NfcSceneReadStateDetecting) {
+            popup_set_header(nfc->popup, "Detecting\nNFC card", 90, 24, AlignCenter, AlignTop);
+            popup_set_icon(nfc->popup, 5, 7, &I_NFC_manual);
+        } else if(state == NfcSceneReadStateReading) {
+            popup_set_header(
+                nfc->popup, "Reading card\nDon't move...", 85, 24, AlignCenter, AlignTop);
+            popup_set_icon(nfc->popup, 19, 23, &A_Loading_24);
+        }
+        scene_manager_set_scene_state(nfc->scene_manager, NfcSceneRead, state);
+    }
+}
+
+void nfc_scene_read_on_enter(void* context) {
+    Nfc* nfc = context;
+    DOLPHIN_DEED(DolphinDeedNfcRead);
+
+    nfc_device_clear(nfc->dev);
+    // Setup view
+    nfc_scene_read_set_state(nfc, NfcSceneReadStateDetecting);
+    view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup);
+    // Start worker
+    nfc_worker_start(
+        nfc->worker, NfcWorkerStateRead, &nfc->dev->dev_data, nfc_scene_read_worker_callback, nfc);
+
+    nfc_blink_start(nfc);
+}
+
+bool nfc_scene_read_on_event(void* context, SceneManagerEvent event) {
+    Nfc* nfc = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if((event.event == NfcWorkerEventReadUidNfcB) ||
+           (event.event == NfcWorkerEventReadUidNfcF) ||
+           (event.event == NfcWorkerEventReadUidNfcV) ||
+           (event.event == NfcWorkerEventReadUidNfcA)) {
+            notification_message(nfc->notifications, &sequence_success);
+            scene_manager_next_scene(nfc->scene_manager, NfcSceneReadCardSuccess);
+            consumed = true;
+        } else if(event.event == NfcWorkerEventReadMfUltralight) {
+            notification_message(nfc->notifications, &sequence_success);
+            scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightReadSuccess);
+            consumed = true;
+        } else if(event.event == NfcWorkerEventReadMfClassicDone) {
+            notification_message(nfc->notifications, &sequence_success);
+            scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicReadSuccess);
+            consumed = true;
+        } else if(event.event == NfcWorkerEventReadMfDesfire) {
+            notification_message(nfc->notifications, &sequence_success);
+            scene_manager_next_scene(nfc->scene_manager, NfcSceneMfDesfireReadSuccess);
+            consumed = true;
+        } else if(event.event == NfcWorkerEventReadBankCard) {
+            notification_message(nfc->notifications, &sequence_success);
+            scene_manager_next_scene(nfc->scene_manager, NfcSceneEmvReadSuccess);
+            consumed = true;
+        } else if(event.event == NfcWorkerEventReadMfClassicDictAttackRequired) {
+            if(mf_classic_dict_check_presence(MfClassicDictTypeFlipper)) {
+                scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicDictAttack);
+            } else {
+                scene_manager_next_scene(nfc->scene_manager, NfcSceneDictNotFound);
+            }
+            consumed = true;
+        } else if(event.event == NfcWorkerEventCardDetected) {
+            nfc_scene_read_set_state(nfc, NfcSceneReadStateReading);
+            consumed = true;
+        } else if(event.event == NfcWorkerEventNoCardDetected) {
+            nfc_scene_read_set_state(nfc, NfcSceneReadStateDetecting);
+            consumed = true;
+        }
+    }
+    return consumed;
+}
+
+void nfc_scene_read_on_exit(void* context) {
+    Nfc* nfc = context;
+
+    // Stop worker
+    nfc_worker_stop(nfc->worker);
+    // Clear view
+    popup_reset(nfc->popup);
+    scene_manager_set_scene_state(nfc->scene_manager, NfcSceneRead, NfcSceneReadStateIdle);
+
+    nfc_blink_stop(nfc);
+}

+ 0 - 51
applications/nfc/scenes/nfc_scene_read_card.c

@@ -1,51 +0,0 @@
-#include "../nfc_i.h"
-#include <dolphin/dolphin.h>
-
-void nfc_read_card_worker_callback(NfcWorkerEvent event, void* context) {
-    UNUSED(event);
-    Nfc* nfc = context;
-    view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventWorkerExit);
-}
-
-void nfc_scene_read_card_on_enter(void* context) {
-    Nfc* nfc = context;
-    DOLPHIN_DEED(DolphinDeedNfcRead);
-
-    // Setup view
-    Popup* popup = nfc->popup;
-    popup_set_header(popup, "Detecting\nNFC card", 70, 34, AlignLeft, AlignTop);
-    popup_set_icon(popup, 0, 3, &I_RFIDDolphinReceive_97x61);
-
-    // Start worker
-    view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup);
-    nfc_worker_start(
-        nfc->worker, NfcWorkerStateDetect, &nfc->dev->dev_data, nfc_read_card_worker_callback, nfc);
-
-    nfc_blink_start(nfc);
-}
-
-bool nfc_scene_read_card_on_event(void* context, SceneManagerEvent event) {
-    Nfc* nfc = context;
-    bool consumed = false;
-
-    if(event.type == SceneManagerEventTypeCustom) {
-        if(event.event == NfcCustomEventWorkerExit) {
-            scene_manager_next_scene(nfc->scene_manager, NfcSceneReadCardSuccess);
-            consumed = true;
-        }
-    } else if(event.type == SceneManagerEventTypeTick) {
-        consumed = true;
-    }
-    return consumed;
-}
-
-void nfc_scene_read_card_on_exit(void* context) {
-    Nfc* nfc = context;
-
-    // Stop worker
-    nfc_worker_stop(nfc->worker);
-    // Clear view
-    popup_reset(nfc->popup);
-
-    nfc_blink_stop(nfc);
-}

+ 5 - 15
applications/nfc/scenes/nfc_scene_read_card_success.c

@@ -22,9 +22,6 @@ void nfc_scene_read_card_success_on_enter(void* context) {
     string_init(uid_str);
     DOLPHIN_DEED(DolphinDeedNfcReadSuccess);
 
-    // Send notification
-    notification_message(nfc->notifications, &sequence_success);
-
     // Setup view
     FuriHalNfcDevData* data = &nfc->dev->dev_data.nfc_data;
     Widget* widget = nfc->widget;
@@ -38,18 +35,12 @@ void nfc_scene_read_card_success_on_enter(void* context) {
         widget, GuiButtonTypeLeft, "Retry", nfc_scene_read_card_success_widget_callback, nfc);
     if(data->type == FuriHalNfcTypeA) {
         widget_add_button_element(
-            widget, GuiButtonTypeRight, "More", nfc_scene_read_card_success_widget_callback, nfc);
+            widget, GuiButtonTypeRight, "Save", nfc_scene_read_card_success_widget_callback, nfc);
         widget_add_icon_element(widget, 8, 13, &I_Medium_chip_22x21);
-        string_cat_printf(data_str, " may be:");
         widget_add_string_element(
             widget, 37, 12, AlignLeft, AlignBottom, FontPrimary, string_get_cstr(data_str));
         string_printf(
-            data_str,
-            "%s\nATQA: %02X%02X SAK: %02X",
-            nfc_guess_protocol(nfc->dev->dev_data.protocol),
-            data->atqa[0],
-            data->atqa[1],
-            data->sak);
+            data_str, "ATQA: %02X%02X\nSAK: %02X", data->atqa[0], data->atqa[1], data->sak);
         widget_add_string_multiline_element(
             widget, 37, 16, AlignLeft, AlignTop, FontSecondary, string_get_cstr(data_str));
         widget_add_string_element(
@@ -69,16 +60,15 @@ void nfc_scene_read_card_success_on_enter(void* context) {
 
 bool nfc_scene_read_card_success_on_event(void* context, SceneManagerEvent event) {
     Nfc* nfc = context;
-    FuriHalNfcDevData* data = &nfc->dev->dev_data.nfc_data;
     bool consumed = false;
 
     if(event.type == SceneManagerEventTypeCustom) {
         if(event.event == GuiButtonTypeLeft) {
             consumed = scene_manager_previous_scene(nfc->scene_manager);
-        } else if(data->type == FuriHalNfcTypeA && event.event == GuiButtonTypeRight) {
-            // Clear device name
+        } else if(event.event == GuiButtonTypeRight) {
+            nfc->dev->format = NfcDeviceSaveFormatUid;
             nfc_device_set_name(nfc->dev, "");
-            scene_manager_next_scene(nfc->scene_manager, NfcSceneCardMenu);
+            scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveName);
             consumed = true;
         }
     }

+ 0 - 58
applications/nfc/scenes/nfc_scene_read_emv_app.c

@@ -1,58 +0,0 @@
-#include "../nfc_i.h"
-#include <dolphin/dolphin.h>
-
-void nfc_read_emv_app_worker_callback(NfcWorkerEvent event, void* context) {
-    UNUSED(event);
-    Nfc* nfc = context;
-    view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventWorkerExit);
-}
-
-void nfc_scene_read_emv_app_on_enter(void* context) {
-    Nfc* nfc = context;
-    DOLPHIN_DEED(DolphinDeedNfcRead);
-
-    // Setup view
-    Popup* popup = nfc->popup;
-    popup_set_header(popup, "Reading\nbank card", 70, 34, AlignLeft, AlignTop);
-    popup_set_icon(popup, 0, 3, &I_RFIDDolphinReceive_97x61);
-
-    view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup);
-    // Start worker
-    nfc_worker_start(
-        nfc->worker,
-        NfcWorkerStateReadEMVApp,
-        &nfc->dev->dev_data,
-        nfc_read_emv_app_worker_callback,
-        nfc);
-    nfc_blink_start(nfc);
-}
-
-bool nfc_scene_read_emv_app_on_event(void* context, SceneManagerEvent event) {
-    Nfc* nfc = context;
-    bool consumed = false;
-
-    if(event.type == SceneManagerEventTypeCustom) {
-        if(event.event == NfcCustomEventWorkerExit) {
-            scene_manager_set_scene_state(
-                nfc->scene_manager, NfcSceneReadEmvAppSuccess, NFC_SEND_NOTIFICATION_TRUE);
-            scene_manager_next_scene(nfc->scene_manager, NfcSceneReadEmvAppSuccess);
-            consumed = true;
-        }
-    } else if(event.type == SceneManagerEventTypeTick) {
-        consumed = true;
-    }
-
-    return consumed;
-}
-
-void nfc_scene_read_emv_app_on_exit(void* context) {
-    Nfc* nfc = context;
-
-    // Stop worker
-    nfc_worker_stop(nfc->worker);
-
-    // Clear view
-    popup_reset(nfc->popup);
-
-    nfc_blink_stop(nfc);
-}

+ 0 - 83
applications/nfc/scenes/nfc_scene_read_emv_app_success.c

@@ -1,83 +0,0 @@
-#include "../nfc_i.h"
-#include "../helpers/nfc_emv_parser.h"
-#include <dolphin/dolphin.h>
-
-void nfc_scene_read_emv_app_widget_callback(GuiButtonType result, InputType type, void* context) {
-    Nfc* nfc = context;
-    if(type == InputTypeShort) {
-        view_dispatcher_send_custom_event(nfc->view_dispatcher, result);
-    }
-}
-
-void nfc_scene_read_emv_app_success_on_enter(void* context) {
-    Nfc* nfc = context;
-    DOLPHIN_DEED(DolphinDeedNfcReadSuccess);
-
-    // Setup view
-    FuriHalNfcDevData* nfc_data = &nfc->dev->dev_data.nfc_data;
-    EmvData* emv_data = &nfc->dev->dev_data.emv_data;
-    Widget* widget = nfc->widget;
-    widget_add_button_element(
-        widget, GuiButtonTypeLeft, "Retry", nfc_scene_read_emv_app_widget_callback, nfc);
-    widget_add_button_element(
-        widget, GuiButtonTypeRight, "Run app", nfc_scene_read_emv_app_widget_callback, nfc);
-    widget_add_string_element(widget, 36, 5, AlignLeft, AlignTop, FontPrimary, "Found EMV App");
-    widget_add_icon_element(widget, 8, 5, &I_Medium_chip_22x21);
-    // Display UID
-    string_t temp_str;
-    string_init_printf(temp_str, "UID:");
-    for(size_t i = 0; i < nfc_data->uid_len; i++) {
-        string_cat_printf(temp_str, " %02X", nfc_data->uid[i]);
-    }
-    widget_add_string_element(
-        widget, 36, 18, AlignLeft, AlignTop, FontSecondary, string_get_cstr(temp_str));
-    string_reset(temp_str);
-    // Display application
-    string_printf(temp_str, "App: ");
-    string_t aid;
-    string_init(aid);
-    bool aid_found =
-        nfc_emv_parser_get_aid_name(nfc->dev->storage, emv_data->aid, emv_data->aid_len, aid);
-    if(!aid_found) {
-        for(uint8_t i = 0; i < emv_data->aid_len; i++) {
-            string_cat_printf(aid, "%02X", emv_data->aid[i]);
-        }
-    }
-    string_cat(temp_str, aid);
-    widget_add_string_element(
-        widget, 7, 29, AlignLeft, AlignTop, FontSecondary, string_get_cstr(temp_str));
-    string_clear(temp_str);
-    string_clear(aid);
-
-    // Send notification
-    if(scene_manager_get_scene_state(nfc->scene_manager, NfcSceneReadEmvAppSuccess) ==
-       NFC_SEND_NOTIFICATION_TRUE) {
-        notification_message(nfc->notifications, &sequence_success);
-        scene_manager_set_scene_state(
-            nfc->scene_manager, NfcSceneReadEmvAppSuccess, NFC_SEND_NOTIFICATION_FALSE);
-    }
-
-    view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget);
-}
-
-bool nfc_scene_read_emv_app_success_on_event(void* context, SceneManagerEvent event) {
-    Nfc* nfc = context;
-    bool consumed = false;
-
-    if(event.type == SceneManagerEventTypeCustom) {
-        if(event.event == GuiButtonTypeLeft) {
-            consumed = scene_manager_previous_scene(nfc->scene_manager);
-        } else if(event.event == GuiButtonTypeRight) {
-            scene_manager_next_scene(nfc->scene_manager, NfcSceneRunEmvAppConfirm);
-            consumed = true;
-        }
-    }
-    return consumed;
-}
-
-void nfc_scene_read_emv_app_success_on_exit(void* context) {
-    Nfc* nfc = context;
-
-    // Clear views
-    widget_reset(nfc->widget);
-}

+ 0 - 59
applications/nfc/scenes/nfc_scene_read_emv_data.c

@@ -1,59 +0,0 @@
-#include "../nfc_i.h"
-#include <dolphin/dolphin.h>
-
-void nfc_read_emv_data_worker_callback(NfcWorkerEvent event, void* context) {
-    UNUSED(event);
-    Nfc* nfc = context;
-    view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventWorkerExit);
-}
-
-void nfc_scene_read_emv_data_on_enter(void* context) {
-    Nfc* nfc = context;
-    DOLPHIN_DEED(DolphinDeedNfcRead);
-
-    // Setup view
-    Popup* popup = nfc->popup;
-    popup_set_header(popup, "Reading\nbank card", 70, 34, AlignLeft, AlignTop);
-    popup_set_icon(popup, 0, 3, &I_RFIDDolphinReceive_97x61);
-
-    view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup);
-    // Clear emv data
-    memset(&nfc->dev->dev_data.emv_data, 0, sizeof(nfc->dev->dev_data.emv_data));
-    // Start worker
-    nfc_worker_start(
-        nfc->worker,
-        NfcWorkerStateReadEMVData,
-        &nfc->dev->dev_data,
-        nfc_read_emv_data_worker_callback,
-        nfc);
-
-    nfc_blink_start(nfc);
-}
-
-bool nfc_scene_read_emv_data_on_event(void* context, SceneManagerEvent event) {
-    Nfc* nfc = context;
-    bool consumed = false;
-
-    if(event.type == SceneManagerEventTypeCustom) {
-        if(event.event == NfcCustomEventWorkerExit) {
-            scene_manager_set_scene_state(
-                nfc->scene_manager, NfcSceneReadEmvDataSuccess, NFC_SEND_NOTIFICATION_TRUE);
-            scene_manager_next_scene(nfc->scene_manager, NfcSceneReadEmvDataSuccess);
-            consumed = true;
-        }
-    } else if(event.type == SceneManagerEventTypeTick) {
-        consumed = true;
-    }
-    return consumed;
-}
-
-void nfc_scene_read_emv_data_on_exit(void* context) {
-    Nfc* nfc = context;
-
-    // Stop worker
-    nfc_worker_stop(nfc->worker);
-    // Clear view
-    popup_reset(nfc->popup);
-
-    nfc_blink_stop(nfc);
-}

+ 0 - 96
applications/nfc/scenes/nfc_scene_read_mifare_classic.c

@@ -1,96 +0,0 @@
-#include "../nfc_i.h"
-
-enum {
-    NfcSceneReadMifareClassicStateInProgress,
-    NfcSceneReadMifareClassicStateDone,
-};
-
-void nfc_read_mifare_classic_worker_callback(NfcWorkerEvent event, void* context) {
-    furi_assert(context);
-    Nfc* nfc = context;
-    view_dispatcher_send_custom_event(nfc->view_dispatcher, event);
-}
-
-void nfc_read_mifare_classic_dict_attack_result_callback(void* context) {
-    furi_assert(context);
-    Nfc* nfc = context;
-    view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventDictAttackDone);
-}
-
-void nfc_scene_read_mifare_classic_on_enter(void* context) {
-    Nfc* nfc = context;
-
-    // Setup and start worker
-    memset(&nfc->dev->dev_data.mf_classic_data, 0, sizeof(MfClassicData));
-    dict_attack_set_result_callback(
-        nfc->dict_attack, nfc_read_mifare_classic_dict_attack_result_callback, nfc);
-    scene_manager_set_scene_state(
-        nfc->scene_manager, NfcSceneReadMifareClassic, NfcSceneReadMifareClassicStateInProgress);
-    view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewDictAttack);
-    nfc_worker_start(
-        nfc->worker,
-        NfcWorkerStateReadMifareClassic,
-        &nfc->dev->dev_data,
-        nfc_read_mifare_classic_worker_callback,
-        nfc);
-
-    nfc_blink_start(nfc);
-}
-
-bool nfc_scene_read_mifare_classic_on_event(void* context, SceneManagerEvent event) {
-    Nfc* nfc = context;
-    bool consumed = false;
-
-    if(event.type == SceneManagerEventTypeTick) {
-        consumed = true;
-    } else if(event.type == SceneManagerEventTypeCustom) {
-        if(event.event == NfcCustomEventDictAttackDone) {
-            scene_manager_next_scene(nfc->scene_manager, NfcSceneMifareClassicMenu);
-            consumed = true;
-        } else if(event.event == NfcWorkerEventDetectedClassic1k) {
-            dict_attack_card_detected(nfc->dict_attack, MfClassicType1k);
-            consumed = true;
-        } else if(event.event == NfcWorkerEventDetectedClassic4k) {
-            dict_attack_card_detected(nfc->dict_attack, MfClassicType4k);
-            consumed = true;
-        } else if(event.event == NfcWorkerEventNewSector) {
-            dict_attack_inc_curr_sector(nfc->dict_attack);
-            consumed = true;
-        } else if(event.event == NfcWorkerEventFoundKeyA) {
-            dict_attack_inc_found_key(nfc->dict_attack, MfClassicKeyA);
-            consumed = true;
-        } else if(event.event == NfcWorkerEventFoundKeyB) {
-            dict_attack_inc_found_key(nfc->dict_attack, MfClassicKeyB);
-            consumed = true;
-        } else if(event.event == NfcWorkerEventNoCardDetected) {
-            dict_attack_card_removed(nfc->dict_attack);
-            consumed = true;
-        } else if(event.event == NfcWorkerEventSuccess) {
-            scene_manager_set_scene_state(
-                nfc->scene_manager, NfcSceneReadMifareClassic, NfcSceneReadMifareClassicStateDone);
-            nfc_blink_stop(nfc);
-            notification_message(nfc->notifications, &sequence_success);
-            dict_attack_set_result(nfc->dict_attack, true);
-            consumed = true;
-        } else if(event.event == NfcWorkerEventFail) {
-            scene_manager_set_scene_state(
-                nfc->scene_manager, NfcSceneReadMifareClassic, NfcSceneReadMifareClassicStateDone);
-            nfc_blink_stop(nfc);
-            dict_attack_set_result(nfc->dict_attack, false);
-            consumed = true;
-        } else if(event.event == NfcWorkerEventNoDictFound) {
-            scene_manager_next_scene(nfc->scene_manager, NfcSceneDictNotFound);
-            consumed = true;
-        }
-    }
-    return consumed;
-}
-
-void nfc_scene_read_mifare_classic_on_exit(void* context) {
-    Nfc* nfc = context;
-    // Stop worker
-    nfc_worker_stop(nfc->worker);
-    dict_attack_reset(nfc->dict_attack);
-
-    nfc_blink_stop(nfc);
-}

+ 0 - 56
applications/nfc/scenes/nfc_scene_read_mifare_desfire.c

@@ -1,56 +0,0 @@
-#include "../nfc_i.h"
-#include <dolphin/dolphin.h>
-
-void nfc_read_mifare_desfire_worker_callback(NfcWorkerEvent event, void* context) {
-    UNUSED(event);
-    Nfc* nfc = context;
-    view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventWorkerExit);
-}
-
-void nfc_scene_read_mifare_desfire_on_enter(void* context) {
-    Nfc* nfc = context;
-    DOLPHIN_DEED(DolphinDeedNfcRead);
-
-    // Setup view
-    Popup* popup = nfc->popup;
-    popup_set_header(popup, "Reading\nDESFire", 70, 34, AlignLeft, AlignTop);
-    popup_set_icon(popup, 0, 3, &I_RFIDDolphinReceive_97x61);
-
-    view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup);
-    // Start worker
-    nfc_worker_start(
-        nfc->worker,
-        NfcWorkerStateReadMifareDesfire,
-        &nfc->dev->dev_data,
-        nfc_read_mifare_desfire_worker_callback,
-        nfc);
-    nfc_blink_start(nfc);
-}
-
-bool nfc_scene_read_mifare_desfire_on_event(void* context, SceneManagerEvent event) {
-    Nfc* nfc = context;
-    bool consumed = false;
-
-    if(event.type == SceneManagerEventTypeCustom) {
-        if(event.event == NfcCustomEventWorkerExit) {
-            notification_message(nfc->notifications, &sequence_success);
-            DOLPHIN_DEED(DolphinDeedNfcReadSuccess);
-            scene_manager_next_scene(nfc->scene_manager, NfcSceneReadMifareDesfireSuccess);
-            consumed = true;
-        }
-    } else if(event.type == SceneManagerEventTypeTick) {
-        consumed = true;
-    }
-    return consumed;
-}
-
-void nfc_scene_read_mifare_desfire_on_exit(void* context) {
-    Nfc* nfc = context;
-
-    // Stop worker
-    nfc_worker_stop(nfc->worker);
-    // Clear view
-    popup_reset(nfc->popup);
-
-    nfc_blink_stop(nfc);
-}

+ 0 - 54
applications/nfc/scenes/nfc_scene_read_mifare_ul.c

@@ -1,54 +0,0 @@
-#include "../nfc_i.h"
-#include <dolphin/dolphin.h>
-
-void nfc_read_mifare_ul_worker_callback(NfcWorkerEvent event, void* context) {
-    UNUSED(event);
-    Nfc* nfc = context;
-    view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventWorkerExit);
-}
-
-void nfc_scene_read_mifare_ul_on_enter(void* context) {
-    Nfc* nfc = context;
-    DOLPHIN_DEED(DolphinDeedNfcRead);
-
-    // Setup view
-    Popup* popup = nfc->popup;
-    popup_set_header(popup, "Detecting\nultralight", 70, 34, AlignLeft, AlignTop);
-    popup_set_icon(popup, 0, 3, &I_RFIDDolphinReceive_97x61);
-
-    view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup);
-    // Start worker
-    nfc_worker_start(
-        nfc->worker,
-        NfcWorkerStateReadMifareUltralight,
-        &nfc->dev->dev_data,
-        nfc_read_mifare_ul_worker_callback,
-        nfc);
-    nfc_blink_start(nfc);
-}
-
-bool nfc_scene_read_mifare_ul_on_event(void* context, SceneManagerEvent event) {
-    Nfc* nfc = context;
-    bool consumed = false;
-
-    if(event.type == SceneManagerEventTypeCustom) {
-        if(event.event == NfcCustomEventWorkerExit) {
-            scene_manager_next_scene(nfc->scene_manager, NfcSceneReadMifareUlSuccess);
-            consumed = true;
-        }
-    } else if(event.type == SceneManagerEventTypeTick) {
-        consumed = true;
-    }
-    return consumed;
-}
-
-void nfc_scene_read_mifare_ul_on_exit(void* context) {
-    Nfc* nfc = context;
-
-    // Stop worker
-    nfc_worker_stop(nfc->worker);
-    // Clear view
-    popup_reset(nfc->popup);
-
-    nfc_blink_stop(nfc);
-}

+ 47 - 0
applications/nfc/scenes/nfc_scene_retry_confirm.c

@@ -0,0 +1,47 @@
+#include "../nfc_i.h"
+
+void nfc_scene_retry_confirm_dialog_callback(DialogExResult result, void* context) {
+    Nfc* nfc = context;
+
+    view_dispatcher_send_custom_event(nfc->view_dispatcher, result);
+}
+
+void nfc_scene_retry_confirm_on_enter(void* context) {
+    Nfc* nfc = context;
+    DialogEx* dialog_ex = nfc->dialog_ex;
+
+    dialog_ex_set_left_button_text(dialog_ex, "Retry");
+    dialog_ex_set_right_button_text(dialog_ex, "Stay");
+    dialog_ex_set_header(dialog_ex, "Retry reading?", 64, 11, AlignCenter, AlignTop);
+    dialog_ex_set_text(
+        dialog_ex, "All unsaved data will be\nlost.", 64, 25, AlignCenter, AlignTop);
+    dialog_ex_set_context(dialog_ex, nfc);
+    dialog_ex_set_result_callback(dialog_ex, nfc_scene_retry_confirm_dialog_callback);
+
+    view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewDialogEx);
+}
+
+bool nfc_scene_retry_confirm_on_event(void* context, SceneManagerEvent event) {
+    Nfc* nfc = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == DialogExResultRight) {
+            consumed = scene_manager_previous_scene(nfc->scene_manager);
+        } else if(event.event == DialogExResultLeft) {
+            consumed = scene_manager_search_and_switch_to_previous_scene(
+                nfc->scene_manager, NfcSceneRead);
+        }
+    } else if(event.type == SceneManagerEventTypeBack) {
+        consumed = true;
+    }
+
+    return consumed;
+}
+
+void nfc_scene_retry_confirm_on_exit(void* context) {
+    Nfc* nfc = context;
+
+    // Clean view
+    dialog_ex_reset(nfc->dialog_ex);
+}

+ 0 - 49
applications/nfc/scenes/nfc_scene_run_emv_app_confirm.c

@@ -1,49 +0,0 @@
-#include "../nfc_i.h"
-
-void nfc_scene_run_emv_app_confirm_dialog_callback(DialogExResult result, void* context) {
-    Nfc* nfc = context;
-
-    view_dispatcher_send_custom_event(nfc->view_dispatcher, result);
-}
-
-void nfc_scene_run_emv_app_confirm_on_enter(void* context) {
-    Nfc* nfc = context;
-
-    DialogEx* dialog_ex = nfc->dialog_ex;
-    dialog_ex_set_left_button_text(dialog_ex, "Back");
-    dialog_ex_set_right_button_text(dialog_ex, "Run");
-    dialog_ex_set_header(dialog_ex, "Run EMV app?", 64, 8, AlignCenter, AlignCenter);
-    dialog_ex_set_text(
-        dialog_ex,
-        "It will try to run card's app\nand detect unencrypted\ndata",
-        64,
-        18,
-        AlignCenter,
-        AlignTop);
-    dialog_ex_set_context(dialog_ex, nfc);
-    dialog_ex_set_result_callback(dialog_ex, nfc_scene_run_emv_app_confirm_dialog_callback);
-
-    view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewDialogEx);
-}
-
-bool nfc_scene_run_emv_app_confirm_on_event(void* context, SceneManagerEvent event) {
-    Nfc* nfc = context;
-    bool consumed = false;
-
-    if(event.type == SceneManagerEventTypeCustom) {
-        if(event.event == DialogExResultLeft) {
-            consumed = scene_manager_previous_scene(nfc->scene_manager);
-        } else if(event.event == DialogExResultRight) {
-            scene_manager_next_scene(nfc->scene_manager, NfcSceneReadEmvData);
-            consumed = true;
-        }
-    }
-    return consumed;
-}
-
-void nfc_scene_run_emv_app_confirm_on_exit(void* context) {
-    Nfc* nfc = context;
-
-    // Clean view
-    dialog_ex_reset(nfc->dialog_ex);
-}

+ 2 - 6
applications/nfc/scenes/nfc_scene_save_success.c

@@ -27,13 +27,9 @@ bool nfc_scene_save_success_on_event(void* context, SceneManagerEvent event) {
 
     if(event.type == SceneManagerEventTypeCustom) {
         if(event.event == NfcCustomEventViewExit) {
-            if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneCardMenu)) {
+            if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneMfDesfireMenu)) {
                 consumed = scene_manager_search_and_switch_to_previous_scene(
-                    nfc->scene_manager, NfcSceneCardMenu);
-            } else if(scene_manager_has_previous_scene(
-                          nfc->scene_manager, NfcSceneMifareDesfireMenu)) {
-                consumed = scene_manager_search_and_switch_to_previous_scene(
-                    nfc->scene_manager, NfcSceneMifareDesfireMenu);
+                    nfc->scene_manager, NfcSceneMfDesfireMenu);
             } else {
                 consumed = scene_manager_search_and_switch_to_previous_scene(
                     nfc->scene_manager, NfcSceneStart);

+ 2 - 2
applications/nfc/scenes/nfc_scene_saved_menu.c

@@ -61,9 +61,9 @@ bool nfc_scene_saved_menu_on_event(void* context, SceneManagerEvent event) {
         scene_manager_set_scene_state(nfc->scene_manager, NfcSceneSavedMenu, event.event);
         if(event.event == SubmenuIndexEmulate) {
             if(nfc->dev->format == NfcDeviceSaveFormatMifareUl) {
-                scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateMifareUl);
+                scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightEmulate);
             } else if(nfc->dev->format == NfcDeviceSaveFormatMifareClassic) {
-                scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateMifareClassic);
+                scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicEmulate);
             } else {
                 scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateUid);
             }

+ 0 - 82
applications/nfc/scenes/nfc_scene_scripts_menu.c

@@ -1,82 +0,0 @@
-#include "../nfc_i.h"
-
-enum SubmenuIndex {
-    SubmenuIndexBankCard,
-    SubmenuIndexMifareUltralight,
-    SubmenuIdexReadMfClassic,
-    SubmenuIndexMifareDesfire,
-};
-
-void nfc_scene_scripts_menu_submenu_callback(void* context, uint32_t index) {
-    Nfc* nfc = context;
-    view_dispatcher_send_custom_event(nfc->view_dispatcher, index);
-}
-
-void nfc_scene_scripts_menu_on_enter(void* context) {
-    Nfc* nfc = context;
-    Submenu* submenu = nfc->submenu;
-
-    submenu_add_item(
-        submenu,
-        "Read Bank Card",
-        SubmenuIndexBankCard,
-        nfc_scene_scripts_menu_submenu_callback,
-        nfc);
-    submenu_add_item(
-        submenu,
-        "Read Mifare Ultral/Ntag",
-        SubmenuIndexMifareUltralight,
-        nfc_scene_scripts_menu_submenu_callback,
-        nfc);
-    submenu_add_item(
-        submenu,
-        "Read Mifare Classic",
-        SubmenuIdexReadMfClassic,
-        nfc_scene_scripts_menu_submenu_callback,
-        nfc);
-    submenu_add_item(
-        submenu,
-        "Read Mifare DESFire",
-        SubmenuIndexMifareDesfire,
-        nfc_scene_scripts_menu_submenu_callback,
-        nfc);
-    submenu_set_selected_item(
-        nfc->submenu, scene_manager_get_scene_state(nfc->scene_manager, NfcSceneScriptsMenu));
-    view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu);
-}
-
-bool nfc_scene_scripts_menu_on_event(void* context, SceneManagerEvent event) {
-    Nfc* nfc = context;
-    bool consumed = false;
-
-    if(event.type == SceneManagerEventTypeCustom) {
-        if(event.event == SubmenuIndexBankCard) {
-            scene_manager_set_scene_state(
-                nfc->scene_manager, NfcSceneScriptsMenu, SubmenuIndexBankCard);
-            scene_manager_next_scene(nfc->scene_manager, NfcSceneReadEmvApp);
-            consumed = true;
-        } else if(event.event == SubmenuIndexMifareUltralight) {
-            scene_manager_set_scene_state(
-                nfc->scene_manager, NfcSceneScriptsMenu, SubmenuIndexMifareUltralight);
-            scene_manager_next_scene(nfc->scene_manager, NfcSceneReadMifareUl);
-            consumed = true;
-        } else if(event.event == SubmenuIdexReadMfClassic) {
-            scene_manager_set_scene_state(
-                nfc->scene_manager, NfcSceneScriptsMenu, SubmenuIdexReadMfClassic);
-            scene_manager_next_scene(nfc->scene_manager, NfcSceneReadMifareClassic);
-            consumed = true;
-        } else if(event.event == SubmenuIndexMifareDesfire) {
-            scene_manager_set_scene_state(
-                nfc->scene_manager, NfcSceneScriptsMenu, SubmenuIndexMifareDesfire);
-            scene_manager_next_scene(nfc->scene_manager, NfcSceneReadMifareDesfire);
-            consumed = true;
-        }
-    }
-
-    return consumed;
-}
-
-void nfc_scene_scripts_menu_on_exit(void* context) {
-    Nfc* nfc = context;
-    submenu_reset(nfc->submenu);
-}

+ 12 - 11
applications/nfc/scenes/nfc_scene_start.c

@@ -2,8 +2,9 @@
 
 enum SubmenuIndex {
     SubmenuIndexRead,
-    SubmenuIndexRunScript,
+    SubmenuIndexDetectReader,
     SubmenuIndexSaved,
+    SubmenuIndexExtraAction,
     SubmenuIndexAddManualy,
     SubmenuIndexDebug,
 };
@@ -18,15 +19,12 @@ void nfc_scene_start_on_enter(void* context) {
     Nfc* nfc = context;
     Submenu* submenu = nfc->submenu;
 
+    submenu_add_item(submenu, "Read", SubmenuIndexRead, nfc_scene_start_submenu_callback, nfc);
     submenu_add_item(
-        submenu, "Read Card", SubmenuIndexRead, nfc_scene_start_submenu_callback, nfc);
-    submenu_add_item(
-        submenu,
-        "Run Special Action",
-        SubmenuIndexRunScript,
-        nfc_scene_start_submenu_callback,
-        nfc);
+        submenu, "Detect Reader", SubmenuIndexDetectReader, nfc_scene_start_submenu_callback, nfc);
     submenu_add_item(submenu, "Saved", SubmenuIndexSaved, nfc_scene_start_submenu_callback, nfc);
+    submenu_add_item(
+        submenu, "Extra Actions", SubmenuIndexExtraAction, nfc_scene_start_submenu_callback, nfc);
     submenu_add_item(
         submenu, "Add Manually", SubmenuIndexAddManualy, nfc_scene_start_submenu_callback, nfc);
 
@@ -48,14 +46,17 @@ bool nfc_scene_start_on_event(void* context, SceneManagerEvent event) {
 
     if(event.type == SceneManagerEventTypeCustom) {
         if(event.event == SubmenuIndexRead) {
-            scene_manager_next_scene(nfc->scene_manager, NfcSceneReadCard);
+            scene_manager_next_scene(nfc->scene_manager, NfcSceneRead);
             consumed = true;
-        } else if(event.event == SubmenuIndexRunScript) {
-            scene_manager_next_scene(nfc->scene_manager, NfcSceneScriptsMenu);
+        } else if(event.event == SubmenuIndexDetectReader) {
+            scene_manager_next_scene(nfc->scene_manager, NfcSceneDetectReader);
             consumed = true;
         } else if(event.event == SubmenuIndexSaved) {
             scene_manager_next_scene(nfc->scene_manager, NfcSceneFileSelect);
             consumed = true;
+        } else if(event.event == SubmenuIndexExtraAction) {
+            scene_manager_next_scene(nfc->scene_manager, NfcSceneExtraActions);
+            consumed = true;
         } else if(event.event == SubmenuIndexAddManualy) {
             scene_manager_next_scene(nfc->scene_manager, NfcSceneSetType);
             consumed = true;

+ 90 - 86
applications/nfc/views/dict_attack.c

@@ -1,83 +1,60 @@
 #include "dict_attack.h"
-#include <m-string.h>
 
+#include <m-string.h>
 #include <gui/elements.h>
 
 typedef enum {
-    DictAttackStateSearchCard,
-    DictAttackStateSearchKeys,
+    DictAttackStateRead,
     DictAttackStateCardRemoved,
-    DictAttackStateSuccess,
-    DictAttackStateFail,
 } DictAttackState;
 
 struct DictAttack {
     View* view;
-    DictAttackResultCallback callback;
+    DictAttackCallback callback;
     void* context;
 };
 
 typedef struct {
     DictAttackState state;
     MfClassicType type;
-    uint8_t current_sector;
-    uint8_t total_sectors;
-    uint8_t keys_a_found;
-    uint8_t keys_a_total;
-    uint8_t keys_b_found;
-    uint8_t keys_b_total;
+    string_t header;
+    uint8_t sectors_total;
+    uint8_t sectors_read;
+    uint8_t sector_current;
+    uint8_t keys_total;
+    uint8_t keys_found;
 } DictAttackViewModel;
 
 static void dict_attack_draw_callback(Canvas* canvas, void* model) {
     DictAttackViewModel* m = model;
-    if(m->state == DictAttackStateSearchCard) {
+    if(m->state == DictAttackStateCardRemoved) {
         canvas_set_font(canvas, FontPrimary);
-        canvas_draw_str_aligned(
-            canvas, 64, 32, AlignCenter, AlignCenter, "Detecting Mifare Classic");
-    } else if(m->state == DictAttackStateCardRemoved) {
+        canvas_draw_str_aligned(canvas, 64, 4, AlignCenter, AlignTop, "Lost the tag!");
+        canvas_set_font(canvas, FontSecondary);
+        elements_multiline_text_aligned(
+            canvas, 64, 23, AlignCenter, AlignTop, "Make sure the tag is\npositioned correctly.");
+    } else if(m->state == DictAttackStateRead) {
+        char draw_str[32] = {};
         canvas_set_font(canvas, FontPrimary);
-        canvas_draw_str_aligned(
-            canvas, 64, 32, AlignCenter, AlignTop, "Place card back to flipper");
-    } else {
-        char draw_str[32];
-        if(m->state == DictAttackStateSearchKeys) {
-            snprintf(
-                draw_str, sizeof(draw_str), "Searching keys for sector %d", m->current_sector);
-            canvas_draw_str_aligned(canvas, 64, 2, AlignCenter, AlignTop, draw_str);
-        } else if(m->state == DictAttackStateSuccess) {
-            canvas_draw_str_aligned(canvas, 64, 2, AlignCenter, AlignTop, "Complete!");
-            elements_button_right(canvas, "More");
-        } else if(m->state == DictAttackStateFail) {
-            canvas_draw_str_aligned(
-                canvas, 64, 2, AlignCenter, AlignTop, "Failed to read any sector");
-        }
-        uint16_t keys_found = m->keys_a_found + m->keys_b_found;
-        uint16_t keys_total = m->keys_a_total + m->keys_b_total;
-        float progress = (float)(m->current_sector) / (float)(m->total_sectors);
-        elements_progress_bar(canvas, 5, 12, 120, progress);
+        canvas_draw_str_aligned(canvas, 64, 2, AlignCenter, AlignTop, string_get_cstr(m->header));
         canvas_set_font(canvas, FontSecondary);
-        snprintf(draw_str, sizeof(draw_str), "Total keys found: %d/%d", keys_found, keys_total);
-        canvas_draw_str_aligned(canvas, 1, 23, AlignLeft, AlignTop, draw_str);
-        snprintf(
-            draw_str, sizeof(draw_str), "A keys found: %d/%d", m->keys_a_found, m->keys_a_total);
-        canvas_draw_str_aligned(canvas, 1, 34, AlignLeft, AlignTop, draw_str);
+        float progress =
+            m->sectors_total == 0 ? 0 : (float)(m->sector_current) / (float)(m->sectors_total);
+        elements_progress_bar(canvas, 5, 15, 120, progress);
+        canvas_set_font(canvas, FontSecondary);
+        snprintf(draw_str, sizeof(draw_str), "Keys found: %d/%d", m->keys_found, m->keys_total);
+        canvas_draw_str_aligned(canvas, 1, 28, AlignLeft, AlignTop, draw_str);
         snprintf(
-            draw_str, sizeof(draw_str), "B keys found: %d/%d", m->keys_b_found, m->keys_b_total);
-        canvas_draw_str_aligned(canvas, 1, 45, AlignLeft, AlignTop, draw_str);
+            draw_str, sizeof(draw_str), "Sectors Read: %d/%d", m->sectors_read, m->sectors_total);
+        canvas_draw_str_aligned(canvas, 1, 40, AlignLeft, AlignTop, draw_str);
     }
+    elements_button_center(canvas, "Skip");
 }
 
 static bool dict_attack_input_callback(InputEvent* event, void* context) {
     DictAttack* dict_attack = context;
     bool consumed = false;
-    DictAttackState state;
-    with_view_model(
-        dict_attack->view, (DictAttackViewModel * model) {
-            state = model->state;
-            return false;
-        });
-    if(state == DictAttackStateSuccess && event->type == InputTypeShort &&
-       event->key == InputKeyRight) {
+    if(event->type == InputTypeShort && event->key == InputKeyOk) {
         if(dict_attack->callback) {
             dict_attack->callback(dict_attack->context);
         }
@@ -93,11 +70,21 @@ DictAttack* dict_attack_alloc() {
     view_set_draw_callback(dict_attack->view, dict_attack_draw_callback);
     view_set_input_callback(dict_attack->view, dict_attack_input_callback);
     view_set_context(dict_attack->view, dict_attack);
+    with_view_model(
+        dict_attack->view, (DictAttackViewModel * model) {
+            string_init(model->header);
+            return false;
+        });
     return dict_attack;
 }
 
 void dict_attack_free(DictAttack* dict_attack) {
     furi_assert(dict_attack);
+    with_view_model(
+        dict_attack->view, (DictAttackViewModel * model) {
+            string_clear(model->header);
+            return false;
+        });
     view_free(dict_attack->view);
     free(dict_attack);
 }
@@ -106,8 +93,15 @@ void dict_attack_reset(DictAttack* dict_attack) {
     furi_assert(dict_attack);
     with_view_model(
         dict_attack->view, (DictAttackViewModel * model) {
-            memset(model, 0, sizeof(DictAttackViewModel));
-            return true;
+            model->state = DictAttackStateRead;
+            model->type = MfClassicType1k;
+            model->sectors_total = 0;
+            model->sectors_read = 0;
+            model->sector_current = 0;
+            model->keys_total = 0;
+            model->keys_found = 0;
+            string_reset(model->header);
+            return false;
         });
 }
 
@@ -116,78 +110,88 @@ View* dict_attack_get_view(DictAttack* dict_attack) {
     return dict_attack->view;
 }
 
-void dict_attack_set_result_callback(
-    DictAttack* dict_attack,
-    DictAttackResultCallback callback,
-    void* context) {
+void dict_attack_set_callback(DictAttack* dict_attack, DictAttackCallback callback, void* context) {
     furi_assert(dict_attack);
     furi_assert(callback);
     dict_attack->callback = callback;
     dict_attack->context = context;
 }
 
-void dict_attack_card_detected(DictAttack* dict_attack, MfClassicType type) {
+void dict_attack_set_header(DictAttack* dict_attack, const char* header) {
     furi_assert(dict_attack);
+    furi_assert(header);
+
     with_view_model(
         dict_attack->view, (DictAttackViewModel * model) {
-            model->state = DictAttackStateSearchKeys;
-            if(type == MfClassicType1k) {
-                model->total_sectors = 16;
-                model->keys_a_total = 16;
-                model->keys_b_total = 16;
-            } else if(type == MfClassicType4k) {
-                model->total_sectors = 40;
-                model->keys_a_total = 40;
-                model->keys_b_total = 40;
-            }
+            string_set_str(model->header, header);
             return true;
         });
 }
 
-void dict_attack_card_removed(DictAttack* dict_attack) {
+void dict_attack_set_card_detected(DictAttack* dict_attack, MfClassicType type) {
     furi_assert(dict_attack);
     with_view_model(
         dict_attack->view, (DictAttackViewModel * model) {
-            if(model->state == DictAttackStateSearchKeys) {
-                model->state = DictAttackStateCardRemoved;
-            } else {
-                model->state = DictAttackStateSearchCard;
-            }
+            model->state = DictAttackStateRead;
+            model->sectors_total = mf_classic_get_total_sectors_num(type);
+            model->keys_total = model->sectors_total * 2;
+            return true;
+        });
+}
+
+void dict_attack_set_card_removed(DictAttack* dict_attack) {
+    furi_assert(dict_attack);
+    with_view_model(
+        dict_attack->view, (DictAttackViewModel * model) {
+            model->state = DictAttackStateCardRemoved;
+            return true;
+        });
+}
+
+void dict_attack_set_sector_read(DictAttack* dict_attack, uint8_t sec_read) {
+    furi_assert(dict_attack);
+    with_view_model(
+        dict_attack->view, (DictAttackViewModel * model) {
+            model->sectors_read = sec_read;
+            return true;
+        });
+}
+
+void dict_attack_set_keys_found(DictAttack* dict_attack, uint8_t keys_found) {
+    furi_assert(dict_attack);
+    with_view_model(
+        dict_attack->view, (DictAttackViewModel * model) {
+            model->keys_found = keys_found;
             return true;
         });
 }
 
-void dict_attack_inc_curr_sector(DictAttack* dict_attack) {
+void dict_attack_set_current_sector(DictAttack* dict_attack, uint8_t curr_sec) {
     furi_assert(dict_attack);
     with_view_model(
         dict_attack->view, (DictAttackViewModel * model) {
-            model->current_sector++;
+            model->sector_current = curr_sec;
             return true;
         });
 }
 
-void dict_attack_inc_found_key(DictAttack* dict_attack, MfClassicKey key) {
+void dict_attack_inc_current_sector(DictAttack* dict_attack) {
     furi_assert(dict_attack);
     with_view_model(
         dict_attack->view, (DictAttackViewModel * model) {
-            model->state = DictAttackStateSearchKeys;
-            if(key == MfClassicKeyA) {
-                model->keys_a_found++;
-            } else if(key == MfClassicKeyB) {
-                model->keys_b_found++;
+            if(model->sector_current < model->sectors_total) {
+                model->sector_current++;
             }
             return true;
         });
 }
 
-void dict_attack_set_result(DictAttack* dict_attack, bool success) {
+void dict_attack_inc_keys_found(DictAttack* dict_attack) {
     furi_assert(dict_attack);
     with_view_model(
         dict_attack->view, (DictAttackViewModel * model) {
-            if(success) {
-                model->state = DictAttackStateSuccess;
-            } else {
-                model->state = DictAttackStateFail;
+            if(model->keys_found < model->keys_total) {
+                model->keys_found++;
             }
             return true;
         });

+ 14 - 11
applications/nfc/views/dict_attack.h

@@ -3,11 +3,11 @@
 #include <gui/view.h>
 #include <gui/modules/widget.h>
 
-#include <lib/nfc_protocols/mifare_classic.h>
+#include <lib/nfc/protocols/mifare_classic.h>
 
 typedef struct DictAttack DictAttack;
 
-typedef void (*DictAttackResultCallback)(void* context);
+typedef void (*DictAttackCallback)(void* context);
 
 DictAttack* dict_attack_alloc();
 
@@ -17,17 +17,20 @@ void dict_attack_reset(DictAttack* dict_attack);
 
 View* dict_attack_get_view(DictAttack* dict_attack);
 
-void dict_attack_set_result_callback(
-    DictAttack* dict_attack,
-    DictAttackResultCallback callback,
-    void* context);
+void dict_attack_set_callback(DictAttack* dict_attack, DictAttackCallback callback, void* context);
 
-void dict_attack_card_detected(DictAttack* dict_attack, MfClassicType type);
+void dict_attack_set_header(DictAttack* dict_attack, const char* header);
 
-void dict_attack_card_removed(DictAttack* dict_attack);
+void dict_attack_set_card_detected(DictAttack* dict_attack, MfClassicType type);
 
-void dict_attack_inc_curr_sector(DictAttack* dict_attack);
+void dict_attack_set_card_removed(DictAttack* dict_attack);
 
-void dict_attack_inc_found_key(DictAttack* dict_attack, MfClassicKey key);
+void dict_attack_set_sector_read(DictAttack* dict_attack, uint8_t sec_read);
 
-void dict_attack_set_result(DictAttack* dict_attack, bool success);
+void dict_attack_set_keys_found(DictAttack* dict_attack, uint8_t keys_found);
+
+void dict_attack_set_current_sector(DictAttack* dict_attack, uint8_t curr_sec);
+
+void dict_attack_inc_current_sector(DictAttack* dict_attack);
+
+void dict_attack_inc_keys_found(DictAttack* dict_attack);

+ 1 - 1
applications/unit_tests/nfc/nfc_test.c

@@ -2,7 +2,7 @@
 #include <furi_hal.h>
 #include <applications/storage/storage.h>
 #include <lib/flipper_format/flipper_format.h>
-#include <lib/nfc_protocols/nfca.h>
+#include <lib/nfc/protocols/nfca.h>
 #include <lib/digital_signal/digital_signal.h>
 
 #include <lib/flipper_format/flipper_format_i.h>

BIN
assets/icons/NFC/NFC_manual.png


BIN
assets/icons/NFC/Reader_detect.png


+ 1 - 0
firmware.scons

@@ -186,6 +186,7 @@ fwelf = fwenv["FW_ELF"] = fwenv.Program(
         "subghz",
         "flipperformat",
         "toolbox",
+        "nfc",
         "microtar",
         "usb_stm32",
         "st25rfal002",

+ 1 - 1
firmware/targets/furi_hal_include/furi_hal_nfc.h

@@ -10,7 +10,7 @@
 #include <stdbool.h>
 #include <stdint.h>
 
-#include <lib/nfc_protocols/nfca.h>
+#include <lib/nfc/protocols/nfca.h>
 
 #ifdef __cplusplus
 extern "C" {

+ 4 - 0
furi/core/common_defines.h

@@ -88,6 +88,10 @@ extern "C" {
 #define FURI_BIT(x, n) (((x) >> (n)) & 1)
 #endif
 
+#ifndef FURI_BIT_SET
+#define FURI_BIT_SET(x, n) ((x) |= (1 << (n)))
+#endif
+
 #ifndef FURI_IS_IRQ_MASKED
 #define FURI_IS_IRQ_MASKED() (__get_PRIMASK() != 0U)
 #endif

+ 1 - 0
lib/SConscript

@@ -70,6 +70,7 @@ libs = env.BuildModules(
         "infrared",
         "littlefs",
         "subghz",
+        "nfc",
         "appframe",
         "misc",
         "mbedtls",

+ 0 - 2
lib/misc.scons

@@ -7,7 +7,6 @@ env.Append(
         "#/lib/heatshrink",
         "#/lib/micro-ecc",
         "#/lib/nanopb",
-        "#/lib/nfc_protocols",
         "#/lib/u8g2",
     ],
     CPPDEFINES=[
@@ -24,7 +23,6 @@ sources = []
 libs_recurse = [
     "digital_signal",
     "micro-ecc",
-    "nfc_protocols",
     "one_wire",
     "u8g2",
     "update_util",

+ 16 - 0
lib/nfc/SConscript

@@ -0,0 +1,16 @@
+Import("env")
+
+env.Append(
+    CPPPATH=[
+        "#/lib/nfc",
+    ],
+)
+
+libenv = env.Clone(FW_LIB_NAME="nfc")
+libenv.ApplyLibFlags()
+
+sources = libenv.GlobRecursive("*.c*")
+
+lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources)
+libenv.Install("${LIB_DIST_DIR}", lib)
+Return("lib")

+ 148 - 0
lib/nfc/helpers/mf_classic_dict.c

@@ -0,0 +1,148 @@
+#include "mf_classic_dict.h"
+
+#include <lib/toolbox/args.h>
+#include <lib/flipper_format/flipper_format.h>
+
+#define MF_CLASSIC_DICT_FLIPPER_PATH EXT_PATH("nfc/assets/mf_classic_dict.nfc")
+#define MF_CLASSIC_DICT_USER_PATH EXT_PATH("nfc/assets/mf_classic_dict_user.nfc")
+
+#define TAG "MfClassicDict"
+
+#define NFC_MF_CLASSIC_KEY_LEN (13)
+
+struct MfClassicDict {
+    Stream* stream;
+    uint32_t total_keys;
+};
+
+bool mf_classic_dict_check_presence(MfClassicDictType dict_type) {
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+
+    bool dict_present = false;
+    if(dict_type == MfClassicDictTypeFlipper) {
+        dict_present = storage_common_stat(storage, MF_CLASSIC_DICT_FLIPPER_PATH, NULL) == FSE_OK;
+    } else if(dict_type == MfClassicDictTypeUser) {
+        dict_present = storage_common_stat(storage, MF_CLASSIC_DICT_USER_PATH, NULL) == FSE_OK;
+    }
+
+    furi_record_close(RECORD_STORAGE);
+
+    return dict_present;
+}
+
+MfClassicDict* mf_classic_dict_alloc(MfClassicDictType dict_type) {
+    MfClassicDict* dict = malloc(sizeof(MfClassicDict));
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    dict->stream = buffered_file_stream_alloc(storage);
+    furi_record_close(RECORD_STORAGE);
+
+    bool dict_loaded = false;
+    do {
+        if(dict_type == MfClassicDictTypeFlipper) {
+            if(!buffered_file_stream_open(
+                   dict->stream, MF_CLASSIC_DICT_FLIPPER_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) {
+                buffered_file_stream_close(dict->stream);
+                break;
+            }
+        } else if(dict_type == MfClassicDictTypeUser) {
+            if(!buffered_file_stream_open(
+                   dict->stream, MF_CLASSIC_DICT_USER_PATH, FSAM_READ_WRITE, FSOM_OPEN_ALWAYS)) {
+                buffered_file_stream_close(dict->stream);
+                break;
+            }
+        }
+
+        // Read total amount of keys
+        string_t next_line;
+        string_init(next_line);
+        while(true) {
+            if(!stream_read_line(dict->stream, next_line)) break;
+            if(string_get_char(next_line, 0) == '#') continue;
+            if(string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN) continue;
+            dict->total_keys++;
+        }
+        string_clear(next_line);
+        stream_rewind(dict->stream);
+
+        dict_loaded = true;
+        FURI_LOG_I(TAG, "Loaded dictionary with %d keys", dict->total_keys);
+    } while(false);
+
+    if(!dict_loaded) {
+        buffered_file_stream_close(dict->stream);
+        free(dict);
+        dict = NULL;
+    }
+
+    return dict;
+}
+
+void mf_classic_dict_free(MfClassicDict* dict) {
+    furi_assert(dict);
+    furi_assert(dict->stream);
+
+    buffered_file_stream_close(dict->stream);
+    stream_free(dict->stream);
+    free(dict);
+}
+
+uint32_t mf_classic_dict_get_total_keys(MfClassicDict* dict) {
+    furi_assert(dict);
+
+    return dict->total_keys;
+}
+
+bool mf_classic_dict_get_next_key(MfClassicDict* dict, uint64_t* key) {
+    furi_assert(dict);
+    furi_assert(dict->stream);
+
+    uint8_t key_byte_tmp = 0;
+    string_t next_line;
+    string_init(next_line);
+
+    bool key_read = false;
+    *key = 0ULL;
+    while(!key_read) {
+        if(!stream_read_line(dict->stream, next_line)) break;
+        if(string_get_char(next_line, 0) == '#') continue;
+        if(string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN) continue;
+        for(uint8_t i = 0; i < 12; i += 2) {
+            args_char_to_hex(
+                string_get_char(next_line, i), string_get_char(next_line, i + 1), &key_byte_tmp);
+            *key |= (uint64_t)key_byte_tmp << 8 * (5 - i / 2);
+        }
+        key_read = true;
+    }
+
+    string_clear(next_line);
+    return key_read;
+}
+
+bool mf_classic_dict_rewind(MfClassicDict* dict) {
+    furi_assert(dict);
+    furi_assert(dict->stream);
+
+    return stream_rewind(dict->stream);
+}
+
+bool mf_classic_dict_add_key(MfClassicDict* dict, uint8_t* key) {
+    furi_assert(dict);
+    furi_assert(dict->stream);
+
+    string_t key_str;
+    string_init(key_str);
+    for(size_t i = 0; i < 6; i++) {
+        string_cat_printf(key_str, "%02X", key[i]);
+    }
+    string_cat_printf(key_str, "\n");
+
+    bool key_added = false;
+    do {
+        if(!stream_seek(dict->stream, 0, StreamOffsetFromEnd)) break;
+        if(!stream_insert_string(dict->stream, key_str)) break;
+        key_added = true;
+    } while(false);
+
+    string_clear(key_str);
+    return key_added;
+}

+ 28 - 0
lib/nfc/helpers/mf_classic_dict.h

@@ -0,0 +1,28 @@
+#pragma once
+
+#include <stdbool.h>
+#include <storage/storage.h>
+#include <lib/flipper_format/flipper_format.h>
+#include <lib/toolbox/stream/file_stream.h>
+#include <lib/toolbox/stream/buffered_file_stream.h>
+
+typedef enum {
+    MfClassicDictTypeUser,
+    MfClassicDictTypeFlipper,
+} MfClassicDictType;
+
+typedef struct MfClassicDict MfClassicDict;
+
+bool mf_classic_dict_check_presence(MfClassicDictType dict_type);
+
+MfClassicDict* mf_classic_dict_alloc(MfClassicDictType dict_type);
+
+void mf_classic_dict_free(MfClassicDict* dict);
+
+uint32_t mf_classic_dict_get_total_keys(MfClassicDict* dict);
+
+bool mf_classic_dict_get_next_key(MfClassicDict* dict, uint64_t* key);
+
+bool mf_classic_dict_rewind(MfClassicDict* dict);
+
+bool mf_classic_dict_add_key(MfClassicDict* dict, uint8_t* key);

+ 0 - 0
applications/nfc/helpers/nfc_debug_pcap.c → lib/nfc/helpers/nfc_debug_pcap.c


+ 0 - 0
applications/nfc/helpers/nfc_debug_pcap.h → lib/nfc/helpers/nfc_debug_pcap.h


+ 255 - 18
applications/nfc/nfc_device.c → lib/nfc/nfc_device.c

@@ -3,20 +3,29 @@
 #include "m-string.h"
 #include "nfc_types.h"
 
-#include <toolbox/path.h>
+#include <lib/toolbox/path.h>
+#include <lib/toolbox/hex.h>
+#include <lib/nfc/protocols/nfc_util.h>
 #include <flipper_format/flipper_format.h>
 
+#define NFC_DEVICE_KEYS_FOLDER EXT_PATH("nfc/cache")
+#define NFC_DEVICE_KEYS_EXTENSION ".keys"
+
 static const char* nfc_file_header = "Flipper NFC device";
 static const uint32_t nfc_file_version = 2;
 
+static const char* nfc_keys_file_header = "Flipper NFC keys";
+static const uint32_t nfc_keys_file_version = 1;
+
 // Protocols format versions
-static const uint32_t nfc_mifare_classic_data_format_version = 1;
+static const uint32_t nfc_mifare_classic_data_format_version = 2;
 
 NfcDevice* nfc_device_alloc() {
     NfcDevice* nfc_dev = malloc(sizeof(NfcDevice));
     nfc_dev->storage = furi_record_open(RECORD_STORAGE);
     nfc_dev->dialogs = furi_record_open(RECORD_DIALOGS);
     string_init(nfc_dev->load_path);
+    string_init(nfc_dev->dev_data.parsed_data);
     return nfc_dev;
 }
 
@@ -26,6 +35,7 @@ void nfc_device_free(NfcDevice* nfc_dev) {
     furi_record_close(RECORD_STORAGE);
     furi_record_close(RECORD_DIALOGS);
     string_clear(nfc_dev->load_path);
+    string_clear(nfc_dev->dev_data.parsed_data);
     free(nfc_dev);
 }
 
@@ -648,6 +658,52 @@ bool nfc_device_load_bank_card_data(FlipperFormat* file, NfcDevice* dev) {
     return parsed;
 }
 
+static void nfc_device_write_mifare_classic_block(
+    string_t block_str,
+    MfClassicData* data,
+    uint8_t block_num) {
+    string_reset(block_str);
+    bool is_sec_trailer = mf_classic_is_sector_trailer(block_num);
+    if(is_sec_trailer) {
+        uint8_t sector_num = mf_classic_get_sector_by_block(block_num);
+        MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, sector_num);
+        // Write key A
+        for(size_t i = 0; i < sizeof(sec_tr->key_a); i++) {
+            if(mf_classic_is_key_found(data, sector_num, MfClassicKeyA)) {
+                string_cat_printf(block_str, "%02X ", sec_tr->key_a[i]);
+            } else {
+                string_cat_printf(block_str, "?? ");
+            }
+        }
+        // Write Access bytes
+        for(size_t i = 0; i < MF_CLASSIC_ACCESS_BYTES_SIZE; i++) {
+            if(mf_classic_is_block_read(data, block_num)) {
+                string_cat_printf(block_str, "%02X ", sec_tr->access_bits[i]);
+            } else {
+                string_cat_printf(block_str, "?? ");
+            }
+        }
+        // Write key B
+        for(size_t i = 0; i < sizeof(sec_tr->key_b); i++) {
+            if(mf_classic_is_key_found(data, sector_num, MfClassicKeyB)) {
+                string_cat_printf(block_str, "%02X ", sec_tr->key_b[i]);
+            } else {
+                string_cat_printf(block_str, "?? ");
+            }
+        }
+    } else {
+        // Write data block
+        for(size_t i = 0; i < MF_CLASSIC_BLOCK_SIZE; i++) {
+            if(mf_classic_is_block_read(data, block_num)) {
+                string_cat_printf(block_str, "%02X ", data->block[block_num].value[i]);
+            } else {
+                string_cat_printf(block_str, "?? ");
+            }
+        }
+    }
+    string_strim(block_str);
+}
+
 static bool nfc_device_save_mifare_classic_data(FlipperFormat* file, NfcDevice* dev) {
     bool saved = false;
     MfClassicData* data = &dev->dev_data.mf_classic_data;
@@ -669,23 +725,21 @@ static bool nfc_device_save_mifare_classic_data(FlipperFormat* file, NfcDevice*
         if(!flipper_format_write_uint32(
                file, "Data format version", &nfc_mifare_classic_data_format_version, 1))
             break;
-
         if(!flipper_format_write_comment_cstr(
-               file, "Key map is the bit mask indicating valid key in each sector"))
+               file, "Mifare Classic blocks, \'??\' means unknown data"))
             break;
-        if(!flipper_format_write_hex_uint64(file, "Key A map", &data->key_a_mask, 1)) break;
-        if(!flipper_format_write_hex_uint64(file, "Key B map", &data->key_b_mask, 1)) break;
-
-        if(!flipper_format_write_comment_cstr(file, "Mifare Classic blocks")) break;
         bool block_saved = true;
+        string_t block_str;
+        string_init(block_str);
         for(size_t i = 0; i < blocks; i++) {
             string_printf(temp_str, "Block %d", i);
-            if(!flipper_format_write_hex(
-                   file, string_get_cstr(temp_str), data->block[i].value, 16)) {
+            nfc_device_write_mifare_classic_block(block_str, data, i);
+            if(!flipper_format_write_string(file, string_get_cstr(temp_str), block_str)) {
                 block_saved = false;
                 break;
             }
         }
+        string_clear(block_str);
         if(!block_saved) break;
         saved = true;
     } while(false);
@@ -694,6 +748,59 @@ static bool nfc_device_save_mifare_classic_data(FlipperFormat* file, NfcDevice*
     return saved;
 }
 
+static void nfc_device_load_mifare_classic_block(
+    string_t block_str,
+    MfClassicData* data,
+    uint8_t block_num) {
+    string_strim(block_str);
+    MfClassicBlock block_tmp = {};
+    bool is_sector_trailer = mf_classic_is_sector_trailer(block_num);
+    uint8_t sector_num = mf_classic_get_sector_by_block(block_num);
+    uint16_t block_unknown_bytes_mask = 0;
+
+    string_strim(block_str);
+    for(size_t i = 0; i < MF_CLASSIC_BLOCK_SIZE; i++) {
+        char hi = string_get_char(block_str, 3 * i);
+        char low = string_get_char(block_str, 3 * i + 1);
+        uint8_t byte = 0;
+        if(hex_chars_to_uint8(hi, low, &byte)) {
+            block_tmp.value[i] = byte;
+        } else {
+            FURI_BIT_SET(block_unknown_bytes_mask, i);
+        }
+    }
+
+    if(block_unknown_bytes_mask == 0xffff) {
+        // All data is unknown, exit
+        return;
+    }
+
+    if(is_sector_trailer) {
+        MfClassicSectorTrailer* sec_tr_tmp = (MfClassicSectorTrailer*)&block_tmp;
+        // Load Key A
+        // Key A mask 0b0000000000111111 = 0x003f
+        if((block_unknown_bytes_mask & 0x003f) == 0) {
+            uint64_t key = nfc_util_bytes2num(sec_tr_tmp->key_a, sizeof(sec_tr_tmp->key_a));
+            mf_classic_set_key_found(data, sector_num, MfClassicKeyA, key);
+        }
+        // Load Access Bits
+        // Access bits mask 0b0000001111000000 = 0x03c0
+        if((block_unknown_bytes_mask & 0x03c0) == 0) {
+            mf_classic_set_block_read(data, block_num, &block_tmp);
+        }
+        // Load Key B
+        // Key B mask 0b1111110000000000 = 0xfc00
+        if((block_unknown_bytes_mask & 0xfc00) == 0) {
+            uint64_t key = nfc_util_bytes2num(sec_tr_tmp->key_b, sizeof(sec_tr_tmp->key_b));
+            mf_classic_set_key_found(data, sector_num, MfClassicKeyB, key);
+        }
+    } else {
+        if(block_unknown_bytes_mask == 0) {
+            mf_classic_set_block_read(data, block_num, &block_tmp);
+        }
+    }
+}
+
 static bool nfc_device_load_mifare_classic_data(FlipperFormat* file, NfcDevice* dev) {
     bool parsed = false;
     MfClassicData* data = &dev->dev_data.mf_classic_data;
@@ -701,6 +808,7 @@ static bool nfc_device_load_mifare_classic_data(FlipperFormat* file, NfcDevice*
     uint32_t data_format_version = 0;
     string_init(temp_str);
     uint16_t data_blocks = 0;
+    memset(data, 0, sizeof(MfClassicData));
 
     do {
         // Read Mifare Classic type
@@ -715,29 +823,40 @@ static bool nfc_device_load_mifare_classic_data(FlipperFormat* file, NfcDevice*
             break;
         }
 
+        bool old_format = false;
         // Read Mifare Classic format version
         if(!flipper_format_read_uint32(file, "Data format version", &data_format_version, 1)) {
             // Load unread sectors with zero keys access for backward compatability
             if(!flipper_format_rewind(file)) break;
-            data->key_a_mask = 0xffffffffffffffff;
-            data->key_b_mask = 0xffffffffffffffff;
+            old_format = true;
         } else {
-            if(data_format_version != nfc_mifare_classic_data_format_version) break;
-            if(!flipper_format_read_hex_uint64(file, "Key A map", &data->key_a_mask, 1)) break;
-            if(!flipper_format_read_hex_uint64(file, "Key B map", &data->key_b_mask, 1)) break;
+            if(data_format_version < nfc_mifare_classic_data_format_version) {
+                old_format = true;
+            }
         }
 
         // Read Mifare Classic blocks
         bool block_read = true;
+        string_t block_str;
+        string_init(block_str);
         for(size_t i = 0; i < data_blocks; i++) {
             string_printf(temp_str, "Block %d", i);
-            if(!flipper_format_read_hex(
-                   file, string_get_cstr(temp_str), data->block[i].value, 16)) {
+            if(!flipper_format_read_string(file, string_get_cstr(temp_str), block_str)) {
                 block_read = false;
                 break;
             }
+            nfc_device_load_mifare_classic_block(block_str, data, i);
         }
+        string_clear(block_str);
         if(!block_read) break;
+
+        // Set keys and blocks as unknown for backward compatibility
+        if(old_format) {
+            data->key_a_mask = 0ULL;
+            data->key_b_mask = 0ULL;
+            memset(data->block_read_mask, 0, sizeof(data->block_read_mask));
+        }
+
         parsed = true;
     } while(false);
 
@@ -745,6 +864,113 @@ static bool nfc_device_load_mifare_classic_data(FlipperFormat* file, NfcDevice*
     return parsed;
 }
 
+static void nfc_device_get_key_cache_file_path(NfcDevice* dev, string_t file_path) {
+    uint8_t* uid = dev->dev_data.nfc_data.uid;
+    uint8_t uid_len = dev->dev_data.nfc_data.uid_len;
+    string_set_str(file_path, NFC_DEVICE_KEYS_FOLDER "/");
+    for(size_t i = 0; i < uid_len; i++) {
+        string_cat_printf(file_path, "%02X", uid[i]);
+    }
+    string_cat_printf(file_path, NFC_DEVICE_KEYS_EXTENSION);
+}
+
+static bool nfc_device_save_mifare_classic_keys(NfcDevice* dev) {
+    FlipperFormat* file = flipper_format_file_alloc(dev->storage);
+    MfClassicData* data = &dev->dev_data.mf_classic_data;
+    string_t temp_str;
+    string_init(temp_str);
+
+    nfc_device_get_key_cache_file_path(dev, temp_str);
+    bool save_success = false;
+    do {
+        if(!storage_simply_mkdir(dev->storage, NFC_DEVICE_KEYS_FOLDER)) break;
+        if(!storage_simply_remove(dev->storage, string_get_cstr(temp_str))) break;
+        if(!flipper_format_file_open_always(file, string_get_cstr(temp_str))) break;
+        if(!flipper_format_write_header_cstr(file, nfc_keys_file_header, nfc_keys_file_version))
+            break;
+        if(data->type == MfClassicType1k) {
+            if(!flipper_format_write_string_cstr(file, "Mifare Classic type", "1K")) break;
+        } else if(data->type == MfClassicType4k) {
+            if(!flipper_format_write_string_cstr(file, "Mifare Classic type", "4K")) break;
+        }
+        if(!flipper_format_write_hex_uint64(file, "Key A map", &data->key_a_mask, 1)) break;
+        if(!flipper_format_write_hex_uint64(file, "Key B map", &data->key_b_mask, 1)) break;
+        uint8_t sector_num = mf_classic_get_total_sectors_num(data->type);
+        bool key_save_success = true;
+        for(size_t i = 0; (i < sector_num) && (key_save_success); i++) {
+            MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, i);
+            if(FURI_BIT(data->key_a_mask, i)) {
+                string_printf(temp_str, "Key A sector %d", i);
+                key_save_success =
+                    flipper_format_write_hex(file, string_get_cstr(temp_str), sec_tr->key_a, 6);
+            }
+            if(!key_save_success) break;
+            if(FURI_BIT(data->key_a_mask, i)) {
+                string_printf(temp_str, "Key B sector %d", i);
+                key_save_success =
+                    flipper_format_write_hex(file, string_get_cstr(temp_str), sec_tr->key_b, 6);
+            }
+        }
+        save_success = key_save_success;
+    } while(false);
+
+    flipper_format_free(file);
+    string_clear(temp_str);
+    return save_success;
+}
+
+bool nfc_device_load_key_cache(NfcDevice* dev) {
+    furi_assert(dev);
+    string_t temp_str;
+    string_init(temp_str);
+
+    MfClassicData* data = &dev->dev_data.mf_classic_data;
+    nfc_device_get_key_cache_file_path(dev, temp_str);
+    FlipperFormat* file = flipper_format_file_alloc(dev->storage);
+
+    bool load_success = false;
+    do {
+        if(storage_common_stat(dev->storage, string_get_cstr(temp_str), NULL) != FSE_OK) break;
+        if(!flipper_format_file_open_existing(file, string_get_cstr(temp_str))) break;
+        uint32_t version = 0;
+        if(!flipper_format_read_header(file, temp_str, &version)) break;
+        if(string_cmp_str(temp_str, nfc_keys_file_header)) break;
+        if(version != nfc_keys_file_version) break;
+        if(!flipper_format_read_string(file, "Mifare Classic type", temp_str)) break;
+        if(!string_cmp_str(temp_str, "1K")) {
+            data->type = MfClassicType1k;
+        } else if(!string_cmp_str(temp_str, "4K")) {
+            data->type = MfClassicType4k;
+        } else {
+            break;
+        }
+        if(!flipper_format_read_hex_uint64(file, "Key A map", &data->key_a_mask, 1)) break;
+        if(!flipper_format_read_hex_uint64(file, "Key B map", &data->key_b_mask, 1)) break;
+        uint8_t sectors = mf_classic_get_total_sectors_num(data->type);
+        bool key_read_success = true;
+        for(size_t i = 0; (i < sectors) && (key_read_success); i++) {
+            MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, i);
+            if(FURI_BIT(data->key_a_mask, i)) {
+                string_printf(temp_str, "Key A sector %d", i);
+                key_read_success =
+                    flipper_format_read_hex(file, string_get_cstr(temp_str), sec_tr->key_a, 6);
+            }
+            if(!key_read_success) break;
+            if(FURI_BIT(data->key_b_mask, i)) {
+                string_printf(temp_str, "Key B sector %d", i);
+                key_read_success =
+                    flipper_format_read_hex(file, string_get_cstr(temp_str), sec_tr->key_b, 6);
+            }
+        }
+        load_success = key_read_success;
+    } while(false);
+
+    string_clear(temp_str);
+    flipper_format_free(file);
+
+    return load_success;
+}
+
 void nfc_device_set_name(NfcDevice* dev, const char* name) {
     furi_assert(dev);
 
@@ -815,7 +1041,10 @@ static bool nfc_device_save_file(
         } else if(dev->format == NfcDeviceSaveFormatBankCard) {
             if(!nfc_device_save_bank_card_data(file, dev)) break;
         } else if(dev->format == NfcDeviceSaveFormatMifareClassic) {
+            // Save data
             if(!nfc_device_save_mifare_classic_data(file, dev)) break;
+            // Save keys cache
+            if(!nfc_device_save_mifare_classic_keys(dev)) break;
         }
         saved = true;
     } while(0);
@@ -954,14 +1183,22 @@ bool nfc_file_select(NfcDevice* dev) {
 void nfc_device_data_clear(NfcDeviceData* dev_data) {
     if(dev_data->protocol == NfcDeviceProtocolMifareDesfire) {
         mf_df_clear(&dev_data->mf_df_data);
+    } else if(dev_data->protocol == NfcDeviceProtocolMifareClassic) {
+        memset(&dev_data->mf_classic_data, 0, sizeof(MfClassicData));
+    } else if(dev_data->protocol == NfcDeviceProtocolMifareUl) {
+        memset(&dev_data->mf_ul_data, 0, sizeof(MfUltralightData));
+    } else if(dev_data->protocol == NfcDeviceProtocolEMV) {
+        memset(&dev_data->emv_data, 0, sizeof(EmvData));
     }
+    memset(&dev_data->nfc_data, 0, sizeof(FuriHalNfcDevData));
+    dev_data->protocol = NfcDeviceProtocolUnknown;
+    string_reset(dev_data->parsed_data);
 }
 
 void nfc_device_clear(NfcDevice* dev) {
     furi_assert(dev);
 
     nfc_device_data_clear(&dev->dev_data);
-    memset(&dev->dev_data, 0, sizeof(dev->dev_data));
     dev->format = NfcDeviceSaveFormatUid;
     string_reset(dev->load_path);
 }

+ 7 - 4
applications/nfc/nfc_device.h → lib/nfc/nfc_device.h

@@ -6,10 +6,10 @@
 #include <dialogs/dialogs.h>
 
 #include <furi_hal_nfc.h>
-#include <lib/nfc_protocols/emv.h>
-#include <lib/nfc_protocols/mifare_ultralight.h>
-#include <lib/nfc_protocols/mifare_classic.h>
-#include <lib/nfc_protocols/mifare_desfire.h>
+#include <lib/nfc/protocols/emv.h>
+#include <lib/nfc/protocols/mifare_ultralight.h>
+#include <lib/nfc/protocols/mifare_classic.h>
+#include <lib/nfc/protocols/mifare_desfire.h>
 
 #define NFC_DEV_NAME_MAX_LEN 22
 #define NFC_READER_DATA_MAX_SIZE 64
@@ -51,6 +51,7 @@ typedef struct {
         MfClassicData mf_classic_data;
         MifareDesfireData mf_df_data;
     };
+    string_t parsed_data;
 } NfcDeviceData;
 
 typedef struct {
@@ -78,6 +79,8 @@ bool nfc_device_save_shadow(NfcDevice* dev, const char* dev_name);
 
 bool nfc_device_load(NfcDevice* dev, const char* file_path, bool show_dialog);
 
+bool nfc_device_load_key_cache(NfcDevice* dev);
+
 bool nfc_file_select(NfcDevice* dev);
 
 void nfc_device_data_clear(NfcDeviceData* dev);

+ 0 - 0
applications/nfc/nfc_types.c → lib/nfc/nfc_types.c


+ 0 - 0
applications/nfc/nfc_types.h → lib/nfc/nfc_types.h


+ 510 - 0
lib/nfc/nfc_worker.c

@@ -0,0 +1,510 @@
+#include "nfc_worker_i.h"
+#include <furi_hal.h>
+
+#include <platform.h>
+#include "parsers/nfc_supported_card.h"
+
+#define TAG "NfcWorker"
+
+/***************************** NFC Worker API *******************************/
+
+NfcWorker* nfc_worker_alloc() {
+    NfcWorker* nfc_worker = malloc(sizeof(NfcWorker));
+
+    // Worker thread attributes
+    nfc_worker->thread = furi_thread_alloc();
+    furi_thread_set_name(nfc_worker->thread, "NfcWorker");
+    furi_thread_set_stack_size(nfc_worker->thread, 8192);
+    furi_thread_set_callback(nfc_worker->thread, nfc_worker_task);
+    furi_thread_set_context(nfc_worker->thread, nfc_worker);
+
+    nfc_worker->callback = NULL;
+    nfc_worker->context = NULL;
+    nfc_worker->storage = furi_record_open(RECORD_STORAGE);
+
+    // Initialize rfal
+    while(furi_hal_nfc_is_busy()) {
+        furi_delay_ms(10);
+    }
+    nfc_worker_change_state(nfc_worker, NfcWorkerStateReady);
+
+    if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
+        nfc_worker->debug_pcap_worker = nfc_debug_pcap_alloc(nfc_worker->storage);
+    }
+
+    return nfc_worker;
+}
+
+void nfc_worker_free(NfcWorker* nfc_worker) {
+    furi_assert(nfc_worker);
+
+    furi_thread_free(nfc_worker->thread);
+
+    furi_record_close(RECORD_STORAGE);
+
+    if(nfc_worker->debug_pcap_worker) nfc_debug_pcap_free(nfc_worker->debug_pcap_worker);
+
+    free(nfc_worker);
+}
+
+NfcWorkerState nfc_worker_get_state(NfcWorker* nfc_worker) {
+    return nfc_worker->state;
+}
+
+void nfc_worker_start(
+    NfcWorker* nfc_worker,
+    NfcWorkerState state,
+    NfcDeviceData* dev_data,
+    NfcWorkerCallback callback,
+    void* context) {
+    furi_assert(nfc_worker);
+    furi_assert(dev_data);
+    while(furi_hal_nfc_is_busy()) {
+        furi_delay_ms(10);
+    }
+
+    nfc_worker->callback = callback;
+    nfc_worker->context = context;
+    nfc_worker->dev_data = dev_data;
+    nfc_worker_change_state(nfc_worker, state);
+    furi_thread_start(nfc_worker->thread);
+}
+
+void nfc_worker_stop(NfcWorker* nfc_worker) {
+    furi_assert(nfc_worker);
+    if(nfc_worker->state == NfcWorkerStateBroken || nfc_worker->state == NfcWorkerStateReady) {
+        return;
+    }
+    furi_hal_nfc_stop();
+    nfc_worker_change_state(nfc_worker, NfcWorkerStateStop);
+    furi_thread_join(nfc_worker->thread);
+}
+
+void nfc_worker_change_state(NfcWorker* nfc_worker, NfcWorkerState state) {
+    nfc_worker->state = state;
+}
+
+/***************************** NFC Worker Thread *******************************/
+
+int32_t nfc_worker_task(void* context) {
+    NfcWorker* nfc_worker = context;
+
+    furi_hal_nfc_exit_sleep();
+
+    if(nfc_worker->state == NfcWorkerStateRead) {
+        nfc_worker_read(nfc_worker);
+    } else if(nfc_worker->state == NfcWorkerStateUidEmulate) {
+        nfc_worker_emulate_uid(nfc_worker);
+    } else if(nfc_worker->state == NfcWorkerStateEmulateApdu) {
+        nfc_worker_emulate_apdu(nfc_worker);
+    } else if(nfc_worker->state == NfcWorkerStateMfUltralightEmulate) {
+        nfc_worker_emulate_mf_ultralight(nfc_worker);
+    } else if(nfc_worker->state == NfcWorkerStateMfClassicEmulate) {
+        nfc_worker_emulate_mf_classic(nfc_worker);
+    } else if(nfc_worker->state == NfcWorkerStateMfClassicUserDictAttack) {
+        nfc_worker_mf_classic_dict_attack(nfc_worker, MfClassicDictTypeUser);
+    } else if(nfc_worker->state == NfcWorkerStateMfClassicFlipperDictAttack) {
+        nfc_worker_mf_classic_dict_attack(nfc_worker, MfClassicDictTypeFlipper);
+    }
+    furi_hal_nfc_sleep();
+    nfc_worker_change_state(nfc_worker, NfcWorkerStateReady);
+
+    return 0;
+}
+
+static bool nfc_worker_read_mf_ultralight(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) {
+    bool read_success = false;
+    MfUltralightReader reader = {};
+    MfUltralightData data = {};
+
+    nfc_debug_pcap_prepare_tx_rx(nfc_worker->debug_pcap_worker, tx_rx, false);
+    do {
+        // Read card
+        if(!furi_hal_nfc_detect(&nfc_worker->dev_data->nfc_data, 200)) break;
+        if(!mf_ul_read_card(tx_rx, &reader, &data)) break;
+        // Copy data
+        nfc_worker->dev_data->mf_ul_data = data;
+        read_success = true;
+    } while(false);
+
+    return read_success;
+}
+
+static bool nfc_worker_read_mf_classic(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) {
+    furi_assert(nfc_worker->callback);
+    bool read_success = false;
+
+    nfc_debug_pcap_prepare_tx_rx(nfc_worker->debug_pcap_worker, tx_rx, false);
+    do {
+        // Try to read supported card
+        FURI_LOG_I(TAG, "Try read supported card ...");
+        for(size_t i = 0; i < NfcSupportedCardTypeEnd; i++) {
+            if(nfc_supported_card[i].protocol == NfcDeviceProtocolMifareClassic) {
+                if(nfc_supported_card[i].verify(nfc_worker, tx_rx)) {
+                    if(nfc_supported_card[i].read(nfc_worker, tx_rx)) {
+                        read_success = true;
+                        nfc_supported_card[i].parse(nfc_worker);
+                    }
+                }
+            }
+        }
+        if(read_success) break;
+        // Try to read card with key cache
+        FURI_LOG_I(TAG, "Search for key cache ...");
+        if(nfc_worker->callback(NfcWorkerEventReadMfClassicLoadKeyCache, nfc_worker->context)) {
+            FURI_LOG_I(TAG, "Load keys cache success. Start reading");
+            uint8_t sectors_read =
+                mf_classic_update_card(tx_rx, &nfc_worker->dev_data->mf_classic_data);
+            uint8_t sectors_total =
+                mf_classic_get_total_sectors_num(nfc_worker->dev_data->mf_classic_data.type);
+            FURI_LOG_I(TAG, "Read %d sectors out of %d total", sectors_read, sectors_total);
+            read_success = (sectors_read == sectors_total);
+        }
+    } while(false);
+
+    return read_success;
+}
+
+static bool nfc_worker_read_mf_desfire(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) {
+    bool read_success = false;
+    MifareDesfireData* data = &nfc_worker->dev_data->mf_df_data;
+
+    nfc_debug_pcap_prepare_tx_rx(nfc_worker->debug_pcap_worker, tx_rx, false);
+    do {
+        if(!furi_hal_nfc_detect(&nfc_worker->dev_data->nfc_data, 300)) break;
+        if(!mf_df_read_card(tx_rx, data)) break;
+        read_success = true;
+    } while(false);
+
+    return read_success;
+}
+
+static bool nfc_worker_read_bank_card(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) {
+    bool read_success = false;
+    EmvApplication emv_app = {};
+    EmvData* result = &nfc_worker->dev_data->emv_data;
+
+    nfc_debug_pcap_prepare_tx_rx(nfc_worker->debug_pcap_worker, tx_rx, false);
+    do {
+        // Read card
+        if(!furi_hal_nfc_detect(&nfc_worker->dev_data->nfc_data, 300)) break;
+        if(!emv_read_bank_card(tx_rx, &emv_app)) break;
+        // Copy data
+        // TODO Set EmvData to reader or like in mifare ultralight!
+        result->number_len = emv_app.card_number_len;
+        memcpy(result->number, emv_app.card_number, result->number_len);
+        result->aid_len = emv_app.aid_len;
+        memcpy(result->aid, emv_app.aid, result->aid_len);
+        if(emv_app.name_found) {
+            memcpy(result->name, emv_app.name, sizeof(emv_app.name));
+        }
+        if(emv_app.exp_month) {
+            result->exp_mon = emv_app.exp_month;
+            result->exp_year = emv_app.exp_year;
+        }
+        if(emv_app.country_code) {
+            result->country_code = emv_app.country_code;
+        }
+        if(emv_app.currency_code) {
+            result->currency_code = emv_app.currency_code;
+        }
+        read_success = true;
+    } while(false);
+
+    return read_success;
+}
+
+static bool nfc_worker_read_nfca(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) {
+    FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data;
+
+    bool card_read = false;
+    furi_hal_nfc_sleep();
+    if(mf_ul_check_card_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak)) {
+        FURI_LOG_I(TAG, "Mifare Ultralight / NTAG detected");
+        nfc_worker->dev_data->protocol = NfcDeviceProtocolMifareUl;
+        card_read = nfc_worker_read_mf_ultralight(nfc_worker, tx_rx);
+    } else if(mf_classic_check_card_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak)) {
+        FURI_LOG_I(TAG, "Mifare Classic detected");
+        nfc_worker->dev_data->protocol = NfcDeviceProtocolMifareClassic;
+        nfc_worker->dev_data->mf_classic_data.type =
+            mf_classic_get_classic_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak);
+        card_read = nfc_worker_read_mf_classic(nfc_worker, tx_rx);
+    } else if(mf_df_check_card_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak)) {
+        FURI_LOG_I(TAG, "Mifare DESFire detected");
+        nfc_worker->dev_data->protocol = NfcDeviceProtocolMifareDesfire;
+        if(!nfc_worker_read_mf_desfire(nfc_worker, tx_rx)) {
+            FURI_LOG_I(TAG, "Unknown card. Save UID");
+            nfc_worker->dev_data->protocol = NfcDeviceProtocolUnknown;
+        }
+        card_read = true;
+    } else if(nfc_data->interface == FuriHalNfcInterfaceIsoDep) {
+        FURI_LOG_I(TAG, "ISO14443-4 card detected");
+        nfc_worker->dev_data->protocol = NfcDeviceProtocolEMV;
+        if(!nfc_worker_read_bank_card(nfc_worker, tx_rx)) {
+            FURI_LOG_I(TAG, "Unknown card. Save UID");
+            nfc_worker->dev_data->protocol = NfcDeviceProtocolUnknown;
+        }
+        card_read = true;
+    }
+
+    return card_read;
+}
+
+void nfc_worker_read(NfcWorker* nfc_worker) {
+    furi_assert(nfc_worker);
+    furi_assert(nfc_worker->callback);
+
+    nfc_device_data_clear(nfc_worker->dev_data);
+    NfcDeviceData* dev_data = nfc_worker->dev_data;
+    FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data;
+    FuriHalNfcTxRxContext tx_rx = {};
+    NfcWorkerEvent event = 0;
+    bool card_not_detected_notified = false;
+
+    while(nfc_worker->state == NfcWorkerStateRead) {
+        if(furi_hal_nfc_detect(nfc_data, 300)) {
+            // Process first found device
+            nfc_worker->callback(NfcWorkerEventCardDetected, nfc_worker->context);
+            card_not_detected_notified = false;
+            if(nfc_data->type == FuriHalNfcTypeA) {
+                if(nfc_worker_read_nfca(nfc_worker, &tx_rx)) {
+                    if(dev_data->protocol == NfcDeviceProtocolMifareUl) {
+                        event = NfcWorkerEventReadMfUltralight;
+                        break;
+                    } else if(dev_data->protocol == NfcDeviceProtocolMifareClassic) {
+                        event = NfcWorkerEventReadMfClassicDone;
+                        break;
+                    } else if(dev_data->protocol == NfcDeviceProtocolMifareDesfire) {
+                        event = NfcWorkerEventReadMfDesfire;
+                        break;
+                    } else if(dev_data->protocol == NfcDeviceProtocolEMV) {
+                        event = NfcWorkerEventReadBankCard;
+                        break;
+                    } else if(dev_data->protocol == NfcDeviceProtocolUnknown) {
+                        event = NfcWorkerEventReadUidNfcA;
+                        break;
+                    }
+                } else {
+                    if(dev_data->protocol == NfcDeviceProtocolMifareClassic) {
+                        event = NfcWorkerEventReadMfClassicDictAttackRequired;
+                        break;
+                    }
+                }
+            } else if(nfc_data->type == FuriHalNfcTypeB) {
+                event = NfcWorkerEventReadUidNfcB;
+                break;
+            } else if(nfc_data->type == FuriHalNfcTypeF) {
+                event = NfcWorkerEventReadUidNfcF;
+                break;
+            } else if(nfc_data->type == FuriHalNfcTypeV) {
+                event = NfcWorkerEventReadUidNfcV;
+                break;
+            }
+        } else {
+            if(!card_not_detected_notified) {
+                nfc_worker->callback(NfcWorkerEventNoCardDetected, nfc_worker->context);
+                card_not_detected_notified = true;
+            }
+        }
+        furi_hal_nfc_sleep();
+        furi_delay_ms(100);
+    }
+    // Notify caller and exit
+    if(event > NfcWorkerEventReserved) {
+        nfc_worker->callback(event, nfc_worker->context);
+    }
+}
+
+void nfc_worker_emulate_uid(NfcWorker* nfc_worker) {
+    FuriHalNfcTxRxContext tx_rx = {};
+    nfc_debug_pcap_prepare_tx_rx(nfc_worker->debug_pcap_worker, &tx_rx, true);
+    FuriHalNfcDevData* data = &nfc_worker->dev_data->nfc_data;
+    NfcReaderRequestData* reader_data = &nfc_worker->dev_data->reader_data;
+
+    while(nfc_worker->state == NfcWorkerStateUidEmulate) {
+        if(furi_hal_nfc_listen(data->uid, data->uid_len, data->atqa, data->sak, true, 100)) {
+            if(furi_hal_nfc_tx_rx(&tx_rx, 100)) {
+                reader_data->size = tx_rx.rx_bits / 8;
+                if(reader_data->size > 0) {
+                    memcpy(reader_data->data, tx_rx.rx_data, reader_data->size);
+                    if(nfc_worker->callback) {
+                        nfc_worker->callback(NfcWorkerEventSuccess, nfc_worker->context);
+                    }
+                }
+            } else {
+                FURI_LOG_E(TAG, "Failed to get reader commands");
+            }
+        }
+    }
+}
+
+void nfc_worker_emulate_apdu(NfcWorker* nfc_worker) {
+    FuriHalNfcTxRxContext tx_rx = {};
+    nfc_debug_pcap_prepare_tx_rx(nfc_worker->debug_pcap_worker, &tx_rx, true);
+    FuriHalNfcDevData params = {
+        .uid = {0xCF, 0x72, 0xd4, 0x40},
+        .uid_len = 4,
+        .atqa = {0x00, 0x04},
+        .sak = 0x20,
+        .type = FuriHalNfcTypeA,
+    };
+
+    while(nfc_worker->state == NfcWorkerStateEmulateApdu) {
+        if(furi_hal_nfc_listen(params.uid, params.uid_len, params.atqa, params.sak, false, 300)) {
+            FURI_LOG_D(TAG, "POS terminal detected");
+            if(emv_card_emulation(&tx_rx)) {
+                FURI_LOG_D(TAG, "EMV card emulated");
+            }
+        } else {
+            FURI_LOG_D(TAG, "Can't find reader");
+        }
+        furi_hal_nfc_sleep();
+        furi_delay_ms(20);
+    }
+}
+
+void nfc_worker_emulate_mf_ultralight(NfcWorker* nfc_worker) {
+    FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data;
+    MfUltralightEmulator emulator = {};
+    mf_ul_prepare_emulation(&emulator, &nfc_worker->dev_data->mf_ul_data);
+    while(nfc_worker->state == NfcWorkerStateMfUltralightEmulate) {
+        mf_ul_reset_emulation(&emulator, true);
+        furi_hal_nfc_emulate_nfca(
+            nfc_data->uid,
+            nfc_data->uid_len,
+            nfc_data->atqa,
+            nfc_data->sak,
+            mf_ul_prepare_emulation_response,
+            &emulator,
+            5000);
+        // Check if data was modified
+        if(emulator.data_changed) {
+            nfc_worker->dev_data->mf_ul_data = emulator.data;
+            if(nfc_worker->callback) {
+                nfc_worker->callback(NfcWorkerEventSuccess, nfc_worker->context);
+            }
+            emulator.data_changed = false;
+        }
+    }
+}
+
+void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker, MfClassicDictType type) {
+    furi_assert(nfc_worker);
+    furi_assert(nfc_worker->callback);
+
+    MfClassicData* data = &nfc_worker->dev_data->mf_classic_data;
+    uint32_t total_sectors = mf_classic_get_total_sectors_num(data->type);
+    uint64_t key = 0;
+    FuriHalNfcTxRxContext tx_rx = {};
+    bool card_found_notified = true;
+    bool card_removed_notified = false;
+
+    // Load dictionary
+    MfClassicDict* dict = mf_classic_dict_alloc(type);
+    if(!dict) {
+        FURI_LOG_E(TAG, "Dictionary not found");
+        nfc_worker->callback(NfcWorkerEventNoDictFound, nfc_worker->context);
+        mf_classic_dict_free(dict);
+        return;
+    }
+
+    FURI_LOG_D(TAG, "Start Dictionary attack");
+    for(size_t i = 0; i < total_sectors; i++) {
+        FURI_LOG_I(TAG, "Sector %d", i);
+        nfc_worker->callback(NfcWorkerEventNewSector, nfc_worker->context);
+        uint8_t block_num = mf_classic_get_sector_trailer_block_num_by_sector(i);
+        if(mf_classic_is_sector_read(data, i)) continue;
+        bool is_key_a_found = mf_classic_is_key_found(data, i, MfClassicKeyA);
+        bool is_key_b_found = mf_classic_is_key_found(data, i, MfClassicKeyB);
+        while(mf_classic_dict_get_next_key(dict, &key)) {
+            furi_hal_nfc_sleep();
+            if(furi_hal_nfc_activate_nfca(200, NULL)) {
+                furi_hal_nfc_sleep();
+                if(!card_found_notified) {
+                    nfc_worker->callback(NfcWorkerEventCardDetected, nfc_worker->context);
+                    card_found_notified = true;
+                    card_removed_notified = false;
+                }
+                FURI_LOG_D(
+                    TAG,
+                    "Try to auth to sector %d with key %04lx%08lx",
+                    i,
+                    (uint32_t)(key >> 32),
+                    (uint32_t)key);
+                if(!is_key_a_found) {
+                    is_key_a_found = mf_classic_is_key_found(data, i, MfClassicKeyA);
+                    if(mf_classic_authenticate(&tx_rx, block_num, key, MfClassicKeyA)) {
+                        mf_classic_set_key_found(data, i, MfClassicKeyA, key);
+                        nfc_worker->callback(NfcWorkerEventFoundKeyA, nfc_worker->context);
+                    }
+                    furi_hal_nfc_sleep();
+                }
+                if(!is_key_b_found) {
+                    is_key_b_found = mf_classic_is_key_found(data, i, MfClassicKeyB);
+                    if(mf_classic_authenticate(&tx_rx, block_num, key, MfClassicKeyB)) {
+                        mf_classic_set_key_found(data, i, MfClassicKeyB, key);
+                        nfc_worker->callback(NfcWorkerEventFoundKeyB, nfc_worker->context);
+                    }
+                }
+                if(is_key_a_found && is_key_b_found) break;
+                if(!((nfc_worker->state == NfcWorkerStateMfClassicUserDictAttack) ||
+                     (nfc_worker->state == NfcWorkerStateMfClassicFlipperDictAttack)))
+                    break;
+            } else {
+                if(!card_removed_notified) {
+                    nfc_worker->callback(NfcWorkerEventNoCardDetected, nfc_worker->context);
+                    card_removed_notified = true;
+                    card_found_notified = false;
+                }
+                if(!((nfc_worker->state == NfcWorkerStateMfClassicUserDictAttack) ||
+                     (nfc_worker->state == NfcWorkerStateMfClassicFlipperDictAttack)))
+                    break;
+            }
+        }
+        if(!((nfc_worker->state == NfcWorkerStateMfClassicUserDictAttack) ||
+             (nfc_worker->state == NfcWorkerStateMfClassicFlipperDictAttack)))
+            break;
+        mf_classic_read_sector(&tx_rx, data, i);
+        mf_classic_dict_rewind(dict);
+    }
+    mf_classic_dict_free(dict);
+    if((nfc_worker->state == NfcWorkerStateMfClassicUserDictAttack) ||
+       (nfc_worker->state == NfcWorkerStateMfClassicFlipperDictAttack)) {
+        nfc_worker->callback(NfcWorkerEventSuccess, nfc_worker->context);
+    } else {
+        nfc_worker->callback(NfcWorkerEventAborted, nfc_worker->context);
+    }
+}
+
+void nfc_worker_emulate_mf_classic(NfcWorker* nfc_worker) {
+    FuriHalNfcTxRxContext tx_rx = {};
+    nfc_debug_pcap_prepare_tx_rx(nfc_worker->debug_pcap_worker, &tx_rx, true);
+    FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data;
+    MfClassicEmulator emulator = {
+        .cuid = nfc_util_bytes2num(&nfc_data->uid[nfc_data->uid_len - 4], 4),
+        .data = nfc_worker->dev_data->mf_classic_data,
+        .data_changed = false,
+    };
+    NfcaSignal* nfca_signal = nfca_signal_alloc();
+    tx_rx.nfca_signal = nfca_signal;
+
+    rfal_platform_spi_acquire();
+
+    furi_hal_nfc_listen_start(nfc_data);
+    while(nfc_worker->state == NfcWorkerStateMfClassicEmulate) {
+        if(furi_hal_nfc_listen_rx(&tx_rx, 300)) {
+            mf_classic_emulator(&emulator, &tx_rx);
+        }
+    }
+    if(emulator.data_changed) {
+        nfc_worker->dev_data->mf_classic_data = emulator.data;
+        if(nfc_worker->callback) {
+            nfc_worker->callback(NfcWorkerEventSuccess, nfc_worker->context);
+        }
+        emulator.data_changed = false;
+    }
+
+    nfca_signal_free(nfca_signal);
+
+    rfal_platform_spi_release();
+}

+ 23 - 13
applications/nfc/nfc_worker.h → lib/nfc/nfc_worker.h

@@ -10,17 +10,15 @@ typedef enum {
     NfcWorkerStateBroken,
     NfcWorkerStateReady,
     // Main worker states
-    NfcWorkerStateDetect,
-    NfcWorkerStateEmulate,
-    NfcWorkerStateReadEMVApp,
-    NfcWorkerStateReadEMVData,
+    NfcWorkerStateRead,
+    NfcWorkerStateUidEmulate,
+    NfcWorkerStateMfUltralightEmulate,
+    NfcWorkerStateMfClassicEmulate,
+    NfcWorkerStateMfClassicUserDictAttack,
+    NfcWorkerStateMfClassicFlipperDictAttack,
+    // Debug
     NfcWorkerStateEmulateApdu,
     NfcWorkerStateField,
-    NfcWorkerStateReadMifareUltralight,
-    NfcWorkerStateEmulateMifareUltralight,
-    NfcWorkerStateReadMifareClassic,
-    NfcWorkerStateEmulateMifareClassic,
-    NfcWorkerStateReadMifareDesfire,
     // Transition
     NfcWorkerStateStop,
 } NfcWorkerState;
@@ -29,21 +27,33 @@ typedef enum {
     // Reserve first 50 events for application events
     NfcWorkerEventReserved = 50,
 
+    // Nfc read events
+    NfcWorkerEventReadUidNfcB,
+    NfcWorkerEventReadUidNfcV,
+    NfcWorkerEventReadUidNfcF,
+    NfcWorkerEventReadUidNfcA,
+    NfcWorkerEventReadMfUltralight,
+    NfcWorkerEventReadMfDesfire,
+    NfcWorkerEventReadMfClassicDone,
+    NfcWorkerEventReadMfClassicLoadKeyCache,
+    NfcWorkerEventReadMfClassicDictAttackRequired,
+    NfcWorkerEventReadBankCard,
+
     // Nfc worker common events
     NfcWorkerEventSuccess,
     NfcWorkerEventFail,
+    NfcWorkerEventAborted,
+    NfcWorkerEventCardDetected,
     NfcWorkerEventNoCardDetected,
+
     // Mifare Classic events
     NfcWorkerEventNoDictFound,
-    NfcWorkerEventDetectedClassic1k,
-    NfcWorkerEventDetectedClassic4k,
     NfcWorkerEventNewSector,
     NfcWorkerEventFoundKeyA,
     NfcWorkerEventFoundKeyB,
-    NfcWorkerEventStartReading,
 } NfcWorkerEvent;
 
-typedef void (*NfcWorkerCallback)(NfcWorkerEvent event, void* context);
+typedef bool (*NfcWorkerCallback)(NfcWorkerEvent event, void* context);
 
 NfcWorker* nfc_worker_alloc();
 

+ 48 - 0
lib/nfc/nfc_worker_i.h

@@ -0,0 +1,48 @@
+#pragma once
+
+#include "nfc_worker.h"
+
+#include <furi.h>
+#include <lib/toolbox/stream/file_stream.h>
+
+#include <lib/nfc/protocols/nfc_util.h>
+#include <lib/nfc/protocols/emv.h>
+#include <lib/nfc/protocols/mifare_common.h>
+#include <lib/nfc/protocols/mifare_ultralight.h>
+#include <lib/nfc/protocols/mifare_classic.h>
+#include <lib/nfc/protocols/mifare_desfire.h>
+#include <lib/nfc/protocols/nfca.h>
+
+#include "helpers/mf_classic_dict.h"
+#include "helpers/nfc_debug_pcap.h"
+
+struct NfcWorker {
+    FuriThread* thread;
+    Storage* storage;
+    Stream* dict_stream;
+
+    NfcDeviceData* dev_data;
+
+    NfcWorkerCallback callback;
+    void* context;
+
+    NfcWorkerState state;
+
+    NfcDebugPcapWorker* debug_pcap_worker;
+};
+
+void nfc_worker_change_state(NfcWorker* nfc_worker, NfcWorkerState state);
+
+int32_t nfc_worker_task(void* context);
+
+void nfc_worker_read(NfcWorker* nfc_worker);
+
+void nfc_worker_emulate_uid(NfcWorker* nfc_worker);
+
+void nfc_worker_emulate_mf_ultralight(NfcWorker* nfc_worker);
+
+void nfc_worker_emulate_mf_classic(NfcWorker* nfc_worker);
+
+void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker, MfClassicDictType type);
+
+void nfc_worker_emulate_apdu(NfcWorker* nfc_worker);

+ 12 - 0
lib/nfc/parsers/nfc_supported_card.c

@@ -0,0 +1,12 @@
+#include "nfc_supported_card.h"
+
+#include "troyka_parser.h"
+
+NfcSupportedCard nfc_supported_card[NfcSupportedCardTypeEnd] = {
+    [NfcSupportedCardTypeTroyka] = {
+        .protocol = NfcDeviceProtocolMifareClassic,
+        .verify = troyka_parser_verify,
+        .read = troyka_parser_read,
+        .parse = troyka_parser_parse,
+    },
+};

+ 27 - 0
lib/nfc/parsers/nfc_supported_card.h

@@ -0,0 +1,27 @@
+#pragma once
+
+#include <furi_hal_nfc.h>
+#include "../nfc_worker.h"
+
+#include <m-string.h>
+
+typedef enum {
+    NfcSupportedCardTypeTroyka,
+
+    NfcSupportedCardTypeEnd,
+} NfcSupportedCardType;
+
+typedef bool (*NfcSupportedCardVerify)(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx);
+
+typedef bool (*NfcSupportedCardRead)(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx);
+
+typedef bool (*NfcSupportedCardParse)(NfcWorker* nfc_worker);
+
+typedef struct {
+    NfcProtocol protocol;
+    NfcSupportedCardVerify verify;
+    NfcSupportedCardRead read;
+    NfcSupportedCardParse parse;
+} NfcSupportedCard;
+
+extern NfcSupportedCard nfc_supported_card[NfcSupportedCardTypeEnd];

+ 70 - 0
lib/nfc/parsers/troyka_parser.c

@@ -0,0 +1,70 @@
+#include "nfc_supported_card.h"
+
+#include <gui/modules/widget.h>
+#include <nfc_worker_i.h>
+
+static const MfClassicAuthContext troyka_keys[] = {
+    {.sector = 0, .key_a = 0xa0a1a2a3a4a5, .key_b = 0xfbf225dc5d58},
+    {.sector = 1, .key_a = 0xa82607b01c0d, .key_b = 0x2910989b6880},
+    {.sector = 2, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99},
+    {.sector = 3, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99},
+    {.sector = 4, .key_a = 0x73068f118c13, .key_b = 0x2b7f3253fac5},
+    {.sector = 5, .key_a = 0xfbc2793d540b, .key_b = 0xd3a297dc2698},
+    {.sector = 6, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99},
+    {.sector = 7, .key_a = 0xae3d65a3dad4, .key_b = 0x0f1c63013dba},
+    {.sector = 8, .key_a = 0xa73f5dc1d333, .key_b = 0xe35173494a81},
+    {.sector = 9, .key_a = 0x69a32f1c2f19, .key_b = 0x6b8bd9860763},
+    {.sector = 10, .key_a = 0x9becdf3d9273, .key_b = 0xf8493407799d},
+    {.sector = 11, .key_a = 0x08b386463229, .key_b = 0x5efbaecef46b},
+    {.sector = 12, .key_a = 0xcd4c61c26e3d, .key_b = 0x31c7610de3b0},
+    {.sector = 13, .key_a = 0xa82607b01c0d, .key_b = 0x2910989b6880},
+    {.sector = 14, .key_a = 0x0e8f64340ba4, .key_b = 0x4acec1205d75},
+    {.sector = 15, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99},
+};
+
+bool troyka_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) {
+    furi_assert(nfc_worker);
+    UNUSED(nfc_worker);
+
+    MfClassicAuthContext auth_ctx = {
+        .key_a = MF_CLASSIC_NO_KEY,
+        .key_b = MF_CLASSIC_NO_KEY,
+        .sector = 8,
+    };
+    return mf_classic_auth_attempt(tx_rx, &auth_ctx, 0xa73f5dc1d333);
+}
+
+bool troyka_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) {
+    furi_assert(nfc_worker);
+
+    MfClassicReader reader = {};
+    FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data;
+    mf_classic_get_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak, &reader);
+    for(size_t i = 0; i < COUNT_OF(troyka_keys); i++) {
+        mf_classic_reader_add_sector(
+            &reader, troyka_keys[i].sector, troyka_keys[i].key_a, troyka_keys[i].key_b);
+    }
+
+    return mf_classic_read_card(tx_rx, &reader, &nfc_worker->dev_data->mf_classic_data) == 16;
+}
+
+bool troyka_parser_parse(NfcWorker* nfc_worker) {
+    MfClassicData* data = &nfc_worker->dev_data->mf_classic_data;
+    uint8_t* temp_ptr = &data->block[8 * 4 + 1].value[5];
+    uint16_t balance = ((temp_ptr[0] << 8) | temp_ptr[1]) / 25;
+    temp_ptr = &data->block[8 * 4].value[3];
+    uint32_t number = 0;
+    for(size_t i = 0; i < 4; i++) {
+        number <<= 8;
+        number |= temp_ptr[i];
+    }
+    number >>= 4;
+
+    string_printf(
+        nfc_worker->dev_data->parsed_data,
+        "Troyka Transport card\nNumber: %ld\nBalance: %d rub",
+        number,
+        balance);
+
+    return true;
+}

+ 9 - 0
lib/nfc/parsers/troyka_parser.h

@@ -0,0 +1,9 @@
+#pragma once
+
+#include "nfc_supported_card.h"
+
+bool troyka_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx);
+
+bool troyka_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx);
+
+bool troyka_parser_parse(NfcWorker* nfc_worker);

+ 0 - 0
lib/nfc_protocols/crypto1.c → lib/nfc/protocols/crypto1.c


+ 0 - 0
lib/nfc_protocols/crypto1.h → lib/nfc/protocols/crypto1.h


+ 0 - 0
lib/nfc_protocols/emv.c → lib/nfc/protocols/emv.c


+ 0 - 0
lib/nfc_protocols/emv.h → lib/nfc/protocols/emv.h


+ 269 - 56
lib/nfc_protocols/mifare_classic.c → lib/nfc/protocols/mifare_classic.c

@@ -25,6 +25,16 @@ typedef enum {
     MfClassicActionACWrite,
 } MfClassicAction;
 
+const char* mf_classic_get_type_str(MfClassicType type) {
+    if(type == MfClassicType1k) {
+        return "MIFARE Classic 1K";
+    } else if(type == MfClassicType4k) {
+        return "MIFARE Classic 4K";
+    } else {
+        return "Unknown";
+    }
+}
+
 static uint8_t mf_classic_get_first_block_num_of_sector(uint8_t sector) {
     furi_assert(sector < 40);
     if(sector < 32) {
@@ -34,7 +44,16 @@ static uint8_t mf_classic_get_first_block_num_of_sector(uint8_t sector) {
     }
 }
 
-static uint8_t mf_classic_get_sector_by_block(uint8_t block) {
+uint8_t mf_classic_get_sector_trailer_block_num_by_sector(uint8_t sector) {
+    furi_assert(sector < 40);
+    if(sector < 32) {
+        return sector * 4 + 3;
+    } else {
+        return 32 * 4 + (sector - 32) * 16 + 15;
+    }
+}
+
+uint8_t mf_classic_get_sector_by_block(uint8_t block) {
     if(block < 128) {
         return (block | 0x03) / 4;
     } else {
@@ -47,7 +66,7 @@ static uint8_t mf_classic_get_blocks_num_in_sector(uint8_t sector) {
     return sector < 32 ? 4 : 16;
 }
 
-static uint8_t mf_classic_get_sector_trailer(uint8_t block) {
+uint8_t mf_classic_get_sector_trailer_num_by_block(uint8_t block) {
     if(block < 128) {
         return block | 0x03;
     } else {
@@ -55,15 +74,21 @@ static uint8_t mf_classic_get_sector_trailer(uint8_t block) {
     }
 }
 
-static bool mf_classic_is_sector_trailer(uint8_t block) {
-    return block == mf_classic_get_sector_trailer(block);
+bool mf_classic_is_sector_trailer(uint8_t block) {
+    return block == mf_classic_get_sector_trailer_num_by_block(block);
 }
 
-uint8_t mf_classic_get_total_sectors_num(MfClassicReader* reader) {
-    furi_assert(reader);
-    if(reader->type == MfClassicType1k) {
+MfClassicSectorTrailer*
+    mf_classic_get_sector_trailer_by_sector(MfClassicData* data, uint8_t sector) {
+    furi_assert(data);
+    uint8_t sec_tr_block_num = mf_classic_get_sector_trailer_block_num_by_sector(sector);
+    return (MfClassicSectorTrailer*)data->block[sec_tr_block_num].value;
+}
+
+uint8_t mf_classic_get_total_sectors_num(MfClassicType type) {
+    if(type == MfClassicType1k) {
         return MF_CLASSIC_1K_TOTAL_SECTORS_NUM;
-    } else if(reader->type == MfClassicType4k) {
+    } else if(type == MfClassicType4k) {
         return MF_CLASSIC_4K_TOTAL_SECTORS_NUM;
     } else {
         return 0;
@@ -80,6 +105,104 @@ static uint16_t mf_classic_get_total_block_num(MfClassicType type) {
     }
 }
 
+bool mf_classic_is_block_read(MfClassicData* data, uint8_t block_num) {
+    furi_assert(data);
+
+    return (FURI_BIT(data->block_read_mask[block_num / 32], block_num % 32) == 1);
+}
+
+void mf_classic_set_block_read(MfClassicData* data, uint8_t block_num, MfClassicBlock* block_data) {
+    furi_assert(data);
+
+    if(mf_classic_is_sector_trailer(block_num)) {
+        memcpy(&data->block[block_num].value[6], &block_data->value[6], 4);
+    } else {
+        memcpy(data->block[block_num].value, block_data->value, MF_CLASSIC_BLOCK_SIZE);
+    }
+    FURI_BIT_SET(data->block_read_mask[block_num / 32], block_num % 32);
+}
+
+bool mf_classic_is_key_found(MfClassicData* data, uint8_t sector_num, MfClassicKey key_type) {
+    furi_assert(data);
+
+    bool key_found = false;
+    if(key_type == MfClassicKeyA) {
+        key_found = (FURI_BIT(data->key_a_mask, sector_num) == 1);
+    } else if(key_type == MfClassicKeyB) {
+        key_found = (FURI_BIT(data->key_b_mask, sector_num) == 1);
+    }
+
+    return key_found;
+}
+
+void mf_classic_set_key_found(
+    MfClassicData* data,
+    uint8_t sector_num,
+    MfClassicKey key_type,
+    uint64_t key) {
+    furi_assert(data);
+
+    uint8_t key_arr[6] = {};
+    MfClassicSectorTrailer* sec_trailer =
+        mf_classic_get_sector_trailer_by_sector(data, sector_num);
+    nfc_util_num2bytes(key, 6, key_arr);
+    if(key_type == MfClassicKeyA) {
+        memcpy(sec_trailer->key_a, key_arr, sizeof(sec_trailer->key_a));
+        FURI_BIT_SET(data->key_a_mask, sector_num);
+    } else if(key_type == MfClassicKeyB) {
+        memcpy(sec_trailer->key_b, key_arr, sizeof(sec_trailer->key_b));
+        FURI_BIT_SET(data->key_b_mask, sector_num);
+    }
+}
+
+bool mf_classic_is_sector_read(MfClassicData* data, uint8_t sector_num) {
+    furi_assert(data);
+
+    bool sector_read = false;
+    do {
+        if(!mf_classic_is_key_found(data, sector_num, MfClassicKeyA)) break;
+        if(!mf_classic_is_key_found(data, sector_num, MfClassicKeyB)) break;
+        uint8_t start_block = mf_classic_get_first_block_num_of_sector(sector_num);
+        uint8_t total_blocks = mf_classic_get_blocks_num_in_sector(sector_num);
+        uint8_t block_read = true;
+        for(size_t i = start_block; i < start_block + total_blocks; i++) {
+            block_read = mf_classic_is_block_read(data, i);
+            if(!block_read) break;
+        }
+        sector_read = block_read;
+    } while(false);
+
+    return sector_read;
+}
+
+void mf_classic_get_read_sectors_and_keys(
+    MfClassicData* data,
+    uint8_t* sectors_read,
+    uint8_t* keys_found) {
+    furi_assert(data);
+    *sectors_read = 0;
+    *keys_found = 0;
+    uint8_t sectors_total = mf_classic_get_total_sectors_num(data->type);
+    for(size_t i = 0; i < sectors_total; i++) {
+        if(mf_classic_is_key_found(data, i, MfClassicKeyA)) {
+            *keys_found += 1;
+        }
+        if(mf_classic_is_key_found(data, i, MfClassicKeyB)) {
+            *keys_found += 1;
+        }
+        uint8_t first_block = mf_classic_get_first_block_num_of_sector(i);
+        uint8_t total_blocks_in_sec = mf_classic_get_blocks_num_in_sector(i);
+        bool blocks_read = true;
+        for(size_t i = first_block; i < first_block + total_blocks_in_sec; i++) {
+            blocks_read = mf_classic_is_block_read(data, i);
+            if(!blocks_read) break;
+        }
+        if(blocks_read) {
+            *sectors_read += 1;
+        }
+    }
+}
+
 static bool mf_classic_is_allowed_access_sector_trailer(
     MfClassicEmulator* emulator,
     uint8_t block_num,
@@ -126,7 +249,8 @@ static bool mf_classic_is_allowed_access_data_block(
     uint8_t block_num,
     MfClassicKey key,
     MfClassicAction action) {
-    uint8_t* sector_trailer = emulator->data.block[mf_classic_get_sector_trailer(block_num)].value;
+    uint8_t* sector_trailer =
+        emulator->data.block[mf_classic_get_sector_trailer_num_by_block(block_num)].value;
 
     uint8_t sector_block;
     if(block_num <= 128) {
@@ -207,15 +331,18 @@ bool mf_classic_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK) {
     }
 }
 
-bool mf_classic_get_type(
-    uint8_t* uid,
-    uint8_t uid_len,
-    uint8_t ATQA0,
-    uint8_t ATQA1,
-    uint8_t SAK,
-    MfClassicReader* reader) {
+MfClassicType mf_classic_get_classic_type(int8_t ATQA0, uint8_t ATQA1, uint8_t SAK) {
+    UNUSED(ATQA1);
+    if((ATQA0 == 0x44 || ATQA0 == 0x04) && (SAK == 0x08 || SAK == 0x88 || SAK == 0x09)) {
+        return MfClassicType1k;
+    } else if((ATQA0 == 0x42 || ATQA0 == 0x02) && (SAK == 0x18)) {
+        return MfClassicType4k;
+    }
+    return MfClassicType1k;
+}
+
+bool mf_classic_get_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK, MfClassicReader* reader) {
     UNUSED(ATQA1);
-    furi_assert(uid);
     furi_assert(reader);
     memset(reader, 0, sizeof(MfClassicReader));
 
@@ -226,14 +353,6 @@ bool mf_classic_get_type(
     } else {
         return false;
     }
-
-    uint8_t* cuid_start = uid;
-    if(uid_len == 7) {
-        cuid_start = &uid[3];
-    }
-    reader->cuid = (cuid_start[0] << 24) | (cuid_start[1] << 16) | (cuid_start[2] << 8) |
-                   (cuid_start[3]);
-
     return true;
 }
 
@@ -246,7 +365,7 @@ void mf_classic_reader_add_sector(
     furi_assert(sector < MF_CLASSIC_SECTORS_MAX);
     furi_assert((key_a != MF_CLASSIC_NO_KEY) || (key_b != MF_CLASSIC_NO_KEY));
 
-    if(reader->sectors_to_read < MF_CLASSIC_SECTORS_MAX - 1) {
+    if(reader->sectors_to_read < MF_CLASSIC_SECTORS_MAX) {
         reader->sector_reader[reader->sectors_to_read].key_a = key_a;
         reader->sector_reader[reader->sectors_to_read].key_b = key_b;
         reader->sector_reader[reader->sectors_to_read].sector_num = sector;
@@ -254,9 +373,8 @@ void mf_classic_reader_add_sector(
     }
 }
 
-void mf_classic_auth_init_context(MfClassicAuthContext* auth_ctx, uint32_t cuid, uint8_t sector) {
+void mf_classic_auth_init_context(MfClassicAuthContext* auth_ctx, uint8_t sector) {
     furi_assert(auth_ctx);
-    auth_ctx->cuid = cuid;
     auth_ctx->sector = sector;
     auth_ctx->key_a = MF_CLASSIC_NO_KEY;
     auth_ctx->key_b = MF_CLASSIC_NO_KEY;
@@ -264,17 +382,18 @@ void mf_classic_auth_init_context(MfClassicAuthContext* auth_ctx, uint32_t cuid,
 
 static bool mf_classic_auth(
     FuriHalNfcTxRxContext* tx_rx,
-    uint32_t cuid,
     uint32_t block,
     uint64_t key,
     MfClassicKey key_type,
     Crypto1* crypto) {
     bool auth_success = false;
+    uint32_t cuid = 0;
     memset(tx_rx->tx_data, 0, sizeof(tx_rx->tx_data));
     memset(tx_rx->tx_parity, 0, sizeof(tx_rx->tx_parity));
     tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault;
 
     do {
+        if(!furi_hal_nfc_activate_nfca(200, &cuid)) break;
         if(key_type == MfClassicKeyA) {
             tx_rx->tx_data[0] = MF_CLASSIC_AUTH_KEY_A_CMD;
         } else {
@@ -315,6 +434,19 @@ static bool mf_classic_auth(
     return auth_success;
 }
 
+bool mf_classic_authenticate(
+    FuriHalNfcTxRxContext* tx_rx,
+    uint8_t block_num,
+    uint64_t key,
+    MfClassicKey key_type) {
+    furi_assert(tx_rx);
+
+    Crypto1 crypto = {};
+    bool key_found = mf_classic_auth(tx_rx, block_num, key, key_type, &crypto);
+    furi_hal_nfc_sleep();
+    return key_found;
+}
+
 bool mf_classic_auth_attempt(
     FuriHalNfcTxRxContext* tx_rx,
     MfClassicAuthContext* auth_ctx,
@@ -330,7 +462,6 @@ bool mf_classic_auth_attempt(
         // Try AUTH with key A
         if(mf_classic_auth(
                tx_rx,
-               auth_ctx->cuid,
                mf_classic_get_first_block_num_of_sector(auth_ctx->sector),
                key,
                MfClassicKeyA,
@@ -342,14 +473,12 @@ bool mf_classic_auth_attempt(
 
     if(need_halt) {
         furi_hal_nfc_sleep();
-        furi_hal_nfc_activate_nfca(300, &auth_ctx->cuid);
     }
 
     if(auth_ctx->key_b == MF_CLASSIC_NO_KEY) {
         // Try AUTH with key B
         if(mf_classic_auth(
                tx_rx,
-               auth_ctx->cuid,
                mf_classic_get_first_block_num_of_sector(auth_ctx->sector),
                key,
                MfClassicKeyB,
@@ -410,7 +539,60 @@ bool mf_classic_read_block(
     return read_block_success;
 }
 
-bool mf_classic_read_sector(
+void mf_classic_read_sector(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data, uint8_t sec_num) {
+    furi_assert(tx_rx);
+    furi_assert(data);
+
+    furi_hal_nfc_sleep();
+    bool key_a_found = mf_classic_is_key_found(data, sec_num, MfClassicKeyA);
+    bool key_b_found = mf_classic_is_key_found(data, sec_num, MfClassicKeyB);
+    uint8_t start_block = mf_classic_get_first_block_num_of_sector(sec_num);
+    uint8_t total_blocks = mf_classic_get_blocks_num_in_sector(sec_num);
+    MfClassicBlock block_tmp = {};
+    uint64_t key = 0;
+    MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, sec_num);
+    Crypto1 crypto = {};
+
+    uint8_t blocks_read = 0;
+    do {
+        if(!key_a_found) break;
+        FURI_LOG_D(TAG, "Try to read blocks with key A");
+        key = nfc_util_bytes2num(sec_tr->key_a, sizeof(sec_tr->key_a));
+        if(!mf_classic_auth(tx_rx, start_block, key, MfClassicKeyA, &crypto)) break;
+        for(size_t i = start_block; i < start_block + total_blocks; i++) {
+            if(!mf_classic_is_block_read(data, i)) {
+                if(mf_classic_read_block(tx_rx, &crypto, i, &block_tmp)) {
+                    mf_classic_set_block_read(data, i, &block_tmp);
+                    blocks_read++;
+                }
+            } else {
+                blocks_read++;
+            }
+        }
+        FURI_LOG_D(TAG, "Read %d blocks out of %d", blocks_read, total_blocks);
+    } while(false);
+    do {
+        if(blocks_read == total_blocks) break;
+        if(!key_b_found) break;
+        FURI_LOG_D(TAG, "Try to read blocks with key B");
+        key = nfc_util_bytes2num(sec_tr->key_b, sizeof(sec_tr->key_b));
+        furi_hal_nfc_sleep();
+        if(!mf_classic_auth(tx_rx, start_block, key, MfClassicKeyB, &crypto)) break;
+        for(size_t i = start_block; i < start_block + total_blocks; i++) {
+            if(!mf_classic_is_block_read(data, i)) {
+                if(mf_classic_read_block(tx_rx, &crypto, i, &block_tmp)) {
+                    mf_classic_set_block_read(data, i, &block_tmp);
+                    blocks_read++;
+                }
+            } else {
+                blocks_read++;
+            }
+        }
+        FURI_LOG_D(TAG, "Read %d blocks out of %d", blocks_read, total_blocks);
+    } while(false);
+}
+
+static bool mf_classic_read_sector_with_reader(
     FuriHalNfcTxRxContext* tx_rx,
     Crypto1* crypto,
     MfClassicSectorReader* sector_reader,
@@ -419,7 +601,6 @@ bool mf_classic_read_sector(
     furi_assert(sector_reader);
     furi_assert(sector);
 
-    uint32_t cuid = 0;
     uint64_t key;
     MfClassicKey key_type;
     uint8_t first_block;
@@ -428,7 +609,6 @@ bool mf_classic_read_sector(
     furi_hal_nfc_sleep();
     do {
         // Activate card
-        if(!furi_hal_nfc_activate_nfca(200, &cuid)) break;
         first_block = mf_classic_get_first_block_num_of_sector(sector_reader->sector_num);
         if(sector_reader->key_a != MF_CLASSIC_NO_KEY) {
             key = sector_reader->key_a;
@@ -441,7 +621,7 @@ bool mf_classic_read_sector(
         }
 
         // Auth to first block in sector
-        if(!mf_classic_auth(tx_rx, cuid, first_block, key, key_type, crypto)) break;
+        if(!mf_classic_auth(tx_rx, first_block, key, key_type, crypto)) break;
         sector->total_blocks = mf_classic_get_blocks_num_in_sector(sector_reader->sector_num);
 
         // Read blocks
@@ -478,18 +658,26 @@ uint8_t mf_classic_read_card(
     data->key_b_mask = 0;
     MfClassicSector temp_sector = {};
     for(uint8_t i = 0; i < reader->sectors_to_read; i++) {
-        if(mf_classic_read_sector(
+        if(mf_classic_read_sector_with_reader(
                tx_rx, &reader->crypto, &reader->sector_reader[i], &temp_sector)) {
             uint8_t first_block =
                 mf_classic_get_first_block_num_of_sector(reader->sector_reader[i].sector_num);
             for(uint8_t j = 0; j < temp_sector.total_blocks; j++) {
-                data->block[first_block + j] = temp_sector.block[j];
+                mf_classic_set_block_read(data, first_block + j, &temp_sector.block[j]);
             }
             if(reader->sector_reader[i].key_a != MF_CLASSIC_NO_KEY) {
-                data->key_a_mask |= 1 << reader->sector_reader[i].sector_num;
+                mf_classic_set_key_found(
+                    data,
+                    reader->sector_reader[i].sector_num,
+                    MfClassicKeyA,
+                    reader->sector_reader[i].key_a);
             }
             if(reader->sector_reader[i].key_b != MF_CLASSIC_NO_KEY) {
-                data->key_b_mask |= 1 << reader->sector_reader[i].sector_num;
+                mf_classic_set_key_found(
+                    data,
+                    reader->sector_reader[i].sector_num,
+                    MfClassicKeyB,
+                    reader->sector_reader[i].key_b);
             }
             sectors_read++;
         }
@@ -498,6 +686,46 @@ uint8_t mf_classic_read_card(
     return sectors_read;
 }
 
+uint8_t mf_classic_update_card(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data) {
+    furi_assert(tx_rx);
+    furi_assert(data);
+
+    uint8_t sectors_read = 0;
+    Crypto1 crypto = {};
+    uint8_t total_sectors = mf_classic_get_total_sectors_num(data->type);
+    uint64_t key_a = 0;
+    uint64_t key_b = 0;
+    MfClassicSectorReader sec_reader = {};
+    MfClassicSector temp_sector = {};
+
+    for(size_t i = 0; i < total_sectors; i++) {
+        MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, i);
+        // Load key A
+        if(mf_classic_is_key_found(data, i, MfClassicKeyA)) {
+            sec_reader.key_a = nfc_util_bytes2num(sec_tr->key_a, 6);
+        } else {
+            sec_reader.key_a = MF_CLASSIC_NO_KEY;
+        }
+        // Load key B
+        if(mf_classic_is_key_found(data, i, MfClassicKeyB)) {
+            sec_reader.key_b = nfc_util_bytes2num(sec_tr->key_b, 6);
+        } else {
+            sec_reader.key_b = MF_CLASSIC_NO_KEY;
+        }
+        if((key_a != MF_CLASSIC_NO_KEY) || (key_b != MF_CLASSIC_NO_KEY)) {
+            sec_reader.sector_num = i;
+            if(mf_classic_read_sector_with_reader(tx_rx, &crypto, &sec_reader, &temp_sector)) {
+                uint8_t first_block = mf_classic_get_first_block_num_of_sector(i);
+                for(uint8_t j = 0; j < temp_sector.total_blocks; j++) {
+                    mf_classic_set_block_read(data, first_block + j, &temp_sector.block[j]);
+                }
+                sectors_read++;
+            }
+        }
+    }
+    return sectors_read;
+}
+
 void mf_crypto1_decrypt(
     Crypto1* crypto,
     uint8_t* encrypted_data,
@@ -573,7 +801,7 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_
         } else if(plain_data[0] == 0x60 || plain_data[0] == 0x61) {
             uint8_t block = plain_data[1];
             uint64_t key = 0;
-            uint8_t sector_trailer_block = mf_classic_get_sector_trailer(block);
+            uint8_t sector_trailer_block = mf_classic_get_sector_trailer_num_by_block(block);
             MfClassicSectorTrailer* sector_trailer =
                 (MfClassicSectorTrailer*)emulator->data.block[sector_trailer_block].value;
             if(plain_data[0] == 0x60) {
@@ -635,21 +863,6 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_
                 nr,
                 ar);
 
-            // Check if we store valid key
-            if(access_key == MfClassicKeyA) {
-                if(FURI_BIT(emulator->data.key_a_mask, mf_classic_get_sector_by_block(block)) ==
-                   0) {
-                    FURI_LOG_D(TAG, "Unsupported sector key A for block %d", sector_trailer_block);
-                    break;
-                }
-            } else if(access_key == MfClassicKeyB) {
-                if(FURI_BIT(emulator->data.key_b_mask, mf_classic_get_sector_by_block(block)) ==
-                   0) {
-                    FURI_LOG_D(TAG, "Unsupported sector key B for block %d", sector_trailer_block);
-                    break;
-                }
-            }
-
             crypto1_word(&emulator->crypto, nr, 1);
             uint32_t cardRr = ar ^ crypto1_word(&emulator->crypto, 0, 0);
             if(cardRr != prng_successor(nonce, 64)) {

+ 51 - 20
lib/nfc_protocols/mifare_classic.h → lib/nfc/protocols/mifare_classic.h

@@ -14,6 +14,8 @@
 
 #define MF_CLASSIC_NO_KEY (0xFFFFFFFFFFFFFFFF)
 #define MF_CLASSIC_MAX_DATA_SIZE (16)
+#define MF_CLASSIC_KEY_SIZE (6)
+#define MF_CLASSIC_ACCESS_BYTES_SIZE (4)
 
 typedef enum {
     MfClassicType1k,
@@ -30,9 +32,9 @@ typedef struct {
 } MfClassicBlock;
 
 typedef struct {
-    uint8_t key_a[6];
-    uint8_t access_bits[4];
-    uint8_t key_b[6];
+    uint8_t key_a[MF_CLASSIC_KEY_SIZE];
+    uint8_t access_bits[MF_CLASSIC_ACCESS_BYTES_SIZE];
+    uint8_t key_b[MF_CLASSIC_KEY_SIZE];
 } MfClassicSectorTrailer;
 
 typedef struct {
@@ -42,13 +44,13 @@ typedef struct {
 
 typedef struct {
     MfClassicType type;
+    uint32_t block_read_mask[MF_CLASSIC_TOTAL_BLOCKS_MAX / 32];
     uint64_t key_a_mask;
     uint64_t key_b_mask;
     MfClassicBlock block[MF_CLASSIC_TOTAL_BLOCKS_MAX];
 } MfClassicData;
 
 typedef struct {
-    uint32_t cuid;
     uint8_t sector;
     uint64_t key_a;
     uint64_t key_b;
@@ -62,9 +64,8 @@ typedef struct {
 
 typedef struct {
     MfClassicType type;
-    uint32_t cuid;
-    uint8_t sectors_to_read;
     Crypto1 crypto;
+    uint8_t sectors_to_read;
     MfClassicSectorReader sector_reader[MF_CLASSIC_SECTORS_MAX];
 } MfClassicReader;
 
@@ -75,19 +76,51 @@ typedef struct {
     bool data_changed;
 } MfClassicEmulator;
 
+const char* mf_classic_get_type_str(MfClassicType type);
+
 bool mf_classic_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK);
 
-bool mf_classic_get_type(
-    uint8_t* uid,
-    uint8_t uid_len,
-    uint8_t ATQA0,
-    uint8_t ATQA1,
-    uint8_t SAK,
-    MfClassicReader* reader);
+MfClassicType mf_classic_get_classic_type(int8_t ATQA0, uint8_t ATQA1, uint8_t SAK);
+
+bool mf_classic_get_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK, MfClassicReader* reader);
 
-uint8_t mf_classic_get_total_sectors_num(MfClassicReader* reader);
+uint8_t mf_classic_get_total_sectors_num(MfClassicType type);
 
-void mf_classic_auth_init_context(MfClassicAuthContext* auth_ctx, uint32_t cuid, uint8_t sector);
+uint8_t mf_classic_get_sector_trailer_block_num_by_sector(uint8_t sector);
+
+bool mf_classic_is_sector_trailer(uint8_t block);
+
+uint8_t mf_classic_get_sector_by_block(uint8_t block);
+
+bool mf_classic_is_key_found(MfClassicData* data, uint8_t sector_num, MfClassicKey key_type);
+
+void mf_classic_set_key_found(
+    MfClassicData* data,
+    uint8_t sector_num,
+    MfClassicKey key_type,
+    uint64_t key);
+
+bool mf_classic_is_block_read(MfClassicData* data, uint8_t block_num);
+
+void mf_classic_set_block_read(MfClassicData* data, uint8_t block_num, MfClassicBlock* block_data);
+
+bool mf_classic_is_sector_read(MfClassicData* data, uint8_t sector_num);
+
+void mf_classic_get_read_sectors_and_keys(
+    MfClassicData* data,
+    uint8_t* sectors_read,
+    uint8_t* keys_found);
+
+MfClassicSectorTrailer*
+    mf_classic_get_sector_trailer_by_sector(MfClassicData* data, uint8_t sector);
+
+void mf_classic_auth_init_context(MfClassicAuthContext* auth_ctx, uint8_t sector);
+
+bool mf_classic_authenticate(
+    FuriHalNfcTxRxContext* tx_rx,
+    uint8_t block_num,
+    uint64_t key,
+    MfClassicKey key_type);
 
 bool mf_classic_auth_attempt(
     FuriHalNfcTxRxContext* tx_rx,
@@ -100,15 +133,13 @@ void mf_classic_reader_add_sector(
     uint64_t key_a,
     uint64_t key_b);
 
-bool mf_classic_read_sector(
-    FuriHalNfcTxRxContext* tx_rx,
-    Crypto1* crypto,
-    MfClassicSectorReader* sector_reader,
-    MfClassicSector* sector);
+void mf_classic_read_sector(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data, uint8_t sec_num);
 
 uint8_t mf_classic_read_card(
     FuriHalNfcTxRxContext* tx_rx,
     MfClassicReader* reader,
     MfClassicData* data);
 
+uint8_t mf_classic_update_card(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data);
+
 bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_rx);

+ 0 - 0
lib/nfc_protocols/mifare_common.c → lib/nfc/protocols/mifare_common.c


+ 0 - 0
lib/nfc_protocols/mifare_common.h → lib/nfc/protocols/mifare_common.h


+ 172 - 0
lib/nfc_protocols/mifare_desfire.c → lib/nfc/protocols/mifare_desfire.c

@@ -2,6 +2,8 @@
 #include <furi.h>
 #include <furi_hal_nfc.h>
 
+#define TAG "MifareDESFire"
+
 void mf_df_clear(MifareDesfireData* data) {
     free(data->free_memory);
     if(data->master_key_settings) {
@@ -449,3 +451,173 @@ bool mf_df_parse_read_data_response(uint8_t* buf, uint16_t len, MifareDesfireFil
     memcpy(out->contents, buf, len);
     return true;
 }
+
+bool mf_df_read_card(FuriHalNfcTxRxContext* tx_rx, MifareDesfireData* data) {
+    furi_assert(tx_rx);
+    furi_assert(data);
+
+    bool card_read = false;
+    do {
+        // Get version
+        tx_rx->tx_bits = 8 * mf_df_prepare_get_version(tx_rx->tx_data);
+        if(!furi_hal_nfc_tx_rx_full(tx_rx)) {
+            FURI_LOG_W(TAG, "Bad exchange getting version");
+            break;
+        }
+        if(!mf_df_parse_get_version_response(tx_rx->rx_data, tx_rx->rx_bits / 8, &data->version)) {
+            FURI_LOG_W(TAG, "Bad DESFire GET_VERSION responce");
+        }
+
+        // Get free memory
+        tx_rx->tx_bits = 8 * mf_df_prepare_get_free_memory(tx_rx->tx_data);
+        if(furi_hal_nfc_tx_rx_full(tx_rx)) {
+            data->free_memory = malloc(sizeof(MifareDesfireFreeMemory));
+            if(!mf_df_parse_get_free_memory_response(
+                   tx_rx->rx_data, tx_rx->rx_bits / 8, data->free_memory)) {
+                FURI_LOG_D(TAG, "Bad DESFire GET_FREE_MEMORY response (normal for pre-EV1 cards)");
+                free(data->free_memory);
+                data->free_memory = NULL;
+            }
+        }
+
+        // Get key settings
+        tx_rx->tx_bits = 8 * mf_df_prepare_get_key_settings(tx_rx->tx_data);
+        if(!furi_hal_nfc_tx_rx_full(tx_rx)) {
+            FURI_LOG_D(TAG, "Bad exchange getting key settings");
+        } else {
+            data->master_key_settings = malloc(sizeof(MifareDesfireKeySettings));
+            if(!mf_df_parse_get_key_settings_response(
+                   tx_rx->rx_data, tx_rx->rx_bits / 8, data->master_key_settings)) {
+                FURI_LOG_W(TAG, "Bad DESFire GET_KEY_SETTINGS response");
+                free(data->master_key_settings);
+                data->master_key_settings = NULL;
+            } else {
+                MifareDesfireKeyVersion** key_version_head =
+                    &data->master_key_settings->key_version_head;
+                for(uint8_t key_id = 0; key_id < data->master_key_settings->max_keys; key_id++) {
+                    tx_rx->tx_bits = 8 * mf_df_prepare_get_key_version(tx_rx->tx_data, key_id);
+                    if(!furi_hal_nfc_tx_rx_full(tx_rx)) {
+                        FURI_LOG_W(TAG, "Bad exchange getting key version");
+                        continue;
+                    }
+                    MifareDesfireKeyVersion* key_version = malloc(sizeof(MifareDesfireKeyVersion));
+                    memset(key_version, 0, sizeof(MifareDesfireKeyVersion));
+                    key_version->id = key_id;
+                    if(!mf_df_parse_get_key_version_response(
+                           tx_rx->rx_data, tx_rx->rx_bits / 8, key_version)) {
+                        FURI_LOG_W(TAG, "Bad DESFire GET_KEY_VERSION response");
+                        free(key_version);
+                        continue;
+                    }
+                    *key_version_head = key_version;
+                    key_version_head = &key_version->next;
+                }
+            }
+        }
+
+        // Get application IDs
+        tx_rx->tx_bits = 8 * mf_df_prepare_get_application_ids(tx_rx->tx_data);
+        if(!furi_hal_nfc_tx_rx_full(tx_rx)) {
+            FURI_LOG_W(TAG, "Bad exchange getting application IDs");
+            break;
+        } else {
+            if(!mf_df_parse_get_application_ids_response(
+                   tx_rx->rx_data, tx_rx->rx_bits / 8, &data->app_head)) {
+                FURI_LOG_W(TAG, "Bad DESFire GET_APPLICATION_IDS response");
+                break;
+            }
+        }
+
+        for(MifareDesfireApplication* app = data->app_head; app; app = app->next) {
+            tx_rx->tx_bits = 8 * mf_df_prepare_select_application(tx_rx->tx_data, app->id);
+            if(!furi_hal_nfc_tx_rx_full(tx_rx) ||
+               !mf_df_parse_select_application_response(tx_rx->rx_data, tx_rx->rx_bits / 8)) {
+                FURI_LOG_W(TAG, "Bad exchange selecting application");
+                continue;
+            }
+            tx_rx->tx_bits = 8 * mf_df_prepare_get_key_settings(tx_rx->tx_data);
+            if(!furi_hal_nfc_tx_rx_full(tx_rx)) {
+                FURI_LOG_W(TAG, "Bad exchange getting key settings");
+            } else {
+                app->key_settings = malloc(sizeof(MifareDesfireKeySettings));
+                memset(app->key_settings, 0, sizeof(MifareDesfireKeySettings));
+                if(!mf_df_parse_get_key_settings_response(
+                       tx_rx->rx_data, tx_rx->rx_bits / 8, app->key_settings)) {
+                    FURI_LOG_W(TAG, "Bad DESFire GET_KEY_SETTINGS response");
+                    free(app->key_settings);
+                    app->key_settings = NULL;
+                    continue;
+                }
+
+                MifareDesfireKeyVersion** key_version_head = &app->key_settings->key_version_head;
+                for(uint8_t key_id = 0; key_id < app->key_settings->max_keys; key_id++) {
+                    tx_rx->tx_bits = 8 * mf_df_prepare_get_key_version(tx_rx->tx_data, key_id);
+                    if(!furi_hal_nfc_tx_rx_full(tx_rx)) {
+                        FURI_LOG_W(TAG, "Bad exchange getting key version");
+                        continue;
+                    }
+                    MifareDesfireKeyVersion* key_version = malloc(sizeof(MifareDesfireKeyVersion));
+                    memset(key_version, 0, sizeof(MifareDesfireKeyVersion));
+                    key_version->id = key_id;
+                    if(!mf_df_parse_get_key_version_response(
+                           tx_rx->rx_data, tx_rx->rx_bits / 8, key_version)) {
+                        FURI_LOG_W(TAG, "Bad DESFire GET_KEY_VERSION response");
+                        free(key_version);
+                        continue;
+                    }
+                    *key_version_head = key_version;
+                    key_version_head = &key_version->next;
+                }
+            }
+
+            tx_rx->tx_bits = 8 * mf_df_prepare_get_file_ids(tx_rx->tx_data);
+            if(!furi_hal_nfc_tx_rx_full(tx_rx)) {
+                FURI_LOG_W(TAG, "Bad exchange getting file IDs");
+            } else {
+                if(!mf_df_parse_get_file_ids_response(
+                       tx_rx->rx_data, tx_rx->rx_bits / 8, &app->file_head)) {
+                    FURI_LOG_W(TAG, "Bad DESFire GET_FILE_IDS response");
+                }
+            }
+
+            for(MifareDesfireFile* file = app->file_head; file; file = file->next) {
+                tx_rx->tx_bits = 8 * mf_df_prepare_get_file_settings(tx_rx->tx_data, file->id);
+                if(!furi_hal_nfc_tx_rx_full(tx_rx)) {
+                    FURI_LOG_W(TAG, "Bad exchange getting file settings");
+                    continue;
+                }
+                if(!mf_df_parse_get_file_settings_response(
+                       tx_rx->rx_data, tx_rx->rx_bits / 8, file)) {
+                    FURI_LOG_W(TAG, "Bad DESFire GET_FILE_SETTINGS response");
+                    continue;
+                }
+                switch(file->type) {
+                case MifareDesfireFileTypeStandard:
+                case MifareDesfireFileTypeBackup:
+                    tx_rx->tx_bits = 8 * mf_df_prepare_read_data(tx_rx->tx_data, file->id, 0, 0);
+                    break;
+                case MifareDesfireFileTypeValue:
+                    tx_rx->tx_bits = 8 * mf_df_prepare_get_value(tx_rx->tx_data, file->id);
+                    break;
+                case MifareDesfireFileTypeLinearRecord:
+                case MifareDesfireFileTypeCyclicRecord:
+                    tx_rx->tx_bits =
+                        8 * mf_df_prepare_read_records(tx_rx->tx_data, file->id, 0, 0);
+                    break;
+                }
+                if(!furi_hal_nfc_tx_rx_full(tx_rx)) {
+                    FURI_LOG_W(TAG, "Bad exchange reading file %d", file->id);
+                    continue;
+                }
+                if(!mf_df_parse_read_data_response(tx_rx->rx_data, tx_rx->rx_bits / 8, file)) {
+                    FURI_LOG_W(TAG, "Bad response reading file %d", file->id);
+                    continue;
+                }
+            }
+        }
+
+        card_read = true;
+    } while(false);
+
+    return card_read;
+}

+ 4 - 0
lib/nfc_protocols/mifare_desfire.h → lib/nfc/protocols/mifare_desfire.h

@@ -4,6 +4,8 @@
 #include <stdint.h>
 #include <stdbool.h>
 
+#include <furi_hal_nfc.h>
+
 #define MF_DF_GET_VERSION (0x60)
 #define MF_DF_GET_FREE_MEMORY (0x6E)
 #define MF_DF_GET_KEY_SETTINGS (0x45)
@@ -163,3 +165,5 @@ uint16_t mf_df_prepare_read_data(uint8_t* dest, uint8_t file_id, uint32_t offset
 uint16_t mf_df_prepare_get_value(uint8_t* dest, uint8_t file_id);
 uint16_t mf_df_prepare_read_records(uint8_t* dest, uint8_t file_id, uint32_t offset, uint32_t len);
 bool mf_df_parse_read_data_response(uint8_t* buf, uint16_t len, MifareDesfireFile* out);
+
+bool mf_df_read_card(FuriHalNfcTxRxContext* tx_rx, MifareDesfireData* data);

+ 0 - 0
lib/nfc_protocols/mifare_ultralight.c → lib/nfc/protocols/mifare_ultralight.c


+ 0 - 0
lib/nfc_protocols/mifare_ultralight.h → lib/nfc/protocols/mifare_ultralight.h


+ 0 - 0
lib/nfc_protocols/nfc_util.c → lib/nfc/protocols/nfc_util.c


+ 0 - 0
lib/nfc_protocols/nfc_util.h → lib/nfc/protocols/nfc_util.h


+ 0 - 0
lib/nfc_protocols/nfca.c → lib/nfc/protocols/nfca.c


+ 0 - 0
lib/nfc_protocols/nfca.h → lib/nfc/protocols/nfca.h