Преглед на файлове

Merge nfc_magic from https://github.com/flipperdevices/flipperzero-good-faps

Willy-JL преди 2 години
родител
ревизия
66b36b5ff8
променени са 54 файла, в които са добавени 2711 реда и са изтрити 1996 реда
  1. BIN
      nfc_magic/assets/Nfc_10px.png
  2. 14 0
      nfc_magic/helpers/nfc_magic_custom_events.h
  3. 0 145
      nfc_magic/lib/magic/classic_gen1.c
  4. 0 11
      nfc_magic/lib/magic/classic_gen1.h
  5. 0 33
      nfc_magic/lib/magic/common.c
  6. 0 19
      nfc_magic/lib/magic/common.h
  7. 0 199
      nfc_magic/lib/magic/gen4.c
  8. 0 50
      nfc_magic/lib/magic/gen4.h
  9. 150 0
      nfc_magic/lib/magic/nfc_magic_scanner.c
  10. 44 0
      nfc_magic/lib/magic/nfc_magic_scanner.h
  11. 279 0
      nfc_magic/lib/magic/protocols/gen1a/gen1a_poller.c
  12. 59 0
      nfc_magic/lib/magic/protocols/gen1a/gen1a_poller.h
  13. 132 0
      nfc_magic/lib/magic/protocols/gen1a/gen1a_poller_i.c
  14. 67 0
      nfc_magic/lib/magic/protocols/gen1a/gen1a_poller_i.h
  15. 533 0
      nfc_magic/lib/magic/protocols/gen4/gen4_poller.c
  16. 84 0
      nfc_magic/lib/magic/protocols/gen4/gen4_poller.h
  17. 129 0
      nfc_magic/lib/magic/protocols/gen4/gen4_poller_i.c
  18. 101 0
      nfc_magic/lib/magic/protocols/gen4/gen4_poller_i.h
  19. 14 0
      nfc_magic/lib/magic/protocols/nfc_magic_protocols.c
  20. 19 0
      nfc_magic/lib/magic/protocols/nfc_magic_protocols.h
  21. 0 23
      nfc_magic/lib/magic/types.c
  22. 0 5
      nfc_magic/lib/magic/types.h
  23. 0 184
      nfc_magic/nfc_magic.c
  24. 0 5
      nfc_magic/nfc_magic.h
  25. 290 0
      nfc_magic/nfc_magic_app.c
  26. 5 0
      nfc_magic/nfc_magic_app.h
  27. 103 0
      nfc_magic/nfc_magic_app_i.h
  28. 0 96
      nfc_magic/nfc_magic_i.h
  29. 0 485
      nfc_magic/nfc_magic_worker.c
  30. 0 42
      nfc_magic/nfc_magic_worker.h
  31. 0 29
      nfc_magic/nfc_magic_worker_i.h
  32. 0 50
      nfc_magic/scenes/nfc_magic_scene_actions.c
  33. 108 0
      nfc_magic/scenes/nfc_magic_scene_change_key.c
  34. 54 0
      nfc_magic/scenes/nfc_magic_scene_change_key_fail.c
  35. 28 63
      nfc_magic/scenes/nfc_magic_scene_check.c
  36. 10 11
      nfc_magic/scenes/nfc_magic_scene_config.h
  37. 35 51
      nfc_magic/scenes/nfc_magic_scene_file_select.c
  38. 53 0
      nfc_magic/scenes/nfc_magic_scene_gen1_menu.c
  39. 0 70
      nfc_magic/scenes/nfc_magic_scene_gen4_actions.c
  40. 63 0
      nfc_magic/scenes/nfc_magic_scene_gen4_menu.c
  41. 25 15
      nfc_magic/scenes/nfc_magic_scene_key_input.c
  42. 25 21
      nfc_magic/scenes/nfc_magic_scene_magic_info.c
  43. 0 45
      nfc_magic/scenes/nfc_magic_scene_new_key_input.c
  44. 12 12
      nfc_magic/scenes/nfc_magic_scene_not_magic.c
  45. 0 95
      nfc_magic/scenes/nfc_magic_scene_rekey.c
  46. 0 50
      nfc_magic/scenes/nfc_magic_scene_rekey_fail.c
  47. 19 16
      nfc_magic/scenes/nfc_magic_scene_start.c
  48. 13 13
      nfc_magic/scenes/nfc_magic_scene_success.c
  49. 91 52
      nfc_magic/scenes/nfc_magic_scene_wipe.c
  50. 14 12
      nfc_magic/scenes/nfc_magic_scene_wipe_fail.c
  51. 99 52
      nfc_magic/scenes/nfc_magic_scene_write.c
  52. 14 17
      nfc_magic/scenes/nfc_magic_scene_write_confirm.c
  53. 13 13
      nfc_magic/scenes/nfc_magic_scene_write_fail.c
  54. 12 12
      nfc_magic/scenes/nfc_magic_scene_wrong_card.c

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;

+ 0 - 145
nfc_magic/lib/magic/classic_gen1.c

@@ -1,145 +0,0 @@
-#include "classic_gen1.h"
-
-#include <furi_hal_nfc.h>
-
-#define TAG "Magic"
-
-#define MAGIC_CMD_WUPA (0x40)
-#define MAGIC_CMD_ACCESS (0x43)
-
-#define MAGIC_MIFARE_READ_CMD (0x30)
-#define MAGIC_MIFARE_WRITE_CMD (0xA0)
-
-#define MAGIC_ACK (0x0A)
-
-#define MAGIC_BUFFER_SIZE (32)
-
-bool magic_gen1_wupa() {
-    bool magic_activated = false;
-    uint8_t tx_data[MAGIC_BUFFER_SIZE] = {};
-    uint8_t rx_data[MAGIC_BUFFER_SIZE] = {};
-    uint16_t rx_len = 0;
-    FuriHalNfcReturn ret = 0;
-
-    do {
-        // Start communication
-        tx_data[0] = MAGIC_CMD_WUPA;
-        ret = furi_hal_nfc_ll_txrx_bits(
-            tx_data,
-            7,
-            rx_data,
-            sizeof(rx_data),
-            &rx_len,
-            FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_TX_MANUAL | FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON |
-                FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_RX_KEEP,
-            furi_hal_nfc_ll_ms2fc(20));
-        if(ret != FuriHalNfcReturnIncompleteByte) break;
-        if(rx_len != 4) break;
-        if(rx_data[0] != MAGIC_ACK) break;
-        magic_activated = true;
-    } while(false);
-
-    return magic_activated;
-}
-
-bool magic_gen1_data_access_cmd() {
-    bool write_cmd_success = false;
-    uint8_t tx_data[MAGIC_BUFFER_SIZE] = {};
-    uint8_t rx_data[MAGIC_BUFFER_SIZE] = {};
-    uint16_t rx_len = 0;
-    FuriHalNfcReturn ret = 0;
-
-    do {
-        tx_data[0] = MAGIC_CMD_ACCESS;
-        ret = furi_hal_nfc_ll_txrx_bits(
-            tx_data,
-            8,
-            rx_data,
-            sizeof(rx_data),
-            &rx_len,
-            FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_TX_MANUAL | FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON |
-                FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_RX_KEEP,
-            furi_hal_nfc_ll_ms2fc(20));
-        if(ret != FuriHalNfcReturnIncompleteByte) break;
-        if(rx_len != 4) break;
-        if(rx_data[0] != MAGIC_ACK) break;
-
-        write_cmd_success = true;
-    } while(false);
-
-    return write_cmd_success;
-}
-
-bool magic_gen1_read_block(uint8_t block_num, MfClassicBlock* data) {
-    furi_assert(data);
-
-    bool read_success = false;
-
-    uint8_t tx_data[MAGIC_BUFFER_SIZE] = {};
-    uint8_t rx_data[MAGIC_BUFFER_SIZE] = {};
-    uint16_t rx_len = 0;
-    FuriHalNfcReturn ret = 0;
-
-    do {
-        tx_data[0] = MAGIC_MIFARE_READ_CMD;
-        tx_data[1] = block_num;
-        ret = furi_hal_nfc_ll_txrx_bits(
-            tx_data,
-            2 * 8,
-            rx_data,
-            sizeof(rx_data),
-            &rx_len,
-            FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON,
-            furi_hal_nfc_ll_ms2fc(20));
-
-        if(ret != FuriHalNfcReturnOk) break;
-        if(rx_len != 16 * 8) break;
-        memcpy(data->value, rx_data, sizeof(data->value));
-        read_success = true;
-    } while(false);
-
-    return read_success;
-}
-
-bool magic_gen1_write_blk(uint8_t block_num, MfClassicBlock* data) {
-    furi_assert(data);
-
-    bool write_success = false;
-    uint8_t tx_data[MAGIC_BUFFER_SIZE] = {};
-    uint8_t rx_data[MAGIC_BUFFER_SIZE] = {};
-    uint16_t rx_len = 0;
-    FuriHalNfcReturn ret = 0;
-
-    do {
-        tx_data[0] = MAGIC_MIFARE_WRITE_CMD;
-        tx_data[1] = block_num;
-        ret = furi_hal_nfc_ll_txrx_bits(
-            tx_data,
-            2 * 8,
-            rx_data,
-            sizeof(rx_data),
-            &rx_len,
-            FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON | FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_RX_KEEP,
-            furi_hal_nfc_ll_ms2fc(20));
-        if(ret != FuriHalNfcReturnIncompleteByte) break;
-        if(rx_len != 4) break;
-        if(rx_data[0] != MAGIC_ACK) break;
-
-        memcpy(tx_data, data->value, sizeof(data->value));
-        ret = furi_hal_nfc_ll_txrx_bits(
-            tx_data,
-            16 * 8,
-            rx_data,
-            sizeof(rx_data),
-            &rx_len,
-            FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON | FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_RX_KEEP,
-            furi_hal_nfc_ll_ms2fc(20));
-        if(ret != FuriHalNfcReturnIncompleteByte) break;
-        if(rx_len != 4) break;
-        if(rx_data[0] != MAGIC_ACK) break;
-
-        write_success = true;
-    } while(false);
-
-    return write_success;
-}

+ 0 - 11
nfc_magic/lib/magic/classic_gen1.h

@@ -1,11 +0,0 @@
-#pragma once
-
-#include <lib/nfc/protocols/mifare_classic.h>
-
-bool magic_gen1_wupa();
-
-bool magic_gen1_read_block(uint8_t block_num, MfClassicBlock* data);
-
-bool magic_gen1_data_access_cmd();
-
-bool magic_gen1_write_blk(uint8_t block_num, MfClassicBlock* data);

+ 0 - 33
nfc_magic/lib/magic/common.c

@@ -1,33 +0,0 @@
-#include "common.h"
-
-#include <furi_hal_nfc.h>
-
-#define REQA (0x26)
-#define CL1_PREFIX (0x93)
-#define SELECT (0x70)
-
-#define MAGIC_BUFFER_SIZE (32)
-
-bool magic_activate() {
-    FuriHalNfcReturn ret = 0;
-
-    // Setup nfc poller
-    furi_hal_nfc_exit_sleep();
-    furi_hal_nfc_ll_txrx_on();
-    furi_hal_nfc_ll_poll();
-    ret = furi_hal_nfc_ll_set_mode(
-        FuriHalNfcModePollNfca, FuriHalNfcBitrate106, FuriHalNfcBitrate106);
-    if(ret != FuriHalNfcReturnOk) return false;
-
-    furi_hal_nfc_ll_set_fdt_listen(FURI_HAL_NFC_LL_FDT_LISTEN_NFCA_POLLER);
-    furi_hal_nfc_ll_set_fdt_poll(FURI_HAL_NFC_LL_FDT_POLL_NFCA_POLLER);
-    furi_hal_nfc_ll_set_error_handling(FuriHalNfcErrorHandlingNfc);
-    furi_hal_nfc_ll_set_guard_time(FURI_HAL_NFC_LL_GT_NFCA);
-
-    return true;
-}
-
-void magic_deactivate() {
-    furi_hal_nfc_ll_txrx_off();
-    furi_hal_nfc_sleep();
-}

+ 0 - 19
nfc_magic/lib/magic/common.h

@@ -1,19 +0,0 @@
-#pragma once
-
-#include <stdint.h>
-#include <stdbool.h>
-
-typedef enum {
-    MagicTypeClassicGen1,
-    MagicTypeClassicDirectWrite,
-    MagicTypeClassicAPDU,
-    MagicTypeUltralightGen1,
-    MagicTypeUltralightDirectWrite,
-    MagicTypeUltralightC_Gen1,
-    MagicTypeUltralightC_DirectWrite,
-    MagicTypeGen4,
-} MagicType;
-
-bool magic_activate();
-
-void magic_deactivate();

+ 0 - 199
nfc_magic/lib/magic/gen4.c

