Bläddra i källkod

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

git-subtree-dir: nfc_magic
git-subtree-mainline: 6c9b84da960aa465d68e5cb1852a453faef08435
git-subtree-split: b084aa169a0ad079b278f806d284f8587d978ce9
Willy-JL 2 år sedan
förälder
incheckning
285e65b0a1
44 ändrade filer med 3268 tillägg och 0 borttagningar
  1. 1 0
      nfc_magic/.gitsubtree
  2. 22 0
      nfc_magic/application.fam
  3. BIN
      nfc_magic/assets/125_10px.png
  4. BIN
      nfc_magic/assets/DolphinCommon_56x48.png
  5. BIN
      nfc_magic/assets/DolphinNice_96x59.png
  6. BIN
      nfc_magic/assets/Loading_24.png
  7. BIN
      nfc_magic/assets/NFC_manual_60x50.png
  8. BIN
      nfc_magic/assets/Nfc_10px.png
  9. 14 0
      nfc_magic/helpers/nfc_magic_custom_events.h
  10. 150 0
      nfc_magic/lib/magic/nfc_magic_scanner.c
  11. 44 0
      nfc_magic/lib/magic/nfc_magic_scanner.h
  12. 279 0
      nfc_magic/lib/magic/protocols/gen1a/gen1a_poller.c
  13. 59 0
      nfc_magic/lib/magic/protocols/gen1a/gen1a_poller.h
  14. 132 0
      nfc_magic/lib/magic/protocols/gen1a/gen1a_poller_i.c
  15. 67 0
      nfc_magic/lib/magic/protocols/gen1a/gen1a_poller_i.h
  16. 533 0
      nfc_magic/lib/magic/protocols/gen4/gen4_poller.c
  17. 84 0
      nfc_magic/lib/magic/protocols/gen4/gen4_poller.h
  18. 129 0
      nfc_magic/lib/magic/protocols/gen4/gen4_poller_i.c
  19. 101 0
      nfc_magic/lib/magic/protocols/gen4/gen4_poller_i.h
  20. 14 0
      nfc_magic/lib/magic/protocols/nfc_magic_protocols.c
  21. 19 0
      nfc_magic/lib/magic/protocols/nfc_magic_protocols.h
  22. 289 0
      nfc_magic/nfc_magic_app.c
  23. 5 0
      nfc_magic/nfc_magic_app.h
  24. 101 0
      nfc_magic/nfc_magic_app_i.h
  25. 30 0
      nfc_magic/scenes/nfc_magic_scene.c
  26. 29 0
      nfc_magic/scenes/nfc_magic_scene.h
  27. 108 0
      nfc_magic/scenes/nfc_magic_scene_change_key.c
  28. 54 0
      nfc_magic/scenes/nfc_magic_scene_change_key_fail.c
  29. 54 0
      nfc_magic/scenes/nfc_magic_scene_check.c
  30. 17 0
      nfc_magic/scenes/nfc_magic_scene_config.h
  31. 60 0
      nfc_magic/scenes/nfc_magic_scene_file_select.c
  32. 53 0
      nfc_magic/scenes/nfc_magic_scene_gen1_menu.c
  33. 63 0
      nfc_magic/scenes/nfc_magic_scene_gen4_menu.c
  34. 55 0
      nfc_magic/scenes/nfc_magic_scene_key_input.c
  35. 63 0
      nfc_magic/scenes/nfc_magic_scene_magic_info.c
  36. 43 0
      nfc_magic/scenes/nfc_magic_scene_not_magic.c
  37. 59 0
      nfc_magic/scenes/nfc_magic_scene_start.c
  38. 42 0
      nfc_magic/scenes/nfc_magic_scene_success.c
  39. 136 0
      nfc_magic/scenes/nfc_magic_scene_wipe.c
  40. 43 0
      nfc_magic/scenes/nfc_magic_scene_wipe_fail.c
  41. 144 0
      nfc_magic/scenes/nfc_magic_scene_write.c
  42. 61 0
      nfc_magic/scenes/nfc_magic_scene_write_confirm.c
  43. 58 0
      nfc_magic/scenes/nfc_magic_scene_write_fail.c
  44. 53 0
      nfc_magic/scenes/nfc_magic_scene_wrong_card.c

+ 1 - 0
nfc_magic/.gitsubtree

@@ -0,0 +1 @@
+https://github.com/xMasterX/all-the-plugins dev base_pack/nfc_magic

+ 22 - 0
nfc_magic/application.fam

@@ -0,0 +1,22 @@
+App(
+    appid="nfc_magic",
+    name="NFC Magic",
+    apptype=FlipperAppType.EXTERNAL,
+    targets=["f7"],
+    entry_point="nfc_magic_app",
+    requires=[
+        "storage",
+        "gui",
+    ],
+    stack_size=4 * 1024,
+    fap_description="Application for writing to NFC tags with modifiable sector 0",
+    fap_version="1.1",
+    fap_icon="assets/Nfc_10px.png",
+    fap_category="NFC",
+    fap_private_libs=[
+        Lib(
+            name="magic",
+        ),
+    ],
+    fap_icon_assets="assets",
+)

BIN
nfc_magic/assets/125_10px.png


BIN
nfc_magic/assets/DolphinCommon_56x48.png


BIN
nfc_magic/assets/DolphinNice_96x59.png


BIN
nfc_magic/assets/Loading_24.png


BIN
nfc_magic/assets/NFC_manual_60x50.png


BIN
nfc_magic/assets/Nfc_10px.png


+ 14 - 0
nfc_magic/helpers/nfc_magic_custom_events.h

@@ -0,0 +1,14 @@
+#pragma once
+
+typedef enum {
+    // Reserve first 100 events for button types and indexes, starting from 0
+    NfcMagicCustomEventReserved = 100,
+
+    NfcMagicCustomEventViewExit,
+
+    NfcMagicCustomEventCardDetected,
+    NfcMagicCustomEventCardLost,
+    NfcMagicCustomEventWorkerSuccess,
+    NfcMagicCustomEventWorkerFail,
+
+} NfcMagicCustomEvent;

+ 150 - 0
nfc_magic/lib/magic/nfc_magic_scanner.c

@@ -0,0 +1,150 @@
+#include "nfc_magic_scanner.h"
+
+#include "protocols/gen1a/gen1a_poller.h"
+#include "protocols/gen4/gen4_poller.h"
+#include <nfc/nfc_poller.h>
+
+#include <furi/furi.h>
+
+typedef enum {
+    NfcMagicScannerSessionStateIdle,
+    NfcMagicScannerSessionStateActive,
+    NfcMagicScannerSessionStateStopRequest,
+} NfcMagicScannerSessionState;
+
+struct NfcMagicScanner {
+    Nfc* nfc;
+    NfcMagicScannerSessionState session_state;
+    NfcMagicProtocol current_protocol;
+
+    uint32_t gen4_password;
+    bool magic_protocol_detected;
+
+    NfcMagicScannerCallback callback;
+    void* context;
+
+    FuriThread* scan_worker;
+};
+
+static const NfcProtocol nfc_magic_scanner_not_magic_protocols[] = {
+    NfcProtocolIso14443_3b,
+    NfcProtocolIso15693_3,
+    NfcProtocolFelica,
+};
+
+static void nfc_magic_scanner_reset(NfcMagicScanner* instance) {
+    instance->session_state = NfcMagicScannerSessionStateIdle;
+    instance->current_protocol = NfcMagicProtocolGen1;
+}
+
+NfcMagicScanner* nfc_magic_scanner_alloc(Nfc* nfc) {
+    furi_assert(nfc);
+
+    NfcMagicScanner* instance = malloc(sizeof(NfcMagicScanner));
+    instance->nfc = nfc;
+
+    return instance;
+}
+
+void nfc_magic_scanner_free(NfcMagicScanner* instance) {
+    furi_assert(instance);
+
+    free(instance);
+}
+
+void nfc_magic_scanner_set_gen4_password(NfcMagicScanner* instance, uint32_t password) {
+    furi_assert(instance);
+
+    instance->gen4_password = password;
+}
+
+static int32_t nfc_magic_scanner_worker(void* context) {
+    furi_assert(context);
+
+    NfcMagicScanner* instance = context;
+    furi_assert(instance->session_state == NfcMagicScannerSessionStateActive);
+
+    while(instance->session_state == NfcMagicScannerSessionStateActive) {
+        if(instance->current_protocol == NfcMagicProtocolGen1) {
+            instance->magic_protocol_detected = gen1a_poller_detect(instance->nfc);
+        } else if(instance->current_protocol == NfcMagicProtocolGen4) {
+            Gen4PollerError error = gen4_poller_detect(instance->nfc, instance->gen4_password);
+            if(error == Gen4PollerErrorProtocol) {
+                NfcMagicScannerEvent event = {
+                    .type = NfcMagicScannerEventTypeDetectedNotMagic,
+                };
+                instance->callback(event, instance->context);
+                break;
+            } else {
+                instance->magic_protocol_detected = (error == Gen4PollerErrorNone);
+            }
+        }
+
+        if(instance->magic_protocol_detected) {
+            NfcMagicScannerEvent event = {
+                .type = NfcMagicScannerEventTypeDetected,
+                .data.protocol = instance->current_protocol,
+            };
+            instance->callback(event, instance->context);
+            break;
+        }
+
+        if(instance->current_protocol == NfcMagicProtocolNum - 1) {
+            bool not_magic_protocol_detected = false;
+            for(size_t i = 0; i < COUNT_OF(nfc_magic_scanner_not_magic_protocols); i++) {
+                NfcProtocol protocol = nfc_magic_scanner_not_magic_protocols[i];
+                NfcPoller* poller = nfc_poller_alloc(instance->nfc, protocol);
+                not_magic_protocol_detected = nfc_poller_detect(poller);
+                nfc_poller_free(poller);
+                if(not_magic_protocol_detected) {
+                    break;
+                }
+            }
+            if(not_magic_protocol_detected) {
+                NfcMagicScannerEvent event = {
+                    .type = NfcMagicScannerEventTypeDetectedNotMagic,
+                };
+                instance->callback(event, instance->context);
+                break;
+            }
+        }
+        instance->current_protocol = (instance->current_protocol + 1) % NfcMagicProtocolNum;
+    }
+
+    nfc_magic_scanner_reset(instance);
+
+    return 0;
+}
+
+void nfc_magic_scanner_start(
+    NfcMagicScanner* instance,
+    NfcMagicScannerCallback callback,
+    void* context) {
+    furi_assert(instance);
+    furi_assert(callback);
+
+    instance->callback = callback;
+    instance->context = context;
+
+    instance->scan_worker = furi_thread_alloc();
+    furi_thread_set_name(instance->scan_worker, "NfcMagicScanWorker");
+    furi_thread_set_context(instance->scan_worker, instance);
+    furi_thread_set_stack_size(instance->scan_worker, 4 * 1024);
+    furi_thread_set_callback(instance->scan_worker, nfc_magic_scanner_worker);
+    furi_thread_start(instance->scan_worker);
+
+    instance->session_state = NfcMagicScannerSessionStateActive;
+}
+
+void nfc_magic_scanner_stop(NfcMagicScanner* instance) {
+    furi_assert(instance);
+
+    instance->session_state = NfcMagicScannerSessionStateStopRequest;
+    furi_thread_join(instance->scan_worker);
+    instance->session_state = NfcMagicScannerSessionStateIdle;
+
+    furi_thread_free(instance->scan_worker);
+    instance->scan_worker = NULL;
+    instance->callback = NULL;
+    instance->context = NULL;
+}

+ 44 - 0
nfc_magic/lib/magic/nfc_magic_scanner.h

@@ -0,0 +1,44 @@
+#pragma once
+
+#include <nfc/nfc.h>
+#include "protocols/nfc_magic_protocols.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum {
+    NfcMagicScannerEventTypeDetected,
+    NfcMagicScannerEventTypeDetectedNotMagic,
+    NfcMagicScannerEventTypeNotDetected,
+} NfcMagicScannerEventType;
+
+typedef struct {
+    NfcMagicProtocol protocol;
+} NfcMagicScannerEventData;
+
+typedef struct {
+    NfcMagicScannerEventType type;
+    NfcMagicScannerEventData data;
+} NfcMagicScannerEvent;
+
+typedef void (*NfcMagicScannerCallback)(NfcMagicScannerEvent event, void* context);
+
+typedef struct NfcMagicScanner NfcMagicScanner;
+
+NfcMagicScanner* nfc_magic_scanner_alloc(Nfc* nfc);
+
+void nfc_magic_scanner_free(NfcMagicScanner* instance);
+
+void nfc_magic_scanner_set_gen4_password(NfcMagicScanner* instance, uint32_t password);
+
+void nfc_magic_scanner_start(
+    NfcMagicScanner* instance,
+    NfcMagicScannerCallback callback,
+    void* context);
+
+void nfc_magic_scanner_stop(NfcMagicScanner* instance);
+
+#ifdef __cplusplus
+}
+#endif

+ 279 - 0
nfc_magic/lib/magic/protocols/gen1a/gen1a_poller.c

