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

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

# Conflicts:
#	nfc_magic/application.fam
#	nfc_magic/lib/magic/protocols/gen4/gen4_poller.c
#	nfc_magic/lib/magic/protocols/gen4/gen4_poller_i.c
#	nfc_magic/scenes/nfc_magic_scene_key_input.c
Willy-JL 1 год назад
Родитель
Сommit
1ddcad9d54
31 измененных файлов с 2862 добавлено и 59 удалено
  1. 1 1
      nfc_magic/application.fam
  2. 26 12
      nfc_magic/lib/magic/nfc_magic_scanner.c
  3. 178 0
      nfc_magic/lib/magic/protocols/gen2/crypto1.c
  4. 45 0
      nfc_magic/lib/magic/protocols/gen2/crypto1.h
  5. 569 0
      nfc_magic/lib/magic/protocols/gen2/gen2_poller.c
  6. 88 0
      nfc_magic/lib/magic/protocols/gen2/gen2_poller.h
  7. 633 0
      nfc_magic/lib/magic/protocols/gen2/gen2_poller_i.c
  8. 139 0
      nfc_magic/lib/magic/protocols/gen2/gen2_poller_i.h
  9. 1 1
      nfc_magic/lib/magic/protocols/gen4/gen4_poller.c
  10. 6 6
      nfc_magic/lib/magic/protocols/gen4/gen4_poller_i.c
  11. 2 0
      nfc_magic/lib/magic/protocols/nfc_magic_protocols.c
  12. 2 0
      nfc_magic/lib/magic/protocols/nfc_magic_protocols.h
  13. 19 2
      nfc_magic/nfc_magic_app.c
  14. 37 0
      nfc_magic/nfc_magic_app_i.h
  15. 6 1
      nfc_magic/scenes/nfc_magic_scene_config.h
  16. 15 1
      nfc_magic/scenes/nfc_magic_scene_file_select.c
  17. 55 0
      nfc_magic/scenes/nfc_magic_scene_gen2_menu.c
  18. 120 0
      nfc_magic/scenes/nfc_magic_scene_gen2_write_check.c
  19. 5 5
      nfc_magic/scenes/nfc_magic_scene_gen4_actions_menu.c
  20. 14 14
      nfc_magic/scenes/nfc_magic_scene_gen4_set_default_cfg.c
  21. 1 1
      nfc_magic/scenes/nfc_magic_scene_gen4_set_direct_write_block_0_mode.c
  22. 1 1
      nfc_magic/scenes/nfc_magic_scene_gen4_set_shd_mode.c
  23. 1 1
      nfc_magic/scenes/nfc_magic_scene_key_input.c
  24. 8 2
      nfc_magic/scenes/nfc_magic_scene_magic_info.c
  25. 311 0
      nfc_magic/scenes/nfc_magic_scene_mf_classic_dict_attack.c
  26. 65 0
      nfc_magic/scenes/nfc_magic_scene_mf_classic_menu.c
  27. 121 0
      nfc_magic/scenes/nfc_magic_scene_mf_classic_write_check.c
  28. 48 6
      nfc_magic/scenes/nfc_magic_scene_wipe.c
  29. 50 5
      nfc_magic/scenes/nfc_magic_scene_write.c
  30. 245 0
      nfc_magic/views/dict_attack.c
  31. 50 0
      nfc_magic/views/dict_attack.h

+ 1 - 1
nfc_magic/application.fam

@@ -10,7 +10,7 @@ App(
     ],
     stack_size=4 * 1024,
     fap_description="Application for writing to NFC tags with modifiable sector 0",
-    fap_version="1.5",
+    fap_version="1.6",
     fap_icon="125_10px.png",
     fap_category="NFC",
     fap_private_libs=[

+ 26 - 12
nfc_magic/lib/magic/nfc_magic_scanner.c

@@ -1,6 +1,7 @@
 #include "nfc_magic_scanner.h"
 
 #include "protocols/gen1a/gen1a_poller.h"
+#include "protocols/gen2/gen2_poller.h"
 #include "protocols/gen4/gen4_poller.h"
 #include <nfc/nfc_poller.h>
 
@@ -65,20 +66,33 @@ static int32_t nfc_magic_scanner_worker(void* context) {
     furi_assert(instance->session_state == NfcMagicScannerSessionStateActive);
 
     while(instance->session_state == NfcMagicScannerSessionStateActive) {
-        if(instance->current_protocol == NfcMagicProtocolGen1) {
-            instance->magic_protocol_detected = gen1a_poller_detect(instance->nfc);
-        } else if(instance->current_protocol == NfcMagicProtocolGen4) {
-            Gen4PollerError error = gen4_poller_detect(instance->nfc, instance->gen4_password);
-            if(error == Gen4PollerErrorProtocol) {
-                NfcMagicScannerEvent event = {
-                    .type = NfcMagicScannerEventTypeDetectedNotMagic,
-                };
-                instance->callback(event, instance->context);
-                break;
-            } else {
+        do {
+            if(instance->current_protocol == NfcMagicProtocolGen1) {
+                instance->magic_protocol_detected = gen1a_poller_detect(instance->nfc);
+                if(instance->magic_protocol_detected) {
+                    break;
+                }
+            } else if(instance->current_protocol == NfcMagicProtocolGen4) {
+                Gen4PollerError error = gen4_poller_detect(instance->nfc, instance->gen4_password);
                 instance->magic_protocol_detected = (error == Gen4PollerErrorNone);
+                if(instance->magic_protocol_detected) {
+                    break;
+                }
+            } else if(instance->current_protocol == NfcMagicProtocolGen2) {
+                Gen2PollerError error = gen2_poller_detect(instance->nfc);
+                instance->magic_protocol_detected = (error == Gen2PollerErrorNone);
+                if(instance->magic_protocol_detected) {
+                    break;
+                }
+            } else if(instance->current_protocol == NfcMagicProtocolClassic) {
+                NfcPoller* poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfClassic);
+                instance->magic_protocol_detected = nfc_poller_detect(poller);
+                nfc_poller_free(poller);
+                if(instance->magic_protocol_detected) {
+                    break;
+                }
             }
-        }
+        } while(false);
 
         if(instance->magic_protocol_detected) {
             NfcMagicScannerEvent event = {

+ 178 - 0
nfc_magic/lib/magic/protocols/gen2/crypto1.c

@@ -0,0 +1,178 @@
+#include "crypto1.h"
+
+#include <lib/nfc/helpers/nfc_util.h>
+#include <bit_lib.h>
+#include <furi.h>
+
+// Algorithm from https://github.com/RfidResearchGroup/proxmark3.git
+
+#define SWAPENDIAN(x) \
+    ((x) = ((x) >> 8 & 0xff00ff) | ((x)&0xff00ff) << 8, (x) = (x) >> 16 | (x) << 16)
+#define LF_POLY_ODD (0x29CE5C)
+#define LF_POLY_EVEN (0x870804)
+
+#define BEBIT(x, n) FURI_BIT(x, (n) ^ 24)
+
+Crypto1* crypto1_alloc() {
+    Crypto1* instance = malloc(sizeof(Crypto1));
+
+    return instance;
+}
+
+void crypto1_free(Crypto1* instance) {
+    furi_assert(instance);
+
+    free(instance);
+}
+
+void crypto1_reset(Crypto1* crypto1) {
+    furi_assert(crypto1);
+    crypto1->even = 0;
+    crypto1->odd = 0;
+}
+
+void crypto1_init(Crypto1* crypto1, uint64_t key) {
+    furi_assert(crypto1);
+    crypto1->even = 0;
+    crypto1->odd = 0;
+    for(int8_t i = 47; i > 0; i -= 2) {
+        crypto1->odd = crypto1->odd << 1 | FURI_BIT(key, (i - 1) ^ 7);
+        crypto1->even = crypto1->even << 1 | FURI_BIT(key, i ^ 7);
+    }
+}
+
+static uint32_t crypto1_filter(uint32_t in) {
+    uint32_t out = 0;
+    out = 0xf22c0 >> (in & 0xf) & 16;
+    out |= 0x6c9c0 >> (in >> 4 & 0xf) & 8;
+    out |= 0x3c8b0 >> (in >> 8 & 0xf) & 4;
+    out |= 0x1e458 >> (in >> 12 & 0xf) & 2;
+    out |= 0x0d938 >> (in >> 16 & 0xf) & 1;
+    return FURI_BIT(0xEC57E80A, out);
+}
+
+uint8_t crypto1_bit(Crypto1* crypto1, uint8_t in, int is_encrypted) {
+    furi_assert(crypto1);
+    uint8_t out = crypto1_filter(crypto1->odd);
+    uint32_t feed = out & (!!is_encrypted);
+    feed ^= !!in;
+    feed ^= LF_POLY_ODD & crypto1->odd;
+    feed ^= LF_POLY_EVEN & crypto1->even;
+    crypto1->even = crypto1->even << 1 | (nfc_util_even_parity32(feed));
+
+    FURI_SWAP(crypto1->odd, crypto1->even);
+    return out;
+}
+
+uint8_t crypto1_byte(Crypto1* crypto1, uint8_t in, int is_encrypted) {
+    furi_assert(crypto1);
+    uint8_t out = 0;
+    for(uint8_t i = 0; i < 8; i++) {
+        out |= crypto1_bit(crypto1, FURI_BIT(in, i), is_encrypted) << i;
+    }
+    return out;
+}
+
+uint32_t crypto1_word(Crypto1* crypto1, uint32_t in, int is_encrypted) {
+    furi_assert(crypto1);
+    uint32_t out = 0;
+    for(uint8_t i = 0; i < 32; i++) {
+        out |= (uint32_t)crypto1_bit(crypto1, BEBIT(in, i), is_encrypted) << (24 ^ i);
+    }
+    return out;
+}
+
+uint32_t prng_successor(uint32_t x, uint32_t n) {
+    SWAPENDIAN(x);
+    while(n--) x = x >> 1 | (x >> 16 ^ x >> 18 ^ x >> 19 ^ x >> 21) << 31;
+
+    return SWAPENDIAN(x);
+}
+
+void crypto1_decrypt(Crypto1* crypto, const BitBuffer* buff, BitBuffer* out) {
+    furi_assert(crypto);
+    furi_assert(buff);
+    furi_assert(out);
+
+    size_t bits = bit_buffer_get_size(buff);
+    bit_buffer_set_size(out, bits);
+    const uint8_t* encrypted_data = bit_buffer_get_data(buff);
+    if(bits < 8) {
+        uint8_t decrypted_byte = 0;
+        uint8_t encrypted_byte = encrypted_data[0];
+        decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_byte, 0)) << 0;
+        decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_byte, 1)) << 1;
+        decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_byte, 2)) << 2;
+        decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_byte, 3)) << 3;
+        bit_buffer_set_byte(out, 0, decrypted_byte);
+    } else {
+        for(size_t i = 0; i < bits / 8; i++) {
+            uint8_t decrypted_byte = crypto1_byte(crypto, 0, 0) ^ encrypted_data[i];
+            bit_buffer_set_byte(out, i, decrypted_byte);
+        }
+    }
+}
+
+void crypto1_encrypt(Crypto1* crypto, uint8_t* keystream, const BitBuffer* buff, BitBuffer* out) {
+    furi_assert(crypto);
+    furi_assert(buff);
+    furi_assert(out);
+
+    size_t bits = bit_buffer_get_size(buff);
+    bit_buffer_set_size(out, bits);
+    const uint8_t* plain_data = bit_buffer_get_data(buff);
+    if(bits < 8) {
+        uint8_t encrypted_byte = 0;
+        for(size_t i = 0; i < bits; i++) {
+            encrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(plain_data[0], i)) << i;
+        }
+        bit_buffer_set_byte(out, 0, encrypted_byte);
+    } else {
+        for(size_t i = 0; i < bits / 8; i++) {
+            uint8_t encrypted_byte = crypto1_byte(crypto, keystream ? keystream[i] : 0, 0) ^
+                                     plain_data[i];
+            bool parity_bit =
+                ((crypto1_filter(crypto->odd) ^ nfc_util_odd_parity8(plain_data[i])) & 0x01);
+            bit_buffer_set_byte_with_parity(out, i, encrypted_byte, parity_bit);
+        }
+    }
+}
+
+void crypto1_encrypt_reader_nonce(
+    Crypto1* crypto,
+    uint64_t key,
+    uint32_t cuid,
+    uint8_t* nt,
+    uint8_t* nr,
+    BitBuffer* out,
+    bool is_nested) {
+    furi_assert(crypto);
+    furi_assert(nt);
+    furi_assert(nr);
+    furi_assert(out);
+
+    bit_buffer_set_size_bytes(out, 8);
+    uint32_t nt_num = bit_lib_bytes_to_num_be(nt, sizeof(uint32_t));
+
+    crypto1_init(crypto, key);
+    if(is_nested) {
+        nt_num = crypto1_word(crypto, nt_num ^ cuid, 1) ^ nt_num;
+    } else {
+        crypto1_word(crypto, nt_num ^ cuid, 0);
+    }
+
+    for(size_t i = 0; i < 4; i++) {
+        uint8_t byte = crypto1_byte(crypto, nr[i], 0) ^ nr[i];
+        bool parity_bit = ((crypto1_filter(crypto->odd) ^ nfc_util_odd_parity8(nr[i])) & 0x01);
+        bit_buffer_set_byte_with_parity(out, i, byte, parity_bit);
+        nr[i] = byte;
+    }
+
+    nt_num = prng_successor(nt_num, 32);
+    for(size_t i = 4; i < 8; i++) {
+        nt_num = prng_successor(nt_num, 8);
+        uint8_t byte = crypto1_byte(crypto, 0, 0) ^ (uint8_t)(nt_num);
+        bool parity_bit = ((crypto1_filter(crypto->odd) ^ nfc_util_odd_parity8(nt_num)) & 0x01);
+        bit_buffer_set_byte_with_parity(out, i, byte, parity_bit);
+    }
+}

+ 45 - 0
nfc_magic/lib/magic/protocols/gen2/crypto1.h

@@ -0,0 +1,45 @@
+#pragma once
+
+#include <toolbox/bit_buffer.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct {
+    uint32_t odd;
+    uint32_t even;
+} Crypto1;
+
+Crypto1* crypto1_alloc();
+
+void crypto1_free(Crypto1* instance);
+
+void crypto1_reset(Crypto1* crypto1);
+
+void crypto1_init(Crypto1* crypto1, uint64_t key);
+
+uint8_t crypto1_bit(Crypto1* crypto1, uint8_t in, int is_encrypted);
+
+uint8_t crypto1_byte(Crypto1* crypto1, uint8_t in, int is_encrypted);
+
+uint32_t crypto1_word(Crypto1* crypto1, uint32_t in, int is_encrypted);
+
+void crypto1_decrypt(Crypto1* crypto, const BitBuffer* buff, BitBuffer* out);
+
+void crypto1_encrypt(Crypto1* crypto, uint8_t* keystream, const BitBuffer* buff, BitBuffer* out);
+
+void crypto1_encrypt_reader_nonce(
+    Crypto1* crypto,
+    uint64_t key,
+    uint32_t cuid,
+    uint8_t* nt,
+    uint8_t* nr,
+    BitBuffer* out,
+    bool is_nested);
+
+uint32_t prng_successor(uint32_t x, uint32_t n);
+
+#ifdef __cplusplus
+}
+#endif

+ 569 - 0
nfc_magic/lib/magic/protocols/gen2/gen2_poller.c