@@ -1,199 +0,0 @@
-#include "gen4.h"
-
-#include <furi_hal_nfc.h>
-#include <stdlib.h>
-
-#define TAG "Magic"
-
-#define MAGIC_CMD_PREFIX (0xCF)
-
-#define MAGIC_CMD_GET_CFG (0xC6)
-#define MAGIC_CMD_WRITE (0xCD)
-#define MAGIC_CMD_READ (0xCE)
-#define MAGIC_CMD_SET_CFG (0xF0)
-#define MAGIC_CMD_FUSE_CFG (0xF1)
-#define MAGIC_CMD_SET_PWD (0xFE)
-
-#define MAGIC_BUFFER_SIZE (40)
-
-const uint8_t MAGIC_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, 0x6b, 0x02
-};
-
-const uint8_t MAGIC_DEFAULT_BLOCK0[] = {
-    0x00, 0x01, 0x02, 0x03, 0x04, 0x04, 0x08, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
-};
-
-const uint8_t MAGIC_EMPTY_BLOCK[16] = { 0 };
-
-const uint8_t MAGIC_DEFAULT_SECTOR_TRAILER[] = {
-    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x07, 0x80, 0x69, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
-};
-
-static bool magic_gen4_is_block_num_trailer(uint8_t n) {
-    n++;
-    if (n < 32 * 4) {
-        return (n % 4 == 0);
-    }
-
-    return (n % 16 == 0);
-}
-
-bool magic_gen4_get_cfg(uint32_t pwd, uint8_t* config) {
-    bool is_valid_config_len = false;
-    uint8_t tx_data[MAGIC_BUFFER_SIZE] = {};
-    uint8_t rx_data[MAGIC_BUFFER_SIZE] = {};
-    uint16_t rx_len = 0;
-    FuriHalNfcReturn ret = 0;
-
-    do {
-        // Start communication
-        tx_data[0] = MAGIC_CMD_PREFIX;
-        tx_data[1] = (uint8_t)(pwd >> 24);
-        tx_data[2] = (uint8_t)(pwd >> 16);
-        tx_data[3] = (uint8_t)(pwd >> 8);
-        tx_data[4] = (uint8_t)pwd;
-        tx_data[5] = MAGIC_CMD_GET_CFG;
-        ret = furi_hal_nfc_ll_txrx(
-            tx_data,
-            6,
-            rx_data,
-            sizeof(rx_data),
-            &rx_len,
-            FURI_HAL_NFC_TXRX_DEFAULT,
-            furi_hal_nfc_ll_ms2fc(20));
-        if(ret != FuriHalNfcReturnOk) break;
-        if(rx_len != 30 && rx_len != 32) break;
-        memcpy(config, rx_data, rx_len);
-        is_valid_config_len = true;
-    } while(false);
-
-    return is_valid_config_len;
-}
-
-bool magic_gen4_set_cfg(uint32_t pwd, const uint8_t* config, uint8_t config_length, bool fuse) {
-    bool write_success = false;
-    uint8_t tx_data[MAGIC_BUFFER_SIZE] = {};
-    uint8_t rx_data[MAGIC_BUFFER_SIZE] = {};
-    uint16_t rx_len = 0;
-    FuriHalNfcReturn ret = 0;
-
-    do {
-        // Start communication
-        tx_data[0] = MAGIC_CMD_PREFIX;
-        tx_data[1] = (uint8_t)(pwd >> 24);
-        tx_data[2] = (uint8_t)(pwd >> 16);
-        tx_data[3] = (uint8_t)(pwd >> 8);
-        tx_data[4] = (uint8_t)pwd;
-        tx_data[5] = fuse ? MAGIC_CMD_FUSE_CFG : MAGIC_CMD_SET_CFG;
-        memcpy(tx_data + 6, config, config_length);
-        ret = furi_hal_nfc_ll_txrx(
-            tx_data,
-            6 + config_length,
-            rx_data,
-            sizeof(rx_data),
-            &rx_len,
-            FURI_HAL_NFC_TXRX_DEFAULT,
-            furi_hal_nfc_ll_ms2fc(20));
-        if(ret != FuriHalNfcReturnOk) break;
-        if(rx_len != 2) break;
-        write_success = true;
-    } while(false);
-
-    return write_success;
-}
-
-bool magic_gen4_set_pwd(uint32_t old_pwd, uint32_t new_pwd) {
-    bool change_success = false;
-    uint8_t tx_data[MAGIC_BUFFER_SIZE] = {};
-    uint8_t rx_data[MAGIC_BUFFER_SIZE] = {};
-    uint16_t rx_len = 0;
-    FuriHalNfcReturn ret = 0;
-
-    do {
-        // Start communication
-        tx_data[0] = MAGIC_CMD_PREFIX;
-        tx_data[1] = (uint8_t)(old_pwd >> 24);
-        tx_data[2] = (uint8_t)(old_pwd >> 16);
-        tx_data[3] = (uint8_t)(old_pwd >> 8);
-        tx_data[4] = (uint8_t)old_pwd;
-        tx_data[5] = MAGIC_CMD_SET_PWD;
-        tx_data[6] = (uint8_t)(new_pwd >> 24);
-        tx_data[7] = (uint8_t)(new_pwd >> 16);
-        tx_data[8] = (uint8_t)(new_pwd >> 8);
-        tx_data[9] = (uint8_t)new_pwd;
-        ret = furi_hal_nfc_ll_txrx(
-            tx_data,
-            10,
-            rx_data,
-            sizeof(rx_data),
-            &rx_len,
-            FURI_HAL_NFC_TXRX_DEFAULT,
-            furi_hal_nfc_ll_ms2fc(20));
-        FURI_LOG_I(TAG, "ret %d, len %d", ret, rx_len);
-        if(ret != FuriHalNfcReturnOk) break;
-        if(rx_len != 2) break;
-        change_success = true;
-    } while(false);
-
-    return change_success;
-}
-
-bool magic_gen4_write_blk(uint32_t pwd, uint8_t block_num, const uint8_t* data) {
-    bool write_success = false;
-    uint8_t tx_data[MAGIC_BUFFER_SIZE] = {};
-    uint8_t rx_data[MAGIC_BUFFER_SIZE] = {};
-    uint16_t rx_len = 0;
-    FuriHalNfcReturn ret = 0;
-
-    do {
-        // Start communication
-        tx_data[0] = MAGIC_CMD_PREFIX;
-        tx_data[1] = (uint8_t)(pwd >> 24);
-        tx_data[2] = (uint8_t)(pwd >> 16);
-        tx_data[3] = (uint8_t)(pwd >> 8);
-        tx_data[4] = (uint8_t)pwd;
-        tx_data[5] = MAGIC_CMD_WRITE;
-        tx_data[6] = block_num;
-        memcpy(tx_data + 7, data, 16);
-        ret = furi_hal_nfc_ll_txrx(
-            tx_data,
-            23,
-            rx_data,
-            sizeof(rx_data),
-            &rx_len,
-            FURI_HAL_NFC_TXRX_DEFAULT,
-            furi_hal_nfc_ll_ms2fc(200));
-        if(ret != FuriHalNfcReturnOk) break;
-        if(rx_len != 2) break;
-        write_success = true;
-    } while(false);
-
-    return write_success;
-}
-
-bool magic_gen4_wipe(uint32_t pwd) {
-    if(!magic_gen4_set_cfg(pwd, MAGIC_DEFAULT_CONFIG, sizeof(MAGIC_DEFAULT_CONFIG), false)) {
-        FURI_LOG_E(TAG, "Set config failed");
-        return false;
-    }
-    if(!magic_gen4_write_blk(pwd, 0, MAGIC_DEFAULT_BLOCK0)) {
-        FURI_LOG_E(TAG, "Block 0 write failed");
-        return false;
-    }
-    for(size_t i = 1; i < 64; i++) {
-        const uint8_t* block = magic_gen4_is_block_num_trailer(i) ? MAGIC_DEFAULT_SECTOR_TRAILER : MAGIC_EMPTY_BLOCK;
-        if(!magic_gen4_write_blk(pwd, i, block)) {
-            FURI_LOG_E(TAG, "Block %d write failed", i);
-            return false;
-        }
-    }
-    for(size_t i = 65; i < 256; i++) {
-        if(!magic_gen4_write_blk(pwd, i, MAGIC_EMPTY_BLOCK)) {
-            FURI_LOG_E(TAG, "Block %d write failed", i);
-            return false;
-        }
-    }
-
-    return true;
-}

+ 0 - 50
nfc_magic/lib/magic/gen4.h

@@ -1,50 +0,0 @@
-#pragma once
-
-#include <lib/nfc/protocols/mifare_classic.h>
-
-#define MAGIC_GEN4_DEFAULT_PWD 0x00000000
-#define MAGIC_GEN4_CONFIG_LEN 30
-
-#define NFCID1_SINGLE_SIZE 4
-#define NFCID1_DOUBLE_SIZE 7
-#define NFCID1_TRIPLE_SIZE 10
-
-typedef enum {
-    MagicGen4UIDLengthSingle = 0x00,
-    MagicGen4UIDLengthDouble = 0x01,
-    MagicGen4UIDLengthTriple = 0x02
-} MagicGen4UIDLength;
-
-typedef enum {
-    MagicGen4UltralightModeUL_EV1 = 0x00,
-    MagicGen4UltralightModeNTAG = 0x01,
-    MagicGen4UltralightModeUL_C = 0x02,
-    MagicGen4UltralightModeUL = 0x03
-} MagicGen4UltralightMode;
-
-typedef enum {
-    // for writing original (shadow) data
-    MagicGen4ShadowModePreWrite = 0x00,
-    // written data can be read once before restored to original
-    MagicGen4ShadowModeRestore = 0x01,
-    // written data is discarded
-    MagicGen4ShadowModeIgnore = 0x02,
-    // apparently for UL?
-    MagicGen4ShadowModeHighSpeedIgnore = 0x03
-} MagicGen4ShadowMode;
-
-extern const uint8_t MAGIC_DEFAULT_CONFIG[];
-
-bool magic_gen4_get_cfg(uint32_t pwd, uint8_t* config);
-
-bool magic_gen4_set_cfg(uint32_t pwd, const uint8_t* config, uint8_t config_length, bool fuse);
-
-bool magic_gen4_set_pwd(uint32_t old_pwd, uint32_t new_pwd);
-
-bool magic_gen4_read_blk(uint32_t pwd, uint8_t block_num, uint8_t* data);
-
-bool magic_gen4_write_blk(uint32_t pwd, uint8_t block_num, const uint8_t* data);
-
-bool magic_gen4_wipe(uint32_t pwd);
-
-void magic_gen4_deactivate();

+ 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

+ 0 - 23
nfc_magic/lib/magic/types.c

@@ -1,23 +0,0 @@
-#include "types.h"
-
-const char* nfc_magic_type(MagicType type) {
-    if(type == MagicTypeClassicGen1) {
-        return "Classic Gen 1A/B";
-    } else if(type == MagicTypeClassicDirectWrite) {
-        return "Classic DirectWrite";
-    } else if(type == MagicTypeClassicAPDU) {
-        return "Classic APDU";
-    } else if(type == MagicTypeUltralightGen1) {
-        return "Ultralight Gen 1";
-    } else if(type == MagicTypeUltralightDirectWrite) {
-        return "Ultralight DirectWrite";
-    } else if(type == MagicTypeUltralightC_Gen1) {
-        return "Ultralight-C Gen 1";
-    } else if(type == MagicTypeUltralightC_DirectWrite) {
-        return "Ultralight-C DirectWrite";
-    } else if(type == MagicTypeGen4) {
-        return "Gen 4 GTU";
-    } else {
-        return "Unknown";
-    }
-}

+ 0 - 5
nfc_magic/lib/magic/types.h

@@ -1,5 +0,0 @@
-#pragma once
-
-#include "common.h"
-
-const char* nfc_magic_type(MagicType type);

+ 0 - 184
nfc_magic/nfc_magic.c