@@ -0,0 +1,279 @@
+#include "gen1a_poller_i.h"
+#include <nfc/protocols/iso14443_3a/iso14443_3a.h>
+#include <nfc/protocols/iso14443_3a/iso14443_3a_poller.h>
+#include <nfc/helpers/nfc_data_generator.h>
+
+#include <furi/furi.h>
+
+#define GEN1A_POLLER_THREAD_FLAG_DETECTED (1U << 0)
+
+typedef NfcCommand (*Gen1aPollerStateHandler)(Gen1aPoller* instance);
+
+typedef struct {
+    Nfc* nfc;
+    BitBuffer* tx_buffer;
+    BitBuffer* rx_buffer;
+    FuriThreadId thread_id;
+    bool detected;
+} Gen1aPollerDetectContext;
+
+Gen1aPoller* gen1a_poller_alloc(Nfc* nfc) {
+    furi_assert(nfc);
+
+    Gen1aPoller* instance = malloc(sizeof(Gen1aPoller));
+    instance->nfc = nfc;
+
+    nfc_config(instance->nfc, NfcModePoller, NfcTechIso14443a);
+    nfc_set_guard_time_us(instance->nfc, ISO14443_3A_GUARD_TIME_US);
+    nfc_set_fdt_poll_fc(instance->nfc, ISO14443_3A_FDT_POLL_FC);
+    nfc_set_fdt_poll_poll_us(instance->nfc, ISO14443_3A_POLL_POLL_MIN_US);
+
+    instance->tx_buffer = bit_buffer_alloc(GEN1A_POLLER_MAX_BUFFER_SIZE);
+    instance->rx_buffer = bit_buffer_alloc(GEN1A_POLLER_MAX_BUFFER_SIZE);
+
+    instance->mfc_device = nfc_device_alloc();
+
+    instance->gen1a_event.data = &instance->gen1a_event_data;
+
+    return instance;
+}
+
+void gen1a_poller_free(Gen1aPoller* instance) {
+    furi_assert(instance);
+
+    bit_buffer_free(instance->tx_buffer);
+    bit_buffer_free(instance->rx_buffer);
+
+    nfc_device_free(instance->mfc_device);
+
+    free(instance);
+}
+
+NfcCommand gen1a_poller_detect_callback(NfcEvent event, void* context) {
+    furi_assert(context);
+
+    NfcCommand command = NfcCommandStop;
+    Gen1aPollerDetectContext* gen1a_poller_detect_ctx = context;
+
+    if(event.type == NfcEventTypePollerReady) {
+        do {
+            bit_buffer_set_size(gen1a_poller_detect_ctx->tx_buffer, 7);
+            bit_buffer_set_byte(gen1a_poller_detect_ctx->tx_buffer, 0, 0x40);
+
+            NfcError error = nfc_poller_trx(
+                gen1a_poller_detect_ctx->nfc,
+                gen1a_poller_detect_ctx->tx_buffer,
+                gen1a_poller_detect_ctx->rx_buffer,
+                GEN1A_POLLER_MAX_FWT);
+
+            if(error != NfcErrorNone) break;
+            if(bit_buffer_get_size(gen1a_poller_detect_ctx->rx_buffer) != 4) break;
+            if(bit_buffer_get_byte(gen1a_poller_detect_ctx->rx_buffer, 0) != 0x0A) break;
+
+            gen1a_poller_detect_ctx->detected = true;
+        } while(false);
+    }
+    furi_thread_flags_set(gen1a_poller_detect_ctx->thread_id, GEN1A_POLLER_THREAD_FLAG_DETECTED);
+
+    return command;
+}
+
+bool gen1a_poller_detect(Nfc* nfc) {
+    furi_assert(nfc);
+
+    nfc_config(nfc, NfcModePoller, NfcTechIso14443a);
+    nfc_set_guard_time_us(nfc, ISO14443_3A_GUARD_TIME_US);
+    nfc_set_fdt_poll_fc(nfc, ISO14443_3A_FDT_POLL_FC);
+    nfc_set_fdt_poll_poll_us(nfc, ISO14443_3A_POLL_POLL_MIN_US);
+
+    Gen1aPollerDetectContext gen1a_poller_detect_ctx = {};
+    gen1a_poller_detect_ctx.nfc = nfc;
+    gen1a_poller_detect_ctx.tx_buffer = bit_buffer_alloc(GEN1A_POLLER_MAX_BUFFER_SIZE);
+    gen1a_poller_detect_ctx.rx_buffer = bit_buffer_alloc(GEN1A_POLLER_MAX_BUFFER_SIZE);
+    gen1a_poller_detect_ctx.thread_id = furi_thread_get_current_id();
+    gen1a_poller_detect_ctx.detected = false;
+
+    nfc_start(nfc, gen1a_poller_detect_callback, &gen1a_poller_detect_ctx);
+    uint32_t flags = furi_thread_flags_wait(
+        GEN1A_POLLER_THREAD_FLAG_DETECTED, FuriFlagWaitAny, FuriWaitForever);
+    if(flags & GEN1A_POLLER_THREAD_FLAG_DETECTED) {
+        furi_thread_flags_clear(GEN1A_POLLER_THREAD_FLAG_DETECTED);
+    }
+    nfc_stop(nfc);
+
+    bit_buffer_free(gen1a_poller_detect_ctx.tx_buffer);
+    bit_buffer_free(gen1a_poller_detect_ctx.rx_buffer);
+
+    return gen1a_poller_detect_ctx.detected;
+}
+
+static void gen1a_poller_reset(Gen1aPoller* instance) {
+    instance->current_block = 0;
+    nfc_data_generator_fill_data(NfcDataGeneratorTypeMfClassic1k_4b, instance->mfc_device);
+}
+
+NfcCommand gen1a_poller_idle_handler(Gen1aPoller* instance) {
+    NfcCommand command = NfcCommandContinue;
+
+    gen1a_poller_reset(instance);
+    Gen1aPollerError error = gen1a_poller_wupa(instance);
+    if(error == Gen1aPollerErrorNone) {
+        instance->state = Gen1aPollerStateRequestMode;
+        instance->gen1a_event.type = Gen1aPollerEventTypeDetected;
+        command = instance->callback(instance->gen1a_event, instance->context);
+    }
+
+    return command;
+}
+
+NfcCommand gen1a_poller_request_mode_handler(Gen1aPoller* instance) {
+    NfcCommand command = NfcCommandContinue;
+
+    instance->gen1a_event.type = Gen1aPollerEventTypeRequestMode;
+    command = instance->callback(instance->gen1a_event, instance->context);
+    if(instance->gen1a_event_data.request_mode.mode == Gen1aPollerModeWipe) {
+        instance->state = Gen1aPollerStateWipe;
+    } else {
+        instance->state = Gen1aPollerStateWriteDataRequest;
+    }
+
+    return command;
+}
+
+NfcCommand gen1a_poller_wipe_handler(Gen1aPoller* instance) {
+    NfcCommand command = NfcCommandContinue;
+    Gen1aPollerError error = Gen1aPollerErrorNone;
+
+    const MfClassicData* mfc_data =
+        nfc_device_get_data(instance->mfc_device, NfcProtocolMfClassic);
+    uint16_t total_block_num = mf_classic_get_total_block_num(mfc_data->type);
+
+    if(instance->current_block == total_block_num) {
+        instance->state = Gen1aPollerStateSuccess;
+    } else {
+        do {
+            if(instance->current_block == 0) {
+                error = gen1a_poller_data_access(instance);
+                if(error != Gen1aPollerErrorNone) {
+                    instance->state = Gen1aPollerStateFail;
+                    break;
+                }
+            }
+            error = gen1a_poller_write_block(
+                instance, instance->current_block, &mfc_data->block[instance->current_block]);
+            if(error != Gen1aPollerErrorNone) {
+                instance->state = Gen1aPollerStateFail;
+                break;
+            }
+            instance->current_block++;
+        } while(false);
+    }
+
+    return command;
+}
+
+NfcCommand gen1a_poller_write_data_request_handler(Gen1aPoller* instance) {
+    NfcCommand command = NfcCommandContinue;
+
+    instance->gen1a_event.type = Gen1aPollerEventTypeRequestDataToWrite;
+    command = instance->callback(instance->gen1a_event, instance->context);
+    instance->state = Gen1aPollerStateWrite;
+
+    return command;
+}
+
+NfcCommand gen1a_poller_write_handler(Gen1aPoller* instance) {
+    NfcCommand command = NfcCommandContinue;
+    Gen1aPollerError error = Gen1aPollerErrorNone;
+
+    const MfClassicData* mfc_data = instance->gen1a_event_data.data_to_write.mfc_data;
+    uint16_t total_block_num = mf_classic_get_total_block_num(mfc_data->type);
+
+    if(instance->current_block == total_block_num) {
+        instance->state = Gen1aPollerStateSuccess;
+    } else {
+        do {
+            if(instance->current_block == 0) {
+                error = gen1a_poller_data_access(instance);
+                if(error != Gen1aPollerErrorNone) {
+                    instance->state = Gen1aPollerStateFail;
+                    break;
+                }
+            }
+            error = gen1a_poller_write_block(
+                instance, instance->current_block, &mfc_data->block[instance->current_block]);
+            if(error != Gen1aPollerErrorNone) {
+                instance->state = Gen1aPollerStateFail;
+                break;
+            }
+            instance->current_block++;
+        } while(false);
+    }
+
+    return command;
+}
+
+NfcCommand gen1a_poller_success_handler(Gen1aPoller* instance) {
+    NfcCommand command = NfcCommandContinue;
+
+    instance->gen1a_event.type = Gen1aPollerEventTypeSuccess;
+    command = instance->callback(instance->gen1a_event, instance->context);
+    instance->state = Gen1aPollerStateIdle;
+
+    return command;
+}
+
+NfcCommand gen1a_poller_fail_handler(Gen1aPoller* instance) {
+    NfcCommand command = NfcCommandContinue;
+
+    instance->gen1a_event.type = Gen1aPollerEventTypeFail;
+    command = instance->callback(instance->gen1a_event, instance->context);
+    instance->state = Gen1aPollerStateIdle;
+
+    return command;
+}
+
+static const Gen1aPollerStateHandler gen1a_poller_state_handlers[Gen1aPollerStateNum] = {
+    [Gen1aPollerStateIdle] = gen1a_poller_idle_handler,
+    [Gen1aPollerStateRequestMode] = gen1a_poller_request_mode_handler,
+    [Gen1aPollerStateWipe] = gen1a_poller_wipe_handler,
+    [Gen1aPollerStateWriteDataRequest] = gen1a_poller_write_data_request_handler,
+    [Gen1aPollerStateWrite] = gen1a_poller_write_handler,
+    [Gen1aPollerStateSuccess] = gen1a_poller_success_handler,
+    [Gen1aPollerStateFail] = gen1a_poller_fail_handler,
+
+};
+
+NfcCommand gen1a_poller_run(NfcEvent event, void* context) {
+    NfcCommand command = NfcCommandContinue;
+    Gen1aPoller* instance = context;
+
+    if(event.type == NfcEventTypePollerReady) {
+        command = gen1a_poller_state_handlers[instance->state](instance);
+    }
+
+    if(instance->session_state == Gen1aPollerSessionStateStopRequest) {
+        command = NfcCommandStop;
+    }
+
+    return command;
+}
+
+void gen1a_poller_start(Gen1aPoller* instance, Gen1aPollerCallback callback, void* context) {
+    furi_assert(instance);
+    furi_assert(callback);
+
+    instance->callback = callback;
+    instance->context = context;
+
+    instance->session_state = Gen1aPollerSessionStateStarted;
+    nfc_start(instance->nfc, gen1a_poller_run, instance);
+}
+
+void gen1a_poller_stop(Gen1aPoller* instance) {
+    furi_assert(instance);
+
+    instance->session_state = Gen1aPollerSessionStateStopRequest;
+    nfc_stop(instance->nfc);
+    instance->session_state = Gen1aPollerSessionStateIdle;
+}

+ 59 - 0
nfc_magic/lib/magic/protocols/gen1a/gen1a_poller.h

@@ -0,0 +1,59 @@
+#pragma once
+
+#include <nfc/nfc.h>
+#include <nfc/protocols/nfc_generic_event.h>
+#include <nfc/protocols/mf_classic/mf_classic.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum {
+    Gen1aPollerEventTypeDetected,
+    Gen1aPollerEventTypeRequestMode,
+    Gen1aPollerEventTypeRequestDataToWrite,
+
+    Gen1aPollerEventTypeSuccess,
+    Gen1aPollerEventTypeFail,
+} Gen1aPollerEventType;
+
+typedef enum {
+    Gen1aPollerModeWipe,
+    Gen1aPollerModeWrite,
+} Gen1aPollerMode;
+
+typedef struct {
+    Gen1aPollerMode mode;
+} Gen1aPollerEventDataRequestMode;
+
+typedef struct {
+    const MfClassicData* mfc_data;
+} Gen1aPollerEventDataRequestDataToWrite;
+
+typedef union {
+    Gen1aPollerEventDataRequestMode request_mode;
+    Gen1aPollerEventDataRequestDataToWrite data_to_write;
+} Gen1aPollerEventData;
+
+typedef struct {
+    Gen1aPollerEventType type;
+    Gen1aPollerEventData* data;
+} Gen1aPollerEvent;
+
+typedef NfcCommand (*Gen1aPollerCallback)(Gen1aPollerEvent event, void* context);
+
+typedef struct Gen1aPoller Gen1aPoller;
+
+bool gen1a_poller_detect(Nfc* nfc);
+
+Gen1aPoller* gen1a_poller_alloc(Nfc* nfc);
+
+void gen1a_poller_free(Gen1aPoller* instance);
+
+void gen1a_poller_start(Gen1aPoller* instance, Gen1aPollerCallback callback, void* context);
+
+void gen1a_poller_stop(Gen1aPoller* instance);
+
+#ifdef __cplusplus
+}
+#endif

+ 132 - 0
nfc_magic/lib/magic/protocols/gen1a/gen1a_poller_i.c

@@ -0,0 +1,132 @@
+#include "gen1a_poller_i.h"
+#include <nfc/helpers/iso14443_crc.h>
+
+#include <furi/furi.h>
+
+static Gen1aPollerError gen1a_poller_process_nfc_error(NfcError error) {
+    Gen1aPollerError ret = Gen1aPollerErrorNone;
+
+    if(error == NfcErrorNone) {
+        ret = Gen1aPollerErrorNone;
+    } else if(error == NfcErrorTimeout) {
+        ret = Gen1aPollerErrorTimeout;
+    } else {
+        ret = Gen1aPollerErrorNotPresent;
+    }
+
+    return ret;
+}
+
+Gen1aPollerError gen1a_poller_wupa(Gen1aPoller* instance) {
+    furi_assert(instance);
+
+    Gen1aPollerError ret = Gen1aPollerErrorNone;
+    bit_buffer_reset(instance->tx_buffer);
+
+    do {
+        bit_buffer_set_size(instance->tx_buffer, 7);
+        bit_buffer_set_byte(instance->tx_buffer, 0, 0x40);
+
+        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) != 4) {
+            ret = Gen1aPollerErrorProtocol;
+            break;
+        }
+        if(bit_buffer_get_byte(instance->rx_buffer, 0) != 0x0A) {
+            ret = Gen1aPollerErrorProtocol;
+            break;
+        }
+    } while(false);
+
+    return ret;
+}
+
+Gen1aPollerError gen1a_poller_data_access(Gen1aPoller* instance) {
+    furi_assert(instance);
+
+    Gen1aPollerError ret = Gen1aPollerErrorNone;
+    bit_buffer_reset(instance->tx_buffer);
+
+    do {
+        bit_buffer_set_size(instance->tx_buffer, 8);
+        bit_buffer_set_byte(instance->tx_buffer, 0, 0x43);
+
+        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) != 4) {
+            ret = Gen1aPollerErrorProtocol;
+            break;
+        }
+        if(bit_buffer_get_byte(instance->rx_buffer, 0) != 0x0A) {
+            ret = Gen1aPollerErrorProtocol;
+            break;
+        }
+    } while(false);
+
+    return ret;
+}
+
+Gen1aPollerError gen1a_poller_write_block(
+    Gen1aPoller* instance,
+    uint8_t block_num,
+    const MfClassicBlock* block) {
+    furi_assert(instance);
+    furi_assert(block);
+
+    Gen1aPollerError ret = Gen1aPollerErrorNone;
+    bit_buffer_reset(instance->tx_buffer);
+
+    do {
+        bit_buffer_append_byte(instance->tx_buffer, 0xA0);
+        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) != 4) {
+            ret = Gen1aPollerErrorProtocol;
+            break;
+        }
+        if(bit_buffer_get_byte(instance->rx_buffer, 0) != 0x0A) {
+            ret = Gen1aPollerErrorProtocol;
+            break;
+        }
+
+        bit_buffer_copy_bytes(instance->tx_buffer, block->data, sizeof(MfClassicBlock));
+        iso14443_crc_append(Iso14443CrcTypeA, instance->tx_buffer);
+
+        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) != 4) {
+            ret = Gen1aPollerErrorProtocol;
+            break;
+        }
+        if(bit_buffer_get_byte(instance->rx_buffer, 0) != 0x0A) {
+            ret = Gen1aPollerErrorProtocol;
+            break;
+        }
+    } while(false);
+
+    return ret;
+}

+ 67 - 0
nfc_magic/lib/magic/protocols/gen1a/gen1a_poller_i.h

