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

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

git-subtree-dir: nfc_magic
git-subtree-mainline: 3c11d5bdc79dec289362a3bba1f1e46d7a90ccd0
git-subtree-split: 7b8f2688e9fcbb8d30b62a83c665f5d0776a8c00
Willy-JL 2 лет назад
Родитель
Сommit
62d258a7da
45 измененных файлов с 2585 добавлено и 0 удалено
  1. 31 0
      nfc_magic/.catalog/README.md
  2. BIN
      nfc_magic/.catalog/screenshots/1.png
  3. BIN
      nfc_magic/.catalog/screenshots/2.png
  4. 1 0
      nfc_magic/.gitsubtree
  5. 22 0
      nfc_magic/application.fam
  6. BIN
      nfc_magic/assets/125_10px.png
  7. BIN
      nfc_magic/assets/DolphinCommon_56x48.png
  8. BIN
      nfc_magic/assets/DolphinNice_96x59.png
  9. BIN
      nfc_magic/assets/Loading_24.png
  10. BIN
      nfc_magic/assets/NFC_manual_60x50.png
  11. 145 0
      nfc_magic/lib/magic/classic_gen1.c
  12. 11 0
      nfc_magic/lib/magic/classic_gen1.h
  13. 33 0
      nfc_magic/lib/magic/common.c
  14. 19 0
      nfc_magic/lib/magic/common.h
  15. 199 0
      nfc_magic/lib/magic/gen4.c
  16. 50 0
      nfc_magic/lib/magic/gen4.h
  17. 23 0
      nfc_magic/lib/magic/types.c
  18. 5 0
      nfc_magic/lib/magic/types.h
  19. 184 0
      nfc_magic/nfc_magic.c
  20. 5 0
      nfc_magic/nfc_magic.h
  21. 94 0
      nfc_magic/nfc_magic_i.h
  22. 485 0
      nfc_magic/nfc_magic_worker.c
  23. 42 0
      nfc_magic/nfc_magic_worker.h
  24. 29 0
      nfc_magic/nfc_magic_worker_i.h
  25. 30 0
      nfc_magic/scenes/nfc_magic_scene.c
  26. 29 0
      nfc_magic/scenes/nfc_magic_scene.h
  27. 50 0
      nfc_magic/scenes/nfc_magic_scene_actions.c
  28. 89 0
      nfc_magic/scenes/nfc_magic_scene_check.c
  29. 18 0
      nfc_magic/scenes/nfc_magic_scene_config.h
  30. 76 0
      nfc_magic/scenes/nfc_magic_scene_file_select.c
  31. 70 0
      nfc_magic/scenes/nfc_magic_scene_gen4_actions.c
  32. 45 0
      nfc_magic/scenes/nfc_magic_scene_key_input.c
  33. 59 0
      nfc_magic/scenes/nfc_magic_scene_magic_info.c
  34. 45 0
      nfc_magic/scenes/nfc_magic_scene_new_key_input.c
  35. 43 0
      nfc_magic/scenes/nfc_magic_scene_not_magic.c
  36. 95 0
      nfc_magic/scenes/nfc_magic_scene_rekey.c
  37. 50 0
      nfc_magic/scenes/nfc_magic_scene_rekey_fail.c
  38. 56 0
      nfc_magic/scenes/nfc_magic_scene_start.c
  39. 42 0
      nfc_magic/scenes/nfc_magic_scene_success.c
  40. 97 0
      nfc_magic/scenes/nfc_magic_scene_wipe.c
  41. 41 0
      nfc_magic/scenes/nfc_magic_scene_wipe_fail.c
  42. 97 0
      nfc_magic/scenes/nfc_magic_scene_write.c
  43. 64 0
      nfc_magic/scenes/nfc_magic_scene_write_confirm.c
  44. 58 0
      nfc_magic/scenes/nfc_magic_scene_write_fail.c
  45. 53 0
      nfc_magic/scenes/nfc_magic_scene_wrong_card.c

+ 31 - 0
nfc_magic/.catalog/README.md