@@ -1,184 +0,0 @@
-#include "nfc_magic_i.h"
-
-bool nfc_magic_custom_event_callback(void* context, uint32_t event) {
-    furi_assert(context);
-    NfcMagic* nfc_magic = context;
-    return scene_manager_handle_custom_event(nfc_magic->scene_manager, event);
-}
-
-bool nfc_magic_back_event_callback(void* context) {
-    furi_assert(context);
-    NfcMagic* nfc_magic = context;
-    return scene_manager_handle_back_event(nfc_magic->scene_manager);
-}
-
-void nfc_magic_tick_event_callback(void* context) {
-    furi_assert(context);
-    NfcMagic* nfc_magic = context;
-    scene_manager_handle_tick_event(nfc_magic->scene_manager);
-}
-
-void nfc_magic_show_loading_popup(void* context, bool show) {
-    NfcMagic* nfc_magic = context;
-    TaskHandle_t timer_task = xTaskGetHandle(configTIMER_SERVICE_TASK_NAME);
-
-    if(show) {
-        // Raise timer priority so that animations can play
-        vTaskPrioritySet(timer_task, configMAX_PRIORITIES - 1);
-        view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewLoading);
-    } else {
-        // Restore default timer priority
-        vTaskPrioritySet(timer_task, configTIMER_TASK_PRIORITY);
-    }
-}
-
-NfcMagic* nfc_magic_alloc() {
-    NfcMagic* nfc_magic = malloc(sizeof(NfcMagic));
-
-    nfc_magic->worker = nfc_magic_worker_alloc();
-    nfc_magic->view_dispatcher = view_dispatcher_alloc();
-    nfc_magic->scene_manager = scene_manager_alloc(&nfc_magic_scene_handlers, nfc_magic);
-    view_dispatcher_enable_queue(nfc_magic->view_dispatcher);
-    view_dispatcher_set_event_callback_context(nfc_magic->view_dispatcher, nfc_magic);
-    view_dispatcher_set_custom_event_callback(
-        nfc_magic->view_dispatcher, nfc_magic_custom_event_callback);
-    view_dispatcher_set_navigation_event_callback(
-        nfc_magic->view_dispatcher, nfc_magic_back_event_callback);
-    view_dispatcher_set_tick_event_callback(
-        nfc_magic->view_dispatcher, nfc_magic_tick_event_callback, 100);
-
-    // Nfc device
-    nfc_magic->dev = malloc(sizeof(NfcMagicDevice));
-    nfc_magic->source_dev = nfc_device_alloc();
-    furi_string_set(nfc_magic->source_dev->folder, NFC_APP_FOLDER);
-
-    // Open GUI record
-    nfc_magic->gui = furi_record_open(RECORD_GUI);
-    view_dispatcher_attach_to_gui(
-        nfc_magic->view_dispatcher, nfc_magic->gui, ViewDispatcherTypeFullscreen);
-
-    // Open Notification record
-    nfc_magic->notifications = furi_record_open(RECORD_NOTIFICATION);
-
-    // Submenu
-    nfc_magic->submenu = submenu_alloc();
-    view_dispatcher_add_view(
-        nfc_magic->view_dispatcher, NfcMagicViewMenu, submenu_get_view(nfc_magic->submenu));
-
-    // Popup
-    nfc_magic->popup = popup_alloc();
-    view_dispatcher_add_view(
-        nfc_magic->view_dispatcher, NfcMagicViewPopup, popup_get_view(nfc_magic->popup));
-
-    // Loading
-    nfc_magic->loading = loading_alloc();
-    view_dispatcher_add_view(
-        nfc_magic->view_dispatcher, NfcMagicViewLoading, loading_get_view(nfc_magic->loading));
-
-    // Text Input
-    nfc_magic->text_input = text_input_alloc();
-    view_dispatcher_add_view(
-        nfc_magic->view_dispatcher,
-        NfcMagicViewTextInput,
-        text_input_get_view(nfc_magic->text_input));
-
-    // Byte Input
-    nfc_magic->byte_input = byte_input_alloc();
-    view_dispatcher_add_view(
-        nfc_magic->view_dispatcher,
-        NfcMagicViewByteInput,
-        byte_input_get_view(nfc_magic->byte_input));
-
-    // Custom Widget
-    nfc_magic->widget = widget_alloc();
-    view_dispatcher_add_view(
-        nfc_magic->view_dispatcher, NfcMagicViewWidget, widget_get_view(nfc_magic->widget));
-
-    return nfc_magic;
-}
-
-void nfc_magic_free(NfcMagic* nfc_magic) {
-    furi_assert(nfc_magic);
-
-    // Nfc device
-    free(nfc_magic->dev);
-    nfc_device_free(nfc_magic->source_dev);
-
-    // Submenu
-    view_dispatcher_remove_view(nfc_magic->view_dispatcher, NfcMagicViewMenu);
-    submenu_free(nfc_magic->submenu);
-
-    // Popup
-    view_dispatcher_remove_view(nfc_magic->view_dispatcher, NfcMagicViewPopup);
-    popup_free(nfc_magic->popup);
-
-    // Loading
-    view_dispatcher_remove_view(nfc_magic->view_dispatcher, NfcMagicViewLoading);
-    loading_free(nfc_magic->loading);
-
-    // Text Input
-    view_dispatcher_remove_view(nfc_magic->view_dispatcher, NfcMagicViewTextInput);
-    text_input_free(nfc_magic->text_input);
-
-    // Byte Input
-    view_dispatcher_remove_view(nfc_magic->view_dispatcher, NfcMagicViewByteInput);
-    byte_input_free(nfc_magic->byte_input);
-
-    // Custom Widget
-    view_dispatcher_remove_view(nfc_magic->view_dispatcher, NfcMagicViewWidget);
-    widget_free(nfc_magic->widget);
-
-    // Worker
-    nfc_magic_worker_stop(nfc_magic->worker);
-    nfc_magic_worker_free(nfc_magic->worker);
-
-    // View Dispatcher
-    view_dispatcher_free(nfc_magic->view_dispatcher);
-
-    // Scene Manager
-    scene_manager_free(nfc_magic->scene_manager);
-
-    // GUI
-    furi_record_close(RECORD_GUI);
-    nfc_magic->gui = NULL;
-
-    // Notifications
-    furi_record_close(RECORD_NOTIFICATION);
-    nfc_magic->notifications = NULL;
-
-    free(nfc_magic);
-}
-
-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_blink_start(NfcMagic* nfc_magic) {
-    notification_message(nfc_magic->notifications, &nfc_magic_sequence_blink_start_cyan);
-}
-
-void nfc_magic_blink_stop(NfcMagic* nfc_magic) {
-    notification_message(nfc_magic->notifications, &nfc_magic_sequence_blink_stop);
-}
-
-int32_t nfc_magic_app(void* p) {
-    UNUSED(p);
-    NfcMagic* nfc_magic = nfc_magic_alloc();
-
-    scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneStart);
-
-    view_dispatcher_run(nfc_magic->view_dispatcher);
-
-    magic_deactivate();
-    nfc_magic_free(nfc_magic);
-
-    return 0;
-}

+ 0 - 5
nfc_magic/nfc_magic.h

@@ -1,5 +0,0 @@
-#pragma once
-
-typedef struct NfcMagicDevice NfcMagicDevice;
-
-typedef struct NfcMagic NfcMagic;

+ 290 - 0
nfc_magic/nfc_magic_app.c

@@ -0,0 +1,290 @@
+#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;
+    TaskHandle_t timer_task = xTaskGetHandle(configTIMER_SERVICE_TASK_NAME);
+
+    if(show) {
+        // Raise timer priority so that animations can play
+        vTaskPrioritySet(timer_task, configMAX_PRIORITIES - 1);
+        view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewLoading);
+    } else {
+        // Restore default timer priority
+        vTaskPrioritySet(timer_task, configTIMER_TASK_PRIORITY);
+    }
+}
+
+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;

+ 103 - 0
nfc_magic/nfc_magic_app_i.h

@@ -0,0 +1,103 @@
+#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 <assets_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);

+ 0 - 96
nfc_magic/nfc_magic_i.h

@@ -1,96 +0,0 @@
-#pragma once
-
-#include "nfc_magic.h"
-#include "nfc_magic_worker.h"
-
-#include "lib/magic/common.h"
-#include "lib/magic/types.h"
-#include "lib/magic/classic_gen1.h"
-#include "lib/magic/gen4.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 <lib/toolbox/path.h>
-
-#include <lib/nfc/nfc_device.h>
-#include "nfc_magic_icons.h"
-
-#include <assets_icons.h>
-
-#define NFC_APP_FOLDER ANY_PATH("nfc")
-
-enum NfcMagicCustomEvent {
-    // Reserve first 100 events for button types and indexes, starting from 0
-    NfcMagicCustomEventReserved = 100,
-
-    NfcMagicCustomEventViewExit,
-    NfcMagicCustomEventWorkerExit,
-    NfcMagicCustomEventByteInputDone,
-    NfcMagicCustomEventTextInputDone,
-};
-
-struct NfcMagicDevice {
-    MagicType type;
-    uint32_t cuid;
-    uint8_t uid_len;
-    uint32_t password;
-};
-
-struct NfcMagic {
-    NfcMagicWorker* worker;
-    ViewDispatcher* view_dispatcher;
-    Gui* gui;
-    NotificationApp* notifications;
-    SceneManager* scene_manager;
-    struct NfcMagicDevice* dev;
-    NfcDevice* source_dev;
-
-    uint32_t new_password;
-
-    FuriString* text_box_store;
-
-    // Common Views
-    Submenu* submenu;
-    Popup* popup;
-    Loading* loading;
-    TextInput* text_input;
-    ByteInput* byte_input;
-    Widget* widget;
-};
-
-typedef enum {
-    NfcMagicViewMenu,
-    NfcMagicViewPopup,
-    NfcMagicViewLoading,
-    NfcMagicViewTextInput,
-    NfcMagicViewByteInput,
-    NfcMagicViewWidget,
-} NfcMagicView;
-
-NfcMagic* nfc_magic_alloc();
-
-void nfc_magic_text_store_set(NfcMagic* nfc_magic, const char* text, ...);
-
-void nfc_magic_text_store_clear(NfcMagic* nfc_magic);
-
-void nfc_magic_blink_start(NfcMagic* nfc_magic);
-
-void nfc_magic_blink_stop(NfcMagic* nfc_magic);
-
-void nfc_magic_show_loading_popup(void* context, bool show);

+ 0 - 485
nfc_magic/nfc_magic_worker.c