@@ -0,0 +1,67 @@
+#pragma once
+
+#include "gen1a_poller.h"
+#include <nfc/protocols/nfc_generic_event.h>
+#include <nfc/nfc_device.h>
+#include <nfc/protocols/mf_classic/mf_classic.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define GEN1A_POLLER_MAX_BUFFER_SIZE (64U)
+#define GEN1A_POLLER_MAX_FWT (60000U)
+
+typedef enum {
+    Gen1aPollerErrorNone,
+    Gen1aPollerErrorTimeout,
+    Gen1aPollerErrorNotPresent,
+    Gen1aPollerErrorProtocol,
+} Gen1aPollerError;
+
+typedef enum {
+    Gen1aPollerStateIdle,
+    Gen1aPollerStateRequestMode,
+    Gen1aPollerStateWipe,
+    Gen1aPollerStateWriteDataRequest,
+    Gen1aPollerStateWrite,
+    Gen1aPollerStateSuccess,
+    Gen1aPollerStateFail,
+
+    Gen1aPollerStateNum,
+} Gen1aPollerState;
+
+typedef enum {
+    Gen1aPollerSessionStateIdle,
+    Gen1aPollerSessionStateStarted,
+    Gen1aPollerSessionStateStopRequest,
+} Gen1aPollerSessionState;
+
+struct Gen1aPoller {
+    Nfc* nfc;
+    Gen1aPollerState state;
+    Gen1aPollerSessionState session_state;
+
+    uint16_t current_block;
+    NfcDevice* mfc_device;
+
+    BitBuffer* tx_buffer;
+    BitBuffer* rx_buffer;
+
+    Gen1aPollerEvent gen1a_event;
+    Gen1aPollerEventData gen1a_event_data;
+
+    Gen1aPollerCallback callback;
+    void* context;
+};
+
+Gen1aPollerError gen1a_poller_wupa(Gen1aPoller* instance);
+
+Gen1aPollerError gen1a_poller_data_access(Gen1aPoller* instance);
+
+Gen1aPollerError
+    gen1a_poller_write_block(Gen1aPoller* instance, uint8_t block_num, const MfClassicBlock* block);
+
+#ifdef __cplusplus
+}
+#endif

+ 533 - 0
nfc_magic/lib/magic/protocols/gen4/gen4_poller.c