@@ -0,0 +1,569 @@
+#include "gen2_poller_i.h"
+#include <nfc/helpers/nfc_data_generator.h>
+
+#include <furi/furi.h>
+
+#define GEN2_POLLER_THREAD_FLAG_DETECTED (1U << 0)
+
+#define TAG "GEN2"
+
+typedef NfcCommand (*Gen2PollerStateHandler)(Gen2Poller* instance);
+
+typedef struct {
+    NfcPoller* poller;
+    BitBuffer* tx_buffer;
+    BitBuffer* rx_buffer;
+    FuriThreadId thread_id;
+    bool detected;
+    Gen2PollerError error;
+} Gen2PollerDetectContext;
+
+// Array of known Gen2 ATS responses
+// 0978009102DABC1910F005 - flavour 2
+// 0978009102DABC1910F005 - flavour 4
+// 0D780071028849A13020150608563D - flavour 6
+// Other flavours can't be detected other than by just trying to write to block 0
+const uint8_t GEN2_ATS[3][16] = {
+    {0x09, 0x78, 0x00, 0x91, 0x02, 0xDA, 0xBC, 0x19, 0x10, 0xF0, 0x05},
+    {0x09, 0x78, 0x00, 0x91, 0x02, 0xDA, 0xBC, 0x19, 0x10, 0xF0, 0x05},
+    {0x0D, 0x78, 0x00, 0x71, 0x02, 0x88, 0x49, 0xA1, 0x30, 0x20, 0x15, 0x06, 0x08, 0x56, 0x3D}};
+
+static MfClassicBlock gen2_poller_default_block_0 = {
+    .data =
+        {0x00,
+         0x01,
+         0x02,
+         0x03,
+         0x00, // BCC - IMPORTANT
+         0x08, // SAK
+         0x04, // ATQA0
+         0x00, // ATQA1
+         0x00,
+         0x00,
+         0x00,
+         0x00,
+         0x00,
+         0x00,
+         0x00,
+         0x00},
+};
+
+static MfClassicBlock gen2_poller_default_empty_block = {
+    .data =
+        {0x00,
+         0x00,
+         0x00,
+         0x00,
+         0x00,
+         0x00,
+         0x00,
+         0x00,
+         0x00,
+         0x00,
+         0x00,
+         0x00,
+         0x00,
+         0x00,
+         0x00,
+         0x00},
+};
+
+static MfClassicBlock gen2_poller_default_sector_trailer_block = {
+    .data =
+        {0xFF,
+         0xFF,
+         0xFF,
+         0xFF,
+         0xFF,
+         0xFF,
+         0xFF,
+         0x07,
+         0x80,
+         0x69,
+         0xFF,
+         0xFF,
+         0xFF,
+         0xFF,
+         0xFF,
+         0xFF},
+};
+
+Gen2Poller* gen2_poller_alloc(Nfc* nfc) {
+    Gen2Poller* instance = malloc(sizeof(Gen2Poller));
+    instance->poller = nfc_poller_alloc(nfc, NfcProtocolIso14443_3a);
+    instance->data = mf_classic_alloc();
+    instance->crypto = crypto1_alloc();
+    instance->tx_plain_buffer = bit_buffer_alloc(GEN2_POLLER_MAX_BUFFER_SIZE);
+    instance->tx_encrypted_buffer = bit_buffer_alloc(GEN2_POLLER_MAX_BUFFER_SIZE);
+    instance->rx_plain_buffer = bit_buffer_alloc(GEN2_POLLER_MAX_BUFFER_SIZE);
+    instance->rx_encrypted_buffer = bit_buffer_alloc(GEN2_POLLER_MAX_BUFFER_SIZE);
+    instance->card_state = Gen2CardStateLost;
+
+    instance->gen2_event.data = &instance->gen2_event_data;
+
+    instance->mode_ctx.write_ctx.mfc_data_source = malloc(sizeof(MfClassicData));
+    instance->mode_ctx.write_ctx.mfc_data_target = malloc(sizeof(MfClassicData));
+
+    instance->mode_ctx.write_ctx.need_halt_before_write = true;
+
+    return instance;
+}
+
+void gen2_poller_free(Gen2Poller* instance) {
+    furi_assert(instance);
+    furi_assert(instance->data);
+    furi_assert(instance->crypto);
+    furi_assert(instance->tx_plain_buffer);
+    furi_assert(instance->rx_plain_buffer);
+    furi_assert(instance->tx_encrypted_buffer);
+    furi_assert(instance->rx_encrypted_buffer);
+
+    nfc_poller_free(instance->poller);
+    mf_classic_free(instance->data);
+    crypto1_free(instance->crypto);
+    bit_buffer_free(instance->tx_plain_buffer);
+    bit_buffer_free(instance->rx_plain_buffer);
+    bit_buffer_free(instance->tx_encrypted_buffer);
+    bit_buffer_free(instance->rx_encrypted_buffer);
+
+    free(instance->mode_ctx.write_ctx.mfc_data_source);
+    free(instance->mode_ctx.write_ctx.mfc_data_target);
+
+    free(instance);
+}
+
+NfcCommand gen2_poller_detect_callback(NfcGenericEvent event, void* context) {
+    furi_assert(context);
+    furi_assert(event.protocol == NfcProtocolIso14443_3a);
+    furi_assert(event.event_data);
+    furi_assert(event.instance);
+
+    NfcCommand command = NfcCommandStop;
+    Gen2PollerDetectContext* detect_ctx = context;
+    Iso14443_3aPoller* iso3_poller = event.instance;
+    Iso14443_3aPollerEvent* iso3_event = event.event_data;
+    detect_ctx->error = Gen2PollerErrorTimeout;
+
+    bit_buffer_reset(detect_ctx->tx_buffer);
+    bit_buffer_append_byte(detect_ctx->tx_buffer, GEN2_CMD_READ_ATS);
+    bit_buffer_append_byte(detect_ctx->tx_buffer, GEN2_FSDI_256 << 4);
+
+    if(iso3_event->type == Iso14443_3aPollerEventTypeReady) {
+        do {
+            const Iso14443_3aError iso14443_3a_error = iso14443_3a_poller_send_standard_frame(
+                iso3_poller, detect_ctx->tx_buffer, detect_ctx->rx_buffer, GEN2_POLLER_MAX_FWT);
+
+            if(iso14443_3a_error != Iso14443_3aErrorNone &&
+               iso14443_3a_error != Iso14443_3aErrorWrongCrc) {
+                FURI_LOG_E(TAG, "ATS request failed");
+                detect_ctx->error = Gen2PollerErrorProtocol;
+                break;
+
+            } else {
+                FURI_LOG_D(TAG, "ATS request succeeded:");
+                // Check against known ATS responses
+                for(size_t i = 0; i < COUNT_OF(GEN2_ATS); i++) {
+                    if(memcmp(
+                           bit_buffer_get_data(detect_ctx->rx_buffer),
+                           GEN2_ATS[i],
+                           sizeof(GEN2_ATS[i])) == 0) {
+                        detect_ctx->error = Gen2PollerErrorNone;
+                        break;
+                    }
+                }
+            }
+        } while(false);
+    } else if(iso3_event->type == Iso14443_3aPollerEventTypeError) {
+        detect_ctx->error = Gen2PollerErrorTimeout;
+    }
+    furi_thread_flags_set(detect_ctx->thread_id, GEN2_POLLER_THREAD_FLAG_DETECTED);
+
+    return command;
+}
+
+Gen2PollerError gen2_poller_detect(Nfc* nfc) {
+    furi_assert(nfc);
+
+    Gen2PollerDetectContext detect_ctx = {
+        .poller = nfc_poller_alloc(nfc, NfcProtocolIso14443_3a),
+        .tx_buffer = bit_buffer_alloc(GEN2_POLLER_MAX_BUFFER_SIZE),
+        .rx_buffer = bit_buffer_alloc(GEN2_POLLER_MAX_BUFFER_SIZE),
+        .thread_id = furi_thread_get_current_id(),
+        .detected = false,
+        .error = Gen2PollerErrorNone,
+    };
+
+    nfc_poller_start(detect_ctx.poller, gen2_poller_detect_callback, &detect_ctx);
+    uint32_t flags =
+        furi_thread_flags_wait(GEN2_POLLER_THREAD_FLAG_DETECTED, FuriFlagWaitAny, FuriWaitForever);
+    if(flags & GEN2_POLLER_THREAD_FLAG_DETECTED) {
+        furi_thread_flags_clear(GEN2_POLLER_THREAD_FLAG_DETECTED);
+    }
+    nfc_poller_stop(detect_ctx.poller);
+
+    bit_buffer_free(detect_ctx.tx_buffer);
+    bit_buffer_free(detect_ctx.rx_buffer);
+    nfc_poller_free(detect_ctx.poller);
+
+    return detect_ctx.error;
+}
+
+NfcCommand gen2_poller_idle_handler(Gen2Poller* instance) {
+    furi_assert(instance);
+
+    NfcCommand command = NfcCommandContinue;
+
+    instance->mode_ctx.write_ctx.current_block = 0;
+    instance->gen2_event.type = Gen2PollerEventTypeDetected;
+    command = instance->callback(instance->gen2_event, instance->context);
+    instance->state = Gen2PollerStateRequestMode;
+
+    return command;
+}
+
+NfcCommand gen2_poller_request_mode_handler(Gen2Poller* instance) {
+    furi_assert(instance);
+
+    NfcCommand command = NfcCommandContinue;
+
+    instance->gen2_event.type = Gen2PollerEventTypeRequestMode;
+    command = instance->callback(instance->gen2_event, instance->context);
+    instance->mode = instance->gen2_event_data.poller_mode.mode;
+    if(instance->gen2_event_data.poller_mode.mode == Gen2PollerModeWipe) {
+        instance->state = Gen2PollerStateWriteTargetDataRequest;
+    } else {
+        instance->state = Gen2PollerStateWriteSourceDataRequest;
+    }
+
+    return command;
+}
+
+NfcCommand gen2_poller_write_source_data_request_handler(Gen2Poller* instance) {
+    NfcCommand command = NfcCommandContinue;
+
+    instance->gen2_event.type = Gen2PollerEventTypeRequestDataToWrite;
+    command = instance->callback(instance->gen2_event, instance->context);
+    memcpy(
+        instance->mode_ctx.write_ctx.mfc_data_source,
+        instance->gen2_event_data.data_to_write.mfc_data,
+        sizeof(MfClassicData));
+    instance->state = Gen2PollerStateWriteTargetDataRequest;
+
+    return command;
+}
+
+NfcCommand gen2_poller_write_target_data_request_handler(Gen2Poller* instance) {
+    NfcCommand command = NfcCommandContinue;
+
+    instance->gen2_event.type = Gen2PollerEventTypeRequestTargetData;
+    command = instance->callback(instance->gen2_event, instance->context);
+    memcpy(
+        instance->mode_ctx.write_ctx.mfc_data_target,
+        instance->gen2_event_data.target_data.mfc_data,
+        sizeof(MfClassicData));
+    if(instance->mode == Gen2PollerModeWipe) {
+        instance->state = Gen2PollerStateWipe;
+    } else {
+        instance->state = Gen2PollerStateWrite;
+    }
+
+    return command;
+}
+
+Gen2PollerError gen2_poller_write_block_handler(
+    Gen2Poller* instance,
+    uint8_t block_num,
+    MfClassicBlock* block) {
+    furi_assert(instance);
+
+    Gen2PollerError error = Gen2PollerErrorNone;
+    Gen2PollerWriteContext* write_ctx = &instance->mode_ctx.write_ctx;
+    MfClassicKey auth_key = write_ctx->auth_key;
+
+    do {
+        // Compare the target and source data
+        if(memcmp(block->data, write_ctx->mfc_data_target->block[block_num].data, 16) == 0) {
+            FURI_LOG_D(TAG, "Block %d is the same, skipping", block_num);
+            break;
+        }
+
+        // Reauth if necessary
+        if(write_ctx->need_halt_before_write) {
+            FURI_LOG_D(TAG, "Auth before writing block %d", write_ctx->current_block);
+            error = gen2_poller_auth(
+                instance, write_ctx->current_block, &auth_key, write_ctx->write_key, NULL);
+            if(error != Gen2PollerErrorNone) {
+                FURI_LOG_D(
+                    TAG, "Failed to auth to block %d for writing", write_ctx->current_block);
+                break;
+            }
+        }
+
+        // Write the block
+        error = gen2_poller_write_block(instance, write_ctx->current_block, block);
+        if(error != Gen2PollerErrorNone) {
+            FURI_LOG_D(TAG, "Failed to write block %d", write_ctx->current_block);
+            break;
+        }
+    } while(false);
+    FURI_LOG_D(TAG, "Block %d finished, halting", write_ctx->current_block);
+    gen2_poller_halt(instance);
+    return error;
+}
+
+NfcCommand gen2_poller_wipe_handler(Gen2Poller* instance) {
+    NfcCommand command = NfcCommandContinue;
+    Gen2PollerError error = Gen2PollerErrorNone;
+    Gen2PollerWriteContext* write_ctx = &instance->mode_ctx.write_ctx;
+    uint8_t block_num = write_ctx->current_block;
+
+    do {
+        // Check whether the ACs for that block are known in target data
+        if(!mf_classic_is_block_read(
+               write_ctx->mfc_data_target,
+               mf_classic_get_sector_trailer_num_by_block(block_num))) {
+            FURI_LOG_E(TAG, "Sector trailer for block %d not present in target data", block_num);
+            break;
+        }
+
+        // Check whether ACs need to be reset and whether they can be reset
+        if(!gen2_poller_can_write_block(write_ctx->mfc_data_target, block_num)) {
+            if(!gen2_can_reset_access_conditions(write_ctx->mfc_data_target, block_num)) {
+                FURI_LOG_E(TAG, "Block %d cannot be written", block_num);
+                break;
+            } else {
+                FURI_LOG_D(TAG, "Resetting ACs for block %d", block_num);
+                // Generate a block with old keys and default ACs (0xFF, 0x07, 0x80)
+                MfClassicBlock block;
+                memset(&block, 0, sizeof(block));
+                memcpy(block.data, write_ctx->mfc_data_target->block[block_num].data, 16);
+                memcpy(block.data + 6, "\xFF\x07\x80", 3);
+
+                error = gen2_poller_write_block_handler(instance, block_num, &block);
+                if(error != Gen2PollerErrorNone) {
+                    FURI_LOG_E(TAG, "Failed to reset ACs for block %d", block_num);
+                    break;
+                } else {
+                    FURI_LOG_D(TAG, "ACs for block %d reset", block_num);
+                    memcpy(write_ctx->mfc_data_target->block[block_num].data, block.data, 16);
+                }
+            }
+        }
+
+        // Figure out which key to use for writing
+        write_ctx->write_key =
+            gen2_poller_get_key_type_to_write(write_ctx->mfc_data_target, block_num);
+
+        // Get the key to use for writing from the target data
+        MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(
+            write_ctx->mfc_data_target, mf_classic_get_sector_by_block(block_num));
+        if(write_ctx->write_key == MfClassicKeyTypeA) {
+            write_ctx->auth_key = sec_tr->key_a;
+        } else {
+            write_ctx->auth_key = sec_tr->key_b;
+        }
+
+        // Write the default block depending on the block type
+        if(block_num == 0) {
+            error =
+                gen2_poller_write_block_handler(instance, block_num, &gen2_poller_default_block_0);
+        } else if(mf_classic_is_sector_trailer(block_num)) {
+            error = gen2_poller_write_block_handler(
+                instance, block_num, &gen2_poller_default_sector_trailer_block);
+        } else {
+            error = gen2_poller_write_block_handler(
+                instance, block_num, &gen2_poller_default_empty_block);
+        }
+        if(error != Gen2PollerErrorNone) {
+            FURI_LOG_E(TAG, "Couldn't write block %d", block_num);
+        }
+    } while(false);
+
+    write_ctx->current_block++;
+
+    if(error != Gen2PollerErrorNone) {
+        FURI_LOG_D(TAG, "Error occurred: %d", error);
+    } else if(
+        write_ctx->current_block ==
+        mf_classic_get_total_block_num(write_ctx->mfc_data_target->type)) {
+        instance->state = Gen2PollerStateSuccess;
+    }
+
+    return command;
+}
+
+NfcCommand gen2_poller_write_handler(Gen2Poller* instance) {
+    NfcCommand command = NfcCommandContinue;
+    Gen2PollerError error = Gen2PollerErrorNone;
+    Gen2PollerWriteContext* write_ctx = &instance->mode_ctx.write_ctx;
+    uint8_t block_num = write_ctx->current_block;
+
+    do {
+        // Check whether the block is present in the source data
+        if(!mf_classic_is_block_read(write_ctx->mfc_data_source, block_num)) {
+            // FURI_LOG_E(TAG, "Block %d not present in source data", block_num);
+            break;
+        }
+
+        // Check whether the ACs for that block are known in target data
+        if(!mf_classic_is_block_read(
+               write_ctx->mfc_data_target,
+               mf_classic_get_sector_trailer_num_by_block(block_num))) {
+            FURI_LOG_E(TAG, "Sector trailer for block %d not present in target data", block_num);
+            break;
+        }
+
+        // Check whether ACs need to be reset and whether they can be reset
+        if(!gen2_poller_can_write_block(write_ctx->mfc_data_target, block_num)) {
+            if(!gen2_can_reset_access_conditions(write_ctx->mfc_data_target, block_num)) {
+                FURI_LOG_E(TAG, "Block %d cannot be written", block_num);
+                break;
+            } else {
+                FURI_LOG_D(TAG, "Resetting ACs for block %d", block_num);
+                // Generate a block with old keys and default ACs (0xFF, 0x07, 0x80)
+                MfClassicBlock block;
+                memset(&block, 0, sizeof(block));
+                memcpy(block.data, write_ctx->mfc_data_target->block[block_num].data, 16);
+                memcpy(block.data + 6, "\xFF\x07\x80", 3);
+
+                error = gen2_poller_write_block_handler(instance, block_num, &block);
+                if(error != Gen2PollerErrorNone) {
+                    FURI_LOG_E(TAG, "Failed to reset ACs for block %d", block_num);
+                    break;
+                } else {
+                    FURI_LOG_D(TAG, "ACs for block %d reset", block_num);
+                    memcpy(write_ctx->mfc_data_target->block[block_num].data, block.data, 16);
+                }
+            }
+        }
+
+        // Figure out which key to use for writing
+        write_ctx->write_key =
+            gen2_poller_get_key_type_to_write(write_ctx->mfc_data_target, block_num);
+
+        // Get the key to use for writing from the target data
+        MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(
+            write_ctx->mfc_data_target, mf_classic_get_sector_by_block(block_num));
+        if(write_ctx->write_key == MfClassicKeyTypeA) {
+            write_ctx->auth_key = sec_tr->key_a;
+        } else {
+            write_ctx->auth_key = sec_tr->key_b;
+        }
+
+        // Write the block
+        error = gen2_poller_write_block_handler(
+            instance, block_num, &write_ctx->mfc_data_source->block[block_num]);
+        if(error != Gen2PollerErrorNone) {
+            FURI_LOG_E(TAG, "Couldn't write block %d", block_num);
+        }
+    } while(false);
+    write_ctx->current_block++;
+
+    if(error != Gen2PollerErrorNone) {
+        FURI_LOG_D(TAG, "Error occurred: %d", error);
+    } else if(
+        write_ctx->current_block ==
+        mf_classic_get_total_block_num(write_ctx->mfc_data_source->type)) {
+        instance->state = Gen2PollerStateSuccess;
+    }
+
+    return command;
+}
+
+NfcCommand gen2_poller_success_handler(Gen2Poller* instance) {
+    furi_assert(instance);
+
+    NfcCommand command = NfcCommandContinue;
+
+    instance->gen2_event.type = Gen2PollerEventTypeSuccess;
+    command = instance->callback(instance->gen2_event, instance->context);
+    instance->state = Gen2PollerStateIdle;
+
+    return command;
+}
+
+NfcCommand gen2_poller_fail_handler(Gen2Poller* instance) {
+    furi_assert(instance);
+
+    NfcCommand command = NfcCommandContinue;
+
+    instance->gen2_event.type = Gen2PollerEventTypeFail;
+    command = instance->callback(instance->gen2_event, instance->context);
+    instance->state = Gen2PollerStateIdle;
+
+    return command;
+}
+
+static const Gen2PollerStateHandler gen2_poller_state_handlers[Gen2PollerStateNum] = {
+    [Gen2PollerStateIdle] = gen2_poller_idle_handler,
+    [Gen2PollerStateRequestMode] = gen2_poller_request_mode_handler,
+    [Gen2PollerStateWipe] = gen2_poller_wipe_handler,
+    [Gen2PollerStateWriteSourceDataRequest] = gen2_poller_write_source_data_request_handler,
+    [Gen2PollerStateWriteTargetDataRequest] = gen2_poller_write_target_data_request_handler,
+    [Gen2PollerStateWrite] = gen2_poller_write_handler,
+    [Gen2PollerStateSuccess] = gen2_poller_success_handler,
+    [Gen2PollerStateFail] = gen2_poller_fail_handler,
+};
+
+NfcCommand gen2_poller_callback(NfcGenericEvent event, void* context) {
+    furi_assert(context);
+    furi_assert(event.protocol == NfcProtocolIso14443_3a);
+    furi_assert(event.event_data);
+    furi_assert(event.instance);
+
+    NfcCommand command = NfcCommandContinue;
+    Gen2Poller* instance = context;
+    instance->iso3_poller = event.instance;
+    Iso14443_3aPollerEvent* iso3_event = event.event_data;
+
+    if(iso3_event->type == Iso14443_3aPollerEventTypeReady) {
+        command = gen2_poller_state_handlers[instance->state](instance);
+    }
+
+    return command;
+}
+
+void gen2_poller_start(Gen2Poller* instance, Gen2PollerCallback callback, void* context) {
+    furi_assert(instance);
+    furi_assert(callback);
+
+    instance->callback = callback;
+    instance->context = context;
+
+    nfc_poller_start(instance->poller, gen2_poller_callback, instance);
+    return;
+}
+
+void gen2_poller_stop(Gen2Poller* instance) {
+    furi_assert(instance);
+
+    FURI_LOG_D(TAG, "Stopping Gen2 poller");
+    nfc_poller_stop(instance->poller);
+    return;
+}
+
+Gen2PollerWriteProblem gen2_poller_can_write_everything(NfcDevice* device) {
+    furi_assert(device);
+
+    Gen2PollerWriteProblem problem = Gen2PollerWriteProblemNone;
+    Gen2PollerWriteProblem problem_next = Gen2PollerWriteProblemNone;
+    const MfClassicData* mfc_data = nfc_device_get_data(device, NfcProtocolMfClassic);
+
+    if(mfc_data) {
+        uint16_t total_block_num = mf_classic_get_total_block_num(mfc_data->type);
+        for(uint16_t i = 0; i < total_block_num; i++) {
+            if(mf_classic_is_sector_trailer(i)) {
+                problem_next = gen2_poller_can_write_sector_trailer(mfc_data, i);
+            } else {
+                problem_next = gen2_poller_can_write_data_block(mfc_data, i);
+            }
+            if(problem_next < problem) {
+                problem = problem_next;
+            }
+        }
+    } else {
+        problem = Gen2PollerWriteProblemNoData;
+    }
+
+    return problem;
+}