@@ -1,485 +0,0 @@
-#include "nfc_magic_worker_i.h"
-
-#include "nfc_magic_i.h"
-#include "lib/magic/common.h"
-#include "lib/magic/classic_gen1.h"
-#include "lib/magic/gen4.h"
-
-#define TAG "NfcMagicWorker"
-
-static void
-    nfc_magic_worker_change_state(NfcMagicWorker* nfc_magic_worker, NfcMagicWorkerState state) {
-    furi_assert(nfc_magic_worker);
-
-    nfc_magic_worker->state = state;
-}
-
-NfcMagicWorker* nfc_magic_worker_alloc() {
-    NfcMagicWorker* nfc_magic_worker = malloc(sizeof(NfcMagicWorker));
-
-    // Worker thread attributes
-    nfc_magic_worker->thread =
-        furi_thread_alloc_ex("NfcMagicWorker", 8192, nfc_magic_worker_task, nfc_magic_worker);
-
-    nfc_magic_worker->callback = NULL;
-    nfc_magic_worker->context = NULL;
-
-    nfc_magic_worker_change_state(nfc_magic_worker, NfcMagicWorkerStateReady);
-
-    return nfc_magic_worker;
-}
-
-void nfc_magic_worker_free(NfcMagicWorker* nfc_magic_worker) {
-    furi_assert(nfc_magic_worker);
-
-    furi_thread_free(nfc_magic_worker->thread);
-    free(nfc_magic_worker);
-}
-
-void nfc_magic_worker_stop(NfcMagicWorker* nfc_magic_worker) {
-    furi_assert(nfc_magic_worker);
-
-    nfc_magic_worker_change_state(nfc_magic_worker, NfcMagicWorkerStateStop);
-    furi_thread_join(nfc_magic_worker->thread);
-}
-
-void nfc_magic_worker_start(
-    NfcMagicWorker* nfc_magic_worker,
-    NfcMagicWorkerState state,
-    NfcMagicDevice* magic_dev,
-    NfcDeviceData* dev_data,
-    uint32_t new_password,
-    NfcMagicWorkerCallback callback,
-    void* context) {
-    furi_assert(nfc_magic_worker);
-    furi_assert(magic_dev);
-    furi_assert(dev_data);
-
-    nfc_magic_worker->callback = callback;
-    nfc_magic_worker->context = context;
-    nfc_magic_worker->magic_dev = magic_dev;
-    nfc_magic_worker->dev_data = dev_data;
-    nfc_magic_worker->new_password = new_password;
-    nfc_magic_worker_change_state(nfc_magic_worker, state);
-    furi_thread_start(nfc_magic_worker->thread);
-}
-
-int32_t nfc_magic_worker_task(void* context) {
-    NfcMagicWorker* nfc_magic_worker = context;
-
-    if(nfc_magic_worker->state == NfcMagicWorkerStateCheck) {
-        nfc_magic_worker_check(nfc_magic_worker);
-    } else if(nfc_magic_worker->state == NfcMagicWorkerStateWrite) {
-        nfc_magic_worker_write(nfc_magic_worker);
-    } else if(nfc_magic_worker->state == NfcMagicWorkerStateRekey) {
-        nfc_magic_worker_rekey(nfc_magic_worker);
-    } else if(nfc_magic_worker->state == NfcMagicWorkerStateWipe) {
-        nfc_magic_worker_wipe(nfc_magic_worker);
-    }
-
-    nfc_magic_worker_change_state(nfc_magic_worker, NfcMagicWorkerStateReady);
-
-    return 0;
-}
-
-void nfc_magic_worker_write(NfcMagicWorker* nfc_magic_worker) {
-    bool card_found_notified = false;
-    bool done = false;
-    FuriHalNfcDevData nfc_data = {};
-    NfcMagicDevice* magic_dev = nfc_magic_worker->magic_dev;
-    NfcDeviceData* dev_data = nfc_magic_worker->dev_data;
-    NfcProtocol dev_protocol = dev_data->protocol;
-
-    while(nfc_magic_worker->state == NfcMagicWorkerStateWrite) {
-        do {
-            if(magic_dev->type == MagicTypeClassicGen1) {
-                if(furi_hal_nfc_detect(&nfc_data, 200)) {
-                    magic_deactivate();
-                    magic_activate();
-                    if(!magic_gen1_wupa()) {
-                        FURI_LOG_E(TAG, "No card response to WUPA (not a magic card)");
-                        nfc_magic_worker->callback(
-                            NfcMagicWorkerEventWrongCard, nfc_magic_worker->context);
-                        done = true;
-                        break;
-                    }
-                    magic_deactivate();
-                }
-                magic_activate();
-                if(magic_gen1_wupa()) {
-                    magic_gen1_data_access_cmd();
-
-                    MfClassicData* mfc_data = &dev_data->mf_classic_data;
-                    for(size_t i = 0; i < 64; i++) {
-                        FURI_LOG_D(TAG, "Writing block %d", i);
-                        if(!magic_gen1_write_blk(i, &mfc_data->block[i])) {
-                            FURI_LOG_E(TAG, "Failed to write %d block", i);
-                            done = true;
-                            nfc_magic_worker->callback(
-                                NfcMagicWorkerEventFail, nfc_magic_worker->context);
-                            break;
-                        }
-                    }
-
-                    done = true;
-                    nfc_magic_worker->callback(
-                        NfcMagicWorkerEventSuccess, nfc_magic_worker->context);
-                    break;
-                }
-            } else if(magic_dev->type == MagicTypeGen4) {
-                if(furi_hal_nfc_detect(&nfc_data, 200)) {
-                    uint8_t gen4_config[MAGIC_GEN4_CONFIG_LEN];
-                    memcpy(gen4_config, MAGIC_DEFAULT_CONFIG, MAGIC_GEN4_CONFIG_LEN);
-                    uint32_t password = magic_dev->password;
-                    uint32_t cuid;
-                    size_t block_count = 64;
-                    MfClassicData* mfc_data;
-                    if(dev_protocol == NfcDeviceProtocolMifareClassic) {
-                        gen4_config[0] = 0x00;
-                        gen4_config[27] = 0x00;
-                        mfc_data = &dev_data->mf_classic_data;
-                        if(mfc_data->type == MfClassicType4k) block_count = 256;
-                    } else if(dev_protocol == NfcDeviceProtocolMifareUl) {
-                        MfUltralightData* mf_ul_data = &dev_data->mf_ul_data;
-                        gen4_config[0] = 0x01;
-                        switch(mf_ul_data->type) {
-                        case MfUltralightTypeUL11:
-                        case MfUltralightTypeUL21:
-                        // UL-C?
-                        // UL?
-                        default:
-                            gen4_config[27] = MagicGen4UltralightModeUL_EV1;
-                            break;
-                        case MfUltralightTypeNTAG203:
-                        case MfUltralightTypeNTAG213:
-                        case MfUltralightTypeNTAG215:
-                        case MfUltralightTypeNTAG216:
-                        case MfUltralightTypeNTAGI2C1K:
-                        case MfUltralightTypeNTAGI2C2K:
-                        case MfUltralightTypeNTAGI2CPlus1K:
-                        case MfUltralightTypeNTAGI2CPlus2K:
-                            gen4_config[27] = MagicGen4UltralightModeNTAG;
-                            block_count = 64 * 2;
-                            break;
-                        }
-                    }
-
-                    if(dev_data->nfc_data.uid_len == 4) {
-                        gen4_config[1] = MagicGen4UIDLengthSingle;
-                    } else if(dev_data->nfc_data.uid_len == 7) {
-                        gen4_config[1] = MagicGen4UIDLengthDouble;
-                    } else {
-                        FURI_LOG_E(TAG, "Unexpected UID length %d", dev_data->nfc_data.uid_len);
-                        nfc_magic_worker->callback(
-                            NfcMagicWorkerEventFail, nfc_magic_worker->context);
-                        done = true;
-                        break;
-                    }
-
-                    gen4_config[2] = (uint8_t)(password >> 24);
-                    gen4_config[3] = (uint8_t)(password >> 16);
-                    gen4_config[4] = (uint8_t)(password >> 8);
-                    gen4_config[5] = (uint8_t)password;
-
-                    if(dev_protocol == NfcDeviceProtocolMifareUl) {
-                        gen4_config[6] = MagicGen4ShadowModeHighSpeedIgnore;
-                    } else {
-                        gen4_config[6] = MagicGen4ShadowModeIgnore;
-                    }
-                    gen4_config[7] = 0x00;
-                    memset(gen4_config + 8, 0, 16);
-                    gen4_config[24] = dev_data->nfc_data.atqa[0];
-                    gen4_config[25] = dev_data->nfc_data.atqa[1];
-                    gen4_config[26] = dev_data->nfc_data.sak;
-
-                    gen4_config[28] = block_count;
-                    gen4_config[29] = 0x01;
-
-                    furi_hal_nfc_sleep();
-                    furi_hal_nfc_activate_nfca(200, &cuid);
-                    if(!magic_gen4_set_cfg(password, gen4_config, sizeof(gen4_config), false)) {
-                        nfc_magic_worker->callback(
-                            NfcMagicWorkerEventFail, nfc_magic_worker->context);
-                        done = true;
-                        break;
-                    }
-                    if(dev_protocol == NfcDeviceProtocolMifareClassic) {
-                        for(size_t i = 0; i < block_count; i++) {
-                            FURI_LOG_D(TAG, "Writing block %d", i);
-                            if(!magic_gen4_write_blk(password, i, mfc_data->block[i].value)) {
-                                FURI_LOG_E(TAG, "Failed to write %d block", i);
-                                nfc_magic_worker->callback(
-                                    NfcMagicWorkerEventFail, nfc_magic_worker->context);
-                                done = true;
-                                break;
-                            }
-                        }
-                    } else if(dev_protocol == NfcDeviceProtocolMifareUl) {
-                        MfUltralightData* mf_ul_data = &dev_data->mf_ul_data;
-                        for(size_t i = 0; (i * 4) < mf_ul_data->data_read; i++) {
-                            size_t data_offset = i * 4;
-                            FURI_LOG_D(
-                                TAG,
-                                "Writing page %zu (%zu/%u)",
-                                i,
-                                data_offset,
-                                mf_ul_data->data_read);
-                            uint8_t* block = mf_ul_data->data + data_offset;
-                            if(!magic_gen4_write_blk(password, i, block)) {
-                                FURI_LOG_E(TAG, "Failed to write %zu page", i);
-                                nfc_magic_worker->callback(
-                                    NfcMagicWorkerEventFail, nfc_magic_worker->context);
-                                done = true;
-                                break;
-                            }
-                        }
-
-                        uint8_t buffer[16] = {0};
-
-                        for(size_t i = 0; i < 8; i++) {
-                            memcpy(buffer, &mf_ul_data->signature[i * 4], 4); //-V1086
-                            if(!magic_gen4_write_blk(password, 0xF2 + i, buffer)) {
-                                FURI_LOG_E(TAG, "Failed to write signature block %d", i);
-                                nfc_magic_worker->callback(
-                                    NfcMagicWorkerEventFail, nfc_magic_worker->context);
-                                done = true;
-                                break;
-                            }
-                        }
-
-                        buffer[0] = mf_ul_data->version.header;
-                        buffer[1] = mf_ul_data->version.vendor_id;
-                        buffer[2] = mf_ul_data->version.prod_type;
-                        buffer[3] = mf_ul_data->version.prod_subtype;
-                        if(!magic_gen4_write_blk(password, 0xFA, buffer)) {
-                            FURI_LOG_E(TAG, "Failed to write version block 0");
-                            nfc_magic_worker->callback(
-                                NfcMagicWorkerEventFail, nfc_magic_worker->context);
-                            done = true;
-                            break;
-                        }
-
-                        buffer[0] = mf_ul_data->version.prod_ver_major;
-                        buffer[1] = mf_ul_data->version.prod_ver_minor;
-                        buffer[2] = mf_ul_data->version.storage_size;
-                        buffer[3] = mf_ul_data->version.protocol_type;
-                        if(!magic_gen4_write_blk(password, 0xFB, buffer)) {
-                            FURI_LOG_E(TAG, "Failed to write version block 1");
-                            nfc_magic_worker->callback(
-                                NfcMagicWorkerEventFail, nfc_magic_worker->context);
-                            done = true;
-                            break;
-                        }
-                    }
-
-                    nfc_magic_worker->callback(
-                        NfcMagicWorkerEventSuccess, nfc_magic_worker->context);
-                    done = true;
-                    break;
-                }
-            }
-        } while(false);
-
-        if(done) break;
-
-        if(card_found_notified) {
-            nfc_magic_worker->callback(
-                NfcMagicWorkerEventNoCardDetected, nfc_magic_worker->context);
-            card_found_notified = false;
-        }
-
-        furi_delay_ms(300);
-    }
-    magic_deactivate();
-}
-
-void nfc_magic_worker_check(NfcMagicWorker* nfc_magic_worker) {
-    FuriHalNfcDevData nfc_data = {};
-    NfcMagicDevice* magic_dev = nfc_magic_worker->magic_dev;
-    bool card_found_notified = false;
-    uint8_t gen4_config[MAGIC_GEN4_CONFIG_LEN];
-
-    while(nfc_magic_worker->state == NfcMagicWorkerStateCheck) {
-        magic_activate();
-        if(magic_gen1_wupa()) {
-            magic_dev->type = MagicTypeClassicGen1;
-            if(!card_found_notified) {
-                nfc_magic_worker->callback(
-                    NfcMagicWorkerEventCardDetected, nfc_magic_worker->context);
-                card_found_notified = true;
-            }
-
-            if(furi_hal_nfc_detect(&nfc_data, 200)) {
-                magic_dev->cuid = nfc_data.cuid;
-                magic_dev->uid_len = nfc_data.uid_len;
-            } else {
-                // wrong BCC
-                magic_dev->uid_len = 4;
-            }
-            nfc_magic_worker->callback(NfcMagicWorkerEventSuccess, nfc_magic_worker->context);
-            break;
-        } else {
-            magic_deactivate();
-            magic_activate();
-            if(furi_hal_nfc_detect(&nfc_data, 200)) {
-                magic_dev->cuid = nfc_data.cuid;
-                magic_dev->uid_len = nfc_data.uid_len;
-                if(magic_gen4_get_cfg(magic_dev->password, gen4_config)) {
-                    magic_dev->type = MagicTypeGen4;
-                    if(!card_found_notified) {
-                        nfc_magic_worker->callback(
-                            NfcMagicWorkerEventCardDetected, nfc_magic_worker->context);
-                        card_found_notified = true;
-                    }
-
-                    nfc_magic_worker->callback(
-                        NfcMagicWorkerEventSuccess, nfc_magic_worker->context);
-                } else {
-                    nfc_magic_worker->callback(
-                        NfcMagicWorkerEventWrongCard, nfc_magic_worker->context);
-                    card_found_notified = true;
-                }
-                break;
-            } else {
-                if(card_found_notified) {
-                    nfc_magic_worker->callback(
-                        NfcMagicWorkerEventNoCardDetected, nfc_magic_worker->context);
-                    card_found_notified = false;
-                }
-            }
-        }
-
-        magic_deactivate();
-        furi_delay_ms(300);
-    }
-
-    magic_deactivate();
-}
-
-void nfc_magic_worker_rekey(NfcMagicWorker* nfc_magic_worker) {
-    NfcMagicDevice* magic_dev = nfc_magic_worker->magic_dev;
-    bool card_found_notified = false;
-
-    if(magic_dev->type != MagicTypeGen4) {
-        nfc_magic_worker->callback(NfcMagicWorkerEventCardDetected, nfc_magic_worker->context);
-        return;
-    }
-
-    while(nfc_magic_worker->state == NfcMagicWorkerStateRekey) {
-        magic_activate();
-        uint32_t cuid;
-        furi_hal_nfc_activate_nfca(200, &cuid);
-        if(cuid != magic_dev->cuid) {
-            if(card_found_notified) {
-                nfc_magic_worker->callback(
-                    NfcMagicWorkerEventNoCardDetected, nfc_magic_worker->context);
-                card_found_notified = false;
-            }
-            continue;
-        }
-
-        nfc_magic_worker->callback(NfcMagicWorkerEventCardDetected, nfc_magic_worker->context);
-        card_found_notified = true;
-
-        if(magic_gen4_set_pwd(magic_dev->password, nfc_magic_worker->new_password)) {
-            magic_dev->password = nfc_magic_worker->new_password;
-            nfc_magic_worker->callback(NfcMagicWorkerEventSuccess, nfc_magic_worker->context);
-            break;
-        }
-
-        if(card_found_notified) { //-V547
-            nfc_magic_worker->callback(
-                NfcMagicWorkerEventNoCardDetected, nfc_magic_worker->context);
-            card_found_notified = false;
-        }
-        furi_delay_ms(300);
-    }
-    magic_deactivate();
-}
-
-void nfc_magic_worker_wipe(NfcMagicWorker* nfc_magic_worker) {
-    NfcMagicDevice* magic_dev = nfc_magic_worker->magic_dev;
-    bool card_found_notified = false;
-    bool card_wiped = false;
-
-    MfClassicBlock block;
-    memset(&block, 0, sizeof(MfClassicBlock));
-    MfClassicBlock empty_block;
-    memset(&empty_block, 0, sizeof(MfClassicBlock));
-    MfClassicBlock trailer_block;
-    memset(&trailer_block, 0xff, sizeof(MfClassicBlock));
-
-    block.value[0] = 0x01;
-    block.value[1] = 0x02;
-    block.value[2] = 0x03;
-    block.value[3] = 0x04;
-    block.value[4] = 0x04;
-    block.value[5] = 0x08;
-    block.value[6] = 0x04;
-
-    trailer_block.value[7] = 0x07;
-    trailer_block.value[8] = 0x80;
-    trailer_block.value[9] = 0x69;
-
-    while(nfc_magic_worker->state == NfcMagicWorkerStateWipe) {
-        do {
-            magic_deactivate();
-            furi_delay_ms(300);
-            if(!magic_activate()) break;
-            if(magic_dev->type == MagicTypeClassicGen1) {
-                if(!magic_gen1_wupa()) break;
-                if(!card_found_notified) {
-                    nfc_magic_worker->callback(
-                        NfcMagicWorkerEventCardDetected, nfc_magic_worker->context);
-                    card_found_notified = true;
-                }
-
-                if(!magic_gen1_data_access_cmd()) break;
-                if(!magic_gen1_write_blk(0, &block)) break;
-
-                for(size_t i = 1; i < 64; i++) {
-                    FURI_LOG_D(TAG, "Wiping block %d", i);
-                    bool success = false;
-                    if((i | 0x03) == i) {
-                        success = magic_gen1_write_blk(i, &trailer_block);
-                    } else {
-                        success = magic_gen1_write_blk(i, &empty_block);
-                    }
-
-                    if(!success) {
-                        FURI_LOG_E(TAG, "Failed to write %d block", i);
-                        nfc_magic_worker->callback(
-                            NfcMagicWorkerEventFail, nfc_magic_worker->context);
-                        break;
-                    }
-                }
-
-                card_wiped = true;
-                nfc_magic_worker->callback(NfcMagicWorkerEventSuccess, nfc_magic_worker->context);
-            } else if(magic_dev->type == MagicTypeGen4) {
-                uint32_t cuid;
-                if(!furi_hal_nfc_activate_nfca(200, &cuid)) break;
-                if(cuid != magic_dev->cuid) break;
-                if(!card_found_notified) {
-                    nfc_magic_worker->callback(
-                        NfcMagicWorkerEventCardDetected, nfc_magic_worker->context);
-                    card_found_notified = true;
-                }
-
-                if(!magic_gen4_wipe(magic_dev->password)) break;
-
-                card_wiped = true;
-                nfc_magic_worker->callback(NfcMagicWorkerEventSuccess, nfc_magic_worker->context);
-            }
-        } while(false);
-
-        if(card_wiped) break;
-
-        if(card_found_notified) {
-            nfc_magic_worker->callback(
-                NfcMagicWorkerEventNoCardDetected, nfc_magic_worker->context);
-            card_found_notified = false;
-        }
-    }
-    magic_deactivate();
-}