@@ -0,0 +1,533 @@
+#include "gen4_poller_i.h"
+#include <nfc/protocols/iso14443_3a/iso14443_3a.h>
+#include <nfc/protocols/iso14443_3a/iso14443_3a_poller.h>
+#include <nfc/helpers/nfc_util.h>
+#include <nfc/nfc_poller.h>
+
+#include <furi/furi.h>
+
+#define GEN4_POLLER_THREAD_FLAG_DETECTED (1U << 0)
+
+typedef NfcCommand (*Gen4PollerStateHandler)(Gen4Poller* instance);
+
+typedef struct {
+    NfcPoller* poller;
+    uint32_t password;
+    BitBuffer* tx_buffer;
+    BitBuffer* rx_buffer;
+    FuriThreadId thread_id;
+    Gen4PollerError error;
+} Gen4PollerDetectContext;
+
+static const uint8_t gen4_poller_default_config[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
+                                                     0x00, 0x09, 0x78, 0x00, 0x91, 0x02, 0xDA,
+                                                     0xBC, 0x19, 0x10, 0x10, 0x11, 0x12, 0x13,
+                                                     0x14, 0x15, 0x16, 0x04, 0x00, 0x08, 0x00};
+static const uint8_t gen4_poller_default_block_0[GEN4_POLLER_BLOCK_SIZE] =
+    {0x00, 0x01, 0x02, 0x03, 0x04, 0x04, 0x08, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+static const uint8_t gen4_poller_default_empty_block[GEN4_POLLER_BLOCK_SIZE] = {0};
+
+static const uint8_t gen4_poller_default_sector_trailer_block[GEN4_POLLER_BLOCK_SIZE] =
+    {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x07, 0x80, 0x69, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
+
+static bool gen4_poller_is_sector_trailer(uint8_t block_num) {
+    uint8_t sec_tr_block_num = 0;
+
+    if(block_num < 128) {
+        sec_tr_block_num = block_num | 0x03;
+    } else {
+        sec_tr_block_num = block_num | 0x0f;
+    }
+
+    return block_num == sec_tr_block_num;
+}
+
+Gen4Poller* gen4_poller_alloc(Nfc* nfc) {
+    furi_assert(nfc);
+
+    Gen4Poller* instance = malloc(sizeof(Gen4Poller));
+    instance->poller = nfc_poller_alloc(nfc, NfcProtocolIso14443_3a);
+
+    instance->gen4_event.data = &instance->gen4_event_data;
+
+    instance->tx_buffer = bit_buffer_alloc(GEN4_POLLER_MAX_BUFFER_SIZE);
+    instance->rx_buffer = bit_buffer_alloc(GEN4_POLLER_MAX_BUFFER_SIZE);
+
+    return instance;
+}
+
+void gen4_poller_free(Gen4Poller* instance) {
+    furi_assert(instance);
+
+    nfc_poller_free(instance->poller);
+
+    bit_buffer_free(instance->tx_buffer);
+    bit_buffer_free(instance->rx_buffer);
+
+    free(instance);
+}
+
+void gen4_poller_set_password(Gen4Poller* instance, uint32_t password) {
+    furi_assert(instance);
+
+    instance->password = password;
+}
+
+NfcCommand gen4_poller_detect_callback(NfcGenericEvent event, void* context) {
+    furi_assert(context);
+    furi_assert(event.protocol == NfcProtocolIso14443_3a);
+    furi_assert(event.instance);
+    furi_assert(event.event_data);
+
+    NfcCommand command = NfcCommandStop;
+    Gen4PollerDetectContext* gen4_poller_detect_ctx = context;
+    Iso14443_3aPoller* iso3_poller = event.instance;
+    Iso14443_3aPollerEvent* iso3_event = event.event_data;
+    gen4_poller_detect_ctx->error = Gen4PollerErrorTimeout;
+
+    if(iso3_event->type == Iso14443_3aPollerEventTypeReady) {
+        do {
+            bit_buffer_append_byte(gen4_poller_detect_ctx->tx_buffer, GEN4_CMD_PREFIX);
+            uint8_t pwd_arr[4] = {};
+            nfc_util_num2bytes(gen4_poller_detect_ctx->password, COUNT_OF(pwd_arr), pwd_arr);
+            bit_buffer_append_bytes(gen4_poller_detect_ctx->tx_buffer, pwd_arr, COUNT_OF(pwd_arr));
+            bit_buffer_append_byte(gen4_poller_detect_ctx->tx_buffer, GEN4_CMD_GET_CFG);
+
+            Iso14443_3aError error = iso14443_3a_poller_send_standard_frame(
+                iso3_poller,
+                gen4_poller_detect_ctx->tx_buffer,
+                gen4_poller_detect_ctx->rx_buffer,
+                GEN4_POLLER_MAX_FWT);
+
+            if(error != Iso14443_3aErrorNone) {
+                gen4_poller_detect_ctx->error = Gen4PollerErrorProtocol;
+                break;
+            }
+            size_t rx_bytes = bit_buffer_get_size_bytes(gen4_poller_detect_ctx->rx_buffer);
+            if((rx_bytes != 30) && (rx_bytes != 32)) {
+                gen4_poller_detect_ctx->error = Gen4PollerErrorProtocol;
+                break;
+            }
+
+            gen4_poller_detect_ctx->error = Gen4PollerErrorNone;
+        } while(false);
+    } else if(iso3_event->type == Iso14443_3aPollerEventTypeError) {
+        gen4_poller_detect_ctx->error = Gen4PollerErrorTimeout;
+    }
+    furi_thread_flags_set(gen4_poller_detect_ctx->thread_id, GEN4_POLLER_THREAD_FLAG_DETECTED);
+
+    return command;
+}
+
+Gen4PollerError gen4_poller_detect(Nfc* nfc, uint32_t password) {
+    furi_assert(nfc);
+
+    Gen4PollerDetectContext gen4_poller_detect_ctx = {};
+    gen4_poller_detect_ctx.poller = nfc_poller_alloc(nfc, NfcProtocolIso14443_3a);
+    gen4_poller_detect_ctx.password = password;
+    gen4_poller_detect_ctx.tx_buffer = bit_buffer_alloc(GEN4_POLLER_MAX_BUFFER_SIZE);
+    gen4_poller_detect_ctx.rx_buffer = bit_buffer_alloc(GEN4_POLLER_MAX_BUFFER_SIZE);
+    gen4_poller_detect_ctx.thread_id = furi_thread_get_current_id();
+    gen4_poller_detect_ctx.error = Gen4PollerErrorNone;
+
+    nfc_poller_start(
+        gen4_poller_detect_ctx.poller, gen4_poller_detect_callback, &gen4_poller_detect_ctx);
+    uint32_t flags =
+        furi_thread_flags_wait(GEN4_POLLER_THREAD_FLAG_DETECTED, FuriFlagWaitAny, FuriWaitForever);
+    if(flags & GEN4_POLLER_THREAD_FLAG_DETECTED) {
+        furi_thread_flags_clear(GEN4_POLLER_THREAD_FLAG_DETECTED);
+    }
+    nfc_poller_stop(gen4_poller_detect_ctx.poller);
+
+    nfc_poller_free(gen4_poller_detect_ctx.poller);
+    bit_buffer_free(gen4_poller_detect_ctx.tx_buffer);
+    bit_buffer_free(gen4_poller_detect_ctx.rx_buffer);
+
+    return gen4_poller_detect_ctx.error;
+}
+
+NfcCommand gen4_poller_idle_handler(Gen4Poller* instance) {
+    NfcCommand command = NfcCommandContinue;
+
+    instance->current_block = 0;
+    memset(instance->config, 0, sizeof(instance->config));
+    instance->gen4_event.type = Gen4PollerEventTypeCardDetected;
+    command = instance->callback(instance->gen4_event, instance->context);
+    instance->state = Gen4PollerStateRequestMode;
+
+    return command;
+}
+
+NfcCommand gen4_poller_request_mode_handler(Gen4Poller* instance) {
+    NfcCommand command = NfcCommandContinue;
+
+    instance->gen4_event.type = Gen4PollerEventTypeRequestMode;
+    command = instance->callback(instance->gen4_event, instance->context);
+    if(instance->gen4_event_data.request_mode.mode == Gen4PollerModeWipe) {
+        instance->state = Gen4PollerStateWipe;
+    } else if(instance->gen4_event_data.request_mode.mode == Gen4PollerModeWrite) {
+        instance->state = Gen4PollerStateRequestWriteData;
+    } else if(instance->gen4_event_data.request_mode.mode == Gen4PollerModeSetPassword) {
+        instance->state = Gen4PollerStateChangePassword;
+    } else {
+        instance->state = Gen4PollerStateFail;
+    }
+
+    return command;
+}
+
+NfcCommand gen4_poller_wipe_handler(Gen4Poller* instance) {
+    NfcCommand command = NfcCommandContinue;
+
+    do {
+        Gen4PollerError error = Gen4PollerErrorNone;
+        if(instance->current_block == 0) {
+            error = gen4_poller_set_config(
+                instance,
+                instance->password,
+                gen4_poller_default_config,
+                sizeof(gen4_poller_default_config),
+                false);
+            if(error != Gen4PollerErrorNone) {
+                FURI_LOG_D(TAG, "Failed to set default config: %d", error);
+                instance->state = Gen4PollerStateFail;
+                break;
+            }
+            error = gen4_poller_write_block(
+                instance, instance->password, instance->current_block, gen4_poller_default_block_0);
+            if(error != Gen4PollerErrorNone) {
+                FURI_LOG_D(TAG, "Failed to write 0 block: %d", error);
+                instance->state = Gen4PollerStateFail;
+                break;
+            }
+        } else if(instance->current_block < GEN4_POLLER_BLOCKS_TOTAL) {
+            const uint8_t* block = gen4_poller_is_sector_trailer(instance->current_block) ?
+                                       gen4_poller_default_sector_trailer_block :
+                                       gen4_poller_default_empty_block;
+            error = gen4_poller_write_block(
+                instance, instance->password, instance->current_block, block);
+            if(error != Gen4PollerErrorNone) {
+                FURI_LOG_D(TAG, "Failed to write %d block: %d", instance->current_block, error);
+                instance->state = Gen4PollerStateFail;
+                break;
+            }
+        } else {
+            instance->state = Gen4PollerStateSuccess;
+            break;
+        }
+        instance->current_block++;
+    } while(false);
+
+    return command;
+}
+
+NfcCommand gen4_poller_request_write_data_handler(Gen4Poller* instance) {
+    NfcCommand command = NfcCommandContinue;
+
+    instance->gen4_event.type = Gen4PollerEventTypeRequestDataToWrite;
+    command = instance->callback(instance->gen4_event, instance->context);
+    instance->protocol = instance->gen4_event_data.request_data.protocol;
+    instance->data = instance->gen4_event_data.request_data.data;
+
+    if((instance->protocol == NfcProtocolMfClassic) ||
+       (instance->protocol == NfcProtocolMfUltralight)) {
+        instance->state = Gen4PollerStateWrite;
+    } else {
+        FURI_LOG_E(TAG, "Unsupported protocol");
+        instance->state = Gen4PollerStateFail;
+    }
+
+    return command;
+}
+
+static NfcCommand gen4_poller_write_mf_classic(Gen4Poller* instance) {
+    NfcCommand command = NfcCommandContinue;
+
+    do {
+        const MfClassicData* mfc_data = instance->data;
+        const Iso14443_3aData* iso3_data = mfc_data->iso14443_3a_data;
+        if(instance->current_block == 0) {
+            instance->config[0] = 0x00;
+            instance->total_blocks = mf_classic_get_total_block_num(mfc_data->type);
+
+            if(iso3_data->uid_len == 4) {
+                instance->config[1] = Gen4PollerUIDLengthSingle;
+            } else if(iso3_data->uid_len == 7) {
+                instance->config[1] = Gen4PollerUIDLengthDouble;
+            } else {
+                FURI_LOG_E(TAG, "Unsupported UID len: %d", iso3_data->uid_len);
+                instance->state = Gen4PollerStateFail;
+                break;
+            }
+
+            instance->config[6] = Gen4PollerShadowModeIgnore;
+            instance->config[24] = iso3_data->atqa[0];
+            instance->config[25] = iso3_data->atqa[1];
+            instance->config[26] = iso3_data->sak;
+            instance->config[27] = 0x00;
+            instance->config[28] = instance->total_blocks;
+            instance->config[29] = 0x01;
+
+            Gen4PollerError error = gen4_poller_set_config(
+                instance, instance->password, instance->config, sizeof(instance->config), false);
+            if(error != Gen4PollerErrorNone) {
+                FURI_LOG_D(TAG, "Failed to write config: %d", error);
+                instance->state = Gen4PollerStateFail;
+                break;
+            }
+        }
+        if(instance->current_block < instance->total_blocks) {
+            FURI_LOG_D(TAG, "Writing block %d", instance->current_block);
+            Gen4PollerError error = gen4_poller_write_block(
+                instance,
+                instance->password,
+                instance->current_block,
+                mfc_data->block[instance->current_block].data);
+            if(error != Gen4PollerErrorNone) {
+                FURI_LOG_D(TAG, "Failed to write %d block: %d", instance->current_block, error);
+                instance->state = Gen4PollerStateFail;
+                break;
+            }
+        } else {
+            instance->state = Gen4PollerStateSuccess;
+            break;
+        }
+        instance->current_block++;
+    } while(false);
+
+    return command;
+}
+
+static NfcCommand gen4_poller_write_mf_ultralight(Gen4Poller* instance) {
+    NfcCommand command = NfcCommandContinue;
+
+    do {
+        const MfUltralightData* mfu_data = instance->data;
+        const Iso14443_3aData* iso3_data = mfu_data->iso14443_3a_data;
+        if(instance->current_block == 0) {
+            instance->total_blocks = 64;
+            instance->config[0] = 0x01;
+            switch(mfu_data->type) {
+            case MfUltralightTypeNTAG203:
+            case MfUltralightTypeNTAG213:
+            case MfUltralightTypeNTAG215:
+            case MfUltralightTypeNTAG216:
+            case MfUltralightTypeNTAGI2C1K:
+            case MfUltralightTypeNTAGI2C2K:
+            case MfUltralightTypeNTAGI2CPlus1K:
+            case MfUltralightTypeNTAGI2CPlus2K:
+                instance->config[27] = Gen4PollerUltralightModeNTAG;
+                instance->total_blocks = 64 * 2;
+                break;
+
+            case MfUltralightTypeUL11:
+            case MfUltralightTypeUL21:
+                // UL-C?
+                // UL?
+            default:
+                instance->config[27] = Gen4PollerUltralightModeUL_EV1;
+                break;
+            }
+
+            if(iso3_data->uid_len == 4) {
+                instance->config[1] = Gen4PollerUIDLengthSingle;
+            } else if(iso3_data->uid_len == 7) {
+                instance->config[1] = Gen4PollerUIDLengthDouble;
+            } else {
+                FURI_LOG_E(TAG, "Unsupported UID len: %d", iso3_data->uid_len);
+                instance->state = Gen4PollerStateFail;
+                break;
+            }
+
+            instance->config[6] = Gen4PollerShadowModeHighSpeedIgnore;
+            instance->config[24] = iso3_data->atqa[0];
+            instance->config[25] = iso3_data->atqa[1];
+            instance->config[26] = iso3_data->sak;
+            instance->config[27] = 0x00;
+            instance->config[28] = instance->total_blocks;
+            instance->config[29] = 0x01;
+
+            Gen4PollerError error = gen4_poller_set_config(
+                instance, instance->password, instance->config, sizeof(instance->config), false);
+            if(error != Gen4PollerErrorNone) {
+                FURI_LOG_D(TAG, "Failed to write config: %d", error);
+                instance->state = Gen4PollerStateFail;
+                break;
+            }
+        }
+
+        if(instance->current_block < mfu_data->pages_read) {
+            FURI_LOG_D(
+                TAG, "Writing page %zu / %zu", instance->current_block, mfu_data->pages_read);
+            Gen4PollerError error = gen4_poller_write_block(
+                instance,
+                instance->password,
+                instance->current_block,
+                mfu_data->page[instance->current_block].data);
+            if(error != Gen4PollerErrorNone) {
+                FURI_LOG_D(TAG, "Failed to write %d page: %d", instance->current_block, error);
+                instance->state = Gen4PollerStateFail;
+                break;
+            }
+            instance->current_block++;
+        } else {
+            uint8_t block[GEN4_POLLER_BLOCK_SIZE] = {};
+            bool write_success = true;
+            for(size_t i = 0; i < 8; i++) {
+                memcpy(block, &mfu_data->signature.data[i * 4], 4); //-V1086
+                Gen4PollerError error =
+                    gen4_poller_write_block(instance, instance->password, 0xF2 + i, block);
+                if(error != Gen4PollerErrorNone) {
+                    write_success = false;
+                    break;
+                }
+            }
+            if(!write_success) {
+                FURI_LOG_E(TAG, "Failed to write Signature");
+                instance->state = Gen4PollerStateFail;
+                break;
+            }
+
+            block[0] = mfu_data->version.header;
+            block[1] = mfu_data->version.vendor_id;
+            block[2] = mfu_data->version.prod_type;
+            block[3] = mfu_data->version.prod_subtype;
+            Gen4PollerError error =
+                gen4_poller_write_block(instance, instance->password, 0xFA, block);
+            if(error != Gen4PollerErrorNone) {
+                FURI_LOG_E(TAG, "Failed to write 1st part Version");
+                instance->state = Gen4PollerStateFail;
+                break;
+            }
+
+            block[0] = mfu_data->version.prod_ver_major;
+            block[1] = mfu_data->version.prod_ver_minor;
+            block[2] = mfu_data->version.storage_size;
+            block[3] = mfu_data->version.protocol_type;
+            error = gen4_poller_write_block(instance, instance->password, 0xFB, block);
+            if(error != Gen4PollerErrorNone) {
+                FURI_LOG_E(TAG, "Failed to write 2nd part Version");
+                instance->state = Gen4PollerStateFail;
+                break;
+            }
+
+            instance->state = Gen4PollerStateSuccess;
+        }
+    } while(false);
+
+    return command;
+}
+
+NfcCommand gen4_poller_write_handler(Gen4Poller* instance) {
+    NfcCommand command = NfcCommandContinue;
+
+    memcpy(instance->config, gen4_poller_default_config, sizeof(gen4_poller_default_config));
+    uint8_t password_arr[4] = {};
+    nfc_util_num2bytes(instance->password, sizeof(password_arr), password_arr);
+    memcpy(&instance->config[2], password_arr, sizeof(password_arr));
+    memset(&instance->config[7], 0, 17);
+    if(instance->protocol == NfcProtocolMfClassic) {
+        command = gen4_poller_write_mf_classic(instance);
+    } else if(instance->protocol == NfcProtocolMfUltralight) {
+        command = gen4_poller_write_mf_ultralight(instance);
+    } else {
+        furi_crash("Unsupported protocol to write");
+    }
+
+    return command;
+}
+
+NfcCommand gen4_poller_change_password_handler(Gen4Poller* instance) {
+    NfcCommand command = NfcCommandContinue;
+
+    do {
+        instance->gen4_event.type = Gen4PollerEventTypeRequestNewPassword;
+        command = instance->callback(instance->gen4_event, instance->context);
+        if(command != NfcCommandContinue) break;
+
+        uint32_t new_password = instance->gen4_event_data.request_password.password;
+        Gen4PollerError error =
+            gen4_poller_change_password(instance, instance->password, new_password);
+        if(error != Gen4PollerErrorNone) {
+            FURI_LOG_E(TAG, "Failed to change password: %d", error);
+            instance->state = Gen4PollerStateFail;
+            break;
+        }
+
+        instance->password = new_password;
+        instance->state = Gen4PollerStateSuccess;
+    } while(false);
+
+    return command;
+}
+
+NfcCommand gen4_poller_success_handler(Gen4Poller* instance) {
+    NfcCommand command = NfcCommandContinue;
+
+    instance->gen4_event.type = Gen4PollerEventTypeSuccess;
+    command = instance->callback(instance->gen4_event, instance->context);
+    if(command != NfcCommandStop) {
+        furi_delay_ms(100);
+    }
+
+    return command;
+}
+
+NfcCommand gen4_poller_fail_handler(Gen4Poller* instance) {
+    NfcCommand command = NfcCommandContinue;
+
+    instance->gen4_event.type = Gen4PollerEventTypeFail;
+    command = instance->callback(instance->gen4_event, instance->context);
+    if(command != NfcCommandStop) {
+        furi_delay_ms(100);
+    }
+
+    return command;
+}
+
+static const Gen4PollerStateHandler gen4_poller_state_handlers[Gen4PollerStateNum] = {
+    [Gen4PollerStateIdle] = gen4_poller_idle_handler,
+    [Gen4PollerStateRequestMode] = gen4_poller_request_mode_handler,
+    [Gen4PollerStateRequestWriteData] = gen4_poller_request_write_data_handler,
+    [Gen4PollerStateWrite] = gen4_poller_write_handler,
+    [Gen4PollerStateWipe] = gen4_poller_wipe_handler,
+    [Gen4PollerStateChangePassword] = gen4_poller_change_password_handler,
+    [Gen4PollerStateSuccess] = gen4_poller_success_handler,
+    [Gen4PollerStateFail] = gen4_poller_fail_handler,
+
+};
+
+static NfcCommand gen4_poller_callback(NfcGenericEvent event, void* context) {
+    furi_assert(context);
+    furi_assert(event.protocol == NfcProtocolIso14443_3a);
+    furi_assert(event.event_data);
+    furi_assert(event.instance);
+
+    NfcCommand command = NfcCommandContinue;
+    Gen4Poller* instance = context;
+    instance->iso3_poller = event.instance;
+    Iso14443_3aPollerEvent* iso3_event = event.event_data;
+
+    if(iso3_event->type == Iso14443_3aPollerEventTypeReady) {
+        command = gen4_poller_state_handlers[instance->state](instance);
+    }
+
+    return command;
+}
+
+void gen4_poller_start(Gen4Poller* instance, Gen4PollerCallback callback, void* context) {
+    furi_assert(instance);
+    furi_assert(callback);
+
+    instance->callback = callback;
+    instance->context = context;
+
+    nfc_poller_start(instance->poller, gen4_poller_callback, instance);
+}
+
+void gen4_poller_stop(Gen4Poller* instance) {
+    furi_assert(instance);
+
+    nfc_poller_stop(instance->poller);
+}

+ 84 - 0
nfc_magic/lib/magic/protocols/gen4/gen4_poller.h

@@ -0,0 +1,84 @@
+#pragma once
+
+#include <nfc/nfc.h>
+#include <nfc/protocols/nfc_protocol.h>
+#include <nfc/protocols/mf_classic/mf_classic.h>
+#include <nfc/protocols/mf_ultralight/mf_ultralight.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define GEN4_CMD_PREFIX (0xCF)
+#define GEN4_CMD_GET_CFG (0xC6)
+#define GEN4_CMD_WRITE (0xCD)
+#define GEN4_CMD_READ (0xCE)
+#define GEN4_CMD_SET_CFG (0xF0)
+#define GEN4_CMD_FUSE_CFG (0xF1)
+#define GEN4_CMD_SET_PWD (0xFE)
+
+typedef enum {
+    Gen4PollerErrorNone,
+    Gen4PollerErrorTimeout,
+    Gen4PollerErrorProtocol,
+} Gen4PollerError;
+
+typedef enum {
+    Gen4PollerEventTypeCardDetected,
+    Gen4PollerEventTypeRequestMode,
+    Gen4PollerEventTypeRequestDataToWrite,
+    Gen4PollerEventTypeRequestNewPassword,
+
+    Gen4PollerEventTypeSuccess,
+    Gen4PollerEventTypeFail,
+} Gen4PollerEventType;
+
+typedef enum {
+    Gen4PollerModeWipe,
+    Gen4PollerModeWrite,
+    Gen4PollerModeSetPassword,
+} Gen4PollerMode;
+
+typedef struct {
+    Gen4PollerMode mode;
+} Gen4PollerEventDataRequestMode;
+
+typedef struct {
+    NfcProtocol protocol;
+    const NfcDeviceData* data;
+} Gen4PollerEventDataRequestDataToWrite;
+
+typedef struct {
+    uint32_t password;
+} Gen4PollerEventDataRequestNewPassword;
+
+typedef union {
+    Gen4PollerEventDataRequestMode request_mode;
+    Gen4PollerEventDataRequestDataToWrite request_data;
+    Gen4PollerEventDataRequestNewPassword request_password;
+} Gen4PollerEventData;
+
+typedef struct {
+    Gen4PollerEventType type;
+    Gen4PollerEventData* data;
+} Gen4PollerEvent;
+
+typedef NfcCommand (*Gen4PollerCallback)(Gen4PollerEvent event, void* context);
+
+typedef struct Gen4Poller Gen4Poller;
+
+Gen4PollerError gen4_poller_detect(Nfc* nfc, uint32_t password);
+
+Gen4Poller* gen4_poller_alloc(Nfc* nfc);
+
+void gen4_poller_free(Gen4Poller* instance);
+
+void gen4_poller_set_password(Gen4Poller* instance, uint32_t password);
+
+void gen4_poller_start(Gen4Poller* instance, Gen4PollerCallback callback, void* context);
+
+void gen4_poller_stop(Gen4Poller* instance);
+
+#ifdef __cplusplus
+}
+#endif

+ 129 - 0
nfc_magic/lib/magic/protocols/gen4/gen4_poller_i.c

@@ -0,0 +1,129 @@
+#include "gen4_poller_i.h"
+
+#include <nfc/protocols/iso14443_3a/iso14443_3a_poller.h>
+#include <nfc/helpers/nfc_util.h>
+
+#define GEN4_CMD_PREFIX (0xCF)
+
+#define GEN4_CMD_GET_CFG (0xC6)
+#define GEN4_CMD_WRITE (0xCD)
+#define GEN4_CMD_READ (0xCE)
+#define GEN4_CMD_SET_CFG (0xF0)
+#define GEN4_CMD_FUSE_CFG (0xF1)
+#define GEN4_CMD_SET_PWD (0xFE)
+
+static Gen4PollerError gen4_poller_process_error(Iso14443_3aError error) {
+    Gen4PollerError ret = Gen4PollerErrorNone;
+
+    if(error == Iso14443_3aErrorNone) {
+        ret = Gen4PollerErrorNone;
+    } else {
+        ret = Gen4PollerErrorTimeout;
+    }
+
+    return ret;
+}
+
+Gen4PollerError gen4_poller_set_config(
+    Gen4Poller* instance,
+    uint32_t password,
+    const uint8_t* config,
+    size_t config_size,
+    bool fuse) {
+    Gen4PollerError ret = Gen4PollerErrorNone;
+    bit_buffer_reset(instance->tx_buffer);
+
+    do {
+        uint8_t password_arr[4] = {};
+        nfc_util_num2bytes(password, COUNT_OF(password_arr), password_arr);
+        bit_buffer_append_byte(instance->tx_buffer, GEN4_CMD_PREFIX);
+        bit_buffer_append_bytes(instance->tx_buffer, password_arr, COUNT_OF(password_arr));
+        uint8_t fuse_config = fuse ? GEN4_CMD_FUSE_CFG : GEN4_CMD_SET_CFG;
+        bit_buffer_append_byte(instance->tx_buffer, fuse_config);
+        bit_buffer_append_bytes(instance->tx_buffer, config, config_size);
+
+        Iso14443_3aError error = iso14443_3a_poller_send_standard_frame(
+            instance->iso3_poller, instance->tx_buffer, instance->rx_buffer, GEN4_POLLER_MAX_FWT);
+
+        if(error != Iso14443_3aErrorNone) {
+            ret = gen4_poller_process_error(error);
+            break;
+        }
+
+        size_t rx_bytes = bit_buffer_get_size_bytes(instance->rx_buffer);
+        if(rx_bytes != 2) {
+            ret = Gen4PollerErrorProtocol;
+            break;
+        }
+    } while(false);
+
+    return ret;
+}
+
+Gen4PollerError gen4_poller_write_block(
+    Gen4Poller* instance,
+    uint32_t password,
+    uint8_t block_num,
+    const uint8_t* data) {
+    Gen4PollerError ret = Gen4PollerErrorNone;
+    bit_buffer_reset(instance->tx_buffer);
+
+    do {
+        uint8_t password_arr[4] = {};
+        nfc_util_num2bytes(password, COUNT_OF(password_arr), password_arr);
+        bit_buffer_append_byte(instance->tx_buffer, GEN4_CMD_PREFIX);
+        bit_buffer_append_bytes(instance->tx_buffer, password_arr, COUNT_OF(password_arr));
+        bit_buffer_append_byte(instance->tx_buffer, GEN4_CMD_WRITE);
+        bit_buffer_append_byte(instance->tx_buffer, block_num);
+        bit_buffer_append_bytes(instance->tx_buffer, data, GEN4_POLLER_BLOCK_SIZE);
+
+        Iso14443_3aError error = iso14443_3a_poller_send_standard_frame(
+            instance->iso3_poller, instance->tx_buffer, instance->rx_buffer, GEN4_POLLER_MAX_FWT);
+
+        if(error != Iso14443_3aErrorNone) {
+            ret = gen4_poller_process_error(error);
+            break;
+        }
+
+        size_t rx_bytes = bit_buffer_get_size_bytes(instance->rx_buffer);
+        if(rx_bytes != 2) {
+            ret = Gen4PollerErrorProtocol;
+            break;
+        }
+    } while(false);
+
+    return ret;
+}
+
+Gen4PollerError
+    gen4_poller_change_password(Gen4Poller* instance, uint32_t pwd_current, uint32_t pwd_new) {
+    Gen4PollerError ret = Gen4PollerErrorNone;
+    bit_buffer_reset(instance->tx_buffer);
+
+    do {
+        uint8_t password_arr[4] = {};
+        nfc_util_num2bytes(pwd_current, COUNT_OF(password_arr), password_arr);
+        bit_buffer_append_byte(instance->tx_buffer, GEN4_CMD_PREFIX);
+        bit_buffer_append_bytes(instance->tx_buffer, password_arr, COUNT_OF(password_arr));
+
+        bit_buffer_append_byte(instance->tx_buffer, GEN4_CMD_SET_PWD);
+        nfc_util_num2bytes(pwd_new, COUNT_OF(password_arr), password_arr);
+        bit_buffer_append_bytes(instance->tx_buffer, password_arr, COUNT_OF(password_arr));
+
+        Iso14443_3aError error = iso14443_3a_poller_send_standard_frame(
+            instance->iso3_poller, instance->tx_buffer, instance->rx_buffer, GEN4_POLLER_MAX_FWT);
+
+        if(error != Iso14443_3aErrorNone) {
+            ret = gen4_poller_process_error(error);
+            break;
+        }
+
+        size_t rx_bytes = bit_buffer_get_size_bytes(instance->rx_buffer);
+        if(rx_bytes != 2) {
+            ret = Gen4PollerErrorProtocol;
+            break;
+        }
+    } while(false);
+
+    return ret;
+}

+ 101 - 0
nfc_magic/lib/magic/protocols/gen4/gen4_poller_i.h

@@ -0,0 +1,101 @@
+#pragma once
+
+#include "gen4_poller.h"
+#include <nfc/nfc_poller.h>
+#include <nfc/protocols/iso14443_3a/iso14443_3a_poller.h>
+
+#define TAG "Gen4Poller"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define GEN4_POLLER_MAX_BUFFER_SIZE (64U)
+#define GEN4_POLLER_MAX_FWT (200000U)
+
+#define GEN4_POLLER_BLOCK_SIZE (16)
+#define GEN4_POLLER_BLOCKS_TOTAL (256)
+
+#define GEN4_POLLER_CONFIG_SIZE_MAX (30)
+
+typedef enum {
+    Gen4PollerUIDLengthSingle = 0x00,
+    Gen4PollerUIDLengthDouble = 0x01,
+    Gen4PollerUIDLengthTriple = 0x02
+} Gen4PollerUIDLength;
+
+typedef enum {
+    Gen4PollerUltralightModeUL_EV1 = 0x00,
+    Gen4PollerUltralightModeNTAG = 0x01,
+    Gen4PollerUltralightModeUL_C = 0x02,
+    Gen4PollerUltralightModeUL = 0x03
+} Gen4PollerUltralightMode;
+
+typedef enum {
+    // for writing original (shadow) data
+    Gen4PollerShadowModePreWrite = 0x00,
+    // written data can be read once before restored to original
+    Gen4PollerShadowModeRestore = 0x01,
+    // written data is discarded
+    Gen4PollerShadowModeIgnore = 0x02,
+    // apparently for UL?
+    Gen4PollerShadowModeHighSpeedIgnore = 0x03
+} Gen4PollerShadowMode;
+
+typedef enum {
+    Gen4PollerStateIdle,
+    Gen4PollerStateRequestMode,
+    Gen4PollerStateRequestWriteData,
+    Gen4PollerStateWrite,
+    Gen4PollerStateWipe,
+    Gen4PollerStateChangePassword,
+    Gen4PollerStateSuccess,
+    Gen4PollerStateFail,
+
+    Gen4PollerStateNum,
+} Gen4PollerState;
+
+struct Gen4Poller {
+    NfcPoller* poller;
+    Iso14443_3aPoller* iso3_poller;
+    Gen4PollerState state;
+    uint32_t password;
+
+    BitBuffer* tx_buffer;
+    BitBuffer* rx_buffer;
+
+    uint16_t current_block;
+    uint16_t total_blocks;
+
+    NfcProtocol protocol;
+    const NfcDeviceData* data;
+    uint32_t new_password;
+
+    uint8_t config[GEN4_POLLER_CONFIG_SIZE_MAX];
+
+    Gen4PollerEvent gen4_event;
+    Gen4PollerEventData gen4_event_data;
+
+    Gen4PollerCallback callback;
+    void* context;
+};
+
+Gen4PollerError gen4_poller_set_config(
+    Gen4Poller* instance,
+    uint32_t password,
+    const uint8_t* config,
+    size_t config_size,
+    bool fuse);
+
+Gen4PollerError gen4_poller_write_block(
+    Gen4Poller* instance,
+    uint32_t password,
+    uint8_t block_num,
+    const uint8_t* data);
+
+Gen4PollerError
+    gen4_poller_change_password(Gen4Poller* instance, uint32_t pwd_current, uint32_t pwd_new);
+
+#ifdef __cplusplus
+}
+#endif