+ 88 - 0
nfc_magic/lib/magic/protocols/gen2/gen2_poller.h

@@ -0,0 +1,88 @@
+#pragma once
+
+#include <nfc/nfc.h>
+#include <nfc/protocols/nfc_generic_event.h>
+#include <nfc/protocols/mf_classic/mf_classic.h>
+#include <nfc/protocols/iso14443_3a/iso14443_3a.h>
+#include <nfc/protocols/iso14443_3a/iso14443_3a_poller.h>
+#include <nfc/nfc_device.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum {
+    Gen2PollerErrorNone,
+    Gen2PollerErrorNotPresent,
+    Gen2PollerErrorProtocol,
+    Gen2PollerErrorAuth,
+    Gen2PollerErrorTimeout,
+    Gen2PollerErrorAccess,
+} Gen2PollerError;
+
+// Possible write problems, sorted by priority top to bottom
+typedef enum {
+    Gen2PollerWriteProblemNoData, // Shouldn't happen, mfc_data missing in nfc device
+    Gen2PollerWriteProblemLockedAccessBits, // Access bits on the target card don't allow writing in some cases
+    Gen2PollerWriteProblemMissingTargetKeys, // Keys to write some sectors are not available
+    Gen2PollerWriteProblemMissingSourceData, // The source dump is incomplete
+    Gen2PollerWriteProblemNone, // Everything OK
+} Gen2PollerWriteProblem;
+
+typedef enum {
+    Gen2PollerEventTypeDetected,
+    Gen2PollerEventTypeRequestMode,
+    Gen2PollerEventTypeRequestDataToWrite,
+    Gen2PollerEventTypeRequestTargetData,
+
+    Gen2PollerEventTypeSuccess,
+    Gen2PollerEventTypeFail,
+} Gen2PollerEventType;
+
+typedef enum {
+    Gen2PollerModeWipe,
+    Gen2PollerModeWrite,
+} Gen2PollerMode;
+
+typedef struct {
+    Gen2PollerMode mode;
+} Gen2PollerEventDataRequestMode;
+
+typedef struct {
+    const MfClassicData* mfc_data;
+} Gen2PollerEventDataRequestDataToWrite;
+
+typedef struct {
+    const MfClassicData* mfc_data;
+} Gen2PollerEventDataRequestTargetData;
+
+typedef union {
+    Gen2PollerEventDataRequestMode poller_mode;
+    Gen2PollerEventDataRequestDataToWrite data_to_write;
+    Gen2PollerEventDataRequestTargetData target_data;
+} Gen2PollerEventData;
+
+typedef struct {
+    Gen2PollerEventType type;
+    Gen2PollerEventData* data;
+} Gen2PollerEvent;
+
+typedef NfcCommand (*Gen2PollerCallback)(Gen2PollerEvent event, void* context);
+
+typedef struct Gen2Poller Gen2Poller;
+
+Gen2PollerError gen2_poller_detect(Nfc* nfc);
+
+Gen2Poller* gen2_poller_alloc(Nfc* nfc);
+
+void gen2_poller_free(Gen2Poller* instance);
+
+void gen2_poller_start(Gen2Poller* instance, Gen2PollerCallback callback, void* context);
+
+void gen2_poller_stop(Gen2Poller* instance);
+
+Gen2PollerWriteProblem gen2_poller_can_write_everything(NfcDevice* device);
+
+#ifdef __cplusplus
+}
+#endif

+ 633 - 0
nfc_magic/lib/magic/protocols/gen2/gen2_poller_i.c