@@ -0,0 +1,31 @@
+# NFC Magic
+
+This application allows you to check whether your NFC card is Magic, write data to Magic cards, and restore them to their original state.
+
+## What's a "Magic" Card?
+
+A Magic card is a card that allows you to change its UID. Generally, NFC cards have a UID pre-programmed at the factory, and it cannot be changed. However, some cards have a special backdoor feature that allows you to change the UID. These cards are called Magic cards.
+
+## Supported Cards
+
+Currently, not all Magic cards are supported. The following cards are supported:
+
+* Mifare Classic 1K/4K Gen1A/Gen1B (including OTP versions)
+* Ultimate Magic Card (Gen4 GTU)
+
+For the Gen4 cards, both the Ultralight and the Classic modes are supported, with the ability to select a custom password.
+
+The following cards are currently not supported:
+
+* Mifare Classic 1K/4K Gen2 (DirectWrite/CUID/FUID/UFUID)
+* Mifare Classic 1K/4K Gen3 (APDU)
+* Mifare Classic Magic Gen4 (GDM)
+* Supercard (Gen1 and Gen2)
+* Mifare Ultralight Gen1A/Gen1B (including OTP versions)
+* Mifare Ultralight DirectWrite
+* Mifare Ultralight EV1 DirectWrite
+* Mifare Ultralight C Gen1
+* Mifare Ultralight C DirectWrite
+* NTAG (all versions)
+* DESFire (all versions)
+* NFC-V/ISO15693 Magic Cards (all versions)

BIN
nfc_magic/.catalog/screenshots/1.png


BIN
nfc_magic/.catalog/screenshots/2.png


+ 1 - 0
nfc_magic/.gitsubtree

@@ -0,0 +1 @@
+https://github.com/flipperdevices/flipperzero-good-faps dev nfc_magic

+ 22 - 0
nfc_magic/application.fam

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

BIN
nfc_magic/assets/125_10px.png


BIN
nfc_magic/assets/DolphinCommon_56x48.png


BIN
nfc_magic/assets/DolphinNice_96x59.png


BIN
nfc_magic/assets/Loading_24.png


BIN
nfc_magic/assets/NFC_manual_60x50.png


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

@@ -0,0 +1,145 @@
+#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;
+}

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

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

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

@@ -0,0 +1,33 @@
+#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();
+}

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

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

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

@@ -0,0 +1,199 @@
+#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;
+}

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

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

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

@@ -0,0 +1,23 @@
+#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";
+    }
+}

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

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

+ 184 - 0
nfc_magic/nfc_magic.c

@@ -0,0 +1,184 @@
+#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;
+}

+ 5 - 0
nfc_magic/nfc_magic.h

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

+ 94 - 0
nfc_magic/nfc_magic_i.h

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

+ 485 - 0
nfc_magic/nfc_magic_worker.c

@@ -0,0 +1,485 @@
+#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();
+}

+ 42 - 0
nfc_magic/nfc_magic_worker.h

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

+ 29 - 0
nfc_magic/nfc_magic_worker_i.h

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

+ 30 - 0
nfc_magic/scenes/nfc_magic_scene.c

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

+ 29 - 0
nfc_magic/scenes/nfc_magic_scene.h

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

+ 50 - 0
nfc_magic/scenes/nfc_magic_scene_actions.c

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

+ 89 - 0
nfc_magic/scenes/nfc_magic_scene_check.c

@@ -0,0 +1,89 @@
+#include "../nfc_magic_i.h"
+
+enum {
+    NfcMagicSceneCheckStateCardSearch,
+    NfcMagicSceneCheckStateCardFound,
+};
+
+bool nfc_magic_check_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_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);
+
+    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);
+    }
+
+    view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewPopup);
+}
+
+void nfc_magic_scene_check_on_enter(void* context) {
+    NfcMagic* nfc_magic = context;
+
+    scene_manager_set_scene_state(
+        nfc_magic->scene_manager, NfcMagicSceneCheck, NfcMagicSceneCheckStateCardSearch);
+    nfc_magic_scene_check_setup_view(nfc_magic);
+
+    // 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);
+}
+
+bool nfc_magic_scene_check_on_event(void* context, SceneManagerEvent event) {
+    NfcMagic* nfc_magic = 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);
+            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);
+            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);
+
+    nfc_magic_blink_stop(nfc_magic);
+}

+ 18 - 0
nfc_magic/scenes/nfc_magic_scene_config.h