+ 0 - 42
nfc_magic/nfc_magic_worker.h

@@ -1,42 +0,0 @@
-#pragma once
-
-#include <lib/nfc/nfc_device.h>
-#include "nfc_magic.h"
-
-typedef struct NfcMagicWorker NfcMagicWorker;
-
-typedef enum {
-    NfcMagicWorkerStateReady,
-
-    NfcMagicWorkerStateCheck,
-    NfcMagicWorkerStateWrite,
-    NfcMagicWorkerStateRekey,
-    NfcMagicWorkerStateWipe,
-
-    NfcMagicWorkerStateStop,
-} NfcMagicWorkerState;
-
-typedef enum {
-    NfcMagicWorkerEventSuccess,
-    NfcMagicWorkerEventFail,
-    NfcMagicWorkerEventCardDetected,
-    NfcMagicWorkerEventNoCardDetected,
-    NfcMagicWorkerEventWrongCard,
-} NfcMagicWorkerEvent;
-
-typedef bool (*NfcMagicWorkerCallback)(NfcMagicWorkerEvent event, void* context);
-
-NfcMagicWorker* nfc_magic_worker_alloc();
-
-void nfc_magic_worker_free(NfcMagicWorker* nfc_magic_worker);
-
-void nfc_magic_worker_stop(NfcMagicWorker* nfc_magic_worker);
-
-void nfc_magic_worker_start(
-    NfcMagicWorker* nfc_magic_worker,
-    NfcMagicWorkerState state,
-    NfcMagicDevice* magic_dev,
-    NfcDeviceData* dev_data,
-    uint32_t new_password,
-    NfcMagicWorkerCallback callback,
-    void* context);

+ 0 - 29
nfc_magic/nfc_magic_worker_i.h

@@ -1,29 +0,0 @@
-#pragma once
-
-#include <furi.h>
-
-#include "nfc_magic_worker.h"
-#include "lib/magic/common.h"
-
-struct NfcMagicWorker {
-    FuriThread* thread;
-
-    NfcMagicDevice* magic_dev;
-    NfcDeviceData* dev_data;
-    uint32_t new_password;
-
-    NfcMagicWorkerCallback callback;
-    void* context;
-
-    NfcMagicWorkerState state;
-};
-
-int32_t nfc_magic_worker_task(void* context);
-
-void nfc_magic_worker_check(NfcMagicWorker* nfc_magic_worker);
-
-void nfc_magic_worker_write(NfcMagicWorker* nfc_magic_worker);
-
-void nfc_magic_worker_rekey(NfcMagicWorker* nfc_magic_worker);
-
-void nfc_magic_worker_wipe(NfcMagicWorker* nfc_magic_worker);

+ 0 - 50
nfc_magic/scenes/nfc_magic_scene_actions.c

@@ -1,50 +0,0 @@
-#include "../nfc_magic_i.h"
-enum SubmenuIndex {
-    SubmenuIndexWrite,
-    SubmenuIndexWipe,
-};
-
-void nfc_magic_scene_actions_submenu_callback(void* context, uint32_t index) {
-    NfcMagic* nfc_magic = context;
-    view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, index);
-}
-
-void nfc_magic_scene_actions_on_enter(void* context) {
-    NfcMagic* nfc_magic = context;
-
-    Submenu* submenu = nfc_magic->submenu;
-    submenu_add_item(
-        submenu, "Write", SubmenuIndexWrite, nfc_magic_scene_actions_submenu_callback, nfc_magic);
-    submenu_add_item(
-        submenu, "Wipe", SubmenuIndexWipe, nfc_magic_scene_actions_submenu_callback, nfc_magic);
-
-    submenu_set_selected_item(
-        submenu, scene_manager_get_scene_state(nfc_magic->scene_manager, NfcMagicSceneActions));
-    view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewMenu);
-}
-
-bool nfc_magic_scene_actions_on_event(void* context, SceneManagerEvent event) {
-    NfcMagic* nfc_magic = context;
-    bool consumed = false;
-
-    if(event.type == SceneManagerEventTypeCustom) {
-        if(event.event == SubmenuIndexWrite) {
-            scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneFileSelect);
-            consumed = true;
-        } else if(event.event == SubmenuIndexWipe) {
-            scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneWipe);
-            consumed = true;
-        }
-        scene_manager_set_scene_state(nfc_magic->scene_manager, NfcMagicSceneActions, event.event);
-    } else if(event.type == SceneManagerEventTypeBack) {
-        consumed = scene_manager_search_and_switch_to_previous_scene(
-            nfc_magic->scene_manager, NfcMagicSceneStart);
-    }
-
-    return consumed;
-}
-
-void nfc_magic_scene_actions_on_exit(void* context) {
-    NfcMagic* nfc_magic = context;
-    submenu_reset(nfc_magic->submenu);
-}

+ 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);
+}

+ 28 - 63
nfc_magic/scenes/nfc_magic_scene_check.c

@@ -1,89 +1,54 @@
-#include "../nfc_magic_i.h"
+#include "../nfc_magic_app_i.h"
 
-enum {
-    NfcMagicSceneCheckStateCardSearch,
-    NfcMagicSceneCheckStateCardFound,
-};
-
-bool nfc_magic_check_worker_callback(NfcMagicWorkerEvent event, void* context) {
+void nfc_magic_check_worker_callback(NfcMagicScannerEvent event, void* context) {
     furi_assert(context);
 
-    NfcMagic* nfc_magic = context;
-    view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, event);
-
-    return true;
-}
-
-static void nfc_magic_scene_check_setup_view(NfcMagic* nfc_magic) {
-    Popup* popup = nfc_magic->popup;
-    popup_reset(popup);
-    uint32_t state = scene_manager_get_scene_state(nfc_magic->scene_manager, NfcMagicSceneCheck);
+    NfcMagicApp* instance = context;
 
-    if(state == NfcMagicSceneCheckStateCardSearch) {
-        popup_set_icon(nfc_magic->popup, 0, 8, &I_NFC_manual_60x50);
-        popup_set_text(
-            nfc_magic->popup, "Apply card to\nthe back", 128, 32, AlignRight, AlignCenter);
-    } else {
-        popup_set_icon(popup, 12, 23, &I_Loading_24);
-        popup_set_header(popup, "Checking\nDon't move...", 52, 32, AlignLeft, AlignCenter);
+    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);
     }
-
-    view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewPopup);
 }
 
 void nfc_magic_scene_check_on_enter(void* context) {
-    NfcMagic* nfc_magic = 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);
 
-    scene_manager_set_scene_state(
-        nfc_magic->scene_manager, NfcMagicSceneCheck, NfcMagicSceneCheckStateCardSearch);
-    nfc_magic_scene_check_setup_view(nfc_magic);
+    nfc_magic_app_blink_start(instance);
 
-    // Setup and start worker
-    nfc_magic_worker_start(
-        nfc_magic->worker,
-        NfcMagicWorkerStateCheck,
-        nfc_magic->dev,
-        &nfc_magic->source_dev->dev_data,
-        nfc_magic->new_password,
-        nfc_magic_check_worker_callback,
-        nfc_magic);
-    nfc_magic_blink_start(nfc_magic);
+    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) {
-    NfcMagic* nfc_magic = context;
+    NfcMagicApp* instance = context;
     bool consumed = false;
 
     if(event.type == SceneManagerEventTypeCustom) {
-        if(event.event == NfcMagicWorkerEventSuccess) {
-            scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneMagicInfo);
-            consumed = true;
-        } else if(event.event == NfcMagicWorkerEventWrongCard) {
-            scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneNotMagic);
-            consumed = true;
-        } else if(event.event == NfcMagicWorkerEventCardDetected) {
-            scene_manager_set_scene_state(
-                nfc_magic->scene_manager, NfcMagicSceneCheck, NfcMagicSceneCheckStateCardFound);
-            nfc_magic_scene_check_setup_view(nfc_magic);
+        if(event.event == NfcMagicCustomEventWorkerSuccess) {
+            scene_manager_next_scene(instance->scene_manager, NfcMagicSceneMagicInfo);
             consumed = true;
-        } else if(event.event == NfcMagicWorkerEventNoCardDetected) {
-            scene_manager_set_scene_state(
-                nfc_magic->scene_manager, NfcMagicSceneCheck, NfcMagicSceneCheckStateCardSearch);
-            nfc_magic_scene_check_setup_view(nfc_magic);
+        } 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) {
-    NfcMagic* nfc_magic = context;
-
-    nfc_magic_worker_stop(nfc_magic->worker);
-    scene_manager_set_scene_state(
-        nfc_magic->scene_manager, NfcMagicSceneCheck, NfcMagicSceneCheckStateCardSearch);
-    // Clear view
-    popup_reset(nfc_magic->popup);
+    NfcMagicApp* instance = context;
 
-    nfc_magic_blink_stop(nfc_magic);
+    nfc_magic_scanner_stop(instance->scanner);
+    popup_reset(instance->popup);
+    nfc_magic_app_blink_stop(instance);
 }

+ 10 - 11
nfc_magic/scenes/nfc_magic_scene_config.h

@@ -1,18 +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, actions, Actions)
-ADD_SCENE(nfc_magic, gen4_actions, Gen4Actions)
-ADD_SCENE(nfc_magic, new_key_input, NewKeyInput)
+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, wrong_card, WrongCard)
 ADD_SCENE(nfc_magic, write, Write)
 ADD_SCENE(nfc_magic, write_fail, WriteFail)
-ADD_SCENE(nfc_magic, success, Success)
-ADD_SCENE(nfc_magic, check, Check)
+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)
-ADD_SCENE(nfc_magic, magic_info, MagicInfo)
-ADD_SCENE(nfc_magic, rekey, Rekey)
-ADD_SCENE(nfc_magic, rekey_fail, RekeyFail)
-ADD_SCENE(nfc_magic, wipe, Wipe)
-ADD_SCENE(nfc_magic, wipe_fail, WipeFail)

+ 35 - 51
nfc_magic/scenes/nfc_magic_scene_file_select.c

@@ -1,66 +1,51 @@
-#include "../nfc_magic_i.h"
+#include "../nfc_magic_app_i.h"
+#include <nfc/protocols/mf_classic/mf_classic.h>
 
-static bool nfc_magic_scene_file_select_is_file_suitable(NfcMagic* nfc_magic) {
-    NfcDevice* nfc_dev = nfc_magic->source_dev;
-    if(nfc_dev->format == NfcDeviceSaveFormatMifareClassic) {
-        switch(nfc_magic->dev->type) {
-        case MagicTypeClassicGen1:
-        case MagicTypeClassicDirectWrite:
-        case MagicTypeClassicAPDU:
-            if((nfc_dev->dev_data.mf_classic_data.type != MfClassicType1k) ||
-               (nfc_dev->dev_data.nfc_data.uid_len != nfc_magic->dev->uid_len)) {
-                return false;
-            }
-            return true;
+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);
 
-        case MagicTypeGen4:
-            return true;
-        default:
-            return false;
+    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(
-        (nfc_dev->format == NfcDeviceSaveFormatMifareUl) &&
-        (nfc_dev->dev_data.nfc_data.uid_len == 7)) {
-        switch(nfc_magic->dev->type) {
-        case MagicTypeUltralightGen1:
-        case MagicTypeUltralightDirectWrite:
-        case MagicTypeUltralightC_Gen1:
-        case MagicTypeUltralightC_DirectWrite:
-        case MagicTypeGen4:
-            switch(nfc_dev->dev_data.mf_ul_data.type) {
-            case MfUltralightTypeNTAGI2C1K:
-            case MfUltralightTypeNTAGI2C2K:
-            case MfUltralightTypeNTAGI2CPlus1K:
-            case MfUltralightTypeNTAGI2CPlus2K:
-                return false;
-            default:
-                return 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);
             }
-        default:
-            return false;
         }
     }
 
-    return false;
+    return suitable;
 }
 
 void nfc_magic_scene_file_select_on_enter(void* context) {
-    NfcMagic* nfc_magic = context;
-    // Process file_select return
-    nfc_device_set_loading_callback(
-        nfc_magic->source_dev, nfc_magic_show_loading_popup, nfc_magic);
+    NfcMagicApp* instance = context;
 
-    if(!furi_string_size(nfc_magic->source_dev->load_path)) {
-        furi_string_set_str(nfc_magic->source_dev->load_path, NFC_APP_FOLDER);
-    }
-    if(nfc_file_select(nfc_magic->source_dev)) {
-        if(nfc_magic_scene_file_select_is_file_suitable(nfc_magic)) {
-            scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneWriteConfirm);
+    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(nfc_magic->scene_manager, NfcMagicSceneWrongCard);
+            scene_manager_next_scene(instance->scene_manager, NfcMagicSceneWrongCard);
         }
     } else {
-        scene_manager_previous_scene(nfc_magic->scene_manager);
+        scene_manager_previous_scene(instance->scene_manager);
     }
 }
 