+ 14 - 0
nfc_magic/lib/magic/protocols/nfc_magic_protocols.c

@@ -0,0 +1,14 @@
+#include "nfc_magic_protocols.h"
+
+#include <furi/furi.h>
+
+static const char* nfc_magic_protocol_names[NfcMagicProtocolNum] = {
+    [NfcMagicProtocolGen1] = "Classic Gen 1A/B",
+    [NfcMagicProtocolGen4] = "Gen 4 GTU",
+};
+
+const char* nfc_magic_protocols_get_name(NfcMagicProtocol protocol) {
+    furi_assert(protocol < NfcMagicProtocolNum);
+
+    return nfc_magic_protocol_names[protocol];
+}

+ 19 - 0
nfc_magic/lib/magic/protocols/nfc_magic_protocols.h

@@ -0,0 +1,19 @@
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum {
+    NfcMagicProtocolGen1,
+    NfcMagicProtocolGen4,
+
+    NfcMagicProtocolNum,
+    NfcMagicProtocolInvalid,
+} NfcMagicProtocol;
+
+const char* nfc_magic_protocols_get_name(NfcMagicProtocol protocol);
+
+#ifdef __cplusplus
+}
+#endif

+ 289 - 0
nfc_magic/nfc_magic_app.c

@@ -0,0 +1,289 @@
+#include "nfc_magic_app_i.h"
+
+bool nfc_magic_app_custom_event_callback(void* context, uint32_t event) {
+    furi_assert(context);
+    NfcMagicApp* instance = context;
+
+    return scene_manager_handle_custom_event(instance->scene_manager, event);
+}
+
+bool nfc_magic_app_back_event_callback(void* context) {
+    furi_assert(context);
+    NfcMagicApp* instance = context;
+
+    return scene_manager_handle_back_event(instance->scene_manager);
+}
+
+void nfc_magic_app_tick_event_callback(void* context) {
+    furi_assert(context);
+    NfcMagicApp* instance = context;
+
+    scene_manager_handle_tick_event(instance->scene_manager);
+}
+
+void nfc_magic_app_show_loading_popup(void* context, bool show) {
+    NfcMagicApp* instance = context;
+
+    if(show) {
+        // Raise timer priority so that animations can play
+        furi_timer_set_thread_priority(FuriTimerThreadPriorityElevated);
+        view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewLoading);
+    } else {
+        // Restore default timer priority
+        furi_timer_set_thread_priority(FuriTimerThreadPriorityNormal);
+    }
+}
+
+NfcMagicApp* nfc_magic_app_alloc() {
+    NfcMagicApp* instance = malloc(sizeof(NfcMagicApp));
+
+    instance->view_dispatcher = view_dispatcher_alloc();
+    instance->scene_manager = scene_manager_alloc(&nfc_magic_scene_handlers, instance);
+    view_dispatcher_enable_queue(instance->view_dispatcher);
+    view_dispatcher_set_event_callback_context(instance->view_dispatcher, instance);
+    view_dispatcher_set_custom_event_callback(
+        instance->view_dispatcher, nfc_magic_app_custom_event_callback);
+    view_dispatcher_set_navigation_event_callback(
+        instance->view_dispatcher, nfc_magic_app_back_event_callback);
+    view_dispatcher_set_tick_event_callback(
+        instance->view_dispatcher, nfc_magic_app_tick_event_callback, 100);
+
+    // Nfc 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();
+
+    // Open GUI record
+    instance->gui = furi_record_open(RECORD_GUI);
+    view_dispatcher_attach_to_gui(
+        instance->view_dispatcher, instance->gui, ViewDispatcherTypeFullscreen);
+
+    // Open Notification record
+    instance->notifications = furi_record_open(RECORD_NOTIFICATION);
+
+    // Open Dialogs
+    instance->dialogs = furi_record_open(RECORD_DIALOGS);
+
+    // Open Storage
+    instance->storage = furi_record_open(RECORD_STORAGE);
+
+    // Submenu
+    instance->submenu = submenu_alloc();
+    view_dispatcher_add_view(
+        instance->view_dispatcher, NfcMagicAppViewMenu, submenu_get_view(instance->submenu));
+
+    // Popup
+    instance->popup = popup_alloc();
+    view_dispatcher_add_view(
+        instance->view_dispatcher, NfcMagicAppViewPopup, popup_get_view(instance->popup));
+
+    // Loading
+    instance->loading = loading_alloc();
+    view_dispatcher_add_view(
+        instance->view_dispatcher, NfcMagicAppViewLoading, loading_get_view(instance->loading));
+
+    // Text Input
+    instance->text_input = text_input_alloc();
+    view_dispatcher_add_view(
+        instance->view_dispatcher,
+        NfcMagicAppViewTextInput,
+        text_input_get_view(instance->text_input));
+
+    // Byte Input
+    instance->byte_input = byte_input_alloc();
+    view_dispatcher_add_view(
+        instance->view_dispatcher,
+        NfcMagicAppViewByteInput,
+        byte_input_get_view(instance->byte_input));
+
+    // Custom Widget
+    instance->widget = widget_alloc();
+    view_dispatcher_add_view(
+        instance->view_dispatcher, NfcMagicAppViewWidget, widget_get_view(instance->widget));
+
+    instance->nfc = nfc_alloc();
+    instance->scanner = nfc_magic_scanner_alloc(instance->nfc);
+
+    return instance;
+}
+
+void nfc_magic_app_free(NfcMagicApp* instance) {
+    furi_assert(instance);
+
+    // Nfc device
+    nfc_device_free(instance->source_dev);
+    furi_string_free(instance->file_name);
+    furi_string_free(instance->file_path);
+
+    // Submenu
+    view_dispatcher_remove_view(instance->view_dispatcher, NfcMagicAppViewMenu);
+    submenu_free(instance->submenu);
+
+    // Popup
+    view_dispatcher_remove_view(instance->view_dispatcher, NfcMagicAppViewPopup);
+    popup_free(instance->popup);
+
+    // Loading
+    view_dispatcher_remove_view(instance->view_dispatcher, NfcMagicAppViewLoading);
+    loading_free(instance->loading);
+
+    // Text Input
+    view_dispatcher_remove_view(instance->view_dispatcher, NfcMagicAppViewTextInput);
+    text_input_free(instance->text_input);
+
+    // Byte Input
+    view_dispatcher_remove_view(instance->view_dispatcher, NfcMagicAppViewByteInput);
+    byte_input_free(instance->byte_input);
+
+    // Custom Widget
+    view_dispatcher_remove_view(instance->view_dispatcher, NfcMagicAppViewWidget);
+    widget_free(instance->widget);
+
+    // View Dispatcher
+    view_dispatcher_free(instance->view_dispatcher);
+
+    // Scene Manager
+    scene_manager_free(instance->scene_manager);
+
+    // GUI
+    furi_record_close(RECORD_GUI);
+    instance->gui = NULL;
+
+    // Notifications
+    furi_record_close(RECORD_NOTIFICATION);
+    instance->notifications = NULL;
+
+    // Dialogs
+    furi_record_close(RECORD_DIALOGS);
+    instance->dialogs = NULL;
+
+    // Storage
+    furi_record_close(RECORD_STORAGE);
+    instance->storage = NULL;
+
+    nfc_magic_scanner_free(instance->scanner);
+    nfc_free(instance->nfc);
+
+    free(instance);
+}
+
+static const NotificationSequence nfc_magic_sequence_blink_start_cyan = {
+    &message_blink_start_10,
+    &message_blink_set_color_cyan,
+    &message_do_not_reset,
+    NULL,
+};
+
+static const NotificationSequence nfc_magic_sequence_blink_stop = {
+    &message_blink_stop,
+    NULL,
+};
+
+void nfc_magic_app_blink_start(NfcMagicApp* instance) {
+    notification_message(instance->notifications, &nfc_magic_sequence_blink_start_cyan);
+}
+
+void nfc_magic_app_blink_stop(NfcMagicApp* instance) {
+    notification_message(instance->notifications, &nfc_magic_sequence_blink_stop);
+}
+
+static bool nfc_magic_set_shadow_file_path(FuriString* file_path, FuriString* shadow_file_path) {
+    furi_assert(file_path);
+    furi_assert(shadow_file_path);
+
+    bool shadow_file_path_set = false;
+    if(furi_string_end_with(file_path, NFC_APP_SHADOW_EXTENSION)) {
+        furi_string_set(shadow_file_path, file_path);
+        shadow_file_path_set = true;
+    } else if(furi_string_end_with(file_path, NFC_APP_EXTENSION)) {
+        size_t path_len = furi_string_size(file_path);
+        // Cut .nfc
+        furi_string_set_n(shadow_file_path, file_path, 0, path_len - 4);
+        furi_string_cat_printf(shadow_file_path, "%s", NFC_APP_SHADOW_EXTENSION);
+        shadow_file_path_set = true;
+    }
+
+    return shadow_file_path_set;
+}
+
+static bool nfc_magic_has_shadow_file_internal(NfcMagicApp* instance, FuriString* path) {
+    furi_assert(path);
+
+    bool has_shadow_file = false;
+    FuriString* shadow_file_path = furi_string_alloc();
+    do {
+        if(furi_string_empty(path)) break;
+        if(!nfc_magic_set_shadow_file_path(path, shadow_file_path)) break;
+        has_shadow_file =
+            storage_common_exists(instance->storage, furi_string_get_cstr(shadow_file_path));
+    } while(false);
+
+    furi_string_free(shadow_file_path);
+
+    return has_shadow_file;
+}
+
+bool nfc_magic_load_file(NfcMagicApp* instance, FuriString* path, bool show_dialog) {
+    furi_assert(instance);
+    furi_assert(path);
+    bool result = false;
+
+    FuriString* load_path = furi_string_alloc();
+    if(nfc_magic_has_shadow_file_internal(instance, path)) {
+        nfc_magic_set_shadow_file_path(path, load_path);
+    } else if(furi_string_end_with(path, NFC_APP_SHADOW_EXTENSION)) {
+        size_t path_len = furi_string_size(path);
+        furi_string_set_n(load_path, path, 0, path_len - 4);
+        furi_string_cat_printf(load_path, "%s", NFC_APP_EXTENSION);
+    } else {
+        furi_string_set(load_path, path);
+    }
+
+    result = nfc_device_load(instance->source_dev, furi_string_get_cstr(load_path));
+
+    if(result) {
+        path_extract_filename(load_path, instance->file_name, true);
+    }
+
+    if((!result) && (show_dialog)) {
+        dialog_message_show_storage_error(instance->dialogs, "Cannot load\nkey file");
+    }
+
+    furi_string_free(load_path);
+
+    return result;
+}
+
+bool nfc_magic_load_from_file_select(NfcMagicApp* instance) {
+    furi_assert(instance);
+
+    DialogsFileBrowserOptions browser_options;
+    dialog_file_browser_set_basic_options(&browser_options, NFC_APP_EXTENSION, &I_Nfc_10px);
+    browser_options.base_path = NFC_APP_FOLDER;
+    browser_options.hide_dot_files = true;
+
+    // Input events and views are managed by file_browser
+    bool result = dialog_file_browser_show(
+        instance->dialogs, instance->file_path, instance->file_path, &browser_options);
+
+    if(result) {
+        result = nfc_magic_load_file(instance, instance->file_path, true);
+    }
+
+    return result;
+}
+
+int32_t nfc_magic_app(void* p) {
+    UNUSED(p);
+    NfcMagicApp* instance = nfc_magic_app_alloc();
+
+    scene_manager_next_scene(instance->scene_manager, NfcMagicSceneStart);
+
+    view_dispatcher_run(instance->view_dispatcher);
+
+    nfc_magic_app_free(instance);
+
+    return 0;
+}