@@ -0,0 +1,18 @@
+ADD_SCENE(nfc_magic, start, Start)
+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, 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, 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)

+ 76 - 0
nfc_magic/scenes/nfc_magic_scene_file_select.c

@@ -0,0 +1,76 @@
+#include "../nfc_magic_i.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;
+
+        case MagicTypeGen4:
+            return true;
+        default:
+            return false;
+        }
+    } 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;
+            }
+        default:
+            return false;
+        }
+    }
+
+    return false;
+}
+
+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);
+
+    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);
+        } else {
+            scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneWrongCard);
+        }
+    } else {
+        scene_manager_previous_scene(nfc_magic->scene_manager);
+    }
+}
+
+bool nfc_magic_scene_file_select_on_event(void* context, SceneManagerEvent event) {
+    UNUSED(context);
+    UNUSED(event);
+    return false;
+}
+
+void nfc_magic_scene_file_select_on_exit(void* context) {
+    NfcMagic* nfc_magic = context;
+    nfc_device_set_loading_callback(nfc_magic->source_dev, NULL, nfc_magic);
+}

+ 70 - 0
nfc_magic/scenes/nfc_magic_scene_gen4_actions.c

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

+ 45 - 0
nfc_magic/scenes/nfc_magic_scene_key_input.c

@@ -0,0 +1,45 @@
+#include "../nfc_magic_i.h"
+
+void nfc_magic_scene_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_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_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);
+}
+
+bool nfc_magic_scene_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, NfcMagicSceneCheck);
+            consumed = true;
+        }
+    }
+    return consumed;
+}
+
+void nfc_magic_scene_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, "");
+}

+ 59 - 0
nfc_magic/scenes/nfc_magic_scene_magic_info.c

@@ -0,0 +1,59 @@
+#include "../nfc_magic_i.h"
+#include "../lib/magic/types.h"
+
+void nfc_magic_scene_magic_info_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_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);
+
+    notification_message(nfc_magic->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_button_element(
+        widget, GuiButtonTypeLeft, "Retry", nfc_magic_scene_magic_info_widget_callback, nfc_magic);
+    widget_add_button_element(
+        widget, GuiButtonTypeRight, "More", nfc_magic_scene_magic_info_widget_callback, nfc_magic);
+
+    // Setup and start worker
+    view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewWidget);
+}
+
+bool nfc_magic_scene_magic_info_on_event(void* context, SceneManagerEvent event) {
+    NfcMagic* nfc_magic = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == GuiButtonTypeLeft) {
+            consumed = scene_manager_previous_scene(nfc_magic->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);
+                consumed = true;
+            } else {
+                scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneActions);
+                consumed = true;
+            }
+        }
+    }
+    return consumed;
+}
+
+void nfc_magic_scene_magic_info_on_exit(void* context) {
+    NfcMagic* nfc_magic = context;
+
+    widget_reset(nfc_magic->widget);
+}

+ 45 - 0
nfc_magic/scenes/nfc_magic_scene_new_key_input.c

@@ -0,0 +1,45 @@
+#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, "");
+}

+ 43 - 0
nfc_magic/scenes/nfc_magic_scene_not_magic.c

@@ -0,0 +1,43 @@
+#include "../nfc_magic_i.h"
+
+void nfc_magic_scene_not_magic_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_not_magic_on_enter(void* context) {
+    NfcMagic* nfc_magic = context;
+    Widget* widget = nfc_magic->widget;
+
+    notification_message(nfc_magic->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);
+
+    // Setup and start worker
+    view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewWidget);
+}
+
+bool nfc_magic_scene_not_magic_on_event(void* context, SceneManagerEvent event) {
+    NfcMagic* nfc_magic = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == GuiButtonTypeLeft) {
+            consumed = scene_manager_previous_scene(nfc_magic->scene_manager);
+        }
+    }
+    return consumed;
+}
+
+void nfc_magic_scene_not_magic_on_exit(void* context) {
+    NfcMagic* nfc_magic = context;
+
+    widget_reset(nfc_magic->widget);
+}

+ 95 - 0
nfc_magic/scenes/nfc_magic_scene_rekey.c

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