@@ -71,6 +56,5 @@ bool nfc_magic_scene_file_select_on_event(void* context, SceneManagerEvent event
 }
 
 void nfc_magic_scene_file_select_on_exit(void* context) {
-    NfcMagic* nfc_magic = context;
-    nfc_device_set_loading_callback(nfc_magic->source_dev, NULL, nfc_magic);
+    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);
+}

+ 0 - 70
nfc_magic/scenes/nfc_magic_scene_gen4_actions.c

@@ -1,70 +0,0 @@
-#include "../nfc_magic_i.h"
-enum SubmenuIndex {
-    SubmenuIndexWrite,
-    SubmenuIndexChangePassword,
-    SubmenuIndexWipe,
-};
-
-void nfc_magic_scene_gen4_actions_submenu_callback(void* context, uint32_t index) {
-    NfcMagic* nfc_magic = context;
-    view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, index);
-}
-
-void nfc_magic_scene_gen4_actions_on_enter(void* context) {
-    NfcMagic* nfc_magic = context;
-
-    Submenu* submenu = nfc_magic->submenu;
-    submenu_add_item(
-        submenu,
-        "Write",
-        SubmenuIndexWrite,
-        nfc_magic_scene_gen4_actions_submenu_callback,
-        nfc_magic);
-    submenu_add_item(
-        submenu,
-        "Change password",
-        SubmenuIndexChangePassword,
-        nfc_magic_scene_gen4_actions_submenu_callback,
-        nfc_magic);
-    submenu_add_item(
-        submenu,
-        "Wipe",
-        SubmenuIndexWipe,
-        nfc_magic_scene_gen4_actions_submenu_callback,
-        nfc_magic);
-
-    submenu_set_selected_item(
-        submenu,
-        scene_manager_get_scene_state(nfc_magic->scene_manager, NfcMagicSceneGen4Actions));
-    view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewMenu);
-}
-
-bool nfc_magic_scene_gen4_actions_on_event(void* context, SceneManagerEvent event) {
-    NfcMagic* nfc_magic = context;
-    bool consumed = false;
-
-    if(event.type == SceneManagerEventTypeCustom) {
-        if(event.event == SubmenuIndexWrite) {
-            scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneFileSelect);
-            consumed = true;
-        } else if(event.event == SubmenuIndexChangePassword) {
-            scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneNewKeyInput);
-            consumed = true;
-        } else if(event.event == SubmenuIndexWipe) {
-            scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneWipe);
-            consumed = true;
-        }
-        scene_manager_set_scene_state(
-            nfc_magic->scene_manager, NfcMagicSceneGen4Actions, event.event);
-    } else if(event.type == SceneManagerEventTypeBack) {
-        consumed = scene_manager_search_and_switch_to_previous_scene(
-            nfc_magic->scene_manager, NfcMagicSceneStart);
-    }
-
-    return consumed;
-}
-
-void nfc_magic_scene_gen4_actions_on_exit(void* context) {
-    NfcMagic* nfc_magic = context;
-    submenu_reset(nfc_magic->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);
+}

+ 25 - 15
nfc_magic/scenes/nfc_magic_scene_key_input.c

@@ -1,35 +1,45 @@
-#include "../nfc_magic_i.h"
+#include "../nfc_magic_app_i.h"
+
+#include <nfc/helpers/nfc_util.h>
 
 void nfc_magic_scene_key_input_byte_input_callback(void* context) {
-    NfcMagic* nfc_magic = context;
+    NfcMagicApp* instance = context;
 
     view_dispatcher_send_custom_event(
-        nfc_magic->view_dispatcher, NfcMagicCustomEventByteInputDone);
+        instance->view_dispatcher, NfcMagicAppCustomEventByteInputDone);
 }
 
 void nfc_magic_scene_key_input_on_enter(void* context) {
-    NfcMagic* nfc_magic = context;
+    NfcMagicApp* instance = context;
 
     // Setup view
-    ByteInput* byte_input = nfc_magic->byte_input;
+    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,
-        nfc_magic,
-        (uint8_t*)&nfc_magic->dev->password,
-        4);
-    view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewByteInput);
+        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) {
-    NfcMagic* nfc_magic = context;
+    NfcMagicApp* instance = context;
     bool consumed = false;
 
     if(event.type == SceneManagerEventTypeCustom) {
-        if(event.event == NfcMagicCustomEventByteInputDone) {
-            scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneCheck);
+        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;
         }
     }
@@ -37,9 +47,9 @@ bool nfc_magic_scene_key_input_on_event(void* context, SceneManagerEvent event)
 }
 
 void nfc_magic_scene_key_input_on_exit(void* context) {
-    NfcMagic* nfc_magic = context;
+    NfcMagicApp* instance = context;
 
     // Clear view
-    byte_input_set_result_callback(nfc_magic->byte_input, NULL, NULL, NULL, NULL, 0);
-    byte_input_set_header_text(nfc_magic->byte_input, "");
+    byte_input_set_result_callback(instance->byte_input, NULL, NULL, NULL, NULL, 0);
+    byte_input_set_header_text(instance->byte_input, "");
 }

+ 25 - 21
nfc_magic/scenes/nfc_magic_scene_magic_info.c

@@ -1,50 +1,54 @@
-#include "../nfc_magic_i.h"
-#include "../lib/magic/types.h"
+#include "../nfc_magic_app_i.h"
 
 void nfc_magic_scene_magic_info_widget_callback(
     GuiButtonType result,
     InputType type,
     void* context) {
-    NfcMagic* nfc_magic = context;
+    NfcMagicApp* instance = context;
+
     if(type == InputTypeShort) {
-        view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, result);
+        view_dispatcher_send_custom_event(instance->view_dispatcher, result);
     }
 }
 
 void nfc_magic_scene_magic_info_on_enter(void* context) {
-    NfcMagic* nfc_magic = context;
-    Widget* widget = nfc_magic->widget;
-    const char* card_type = nfc_magic_type(nfc_magic->dev->type);
+    NfcMagicApp* instance = context;
+    Widget* widget = instance->widget;
 
-    notification_message(nfc_magic->notifications, &sequence_success);
+    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, card_type);
+    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, nfc_magic);
+        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, nfc_magic);
+        widget, GuiButtonTypeRight, "More", nfc_magic_scene_magic_info_widget_callback, instance);
 
-    // Setup and start worker
-    view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewWidget);
+    view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewWidget);
 }
 
 bool nfc_magic_scene_magic_info_on_event(void* context, SceneManagerEvent event) {
-    NfcMagic* nfc_magic = context;
+    NfcMagicApp* instance = context;
     bool consumed = false;
 
     if(event.type == SceneManagerEventTypeCustom) {
         if(event.event == GuiButtonTypeLeft) {
-            consumed = scene_manager_previous_scene(nfc_magic->scene_manager);
+            consumed = scene_manager_previous_scene(instance->scene_manager);
         } else if(event.event == GuiButtonTypeRight) {
-            MagicType type = nfc_magic->dev->type;
-            if(type == MagicTypeGen4) {
-                scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneGen4Actions);
+            if(instance->protocol == NfcMagicProtocolGen1) {
+                scene_manager_next_scene(instance->scene_manager, NfcMagicSceneGen1Menu);
                 consumed = true;
             } else {
-                scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneActions);
+                scene_manager_next_scene(instance->scene_manager, NfcMagicSceneGen4Menu);
                 consumed = true;
             }
         }
@@ -53,7 +57,7 @@ bool nfc_magic_scene_magic_info_on_event(void* context, SceneManagerEvent event)
 }
 
 void nfc_magic_scene_magic_info_on_exit(void* context) {
-    NfcMagic* nfc_magic = context;
+    NfcMagicApp* instance = context;
 
-    widget_reset(nfc_magic->widget);
+    widget_reset(instance->widget);
 }

+ 0 - 45
nfc_magic/scenes/nfc_magic_scene_new_key_input.c

@@ -1,45 +0,0 @@
-#include "../nfc_magic_i.h"
-
-void nfc_magic_scene_new_key_input_byte_input_callback(void* context) {
-    NfcMagic* nfc_magic = context;
-
-    view_dispatcher_send_custom_event(
-        nfc_magic->view_dispatcher, NfcMagicCustomEventByteInputDone);
-}
-
-void nfc_magic_scene_new_key_input_on_enter(void* context) {
-    NfcMagic* nfc_magic = context;
-
-    // Setup view
-    ByteInput* byte_input = nfc_magic->byte_input;
-    byte_input_set_header_text(byte_input, "Enter the password in hex");
-    byte_input_set_result_callback(
-        byte_input,
-        nfc_magic_scene_new_key_input_byte_input_callback,
-        NULL,
-        nfc_magic,
-        (uint8_t*)&nfc_magic->new_password,
-        4);
-    view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewByteInput);
-}
-
-bool nfc_magic_scene_new_key_input_on_event(void* context, SceneManagerEvent event) {
-    NfcMagic* nfc_magic = context;
-    bool consumed = false;
-
-    if(event.type == SceneManagerEventTypeCustom) {
-        if(event.event == NfcMagicCustomEventByteInputDone) {
-            scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneRekey);
-            consumed = true;
-        }
-    }
-    return consumed;
-}
-
-void nfc_magic_scene_new_key_input_on_exit(void* context) {
-    NfcMagic* nfc_magic = context;
-
-    // Clear view
-    byte_input_set_result_callback(nfc_magic->byte_input, NULL, NULL, NULL, NULL, 0);
-    byte_input_set_header_text(nfc_magic->byte_input, "");
-}

+ 12 - 12
nfc_magic/scenes/nfc_magic_scene_not_magic.c

@@ -1,43 +1,43 @@
-#include "../nfc_magic_i.h"
+#include "../nfc_magic_app_i.h"
 
 void nfc_magic_scene_not_magic_widget_callback(GuiButtonType result, InputType type, void* context) {
-    NfcMagic* nfc_magic = context;
+    NfcMagicApp* instance = context;
     if(type == InputTypeShort) {
-        view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, result);
+        view_dispatcher_send_custom_event(instance->view_dispatcher, result);
     }
 }
 
 void nfc_magic_scene_not_magic_on_enter(void* context) {
-    NfcMagic* nfc_magic = context;
-    Widget* widget = nfc_magic->widget;
+    NfcMagicApp* instance = context;
+    Widget* widget = instance->widget;
 
-    notification_message(nfc_magic->notifications, &sequence_error);
+    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, nfc_magic);
+        widget, GuiButtonTypeLeft, "Retry", nfc_magic_scene_not_magic_widget_callback, instance);
 
     // Setup and start worker
-    view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewWidget);
+    view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewWidget);
 }
 
 bool nfc_magic_scene_not_magic_on_event(void* context, SceneManagerEvent event) {
-    NfcMagic* nfc_magic = context;
+    NfcMagicApp* instance = context;
     bool consumed = false;
 
     if(event.type == SceneManagerEventTypeCustom) {
         if(event.event == GuiButtonTypeLeft) {
-            consumed = scene_manager_previous_scene(nfc_magic->scene_manager);
+            consumed = scene_manager_previous_scene(instance->scene_manager);
         }
     }
     return consumed;
 }
 
 void nfc_magic_scene_not_magic_on_exit(void* context) {
-    NfcMagic* nfc_magic = context;
+    NfcMagicApp* instance = context;
 
-    widget_reset(nfc_magic->widget);
+    widget_reset(instance->widget);
 }

+ 0 - 95
nfc_magic/scenes/nfc_magic_scene_rekey.c