@@ -0,0 +1,633 @@
+#include "gen2_poller_i.h"
+#include <nfc/helpers/iso14443_crc.h>
+
+#include <bit_lib.h>
+#include "furi_hal_random.h"
+
+#include <furi/furi.h>
+
+#define TAG "GEN2_I"
+
+MfClassicError mf_classic_process_error(Iso14443_3aError error) {
+    MfClassicError ret = MfClassicErrorNone;
+
+    switch(error) {
+    case Iso14443_3aErrorNone:
+        ret = MfClassicErrorNone;
+        break;
+    case Iso14443_3aErrorNotPresent:
+        ret = MfClassicErrorNotPresent;
+        break;
+    case Iso14443_3aErrorColResFailed:
+    case Iso14443_3aErrorCommunication:
+    case Iso14443_3aErrorWrongCrc:
+        ret = MfClassicErrorProtocol;
+        break;
+    case Iso14443_3aErrorTimeout:
+        ret = MfClassicErrorTimeout;
+        break;
+    default:
+        ret = MfClassicErrorProtocol;
+        break;
+    }
+    return ret;
+}
+
+Gen2PollerError gen2_poller_process_iso3_error(Iso14443_3aError error) {
+    Gen2PollerError ret = Gen2PollerErrorNone;
+
+    switch(error) {
+    case Iso14443_3aErrorNone:
+        ret = Gen2PollerErrorNone;
+        break;
+    case Iso14443_3aErrorNotPresent:
+        ret = Gen2PollerErrorNotPresent;
+        break;
+    case Iso14443_3aErrorWrongCrc:
+        ret = Gen2PollerErrorProtocol;
+        break;
+    case Iso14443_3aErrorTimeout:
+        ret = Gen2PollerErrorTimeout;
+        break;
+    default:
+        ret = Gen2PollerErrorProtocol;
+        break;
+    }
+    return ret;
+}
+
+Gen2PollerError gen2_poller_process_mifare_classic_error(MfClassicError error) {
+    Gen2PollerError ret = Gen2PollerErrorNone;
+
+    switch(error) {
+    case MfClassicErrorNone:
+        ret = Gen2PollerErrorNone;
+        break;
+    case MfClassicErrorNotPresent:
+        ret = Gen2PollerErrorNotPresent;
+        break;
+    case MfClassicErrorProtocol:
+        ret = Gen2PollerErrorProtocol;
+        break;
+    case MfClassicErrorAuth:
+        ret = Gen2PollerErrorAuth;
+        break;
+    case MfClassicErrorTimeout:
+        ret = Gen2PollerErrorTimeout;
+        break;
+    default:
+        ret = Gen2PollerErrorProtocol;
+        break;
+    }
+
+    return ret;
+}
+
+static Gen2PollerError gen2_poller_get_nt_common(
+    Gen2Poller* instance,
+    uint8_t block_num,
+    MfClassicKeyType key_type,
+    MfClassicNt* nt,
+    bool is_nested) {
+    MfClassicError ret = MfClassicErrorNone;
+    Iso14443_3aError error = Iso14443_3aErrorNone;
+
+    do {
+        uint8_t auth_type = (key_type == MfClassicKeyTypeB) ? MF_CLASSIC_CMD_AUTH_KEY_B :
+                                                              MF_CLASSIC_CMD_AUTH_KEY_A;
+        uint8_t auth_cmd[2] = {auth_type, block_num};
+        bit_buffer_copy_bytes(instance->tx_plain_buffer, auth_cmd, sizeof(auth_cmd));
+
+        if(is_nested) {
+            iso14443_crc_append(Iso14443CrcTypeA, instance->tx_plain_buffer);
+            crypto1_encrypt(
+                instance->crypto, NULL, instance->tx_plain_buffer, instance->tx_encrypted_buffer);
+            error = iso14443_3a_poller_txrx_custom_parity(
+                instance->iso3_poller,
+                instance->tx_encrypted_buffer,
+                instance->rx_plain_buffer, // NT gets decrypted by mf_classic_async_auth
+                GEN2_POLLER_MAX_FWT);
+            if(error != Iso14443_3aErrorNone) {
+                ret = mf_classic_process_error(error);
+                break;
+            }
+        } else {
+            FURI_LOG_D(TAG, "Plain auth cmd");
+            error = iso14443_3a_poller_send_standard_frame(
+                instance->iso3_poller,
+                instance->tx_plain_buffer,
+                instance->rx_plain_buffer,
+                GEN2_POLLER_MAX_FWT);
+            if(error != Iso14443_3aErrorWrongCrc) {
+                ret = mf_classic_process_error(error);
+                break;
+            }
+        }
+        if(bit_buffer_get_size_bytes(instance->rx_plain_buffer) != sizeof(MfClassicNt)) {
+            ret = MfClassicErrorProtocol;
+            break;
+        }
+
+        if(nt) {
+            bit_buffer_write_bytes(instance->rx_plain_buffer, nt->data, sizeof(MfClassicNt));
+        }
+    } while(false);
+
+    return gen2_poller_process_mifare_classic_error(ret);
+}
+
+Gen2PollerError gen2_poller_get_nt(
+    Gen2Poller* instance,
+    uint8_t block_num,
+    MfClassicKeyType key_type,
+    MfClassicNt* nt) {
+    return gen2_poller_get_nt_common(instance, block_num, key_type, nt, false);
+}
+
+Gen2PollerError gen2_poller_get_nt_nested(
+    Gen2Poller* instance,
+    uint8_t block_num,
+    MfClassicKeyType key_type,
+    MfClassicNt* nt) {
+    return gen2_poller_get_nt_common(instance, block_num, key_type, nt, true);
+}
+
+static Gen2PollerError gen2_poller_auth_common(
+    Gen2Poller* instance,
+    uint8_t block_num,
+    MfClassicKey* key,
+    MfClassicKeyType key_type,
+    MfClassicAuthContext* data,
+    bool is_nested) {
+    Gen2PollerError ret = Gen2PollerErrorNone;
+    Iso14443_3aError error = Iso14443_3aErrorNone;
+
+    do {
+        iso14443_3a_copy(instance->data->iso14443_3a_data, nfc_poller_get_data(instance->poller));
+
+        MfClassicNt nt = {};
+        if(is_nested) {
+            ret = gen2_poller_get_nt_nested(instance, block_num, key_type, &nt);
+        } else {
+            ret = gen2_poller_get_nt(instance, block_num, key_type, &nt);
+        }
+        if(ret != Gen2PollerErrorNone) break;
+        if(data) {
+            data->nt = nt;
+        }
+
+        uint32_t cuid = iso14443_3a_get_cuid(instance->data->iso14443_3a_data);
+        uint64_t key_num = bit_lib_bytes_to_num_be(key->data, sizeof(MfClassicKey));
+        MfClassicNr nr = {};
+        furi_hal_random_fill_buf(nr.data, sizeof(MfClassicNr));
+
+        crypto1_encrypt_reader_nonce(
+            instance->crypto,
+            key_num,
+            cuid,
+            nt.data,
+            nr.data,
+            instance->tx_encrypted_buffer,
+            is_nested);
+        error = iso14443_3a_poller_txrx_custom_parity(
+            instance->iso3_poller,
+            instance->tx_encrypted_buffer,
+            instance->rx_encrypted_buffer,
+            GEN2_POLLER_MAX_FWT);
+
+        if(error != Iso14443_3aErrorNone) {
+            ret = gen2_poller_process_iso3_error(error);
+            break;
+        }
+        if(bit_buffer_get_size_bytes(instance->rx_encrypted_buffer) != 4) {
+            ret = Gen2PollerErrorAuth;
+        }
+
+        crypto1_word(instance->crypto, 0, 0);
+        instance->auth_state = Gen2AuthStatePassed;
+
+        if(data) {
+            data->nr = nr;
+            const uint8_t* nr_ar = bit_buffer_get_data(instance->tx_encrypted_buffer);
+            memcpy(data->ar.data, &nr_ar[4], sizeof(MfClassicAr));
+            bit_buffer_write_bytes(
+                instance->rx_encrypted_buffer, data->at.data, sizeof(MfClassicAt));
+        }
+    } while(false);
+
+    if(ret != Gen2PollerErrorNone) {
+        iso14443_3a_poller_halt(instance->iso3_poller);
+    }
+
+    return ret;
+}
+
+Gen2PollerError gen2_poller_auth(
+    Gen2Poller* instance,
+    uint8_t block_num,
+    MfClassicKey* key,
+    MfClassicKeyType key_type,
+    MfClassicAuthContext* data) {
+    return gen2_poller_auth_common(instance, block_num, key, key_type, data, false);
+}
+
+Gen2PollerError gen2_poller_halt(Gen2Poller* instance) {
+    Gen2PollerError ret = Gen2PollerErrorNone;
+    Iso14443_3aError error = Iso14443_3aErrorNone;
+
+    do {
+        uint8_t halt_cmd[2] = {MF_CLASSIC_CMD_HALT_MSB, MF_CLASSIC_CMD_HALT_LSB};
+        bit_buffer_copy_bytes(instance->tx_plain_buffer, halt_cmd, sizeof(halt_cmd));
+        iso14443_crc_append(Iso14443CrcTypeA, instance->tx_plain_buffer);
+
+        if(instance->auth_state == Gen2AuthStatePassed) {
+            // Send an encrypted halt command
+            crypto1_encrypt(
+                instance->crypto, NULL, instance->tx_plain_buffer, instance->tx_encrypted_buffer);
+            FURI_LOG_D(TAG, "Send enc halt");
+            error = iso14443_3a_poller_txrx_custom_parity(
+                instance->iso3_poller,
+                instance->tx_encrypted_buffer,
+                instance->rx_encrypted_buffer,
+                GEN2_POLLER_MAX_FWT);
+        }
+
+        if(error != Iso14443_3aErrorNone) {
+            FURI_LOG_D(TAG, "Enc halt error");
+            // Do not break because we still need to halt the iso3 poller
+        }
+
+        // Send a regular halt command to halt the iso3 poller
+        FURI_LOG_D(TAG, "Send reg halt");
+        error = iso14443_3a_poller_halt(instance->iso3_poller);
+
+        if(error != Iso14443_3aErrorTimeout) {
+            FURI_LOG_D(TAG, "Reg halt error");
+            // Do not break as well becaue the first halt command might have worked
+            // and the card didn't respond because it was already halted
+        }
+
+        crypto1_reset(instance->crypto);
+        instance->auth_state = Gen2AuthStateIdle;
+    } while(false);
+
+    return ret;
+}
+
+Gen2PollerError
+    gen2_poller_write_block(Gen2Poller* instance, uint8_t block_num, const MfClassicBlock* data) {
+    Gen2PollerError ret = Gen2PollerErrorNone;
+    Iso14443_3aError error = Iso14443_3aErrorNone;
+
+    do {
+        uint8_t write_block_cmd[2] = {MF_CLASSIC_CMD_WRITE_BLOCK, block_num};
+        bit_buffer_copy_bytes(instance->tx_plain_buffer, write_block_cmd, sizeof(write_block_cmd));
+        iso14443_crc_append(Iso14443CrcTypeA, instance->tx_plain_buffer);
+
+        crypto1_encrypt(
+            instance->crypto, NULL, instance->tx_plain_buffer, instance->tx_encrypted_buffer);
+
+        error = iso14443_3a_poller_txrx_custom_parity(
+            instance->iso3_poller,
+            instance->tx_encrypted_buffer,
+            instance->rx_encrypted_buffer,
+            GEN2_POLLER_MAX_FWT);
+        if(error != Iso14443_3aErrorNone) {
+            ret = gen2_poller_process_iso3_error(error);
+            break;
+        }
+        if(bit_buffer_get_size(instance->rx_encrypted_buffer) != 4) {
+            ret = Gen2PollerErrorProtocol;
+            break;
+        }
+
+        crypto1_decrypt(
+            instance->crypto, instance->rx_encrypted_buffer, instance->rx_plain_buffer);
+
+        if(bit_buffer_get_byte(instance->rx_plain_buffer, 0) != MF_CLASSIC_CMD_ACK) {
+            FURI_LOG_D(TAG, "NACK received");
+            ret = Gen2PollerErrorProtocol;
+            break;
+        }
+
+        bit_buffer_copy_bytes(instance->tx_plain_buffer, data->data, sizeof(MfClassicBlock));
+        iso14443_crc_append(Iso14443CrcTypeA, instance->tx_plain_buffer);
+
+        crypto1_encrypt(
+            instance->crypto, NULL, instance->tx_plain_buffer, instance->tx_encrypted_buffer);
+
+        error = iso14443_3a_poller_txrx_custom_parity(
+            instance->iso3_poller,
+            instance->tx_encrypted_buffer,
+            instance->rx_encrypted_buffer,
+            GEN2_POLLER_MAX_FWT);
+        if(error != Iso14443_3aErrorNone) {
+            ret = gen2_poller_process_iso3_error(error);
+            break;
+        }
+        if(bit_buffer_get_size(instance->rx_encrypted_buffer) != 4) {
+            ret = Gen2PollerErrorProtocol;
+            break;
+        }
+
+        crypto1_decrypt(
+            instance->crypto, instance->rx_encrypted_buffer, instance->rx_plain_buffer);
+
+        if(bit_buffer_get_byte(instance->rx_plain_buffer, 0) != MF_CLASSIC_CMD_ACK) {
+            FURI_LOG_D(TAG, "NACK received");
+            ret = Gen2PollerErrorProtocol;
+            break;
+        }
+    } while(false);
+
+    return ret;
+}
+
+bool gen2_poller_can_write_block(const MfClassicData* mfc_data, uint8_t block_num) {
+    furi_assert(mfc_data);
+
+    bool can_write = true;
+
+    if(block_num == 0 && mfc_data->iso14443_3a_data->uid_len == 7) {
+        // 7-byte UID gen2 cards are not supported yet, need further testing
+        can_write = false;
+    }
+
+    if(mf_classic_is_sector_trailer(block_num)) {
+        can_write = gen2_poller_can_write_sector_trailer(mfc_data, block_num);
+    } else {
+        can_write = gen2_poller_can_write_data_block(mfc_data, block_num);
+    }
+
+    return can_write;
+}
+
+Gen2PollerWriteProblem
+    gen2_poller_can_write_data_block(const MfClassicData* mfc_data, uint8_t block_num) {
+    // Check whether it's possible to write the block
+    furi_assert(mfc_data);
+
+    // Check rules:
+    // 1. Check if block is read
+    // 2. Check if we have any of the keys
+    // 3. For each key, check if we can write the block
+    // 3.1. If none of the keys can write the block, check whether access conditions can be reset to allow writing
+    // 3.2 If the above conditions are not met, return an error code
+
+    Gen2PollerWriteProblem can_write = Gen2PollerWriteProblemNone;
+
+    bool has_key_a = mf_classic_is_key_found(
+        mfc_data, mf_classic_get_sector_by_block(block_num), MfClassicKeyTypeA);
+    bool has_key_b = mf_classic_is_key_found(
+        mfc_data, mf_classic_get_sector_by_block(block_num), MfClassicKeyTypeB);
+
+    if(!mf_classic_is_block_read(mfc_data, block_num)) {
+        can_write = Gen2PollerWriteProblemMissingSourceData;
+        return can_write;
+    }
+    if(!has_key_a && !has_key_b) {
+        can_write = Gen2PollerWriteProblemMissingTargetKeys;
+        return can_write;
+    }
+    if(!gen2_is_allowed_access(mfc_data, block_num, MfClassicKeyTypeA, MfClassicActionDataWrite) &&
+       !gen2_is_allowed_access(mfc_data, block_num, MfClassicKeyTypeB, MfClassicActionDataWrite)) {
+        if(!gen2_can_reset_access_conditions(mfc_data, block_num)) {
+            can_write = Gen2PollerWriteProblemLockedAccessBits;
+            return can_write;
+        }
+    }
+
+    return can_write;
+}
+
+Gen2PollerWriteProblem
+    gen2_poller_can_write_sector_trailer(const MfClassicData* mfc_data, uint8_t block_num) {
+    // Check whether it's possible to write the sector trailer
+    furi_assert(mfc_data);
+
+    // Check rules:
+    // 1. Check if block is read
+    // 2. Check if we have any of the keys
+    // 3. For each key, check if we can write the block
+    // 3.1 Check that at least one of the keys can write Key A
+    // 3.1.1 If none of the keys can write Key A, check whether access conditions can be reset to allow writing
+    // 3.2 Check that at least one of the keys can write the Access Conditions
+    // 3.3 Check that at least one of the keys can write Key B
+    // 3.3.1 If none of the keys can write Key B, check whether access conditions can be reset to allow writing
+    // 3.4 If any of the above conditions are not met, return an error code
+
+    Gen2PollerWriteProblem can_write = Gen2PollerWriteProblemNone;
+
+    bool has_key_a = mf_classic_is_key_found(
+        mfc_data, mf_classic_get_sector_by_block(block_num), MfClassicKeyTypeA);
+    bool has_key_b = mf_classic_is_key_found(
+        mfc_data, mf_classic_get_sector_by_block(block_num), MfClassicKeyTypeB);
+    if(!mf_classic_is_block_read(mfc_data, block_num)) {
+        can_write = Gen2PollerWriteProblemMissingSourceData;
+        return can_write;
+    }
+    if(!has_key_a && !has_key_b) {
+        can_write = Gen2PollerWriteProblemMissingTargetKeys;
+        return can_write;
+    }
+    if(!gen2_is_allowed_access(mfc_data, block_num, MfClassicKeyTypeA, MfClassicActionKeyAWrite) &&
+       !gen2_is_allowed_access(mfc_data, block_num, MfClassicKeyTypeB, MfClassicActionKeyAWrite)) {
+        if(!gen2_can_reset_access_conditions(mfc_data, block_num)) {
+            can_write = Gen2PollerWriteProblemLockedAccessBits;
+            return can_write;
+        }
+    }
+    if(!gen2_is_allowed_access(mfc_data, block_num, MfClassicKeyTypeA, MfClassicActionACWrite) &&
+       !gen2_is_allowed_access(mfc_data, block_num, MfClassicKeyTypeB, MfClassicActionACWrite)) {
+        can_write = Gen2PollerWriteProblemLockedAccessBits;
+        return can_write;
+    }
+    if(!gen2_is_allowed_access(mfc_data, block_num, MfClassicKeyTypeA, MfClassicActionKeyBWrite) &&
+       !gen2_is_allowed_access(mfc_data, block_num, MfClassicKeyTypeB, MfClassicActionKeyBWrite)) {
+        if(!gen2_can_reset_access_conditions(mfc_data, block_num)) {
+            can_write = Gen2PollerWriteProblemLockedAccessBits;
+            return can_write;
+        }
+    }
+
+    return can_write;
+}
+
+bool gen2_can_reset_access_conditions(const MfClassicData* mfc_data, uint8_t block_num) {
+    // Check whether it's possible to reset the access conditions
+    furi_assert(mfc_data);
+
+    // Check rules:
+    // 1. Check if the sector trailer for this block is read
+    // 2. Check if we have any of the keys
+    // 3. For each key, check if we can write the access conditions
+    // 3.1. If none of the keys can write the access conditions, return false
+
+    bool can_reset = false;
+
+    bool has_key_a = mf_classic_is_key_found(
+        mfc_data, mf_classic_get_sector_by_block(block_num), MfClassicKeyTypeA);
+    bool has_key_b = mf_classic_is_key_found(
+        mfc_data, mf_classic_get_sector_by_block(block_num), MfClassicKeyTypeB);
+    uint8_t sector_tr_num = mf_classic_get_sector_trailer_num_by_block(block_num);
+    if(!mf_classic_is_block_read(mfc_data, sector_tr_num)) {
+        can_reset = false;
+        return can_reset;
+    }
+    if(!has_key_a && !has_key_b) {
+        can_reset = false;
+        return can_reset;
+    }
+    if(gen2_is_allowed_access(mfc_data, block_num, MfClassicKeyTypeA, MfClassicActionACWrite) ||
+       gen2_is_allowed_access(mfc_data, block_num, MfClassicKeyTypeB, MfClassicActionACWrite)) {
+        can_reset = true;
+    }
+
+    return can_reset;
+}
+
+MfClassicKeyType
+    gen2_poller_get_key_type_to_write(const MfClassicData* mfc_data, uint8_t block_num) {
+    // Get the key type to use for writing
+    // We assume that at least one of the keys can write the block
+    furi_assert(mfc_data);
+
+    MfClassicKeyType key_type = MfClassicKeyTypeA;
+
+    if(gen2_is_allowed_access(mfc_data, block_num, MfClassicKeyTypeA, MfClassicActionDataWrite)) {
+        key_type = MfClassicKeyTypeA;
+    } else if(gen2_is_allowed_access(
+                  mfc_data, block_num, MfClassicKeyTypeB, MfClassicActionDataWrite)) {
+        key_type = MfClassicKeyTypeB;
+    }
+
+    return key_type;
+}
+
+static bool gen2_is_allowed_access_sector_trailer(
+    const MfClassicData* data,
+    uint8_t block_num,
+    MfClassicKeyType key_type,
+    MfClassicAction action) {
+    uint8_t sector_num = mf_classic_get_sector_by_block(block_num);
+    MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, sector_num);
+    uint8_t* access_bits_arr = sec_tr->access_bits.data;
+    uint8_t AC = ((access_bits_arr[1] >> 5) & 0x04) | ((access_bits_arr[2] >> 2) & 0x02) |
+                 ((access_bits_arr[2] >> 7) & 0x01);
+    FURI_LOG_T("NFC", "AC: %02X", AC);
+
+    switch(action) {
+    case MfClassicActionKeyARead: {
+        return false;
+    }
+    case MfClassicActionKeyAWrite:
+    case MfClassicActionKeyBWrite: {
+        return (
+            (key_type == MfClassicKeyTypeA && (AC == 0x00 || AC == 0x01)) ||
+            (key_type == MfClassicKeyTypeB &&
+             (AC == 0x00 || AC == 0x04 || AC == 0x03 || AC == 0x01)));
+    }
+    case MfClassicActionKeyBRead: {
+        return (key_type == MfClassicKeyTypeA && (AC == 0x00 || AC == 0x02 || AC == 0x01)) ||
+               (key_type == MfClassicKeyTypeB && (AC == 0x00 || AC == 0x02 || AC == 0x01));
+    }
+    case MfClassicActionACRead: {
+        return ((key_type == MfClassicKeyTypeA) || (key_type == MfClassicKeyTypeB));
+    }
+    case MfClassicActionACWrite: {
+        return (
+            (key_type == MfClassicKeyTypeA && (AC == 0x01)) ||
+            (key_type == MfClassicKeyTypeB && (AC == 0x01 || AC == 0x03 || AC == 0x05)));
+    }
+    default:
+        return false;
+    }
+    return true;
+}
+
+bool gen2_is_allowed_access_data_block(
+    MfClassicSectorTrailer* sec_tr,
+    uint8_t block_num,
+    MfClassicKeyType key_type,
+    MfClassicAction action) {
+    // Same as mf_classic_is_allowed_access_data_block but with sector 0 allowed
+    furi_assert(sec_tr);
+
+    uint8_t* access_bits_arr = sec_tr->access_bits.data;
+
+    uint8_t sector_block = 0;
+    if(block_num <= 128) {
+        sector_block = block_num & 0x03;
+    } else {
+        sector_block = (block_num & 0x0f) / 5;
+    }
+
+    uint8_t AC;
+    switch(sector_block) {
+    case 0x00: {
+        AC = ((access_bits_arr[1] >> 2) & 0x04) | ((access_bits_arr[2] << 1) & 0x02) |
+             ((access_bits_arr[2] >> 4) & 0x01);
+        break;
+    }
+    case 0x01: {
+        AC = ((access_bits_arr[1] >> 3) & 0x04) | ((access_bits_arr[2] >> 0) & 0x02) |
+             ((access_bits_arr[2] >> 5) & 0x01);
+        break;
+    }
+    case 0x02: {
+        AC = ((access_bits_arr[1] >> 4) & 0x04) | ((access_bits_arr[2] >> 1) & 0x02) |
+             ((access_bits_arr[2] >> 6) & 0x01);
+        break;
+    }
+    default:
+        return false;
+    }
+
+    switch(action) {
+    case MfClassicActionDataRead: {
+        return (
+            (key_type == MfClassicKeyTypeA && !(AC == 0x03 || AC == 0x05 || AC == 0x07)) ||
+            (key_type == MfClassicKeyTypeB && !(AC == 0x07)));
+    }
+    case MfClassicActionDataWrite: {
+        return (
+            (key_type == MfClassicKeyTypeA && (AC == 0x00)) ||
+            (key_type == MfClassicKeyTypeB &&
+             (AC == 0x00 || AC == 0x04 || AC == 0x06 || AC == 0x03)));
+    }
+    case MfClassicActionDataInc: {
+        return (
+            (key_type == MfClassicKeyTypeA && (AC == 0x00)) ||
+            (key_type == MfClassicKeyTypeB && (AC == 0x00 || AC == 0x06)));
+    }
+    case MfClassicActionDataDec: {
+        return (
+            (key_type == MfClassicKeyTypeA && (AC == 0x00 || AC == 0x06 || AC == 0x01)) ||
+            (key_type == MfClassicKeyTypeB && (AC == 0x00 || AC == 0x06 || AC == 0x01)));
+    }
+    default:
+        return false;
+    }
+
+    return false;
+}
+
+bool gen2_is_allowed_access(
+    const MfClassicData* data,
+    uint8_t block_num,
+    MfClassicKeyType key_type,
+    MfClassicAction action) {
+    // Same as mf_classic_is_allowed_access but with sector 0 allowed
+    furi_assert(data);
+
+    bool access_allowed = false;
+    if(mf_classic_is_sector_trailer(block_num)) {
+        access_allowed = gen2_is_allowed_access_sector_trailer(data, block_num, key_type, action);
+    } else {
+        uint8_t sector_num = mf_classic_get_sector_by_block(block_num);
+        MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, sector_num);
+        access_allowed = gen2_is_allowed_access_data_block(sec_tr, block_num, key_type, action);
+    }
+
+    return access_allowed;
+}

+ 139 - 0
nfc_magic/lib/magic/protocols/gen2/gen2_poller_i.h

@@ -0,0 +1,139 @@
+#pragma once
+
+#include "gen2_poller.h"
+#include <nfc/protocols/nfc_generic_event.h>
+#include "crypto1.h" // TODO: Move to a better home
+#include <nfc/protocols/iso14443_3a/iso14443_3a_poller.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define GEN2_CMD_READ_ATS (0xE0)
+#define GEN2_FSDI_256 (0x8U)
+
+#define GEN2_POLLER_BLOCK_SIZE (16)
+
+#define GEN2_POLLER_MAX_BUFFER_SIZE (64U)
+#define GEN2_POLLER_MAX_FWT (150000U)
+
+typedef enum {
+    Gen2PollerStateIdle,
+    Gen2PollerStateRequestMode,
+    Gen2PollerStateWipe,
+    Gen2PollerStateWriteSourceDataRequest,
+    Gen2PollerStateWriteTargetDataRequest,
+    Gen2PollerStateWrite,
+    Gen2PollerStateSuccess,
+    Gen2PollerStateFail,
+
+    Gen2PollerStateNum,
+} Gen2PollerState;
+
+typedef enum {
+    Gen2AuthStateIdle,
+    Gen2AuthStatePassed,
+} Gen2AuthState;
+
+typedef enum {
+    Gen2CardStateDetected,
+    Gen2CardStateLost,
+} Gen2CardState;
+
+typedef struct {
+    MfClassicData* mfc_data_source;
+    MfClassicData* mfc_data_target;
+    MfClassicKey auth_key;
+    MfClassicKeyType read_key;
+    MfClassicKeyType write_key;
+    uint16_t current_block;
+    bool need_halt_before_write;
+} Gen2PollerWriteContext;
+
+typedef union {
+    Gen2PollerWriteContext write_ctx;
+} Gen2PollerModeContext;
+
+struct Gen2Poller {
+    Nfc* nfc;
+    Gen2PollerState state;
+
+    NfcPoller* poller;
+    Iso14443_3aPoller* iso3_poller;
+
+    Gen2AuthState auth_state;
+    Gen2CardState card_state;
+
+    Gen2PollerModeContext mode_ctx;
+    Gen2PollerMode mode;
+
+    Crypto1* crypto;
+    BitBuffer* tx_plain_buffer;
+    BitBuffer* tx_encrypted_buffer;
+    BitBuffer* rx_plain_buffer;
+    BitBuffer* rx_encrypted_buffer;
+    MfClassicData* data;
+
+    Gen2PollerEvent gen2_event;
+    Gen2PollerEventData gen2_event_data;
+
+    Gen2PollerCallback callback;
+    void* context;
+};
+
+typedef struct {
+    uint8_t block;
+    MfClassicKeyType key_type;
+    MfClassicNt nt;
+} Gen2CollectNtContext;
+
+typedef struct {
+    uint8_t block_num;
+    MfClassicKey key;
+    MfClassicKeyType key_type;
+    MfClassicBlock block;
+} Gen2ReadBlockContext;
+
+typedef struct {
+    uint8_t block_num;
+    MfClassicKey key;
+    MfClassicKeyType key_type;
+    MfClassicBlock block;
+} Gen2WriteBlockContext;
+
+Gen2PollerError gen2_poller_write(Gen2Poller* instance);
+
+Gen2PollerError gen2_poller_auth(
+    Gen2Poller* instance,
+    uint8_t block_num,
+    MfClassicKey* key,
+    MfClassicKeyType key_type,
+    MfClassicAuthContext* data);
+
+Gen2PollerError gen2_poller_halt(Gen2Poller* instance);
+
+Gen2PollerError
+    gen2_poller_write_block(Gen2Poller* instance, uint8_t block_num, const MfClassicBlock* data);
+
+MfClassicKeyType
+    gen2_poller_get_key_type_to_write(const MfClassicData* mfc_data, uint8_t block_num);
+
+bool gen2_poller_can_write_block(const MfClassicData* mfc_data, uint8_t block_num);
+
+bool gen2_can_reset_access_conditions(const MfClassicData* mfc_data, uint8_t block_num);
+
+Gen2PollerWriteProblem
+    gen2_poller_can_write_data_block(const MfClassicData* mfc_data, uint8_t block_num);
+
+Gen2PollerWriteProblem
+    gen2_poller_can_write_sector_trailer(const MfClassicData* mfc_data, uint8_t block_num);
+
+bool gen2_is_allowed_access(
+    const MfClassicData* data,
+    uint8_t block_num,
+    MfClassicKeyType key_type,
+    MfClassicAction action);
+
+#ifdef __cplusplus
+}
+#endif