+ 5 - 0
nfc_magic/nfc_magic_app.h

@@ -0,0 +1,5 @@
+#pragma once
+
+typedef struct NfcMagicAppDevice NfcMagicAppDevice;
+
+typedef struct NfcMagicApp NfcMagicApp;

+ 101 - 0
nfc_magic/nfc_magic_app_i.h

@@ -0,0 +1,101 @@
+#pragma once
+
+#include "nfc_magic_app.h"
+#include "helpers/nfc_magic_custom_events.h"
+
+#include <furi.h>
+#include <gui/gui.h>
+#include <gui/view_dispatcher.h>
+#include <gui/scene_manager.h>
+#include <notification/notification_messages.h>
+
+#include <gui/modules/submenu.h>
+#include <gui/modules/popup.h>
+#include <gui/modules/loading.h>
+#include <gui/modules/text_input.h>
+#include <gui/modules/byte_input.h>
+#include <gui/modules/widget.h>
+
+#include <input/input.h>
+
+#include "scenes/nfc_magic_scene.h"
+
+#include <storage/storage.h>
+#include <dialogs/dialogs.h>
+#include <lib/toolbox/path.h>
+
+#include "nfc_magic_icons.h"
+
+#include <nfc/nfc.h>
+#include <nfc/nfc_device.h>
+
+#include "lib/magic/nfc_magic_scanner.h"
+#include "lib/magic/protocols/nfc_magic_protocols.h"
+#include "lib/magic/protocols/gen1a/gen1a_poller.h"
+#include "lib/magic/protocols/gen4/gen4_poller.h"
+
+#define NFC_APP_FOLDER ANY_PATH("nfc")
+#define NFC_APP_EXTENSION ".nfc"
+#define NFC_APP_SHADOW_EXTENSION ".shd"
+
+#define NFC_MAGIC_APP_BYTE_INPUT_STORE_SIZE (4)
+
+enum NfcMagicAppCustomEvent {
+    // Reserve first 100 events for button types and indexes, starting from 0
+    NfcMagicAppCustomEventReserved = 100,
+
+    NfcMagicAppCustomEventViewExit,
+    NfcMagicAppCustomEventWorkerExit,
+    NfcMagicAppCustomEventByteInputDone,
+    NfcMagicAppCustomEventTextInputDone,
+};
+
+struct NfcMagicApp {
+    ViewDispatcher* view_dispatcher;
+    Gui* gui;
+    NotificationApp* notifications;
+    DialogsApp* dialogs;
+    Storage* storage;
+
+    SceneManager* scene_manager;
+    NfcDevice* source_dev;
+    FuriString* file_name;
+    FuriString* file_path;
+
+    Nfc* nfc;
+    NfcMagicProtocol protocol;
+    NfcMagicScanner* scanner;
+    Gen1aPoller* gen1a_poller;
+    Gen4Poller* gen4_poller;
+
+    uint32_t gen4_password;
+    uint32_t gen4_password_new;
+
+    FuriString* text_box_store;
+    uint8_t byte_input_store[NFC_MAGIC_APP_BYTE_INPUT_STORE_SIZE];
+
+    // Common Views
+    Submenu* submenu;
+    Popup* popup;
+    Loading* loading;
+    TextInput* text_input;
+    ByteInput* byte_input;
+    Widget* widget;
+};
+
+typedef enum {
+    NfcMagicAppViewMenu,
+    NfcMagicAppViewPopup,
+    NfcMagicAppViewLoading,
+    NfcMagicAppViewTextInput,
+    NfcMagicAppViewByteInput,
+    NfcMagicAppViewWidget,
+} NfcMagicAppView;
+
+void nfc_magic_app_blink_start(NfcMagicApp* nfc_magic);
+
+void nfc_magic_app_blink_stop(NfcMagicApp* nfc_magic);
+
+void nfc_magic_app_show_loading_popup(void* context, bool show);
+
+bool nfc_magic_load_from_file_select(NfcMagicApp* instance);

+ 30 - 0
nfc_magic/scenes/nfc_magic_scene.c

@@ -0,0 +1,30 @@
+#include "nfc_magic_scene.h"
+
+// Generate scene on_enter handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
+void (*const nfc_magic_on_enter_handlers[])(void*) = {
+#include "nfc_magic_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Generate scene on_event handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event,
+bool (*const nfc_magic_on_event_handlers[])(void* context, SceneManagerEvent event) = {
+#include "nfc_magic_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Generate scene on_exit handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit,
+void (*const nfc_magic_on_exit_handlers[])(void* context) = {
+#include "nfc_magic_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Initialize scene handlers configuration structure
+const SceneManagerHandlers nfc_magic_scene_handlers = {
+    .on_enter_handlers = nfc_magic_on_enter_handlers,
+    .on_event_handlers = nfc_magic_on_event_handlers,
+    .on_exit_handlers = nfc_magic_on_exit_handlers,
+    .scene_num = NfcMagicSceneNum,
+};

+ 29 - 0
nfc_magic/scenes/nfc_magic_scene.h

@@ -0,0 +1,29 @@
+#pragma once
+
+#include <gui/scene_manager.h>
+
+// Generate scene id and total number
+#define ADD_SCENE(prefix, name, id) NfcMagicScene##id,
+typedef enum {
+#include "nfc_magic_scene_config.h"
+    NfcMagicSceneNum,
+} NfcMagicScene;
+#undef ADD_SCENE
+
+extern const SceneManagerHandlers nfc_magic_scene_handlers;
+
+// Generate scene on_enter handlers declaration
+#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
+#include "nfc_magic_scene_config.h"
+#undef ADD_SCENE
+
+// Generate scene on_event handlers declaration
+#define ADD_SCENE(prefix, name, id) \
+    bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event);
+#include "nfc_magic_scene_config.h"
+#undef ADD_SCENE
+
+// Generate scene on_exit handlers declaration
+#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context);
+#include "nfc_magic_scene_config.h"
+#undef ADD_SCENE

+ 108 - 0
nfc_magic/scenes/nfc_magic_scene_change_key.c

@@ -0,0 +1,108 @@
+#include "../nfc_magic_app_i.h"
+
+enum {
+    NfcMagicSceneChangeKeyStateCardSearch,
+    NfcMagicSceneChangeKeyStateCardFound,
+};
+
+NfcCommand nfc_mafic_scene_change_key_gen4_poller_callback(Gen4PollerEvent event, void* context) {
+    NfcMagicApp* instance = context;
+    furi_assert(event.data);
+
+    NfcCommand command = NfcCommandContinue;
+
+    if(event.type == Gen4PollerEventTypeCardDetected) {
+        view_dispatcher_send_custom_event(
+            instance->view_dispatcher, NfcMagicCustomEventCardDetected);
+    } else if(event.type == Gen4PollerEventTypeRequestMode) {
+        event.data->request_mode.mode = Gen4PollerModeSetPassword;
+    } else if(event.type == Gen4PollerEventTypeRequestNewPassword) {
+        event.data->request_password.password = instance->gen4_password_new;
+    } else if(event.type == Gen4PollerEventTypeSuccess) {
+        view_dispatcher_send_custom_event(
+            instance->view_dispatcher, NfcMagicCustomEventWorkerSuccess);
+        command = NfcCommandStop;
+    } else if(event.type == Gen4PollerEventTypeFail) {
+        view_dispatcher_send_custom_event(
+            instance->view_dispatcher, NfcMagicCustomEventWorkerFail);
+        command = NfcCommandStop;
+    }
+
+    return command;
+}
+
+static void nfc_magic_scene_change_key_setup_view(NfcMagicApp* instance) {
+    Popup* popup = instance->popup;
+    popup_reset(popup);
+    uint32_t state =
+        scene_manager_get_scene_state(instance->scene_manager, NfcMagicSceneChangeKey);
+
+    if(state == NfcMagicSceneChangeKeyStateCardSearch) {
+        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, "Writing\nDon't move...", 52, 32, AlignLeft, AlignCenter);
+    }
+
+    view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewPopup);
+}
+
+void nfc_magic_scene_change_key_on_enter(void* context) {
+    NfcMagicApp* instance = context;
+
+    scene_manager_set_scene_state(
+        instance->scene_manager, NfcMagicSceneChangeKey, NfcMagicSceneChangeKeyStateCardSearch);
+    nfc_magic_scene_change_key_setup_view(instance);
+
+    nfc_magic_app_blink_start(instance);
+
+    instance->gen4_poller = gen4_poller_alloc(instance->nfc);
+    gen4_poller_start(
+        instance->gen4_poller, nfc_mafic_scene_change_key_gen4_poller_callback, instance);
+}
+
+bool nfc_magic_scene_change_key_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,
+                NfcMagicSceneChangeKey,
+                NfcMagicSceneChangeKeyStateCardFound);
+            nfc_magic_scene_change_key_setup_view(instance);
+            consumed = true;
+        } else if(event.event == NfcMagicCustomEventCardLost) {
+            scene_manager_set_scene_state(
+                instance->scene_manager,
+                NfcMagicSceneChangeKey,
+                NfcMagicSceneChangeKeyStateCardSearch);
+            nfc_magic_scene_change_key_setup_view(instance);
+            consumed = true;
+        } else if(event.event == NfcMagicCustomEventWorkerSuccess) {
+            scene_manager_next_scene(instance->scene_manager, NfcMagicSceneSuccess);
+            consumed = true;
+        } else if(event.event == NfcMagicCustomEventWorkerFail) {
+            scene_manager_next_scene(instance->scene_manager, NfcMagicSceneChangeKeyFail);
+            consumed = true;
+        }
+    }
+
+    return consumed;
+}
+
+void nfc_magic_scene_change_key_on_exit(void* context) {
+    NfcMagicApp* instance = context;
+
+    gen4_poller_stop(instance->gen4_poller);
+    gen4_poller_free(instance->gen4_poller);
+    scene_manager_set_scene_state(
+        instance->scene_manager, NfcMagicSceneChangeKey, NfcMagicSceneChangeKeyStateCardSearch);
+    // Clear view
+    popup_reset(instance->popup);
+
+    nfc_magic_app_blink_stop(instance);
+}

+ 54 - 0
nfc_magic/scenes/nfc_magic_scene_change_key_fail.c

@@ -0,0 +1,54 @@
+#include "../nfc_magic_app_i.h"
+
+void nfc_magic_scene_change_key_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_change_key_fail_on_enter(void* context) {
+    NfcMagicApp* instance = context;
+    Widget* widget = instance->widget;
+
+    notification_message(instance->notifications, &sequence_error);
+
+    widget_add_icon_element(widget, 72, 17, &I_DolphinCommon_56x48);
+    widget_add_string_element(
+        widget, 7, 4, AlignLeft, AlignTop, FontPrimary, "Can't change password!");
+
+    widget_add_button_element(
+        widget,
+        GuiButtonTypeLeft,
+        "Finish",
+        nfc_magic_scene_change_key_fail_widget_callback,
+        instance);
+
+    // Setup and start worker
+    view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewWidget);
+}
+
+bool nfc_magic_scene_change_key_fail_on_event(void* context, SceneManagerEvent event) {
+    NfcMagicApp* instance = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == GuiButtonTypeLeft) {
+            consumed = scene_manager_search_and_switch_to_previous_scene(
+                instance->scene_manager, NfcMagicSceneStart);
+        }
+    } else if(event.type == SceneManagerEventTypeBack) {
+        consumed = scene_manager_search_and_switch_to_previous_scene(
+            instance->scene_manager, NfcMagicSceneStart);
+    }
+    return consumed;
+}
+
+void nfc_magic_scene_change_key_fail_on_exit(void* context) {
+    NfcMagicApp* instance = context;
+
+    widget_reset(instance->widget);
+}

+ 54 - 0
nfc_magic/scenes/nfc_magic_scene_check.c

@@ -0,0 +1,54 @@
+#include "../nfc_magic_app_i.h"
+
+void nfc_magic_check_worker_callback(NfcMagicScannerEvent event, void* context) {
+    furi_assert(context);
+
+    NfcMagicApp* instance = context;
+
+    if(event.type == NfcMagicScannerEventTypeDetected) {
+        instance->protocol = event.data.protocol;
+        view_dispatcher_send_custom_event(
+            instance->view_dispatcher, NfcMagicCustomEventWorkerSuccess);
+    } else if(event.type == NfcMagicScannerEventTypeDetectedNotMagic) {
+        view_dispatcher_send_custom_event(
+            instance->view_dispatcher, NfcMagicCustomEventWorkerFail);
+    }
+}
+
+void nfc_magic_scene_check_on_enter(void* context) {
+    NfcMagicApp* instance = context;
+
+    popup_set_icon(instance->popup, 0, 8, &I_NFC_manual_60x50);
+    popup_set_text(instance->popup, "Apply card to\nthe back", 128, 32, AlignRight, AlignCenter);
+
+    nfc_magic_app_blink_start(instance);
+
+    nfc_magic_scanner_start(instance->scanner, nfc_magic_check_worker_callback, instance);
+
+    view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewPopup);
+}
+
+bool nfc_magic_scene_check_on_event(void* context, SceneManagerEvent event) {
+    NfcMagicApp* instance = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == NfcMagicCustomEventWorkerSuccess) {
+            scene_manager_next_scene(instance->scene_manager, NfcMagicSceneMagicInfo);
+            consumed = true;
+        } else if(event.event == NfcMagicCustomEventWorkerFail) {
+            scene_manager_next_scene(instance->scene_manager, NfcMagicSceneNotMagic);
+            consumed = true;
+        }
+    }
+
+    return consumed;
+}
+
+void nfc_magic_scene_check_on_exit(void* context) {
+    NfcMagicApp* instance = context;
+
+    nfc_magic_scanner_stop(instance->scanner);
+    popup_reset(instance->popup);
+    nfc_magic_app_blink_stop(instance);
+}

+ 17 - 0
nfc_magic/scenes/nfc_magic_scene_config.h