@@ -1,95 +0,0 @@
-#include "../nfc_magic_i.h"
-
-enum {
-    NfcMagicSceneRekeyStateCardSearch,
-    NfcMagicSceneRekeyStateCardFound,
-};
-
-bool nfc_magic_rekey_worker_callback(NfcMagicWorkerEvent event, void* context) {
-    furi_assert(context);
-
-    NfcMagic* nfc_magic = context;
-    view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, event);
-
-    return true;
-}
-
-static void nfc_magic_scene_rekey_setup_view(NfcMagic* nfc_magic) {
-    Popup* popup = nfc_magic->popup;
-    popup_reset(popup);
-    uint32_t state = scene_manager_get_scene_state(nfc_magic->scene_manager, NfcMagicSceneRekey);
-
-    if(state == NfcMagicSceneRekeyStateCardSearch) {
-        popup_set_text(
-            nfc_magic->popup,
-            "Apply the\nsame card\nto the back",
-            128,
-            32,
-            AlignRight,
-            AlignCenter);
-        popup_set_icon(nfc_magic->popup, 0, 8, &I_NFC_manual_60x50);
-    } 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(nfc_magic->view_dispatcher, NfcMagicViewPopup);
-}
-
-void nfc_magic_scene_rekey_on_enter(void* context) {
-    NfcMagic* nfc_magic = context;
-
-    scene_manager_set_scene_state(
-        nfc_magic->scene_manager, NfcMagicSceneRekey, NfcMagicSceneRekeyStateCardSearch);
-    nfc_magic_scene_rekey_setup_view(nfc_magic);
-
-    // Setup and start worker
-    nfc_magic_worker_start(
-        nfc_magic->worker,
-        NfcMagicWorkerStateRekey,
-        nfc_magic->dev,
-        &nfc_magic->source_dev->dev_data,
-        nfc_magic->new_password,
-        nfc_magic_rekey_worker_callback,
-        nfc_magic);
-    nfc_magic_blink_start(nfc_magic);
-}
-
-bool nfc_magic_scene_rekey_on_event(void* context, SceneManagerEvent event) {
-    NfcMagic* nfc_magic = context;
-    bool consumed = false;
-
-    if(event.type == SceneManagerEventTypeCustom) {
-        if(event.event == NfcMagicWorkerEventSuccess) {
-            nfc_magic->dev->password = nfc_magic->new_password;
-            scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneSuccess);
-            consumed = true;
-        } else if(event.event == NfcMagicWorkerEventFail) {
-            scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneRekeyFail);
-            consumed = true;
-        } else if(event.event == NfcMagicWorkerEventCardDetected) {
-            scene_manager_set_scene_state(
-                nfc_magic->scene_manager, NfcMagicSceneRekey, NfcMagicSceneRekeyStateCardFound);
-            nfc_magic_scene_rekey_setup_view(nfc_magic);
-            consumed = true;
-        } else if(event.event == NfcMagicWorkerEventNoCardDetected) {
-            scene_manager_set_scene_state(
-                nfc_magic->scene_manager, NfcMagicSceneRekey, NfcMagicSceneRekeyStateCardSearch);
-            nfc_magic_scene_rekey_setup_view(nfc_magic);
-            consumed = true;
-        }
-    }
-    return consumed;
-}
-
-void nfc_magic_scene_rekey_on_exit(void* context) {
-    NfcMagic* nfc_magic = context;
-
-    nfc_magic_worker_stop(nfc_magic->worker);
-    scene_manager_set_scene_state(
-        nfc_magic->scene_manager, NfcMagicSceneRekey, NfcMagicSceneRekeyStateCardSearch);
-    // Clear view
-    popup_reset(nfc_magic->popup);
-
-    nfc_magic_blink_stop(nfc_magic);
-}

+ 0 - 50
nfc_magic/scenes/nfc_magic_scene_rekey_fail.c

@@ -1,50 +0,0 @@
-#include "../nfc_magic_i.h"
-
-void nfc_magic_scene_rekey_fail_widget_callback(
-    GuiButtonType result,
-    InputType type,
-    void* context) {
-    NfcMagic* nfc_magic = context;
-    if(type == InputTypeShort) {
-        view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, result);
-    }
-}
-
-void nfc_magic_scene_rekey_fail_on_enter(void* context) {
-    NfcMagic* nfc_magic = context;
-    Widget* widget = nfc_magic->widget;
-
-    notification_message(nfc_magic->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_rekey_fail_widget_callback, nfc_magic);
-
-    // Setup and start worker
-    view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewWidget);
-}
-
-bool nfc_magic_scene_rekey_fail_on_event(void* context, SceneManagerEvent event) {
-    NfcMagic* nfc_magic = context;
-    bool consumed = false;
-
-    if(event.type == SceneManagerEventTypeCustom) {
-        if(event.event == GuiButtonTypeLeft) {
-            consumed = scene_manager_search_and_switch_to_previous_scene(
-                nfc_magic->scene_manager, NfcMagicSceneStart);
-        }
-    } else if(event.type == SceneManagerEventTypeBack) {
-        consumed = scene_manager_search_and_switch_to_previous_scene(
-            nfc_magic->scene_manager, NfcMagicSceneStart);
-    }
-    return consumed;
-}
-
-void nfc_magic_scene_rekey_fail_on_exit(void* context) {
-    NfcMagic* nfc_magic = context;
-
-    widget_reset(nfc_magic->widget);
-}

+ 19 - 16
nfc_magic/scenes/nfc_magic_scene_start.c

@@ -1,49 +1,51 @@
-#include "../nfc_magic_i.h"
+#include "../nfc_magic_app_i.h"
+
 enum SubmenuIndex {
     SubmenuIndexCheck,
     SubmenuIndexAuthenticateGen4,
 };
 
 void nfc_magic_scene_start_submenu_callback(void* context, uint32_t index) {
-    NfcMagic* nfc_magic = context;
-    view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, index);
+    NfcMagicApp* instance = context;
+    view_dispatcher_send_custom_event(instance->view_dispatcher, index);
 }
 
 void nfc_magic_scene_start_on_enter(void* context) {
-    NfcMagic* nfc_magic = context;
+    NfcMagicApp* instance = context;
 
-    Submenu* submenu = nfc_magic->submenu;
+    Submenu* submenu = instance->submenu;
     submenu_add_item(
         submenu,
         "Check Magic Tag",
         SubmenuIndexCheck,
         nfc_magic_scene_start_submenu_callback,
-        nfc_magic);
+        instance);
     submenu_add_item(
         submenu,
         "Authenticate Gen4",
         SubmenuIndexAuthenticateGen4,
         nfc_magic_scene_start_submenu_callback,
-        nfc_magic);
+        instance);
+
+    instance->gen4_password = 0;
 
     submenu_set_selected_item(
-        submenu, scene_manager_get_scene_state(nfc_magic->scene_manager, NfcMagicSceneStart));
-    view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewMenu);
+        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) {
-    NfcMagic* nfc_magic = context;
+    NfcMagicApp* instance = context;
     bool consumed = false;
 
     if(event.type == SceneManagerEventTypeCustom) {
         if(event.event == SubmenuIndexCheck) {
-            nfc_magic->dev->password = MAGIC_GEN4_DEFAULT_PWD;
             scene_manager_set_scene_state(
-                nfc_magic->scene_manager, NfcMagicSceneStart, SubmenuIndexCheck);
-            scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneCheck);
+                instance->scene_manager, NfcMagicSceneStart, SubmenuIndexCheck);
+            scene_manager_next_scene(instance->scene_manager, NfcMagicSceneCheck);
             consumed = true;
         } else if(event.event == SubmenuIndexAuthenticateGen4) {
-            scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneKeyInput);
+            scene_manager_next_scene(instance->scene_manager, NfcMagicSceneKeyInput);
         }
     }
 
@@ -51,6 +53,7 @@ bool nfc_magic_scene_start_on_event(void* context, SceneManagerEvent event) {
 }
 
 void nfc_magic_scene_start_on_exit(void* context) {
-    NfcMagic* nfc_magic = context;
-    submenu_reset(nfc_magic->submenu);
+    NfcMagicApp* instance = context;
+
+    submenu_reset(instance->submenu);
 }

+ 13 - 13
nfc_magic/scenes/nfc_magic_scene_success.c

@@ -1,42 +1,42 @@
-#include "../nfc_magic_i.h"
+#include "../nfc_magic_app_i.h"
 
 void nfc_magic_scene_success_popup_callback(void* context) {
-    NfcMagic* nfc_magic = context;
-    view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, NfcMagicCustomEventViewExit);
+    NfcMagicApp* instance = context;
+    view_dispatcher_send_custom_event(instance->view_dispatcher, NfcMagicAppCustomEventViewExit);
 }
 
 void nfc_magic_scene_success_on_enter(void* context) {
-    NfcMagic* nfc_magic = context;
+    NfcMagicApp* instance = context;
 
-    notification_message(nfc_magic->notifications, &sequence_success);
+    notification_message(instance->notifications, &sequence_success);
 
-    Popup* popup = nfc_magic->popup;
+    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, nfc_magic);
+    popup_set_context(popup, instance);
     popup_set_callback(popup, nfc_magic_scene_success_popup_callback);
     popup_enable_timeout(popup);
 
-    view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewPopup);
+    view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewPopup);
 }
 
 bool nfc_magic_scene_success_on_event(void* context, SceneManagerEvent event) {
-    NfcMagic* nfc_magic = context;
+    NfcMagicApp* instance = context;
     bool consumed = false;
 
     if(event.type == SceneManagerEventTypeCustom) {
-        if(event.event == NfcMagicCustomEventViewExit) {
+        if(event.event == NfcMagicAppCustomEventViewExit) {
             consumed = scene_manager_search_and_switch_to_previous_scene(
-                nfc_magic->scene_manager, NfcMagicSceneStart);
+                instance->scene_manager, NfcMagicSceneStart);
         }
     }
     return consumed;
 }
 
 void nfc_magic_scene_success_on_exit(void* context) {
-    NfcMagic* nfc_magic = context;
+    NfcMagicApp* instance = context;
 
     // Clear view
-    popup_reset(nfc_magic->popup);
+    popup_reset(instance->popup);
 }

+ 91 - 52
nfc_magic/scenes/nfc_magic_scene_wipe.c

@@ -1,97 +1,136 @@
-#include "../nfc_magic_i.h"
+#include "../nfc_magic_app_i.h"
 
 enum {
     NfcMagicSceneWipeStateCardSearch,
     NfcMagicSceneWipeStateCardFound,
 };
 
-bool nfc_magic_wipe_worker_callback(NfcMagicWorkerEvent event, void* context) {
-    furi_assert(context);
+NfcCommand nfc_mafic_scene_wipe_gen1_poller_callback(Gen1aPollerEvent event, void* context) {
+    NfcMagicApp* instance = context;
+    furi_assert(event.data);
 
-    NfcMagic* nfc_magic = context;
-    view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, event);
+    NfcCommand command = NfcCommandContinue;
 
-    return true;
+    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(NfcMagic* nfc_magic) {
-    Popup* popup = nfc_magic->popup;
+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(nfc_magic->scene_manager, NfcMagicSceneWipe);
+    uint32_t state = scene_manager_get_scene_state(instance->scene_manager, NfcMagicSceneWipe);
 
     if(state == NfcMagicSceneWipeStateCardSearch) {
-        popup_set_icon(nfc_magic->popup, 0, 8, &I_NFC_manual_60x50);
+        popup_set_icon(instance->popup, 0, 8, &I_NFC_manual_60x50);
         popup_set_text(
-            nfc_magic->popup,
-            "Apply the\nsame card\nto the back",
-            128,
-            32,
-            AlignRight,
-            AlignCenter);
+            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(nfc_magic->view_dispatcher, NfcMagicViewPopup);
+    view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewPopup);
 }
 
 void nfc_magic_scene_wipe_on_enter(void* context) {
-    NfcMagic* nfc_magic = context;
+    NfcMagicApp* instance = context;
 
     scene_manager_set_scene_state(
-        nfc_magic->scene_manager, NfcMagicSceneWipe, NfcMagicSceneWipeStateCardSearch);
-    nfc_magic_scene_wipe_setup_view(nfc_magic);
-
-    // Setup and start worker
-    nfc_magic_worker_start(
-        nfc_magic->worker,
-        NfcMagicWorkerStateWipe,
-        nfc_magic->dev,
-        &nfc_magic->source_dev->dev_data,
-        nfc_magic->new_password,
-        nfc_magic_wipe_worker_callback,
-        nfc_magic);
-    nfc_magic_blink_start(nfc_magic);
+        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) {
-    NfcMagic* nfc_magic = context;
+    NfcMagicApp* instance = context;
     bool consumed = false;
 
     if(event.type == SceneManagerEventTypeCustom) {
-        if(event.event == NfcMagicWorkerEventSuccess) {
-            scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneSuccess);
-            consumed = true;
-        } else if(event.event == NfcMagicWorkerEventFail) {
-            scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneWipeFail);
-            consumed = true;
-        } else if(event.event == NfcMagicWorkerEventWrongCard) {
-            scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneNotMagic);
-            consumed = true;
-        } else if(event.event == NfcMagicWorkerEventCardDetected) {
+        if(event.event == NfcMagicCustomEventCardDetected) {
             scene_manager_set_scene_state(
-                nfc_magic->scene_manager, NfcMagicSceneWipe, NfcMagicSceneWipeStateCardFound);
-            nfc_magic_scene_wipe_setup_view(nfc_magic);
+                instance->scene_manager, NfcMagicSceneWipe, NfcMagicSceneWipeStateCardFound);
+            nfc_magic_scene_wipe_setup_view(instance);
             consumed = true;
-        } else if(event.event == NfcMagicWorkerEventNoCardDetected) {
+        } else if(event.event == NfcMagicCustomEventCardLost) {
             scene_manager_set_scene_state(
-                nfc_magic->scene_manager, NfcMagicSceneWipe, NfcMagicSceneWipeStateCardSearch);
-            nfc_magic_scene_wipe_setup_view(nfc_magic);
+                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) {
-    NfcMagic* nfc_magic = context;
+    NfcMagicApp* instance = context;
 
-    nfc_magic_worker_stop(nfc_magic->worker);
+    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(
-        nfc_magic->scene_manager, NfcMagicSceneWipe, NfcMagicSceneWipeStateCardSearch);
+        instance->scene_manager, NfcMagicSceneWipe, NfcMagicSceneWipeStateCardSearch);
     // Clear view
-    popup_reset(nfc_magic->popup);
+    popup_reset(instance->popup);
 
-    nfc_magic_blink_stop(nfc_magic);
+    nfc_magic_app_blink_stop(instance);
 }

+ 14 - 12
nfc_magic/scenes/nfc_magic_scene_wipe_fail.c