+ 1 - 1
nfc_magic/lib/magic/protocols/gen4/gen4_poller.c

@@ -2,8 +2,8 @@
 #include "protocols/gen4/gen4_poller.h"
 #include <nfc/protocols/iso14443_3a/iso14443_3a.h>
 #include <nfc/protocols/iso14443_3a/iso14443_3a_poller.h>
-#include <lib/bit_lib/bit_lib.h>
 #include <nfc/nfc_poller.h>
+#include <bit_lib.h>
 
 #define GEN4_POLLER_THREAD_FLAG_DETECTED (1U << 0)
 

+ 6 - 6
nfc_magic/lib/magic/protocols/gen4/gen4_poller_i.c

@@ -3,7 +3,7 @@
 #include "bit_buffer.h"
 #include "protocols/gen4/gen4_poller.h"
 #include <nfc/protocols/iso14443_3a/iso14443_3a_poller.h>
-#include <lib/bit_lib/bit_lib.h>
+#include <bit_lib.h>
 
 #define GEN4_CMD_PREFIX (0xCF)
 
@@ -58,7 +58,7 @@ Gen4PollerError gen4_poller_set_shadow_mode(
             break;
         }
 
-        uint16_t response = bit_buffer_get_size_bytes(instance->rx_buffer);
+        size_t response = bit_buffer_get_size_bytes(instance->rx_buffer);
 
         FURI_LOG_D(TAG, "Card response: 0x%02X, Shadow mode set: 0x%02X", response, mode);
 
@@ -94,7 +94,7 @@ Gen4PollerError gen4_poller_set_direct_write_block_0_mode(
             ret = gen4_poller_process_error(error);
             break;
         }
-        uint16_t response = bit_buffer_get_size_bytes(instance->rx_buffer);
+        size_t response = bit_buffer_get_size_bytes(instance->rx_buffer);
 
         FURI_LOG_D(
             TAG, "Card response: 0x%02X, Direct write to block 0 mode set: 0x%02X", response, mode);
@@ -131,7 +131,7 @@ Gen4PollerError
 
         size_t rx_bytes = bit_buffer_get_size_bytes(instance->rx_buffer);
 
-        if(rx_bytes != CONFIG_SIZE_MAX || rx_bytes != CONFIG_SIZE_MIN) {
+        if((rx_bytes != CONFIG_SIZE_MAX) && (rx_bytes != CONFIG_SIZE_MIN)) {
             ret = Gen4PollerErrorProtocol;
             break;
         }
@@ -198,7 +198,7 @@ Gen4PollerError gen4_poller_set_config(
             break;
         }
 
-        uint16_t response = bit_buffer_get_size_bytes(instance->rx_buffer);
+        size_t response = bit_buffer_get_size_bytes(instance->rx_buffer);
 
         FURI_LOG_D(TAG, "Card response to set default config command: 0x%02X", response);
 
@@ -269,7 +269,7 @@ Gen4PollerError
             break;
         }
 