+ 50 - 0
nfc_magic/scenes/nfc_magic_scene_rekey_fail.c

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

+ 56 - 0
nfc_magic/scenes/nfc_magic_scene_start.c

@@ -0,0 +1,56 @@
+#include "../nfc_magic_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);
+}
+
+void nfc_magic_scene_start_on_enter(void* context) {
+    NfcMagic* nfc_magic = context;
+
+    Submenu* submenu = nfc_magic->submenu;
+    submenu_add_item(
+        submenu,
+        "Check Magic Tag",
+        SubmenuIndexCheck,
+        nfc_magic_scene_start_submenu_callback,
+        nfc_magic);
+    submenu_add_item(
+        submenu,
+        "Authenticate Gen4",
+        SubmenuIndexAuthenticateGen4,
+        nfc_magic_scene_start_submenu_callback,
+        nfc_magic);
+
+    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);
+}
+
+bool nfc_magic_scene_start_on_event(void* context, SceneManagerEvent event) {
+    NfcMagic* nfc_magic = 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);
+            consumed = true;
+        } else if(event.event == SubmenuIndexAuthenticateGen4) {
+            scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneKeyInput);
+        }
+    }
+
+    return consumed;
+}
+
+void nfc_magic_scene_start_on_exit(void* context) {
+    NfcMagic* nfc_magic = context;
+    submenu_reset(nfc_magic->submenu);
+}

+ 42 - 0
nfc_magic/scenes/nfc_magic_scene_success.c

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

+ 97 - 0
nfc_magic/scenes/nfc_magic_scene_wipe.c

@@ -0,0 +1,97 @@
+#include "../nfc_magic_i.h"
+
+enum {
+    NfcMagicSceneWipeStateCardSearch,
+    NfcMagicSceneWipeStateCardFound,
+};
+
+bool nfc_magic_wipe_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_wipe_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, NfcMagicSceneWipe);
+
+    if(state == NfcMagicSceneWipeStateCardSearch) {
+        popup_set_icon(nfc_magic->popup, 0, 8, &I_NFC_manual_60x50);
+        popup_set_text(
+            nfc_magic->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);
+}
+
+void nfc_magic_scene_wipe_on_enter(void* context) {
+    NfcMagic* nfc_magic = 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);
+}
+
+bool nfc_magic_scene_wipe_on_event(void* context, SceneManagerEvent event) {
+    NfcMagic* nfc_magic = 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) {
+            scene_manager_set_scene_state(
+                nfc_magic->scene_manager, NfcMagicSceneWipe, NfcMagicSceneWipeStateCardFound);
+            nfc_magic_scene_wipe_setup_view(nfc_magic);
+            consumed = true;
+        } else if(event.event == NfcMagicWorkerEventNoCardDetected) {
+            scene_manager_set_scene_state(
+                nfc_magic->scene_manager, NfcMagicSceneWipe, NfcMagicSceneWipeStateCardSearch);
+            nfc_magic_scene_wipe_setup_view(nfc_magic);
+            consumed = true;
+        }
+    }
+    return consumed;
+}
+
+void nfc_magic_scene_wipe_on_exit(void* context) {
+    NfcMagic* nfc_magic = context;
+
+    nfc_magic_worker_stop(nfc_magic->worker);
+    scene_manager_set_scene_state(
+        nfc_magic->scene_manager, NfcMagicSceneWipe, NfcMagicSceneWipeStateCardSearch);
+    // Clear view
+    popup_reset(nfc_magic->popup);
+
+    nfc_magic_blink_stop(nfc_magic);
+}

+ 41 - 0
nfc_magic/scenes/nfc_magic_scene_wipe_fail.c

@@ -0,0 +1,41 @@
+#include "../nfc_magic_i.h"
+
+void nfc_magic_scene_wipe_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_wipe_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, 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);
+
+    // Setup and start worker
+    view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewWidget);
+}
+
+bool nfc_magic_scene_wipe_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_previous_scene(nfc_magic->scene_manager);
+        }
+    }
+    return consumed;
+}
+
+void nfc_magic_scene_wipe_fail_on_exit(void* context) {
+    NfcMagic* nfc_magic = context;
+
+    widget_reset(nfc_magic->widget);
+}