@@ -1,41 +1,43 @@
-#include "../nfc_magic_i.h"
+#include "../nfc_magic_app_i.h"
 
 void nfc_magic_scene_wipe_fail_widget_callback(GuiButtonType result, InputType type, void* context) {
-    NfcMagic* nfc_magic = context;
+    NfcMagicApp* instance = context;
+
     if(type == InputTypeShort) {
-        view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, result);
+        view_dispatcher_send_custom_event(instance->view_dispatcher, result);
     }
 }
 
 void nfc_magic_scene_wipe_fail_on_enter(void* context) {
-    NfcMagic* nfc_magic = context;
-    Widget* widget = nfc_magic->widget;
+    NfcMagicApp* instance = context;
 
-    notification_message(nfc_magic->notifications, &sequence_error);
+    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, nfc_magic);
+        widget, GuiButtonTypeLeft, "Retry", nfc_magic_scene_wipe_fail_widget_callback, instance);
 
     // Setup and start worker
-    view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewWidget);
+    view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewWidget);
 }
 
 bool nfc_magic_scene_wipe_fail_on_event(void* context, SceneManagerEvent event) {
-    NfcMagic* nfc_magic = context;
+    NfcMagicApp* instance = context;
+
     bool consumed = false;
 
     if(event.type == SceneManagerEventTypeCustom) {
         if(event.event == GuiButtonTypeLeft) {
-            consumed = scene_manager_previous_scene(nfc_magic->scene_manager);
+            consumed = scene_manager_previous_scene(instance->scene_manager);
         }
     }
     return consumed;
 }
 
 void nfc_magic_scene_wipe_fail_on_exit(void* context) {
-    NfcMagic* nfc_magic = context;
+    NfcMagicApp* instance = context;
 
-    widget_reset(nfc_magic->widget);
+    widget_reset(instance->widget);
 }

+ 99 - 52
nfc_magic/scenes/nfc_magic_scene_write.c

@@ -1,97 +1,144 @@
-#include "../nfc_magic_i.h"
+#include "../nfc_magic_app_i.h"
 
 enum {
     NfcMagicSceneWriteStateCardSearch,
     NfcMagicSceneWriteStateCardFound,
 };
 
-bool nfc_magic_write_worker_callback(NfcMagicWorkerEvent event, void* context) {
-    furi_assert(context);
+NfcCommand nfc_mafic_scene_write_gen1_poller_callback(Gen1aPollerEvent event, void* context) {
+    NfcMagicApp* instance = context;
+    furi_assert(event.data);
 
-    NfcMagic* nfc_magic = context;
-    view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, event);
+    NfcCommand command = NfcCommandContinue;
 
-    return true;
+    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(NfcMagic* nfc_magic) {
-    Popup* popup = nfc_magic->popup;
+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(nfc_magic->scene_manager, NfcMagicSceneWrite);
+    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(
-            nfc_magic->popup,
-            "Apply the\nsame card\nto the back",
-            128,
-            32,
-            AlignRight,
-            AlignCenter);
-        popup_set_icon(nfc_magic->popup, 0, 8, &I_NFC_manual_60x50);
+            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(nfc_magic->view_dispatcher, NfcMagicViewPopup);
+    view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewPopup);
 }
 
 void nfc_magic_scene_write_on_enter(void* context) {
-    NfcMagic* nfc_magic = context;
+    NfcMagicApp* instance = context;
 
     scene_manager_set_scene_state(
-        nfc_magic->scene_manager, NfcMagicSceneWrite, NfcMagicSceneWriteStateCardSearch);
-    nfc_magic_scene_write_setup_view(nfc_magic);
-
-    // Setup and start worker
-    nfc_magic_worker_start(
-        nfc_magic->worker,
-        NfcMagicWorkerStateWrite,
-        nfc_magic->dev,
-        &nfc_magic->source_dev->dev_data,
-        nfc_magic->new_password,
-        nfc_magic_write_worker_callback,
-        nfc_magic);
-    nfc_magic_blink_start(nfc_magic);
+        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) {
-    NfcMagic* nfc_magic = context;
+    NfcMagicApp* instance = context;
     bool consumed = false;
 
     if(event.type == SceneManagerEventTypeCustom) {
-        if(event.event == NfcMagicWorkerEventSuccess) {
-            scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneSuccess);
-            consumed = true;
-        } else if(event.event == NfcMagicWorkerEventFail) {
-            scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneWriteFail);
-            consumed = true;
-        } else if(event.event == NfcMagicWorkerEventWrongCard) {
-            scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneNotMagic);
-            consumed = true;
-        } else if(event.event == NfcMagicWorkerEventCardDetected) {
+        if(event.event == NfcMagicCustomEventCardDetected) {
             scene_manager_set_scene_state(
-                nfc_magic->scene_manager, NfcMagicSceneWrite, NfcMagicSceneWriteStateCardFound);
-            nfc_magic_scene_write_setup_view(nfc_magic);
+                instance->scene_manager, NfcMagicSceneWrite, NfcMagicSceneWriteStateCardFound);
+            nfc_magic_scene_write_setup_view(instance);
             consumed = true;
-        } else if(event.event == NfcMagicWorkerEventNoCardDetected) {
+        } else if(event.event == NfcMagicCustomEventCardLost) {
             scene_manager_set_scene_state(
-                nfc_magic->scene_manager, NfcMagicSceneWrite, NfcMagicSceneWriteStateCardSearch);
-            nfc_magic_scene_write_setup_view(nfc_magic);
+                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) {
-    NfcMagic* nfc_magic = context;
+    NfcMagicApp* instance = context;
 
-    nfc_magic_worker_stop(nfc_magic->worker);
+    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(
-        nfc_magic->scene_manager, NfcMagicSceneWrite, NfcMagicSceneWriteStateCardSearch);
+        instance->scene_manager, NfcMagicSceneWrite, NfcMagicSceneWriteStateCardSearch);
     // Clear view
-    popup_reset(nfc_magic->popup);
+    popup_reset(instance->popup);
 
-    nfc_magic_blink_stop(nfc_magic);
+    nfc_magic_app_blink_stop(instance);
 }

+ 14 - 17
nfc_magic/scenes/nfc_magic_scene_write_confirm.c

@@ -1,18 +1,19 @@
-#include "../nfc_magic_i.h"
+#include "../nfc_magic_app_i.h"
 
 void nfc_magic_scene_write_confirm_widget_callback(
     GuiButtonType result,
     InputType type,
     void* context) {
-    NfcMagic* nfc_magic = context;
+    NfcMagicApp* instance = context;
+
     if(type == InputTypeShort) {
-        view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, result);
+        view_dispatcher_send_custom_event(instance->view_dispatcher, result);
     }
 }
 
 void nfc_magic_scene_write_confirm_on_enter(void* context) {
-    NfcMagic* nfc_magic = context;
-    Widget* widget = nfc_magic->widget;
+    NfcMagicApp* instance = context;
+    Widget* widget = instance->widget;
 
     widget_add_string_element(widget, 3, 0, AlignLeft, AlignTop, FontPrimary, "Risky operation");
     widget_add_text_box_element(
@@ -30,27 +31,23 @@ void nfc_magic_scene_write_confirm_on_enter(void* context) {
         GuiButtonTypeCenter,
         "Continue",
         nfc_magic_scene_write_confirm_widget_callback,
-        nfc_magic);
+        instance);
     widget_add_button_element(
-        widget,
-        GuiButtonTypeLeft,
-        "Back",
-        nfc_magic_scene_write_confirm_widget_callback,
-        nfc_magic);
+        widget, GuiButtonTypeLeft, "Back", nfc_magic_scene_write_confirm_widget_callback, instance);
 
     // Setup and start worker
-    view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewWidget);
+    view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewWidget);
 }
 
 bool nfc_magic_scene_write_confirm_on_event(void* context, SceneManagerEvent event) {
-    NfcMagic* nfc_magic = context;
+    NfcMagicApp* instance = context;
     bool consumed = false;
 
     if(event.type == SceneManagerEventTypeCustom) {
         if(event.event == GuiButtonTypeLeft) {
-            consumed = scene_manager_previous_scene(nfc_magic->scene_manager);
+            consumed = scene_manager_previous_scene(instance->scene_manager);
         } else if(event.event == GuiButtonTypeCenter) {
-            scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneWrite);
+            scene_manager_next_scene(instance->scene_manager, NfcMagicSceneWrite);
             consumed = true;
         }
     }
@@ -58,7 +55,7 @@ bool nfc_magic_scene_write_confirm_on_event(void* context, SceneManagerEvent eve
 }
 
 void nfc_magic_scene_write_confirm_on_exit(void* context) {
-    NfcMagic* nfc_magic = context;
+    NfcMagicApp* instance = context;
 
-    widget_reset(nfc_magic->widget);
+    widget_reset(instance->widget);
 }

+ 13 - 13
nfc_magic/scenes/nfc_magic_scene_write_fail.c

@@ -1,20 +1,20 @@
-#include "../nfc_magic_i.h"
+#include "../nfc_magic_app_i.h"
 
 void nfc_magic_scene_write_fail_widget_callback(
     GuiButtonType result,
     InputType type,
     void* context) {
-    NfcMagic* nfc_magic = context;
+    NfcMagicApp* instance = context;
     if(type == InputTypeShort) {
-        view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, result);
+        view_dispatcher_send_custom_event(instance->view_dispatcher, result);
     }
 }
 
 void nfc_magic_scene_write_fail_on_enter(void* context) {
-    NfcMagic* nfc_magic = context;
-    Widget* widget = nfc_magic->widget;
+    NfcMagicApp* instance = context;
+    Widget* widget = instance->widget;
 
-    notification_message(nfc_magic->notifications, &sequence_error);
+    notification_message(instance->notifications, &sequence_error);
 
     widget_add_icon_element(widget, 72, 17, &I_DolphinCommon_56x48);
     widget_add_string_element(
@@ -29,30 +29,30 @@ void nfc_magic_scene_write_fail_on_enter(void* context) {
         "Not all sectors\nwere written\ncorrectly.");
 
     widget_add_button_element(
-        widget, GuiButtonTypeLeft, "Finish", nfc_magic_scene_write_fail_widget_callback, nfc_magic);
+        widget, GuiButtonTypeLeft, "Finish", nfc_magic_scene_write_fail_widget_callback, instance);
 
     // Setup and start worker
-    view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewWidget);
+    view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewWidget);
 }
 
 bool nfc_magic_scene_write_fail_on_event(void* context, SceneManagerEvent event) {
-    NfcMagic* nfc_magic = context;
+    NfcMagicApp* instance = context;
     bool consumed = false;
 
     if(event.type == SceneManagerEventTypeCustom) {
         if(event.event == GuiButtonTypeLeft) {
             consumed = scene_manager_search_and_switch_to_previous_scene(
-                nfc_magic->scene_manager, NfcMagicSceneStart);
+                instance->scene_manager, NfcMagicSceneStart);
         }
     } else if(event.type == SceneManagerEventTypeBack) {
         consumed = scene_manager_search_and_switch_to_previous_scene(
-            nfc_magic->scene_manager, NfcMagicSceneStart);
+            instance->scene_manager, NfcMagicSceneStart);
     }
     return consumed;
 }
 
 void nfc_magic_scene_write_fail_on_exit(void* context) {
-    NfcMagic* nfc_magic = context;
+    NfcMagicApp* instance = context;
 
-    widget_reset(nfc_magic->widget);
+    widget_reset(instance->widget);
 }

+ 12 - 12
nfc_magic/scenes/nfc_magic_scene_wrong_card.c

@@ -1,20 +1,20 @@
-#include "../nfc_magic_i.h"
+#include "../nfc_magic_app_i.h"
 
 void nfc_magic_scene_wrong_card_widget_callback(
     GuiButtonType result,
     InputType type,
     void* context) {
-    NfcMagic* nfc_magic = context;
+    NfcMagicApp* instance = context;
     if(type == InputTypeShort) {
-        view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, result);
+        view_dispatcher_send_custom_event(instance->view_dispatcher, result);
     }
 }
 
 void nfc_magic_scene_wrong_card_on_enter(void* context) {
-    NfcMagic* nfc_magic = context;
-    Widget* widget = nfc_magic->widget;
+    NfcMagicApp* instance = context;
+    Widget* widget = instance->widget;
 
-    notification_message(nfc_magic->notifications, &sequence_error);
+    notification_message(instance->notifications, &sequence_error);
 
     widget_add_icon_element(widget, 73, 17, &I_DolphinCommon_56x48);
     widget_add_string_element(
@@ -28,26 +28,26 @@ void nfc_magic_scene_wrong_card_on_enter(void* context) {
         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, nfc_magic);
+        widget, GuiButtonTypeLeft, "Retry", nfc_magic_scene_wrong_card_widget_callback, instance);
 
     // Setup and start worker
-    view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewWidget);
+    view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewWidget);
 }
 
 bool nfc_magic_scene_wrong_card_on_event(void* context, SceneManagerEvent event) {
-    NfcMagic* nfc_magic = context;
+    NfcMagicApp* instance = context;
     bool consumed = false;
 
     if(event.type == SceneManagerEventTypeCustom) {
         if(event.event == GuiButtonTypeLeft) {
-            consumed = scene_manager_previous_scene(nfc_magic->scene_manager);
+            consumed = scene_manager_previous_scene(instance->scene_manager);
         }
     }
     return consumed;
 }
 
 void nfc_magic_scene_wrong_card_on_exit(void* context) {
-    NfcMagic* nfc_magic = context;
+    NfcMagicApp* instance = context;
 
-    widget_reset(nfc_magic->widget);
+    widget_reset(instance->widget);
 }