-        uint16_t response = bit_buffer_get_size_bytes(instance->rx_buffer);
+        size_t response = bit_buffer_get_size_bytes(instance->rx_buffer);
 
         FURI_LOG_D(
             TAG,

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

@@ -4,6 +4,8 @@
 
 static const char* nfc_magic_protocol_names[NfcMagicProtocolNum] = {
     [NfcMagicProtocolGen1] = "Classic Gen 1A/B",
+    [NfcMagicProtocolGen2] = "Classic Gen 2",
+    [NfcMagicProtocolClassic] = "Classic (possibly not magic)",
     [NfcMagicProtocolGen4] = "Gen 4 GTU",
 };
 

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

@@ -6,7 +6,9 @@ extern "C" {
 
 typedef enum {
     NfcMagicProtocolGen1,
+    NfcMagicProtocolGen2,
     NfcMagicProtocolGen4,
+    NfcMagicProtocolClassic, // Last to give priority to the others
 
     NfcMagicProtocolNum,
     NfcMagicProtocolInvalid,

+ 19 - 2
nfc_magic/nfc_magic_app.c

@@ -48,13 +48,16 @@ NfcMagicApp* nfc_magic_app_alloc() {
     view_dispatcher_set_tick_event_callback(
         instance->view_dispatcher, nfc_magic_app_tick_event_callback, 100);
 
-    // Nfc device
+    // NFC source device (file)
     instance->source_dev = nfc_device_alloc();
     nfc_device_set_loading_callback(
         instance->source_dev, nfc_magic_app_show_loading_popup, instance);
     instance->file_path = furi_string_alloc_set(NFC_APP_FOLDER);
     instance->file_name = furi_string_alloc();
 
+    // NFC target device (tag)
+    instance->target_dev = nfc_device_alloc();
+
     // Open GUI record
     instance->gui = furi_record_open(RECORD_GUI);
     view_dispatcher_attach_to_gui(
@@ -103,6 +106,13 @@ NfcMagicApp* nfc_magic_app_alloc() {
     view_dispatcher_add_view(
         instance->view_dispatcher, NfcMagicAppViewWidget, widget_get_view(instance->widget));
 
+    // Dict attack
+    instance->dict_attack = dict_attack_alloc();
+    view_dispatcher_add_view(
+        instance->view_dispatcher,
+        NfcMagicAppViewDictAttack,
+        dict_attack_get_view(instance->dict_attack));
+
     instance->nfc = nfc_alloc();
     instance->scanner = nfc_magic_scanner_alloc(instance->nfc);
 
@@ -112,11 +122,14 @@ NfcMagicApp* nfc_magic_app_alloc() {
 void nfc_magic_app_free(NfcMagicApp* instance) {
     furi_assert(instance);
 
-    // Nfc device
+    // Nfc source device
     nfc_device_free(instance->source_dev);
     furi_string_free(instance->file_name);
     furi_string_free(instance->file_path);
 
+    // Nfc target device
+    nfc_device_free(instance->target_dev);
+
     // Submenu
     view_dispatcher_remove_view(instance->view_dispatcher, NfcMagicAppViewMenu);
     submenu_free(instance->submenu);
@@ -141,6 +154,10 @@ void nfc_magic_app_free(NfcMagicApp* instance) {
     view_dispatcher_remove_view(instance->view_dispatcher, NfcMagicAppViewWidget);
     widget_free(instance->widget);
 
+    // Dict attack
+    view_dispatcher_remove_view(instance->view_dispatcher, NfcMagicAppViewDictAttack);
+    dict_attack_free(instance->dict_attack);
+
     // View Dispatcher
     view_dispatcher_free(instance->view_dispatcher);
 

+ 37 - 0
nfc_magic/nfc_magic_app_i.h

@@ -15,6 +15,7 @@
 #include <gui/modules/text_input.h>
 #include <gui/modules/byte_input.h>
 #include <gui/modules/widget.h>
+#include <views/dict_attack.h>
 
 #include <input/input.h>
 
@@ -30,16 +31,24 @@
 
 #include <nfc/nfc.h>
 #include <nfc/nfc_device.h>
+#include <nfc/nfc_poller.h>
+#include <toolbox/keys_dict.h>
 
 #include "lib/magic/nfc_magic_scanner.h"
 #include "lib/magic/protocols/nfc_magic_protocols.h"
 #include "lib/magic/protocols/gen1a/gen1a_poller.h"
+#include "lib/magic/protocols/gen2/gen2_poller.h"
 #include "lib/magic/protocols/gen4/gen4_poller.h"
 
+#include "lib/nfc/protocols/mf_classic/mf_classic_poller.h"
+
 #define NFC_APP_FOLDER ANY_PATH("nfc")
 #define NFC_APP_EXTENSION ".nfc"
 #define NFC_APP_SHADOW_EXTENSION ".shd"
 
+#define NFC_APP_MF_CLASSIC_DICT_USER_PATH (NFC_APP_FOLDER "/assets/mf_classic_dict_user.nfc")
+#define NFC_APP_MF_CLASSIC_DICT_SYSTEM_PATH (NFC_APP_FOLDER "/assets/mf_classic_dict.nfc")
+
 #define NFC_MAGIC_APP_BYTE_INPUT_STORE_SIZE (4)
 
 enum NfcMagicAppCustomEvent {
@@ -50,8 +59,26 @@ enum NfcMagicAppCustomEvent {
     NfcMagicAppCustomEventWorkerExit,
     NfcMagicAppCustomEventByteInputDone,
     NfcMagicAppCustomEventTextInputDone,
+    NfcMagicAppCustomEventCardDetected,
+    NfcMagicAppCustomEventCardLost,
+    NfcMagicAppCustomEventDictAttackDataUpdate,
+    NfcMagicAppCustomEventDictAttackComplete,
+    NfcMagicAppCustomEventDictAttackSkip,
 };
 
+typedef struct {
+    KeysDict* dict;
+    uint8_t sectors_total;
+    uint8_t sectors_read;
+    uint8_t current_sector;
+    uint8_t keys_found;
+    size_t dict_keys_total;
+    size_t dict_keys_current;
+    bool is_key_attack;
+    uint8_t key_attack_current_sector;
+    bool is_card_present;
+} NfcMagicAppMfClassicDictAttackContext;
+
 struct NfcMagicApp {
     ViewDispatcher* view_dispatcher;
     Gui* gui;
@@ -61,15 +88,24 @@ struct NfcMagicApp {
 
     SceneManager* scene_manager;
     NfcDevice* source_dev;
+    NfcDevice* target_dev;
     FuriString* file_name;
     FuriString* file_path;
 
     Nfc* nfc;
     NfcMagicProtocol protocol;
     NfcMagicScanner* scanner;
+    NfcPoller* poller;
     Gen1aPoller* gen1a_poller;
+
+    Gen2Poller* gen2_poller;
+    bool gen2_poller_is_wipe_mode;
+
     Gen4Poller* gen4_poller;
 
+    NfcMagicAppMfClassicDictAttackContext nfc_dict_context;
+    DictAttack* dict_attack;
+
     uint32_t gen4_password;
     uint32_t gen4_password_new;
 
@@ -95,6 +131,7 @@ typedef enum {
     NfcMagicAppViewTextInput,
     NfcMagicAppViewByteInput,
     NfcMagicAppViewWidget,
+    NfcMagicAppViewDictAttack,
 } NfcMagicAppView;
 
 void nfc_magic_app_blink_start(NfcMagicApp* nfc_magic);

+ 6 - 1
nfc_magic/scenes/nfc_magic_scene_config.h

@@ -6,7 +6,7 @@ ADD_SCENE(nfc_magic, gen1_menu, Gen1Menu)
 ADD_SCENE(nfc_magic, gen4_menu, Gen4Menu)
 ADD_SCENE(nfc_magic, gen4_actions_menu, Gen4ActionsMenu)
 ADD_SCENE(nfc_magic, gen4_get_cfg, Gen4GetCfg)
-ADD_SCENE(nfc_magic, gen4_set_cfg, Gen4SetCfg)
+ADD_SCENE(nfc_magic, gen4_set_default_cfg, Gen4SetDefaultCfg)
 ADD_SCENE(nfc_magic, gen4_revision, Gen4Revision)
 ADD_SCENE(nfc_magic, gen4_show_rev, Gen4ShowRev)
 ADD_SCENE(nfc_magic, gen4_show_cfg, Gen4ShowCfg)
@@ -26,3 +26,8 @@ ADD_SCENE(nfc_magic, change_key, ChangeKey)
 ADD_SCENE(nfc_magic, change_key_fail, ChangeKeyFail)
 ADD_SCENE(nfc_magic, wrong_card, WrongCard)
 ADD_SCENE(nfc_magic, not_magic, NotMagic)
+ADD_SCENE(nfc_magic, gen2_menu, Gen2Menu)
+ADD_SCENE(nfc_magic, mf_classic_menu, MfClassicMenu)
+ADD_SCENE(nfc_magic, mf_classic_dict_attack, MfClassicDictAttack)
+ADD_SCENE(nfc_magic, gen2_write_check, Gen2WriteCheck)
+ADD_SCENE(nfc_magic, mf_classic_write_check, MfClassicWriteCheck)

+ 15 - 1
nfc_magic/scenes/nfc_magic_scene_file_select.c

@@ -30,6 +30,14 @@ static bool nfc_magic_scene_file_select_is_file_suitable(NfcMagicApp* instance)
                            (mfu_type != MfUltralightTypeNTAGI2CPlus2K);
             }
         }
+    } else if(instance->protocol == NfcMagicProtocolGen2) {
+        if(protocol == NfcProtocolMfClassic) {
+            suitable = true;
+        }
+    } else if(instance->protocol == NfcMagicProtocolClassic) {
+        if(protocol == NfcProtocolMfClassic) {
+            suitable = true;
+        }
     }
 
     return suitable;
@@ -40,7 +48,13 @@ void nfc_magic_scene_file_select_on_enter(void* context) {
 
     if(nfc_magic_load_from_file_select(instance)) {
         if(nfc_magic_scene_file_select_is_file_suitable(instance)) {
-            scene_manager_next_scene(instance->scene_manager, NfcMagicSceneWriteConfirm);
+            if(instance->protocol == NfcMagicProtocolClassic ||
+               instance->protocol == NfcMagicProtocolGen2) {
+                scene_manager_next_scene(
+                    instance->scene_manager, NfcMagicSceneMfClassicDictAttack);
+            } else {
+                scene_manager_next_scene(instance->scene_manager, NfcMagicSceneWriteConfirm);
+            }
         } else {
             scene_manager_next_scene(instance->scene_manager, NfcMagicSceneWrongCard);
         }

+ 55 - 0
nfc_magic/scenes/nfc_magic_scene_gen2_menu.c

@@ -0,0 +1,55 @@
+#include "../nfc_magic_app_i.h"
+
+enum SubmenuIndex {
+    SubmenuIndexWrite,
+    SubmenuIndexWipe,
+};
+
+void nfc_magic_scene_gen2_menu_submenu_callback(void* context, uint32_t index) {
+    NfcMagicApp* instance = context;
+
+    view_dispatcher_send_custom_event(instance->view_dispatcher, index);
+}
+
+void nfc_magic_scene_gen2_menu_on_enter(void* context) {
+    NfcMagicApp* instance = context;
+
+    Submenu* submenu = instance->submenu;
+    submenu_add_item(
+        submenu, "Write", SubmenuIndexWrite, nfc_magic_scene_gen2_menu_submenu_callback, instance);
+    submenu_add_item(
+        submenu, "Wipe", SubmenuIndexWipe, nfc_magic_scene_gen2_menu_submenu_callback, instance);
+
+    submenu_set_selected_item(
+        submenu, scene_manager_get_scene_state(instance->scene_manager, NfcMagicSceneGen2Menu));
+    view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewMenu);
+}
+
+bool nfc_magic_scene_gen2_menu_on_event(void* context, SceneManagerEvent event) {
+    NfcMagicApp* instance = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == SubmenuIndexWrite) {
+            instance->gen2_poller_is_wipe_mode = false;
+            scene_manager_next_scene(instance->scene_manager, NfcMagicSceneFileSelect);
+            consumed = true;
+        } else if(event.event == SubmenuIndexWipe) {
+            instance->gen2_poller_is_wipe_mode = true;
+            scene_manager_next_scene(instance->scene_manager, NfcMagicSceneMfClassicDictAttack);
+            consumed = true;
+        }
+        scene_manager_set_scene_state(instance->scene_manager, NfcMagicSceneGen2Menu, event.event);
+    } else if(event.type == SceneManagerEventTypeBack) {
+        consumed = scene_manager_search_and_switch_to_previous_scene(
+            instance->scene_manager, NfcMagicSceneStart);
+    }
+
+    return consumed;
+}
+
+void nfc_magic_scene_gen2_menu_on_exit(void* context) {
+    NfcMagicApp* instance = context;
+
+    submenu_reset(instance->submenu);
+}

+ 120 - 0
nfc_magic/scenes/nfc_magic_scene_gen2_write_check.c

@@ -0,0 +1,120 @@
+#include "../nfc_magic_app_i.h"
+
+void nfc_magic_scene_gen2_write_check_widget_callback(
+    GuiButtonType result,
+    InputType type,
+    void* context) {
+    NfcMagicApp* instance = context;
+
+    if(type == InputTypeShort) {
+        view_dispatcher_send_custom_event(instance->view_dispatcher, result);
+    }
+}
+
+void nfc_magic_scene_gen2_write_check_on_enter(void* context) {
+    NfcMagicApp* instance = context;
+    Widget* widget = instance->widget;
+    Gen2PollerWriteProblem can_write_all = gen2_poller_can_write_everything(instance->target_dev);
+    furi_assert(can_write_all != Gen2PollerWriteProblemNoData, "No MFC data in nfc device");
+
+    if(can_write_all == Gen2PollerWriteProblemNone) {
+        if(instance->gen2_poller_is_wipe_mode) {
+            scene_manager_next_scene(instance->scene_manager, NfcMagicSceneWipe);
+            return;
+        } else {
+            scene_manager_next_scene(instance->scene_manager, NfcMagicSceneWrite);
+            return;
+        }
+    }
+
+    widget_add_string_element(widget, 0, 0, AlignLeft, AlignTop, FontPrimary, "Write problems");
+
+    if(instance->gen2_poller_is_wipe_mode) {
+        widget_add_text_box_element(
+            widget,
+            0,
+            13,
+            128,
+            54,
+            AlignLeft,
+            AlignTop,
+            "Not all sectors can be wiped\nTry wiping anyway?",
+            false);
+    } else {
+        if(can_write_all == Gen2PollerWriteProblemMissingSourceData) {
+            widget_add_text_box_element(
+                widget,
+                0,
+                13,
+                128,
+                54,
+                AlignLeft,
+                AlignTop,
+                "The source dump is incomplete\nCloned card may not work\nTry writing anyway?",
+                false);
+        } else if(can_write_all == Gen2PollerWriteProblemMissingTargetKeys) {
+            widget_add_text_box_element(
+                widget,
+                0,
+                13,
+                128,
+                54,
+                AlignLeft,
+                AlignTop,
+                "Keys to write some sectors\nare not available\nCloned card may not work\nTry writing anyway?",
+                false);
+        } else if(can_write_all == Gen2PollerWriteProblemLockedAccessBits) {
+            widget_add_text_box_element(
+                widget,
+                0,
+                13,
+                128,
+                54,
+                AlignLeft,
+                AlignTop,
+                "Target card doesn't allow\nwriting in some cases\nCloned card may not work\nTry writing anyway?",
+                false);
+        }
+    }
+    widget_add_button_element(
+        widget,
+        GuiButtonTypeCenter,
+        "Continue",
+        nfc_magic_scene_gen2_write_check_widget_callback,
+        instance);
+    widget_add_button_element(
+        widget,
+        GuiButtonTypeLeft,
+        "Back",
+        nfc_magic_scene_gen2_write_check_widget_callback,
+        instance);
+
+    // Setup and start worker
+    view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewWidget);
+}
+
+bool nfc_magic_scene_gen2_write_check_on_event(void* context, SceneManagerEvent event) {
+    NfcMagicApp* instance = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == GuiButtonTypeLeft) {
+            consumed = scene_manager_search_and_switch_to_previous_scene(
+                instance->scene_manager, NfcMagicSceneGen2Menu);
+        } else if(event.event == GuiButtonTypeCenter) {
+            if(instance->gen2_poller_is_wipe_mode) {
+                scene_manager_next_scene(instance->scene_manager, NfcMagicSceneWipe);
+            } else {
+                scene_manager_next_scene(instance->scene_manager, NfcMagicSceneWrite);
+            }
+            consumed = true;
+        }
+    }
+    return consumed;
+}
+
+void nfc_magic_scene_gen2_write_check_on_exit(void* context) {
+    NfcMagicApp* instance = context;
+
+    widget_reset(instance->widget);
+}

+ 5 - 5
nfc_magic/scenes/nfc_magic_scene_gen4_actions_menu.c

@@ -2,7 +2,7 @@
 
 enum SubmenuIndex {
     SubmenuIndexAuthenticate,
-    SubmenuIndexSetStandartConfig,
+    SubmenuIndexSetDefaultConfig,
 };
 
 void nfc_magic_scene_gen4_actions_menu_submenu_callback(void* context, uint32_t index) {
@@ -23,8 +23,8 @@ void nfc_magic_scene_gen4_actions_menu_on_enter(void* context) {
         instance);
     submenu_add_item(
         submenu,
-        "Set Standart Config",
-        SubmenuIndexSetStandartConfig,
+        "Set Default Config",
+        SubmenuIndexSetDefaultConfig,
         nfc_magic_scene_gen4_actions_menu_submenu_callback,
         instance);
 
@@ -42,8 +42,8 @@ bool nfc_magic_scene_gen4_actions_menu_on_event(void* context, SceneManagerEvent
         if(event.event == SubmenuIndexAuthenticate) {
             scene_manager_next_scene(instance->scene_manager, NfcMagicSceneKeyInput);
             consumed = true;
-        } else if(event.event == SubmenuIndexSetStandartConfig) {
-            scene_manager_next_scene(instance->scene_manager, NfcMagicSceneGen4SetCfg);
+        } else if(event.event == SubmenuIndexSetDefaultConfig) {
+            scene_manager_next_scene(instance->scene_manager, NfcMagicSceneGen4SetDefaultCfg);
             consumed = true;
         }
 

+ 14 - 14
nfc_magic/scenes/nfc_magic_scene_gen4_set_cfg.c → nfc_magic/scenes/nfc_magic_scene_gen4_set_default_cfg.c

@@ -5,7 +5,7 @@ enum {
     NfcMagicSceneGen4SetDefCfgStateCardFound,
 };
 
-NfcCommand nfc_mafic_scene_gen4_set_cfg_poller_callback(Gen4PollerEvent event, void* context) {
+NfcCommand nfc_mafic_scene_gen4_set_default_cfg_poller_callback(Gen4PollerEvent event, void* context) {
     NfcMagicApp* instance = context;
     furi_assert(event.data);
 
@@ -29,11 +29,11 @@ NfcCommand nfc_mafic_scene_gen4_set_cfg_poller_callback(Gen4PollerEvent event, v
     return command;
 }
 
-static void nfc_magic_scene_gen4_set_cfg_setup_view(NfcMagicApp* instance) {
+static void nfc_magic_scene_gen4_set_default_cfg_setup_view(NfcMagicApp* instance) {
     Popup* popup = instance->popup;
     popup_reset(popup);
     uint32_t state =
-        scene_manager_get_scene_state(instance->scene_manager, NfcMagicSceneGen4SetCfg);
+        scene_manager_get_scene_state(instance->scene_manager, NfcMagicSceneGen4SetDefaultCfg);
 
     if(state == NfcMagicSceneGen4SetDefCfgStateCardSearch) {
         popup_set_icon(instance->popup, 0, 8, &I_NFC_manual_60x50);
@@ -47,24 +47,24 @@ static void nfc_magic_scene_gen4_set_cfg_setup_view(NfcMagicApp* instance) {
     view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewPopup);
 }
 
-void nfc_magic_scene_gen4_set_cfg_on_enter(void* context) {
+void nfc_magic_scene_gen4_set_default_cfg_on_enter(void* context) {
     NfcMagicApp* instance = context;
 
     scene_manager_set_scene_state(
         instance->scene_manager,
-        NfcMagicSceneGen4SetCfg,
+        NfcMagicSceneGen4SetDefaultCfg,
         NfcMagicSceneGen4SetDefCfgStateCardSearch);
-    nfc_magic_scene_gen4_set_cfg_setup_view(instance);
+    nfc_magic_scene_gen4_set_default_cfg_setup_view(instance);
 
     nfc_magic_app_blink_start(instance);
 
     instance->gen4_poller = gen4_poller_alloc(instance->nfc);
     gen4_poller_set_password(instance->gen4_poller, instance->gen4_password);
     gen4_poller_start(
-        instance->gen4_poller, nfc_mafic_scene_gen4_set_cfg_poller_callback, instance);
+        instance->gen4_poller, nfc_mafic_scene_gen4_set_default_cfg_poller_callback, instance);
 }
 
-bool nfc_magic_scene_gen4_set_cfg_on_event(void* context, SceneManagerEvent event) {
+bool nfc_magic_scene_gen4_set_default_cfg_on_event(void* context, SceneManagerEvent event) {
     NfcMagicApp* instance = context;
     bool consumed = false;
 
@@ -72,16 +72,16 @@ bool nfc_magic_scene_gen4_set_cfg_on_event(void* context, SceneManagerEvent even
         if(event.event == NfcMagicCustomEventCardDetected) {
             scene_manager_set_scene_state(
                 instance->scene_manager,
-                NfcMagicSceneGen4SetCfg,
+                NfcMagicSceneGen4SetDefaultCfg,
                 NfcMagicSceneGen4SetDefCfgStateCardFound);
-            nfc_magic_scene_gen4_set_cfg_setup_view(instance);
+            nfc_magic_scene_gen4_set_default_cfg_setup_view(instance);
             consumed = true;
         } else if(event.event == NfcMagicCustomEventCardLost) {
             scene_manager_set_scene_state(
                 instance->scene_manager,
-                NfcMagicSceneGen4SetCfg,
+                NfcMagicSceneGen4SetDefaultCfg,
                 NfcMagicSceneGen4SetDefCfgStateCardSearch);
-            nfc_magic_scene_gen4_set_cfg_setup_view(instance);
+            nfc_magic_scene_gen4_set_default_cfg_setup_view(instance);
             consumed = true;
         } else if(event.event == NfcMagicCustomEventWorkerSuccess) {
             scene_manager_next_scene(instance->scene_manager, NfcMagicSceneSuccess);
@@ -95,14 +95,14 @@ bool nfc_magic_scene_gen4_set_cfg_on_event(void* context, SceneManagerEvent even
     return consumed;
 }
 
-void nfc_magic_scene_gen4_set_cfg_on_exit(void* context) {
+void nfc_magic_scene_gen4_set_default_cfg_on_exit(void* context) {
     NfcMagicApp* instance = context;
 
     gen4_poller_stop(instance->gen4_poller);
     gen4_poller_free(instance->gen4_poller);
     scene_manager_set_scene_state(
         instance->scene_manager,
-        NfcMagicSceneGen4SetCfg,
+        NfcMagicSceneGen4SetDefaultCfg,
         NfcMagicSceneGen4SetDefCfgStateCardSearch);
     // Clear view
     popup_reset(instance->popup);

+ 1 - 1
nfc_magic/scenes/nfc_magic_scene_gen4_set_direct_write_block_0_mode.c

@@ -119,4 +119,4 @@ void nfc_magic_scene_gen4_set_direct_write_block_0_mode_on_exit(void* context) {
     popup_reset(instance->popup);
 
     nfc_magic_app_blink_stop(instance);
-}
+}

+ 1 - 1
nfc_magic/scenes/nfc_magic_scene_gen4_set_shd_mode.c

@@ -114,4 +114,4 @@ void nfc_magic_scene_gen4_set_shd_mode_on_exit(void* context) {
     popup_reset(instance->popup);
 
     nfc_magic_app_blink_stop(instance);
-}
+}

+ 1 - 1
nfc_magic/scenes/nfc_magic_scene_key_input.c

@@ -1,6 +1,6 @@
 #include "../nfc_magic_app_i.h"
 
-#include <lib/bit_lib/bit_lib.h>
+#include <bit_lib.h>
 
 void nfc_magic_scene_key_input_byte_input_callback(void* context) {
     NfcMagicApp* instance = context;

+ 8 - 2
nfc_magic/scenes/nfc_magic_scene_magic_info.c

@@ -19,7 +19,7 @@ void nfc_magic_scene_magic_info_on_enter(void* context) {
 
     widget_add_icon_element(widget, 84, 22, &I_WarningDolphinFlip_45x42);
     widget_add_string_element(
-        widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "Magic card detected");
+        widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "Supported card detected");
     widget_add_string_element(
         widget,
         3,
@@ -47,9 +47,15 @@ bool nfc_magic_scene_magic_info_on_event(void* context, SceneManagerEvent event)
             if(instance->protocol == NfcMagicProtocolGen1) {
                 scene_manager_next_scene(instance->scene_manager, NfcMagicSceneGen1Menu);
                 consumed = true;
-            } else {
+            } else if(instance->protocol == NfcMagicProtocolGen4) {
                 scene_manager_next_scene(instance->scene_manager, NfcMagicSceneGen4Menu);
                 consumed = true;
+            } else if(instance->protocol == NfcMagicProtocolGen2) {
+                scene_manager_next_scene(instance->scene_manager, NfcMagicSceneGen2Menu);
+                consumed = true;
+            } else if(instance->protocol == NfcMagicProtocolClassic) {
+                scene_manager_next_scene(instance->scene_manager, NfcMagicSceneMfClassicMenu);
+                consumed = true;
             }
         }
     } else if(event.type == SceneManagerEventTypeBack) {

+ 311 - 0
nfc_magic/scenes/nfc_magic_scene_mf_classic_dict_attack.c

@@ -0,0 +1,311 @@
+#include "../nfc_magic_app_i.h"
+
+#include <dolphin/dolphin.h>
+#include <lib/nfc/protocols/mf_classic/mf_classic_poller.h>
+
+#include "views/dict_attack.h"
+
+#define TAG "NfcMagicMfClassicDictAttack"
+
+typedef enum {
+    DictAttackStateUserDictInProgress,
+    DictAttackStateSystemDictInProgress,
+} DictAttackState;
+
+NfcCommand nfc_dict_attack_worker_callback(NfcGenericEvent event, void* context) {
+    furi_assert(context);
+    furi_assert(event.event_data);
+    furi_assert(event.instance);
+    furi_assert(event.protocol == NfcProtocolMfClassic);
+
+    NfcCommand command = NfcCommandContinue;
+    MfClassicPollerEvent* mfc_event = event.event_data;
+
+    NfcMagicApp* instance = context;
+    if(mfc_event->type == MfClassicPollerEventTypeCardDetected) {
+        instance->nfc_dict_context.is_card_present = true;
+        view_dispatcher_send_custom_event(
+            instance->view_dispatcher, NfcMagicAppCustomEventCardDetected);
+    } else if(mfc_event->type == MfClassicPollerEventTypeCardLost) {
+        instance->nfc_dict_context.is_card_present = false;
+        view_dispatcher_send_custom_event(
+            instance->view_dispatcher, NfcMagicAppCustomEventCardLost);
+    } else if(mfc_event->type == MfClassicPollerEventTypeRequestMode) {
+        const MfClassicData* mfc_data = nfc_poller_get_data(instance->poller);
+        nfc_device_set_data(instance->target_dev, NfcProtocolMfClassic, mfc_data);
+        FURI_LOG_D(TAG, "MFC type: %d", mfc_data->type);
+        mfc_event->data->poller_mode.mode = MfClassicPollerModeDictAttack;
+        mfc_event->data->poller_mode.data = mfc_data;
+        instance->nfc_dict_context.sectors_total =
+            mf_classic_get_total_sectors_num(mfc_data->type);
+        FURI_LOG_D(TAG, "Total sectors: %d", mf_classic_get_total_sectors_num(mfc_data->type));
+        mf_classic_get_read_sectors_and_keys(
+            mfc_data,
+            &instance->nfc_dict_context.sectors_read,
+            &instance->nfc_dict_context.keys_found);
+        view_dispatcher_send_custom_event(
+            instance->view_dispatcher, NfcMagicAppCustomEventDictAttackDataUpdate);
+    } else if(mfc_event->type == MfClassicPollerEventTypeRequestKey) {
+        MfClassicKey key = {};
+        if(keys_dict_get_next_key(
+               instance->nfc_dict_context.dict, key.data, sizeof(MfClassicKey))) {
+            mfc_event->data->key_request_data.key = key;
+            mfc_event->data->key_request_data.key_provided = true;
+            instance->nfc_dict_context.dict_keys_current++;
+            if(instance->nfc_dict_context.dict_keys_current % 10 == 0) {
+                view_dispatcher_send_custom_event(
+                    instance->view_dispatcher, NfcMagicAppCustomEventDictAttackDataUpdate);
+            }
+        } else {
+            mfc_event->data->key_request_data.key_provided = false;
+        }
+    } else if(mfc_event->type == MfClassicPollerEventTypeDataUpdate) {
+        MfClassicPollerEventDataUpdate* data_update = &mfc_event->data->data_update;
+        instance->nfc_dict_context.sectors_read = data_update->sectors_read;
+        instance->nfc_dict_context.keys_found = data_update->keys_found;
+        instance->nfc_dict_context.current_sector = data_update->current_sector;
+        view_dispatcher_send_custom_event(
+            instance->view_dispatcher, NfcMagicAppCustomEventDictAttackDataUpdate);
+    } else if(mfc_event->type == MfClassicPollerEventTypeNextSector) {
+        keys_dict_rewind(instance->nfc_dict_context.dict);
+        instance->nfc_dict_context.dict_keys_current = 0;
+        instance->nfc_dict_context.current_sector =
+            mfc_event->data->next_sector_data.current_sector;
+        view_dispatcher_send_custom_event(
+            instance->view_dispatcher, NfcMagicAppCustomEventDictAttackDataUpdate);
+    } else if(mfc_event->type == MfClassicPollerEventTypeFoundKeyA) {
+        view_dispatcher_send_custom_event(
+            instance->view_dispatcher, NfcMagicAppCustomEventDictAttackDataUpdate);
+    } else if(mfc_event->type == MfClassicPollerEventTypeFoundKeyB) {
+        view_dispatcher_send_custom_event(
+            instance->view_dispatcher, NfcMagicAppCustomEventDictAttackDataUpdate);
+    } else if(mfc_event->type == MfClassicPollerEventTypeKeyAttackStart) {
+        instance->nfc_dict_context.key_attack_current_sector =
+            mfc_event->data->key_attack_data.current_sector;
+        instance->nfc_dict_context.is_key_attack = true;
+        view_dispatcher_send_custom_event(
+            instance->view_dispatcher, NfcMagicAppCustomEventDictAttackDataUpdate);
+    } else if(mfc_event->type == MfClassicPollerEventTypeKeyAttackStop) {
+        keys_dict_rewind(instance->nfc_dict_context.dict);
+        instance->nfc_dict_context.is_key_attack = false;
+        instance->nfc_dict_context.dict_keys_current = 0;
+        view_dispatcher_send_custom_event(
+            instance->view_dispatcher, NfcMagicAppCustomEventDictAttackDataUpdate);
+    } else if(mfc_event->type == MfClassicPollerEventTypeSuccess) {
+        const MfClassicData* mfc_data = nfc_poller_get_data(instance->poller);
+        nfc_device_set_data(instance->target_dev, NfcProtocolMfClassic, mfc_data);
+        view_dispatcher_send_custom_event(
+            instance->view_dispatcher, NfcMagicAppCustomEventDictAttackComplete);
+        command = NfcCommandStop;
+    }
+
+    return command;
+}
+
+void nfc_dict_attack_dict_attack_result_callback(DictAttackEvent event, void* context) {
+    furi_assert(context);
+    NfcMagicApp* instance = context;
+
+    if(event == DictAttackEventSkipPressed) {
+        view_dispatcher_send_custom_event(
+            instance->view_dispatcher, NfcMagicAppCustomEventDictAttackSkip);
+    }
+}
+
+static void nfc_magic_scene_mf_classic_dict_attack_update_view(NfcMagicApp* instance) {
+    NfcMagicAppMfClassicDictAttackContext* mfc_dict = &instance->nfc_dict_context;
+
+    if(mfc_dict->is_key_attack) {
+        dict_attack_set_key_attack(instance->dict_attack, mfc_dict->key_attack_current_sector);
+    } else {
+        dict_attack_reset_key_attack(instance->dict_attack);
+        dict_attack_set_sectors_total(instance->dict_attack, mfc_dict->sectors_total);
+        dict_attack_set_sectors_read(instance->dict_attack, mfc_dict->sectors_read);
+        dict_attack_set_keys_found(instance->dict_attack, mfc_dict->keys_found);
+        dict_attack_set_current_dict_key(instance->dict_attack, mfc_dict->dict_keys_current);
+        dict_attack_set_current_sector(instance->dict_attack, mfc_dict->current_sector);
+    }
+}
+
+static void nfc_magic_scene_mf_classic_dict_attack_prepare_view(NfcMagicApp* instance) {
+    uint32_t state =
+        scene_manager_get_scene_state(instance->scene_manager, NfcMagicSceneMfClassicDictAttack);
+    if(state == DictAttackStateUserDictInProgress) {
+        do {
+            if(!keys_dict_check_presence(NFC_APP_MF_CLASSIC_DICT_USER_PATH)) {
+                state = DictAttackStateSystemDictInProgress;
+                break;
+            }
+
+            instance->nfc_dict_context.dict = keys_dict_alloc(
+                NFC_APP_MF_CLASSIC_DICT_USER_PATH, KeysDictModeOpenAlways, sizeof(MfClassicKey));
+            if(keys_dict_get_total_keys(instance->nfc_dict_context.dict) == 0) {
+                keys_dict_free(instance->nfc_dict_context.dict);
+                state = DictAttackStateSystemDictInProgress;
+                break;
+            }
+
+            dict_attack_set_header(instance->dict_attack, "MF Classic User Dictionary");
+        } while(false);
+    }
+    if(state == DictAttackStateSystemDictInProgress) {
+        instance->nfc_dict_context.dict = keys_dict_alloc(
+            NFC_APP_MF_CLASSIC_DICT_SYSTEM_PATH, KeysDictModeOpenExisting, sizeof(MfClassicKey));
+        dict_attack_set_header(instance->dict_attack, "MF Classic System Dictionary");
+    }
+
+    instance->nfc_dict_context.dict_keys_total =
+        keys_dict_get_total_keys(instance->nfc_dict_context.dict);
+    dict_attack_set_total_dict_keys(
+        instance->dict_attack, instance->nfc_dict_context.dict_keys_total);
+    instance->nfc_dict_context.dict_keys_current = 0;
+
+    dict_attack_set_callback(
+        instance->dict_attack, nfc_dict_attack_dict_attack_result_callback, instance);
+    nfc_magic_scene_mf_classic_dict_attack_update_view(instance);
+
+    scene_manager_set_scene_state(
+        instance->scene_manager, NfcMagicSceneMfClassicDictAttack, state);
+}
+
+void nfc_magic_scene_mf_classic_dict_attack_on_enter(void* context) {
+    NfcMagicApp* instance = context;
+
+    scene_manager_set_scene_state(
+        instance->scene_manager,
+        NfcMagicSceneMfClassicDictAttack,
+        DictAttackStateUserDictInProgress);
+    nfc_magic_scene_mf_classic_dict_attack_prepare_view(instance);
+    dict_attack_set_card_state(instance->dict_attack, true);
+    view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewDictAttack);
+    nfc_magic_app_blink_start(instance);
+    notification_message(instance->notifications, &sequence_display_backlight_enforce_on);
+
+    instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfClassic);
+    nfc_poller_start(instance->poller, nfc_dict_attack_worker_callback, instance);
+}
+
+static void nfc_magic_scene_mf_classic_dict_attack_notify_read(NfcMagicApp* instance) {
+    const MfClassicData* mfc_data = nfc_poller_get_data(instance->poller);
+    bool is_card_fully_read = mf_classic_is_card_read(mfc_data);
+    if(is_card_fully_read) {
+        notification_message(instance->notifications, &sequence_success);
+    } else {
+        notification_message(instance->notifications, &sequence_semi_success);
+    }
+}
+
+bool nfc_magic_scene_mf_classic_dict_attack_on_event(void* context, SceneManagerEvent event) {
+    NfcMagicApp* instance = context;
+    bool consumed = false;
+
+    uint32_t state =
+        scene_manager_get_scene_state(instance->scene_manager, NfcMagicSceneMfClassicDictAttack);
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == NfcMagicAppCustomEventDictAttackComplete) {
+            if(state == DictAttackStateUserDictInProgress) {
+                nfc_poller_stop(instance->poller);
+                nfc_poller_free(instance->poller);
+                keys_dict_free(instance->nfc_dict_context.dict);
+                scene_manager_set_scene_state(
+                    instance->scene_manager,
+                    NfcMagicSceneMfClassicDictAttack,
+                    DictAttackStateSystemDictInProgress);
+                nfc_magic_scene_mf_classic_dict_attack_prepare_view(instance);
+                instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfClassic);
+                nfc_poller_start(instance->poller, nfc_dict_attack_worker_callback, instance);
+                consumed = true;
+            } else {
+                nfc_magic_scene_mf_classic_dict_attack_notify_read(instance);
+                if(instance->protocol == NfcMagicProtocolGen2) {
+                    scene_manager_next_scene(instance->scene_manager, NfcMagicSceneGen2WriteCheck);
+                } else {
+                    scene_manager_next_scene(
+                        instance->scene_manager, NfcMagicSceneMfClassicWriteCheck);
+                }
+                dolphin_deed(DolphinDeedNfcReadSuccess);
+                consumed = true;
+            }
+        } else if(event.event == NfcMagicAppCustomEventCardDetected) {
+            dict_attack_set_card_state(instance->dict_attack, true);
+            consumed = true;
+        } else if(event.event == NfcMagicAppCustomEventCardLost) {
+            dict_attack_set_card_state(instance->dict_attack, false);
+            consumed = true;
+        } else if(event.event == NfcMagicAppCustomEventDictAttackDataUpdate) {
+            nfc_magic_scene_mf_classic_dict_attack_update_view(instance);
+        } else if(event.event == NfcMagicAppCustomEventDictAttackSkip) {
+            const MfClassicData* mfc_data = nfc_poller_get_data(instance->poller);
+            nfc_device_set_data(instance->target_dev, NfcProtocolMfClassic, mfc_data);
+            if(state == DictAttackStateUserDictInProgress) {
+                if(instance->nfc_dict_context.is_card_present) {
+                    nfc_poller_stop(instance->poller);
+                    nfc_poller_free(instance->poller);
+                    keys_dict_free(instance->nfc_dict_context.dict);
+                    scene_manager_set_scene_state(
+                        instance->scene_manager,
+                        NfcMagicSceneMfClassicDictAttack,
+                        DictAttackStateSystemDictInProgress);
+                    nfc_magic_scene_mf_classic_dict_attack_prepare_view(instance);
+                    instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfClassic);
+                    nfc_poller_start(instance->poller, nfc_dict_attack_worker_callback, instance);
+                } else {
+                    nfc_magic_scene_mf_classic_dict_attack_notify_read(instance);
+                    if(instance->protocol == NfcMagicProtocolGen2) {
+                        scene_manager_next_scene(
+                            instance->scene_manager, NfcMagicSceneGen2WriteCheck);
+                    } else {
+                        scene_manager_next_scene(
+                            instance->scene_manager, NfcMagicSceneMfClassicWriteCheck);
+                    }
+                    dolphin_deed(DolphinDeedNfcReadSuccess);
+                }
+                consumed = true;
+            } else if(state == DictAttackStateSystemDictInProgress) {
+                nfc_magic_scene_mf_classic_dict_attack_notify_read(instance);
+                if(instance->protocol == NfcMagicProtocolGen2) {
+                    scene_manager_next_scene(instance->scene_manager, NfcMagicSceneGen2WriteCheck);
+                } else {
+                    scene_manager_next_scene(
+                        instance->scene_manager, NfcMagicSceneMfClassicWriteCheck);
+                }
+                dolphin_deed(DolphinDeedNfcReadSuccess);
+                consumed = true;
+            }
+        }
+    } else if(event.type == SceneManagerEventTypeBack) {
+        scene_manager_previous_scene(instance->scene_manager);
+        consumed = true;
+    }
+    return consumed;
+}
+
+void nfc_magic_scene_mf_classic_dict_attack_on_exit(void* context) {
+    NfcMagicApp* instance = context;
+    const MfClassicData* mfc_data = nfc_poller_get_data(instance->poller);
+    nfc_device_set_data(instance->target_dev, NfcProtocolMfClassic, mfc_data);
+
+    nfc_poller_stop(instance->poller);
+    nfc_poller_free(instance->poller);
+
+    dict_attack_reset(instance->dict_attack);
+    scene_manager_set_scene_state(
+        instance->scene_manager,
+        NfcMagicSceneMfClassicDictAttack,
+        DictAttackStateUserDictInProgress);
+
+    keys_dict_free(instance->nfc_dict_context.dict);
+
+    instance->nfc_dict_context.current_sector = 0;
+    instance->nfc_dict_context.sectors_total = 0;
+    instance->nfc_dict_context.sectors_read = 0;
+    instance->nfc_dict_context.keys_found = 0;
+    instance->nfc_dict_context.dict_keys_total = 0;
+    instance->nfc_dict_context.dict_keys_current = 0;
+    instance->nfc_dict_context.is_key_attack = false;
+    instance->nfc_dict_context.key_attack_current_sector = 0;
+    instance->nfc_dict_context.is_card_present = false;
+
+    nfc_magic_app_blink_stop(instance);
+    notification_message(instance->notifications, &sequence_display_backlight_enforce_auto);
+}

+ 65 - 0
nfc_magic/scenes/nfc_magic_scene_mf_classic_menu.c

@@ -0,0 +1,65 @@
+#include "../nfc_magic_app_i.h"
+
+enum SubmenuIndex {
+    SubmenuIndexWrite,
+    SubmenuIndexWipe,
+};
+
+void nfc_magic_scene_mf_classic_menu_submenu_callback(void* context, uint32_t index) {
+    NfcMagicApp* instance = context;
+
+    view_dispatcher_send_custom_event(instance->view_dispatcher, index);
+}
+
+void nfc_magic_scene_mf_classic_menu_on_enter(void* context) {
+    NfcMagicApp* instance = context;
+
+    Submenu* submenu = instance->submenu;
+    submenu_add_item(
+        submenu,
+        "Write",
+        SubmenuIndexWrite,
+        nfc_magic_scene_mf_classic_menu_submenu_callback,
+        instance);
+    submenu_add_item(
+        submenu,
+        "Wipe",
+        SubmenuIndexWipe,
+        nfc_magic_scene_mf_classic_menu_submenu_callback,
+        instance);
+
+    submenu_set_selected_item(
+        submenu,
+        scene_manager_get_scene_state(instance->scene_manager, NfcMagicSceneMfClassicMenu));
+    view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewMenu);
+}
+
+bool nfc_magic_scene_mf_classic_menu_on_event(void* context, SceneManagerEvent event) {
+    NfcMagicApp* instance = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == SubmenuIndexWrite) {
+            instance->gen2_poller_is_wipe_mode = false;
+            scene_manager_next_scene(instance->scene_manager, NfcMagicSceneFileSelect);
+            consumed = true;
+        } else if(event.event == SubmenuIndexWipe) {
+            instance->gen2_poller_is_wipe_mode = true;
+            scene_manager_next_scene(instance->scene_manager, NfcMagicSceneMfClassicDictAttack);
+            consumed = true;
+        }
+        scene_manager_set_scene_state(
+            instance->scene_manager, NfcMagicSceneMfClassicMenu, event.event);
+    } else if(event.type == SceneManagerEventTypeBack) {
+        consumed = scene_manager_search_and_switch_to_previous_scene(
+            instance->scene_manager, NfcMagicSceneStart);
+    }
+
+    return consumed;
+}
+
+void nfc_magic_scene_mf_classic_menu_on_exit(void* context) {
+    NfcMagicApp* instance = context;
+
+    submenu_reset(instance->submenu);
+}

+ 121 - 0
nfc_magic/scenes/nfc_magic_scene_mf_classic_write_check.c

@@ -0,0 +1,121 @@
+#include "../nfc_magic_app_i.h"
+
+void nfc_magic_scene_mf_classic_write_check_widget_callback(
+    GuiButtonType result,
+    InputType type,
+    void* context) {
+    NfcMagicApp* instance = context;
+
+    if(type == InputTypeShort) {
+        view_dispatcher_send_custom_event(instance->view_dispatcher, result);
+    }
+}
+
+void nfc_magic_scene_mf_classic_write_check_on_enter(void* context) {
+    NfcMagicApp* instance = context;
+    Widget* widget = instance->widget;
+    Gen2PollerWriteProblem can_write_all = gen2_poller_can_write_everything(instance->target_dev);
+    furi_assert(can_write_all != Gen2PollerWriteProblemNoData, "No MFC data in nfc device");
+
+    widget_add_string_element(widget, 3, 0, AlignLeft, AlignTop, FontPrimary, "Write problems");
+
+    if(instance->gen2_poller_is_wipe_mode) {
+        widget_add_text_box_element(
+            widget,
+            0,
+            13,
+            128,
+            54,
+            AlignLeft,
+            AlignTop,
+            "Not all sectors can be wiped\nTry wiping anyway?",
+            false);
+    } else {
+        if(can_write_all == Gen2PollerWriteProblemNone) {
+            widget_add_text_box_element(
+                widget,
+                0,
+                13,
+                128,
+                54,
+                AlignLeft,
+                AlignTop,
+                "This card might not have a\nrewriteable UID\nCloned card may not work\nTry writing anyway?",
+                false);
+        } else if(can_write_all == Gen2PollerWriteProblemMissingSourceData) {
+            widget_add_text_box_element(
+                widget,
+                0,
+                13,
+                128,
+                54,
+                AlignLeft,
+                AlignTop,
+                "The source dump is incomplete\nCloned card may not work\nTry writing anyway?",
+                false);
+        } else if(can_write_all == Gen2PollerWriteProblemMissingTargetKeys) {
+            widget_add_text_box_element(
+                widget,
+                0,
+                13,
+                128,
+                54,
+                AlignLeft,
+                AlignTop,
+                "Keys to write some sectors\nare not available\nCloned card may not work\nTry writing anyway?",
+                false);
+        } else if(can_write_all == Gen2PollerWriteProblemLockedAccessBits) {
+            widget_add_text_box_element(
+                widget,
+                0,
+                13,
+                128,
+                54,
+                AlignLeft,
+                AlignTop,
+                "Target card doesn't allow\nwriting in some cases\nCloned card may not work\nTry writing anyway?",
+                false);
+        }
+    }
+    widget_add_button_element(
+        widget,
+        GuiButtonTypeCenter,
+        "Continue",
+        nfc_magic_scene_mf_classic_write_check_widget_callback,
+        instance);
+    widget_add_button_element(
+        widget,
+        GuiButtonTypeLeft,
+        "Back",
+        nfc_magic_scene_mf_classic_write_check_widget_callback,
+        instance);
+
+    // Setup and start worker
+    view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewWidget);
+}
+
+bool nfc_magic_scene_mf_classic_write_check_on_event(void* context, SceneManagerEvent event) {
+    NfcMagicApp* instance = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == GuiButtonTypeLeft) {
+            consumed = scene_manager_search_and_switch_to_previous_scene(
+                instance->scene_manager, NfcMagicSceneMfClassicMenu);
+        } else if(event.event == GuiButtonTypeCenter) {
+            if(instance->gen2_poller_is_wipe_mode) {
+                scene_manager_next_scene(instance->scene_manager, NfcMagicSceneWipe);
+            } else {
+                scene_manager_next_scene(instance->scene_manager, NfcMagicSceneWrite);
+            }
+            consumed = true;
+        }
+    }
+    return consumed;
+}
+
+void nfc_magic_scene_mf_classic_write_check_on_exit(void* context) {
+    NfcMagicApp* instance = context;
+
+    widget_reset(instance->widget);
+}

+ 48 - 6
nfc_magic/scenes/nfc_magic_scene_wipe.c

@@ -5,7 +5,7 @@ enum {
     NfcMagicSceneWipeStateCardFound,
 };
 
-NfcCommand nfc_mafic_scene_wipe_gen1_poller_callback(Gen1aPollerEvent event, void* context) {
+NfcCommand nfc_magic_scene_wipe_gen1_poller_callback(Gen1aPollerEvent event, void* context) {
     NfcMagicApp* instance = context;
     furi_assert(event.data);
 
@@ -29,7 +29,35 @@ NfcCommand nfc_mafic_scene_wipe_gen1_poller_callback(Gen1aPollerEvent event, voi
     return command;
 }
 
-NfcCommand nfc_mafic_scene_wipe_gen4_poller_callback(Gen4PollerEvent event, void* context) {
+NfcCommand nfc_magic_scene_wipe_gen2_poller_callback(Gen2PollerEvent event, void* context) {
+    NfcMagicApp* instance = context;
+
+    NfcCommand command = NfcCommandContinue;
+
+    if(event.type == Gen2PollerEventTypeDetected) {
+        view_dispatcher_send_custom_event(
+            instance->view_dispatcher, NfcMagicCustomEventCardDetected);
+    } else if(event.type == Gen2PollerEventTypeRequestMode) {
+        event.data->poller_mode.mode = Gen2PollerModeWipe;
+    } else if(event.type == Gen2PollerEventTypeRequestTargetData) {
+        const MfClassicData* mfc_data =
+            nfc_device_get_data(instance->target_dev, NfcProtocolMfClassic);
+        event.data->target_data.mfc_data = mfc_data;
+
+    } else if(event.type == Gen2PollerEventTypeSuccess) {
+        view_dispatcher_send_custom_event(
+            instance->view_dispatcher, NfcMagicCustomEventWorkerSuccess);
+        command = NfcCommandStop;
+    } else if(event.type == Gen2PollerEventTypeFail) {
+        view_dispatcher_send_custom_event(
+            instance->view_dispatcher, NfcMagicCustomEventWorkerFail);
+        command = NfcCommandStop;
+    }
+
+    return command;
+}
+
+NfcCommand nfc_magic_scene_wipe_gen4_poller_callback(Gen4PollerEvent event, void* context) {
     NfcMagicApp* instance = context;
 
     NfcCommand command = NfcCommandContinue;
@@ -81,12 +109,20 @@ void nfc_magic_scene_wipe_on_enter(void* context) {
     if(instance->protocol == NfcMagicProtocolGen1) {
         instance->gen1a_poller = gen1a_poller_alloc(instance->nfc);
         gen1a_poller_start(
-            instance->gen1a_poller, nfc_mafic_scene_wipe_gen1_poller_callback, instance);
-    } else {
+            instance->gen1a_poller, nfc_magic_scene_wipe_gen1_poller_callback, instance);
+    } else if(instance->protocol == NfcMagicProtocolGen2) {
+        instance->gen2_poller = gen2_poller_alloc(instance->nfc);
+        gen2_poller_start(
+            instance->gen2_poller, nfc_magic_scene_wipe_gen2_poller_callback, instance);
+    } else if(instance->protocol == NfcMagicProtocolClassic) {
+        instance->gen2_poller = gen2_poller_alloc(instance->nfc);
+        gen2_poller_start(
+            instance->gen2_poller, nfc_magic_scene_wipe_gen2_poller_callback, instance);
+    } else if(instance->protocol == NfcMagicProtocolGen4) {
         instance->gen4_poller = gen4_poller_alloc(instance->nfc);
         gen4_poller_set_password(instance->gen4_poller, instance->gen4_password);
         gen4_poller_start(
-            instance->gen4_poller, nfc_mafic_scene_wipe_gen4_poller_callback, instance);
+            instance->gen4_poller, nfc_magic_scene_wipe_gen4_poller_callback, instance);
     }
 }
 
@@ -123,7 +159,13 @@ void nfc_magic_scene_wipe_on_exit(void* context) {
     if(instance->protocol == NfcMagicProtocolGen1) {
         gen1a_poller_stop(instance->gen1a_poller);
         gen1a_poller_free(instance->gen1a_poller);
-    } else {
+    } else if(instance->protocol == NfcMagicProtocolGen2) {
+        gen2_poller_stop(instance->gen2_poller);
+        gen2_poller_free(instance->gen2_poller);
+    } else if(instance->protocol == NfcMagicProtocolClassic) {
+        gen2_poller_stop(instance->gen2_poller);
+        gen2_poller_free(instance->gen2_poller);
+    } else if(instance->protocol == NfcMagicProtocolGen4) {
         gen4_poller_stop(instance->gen4_poller);
         gen4_poller_free(instance->gen4_poller);
     }

+ 50 - 5
nfc_magic/scenes/nfc_magic_scene_write.c

@@ -5,7 +5,7 @@ enum {
     NfcMagicSceneWriteStateCardFound,
 };
 
-NfcCommand nfc_mafic_scene_write_gen1_poller_callback(Gen1aPollerEvent event, void* context) {
+NfcCommand nfc_magic_scene_write_gen1_poller_callback(Gen1aPollerEvent event, void* context) {
     NfcMagicApp* instance = context;
     furi_assert(event.data);
 
@@ -33,7 +33,39 @@ NfcCommand nfc_mafic_scene_write_gen1_poller_callback(Gen1aPollerEvent event, vo
     return command;
 }
 
-NfcCommand nfc_mafic_scene_write_gen4_poller_callback(Gen4PollerEvent event, void* context) {
+NfcCommand nfc_magic_scene_write_gen2_poller_callback(Gen2PollerEvent event, void* context) {
+    NfcMagicApp* instance = context;
+    furi_assert(event.data);
+
+    NfcCommand command = NfcCommandContinue;
+
+    if(event.type == Gen2PollerEventTypeDetected) {
+        view_dispatcher_send_custom_event(
+            instance->view_dispatcher, NfcMagicCustomEventCardDetected);
+    } else if(event.type == Gen2PollerEventTypeRequestMode) {
+        event.data->poller_mode.mode = Gen2PollerModeWrite;
+    } else if(event.type == Gen2PollerEventTypeRequestDataToWrite) {
+        const MfClassicData* mfc_data =
+            nfc_device_get_data(instance->source_dev, NfcProtocolMfClassic);
+        event.data->data_to_write.mfc_data = mfc_data;
+    } else if(event.type == Gen2PollerEventTypeRequestTargetData) {
+        const MfClassicData* mfc_data =
+            nfc_device_get_data(instance->target_dev, NfcProtocolMfClassic);
+        event.data->target_data.mfc_data = mfc_data;
+    } else if(event.type == Gen2PollerEventTypeSuccess) {
+        view_dispatcher_send_custom_event(
+            instance->view_dispatcher, NfcMagicCustomEventWorkerSuccess);
+        command = NfcCommandStop;
+    } else if(event.type == Gen2PollerEventTypeFail) {
+        view_dispatcher_send_custom_event(
+            instance->view_dispatcher, NfcMagicCustomEventWorkerFail);
+        command = NfcCommandStop;
+    }
+
+    return command;
+}
+
+NfcCommand nfc_magic_scene_write_gen4_poller_callback(Gen4PollerEvent event, void* context) {
     NfcMagicApp* instance = context;
     furi_assert(event.data);
 
@@ -90,12 +122,20 @@ void nfc_magic_scene_write_on_enter(void* context) {
     if(instance->protocol == NfcMagicProtocolGen1) {
         instance->gen1a_poller = gen1a_poller_alloc(instance->nfc);
         gen1a_poller_start(
-            instance->gen1a_poller, nfc_mafic_scene_write_gen1_poller_callback, instance);
+            instance->gen1a_poller, nfc_magic_scene_write_gen1_poller_callback, instance);
+    } else if(instance->protocol == NfcMagicProtocolGen2) {
+        instance->gen2_poller = gen2_poller_alloc(instance->nfc);
+        gen2_poller_start(
+            instance->gen2_poller, nfc_magic_scene_write_gen2_poller_callback, instance);
+    } else if(instance->protocol == NfcMagicProtocolClassic) {
+        instance->gen2_poller = gen2_poller_alloc(instance->nfc);
+        gen2_poller_start(
+            instance->gen2_poller, nfc_magic_scene_write_gen2_poller_callback, instance);
     } else {
         instance->gen4_poller = gen4_poller_alloc(instance->nfc);
         gen4_poller_set_password(instance->gen4_poller, instance->gen4_password);
         gen4_poller_start(
-            instance->gen4_poller, nfc_mafic_scene_write_gen4_poller_callback, instance);
+            instance->gen4_poller, nfc_magic_scene_write_gen4_poller_callback, instance);
     }
 }
 
@@ -132,7 +172,12 @@ void nfc_magic_scene_write_on_exit(void* context) {
     if(instance->protocol == NfcMagicProtocolGen1) {
         gen1a_poller_stop(instance->gen1a_poller);
         gen1a_poller_free(instance->gen1a_poller);
-    } else {
+    } else if(
+        instance->protocol == NfcMagicProtocolGen2 ||
+        instance->protocol == NfcMagicProtocolClassic) {
+        gen2_poller_stop(instance->gen2_poller);
+        gen2_poller_free(instance->gen2_poller);
+    } else if(instance->protocol == NfcMagicProtocolGen4) {
         gen4_poller_stop(instance->gen4_poller);
         gen4_poller_free(instance->gen4_poller);
     }

+ 245 - 0
nfc_magic/views/dict_attack.c

@@ -0,0 +1,245 @@
+#include "dict_attack.h"
+
+#include <gui/elements.h>
+
+#define NFC_CLASSIC_KEYS_PER_SECTOR 2
+
+struct DictAttack {
+    View* view;
+    DictAttackCallback callback;
+    void* context;
+};
+
+typedef struct {
+    FuriString* header;
+    bool card_detected;
+    uint8_t sectors_total;
+    uint8_t sectors_read;
+    uint8_t current_sector;
+    uint8_t keys_found;
+    size_t dict_keys_total;
+    size_t dict_keys_current;
+    bool is_key_attack;
+    uint8_t key_attack_current_sector;
+} DictAttackViewModel;
+
+static void dict_attack_draw_callback(Canvas* canvas, void* model) {
+    DictAttackViewModel* m = model;
+    if(!m->card_detected) {
+        canvas_set_font(canvas, FontPrimary);
+        canvas_draw_str_aligned(
+            canvas, 64, 4, AlignCenter, AlignTop, "Hold the tag to the Flipper!");
+        canvas_set_font(canvas, FontSecondary);
+        elements_multiline_text_aligned(
+            canvas, 64, 23, AlignCenter, AlignTop, "Make sure the tag is\npositioned correctly.");
+    } else {
+        char draw_str[32] = {};
+        canvas_set_font(canvas, FontSecondary);
+        canvas_draw_str_aligned(
+            canvas, 64, 0, AlignCenter, AlignTop, furi_string_get_cstr(m->header));
+        if(m->is_key_attack) {
+            snprintf(
+                draw_str,
+                sizeof(draw_str),
+                "Reuse key check for sector: %d",
+                m->key_attack_current_sector);
+        } else {
+            snprintf(draw_str, sizeof(draw_str), "Unlocking sector: %d", m->current_sector);
+        }
+        canvas_draw_str_aligned(canvas, 0, 10, AlignLeft, AlignTop, draw_str);
+        float dict_progress = m->dict_keys_total == 0 ?
+                                  0 :
+                                  (float)(m->dict_keys_current) / (float)(m->dict_keys_total);
+        float progress = m->sectors_total == 0 ? 0 :
+                                                 ((float)(m->current_sector) + dict_progress) /
+                                                     (float)(m->sectors_total);
+        if(progress > 1.0f) {
+            progress = 1.0f;
+        }
+        if(m->dict_keys_current == 0) {
+            // Cause when people see 0 they think it's broken
+            snprintf(draw_str, sizeof(draw_str), "%d/%zu", 1, m->dict_keys_total);
+        } else {
+            snprintf(
+                draw_str, sizeof(draw_str), "%zu/%zu", m->dict_keys_current, m->dict_keys_total);
+        }
+        elements_progress_bar_with_text(canvas, 0, 20, 128, dict_progress, draw_str);
+        canvas_set_font(canvas, FontSecondary);
+        snprintf(
+            draw_str,
+            sizeof(draw_str),
+            "Keys found: %d/%d",
+            m->keys_found,
+            m->sectors_total * NFC_CLASSIC_KEYS_PER_SECTOR);
+        canvas_draw_str_aligned(canvas, 0, 33, AlignLeft, AlignTop, draw_str);
+        snprintf(
+            draw_str, sizeof(draw_str), "Sectors Read: %d/%d", m->sectors_read, m->sectors_total);
+        canvas_draw_str_aligned(canvas, 0, 43, AlignLeft, AlignTop, draw_str);
+    }
+    elements_button_center(canvas, "Skip");
+}
+
+static bool dict_attack_input_callback(InputEvent* event, void* context) {
+    DictAttack* instance = context;
+    bool consumed = false;
+
+    if(event->type == InputTypeShort && event->key == InputKeyOk) {
+        if(instance->callback) {
+            instance->callback(DictAttackEventSkipPressed, instance->context);
+        }
+        consumed = true;
+    }
+
+    return consumed;
+}
+
+DictAttack* dict_attack_alloc() {
+    DictAttack* instance = malloc(sizeof(DictAttack));
+    instance->view = view_alloc();
+    view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(DictAttackViewModel));
+    view_set_draw_callback(instance->view, dict_attack_draw_callback);
+    view_set_input_callback(instance->view, dict_attack_input_callback);
+    view_set_context(instance->view, instance);
+    with_view_model(
+        instance->view,
+        DictAttackViewModel * model,
+        { model->header = furi_string_alloc(); },
+        false);
+
+    return instance;
+}
+
+void dict_attack_free(DictAttack* instance) {
+    furi_assert(instance);
+
+    with_view_model(
+        instance->view, DictAttackViewModel * model, { furi_string_free(model->header); }, false);
+
+    view_free(instance->view);
+    free(instance);
+}
+
+void dict_attack_reset(DictAttack* instance) {
+    furi_assert(instance);
+
+    with_view_model(
+        instance->view,
+        DictAttackViewModel * model,
+        {
+            model->sectors_total = 0;
+            model->sectors_read = 0;
+            model->current_sector = 0;
+            model->keys_found = 0;
+            model->dict_keys_total = 0;
+            model->dict_keys_current = 0;
+            model->is_key_attack = false;
+            furi_string_reset(model->header);
+        },
+        false);
+}
+
+View* dict_attack_get_view(DictAttack* instance) {
+    furi_assert(instance);
+
+    return instance->view;
+}
+
+void dict_attack_set_callback(DictAttack* instance, DictAttackCallback callback, void* context) {
+    furi_assert(instance);
+    furi_assert(callback);
+
+    instance->callback = callback;
+    instance->context = context;
+}
+
+void dict_attack_set_header(DictAttack* instance, const char* header) {
+    furi_assert(instance);
+    furi_assert(header);
+
+    with_view_model(
+        instance->view,
+        DictAttackViewModel * model,
+        { furi_string_set(model->header, header); },
+        true);
+}
+
+void dict_attack_set_card_state(DictAttack* instance, bool detected) {
+    furi_assert(instance);
+
+    with_view_model(
+        instance->view, DictAttackViewModel * model, { model->card_detected = detected; }, true);
+}
+
+void dict_attack_set_sectors_total(DictAttack* instance, uint8_t sectors_total) {
+    furi_assert(instance);
+
+    with_view_model(
+        instance->view,
+        DictAttackViewModel * model,
+        { model->sectors_total = sectors_total; },
+        true);
+}
+
+void dict_attack_set_sectors_read(DictAttack* instance, uint8_t sectors_read) {
+    furi_assert(instance);
+
+    with_view_model(
+        instance->view, DictAttackViewModel * model, { model->sectors_read = sectors_read; }, true);
+}
+
+void dict_attack_set_keys_found(DictAttack* instance, uint8_t keys_found) {
+    furi_assert(instance);
+
+    with_view_model(
+        instance->view, DictAttackViewModel * model, { model->keys_found = keys_found; }, true);
+}
+
+void dict_attack_set_current_sector(DictAttack* instance, uint8_t current_sector) {
+    furi_assert(instance);
+
+    with_view_model(
+        instance->view,
+        DictAttackViewModel * model,
+        { model->current_sector = current_sector; },
+        true);
+}
+
+void dict_attack_set_total_dict_keys(DictAttack* instance, size_t dict_keys_total) {
+    furi_assert(instance);
+
+    with_view_model(
+        instance->view,
+        DictAttackViewModel * model,
+        { model->dict_keys_total = dict_keys_total; },
+        true);
+}
+
+void dict_attack_set_current_dict_key(DictAttack* instance, size_t cur_key_num) {
+    furi_assert(instance);
+
+    with_view_model(
+        instance->view,
+        DictAttackViewModel * model,
+        { model->dict_keys_current = cur_key_num; },
+        true);
+}
+
+void dict_attack_set_key_attack(DictAttack* instance, uint8_t sector) {
+    furi_assert(instance);
+
+    with_view_model(
+        instance->view,
+        DictAttackViewModel * model,
+        {
+            model->is_key_attack = true;
+            model->key_attack_current_sector = sector;
+        },
+        true);
+}
+
+void dict_attack_reset_key_attack(DictAttack* instance) {
+    furi_assert(instance);
+
+    with_view_model(
+        instance->view, DictAttackViewModel * model, { model->is_key_attack = false; }, true);
+}

+ 50 - 0
nfc_magic/views/dict_attack.h

@@ -0,0 +1,50 @@
+#pragma once
+
+#include <stdint.h>
+#include <gui/view.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct DictAttack DictAttack;
+
+typedef enum {
+    DictAttackEventSkipPressed,
+} DictAttackEvent;
+
+typedef void (*DictAttackCallback)(DictAttackEvent event, void* context);
+
+DictAttack* dict_attack_alloc();
+
+void dict_attack_free(DictAttack* instance);
+
+void dict_attack_reset(DictAttack* instance);
+
+View* dict_attack_get_view(DictAttack* instance);
+
+void dict_attack_set_callback(DictAttack* instance, DictAttackCallback callback, void* context);
+
+void dict_attack_set_header(DictAttack* instance, const char* header);
+
+void dict_attack_set_card_state(DictAttack* instance, bool detected);
+
+void dict_attack_set_sectors_total(DictAttack* instance, uint8_t sectors_total);
+
+void dict_attack_set_sectors_read(DictAttack* instance, uint8_t sectors_read);
+
+void dict_attack_set_keys_found(DictAttack* instance, uint8_t keys_found);
+
+void dict_attack_set_current_sector(DictAttack* instance, uint8_t curr_sec);
+
+void dict_attack_set_total_dict_keys(DictAttack* instance, size_t dict_keys_total);
+
+void dict_attack_set_current_dict_key(DictAttack* instance, size_t cur_key_num);
+
+void dict_attack_set_key_attack(DictAttack* instance, uint8_t sector);
+
+void dict_attack_reset_key_attack(DictAttack* instance);
+
+#ifdef __cplusplus
+}
+#endif