@@ -0,0 +1,17 @@
+ADD_SCENE(nfc_magic, start, Start)
+ADD_SCENE(nfc_magic, check, Check)
+ADD_SCENE(nfc_magic, key_input, KeyInput)
+ADD_SCENE(nfc_magic, magic_info, MagicInfo)
+ADD_SCENE(nfc_magic, gen1_menu, Gen1Menu)
+ADD_SCENE(nfc_magic, gen4_menu, Gen4Menu)
+ADD_SCENE(nfc_magic, wipe, Wipe)
+ADD_SCENE(nfc_magic, wipe_fail, WipeFail)
+ADD_SCENE(nfc_magic, success, Success)
+ADD_SCENE(nfc_magic, file_select, FileSelect)
+ADD_SCENE(nfc_magic, write_confirm, WriteConfirm)
+ADD_SCENE(nfc_magic, write, Write)
+ADD_SCENE(nfc_magic, write_fail, WriteFail)
+ADD_SCENE(nfc_magic, change_key, ChangeKey)
+ADD_SCENE(nfc_magic, change_key_fail, ChangeKeyFail)
+ADD_SCENE(nfc_magic, wrong_card, WrongCard)
+ADD_SCENE(nfc_magic, not_magic, NotMagic)

+ 60 - 0
nfc_magic/scenes/nfc_magic_scene_file_select.c

@@ -0,0 +1,60 @@
+#include "../nfc_magic_app_i.h"
+#include <nfc/protocols/mf_classic/mf_classic.h>
+
+static bool nfc_magic_scene_file_select_is_file_suitable(NfcMagicApp* instance) {
+    NfcProtocol protocol = nfc_device_get_protocol(instance->source_dev);
+    size_t uid_len = 0;
+    nfc_device_get_uid(instance->source_dev, &uid_len);
+
+    bool suitable = false;
+    if(instance->protocol == NfcMagicProtocolGen1) {
+        if((uid_len == 4) && (protocol == NfcProtocolMfClassic)) {
+            const MfClassicData* mfc_data =
+                nfc_device_get_data(instance->source_dev, NfcProtocolMfClassic);
+            if(mfc_data->type == MfClassicType1k) {
+                suitable = true;
+            }
+        }
+    } else if(instance->protocol == NfcMagicProtocolGen4) {
+        if(protocol == NfcProtocolMfClassic) {
+            suitable = true;
+        } else if(protocol == NfcProtocolMfUltralight) {
+            const MfUltralightData* mfu_data =
+                nfc_device_get_data(instance->source_dev, NfcProtocolMfUltralight);
+            const Iso14443_3aData* iso3_data = mfu_data->iso14443_3a_data;
+            if(iso3_data->uid_len == 7) {
+                MfUltralightType mfu_type = mfu_data->type;
+                suitable = (mfu_type != MfUltralightTypeNTAGI2C1K) &&
+                           (mfu_type != MfUltralightTypeNTAGI2C2K) &&
+                           (mfu_type != MfUltralightTypeNTAGI2CPlus1K) &&
+                           (mfu_type != MfUltralightTypeNTAGI2CPlus2K);
+            }
+        }
+    }
+
+    return suitable;
+}
+
+void nfc_magic_scene_file_select_on_enter(void* context) {
+    NfcMagicApp* instance = context;
+
+    if(nfc_magic_load_from_file_select(instance)) {
+        if(nfc_magic_scene_file_select_is_file_suitable(instance)) {
+            scene_manager_next_scene(instance->scene_manager, NfcMagicSceneWriteConfirm);
+        } else {
+            scene_manager_next_scene(instance->scene_manager, NfcMagicSceneWrongCard);
+        }
+    } else {
+        scene_manager_previous_scene(instance->scene_manager);
+    }
+}
+
+bool nfc_magic_scene_file_select_on_event(void* context, SceneManagerEvent event) {
+    UNUSED(context);
+    UNUSED(event);
+    return false;
+}
+
+void nfc_magic_scene_file_select_on_exit(void* context) {
+    UNUSED(context);
+}

+ 53 - 0
nfc_magic/scenes/nfc_magic_scene_gen1_menu.c

@@ -0,0 +1,53 @@
+#include "../nfc_magic_app_i.h"
+
+enum SubmenuIndex {
+    SubmenuIndexWrite,
+    SubmenuIndexWipe,
+};
+
+void nfc_magic_scene_gen1_menu_submenu_callback(void* context, uint32_t index) {
+    NfcMagicApp* instance = context;
+
+    view_dispatcher_send_custom_event(instance->view_dispatcher, index);
+}
+
+void nfc_magic_scene_gen1_menu_on_enter(void* context) {
+    NfcMagicApp* instance = context;
+
+    Submenu* submenu = instance->submenu;
+    submenu_add_item(
+        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_set_selected_item(
+        submenu, scene_manager_get_scene_state(instance->scene_manager, NfcMagicSceneGen4Menu));
+    view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewMenu);
+}
+
+bool nfc_magic_scene_gen1_menu_on_event(void* context, SceneManagerEvent event) {
+    NfcMagicApp* instance = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == SubmenuIndexWrite) {
+            scene_manager_next_scene(instance->scene_manager, NfcMagicSceneFileSelect);
+            consumed = true;
+        } else if(event.event == SubmenuIndexWipe) {
+            scene_manager_next_scene(instance->scene_manager, NfcMagicSceneWipe);
+            consumed = true;
+        }
+        scene_manager_set_scene_state(instance->scene_manager, NfcMagicSceneGen4Menu, event.event);
+    } else if(event.type == SceneManagerEventTypeBack) {
+        consumed = scene_manager_search_and_switch_to_previous_scene(
+            instance->scene_manager, NfcMagicSceneStart);
+    }
+
+    return consumed;
+}
+
+void nfc_magic_scene_gen1_menu_on_exit(void* context) {
+    NfcMagicApp* instance = context;
+
+    submenu_reset(instance->submenu);
+}

+ 63 - 0
nfc_magic/scenes/nfc_magic_scene_gen4_menu.c

@@ -0,0 +1,63 @@
+#include "../nfc_magic_app_i.h"
+
+enum SubmenuIndex {
+    SubmenuIndexWrite,
+    SubmenuIndexChangePassword,
+    SubmenuIndexWipe,
+};
+
+void nfc_magic_scene_gen4_menu_submenu_callback(void* context, uint32_t index) {
+    NfcMagicApp* instance = context;
+
+    view_dispatcher_send_custom_event(instance->view_dispatcher, index);
+}
+
+void nfc_magic_scene_gen4_menu_on_enter(void* context) {
+    NfcMagicApp* instance = context;
+
+    Submenu* submenu = instance->submenu;
+    submenu_add_item(
+        submenu, "Write", SubmenuIndexWrite, nfc_magic_scene_gen4_menu_submenu_callback, instance);
+    submenu_add_item(
+        submenu,
+        "Change password",
+        SubmenuIndexChangePassword,
+        nfc_magic_scene_gen4_menu_submenu_callback,
+        instance);
+    submenu_add_item(
+        submenu, "Wipe", SubmenuIndexWipe, nfc_magic_scene_gen4_menu_submenu_callback, instance);
+
+    submenu_set_selected_item(
+        submenu, scene_manager_get_scene_state(instance->scene_manager, NfcMagicSceneGen4Menu));
+    view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewMenu);
+}
+
+bool nfc_magic_scene_gen4_menu_on_event(void* context, SceneManagerEvent event) {
+    NfcMagicApp* instance = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == SubmenuIndexWrite) {
+            scene_manager_next_scene(instance->scene_manager, NfcMagicSceneFileSelect);
+            consumed = true;
+        } else if(event.event == SubmenuIndexChangePassword) {
+            scene_manager_next_scene(instance->scene_manager, NfcMagicSceneKeyInput);
+            consumed = true;
+        } else if(event.event == SubmenuIndexWipe) {
+            scene_manager_next_scene(instance->scene_manager, NfcMagicSceneWipe);
+            consumed = true;
+        }
+        scene_manager_set_scene_state(instance->scene_manager, NfcMagicSceneGen4Menu, event.event);
+    } else if(event.type == SceneManagerEventTypeBack) {
+        consumed = scene_manager_search_and_switch_to_previous_scene(
+            instance->scene_manager, NfcMagicSceneStart);
+    }
+
+    return consumed;
+}
+
+void nfc_magic_scene_gen4_menu_on_exit(void* context) {
+    NfcMagicApp* instance = context;
+
+    submenu_reset(instance->submenu);
+}

+ 55 - 0
nfc_magic/scenes/nfc_magic_scene_key_input.c

@@ -0,0 +1,55 @@
+#include "../nfc_magic_app_i.h"
+
+#include <nfc/helpers/nfc_util.h>
+
+void nfc_magic_scene_key_input_byte_input_callback(void* context) {
+    NfcMagicApp* instance = context;
+
+    view_dispatcher_send_custom_event(
+        instance->view_dispatcher, NfcMagicAppCustomEventByteInputDone);
+}
+
+void nfc_magic_scene_key_input_on_enter(void* context) {
+    NfcMagicApp* instance = context;
+
+    // Setup view
+    ByteInput* byte_input = instance->byte_input;
+    byte_input_set_header_text(byte_input, "Enter the password in hex");
+    byte_input_set_result_callback(
+        byte_input,
+        nfc_magic_scene_key_input_byte_input_callback,
+        NULL,
+        instance,
+        instance->byte_input_store,
+        NFC_MAGIC_APP_BYTE_INPUT_STORE_SIZE);
+    view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewByteInput);
+}
+
+bool nfc_magic_scene_key_input_on_event(void* context, SceneManagerEvent event) {
+    NfcMagicApp* instance = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == NfcMagicAppCustomEventByteInputDone) {
+            if(scene_manager_has_previous_scene(instance->scene_manager, NfcMagicSceneGen4Menu)) {
+                instance->gen4_password_new = nfc_util_bytes2num(
+                    instance->byte_input_store, NFC_MAGIC_APP_BYTE_INPUT_STORE_SIZE);
+                scene_manager_next_scene(instance->scene_manager, NfcMagicSceneChangeKey);
+            } else {
+                instance->gen4_password = nfc_util_bytes2num(
+                    instance->byte_input_store, NFC_MAGIC_APP_BYTE_INPUT_STORE_SIZE);
+                scene_manager_next_scene(instance->scene_manager, NfcMagicSceneCheck);
+            }
+            consumed = true;
+        }
+    }
+    return consumed;
+}
+
+void nfc_magic_scene_key_input_on_exit(void* context) {
+    NfcMagicApp* instance = context;
+
+    // Clear view
+    byte_input_set_result_callback(instance->byte_input, NULL, NULL, NULL, NULL, 0);
+    byte_input_set_header_text(instance->byte_input, "");
+}

+ 63 - 0
nfc_magic/scenes/nfc_magic_scene_magic_info.c

@@ -0,0 +1,63 @@
+#include "../nfc_magic_app_i.h"
+
+void nfc_magic_scene_magic_info_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_magic_info_on_enter(void* context) {
+    NfcMagicApp* instance = context;
+    Widget* widget = instance->widget;
+
+    notification_message(instance->notifications, &sequence_success);
+
+    widget_add_icon_element(widget, 73, 17, &I_DolphinCommon_56x48);
+    widget_add_string_element(
+        widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "Magic card detected");
+    widget_add_string_element(
+        widget,
+        3,
+        17,
+        AlignLeft,
+        AlignTop,
+        FontSecondary,
+        nfc_magic_protocols_get_name(instance->protocol));
+    widget_add_button_element(
+        widget, GuiButtonTypeLeft, "Retry", nfc_magic_scene_magic_info_widget_callback, instance);
+    widget_add_button_element(
+        widget, GuiButtonTypeRight, "More", nfc_magic_scene_magic_info_widget_callback, instance);
+
+    view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewWidget);
+}
+
+bool nfc_magic_scene_magic_info_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);
+        } else if(event.event == GuiButtonTypeRight) {
+            if(instance->protocol == NfcMagicProtocolGen1) {
+                scene_manager_next_scene(instance->scene_manager, NfcMagicSceneGen1Menu);
+                consumed = true;
+            } else {
+                scene_manager_next_scene(instance->scene_manager, NfcMagicSceneGen4Menu);
+                consumed = true;
+            }
+        }
+    }
+    return consumed;
+}
+
+void nfc_magic_scene_magic_info_on_exit(void* context) {
+    NfcMagicApp* instance = context;
+
+    widget_reset(instance->widget);
+}

+ 43 - 0
nfc_magic/scenes/nfc_magic_scene_not_magic.c

@@ -0,0 +1,43 @@
+#include "../nfc_magic_app_i.h"
+
+void nfc_magic_scene_not_magic_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_not_magic_on_enter(void* context) {
+    NfcMagicApp* instance = context;
+    Widget* widget = instance->widget;
+
+    notification_message(instance->notifications, &sequence_error);
+
+    widget_add_string_element(
+        widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "This is wrong card");
+    widget_add_string_multiline_element(
+        widget, 4, 17, AlignLeft, AlignTop, FontSecondary, "Not magic or unsupported\ncard");
+    widget_add_button_element(
+        widget, GuiButtonTypeLeft, "Retry", nfc_magic_scene_not_magic_widget_callback, instance);
+
+    // Setup and start worker
+    view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewWidget);
+}
+
+bool nfc_magic_scene_not_magic_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_not_magic_on_exit(void* context) {
+    NfcMagicApp* instance = context;
+
+    widget_reset(instance->widget);
+}

+ 59 - 0
nfc_magic/scenes/nfc_magic_scene_start.c

@@ -0,0 +1,59 @@
+#include "../nfc_magic_app_i.h"
+
+enum SubmenuIndex {
+    SubmenuIndexCheck,
+    SubmenuIndexAuthenticateGen4,
+};
+
+void nfc_magic_scene_start_submenu_callback(void* context, uint32_t index) {
+    NfcMagicApp* instance = context;
+    view_dispatcher_send_custom_event(instance->view_dispatcher, index);
+}
+
+void nfc_magic_scene_start_on_enter(void* context) {
+    NfcMagicApp* instance = context;
+
+    Submenu* submenu = instance->submenu;
+    submenu_add_item(
+        submenu,
+        "Check Magic Tag",
+        SubmenuIndexCheck,
+        nfc_magic_scene_start_submenu_callback,
+        instance);
+    submenu_add_item(
+        submenu,
+        "Authenticate Gen4",
+        SubmenuIndexAuthenticateGen4,
+        nfc_magic_scene_start_submenu_callback,
+        instance);
+
+    instance->gen4_password = 0;
+
+    submenu_set_selected_item(
+        submenu, scene_manager_get_scene_state(instance->scene_manager, NfcMagicSceneStart));
+    view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewMenu);
+}
+
+bool nfc_magic_scene_start_on_event(void* context, SceneManagerEvent event) {
+    NfcMagicApp* instance = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == SubmenuIndexCheck) {
+            scene_manager_set_scene_state(
+                instance->scene_manager, NfcMagicSceneStart, SubmenuIndexCheck);
+            scene_manager_next_scene(instance->scene_manager, NfcMagicSceneCheck);
+            consumed = true;
+        } else if(event.event == SubmenuIndexAuthenticateGen4) {
+            scene_manager_next_scene(instance->scene_manager, NfcMagicSceneKeyInput);
+        }
+    }
+
+    return consumed;
+}
+
+void nfc_magic_scene_start_on_exit(void* context) {
+    NfcMagicApp* instance = context;
+
+    submenu_reset(instance->submenu);
+}

+ 42 - 0
nfc_magic/scenes/nfc_magic_scene_success.c