+ 97 - 0
nfc_magic/scenes/nfc_magic_scene_write.c

@@ -0,0 +1,97 @@
+#include "../nfc_magic_i.h"
+
+enum {
+    NfcMagicSceneWriteStateCardSearch,
+    NfcMagicSceneWriteStateCardFound,
+};
+
+bool nfc_magic_write_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_write_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, NfcMagicSceneWrite);
+
+    if(state == NfcMagicSceneWriteStateCardSearch) {
+        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_write_on_enter(void* context) {
+    NfcMagic* nfc_magic = 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);
+}
+
+bool nfc_magic_scene_write_on_event(void* context, SceneManagerEvent event) {
+    NfcMagic* nfc_magic = 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) {
+            scene_manager_set_scene_state(
+                nfc_magic->scene_manager, NfcMagicSceneWrite, NfcMagicSceneWriteStateCardFound);
+            nfc_magic_scene_write_setup_view(nfc_magic);
+            consumed = true;
+        } else if(event.event == NfcMagicWorkerEventNoCardDetected) {
+            scene_manager_set_scene_state(
+                nfc_magic->scene_manager, NfcMagicSceneWrite, NfcMagicSceneWriteStateCardSearch);
+            nfc_magic_scene_write_setup_view(nfc_magic);
+            consumed = true;
+        }
+    }
+    return consumed;
+}
+
+void nfc_magic_scene_write_on_exit(void* context) {
+    NfcMagic* nfc_magic = context;
+
+    nfc_magic_worker_stop(nfc_magic->worker);
+    scene_manager_set_scene_state(
+        nfc_magic->scene_manager, NfcMagicSceneWrite, NfcMagicSceneWriteStateCardSearch);
+    // Clear view
+    popup_reset(nfc_magic->popup);
+
+    nfc_magic_blink_stop(nfc_magic);
+}

+ 64 - 0
nfc_magic/scenes/nfc_magic_scene_write_confirm.c

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

+ 58 - 0
nfc_magic/scenes/nfc_magic_scene_write_fail.c

@@ -0,0 +1,58 @@
+#include "../nfc_magic_i.h"
+
+void nfc_magic_scene_write_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_write_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, "Writing gone wrong!");
+    widget_add_string_multiline_element(
+        widget,
+        7,
+        17,
+        AlignLeft,
+        AlignTop,
+        FontSecondary,
+        "Not all sectors\nwere written\ncorrectly.");
+
+    widget_add_button_element(
+        widget, GuiButtonTypeLeft, "Finish", nfc_magic_scene_write_fail_widget_callback, nfc_magic);
+
+    // Setup and start worker
+    view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewWidget);
+}
+
+bool nfc_magic_scene_write_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_write_fail_on_exit(void* context) {
+    NfcMagic* nfc_magic = context;
+
+    widget_reset(nfc_magic->widget);
+}

+ 53 - 0
nfc_magic/scenes/nfc_magic_scene_wrong_card.c

@@ -0,0 +1,53 @@
+#include "../nfc_magic_i.h"
+
+void nfc_magic_scene_wrong_card_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_wrong_card_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, 73, 17, &I_DolphinCommon_56x48);
+    widget_add_string_element(
+        widget, 1, 4, AlignLeft, AlignTop, FontPrimary, "This is wrong card");
+    widget_add_string_multiline_element(
+        widget,
+        1,
+        17,
+        AlignLeft,
+        AlignTop,
+        FontSecondary,
+        "Writing this file is\nnot supported for\nthis magic card.");
+    widget_add_button_element(
+        widget, GuiButtonTypeLeft, "Retry", nfc_magic_scene_wrong_card_widget_callback, nfc_magic);
+
+    // Setup and start worker
+    view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewWidget);
+}
+
+bool nfc_magic_scene_wrong_card_on_event(void* context, SceneManagerEvent event) {
+    NfcMagic* nfc_magic = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == GuiButtonTypeLeft) {
+            consumed = scene_manager_previous_scene(nfc_magic->scene_manager);
+        }
+    }
+    return consumed;
+}
+
+void nfc_magic_scene_wrong_card_on_exit(void* context) {
+    NfcMagic* nfc_magic = context;
+
+    widget_reset(nfc_magic->widget);
+}