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

Merge nfc_magic from https://github.com/xMasterX/all-the-plugins

# Conflicts:
#	nfc_magic/application.fam
Willy-JL 1 год назад
Родитель
Сommit
d87313927a

+ 82 - 0
nfc_magic/magic/protocols/gen1a/gen1a_poller.c

@@ -17,6 +17,24 @@ typedef struct {
     bool detected;
 } Gen1aPollerDetectContext;
 
+Gen1aPollerError gen1a_poller_parse_block0(MfClassicBlock* block, MfClassicData* mf_data) {
+    furi_assert(mf_data);
+    furi_assert(block);
+
+    Gen1aPollerError ret = Gen1aPollerErrorNone;
+
+    // Get UID, SAK, and ATQA from block 0
+    memcpy(mf_data->iso14443_3a_data->uid, block->data, 4);
+    mf_data->iso14443_3a_data->uid_len = 4;
+    mf_data->iso14443_3a_data->sak = block->data[5];
+    memcpy(mf_data->iso14443_3a_data->atqa, &block->data[6], 2);
+
+    // Gen1 tags are always 1k
+    mf_data->type = MfClassicType1k;
+
+    return ret;
+}
+
 Gen1aPoller* gen1a_poller_alloc(Nfc* nfc) {
     furi_assert(nfc);
 
@@ -133,6 +151,8 @@ NfcCommand gen1a_poller_request_mode_handler(Gen1aPoller* instance) {
     command = instance->callback(instance->gen1a_event, instance->context);
     if(instance->gen1a_event_data.request_mode.mode == Gen1aPollerModeWipe) {
         instance->state = Gen1aPollerStateWipe;
+    } else if(instance->gen1a_event_data.request_mode.mode == Gen1aPollerModeDump) {
+        instance->state = Gen1aPollerStateDumpDataRequest;
     } else {
         instance->state = Gen1aPollerStateWriteDataRequest;
     }
@@ -213,6 +233,66 @@ NfcCommand gen1a_poller_write_handler(Gen1aPoller* instance) {
     return command;
 }
 
+NfcCommand gen1a_poller_dump_data_request_handler(Gen1aPoller* instance) {
+    NfcCommand command = NfcCommandContinue;
+
+    instance->gen1a_event.type = Gen1aPollerEventTypeRequestDataToDump;
+    command = instance->callback(instance->gen1a_event, instance->context);
+    instance->state = Gen1aPollerStateDump;
+
+    return command;
+}
+
+NfcCommand gen1a_poller_dump_handler(Gen1aPoller* instance) {
+    NfcCommand command = NfcCommandContinue;
+    Gen1aPollerError error = Gen1aPollerErrorNone;
+
+    MfClassicData* mfc_data = instance->gen1a_event_data.data_to_dump.mfc_data;
+    MfClassicBlock block = {};
+    uint16_t total_block_num =
+        mf_classic_get_total_block_num(MfClassicType1k); // Gen1 can only be 1k
+
+    while(instance->current_block < total_block_num) {
+        if(instance->current_block == 0) {
+            error = gen1a_poller_data_access(instance);
+            if(error != Gen1aPollerErrorNone) {
+                instance->state = Gen1aPollerStateFail;
+                break;
+            }
+        }
+
+        error = gen1a_poller_read_block(instance, instance->current_block, &block);
+
+        if(error != Gen1aPollerErrorNone) {
+            instance->state = Gen1aPollerStateFail;
+            break;
+        } else {
+            mf_classic_set_block_read(mfc_data, instance->current_block, &block);
+
+            if(mf_classic_is_sector_trailer(instance->current_block)) {
+                mf_classic_set_sector_trailer_read(
+                    mfc_data, instance->current_block, (MfClassicSectorTrailer*)&block);
+            }
+        }
+
+        if(instance->current_block == 0) {
+            error = gen1a_poller_parse_block0(&mfc_data->block[instance->current_block], mfc_data);
+            if(error != Gen1aPollerErrorNone) {
+                instance->state = Gen1aPollerStateFail;
+                break;
+            }
+        }
+
+        instance->current_block++;
+    }
+
+    if(instance->current_block == total_block_num) {
+        instance->state = Gen1aPollerStateSuccess;
+    }
+
+    return command;
+}
+
 NfcCommand gen1a_poller_success_handler(Gen1aPoller* instance) {
     NfcCommand command = NfcCommandContinue;
 
@@ -239,6 +319,8 @@ static const Gen1aPollerStateHandler gen1a_poller_state_handlers[Gen1aPollerStat
     [Gen1aPollerStateWipe] = gen1a_poller_wipe_handler,
     [Gen1aPollerStateWriteDataRequest] = gen1a_poller_write_data_request_handler,
     [Gen1aPollerStateWrite] = gen1a_poller_write_handler,
+    [Gen1aPollerStateDumpDataRequest] = gen1a_poller_dump_data_request_handler,
+    [Gen1aPollerStateDump] = gen1a_poller_dump_handler,
     [Gen1aPollerStateSuccess] = gen1a_poller_success_handler,
     [Gen1aPollerStateFail] = gen1a_poller_fail_handler,
 

+ 7 - 0
nfc_magic/magic/protocols/gen1a/gen1a_poller.h

@@ -12,6 +12,7 @@ typedef enum {
     Gen1aPollerEventTypeDetected,
     Gen1aPollerEventTypeRequestMode,
     Gen1aPollerEventTypeRequestDataToWrite,
+    Gen1aPollerEventTypeRequestDataToDump,
 
     Gen1aPollerEventTypeSuccess,
     Gen1aPollerEventTypeFail,
@@ -19,6 +20,7 @@ typedef enum {
 
 typedef enum {
     Gen1aPollerModeWipe,
+    Gen1aPollerModeDump,
     Gen1aPollerModeWrite,
 } Gen1aPollerMode;
 
@@ -30,9 +32,14 @@ typedef struct {
     const MfClassicData* mfc_data;
 } Gen1aPollerEventDataRequestDataToWrite;
 
+typedef struct {
+    MfClassicData* mfc_data;
+} Gen1aPollerEventDataRequestDataToDump;
+
 typedef union {
     Gen1aPollerEventDataRequestMode request_mode;
     Gen1aPollerEventDataRequestDataToWrite data_to_write;
+    Gen1aPollerEventDataRequestDataToDump data_to_dump;
 } Gen1aPollerEventData;
 
 typedef struct {

+ 36 - 0
nfc_magic/magic/protocols/gen1a/gen1a_poller_i.c

@@ -3,6 +3,8 @@
 
 #include <furi/furi.h>
 
+#define TAG "GEN1A_POLLER"
+
 static Gen1aPollerError gen1a_poller_process_nfc_error(NfcError error) {
     Gen1aPollerError ret = Gen1aPollerErrorNone;
 
@@ -130,3 +132,37 @@ Gen1aPollerError gen1a_poller_write_block(
 
     return ret;
 }
+
+Gen1aPollerError
+    gen1a_poller_read_block(Gen1aPoller* instance, uint8_t block_num, MfClassicBlock* block) {
+    furi_assert(instance);
+    furi_assert(block);
+
+    Gen1aPollerError ret = Gen1aPollerErrorNone;
+    bit_buffer_reset(instance->tx_buffer);
+
+    do {
+        bit_buffer_reset(instance->tx_buffer);
+        bit_buffer_reset(instance->rx_buffer);
+        bit_buffer_append_byte(instance->tx_buffer, 0x30);
+        bit_buffer_append_byte(instance->tx_buffer, block_num);
+        iso14443_crc_append(Iso14443CrcTypeA, instance->tx_buffer);
+
+        NfcError error = nfc_poller_trx(
+            instance->nfc, instance->tx_buffer, instance->rx_buffer, GEN1A_POLLER_MAX_FWT);
+
+        if(error != NfcErrorNone) {
+            ret = gen1a_poller_process_nfc_error(error);
+            break;
+        }
+        if(bit_buffer_get_size(instance->rx_buffer) != 18 * 8) { // 18 bytes
+            ret = Gen1aPollerErrorProtocol;
+            FURI_LOG_D(TAG, "Expected 18 bytes, got %d", bit_buffer_get_size(instance->rx_buffer));
+            break;
+        }
+
+        memcpy(block->data, bit_buffer_get_data(instance->rx_buffer), 16);
+    } while(false);
+
+    return ret;
+}

+ 5 - 0
nfc_magic/magic/protocols/gen1a/gen1a_poller_i.h

@@ -25,6 +25,8 @@ typedef enum {
     Gen1aPollerStateWipe,
     Gen1aPollerStateWriteDataRequest,
     Gen1aPollerStateWrite,
+    Gen1aPollerStateDumpDataRequest,
+    Gen1aPollerStateDump,
     Gen1aPollerStateSuccess,
     Gen1aPollerStateFail,
 
@@ -62,6 +64,9 @@ Gen1aPollerError gen1a_poller_data_access(Gen1aPoller* instance);
 Gen1aPollerError
     gen1a_poller_write_block(Gen1aPoller* instance, uint8_t block_num, const MfClassicBlock* block);
 
+Gen1aPollerError
+    gen1a_poller_read_block(Gen1aPoller* instance, uint8_t block_num, MfClassicBlock* block);
+
 #ifdef __cplusplus
 }
 #endif

+ 8 - 2
nfc_magic/nfc_magic_app.c

@@ -49,16 +49,19 @@ NfcMagicApp* nfc_magic_app_alloc() {
     view_dispatcher_set_tick_event_callback(
         instance->view_dispatcher, nfc_magic_app_tick_event_callback, 100);
 
-    // NFC source device (file)
+    // NFC source device
     instance->source_dev = nfc_device_alloc();
     nfc_device_set_loading_callback(
         instance->source_dev, nfc_magic_app_show_loading_popup, instance);
     instance->file_path = furi_string_alloc_set(NFC_APP_FOLDER);
     instance->file_name = furi_string_alloc();
 
-    // NFC target device (tag)
+    // NFC target device
     instance->target_dev = nfc_device_alloc();
 
+    // NFC dump data
+    instance->dump_data = mf_classic_alloc();
+
     // Open GUI record
     instance->gui = furi_record_open(RECORD_GUI);
     view_dispatcher_attach_to_gui(
@@ -140,6 +143,9 @@ void nfc_magic_app_free(NfcMagicApp* instance) {
     // Nfc target device
     nfc_device_free(instance->target_dev);
 
+    // Nfc dump data
+    mf_classic_free(instance->dump_data);
+
     // Submenu
     view_dispatcher_remove_view(instance->view_dispatcher, NfcMagicAppViewMenu);
     submenu_free(instance->submenu);

+ 9 - 0
nfc_magic/nfc_magic_app_i.h

@@ -25,6 +25,7 @@
 #include <storage/storage.h>
 #include <dialogs/dialogs.h>
 #include <lib/toolbox/path.h>
+#include <dolphin/dolphin.h>
 
 #include "nfc_magic_icons.h"
 
@@ -48,6 +49,11 @@
 #define NFC_APP_MF_CLASSIC_DICT_USER_PATH (NFC_APP_FOLDER "/assets/mf_classic_dict_user.nfc")
 #define NFC_APP_MF_CLASSIC_DICT_SYSTEM_PATH (NFC_APP_FOLDER "/assets/mf_classic_dict.nfc")
 
+#define NFC_MAGIC_APP_NAME_SIZE 22
+#define NFC_MAGIC_APP_TEXT_STORE_SIZE 128
+#define NFC_MAGIC_APP_FOLDER ANY_PATH("nfc")
+#define NFC_MAGIC_APP_EXTENSION ".nfc"
+#define NFC_MAGIC_APP_FILENAME_PREFIX "NFC"
 #define NFC_MAGIC_APP_BYTE_INPUT_STORE_SIZE (4)
 
 enum NfcMagicAppCustomEvent {
@@ -63,6 +69,7 @@ enum NfcMagicAppCustomEvent {
     NfcMagicAppCustomEventDictAttackDataUpdate,
     NfcMagicAppCustomEventDictAttackComplete,
     NfcMagicAppCustomEventDictAttackSkip,
+    NfcMagicCustomEventTextInputDone,
 };
 
 typedef struct {
@@ -95,8 +102,10 @@ struct NfcMagicApp {
     SceneManager* scene_manager;
     NfcDevice* source_dev;
     NfcDevice* target_dev;
+    char text_store[NFC_MAGIC_APP_TEXT_STORE_SIZE + 1];
     FuriString* file_name;
     FuriString* file_path;
+    MfClassicData* dump_data;
 
     Nfc* nfc;
     NfcMagicProtocol protocol;

+ 4 - 1
nfc_magic/scenes/nfc_magic_scene_config.h

@@ -28,4 +28,7 @@ ADD_SCENE(nfc_magic, gen2_menu, Gen2Menu)
 ADD_SCENE(nfc_magic, mf_classic_menu, MfClassicMenu)
 ADD_SCENE(nfc_magic, mf_classic_dict_attack, MfClassicDictAttack)
 ADD_SCENE(nfc_magic, gen2_write_check, Gen2WriteCheck)
-ADD_SCENE(nfc_magic, mf_classic_write_check, MfClassicWriteCheck)
+ADD_SCENE(nfc_magic, mf_classic_write_check, MfClassicWriteCheck)
+ADD_SCENE(nfc_magic, dump, Dump)
+ADD_SCENE(nfc_magic, dump_fail, DumpFail)
+ADD_SCENE(nfc_magic, gen1_save_name, Gen1SaveName)

+ 110 - 0
nfc_magic/scenes/nfc_magic_scene_dump.c

@@ -0,0 +1,110 @@
+#include "../nfc_magic_app_i.h"
+
+enum {
+    NfcMagicSceneDumpStateCardSearch,
+    NfcMagicSceneDumpStateCardFound,
+};
+
+NfcCommand nfc_magic_scene_dump_gen1_poller_callback(Gen1aPollerEvent event, void* context) {
+    NfcMagicApp* instance = context;
+    furi_assert(event.data);
+
+    NfcCommand command = NfcCommandContinue;
+
+    if(event.type == Gen1aPollerEventTypeDetected) {
+        view_dispatcher_send_custom_event(
+            instance->view_dispatcher, NfcMagicCustomEventCardDetected);
+    } else if(event.type == Gen1aPollerEventTypeRequestMode) {
+        event.data->request_mode.mode = Gen1aPollerModeDump;
+    } else if(event.type == Gen1aPollerEventTypeRequestDataToDump) {
+        event.data->data_to_dump.mfc_data = instance->dump_data;
+    } else if(event.type == Gen1aPollerEventTypeSuccess) {
+        view_dispatcher_send_custom_event(
+            instance->view_dispatcher, NfcMagicCustomEventWorkerSuccess);
+        command = NfcCommandStop;
+    } else if(event.type == Gen1aPollerEventTypeFail) {
+        view_dispatcher_send_custom_event(
+            instance->view_dispatcher, NfcMagicCustomEventWorkerFail);
+        command = NfcCommandStop;
+    }
+
+    return command;
+}
+
+static void nfc_magic_scene_dump_setup_view(NfcMagicApp* instance) {
+    Popup* popup = instance->popup;
+    popup_reset(popup);
+    uint32_t state = scene_manager_get_scene_state(instance->scene_manager, NfcMagicSceneDump);
+
+    if(state == NfcMagicSceneDumpStateCardSearch) {
+        popup_set_icon(instance->popup, 0, 8, &I_NFC_manual_60x50);
+        popup_set_text(
+            instance->popup, "Apply the\nsame card\nto the back", 128, 32, AlignRight, AlignCenter);
+    } else {
+        popup_set_icon(popup, 12, 23, &I_Loading_24);
+        popup_set_header(popup, "Dumping\nDon't move...", 52, 32, AlignLeft, AlignCenter);
+    }
+
+    view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewPopup);
+}
+
+void nfc_magic_scene_dump_on_enter(void* context) {
+    NfcMagicApp* instance = context;
+
+    scene_manager_set_scene_state(
+        instance->scene_manager, NfcMagicSceneDump, NfcMagicSceneDumpStateCardSearch);
+    nfc_magic_scene_dump_setup_view(instance);
+
+    nfc_magic_app_blink_start(instance);
+
+    if(instance->protocol == NfcMagicProtocolGen1) {
+        instance->gen1a_poller = gen1a_poller_alloc(instance->nfc);
+        gen1a_poller_start(
+            instance->gen1a_poller, nfc_magic_scene_dump_gen1_poller_callback, instance);
+    }
+}
+
+bool nfc_magic_scene_dump_on_event(void* context, SceneManagerEvent event) {
+    NfcMagicApp* instance = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == NfcMagicCustomEventCardDetected) {
+            scene_manager_set_scene_state(
+                instance->scene_manager, NfcMagicSceneDump, NfcMagicSceneDumpStateCardFound);
+            nfc_magic_scene_dump_setup_view(instance);
+            consumed = true;
+        } else if(event.event == NfcMagicCustomEventCardLost) {
+            scene_manager_set_scene_state(
+                instance->scene_manager, NfcMagicSceneDump, NfcMagicSceneDumpStateCardSearch);
+            nfc_magic_scene_dump_setup_view(instance);
+            consumed = true;
+        } else if(event.event == NfcMagicCustomEventWorkerSuccess) {
+            scene_manager_next_scene(instance->scene_manager, NfcMagicSceneGen1SaveName);
+            consumed = true;
+        } else if(event.event == NfcMagicCustomEventWorkerFail) {
+            scene_manager_next_scene(instance->scene_manager, NfcMagicSceneDumpFail);
+            consumed = true;
+        }
+    }
+
+    return consumed;
+}
+
+void nfc_magic_scene_dump_on_exit(void* context) {
+    NfcMagicApp* instance = context;
+
+    if(instance->protocol == NfcMagicProtocolGen1) {
+        gen1a_poller_stop(instance->gen1a_poller);
+        gen1a_poller_free(instance->gen1a_poller);
+    }
+
+    nfc_device_set_data(instance->source_dev, NfcProtocolMfClassic, instance->dump_data);
+
+    scene_manager_set_scene_state(
+        instance->scene_manager, NfcMagicSceneDump, NfcMagicSceneDumpStateCardSearch);
+    // Clear view
+    popup_reset(instance->popup);
+
+    nfc_magic_app_blink_stop(instance);
+}

+ 46 - 0
nfc_magic/scenes/nfc_magic_scene_dump_fail.c

@@ -0,0 +1,46 @@
+#include "../nfc_magic_app_i.h"
+
+void nfc_magic_scene_dump_fail_widget_callback(GuiButtonType result, InputType type, void* context) {
+    NfcMagicApp* instance = context;
+
+    if(type == InputTypeShort) {
+        view_dispatcher_send_custom_event(instance->view_dispatcher, result);
+    }
+}
+
+void nfc_magic_scene_dump_fail_on_enter(void* context) {
+    NfcMagicApp* instance = context;
+
+    Widget* widget = instance->widget;
+    notification_message(instance->notifications, &sequence_error);
+
+    widget_add_icon_element(widget, 83, 22, &I_WarningDolphinFlip_45x42);
+    widget_add_string_element(widget, 64, 0, AlignCenter, AlignTop, FontPrimary, "Failed to Dump");
+    widget_add_string_multiline_element(
+        widget, 0, 13, AlignLeft, AlignTop, FontSecondary, "Something went\nwrong while dumping");
+
+    widget_add_button_element(
+        widget, GuiButtonTypeLeft, "Retry", nfc_magic_scene_dump_fail_widget_callback, instance);
+
+    // Setup and start worker
+    view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewWidget);
+}
+
+bool nfc_magic_scene_dump_fail_on_event(void* context, SceneManagerEvent event) {
+    NfcMagicApp* instance = context;
+
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == GuiButtonTypeLeft) {
+            consumed = scene_manager_previous_scene(instance->scene_manager);
+        }
+    }
+    return consumed;
+}
+
+void nfc_magic_scene_dump_fail_on_exit(void* context) {
+    NfcMagicApp* instance = context;
+
+    widget_reset(instance->widget);
+}

+ 6 - 0
nfc_magic/scenes/nfc_magic_scene_gen1_menu.c

@@ -3,6 +3,7 @@
 enum SubmenuIndex {
     SubmenuIndexWrite,
     SubmenuIndexWipe,
+    SubmenuIndexDump,
 };
 
 void nfc_magic_scene_gen1_menu_submenu_callback(void* context, uint32_t index) {
@@ -19,6 +20,8 @@ void nfc_magic_scene_gen1_menu_on_enter(void* context) {
         submenu, "Write", SubmenuIndexWrite, nfc_magic_scene_gen1_menu_submenu_callback, instance);
     submenu_add_item(
         submenu, "Wipe", SubmenuIndexWipe, nfc_magic_scene_gen1_menu_submenu_callback, instance);
+    submenu_add_item(
+        submenu, "Dump", SubmenuIndexDump, nfc_magic_scene_gen1_menu_submenu_callback, instance);
 
     submenu_set_selected_item(
         submenu, scene_manager_get_scene_state(instance->scene_manager, NfcMagicSceneGen1Menu));
@@ -36,6 +39,9 @@ bool nfc_magic_scene_gen1_menu_on_event(void* context, SceneManagerEvent event)
         } else if(event.event == SubmenuIndexWipe) {
             scene_manager_next_scene(instance->scene_manager, NfcMagicSceneWipe);
             consumed = true;
+        } else if(event.event == SubmenuIndexDump) {
+            scene_manager_next_scene(instance->scene_manager, NfcMagicSceneDump);
+            consumed = true;
         }
         scene_manager_set_scene_state(instance->scene_manager, NfcMagicSceneGen1Menu, event.event);
     } else if(event.type == SceneManagerEventTypeBack) {

+ 77 - 0
nfc_magic/scenes/nfc_magic_scene_gen1_save_name.c

@@ -0,0 +1,77 @@
+#include "../nfc_magic_app_i.h"
+#include "furi.h"
+#include <toolbox/name_generator.h>
+
+void nfc_magic_scene_gen1_save_name_text_input_done_callback(void* context) {
+    NfcMagicApp* instance = context;
+
+    view_dispatcher_send_custom_event(instance->view_dispatcher, NfcMagicCustomEventTextInputDone);
+}
+
+void nfc_magic_scene_gen1_save_name_on_enter(void* context) {
+    NfcMagicApp* instance = context;
+
+    FuriString* folder_path = furi_string_alloc();
+    TextInput* text_input = instance->text_input;
+
+    furi_string_set(instance->file_path, NFC_APP_FOLDER);
+    name_generator_make_auto(
+        instance->text_store, NFC_MAGIC_APP_TEXT_STORE_SIZE, NFC_MAGIC_APP_FILENAME_PREFIX);
+    furi_string_set(folder_path, NFC_APP_FOLDER);
+
+    text_input_set_header_text(text_input, "Name the card");
+    text_input_set_result_callback(
+        text_input,
+        nfc_magic_scene_gen1_save_name_text_input_done_callback,
+        instance,
+        instance->text_store,
+        NFC_MAGIC_APP_NAME_SIZE,
+        false);
+
+    ValidatorIsFile* validator_is_file = validator_is_file_alloc_init(
+        furi_string_get_cstr(folder_path),
+        NFC_APP_EXTENSION,
+        furi_string_get_cstr(instance->file_name));
+    text_input_set_validator(text_input, validator_is_file_callback, validator_is_file);
+
+    furi_string_free(folder_path);
+
+    view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewTextInput);
+}
+
+bool nfc_magic_scene_gen1_save_name_on_event(void* context, SceneManagerEvent event) {
+    NfcMagicApp* instance = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == NfcMagicCustomEventTextInputDone) {
+            furi_string_set(instance->file_name, instance->text_store);
+
+            furi_string_cat_printf(
+                instance->file_path,
+                "/%s%s",
+                furi_string_get_cstr(instance->file_name),
+                NFC_APP_EXTENSION);
+
+            if(nfc_device_save(instance->source_dev, furi_string_get_cstr(instance->file_path))) {
+                scene_manager_next_scene(instance->scene_manager, NfcMagicSceneSuccess);
+                dolphin_deed(DolphinDeedNfcSave);
+            } else {
+                consumed = scene_manager_search_and_switch_to_previous_scene(
+                    instance->scene_manager, NfcMagicSceneStart);
+            }
+        }
+    }
+
+    return consumed;
+}
+
+void nfc_magic_scene_gen1_save_name_on_exit(void* context) {
+    NfcMagicApp* instance = context;
+
+    void* validator_context = text_input_get_validator_callback_context(instance->text_input);
+    text_input_set_validator(instance->text_input, NULL, NULL);
+    validator_is_file_free(validator_context);
+
+    text_input_reset(instance->text_input);
+}

+ 2 - 2
nfc_magic/scenes/nfc_magic_scene_not_magic.c

@@ -14,7 +14,7 @@ void nfc_magic_scene_not_magic_on_enter(void* context) {
     notification_message(instance->notifications, &sequence_error);
 
     widget_add_string_element(
-        widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "This is wrong card");
+        widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "Incorrect card type");
     widget_add_string_multiline_element(
         widget,
         4,
@@ -22,7 +22,7 @@ void nfc_magic_scene_not_magic_on_enter(void* context) {
         AlignLeft,
         AlignTop,
         FontSecondary,
-        "Not magic or unsupported\ncard. Only Gen1 and \nGen4 UMC cards supported.");
+        "Not magic or unsupported\ncard. Only Gen1, Gen2 and \nGen4 UMC cards supported.");
     widget_add_button_element(
         widget, GuiButtonTypeLeft, "Retry", nfc_magic_scene_not_magic_widget_callback, instance);