@@ -0,0 +1,42 @@
+#include "../nfc_magic_app_i.h"
+
+void nfc_magic_scene_success_popup_callback(void* context) {
+    NfcMagicApp* instance = context;
+    view_dispatcher_send_custom_event(instance->view_dispatcher, NfcMagicAppCustomEventViewExit);
+}
+
+void nfc_magic_scene_success_on_enter(void* context) {
+    NfcMagicApp* instance = context;
+
+    notification_message(instance->notifications, &sequence_success);
+
+    Popup* popup = instance->popup;
+    popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59);
+    popup_set_header(popup, "Success!", 10, 20, AlignLeft, AlignBottom);
+    popup_set_timeout(popup, 1500);
+    popup_set_context(popup, instance);
+    popup_set_callback(popup, nfc_magic_scene_success_popup_callback);
+    popup_enable_timeout(popup);
+
+    view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewPopup);
+}
+
+bool nfc_magic_scene_success_on_event(void* context, SceneManagerEvent event) {
+    NfcMagicApp* instance = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == NfcMagicAppCustomEventViewExit) {
+            consumed = scene_manager_search_and_switch_to_previous_scene(
+                instance->scene_manager, NfcMagicSceneStart);
+        }
+    }
+    return consumed;
+}
+
+void nfc_magic_scene_success_on_exit(void* context) {
+    NfcMagicApp* instance = context;
+
+    // Clear view
+    popup_reset(instance->popup);
+}

+ 136 - 0
nfc_magic/scenes/nfc_magic_scene_wipe.c

@@ -0,0 +1,136 @@
+#include "../nfc_magic_app_i.h"
+
+enum {
+    NfcMagicSceneWipeStateCardSearch,
+    NfcMagicSceneWipeStateCardFound,
+};
+
+NfcCommand nfc_mafic_scene_wipe_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 = Gen1aPollerModeWipe;
+    } 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;
+}
+
+NfcCommand nfc_mafic_scene_wipe_gen4_poller_callback(Gen4PollerEvent event, void* context) {
+    NfcMagicApp* instance = context;
+
+    NfcCommand command = NfcCommandContinue;
+
+    if(event.type == Gen4PollerEventTypeCardDetected) {
+        view_dispatcher_send_custom_event(
+            instance->view_dispatcher, NfcMagicCustomEventCardDetected);
+    } else if(event.type == Gen4PollerEventTypeRequestMode) {
+        event.data->request_mode.mode = Gen4PollerModeWipe;
+    } else if(event.type == Gen4PollerEventTypeSuccess) {
+        view_dispatcher_send_custom_event(
+            instance->view_dispatcher, NfcMagicCustomEventWorkerSuccess);
+        command = NfcCommandStop;
+    } else if(event.type == Gen4PollerEventTypeFail) {
+        view_dispatcher_send_custom_event(
+            instance->view_dispatcher, NfcMagicCustomEventWorkerFail);
+        command = NfcCommandStop;
+    }
+
+    return command;
+}
+
+static void nfc_magic_scene_wipe_setup_view(NfcMagicApp* instance) {
+    Popup* popup = instance->popup;
+    popup_reset(popup);
+    uint32_t state = scene_manager_get_scene_state(instance->scene_manager, NfcMagicSceneWipe);
+
+    if(state == NfcMagicSceneWipeStateCardSearch) {
+        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, "Wiping\nDon't move...", 52, 32, AlignLeft, AlignCenter);
+    }
+
+    view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewPopup);
+}
+
+void nfc_magic_scene_wipe_on_enter(void* context) {
+    NfcMagicApp* instance = context;
+
+    scene_manager_set_scene_state(
+        instance->scene_manager, NfcMagicSceneWipe, NfcMagicSceneWipeStateCardSearch);
+    nfc_magic_scene_wipe_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_mafic_scene_wipe_gen1_poller_callback, instance);
+    } else {
+        instance->gen4_poller = gen4_poller_alloc(instance->nfc);
+        gen4_poller_set_password(instance->gen4_poller, instance->gen4_password);
+        gen4_poller_start(
+            instance->gen4_poller, nfc_mafic_scene_wipe_gen4_poller_callback, instance);
+    }
+}
+
+bool nfc_magic_scene_wipe_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, NfcMagicSceneWipe, NfcMagicSceneWipeStateCardFound);
+            nfc_magic_scene_wipe_setup_view(instance);
+            consumed = true;
+        } else if(event.event == NfcMagicCustomEventCardLost) {
+            scene_manager_set_scene_state(
+                instance->scene_manager, NfcMagicSceneWipe, NfcMagicSceneWipeStateCardSearch);
+            nfc_magic_scene_wipe_setup_view(instance);
+            consumed = true;
+        } else if(event.event == NfcMagicCustomEventWorkerSuccess) {
+            scene_manager_next_scene(instance->scene_manager, NfcMagicSceneSuccess);
+            consumed = true;
+        } else if(event.event == NfcMagicCustomEventWorkerFail) {
+            scene_manager_next_scene(instance->scene_manager, NfcMagicSceneWipeFail);
+            consumed = true;
+        }
+    }
+
+    return consumed;
+}
+
+void nfc_magic_scene_wipe_on_exit(void* context) {
+    NfcMagicApp* instance = context;
+
+    if(instance->protocol == NfcMagicProtocolGen1) {
+        gen1a_poller_stop(instance->gen1a_poller);
+        gen1a_poller_free(instance->gen1a_poller);
+    } else {
+        gen4_poller_stop(instance->gen4_poller);
+        gen4_poller_free(instance->gen4_poller);
+    }
+    scene_manager_set_scene_state(
+        instance->scene_manager, NfcMagicSceneWipe, NfcMagicSceneWipeStateCardSearch);
+    // Clear view
+    popup_reset(instance->popup);
+
+    nfc_magic_app_blink_stop(instance);
+}

+ 43 - 0
nfc_magic/scenes/nfc_magic_scene_wipe_fail.c

@@ -0,0 +1,43 @@
+#include "../nfc_magic_app_i.h"
+
+void nfc_magic_scene_wipe_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_wipe_fail_on_enter(void* context) {
+    NfcMagicApp* instance = context;
+
+    Widget* widget = instance->widget;
+    notification_message(instance->notifications, &sequence_error);
+
+    widget_add_icon_element(widget, 73, 17, &I_DolphinCommon_56x48);
+    widget_add_string_element(widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "Wipe failed");
+    widget_add_button_element(
+        widget, GuiButtonTypeLeft, "Retry", nfc_magic_scene_wipe_fail_widget_callback, instance);
+
+    // Setup and start worker
+    view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewWidget);
+}
+
+bool nfc_magic_scene_wipe_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_wipe_fail_on_exit(void* context) {
+    NfcMagicApp* instance = context;
+
+    widget_reset(instance->widget);
+}

+ 144 - 0
nfc_magic/scenes/nfc_magic_scene_write.c

@@ -0,0 +1,144 @@
+#include "../nfc_magic_app_i.h"
+
+enum {
+    NfcMagicSceneWriteStateCardSearch,
+    NfcMagicSceneWriteStateCardFound,
+};
+
+NfcCommand nfc_mafic_scene_write_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 = Gen1aPollerModeWrite;
+    } else if(event.type == Gen1aPollerEventTypeRequestDataToWrite) {
+        const MfClassicData* mfc_data =
+            nfc_device_get_data(instance->source_dev, NfcProtocolMfClassic);
+        event.data->data_to_write.mfc_data = mfc_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;
+}
+
+NfcCommand nfc_mafic_scene_write_gen4_poller_callback(Gen4PollerEvent event, void* context) {
+    NfcMagicApp* instance = context;
+    furi_assert(event.data);
+
+    NfcCommand command = NfcCommandContinue;
+
+    if(event.type == Gen4PollerEventTypeCardDetected) {
+        view_dispatcher_send_custom_event(
+            instance->view_dispatcher, NfcMagicCustomEventCardDetected);
+    } else if(event.type == Gen4PollerEventTypeRequestMode) {
+        event.data->request_mode.mode = Gen4PollerModeWrite;
+    } else if(event.type == Gen4PollerEventTypeRequestDataToWrite) {
+        NfcProtocol protocol = nfc_device_get_protocol(instance->source_dev);
+        event.data->request_data.protocol = protocol;
+        event.data->request_data.data = nfc_device_get_data(instance->source_dev, protocol);
+    } else if(event.type == Gen4PollerEventTypeSuccess) {
+        view_dispatcher_send_custom_event(
+            instance->view_dispatcher, NfcMagicCustomEventWorkerSuccess);
+        command = NfcCommandStop;
+    } else if(event.type == Gen4PollerEventTypeFail) {
+        view_dispatcher_send_custom_event(
+            instance->view_dispatcher, NfcMagicCustomEventWorkerFail);
+        command = NfcCommandStop;
+    }
+
+    return command;
+}
+
+static void nfc_magic_scene_write_setup_view(NfcMagicApp* instance) {
+    Popup* popup = instance->popup;
+    popup_reset(popup);
+    uint32_t state = scene_manager_get_scene_state(instance->scene_manager, NfcMagicSceneWrite);
+
+    if(state == NfcMagicSceneWriteStateCardSearch) {
+        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, "Writing\nDon't move...", 52, 32, AlignLeft, AlignCenter);
+    }
+
+    view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewPopup);
+}
+
+void nfc_magic_scene_write_on_enter(void* context) {
+    NfcMagicApp* instance = context;
+
+    scene_manager_set_scene_state(
+        instance->scene_manager, NfcMagicSceneWrite, NfcMagicSceneWriteStateCardSearch);
+    nfc_magic_scene_write_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_mafic_scene_write_gen1_poller_callback, instance);
+    } else {
+        instance->gen4_poller = gen4_poller_alloc(instance->nfc);
+        gen4_poller_start(
+            instance->gen4_poller, nfc_mafic_scene_write_gen4_poller_callback, instance);
+    }
+}
+
+bool nfc_magic_scene_write_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, NfcMagicSceneWrite, NfcMagicSceneWriteStateCardFound);
+            nfc_magic_scene_write_setup_view(instance);
+            consumed = true;
+        } else if(event.event == NfcMagicCustomEventCardLost) {
+            scene_manager_set_scene_state(
+                instance->scene_manager, NfcMagicSceneWrite, NfcMagicSceneWriteStateCardSearch);
+            nfc_magic_scene_write_setup_view(instance);
+            consumed = true;
+        } else if(event.event == NfcMagicCustomEventWorkerSuccess) {
+            scene_manager_next_scene(instance->scene_manager, NfcMagicSceneSuccess);
+            consumed = true;
+        } else if(event.event == NfcMagicCustomEventWorkerFail) {
+            scene_manager_next_scene(instance->scene_manager, NfcMagicSceneWriteFail);
+            consumed = true;
+        }
+    }
+
+    return consumed;
+}
+
+void nfc_magic_scene_write_on_exit(void* context) {
+    NfcMagicApp* instance = context;
+
+    if(instance->protocol == NfcMagicProtocolGen1) {
+        gen1a_poller_stop(instance->gen1a_poller);
+        gen1a_poller_free(instance->gen1a_poller);
+    } else {
+        gen4_poller_stop(instance->gen4_poller);
+        gen4_poller_free(instance->gen4_poller);
+    }
+    scene_manager_set_scene_state(
+        instance->scene_manager, NfcMagicSceneWrite, NfcMagicSceneWriteStateCardSearch);
+    // Clear view
+    popup_reset(instance->popup);
+
+    nfc_magic_app_blink_stop(instance);
+}

+ 61 - 0
nfc_magic/scenes/nfc_magic_scene_write_confirm.c

@@ -0,0 +1,61 @@
+#include "../nfc_magic_app_i.h"
+
+void nfc_magic_scene_write_confirm_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_write_confirm_on_enter(void* context) {
+    NfcMagicApp* instance = context;
+    Widget* widget = instance->widget;
+
+    widget_add_string_element(widget, 3, 0, AlignLeft, AlignTop, FontPrimary, "Risky operation");
+    widget_add_text_box_element(
+        widget,
+        0,
+        13,
+        128,
+        54,
+        AlignLeft,
+        AlignTop,
+        "Writing to this card will change manufacturer block. On some cards it may not be rewritten",
+        false);
+    widget_add_button_element(
+        widget,
+        GuiButtonTypeCenter,
+        "Continue",
+        nfc_magic_scene_write_confirm_widget_callback,
+        instance);
+    widget_add_button_element(
+        widget, GuiButtonTypeLeft, "Back", nfc_magic_scene_write_confirm_widget_callback, instance);
+
+    // Setup and start worker
+    view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewWidget);
+}
+
+bool nfc_magic_scene_write_confirm_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);
+        } else if(event.event == GuiButtonTypeCenter) {
+            scene_manager_next_scene(instance->scene_manager, NfcMagicSceneWrite);
+            consumed = true;
+        }
+    }
+    return consumed;
+}
+
+void nfc_magic_scene_write_confirm_on_exit(void* context) {
+    NfcMagicApp* instance = context;
+
+    widget_reset(instance->widget);
+}

+ 58 - 0
nfc_magic/scenes/nfc_magic_scene_write_fail.c

@@ -0,0 +1,58 @@
+#include "../nfc_magic_app_i.h"
+
+void nfc_magic_scene_write_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_write_fail_on_enter(void* context) {
+    NfcMagicApp* instance = context;
+    Widget* widget = instance->widget;
+
+    notification_message(instance->notifications, &sequence_error);
+
+    widget_add_icon_element(widget, 72, 17, &I_DolphinCommon_56x48);
+    widget_add_string_element(
+        widget, 7, 4, AlignLeft, AlignTop, FontPrimary, "Writing gone wrong!");
+    widget_add_string_multiline_element(
+        widget,
+        7,
+        17,
+        AlignLeft,
+        AlignTop,
+        FontSecondary,
+        "Not all sectors\nwere written\ncorrectly.");
+
+    widget_add_button_element(
+        widget, GuiButtonTypeLeft, "Finish", nfc_magic_scene_write_fail_widget_callback, instance);
+
+    // Setup and start worker
+    view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewWidget);
+}
+
+bool nfc_magic_scene_write_fail_on_event(void* context, SceneManagerEvent event) {
+    NfcMagicApp* instance = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == GuiButtonTypeLeft) {
+            consumed = scene_manager_search_and_switch_to_previous_scene(
+                instance->scene_manager, NfcMagicSceneStart);
+        }
+    } else if(event.type == SceneManagerEventTypeBack) {
+        consumed = scene_manager_search_and_switch_to_previous_scene(
+            instance->scene_manager, NfcMagicSceneStart);
+    }
+    return consumed;
+}
+
+void nfc_magic_scene_write_fail_on_exit(void* context) {
+    NfcMagicApp* instance = context;
+
+    widget_reset(instance->widget);
+}

+ 53 - 0
nfc_magic/scenes/nfc_magic_scene_wrong_card.c

@@ -0,0 +1,53 @@
+#include "../nfc_magic_app_i.h"
+
+void nfc_magic_scene_wrong_card_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_wrong_card_on_enter(void* context) {
+    NfcMagicApp* instance = context;
+    Widget* widget = instance->widget;
+
+    notification_message(instance->notifications, &sequence_error);
+
+    widget_add_icon_element(widget, 73, 17, &I_DolphinCommon_56x48);
+    widget_add_string_element(
+        widget, 1, 4, AlignLeft, AlignTop, FontPrimary, "This is wrong card");
+    widget_add_string_multiline_element(
+        widget,
+        1,
+        17,
+        AlignLeft,
+        AlignTop,
+        FontSecondary,
+        "Writing this file is\nnot supported for\nthis magic card.");
+    widget_add_button_element(
+        widget, GuiButtonTypeLeft, "Retry", nfc_magic_scene_wrong_card_widget_callback, instance);
+
+    // Setup and start worker
+    view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewWidget);
+}
+
+bool nfc_magic_scene_wrong_card_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_wrong_card_on_exit(void* context) {
+    NfcMagicApp* instance = context;
+
+    widget_reset(instance->widget);
+}