Bläddra i källkod

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

This reverts commit 527888be02ec56bbd70f0fc113727d1a993870f2, reversing
changes made to c1bea0f84fdb2c858ab9a1f4bbfc3c4818f31771.
Willy-JL 1 år sedan
förälder
incheckning
c60c3ea8f7
48 ändrade filer med 3529 tillägg och 648 borttagningar
  1. BIN
      nfc_magic/assets/DolphinSuccess_91x55.png
  2. BIN
      nfc_magic/assets/WarningDolphinFlip_45x42.png
  3. 28 29
      nfc_magic/lib/magic/nfc_magic_scanner.c
  4. 1 4
      nfc_magic/lib/magic/nfc_magic_scanner.h
  5. 178 0
      nfc_magic/lib/magic/protocols/gen2/crypto1.c
  6. 45 0
      nfc_magic/lib/magic/protocols/gen2/crypto1.h
  7. 594 0
      nfc_magic/lib/magic/protocols/gen2/gen2_poller.c
  8. 97 0
      nfc_magic/lib/magic/protocols/gen2/gen2_poller.h
  9. 629 0
      nfc_magic/lib/magic/protocols/gen2/gen2_poller_i.c
  10. 139 0
      nfc_magic/lib/magic/protocols/gen2/gen2_poller_i.h
  11. 0 97
      nfc_magic/lib/magic/protocols/gen4/gen4.c
  12. 0 101
      nfc_magic/lib/magic/protocols/gen4/gen4.h
  13. 61 156
      nfc_magic/lib/magic/protocols/gen4/gen4_poller.c
  14. 8 6
      nfc_magic/lib/magic/protocols/gen4/gen4_poller.h
  15. 95 59
      nfc_magic/lib/magic/protocols/gen4/gen4_poller_i.c
  16. 56 26
      nfc_magic/lib/magic/protocols/gen4/gen4_poller_i.h
  17. 4 2
      nfc_magic/lib/magic/protocols/nfc_magic_protocols.c
  18. 2 0
      nfc_magic/lib/magic/protocols/nfc_magic_protocols.h
  19. 29 6
      nfc_magic/nfc_magic_app.c
  20. 54 3
      nfc_magic/nfc_magic_app_i.h
  21. 6 0
      nfc_magic/scenes/nfc_magic_scene_config.h
  22. 15 1
      nfc_magic/scenes/nfc_magic_scene_file_select.c
  23. 55 0
      nfc_magic/scenes/nfc_magic_scene_gen2_menu.c
  24. 130 0
      nfc_magic/scenes/nfc_magic_scene_gen2_write_check.c
  25. 3 8
      nfc_magic/scenes/nfc_magic_scene_gen4_get_info.c
  26. 8 29
      nfc_magic/scenes/nfc_magic_scene_gen4_menu.c
  27. 96 0
      nfc_magic/scenes/nfc_magic_scene_gen4_select_direct_write_block_0_mode.c
  28. 9 5
      nfc_magic/scenes/nfc_magic_scene_gen4_select_shd_mode.c
  29. 1 1
      nfc_magic/scenes/nfc_magic_scene_gen4_set_default_cfg.c
  30. 1 2
      nfc_magic/scenes/nfc_magic_scene_gen4_set_direct_write_block_0_mode.c
  31. 1 1
      nfc_magic/scenes/nfc_magic_scene_gen4_set_shd_mode.c
  32. 6 7
      nfc_magic/scenes/nfc_magic_scene_gen4_show_cfg.c
  33. 56 28
      nfc_magic/scenes/nfc_magic_scene_gen4_show_info.c
  34. 5 10
      nfc_magic/scenes/nfc_magic_scene_key_input.c
  35. 18 37
      nfc_magic/scenes/nfc_magic_scene_magic_info.c
  36. 311 0
      nfc_magic/scenes/nfc_magic_scene_mf_classic_dict_attack.c
  37. 65 0
      nfc_magic/scenes/nfc_magic_scene_mf_classic_menu.c
  38. 124 0
      nfc_magic/scenes/nfc_magic_scene_mf_classic_write_check.c
  39. 1 1
      nfc_magic/scenes/nfc_magic_scene_start.c
  40. 2 2
      nfc_magic/scenes/nfc_magic_scene_success.c
  41. 48 6
      nfc_magic/scenes/nfc_magic_scene_wipe.c
  42. 5 2
      nfc_magic/scenes/nfc_magic_scene_wipe_fail.c
  43. 50 5
      nfc_magic/scenes/nfc_magic_scene_write.c
  44. 6 14
      nfc_magic/scenes/nfc_magic_scene_write_fail.c
  45. 245 0
      nfc_magic/views/dict_attack.c
  46. 50 0
      nfc_magic/views/dict_attack.h
  47. 160 0
      nfc_magic/views/write_problems.c
  48. 32 0
      nfc_magic/views/write_problems.h

BIN
nfc_magic/assets/DolphinSuccess_91x55.png


BIN
nfc_magic/assets/WarningDolphinFlip_45x42.png


+ 28 - 29
nfc_magic/lib/magic/nfc_magic_scanner.c

@@ -1,8 +1,7 @@
 #include "nfc_magic_scanner.h"
 #include "nfc_magic_scanner.h"
 
 
-#include "core/check.h"
 #include "protocols/gen1a/gen1a_poller.h"
 #include "protocols/gen1a/gen1a_poller.h"
-#include "protocols/gen4/gen4.h"
+#include "protocols/gen2/gen2_poller.h"
 #include "protocols/gen4/gen4_poller.h"
 #include "protocols/gen4/gen4_poller.h"
 #include <nfc/nfc_poller.h>
 #include <nfc/nfc_poller.h>
 
 
@@ -19,8 +18,7 @@ struct NfcMagicScanner {
     NfcMagicScannerSessionState session_state;
     NfcMagicScannerSessionState session_state;
     NfcMagicProtocol current_protocol;
     NfcMagicProtocol current_protocol;
 
 
-    Gen4Password gen4_password;
-    Gen4* gen4_data;
+    uint32_t gen4_password;
     bool magic_protocol_detected;
     bool magic_protocol_detected;
 
 
     NfcMagicScannerCallback callback;
     NfcMagicScannerCallback callback;
@@ -45,7 +43,6 @@ NfcMagicScanner* nfc_magic_scanner_alloc(Nfc* nfc) {
 
 
     NfcMagicScanner* instance = malloc(sizeof(NfcMagicScanner));
     NfcMagicScanner* instance = malloc(sizeof(NfcMagicScanner));
     instance->nfc = nfc;
     instance->nfc = nfc;
-    instance->gen4_data = gen4_alloc();
 
 
     return instance;
     return instance;
 }
 }
@@ -53,11 +50,10 @@ NfcMagicScanner* nfc_magic_scanner_alloc(Nfc* nfc) {
 void nfc_magic_scanner_free(NfcMagicScanner* instance) {
 void nfc_magic_scanner_free(NfcMagicScanner* instance) {
     furi_assert(instance);
     furi_assert(instance);
 
 
-    gen4_free(instance->gen4_data);
     free(instance);
     free(instance);
 }
 }
 
 
-void nfc_magic_scanner_set_gen4_password(NfcMagicScanner* instance, Gen4Password password) {
+void nfc_magic_scanner_set_gen4_password(NfcMagicScanner* instance, uint32_t password) {
     furi_assert(instance);
     furi_assert(instance);
 
 
     instance->gen4_password = password;
     instance->gen4_password = password;
@@ -70,24 +66,33 @@ static int32_t nfc_magic_scanner_worker(void* context) {
     furi_assert(instance->session_state == NfcMagicScannerSessionStateActive);
     furi_assert(instance->session_state == NfcMagicScannerSessionStateActive);
 
 
     while(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) {
-            gen4_reset(instance->gen4_data);
-            Gen4 gen4_data;
-            Gen4PollerError error =
-                gen4_poller_detect(instance->nfc, instance->gen4_password, &gen4_data);
-            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);
                 instance->magic_protocol_detected = (error == Gen4PollerErrorNone);
-                gen4_copy(instance->gen4_data, &gen4_data);
+                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) {
         if(instance->magic_protocol_detected) {
             NfcMagicScannerEvent event = {
             NfcMagicScannerEvent event = {
@@ -157,9 +162,3 @@ void nfc_magic_scanner_stop(NfcMagicScanner* instance) {
     instance->callback = NULL;
     instance->callback = NULL;
     instance->context = NULL;
     instance->context = NULL;
 }
 }
-
-Gen4* nfc_magic_scanner_get_gen4_data(NfcMagicScanner* instance) {
-    furi_assert(instance);
-
-    return instance->gen4_data;
-}

+ 1 - 4
nfc_magic/lib/magic/nfc_magic_scanner.h

@@ -1,6 +1,5 @@
 #pragma once
 #pragma once
 
 
-#include "protocols/gen4/gen4.h"
 #include <nfc/nfc.h>
 #include <nfc/nfc.h>
 #include "protocols/nfc_magic_protocols.h"
 #include "protocols/nfc_magic_protocols.h"
 
 
@@ -31,7 +30,7 @@ NfcMagicScanner* nfc_magic_scanner_alloc(Nfc* nfc);
 
 
 void nfc_magic_scanner_free(NfcMagicScanner* instance);
 void nfc_magic_scanner_free(NfcMagicScanner* instance);
 
 
-void nfc_magic_scanner_set_gen4_password(NfcMagicScanner* instance, Gen4Password password);
+void nfc_magic_scanner_set_gen4_password(NfcMagicScanner* instance, uint32_t password);
 
 
 void nfc_magic_scanner_start(
 void nfc_magic_scanner_start(
     NfcMagicScanner* instance,
     NfcMagicScanner* instance,
@@ -40,8 +39,6 @@ void nfc_magic_scanner_start(
 
 
 void nfc_magic_scanner_stop(NfcMagicScanner* instance);
 void nfc_magic_scanner_stop(NfcMagicScanner* instance);
 
 
-Gen4* nfc_magic_scanner_get_gen4_data(NfcMagicScanner* instance);
-
 #ifdef __cplusplus
 #ifdef __cplusplus
 }
 }
 #endif
 #endif

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

@@ -0,0 +1,178 @@
+#include "crypto1.h"
+
+#include <nfc/helpers/nfc_util.h>
+#include <bit_lib/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

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

@@ -0,0 +1,594 @@
+#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},
+};
+
+char* gen2_problem_strings[] = {
+    "UID may be non-\nrewritable. Check data after writing",
+    "No data in selected file",
+    "Some sectors are locked",
+    "Can't find keys to some sectors",
+    "The selected file is incomplete",
+};
+
+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);
+    }
+
+    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;
+}
+
+Gen2PollerWriteProblems gen2_poller_check_target_problems(NfcDevice* target_dev) {
+    furi_assert(target_dev);
+
+    Gen2PollerWriteProblems problems = {0};
+    const MfClassicData* mfc_data = nfc_device_get_data(target_dev, 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)) {
+                problems.all_problems |=
+                    gen2_poller_can_write_sector_trailer(mfc_data, i).all_problems;
+            } else {
+                problems.all_problems |=
+                    gen2_poller_can_write_data_block(mfc_data, i).all_problems;
+            }
+        }
+    } else {
+        problems.no_data = true;
+    }
+
+    return problems;
+}
+
+Gen2PollerWriteProblems gen2_poller_check_source_problems(NfcDevice* source_dev) {
+    furi_assert(source_dev);
+
+    Gen2PollerWriteProblems problems = {0};
+    const MfClassicData* mfc_data = nfc_device_get_data(source_dev, 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_block_read(mfc_data, i)) {
+                problems.missing_source_data = true;
+            }
+        }
+    }
+
+    return problems;
+}

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

@@ -0,0 +1,97 @@
+#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 union {
+    uint8_t all_problems;
+    struct {
+        bool uid_locked : 1; // UID may be non-rewritable. Check data after writing
+        bool no_data : 1; // Shouldn't happen, mfc_data missing in nfc device
+        bool locked_access_bits : 1; // Access bits on the target card don't allow writing in some cases
+        bool missing_target_keys : 1; // Keys to write some sectors are not available
+        bool missing_source_data : 1; // The source dump is incomplete
+    };
+} Gen2PollerWriteProblems;
+
+#define GEN2_POLLER_WRITE_PROBLEMS_LEN (5)
+
+extern char* gen2_problem_strings[];
+
+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);
+
+Gen2PollerWriteProblems gen2_poller_check_target_problems(NfcDevice* target_dev);
+
+Gen2PollerWriteProblems gen2_poller_check_source_problems(NfcDevice* source_dev);
+
+#ifdef __cplusplus
+}
+#endif

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

@@ -0,0 +1,629 @@
+#include "gen2_poller_i.h"
+#include <nfc/helpers/iso14443_crc.h>
+
+#include <bit_lib/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* target_data, uint8_t block_num) {
+    furi_assert(target_data);
+
+    bool can_write = true;
+
+    if(block_num == 0 && target_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(target_data, block_num).all_problems == 0;
+    } else {
+        can_write = gen2_poller_can_write_data_block(target_data, block_num).all_problems == 0;
+    }
+
+    return can_write;
+}
+
+Gen2PollerWriteProblems
+    gen2_poller_can_write_data_block(const MfClassicData* target_data, uint8_t block_num) {
+    // Check whether it's possible to write the block
+    furi_assert(target_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
+
+    Gen2PollerWriteProblems can_write = {0};
+
+    bool has_key_a = mf_classic_is_key_found(
+        target_data, mf_classic_get_sector_by_block(block_num), MfClassicKeyTypeA);
+    bool has_key_b = mf_classic_is_key_found(
+        target_data, mf_classic_get_sector_by_block(block_num), MfClassicKeyTypeB);
+
+    if(!has_key_a && !has_key_b) {
+        can_write.missing_target_keys = true;
+    }
+    if(!gen2_is_allowed_access(
+           target_data, block_num, MfClassicKeyTypeA, MfClassicActionDataWrite) &&
+       !gen2_is_allowed_access(
+           target_data, block_num, MfClassicKeyTypeB, MfClassicActionDataWrite)) {
+        if(!gen2_can_reset_access_conditions(target_data, block_num)) {
+            can_write.locked_access_bits = true;
+        }
+    }
+
+    return can_write;
+}
+
+Gen2PollerWriteProblems
+    gen2_poller_can_write_sector_trailer(const MfClassicData* target_data, uint8_t block_num) {
+    // Check whether it's possible to write the sector trailer
+    furi_assert(target_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
+
+    Gen2PollerWriteProblems can_write = {0};
+
+    bool has_key_a = mf_classic_is_key_found(
+        target_data, mf_classic_get_sector_by_block(block_num), MfClassicKeyTypeA);
+    bool has_key_b = mf_classic_is_key_found(
+        target_data, mf_classic_get_sector_by_block(block_num), MfClassicKeyTypeB);
+
+    if(!has_key_a && !has_key_b) {
+        can_write.missing_target_keys = true;
+    }
+    if(!gen2_is_allowed_access(
+           target_data, block_num, MfClassicKeyTypeA, MfClassicActionKeyAWrite) &&
+       !gen2_is_allowed_access(
+           target_data, block_num, MfClassicKeyTypeB, MfClassicActionKeyAWrite)) {
+        if(!gen2_can_reset_access_conditions(target_data, block_num)) {
+            can_write.locked_access_bits = true;
+        }
+    }
+    if(!gen2_is_allowed_access(target_data, block_num, MfClassicKeyTypeA, MfClassicActionACWrite) &&
+       !gen2_is_allowed_access(target_data, block_num, MfClassicKeyTypeB, MfClassicActionACWrite)) {
+        can_write.locked_access_bits = true;
+    }
+    if(!gen2_is_allowed_access(
+           target_data, block_num, MfClassicKeyTypeA, MfClassicActionKeyBWrite) &&
+       !gen2_is_allowed_access(
+           target_data, block_num, MfClassicKeyTypeB, MfClassicActionKeyBWrite)) {
+        if(!gen2_can_reset_access_conditions(target_data, block_num)) {
+            can_write.locked_access_bits = true;
+        }
+    }
+
+    return can_write;
+}
+
+bool gen2_can_reset_access_conditions(const MfClassicData* target_data, uint8_t block_num) {
+    // Check whether it's possible to reset the access conditions
+    furi_assert(target_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(
+        target_data, mf_classic_get_sector_by_block(block_num), MfClassicKeyTypeA);
+    bool has_key_b = mf_classic_is_key_found(
+        target_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(target_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(target_data, block_num, MfClassicKeyTypeA, MfClassicActionACWrite) ||
+       gen2_is_allowed_access(target_data, block_num, MfClassicKeyTypeB, MfClassicActionACWrite)) {
+        can_reset = true;
+    }
+
+    return can_reset;
+}
+
+MfClassicKeyType
+    gen2_poller_get_key_type_to_write(const MfClassicData* target_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(target_data);
+
+    MfClassicKeyType key_type = MfClassicKeyTypeA;
+
+    if(gen2_is_allowed_access(
+           target_data, block_num, MfClassicKeyTypeA, MfClassicActionDataWrite)) {
+        key_type = MfClassicKeyTypeA;
+    } else if(gen2_is_allowed_access(
+                  target_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);
+
+Gen2PollerWriteProblems
+    gen2_poller_can_write_data_block(const MfClassicData* mfc_data, uint8_t block_num);
+
+Gen2PollerWriteProblems
+    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

+ 0 - 97
nfc_magic/lib/magic/protocols/gen4/gen4.c

@@ -1,97 +0,0 @@
-#include "gen4.h"
-#include "core/check.h"
-
-Gen4* gen4_alloc() {
-    Gen4* instance = (Gen4*)malloc(sizeof(Gen4));
-    return instance;
-}
-
-void gen4_free(Gen4* instance) {
-    furi_check(instance != NULL);
-    free(instance);
-}
-
-void gen4_reset(Gen4* instance) {
-    furi_check(instance != NULL);
-    memset(&instance->config, 0, sizeof(Gen4Config));
-    memset(&instance->revision, 0, sizeof(Gen4Revision));
-}
-
-void gen4_copy(Gen4* dest, const Gen4* source) {
-    furi_check(dest != NULL);
-    furi_check(source != NULL);
-    memcpy(dest, source, sizeof(Gen4));
-}
-
-char* gen4_get_shadow_mode_name(Gen4ShadowMode mode) {
-    switch(mode) {
-    case Gen4ShadowModePreWrite:
-        return "Pre-Write";
-    case Gen4ShadowModeRestore:
-        return "Restore";
-    case Gen4ShadowModeDisabled:
-        return "Disabled";
-    case Gen4ShadowModeHighSpeedDisabled:
-        return "Disabled (High-speed)";
-    case Gen4ShadowModeSplit:
-        return "Split";
-    default:
-        return "Unknown";
-    }
-}
-
-char* gen4_get_direct_write_mode_name(Gen4DirectWriteBlock0Mode mode) {
-    switch(mode) {
-    case Gen4DirectWriteBlock0ModeEnabled:
-        return "Enabled";
-    case Gen4DirectWriteBlock0ModeDisabled:
-        return "Disabled";
-    case Gen4DirectWriteBlock0ModeDefault:
-        return "Default";
-    default:
-        return "Unknown";
-    }
-}
-
-char* gen4_get_uid_len_num(Gen4UIDLength code) {
-    switch(code) {
-    case Gen4UIDLengthSingle:
-        return "4";
-    case Gen4UIDLengthDouble:
-        return "7";
-    case Gen4UIDLengthTriple:
-        return "10";
-    default:
-        return "Unknown";
-    }
-}
-
-char* gen4_get_configuration_name(const Gen4Config* config) {
-    switch(config->data_parsed.protocol) {
-    case Gen4ProtocolMfClassic: {
-        switch(config->data_parsed.total_blocks) {
-        case 255:
-            return "MIFARE Classic 4K";
-        case 63:
-            return "MIFARE Classic 1K";
-        case 19:
-            return "MIFARE Classic Mini (0.3K)";
-        default:
-            return "Unknown";
-        }
-    } break;
-    case Gen4ProtocolMfUltralight: {
-        switch(config->data_parsed.total_blocks) {
-        case 63:
-            return "MIFARE Ultralight";
-        case 127:
-            return "NTAG 2XX";
-        default:
-            return "Unknown";
-        }
-    } break;
-    default:
-        return "Unknown";
-        break;
-    };
-}

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

@@ -1,101 +0,0 @@
-#pragma once
-
-#include <stdint.h>
-
-#define GEN4_CONFIG_SIZE (32)
-#define GEN4_REVISION_SIZE (5)
-
-#define GEN4_PASSWORD_LEN (4)
-#define GEN4_ATS_MAX_LEN (16)
-#define GEN4_ATQA_LEN (2)
-#define GEN4_CRC_LEN (2)
-
-typedef enum {
-    Gen4ProtocolMfClassic = 0x00,
-    Gen4ProtocolMfUltralight = 0x01,
-} Gen4Protocol;
-
-typedef union {
-    uint32_t value;
-    uint8_t bytes[GEN4_PASSWORD_LEN];
-} Gen4Password;
-
-typedef enum {
-    Gen4UIDLengthSingle = 0x00,
-    Gen4UIDLengthDouble = 0x01,
-    Gen4UIDLengthTriple = 0x02
-} Gen4UIDLength;
-
-typedef enum {
-    Gen4UltralightModeUL_EV1 = 0x00,
-    Gen4UltralightModeNTAG = 0x01,
-    Gen4UltralightModeUL_C = 0x02,
-    Gen4UltralightModeUL = 0x03
-} Gen4UltralightMode;
-
-typedef enum {
-    // for writing original (shadow) data
-    Gen4ShadowModePreWrite = 0x00,
-    // written data can be read once before restored to original
-    Gen4ShadowModeRestore = 0x01,
-    // shadow mode disabled
-    Gen4ShadowModeDisabled = 0x02,
-    // apparently for UL?
-    Gen4ShadowModeHighSpeedDisabled = 0x03,
-    // work with new UMC. With old UMC is untested
-    Gen4ShadowModeSplit = 0x04,
-} Gen4ShadowMode;
-
-typedef enum {
-    // gen2 card behavour
-    Gen4DirectWriteBlock0ModeEnabled = 0x00,
-    // common card behavour
-    Gen4DirectWriteBlock0ModeDisabled = 0x01,
-    // default mode. same behavour as Gen4DirectWriteBlock0ModeActivate
-    Gen4DirectWriteBlock0ModeDefault = 0x02,
-} Gen4DirectWriteBlock0Mode;
-
-typedef union {
-    uint8_t data_raw[GEN4_CONFIG_SIZE];
-#pragma pack(push, 1)
-    struct {
-        Gen4Protocol protocol;
-        Gen4UIDLength uid_len_code;
-        Gen4Password password;
-        Gen4ShadowMode gtu_mode;
-        uint8_t ats_len;
-        uint8_t ats[GEN4_ATS_MAX_LEN]; // mb another class?
-        uint8_t atqa[GEN4_ATQA_LEN];
-        uint8_t sak;
-        Gen4UltralightMode mfu_mode;
-        uint8_t total_blocks;
-        Gen4DirectWriteBlock0Mode direct_write_mode;
-        uint8_t crc[GEN4_CRC_LEN];
-    } data_parsed;
-#pragma pack(pop)
-} Gen4Config;
-
-typedef struct {
-    uint8_t data[GEN4_REVISION_SIZE];
-} Gen4Revision;
-
-typedef struct {
-    Gen4Config config;
-    Gen4Revision revision;
-} Gen4;
-
-Gen4* gen4_alloc();
-
-void gen4_free(Gen4* instance);
-
-void gen4_reset(Gen4* instance);
-
-void gen4_copy(Gen4* dest, const Gen4* source);
-
-char* gen4_get_shadow_mode_name(Gen4ShadowMode mode);
-
-char* gen4_get_direct_write_mode_name(Gen4DirectWriteBlock0Mode mode);
-
-char* gen4_get_uid_len_num(Gen4UIDLength code);
-
-char* gen4_get_configuration_name(const Gen4Config* config);

+ 61 - 156
nfc_magic/lib/magic/protocols/gen4/gen4_poller.c

@@ -1,33 +1,27 @@
-#include "bit_buffer.h"
 #include "gen4_poller_i.h"
 #include "gen4_poller_i.h"
-#include "protocols/gen4/gen4.h"
 #include "protocols/gen4/gen4_poller.h"
 #include "protocols/gen4/gen4_poller.h"
 #include <nfc/protocols/iso14443_3a/iso14443_3a.h>
 #include <nfc/protocols/iso14443_3a/iso14443_3a.h>
 #include <nfc/protocols/iso14443_3a/iso14443_3a_poller.h>
 #include <nfc/protocols/iso14443_3a/iso14443_3a_poller.h>
 #include <nfc/nfc_poller.h>
 #include <nfc/nfc_poller.h>
 #include <bit_lib.h>
 #include <bit_lib.h>
-#include <string.h>
 
 
 #define GEN4_POLLER_THREAD_FLAG_DETECTED (1U << 0)
 #define GEN4_POLLER_THREAD_FLAG_DETECTED (1U << 0)
-#define GEN4_POLLER_DEFAULT_CONFIG_SIZE (28)
 
 
 typedef NfcCommand (*Gen4PollerStateHandler)(Gen4Poller* instance);
 typedef NfcCommand (*Gen4PollerStateHandler)(Gen4Poller* instance);
 
 
 typedef struct {
 typedef struct {
     NfcPoller* poller;
     NfcPoller* poller;
-    Gen4Password password;
-    Gen4 gen4_data;
+    uint32_t password;
     BitBuffer* tx_buffer;
     BitBuffer* tx_buffer;
     BitBuffer* rx_buffer;
     BitBuffer* rx_buffer;
     FuriThreadId thread_id;
     FuriThreadId thread_id;
     Gen4PollerError error;
     Gen4PollerError error;
 } Gen4PollerDetectContext;
 } Gen4PollerDetectContext;
 
 
-static const Gen4Config gen4_poller_default_config = {
-    .data_raw = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x09, 0x78,
-                 0x00, 0x91, 0x02, 0xDA, 0xBC, 0x19, 0x10, 0x10, 0x11, 0x12,
-                 0x13, 0x14, 0x15, 0x16, 0x04, 0x00, 0x08, 0x00}};
-
+static const uint8_t gen4_poller_default_config[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
+                                                     0x00, 0x09, 0x78, 0x00, 0x91, 0x02, 0xDA,
+                                                     0xBC, 0x19, 0x10, 0x10, 0x11, 0x12, 0x13,
+                                                     0x14, 0x15, 0x16, 0x04, 0x00, 0x08, 0x00};
 static const uint8_t gen4_poller_default_block_0[GEN4_POLLER_BLOCK_SIZE] =
 static const uint8_t gen4_poller_default_block_0[GEN4_POLLER_BLOCK_SIZE] =
     {0x00, 0x01, 0x02, 0x03, 0x04, 0x04, 0x08, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
     {0x00, 0x01, 0x02, 0x03, 0x04, 0x04, 0x08, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
 
 
@@ -59,8 +53,6 @@ Gen4Poller* gen4_poller_alloc(Nfc* nfc) {
     instance->tx_buffer = bit_buffer_alloc(GEN4_POLLER_MAX_BUFFER_SIZE);
     instance->tx_buffer = bit_buffer_alloc(GEN4_POLLER_MAX_BUFFER_SIZE);
     instance->rx_buffer = bit_buffer_alloc(GEN4_POLLER_MAX_BUFFER_SIZE);
     instance->rx_buffer = bit_buffer_alloc(GEN4_POLLER_MAX_BUFFER_SIZE);
 
 
-    instance->gen4_data = gen4_alloc();
-
     return instance;
     return instance;
 }
 }
 
 
@@ -72,12 +64,10 @@ void gen4_poller_free(Gen4Poller* instance) {
     bit_buffer_free(instance->tx_buffer);
     bit_buffer_free(instance->tx_buffer);
     bit_buffer_free(instance->rx_buffer);
     bit_buffer_free(instance->rx_buffer);
 
 
-    gen4_free(instance->gen4_data);
-
     free(instance);
     free(instance);
 }
 }
 
 
-void gen4_poller_set_password(Gen4Poller* instance, Gen4Password password) {
+void gen4_poller_set_password(Gen4Poller* instance, uint32_t password) {
     furi_assert(instance);
     furi_assert(instance);
 
 
     instance->password = password;
     instance->password = password;
@@ -97,12 +87,10 @@ NfcCommand gen4_poller_detect_callback(NfcGenericEvent event, void* context) {
 
 
     if(iso3_event->type == Iso14443_3aPollerEventTypeReady) {
     if(iso3_event->type == Iso14443_3aPollerEventTypeReady) {
         do {
         do {
-            // check config
             bit_buffer_append_byte(gen4_poller_detect_ctx->tx_buffer, GEN4_CMD_PREFIX);
             bit_buffer_append_byte(gen4_poller_detect_ctx->tx_buffer, GEN4_CMD_PREFIX);
-            bit_buffer_append_bytes(
-                gen4_poller_detect_ctx->tx_buffer,
-                gen4_poller_detect_ctx->password.bytes,
-                GEN4_PASSWORD_LEN);
+            uint8_t pwd_arr[4] = {};
+            bit_lib_num_to_bytes_be(gen4_poller_detect_ctx->password, COUNT_OF(pwd_arr), pwd_arr);
+            bit_buffer_append_bytes(gen4_poller_detect_ctx->tx_buffer, pwd_arr, COUNT_OF(pwd_arr));
             bit_buffer_append_byte(gen4_poller_detect_ctx->tx_buffer, GEN4_CMD_GET_CFG);
             bit_buffer_append_byte(gen4_poller_detect_ctx->tx_buffer, GEN4_CMD_GET_CFG);
 
 
             Iso14443_3aError error = iso14443_3a_poller_send_standard_frame(
             Iso14443_3aError error = iso14443_3a_poller_send_standard_frame(
@@ -116,48 +104,11 @@ NfcCommand gen4_poller_detect_callback(NfcGenericEvent event, void* context) {
                 break;
                 break;
             }
             }
             size_t rx_bytes = bit_buffer_get_size_bytes(gen4_poller_detect_ctx->rx_buffer);
             size_t rx_bytes = bit_buffer_get_size_bytes(gen4_poller_detect_ctx->rx_buffer);
-            if(rx_bytes != GEN4_CONFIG_SIZE) {
-                gen4_poller_detect_ctx->error = Gen4PollerErrorProtocol;
-                break;
-            }
-
-            memcpy(
-                gen4_poller_detect_ctx->gen4_data.config.data_raw,
-                bit_buffer_get_data(gen4_poller_detect_ctx->rx_buffer),
-                GEN4_CONFIG_SIZE);
-
-            // check revision
-            bit_buffer_reset(gen4_poller_detect_ctx->tx_buffer);
-            bit_buffer_reset(gen4_poller_detect_ctx->rx_buffer);
-
-            bit_buffer_append_byte(gen4_poller_detect_ctx->tx_buffer, GEN4_CMD_PREFIX);
-            bit_buffer_append_bytes(
-                gen4_poller_detect_ctx->tx_buffer,
-                gen4_poller_detect_ctx->password.bytes,
-                GEN4_PASSWORD_LEN);
-            bit_buffer_append_byte(gen4_poller_detect_ctx->tx_buffer, GEN4_CMD_GET_REVISION);
-
-            error = iso14443_3a_poller_send_standard_frame(
-                iso3_poller,
-                gen4_poller_detect_ctx->tx_buffer,
-                gen4_poller_detect_ctx->rx_buffer,
-                GEN4_POLLER_MAX_FWT);
-
-            if(error != Iso14443_3aErrorNone) {
-                gen4_poller_detect_ctx->error = Gen4PollerErrorProtocol;
-                break;
-            }
-            rx_bytes = bit_buffer_get_size_bytes(gen4_poller_detect_ctx->rx_buffer);
-            if(rx_bytes != GEN4_REVISION_SIZE) {
+            if((rx_bytes != 30) && (rx_bytes != 32)) {
                 gen4_poller_detect_ctx->error = Gen4PollerErrorProtocol;
                 gen4_poller_detect_ctx->error = Gen4PollerErrorProtocol;
                 break;
                 break;
             }
             }
 
 
-            memcpy(
-                gen4_poller_detect_ctx->gen4_data.revision.data,
-                bit_buffer_get_data(gen4_poller_detect_ctx->rx_buffer),
-                GEN4_REVISION_SIZE);
-
             gen4_poller_detect_ctx->error = Gen4PollerErrorNone;
             gen4_poller_detect_ctx->error = Gen4PollerErrorNone;
         } while(false);
         } while(false);
     } else if(iso3_event->type == Iso14443_3aPollerEventTypeError) {
     } else if(iso3_event->type == Iso14443_3aPollerEventTypeError) {
@@ -168,7 +119,7 @@ NfcCommand gen4_poller_detect_callback(NfcGenericEvent event, void* context) {
     return command;
     return command;
 }
 }
 
 
-Gen4PollerError gen4_poller_detect(Nfc* nfc, Gen4Password password, Gen4* gen4_data) {
+Gen4PollerError gen4_poller_detect(Nfc* nfc, uint32_t password) {
     furi_assert(nfc);
     furi_assert(nfc);
 
 
     Gen4PollerDetectContext gen4_poller_detect_ctx = {};
     Gen4PollerDetectContext gen4_poller_detect_ctx = {};
@@ -192,9 +143,6 @@ Gen4PollerError gen4_poller_detect(Nfc* nfc, Gen4Password password, Gen4* gen4_d
     bit_buffer_free(gen4_poller_detect_ctx.tx_buffer);
     bit_buffer_free(gen4_poller_detect_ctx.tx_buffer);
     bit_buffer_free(gen4_poller_detect_ctx.rx_buffer);
     bit_buffer_free(gen4_poller_detect_ctx.rx_buffer);
 
 
-    if(gen4_poller_detect_ctx.error == Gen4PollerErrorNone)
-        gen4_copy(gen4_data, &gen4_poller_detect_ctx.gen4_data);
-
     return gen4_poller_detect_ctx.error;
     return gen4_poller_detect_ctx.error;
 }
 }
 
 
@@ -202,8 +150,7 @@ NfcCommand gen4_poller_idle_handler(Gen4Poller* instance) {
     NfcCommand command = NfcCommandContinue;
     NfcCommand command = NfcCommandContinue;
 
 
     instance->current_block = 0;
     instance->current_block = 0;
-    //TODO: FOR WHAT?
-    //memset(instance->config, 0, sizeof(instance->config));
+    memset(instance->config, 0, sizeof(instance->config));
     instance->gen4_event.type = Gen4PollerEventTypeCardDetected;
     instance->gen4_event.type = Gen4PollerEventTypeCardDetected;
     command = instance->callback(instance->gen4_event, instance->context);
     command = instance->callback(instance->gen4_event, instance->context);
     instance->state = Gen4PollerStateRequestMode;
     instance->state = Gen4PollerStateRequestMode;
@@ -246,15 +193,15 @@ NfcCommand gen4_poller_wipe_handler(Gen4Poller* instance) {
             error = gen4_poller_set_config(
             error = gen4_poller_set_config(
                 instance,
                 instance,
                 instance->password,
                 instance->password,
-                &gen4_poller_default_config,
-                GEN4_POLLER_DEFAULT_CONFIG_SIZE,
+                gen4_poller_default_config,
+                sizeof(gen4_poller_default_config),
                 false);
                 false);
             if(error != Gen4PollerErrorNone) {
             if(error != Gen4PollerErrorNone) {
                 FURI_LOG_D(TAG, "Failed to set default config: %d", error);
                 FURI_LOG_D(TAG, "Failed to set default config: %d", error);
                 instance->state = Gen4PollerStateFail;
                 instance->state = Gen4PollerStateFail;
                 break;
                 break;
             }
             }
-            instance->password.value = 0;
+            instance->password = 0;
             error = gen4_poller_write_block(
             error = gen4_poller_write_block(
                 instance, instance->password, instance->current_block, gen4_poller_default_block_0);
                 instance, instance->password, instance->current_block, gen4_poller_default_block_0);
             if(error != Gen4PollerErrorNone) {
             if(error != Gen4PollerErrorNone) {
@@ -309,29 +256,29 @@ static NfcCommand gen4_poller_write_mf_classic(Gen4Poller* instance) {
         const MfClassicData* mfc_data = instance->data;
         const MfClassicData* mfc_data = instance->data;
         const Iso14443_3aData* iso3_data = mfc_data->iso14443_3a_data;
         const Iso14443_3aData* iso3_data = mfc_data->iso14443_3a_data;
         if(instance->current_block == 0) {
         if(instance->current_block == 0) {
-            instance->config.data_parsed.protocol = Gen4ProtocolMfClassic;
+            instance->config[0] = 0x00;
             instance->total_blocks = mf_classic_get_total_block_num(mfc_data->type);
             instance->total_blocks = mf_classic_get_total_block_num(mfc_data->type);
 
 
             if(iso3_data->uid_len == 4) {
             if(iso3_data->uid_len == 4) {
-                instance->config.data_parsed.uid_len_code = Gen4UIDLengthSingle;
+                instance->config[1] = Gen4PollerUIDLengthSingle;
             } else if(iso3_data->uid_len == 7) {
             } else if(iso3_data->uid_len == 7) {
-                instance->config.data_parsed.uid_len_code = Gen4UIDLengthDouble;
+                instance->config[1] = Gen4PollerUIDLengthDouble;
             } else {
             } else {
                 FURI_LOG_E(TAG, "Unsupported UID len: %d", iso3_data->uid_len);
                 FURI_LOG_E(TAG, "Unsupported UID len: %d", iso3_data->uid_len);
                 instance->state = Gen4PollerStateFail;
                 instance->state = Gen4PollerStateFail;
                 break;
                 break;
             }
             }
 
 
-            instance->config.data_parsed.gtu_mode = Gen4ShadowModeDisabled;
-            instance->config.data_parsed.atqa[0] = iso3_data->atqa[0];
-            instance->config.data_parsed.atqa[1] = iso3_data->atqa[1];
-            instance->config.data_parsed.sak = iso3_data->sak;
-            instance->config.data_parsed.mfu_mode = Gen4UltralightModeUL_EV1;
-            instance->config.data_parsed.total_blocks = instance->total_blocks - 1;
-            instance->config.data_parsed.direct_write_mode = Gen4DirectWriteBlock0ModeDisabled;
+            instance->config[6] = Gen4PollerShadowModeDisabled;
+            instance->config[24] = iso3_data->atqa[0];
+            instance->config[25] = iso3_data->atqa[1];
+            instance->config[26] = iso3_data->sak;
+            instance->config[27] = 0x00;
+            instance->config[28] = instance->total_blocks - 1;
+            instance->config[29] = Gen4PollerDirectWriteBlock0ModeDisabled;
 
 
             Gen4PollerError error = gen4_poller_set_config(
             Gen4PollerError error = gen4_poller_set_config(
-                instance, instance->password, &instance->config, GEN4_CONFIG_SIZE, false);
+                instance, instance->password, instance->config, sizeof(instance->config), false);
             if(error != Gen4PollerErrorNone) {
             if(error != Gen4PollerErrorNone) {
                 FURI_LOG_D(TAG, "Failed to write config: %d", error);
                 FURI_LOG_D(TAG, "Failed to write config: %d", error);
                 instance->state = Gen4PollerStateFail;
                 instance->state = Gen4PollerStateFail;
@@ -368,7 +315,7 @@ static NfcCommand gen4_poller_write_mf_ultralight(Gen4Poller* instance) {
         const Iso14443_3aData* iso3_data = mfu_data->iso14443_3a_data;
         const Iso14443_3aData* iso3_data = mfu_data->iso14443_3a_data;
         if(instance->current_block == 0) {
         if(instance->current_block == 0) {
             instance->total_blocks = 64;
             instance->total_blocks = 64;
-            instance->config.data_parsed.protocol = Gen4ProtocolMfUltralight;
+            instance->config[0] = 0x01;
             switch(mfu_data->type) {
             switch(mfu_data->type) {
             case MfUltralightTypeNTAG203:
             case MfUltralightTypeNTAG203:
             case MfUltralightTypeNTAG213:
             case MfUltralightTypeNTAG213:
@@ -378,7 +325,7 @@ static NfcCommand gen4_poller_write_mf_ultralight(Gen4Poller* instance) {
             case MfUltralightTypeNTAGI2C2K:
             case MfUltralightTypeNTAGI2C2K:
             case MfUltralightTypeNTAGI2CPlus1K:
             case MfUltralightTypeNTAGI2CPlus1K:
             case MfUltralightTypeNTAGI2CPlus2K:
             case MfUltralightTypeNTAGI2CPlus2K:
-                instance->config.data_parsed.mfu_mode = Gen4UltralightModeNTAG;
+                instance->config[27] = Gen4PollerUltralightModeNTAG;
                 instance->total_blocks = 64 * 2;
                 instance->total_blocks = 64 * 2;
                 break;
                 break;
 
 
@@ -387,30 +334,30 @@ static NfcCommand gen4_poller_write_mf_ultralight(Gen4Poller* instance) {
                 // UL-C?
                 // UL-C?
                 // UL?
                 // UL?
             default:
             default:
-                instance->config.data_parsed.mfu_mode = Gen4UltralightModeUL_EV1;
+                instance->config[27] = Gen4PollerUltralightModeUL_EV1;
                 break;
                 break;
             }
             }
 
 
             if(iso3_data->uid_len == 4) {
             if(iso3_data->uid_len == 4) {
-                instance->config.data_parsed.uid_len_code = Gen4UIDLengthSingle;
+                instance->config[1] = Gen4PollerUIDLengthSingle;
             } else if(iso3_data->uid_len == 7) {
             } else if(iso3_data->uid_len == 7) {
-                instance->config.data_parsed.uid_len_code = Gen4UIDLengthDouble;
+                instance->config[1] = Gen4PollerUIDLengthDouble;
             } else {
             } else {
                 FURI_LOG_E(TAG, "Unsupported UID len: %d", iso3_data->uid_len);
                 FURI_LOG_E(TAG, "Unsupported UID len: %d", iso3_data->uid_len);
                 instance->state = Gen4PollerStateFail;
                 instance->state = Gen4PollerStateFail;
                 break;
                 break;
             }
             }
 
 
-            instance->config.data_parsed.gtu_mode = Gen4ShadowModeHighSpeedDisabled;
-            instance->config.data_parsed.atqa[0] = iso3_data->atqa[0];
-            instance->config.data_parsed.atqa[1] = iso3_data->atqa[1];
-            instance->config.data_parsed.sak = iso3_data->sak;
-            //instance->config.data_parsed.mfu_mode = Gen4UltralightModeUL_EV1;
-            instance->config.data_parsed.total_blocks = instance->total_blocks - 1;
-            instance->config.data_parsed.direct_write_mode = Gen4DirectWriteBlock0ModeDisabled;
+            instance->config[6] = Gen4PollerShadowModeHighSpeedDisabled;
+            instance->config[24] = iso3_data->atqa[0];
+            instance->config[25] = iso3_data->atqa[1];
+            instance->config[26] = iso3_data->sak;
+            instance->config[27] = 0x00;
+            instance->config[28] = instance->total_blocks - 1;
+            instance->config[29] = Gen4PollerDirectWriteBlock0ModeDisabled;
 
 
             Gen4PollerError error = gen4_poller_set_config(
             Gen4PollerError error = gen4_poller_set_config(
-                instance, instance->password, &instance->config, GEN4_CONFIG_SIZE, false);
+                instance, instance->password, instance->config, sizeof(instance->config), false);
             if(error != Gen4PollerErrorNone) {
             if(error != Gen4PollerErrorNone) {
                 FURI_LOG_D(TAG, "Failed to write config: %d", error);
                 FURI_LOG_D(TAG, "Failed to write config: %d", error);
                 instance->state = Gen4PollerStateFail;
                 instance->state = Gen4PollerStateFail;
@@ -473,47 +420,6 @@ static NfcCommand gen4_poller_write_mf_ultralight(Gen4Poller* instance) {
                 break;
                 break;
             }
             }
 
 
-            // Password
-            MfUltralightConfigPages* config_pages = malloc(sizeof(MfUltralightConfigPages));
-            mf_ultralight_get_config_page(mfu_data, &config_pages);
-
-            block[0] = config_pages->password.data[0];
-            block[1] = config_pages->password.data[1];
-            block[2] = config_pages->password.data[2];
-            block[3] = config_pages->password.data[3];
-            error = gen4_poller_write_block(instance, instance->password, 0xE5, block);
-            if(error != Gen4PollerErrorNone) {
-                FURI_LOG_E(TAG, "Failed to write Password to sector E5");
-                instance->state = Gen4PollerStateFail;
-                break;
-            }
-            error = gen4_poller_write_block(instance, instance->password, 0xF0, block);
-            if(error != Gen4PollerErrorNone) {
-                FURI_LOG_E(TAG, "Failed to write Password to sector F0");
-                instance->state = Gen4PollerStateFail;
-                break;
-            }
-
-            // PACK
-            block[0] = config_pages->pack.data[0];
-            block[1] = config_pages->pack.data[1];
-            block[2] = 0x00;
-            block[3] = 0x00;
-            error = gen4_poller_write_block(instance, instance->password, 0xE6, block);
-            if(error != Gen4PollerErrorNone) {
-                FURI_LOG_E(TAG, "Failed to write PACK to sector E6");
-                instance->state = Gen4PollerStateFail;
-                break;
-            }
-            error = gen4_poller_write_block(instance, instance->password, 0xF1, block);
-            if(error != Gen4PollerErrorNone) {
-                FURI_LOG_E(TAG, "Failed to write PACK to sector F1");
-                instance->state = Gen4PollerStateFail;
-                break;
-            }
-
-            free(config_pages);
-
             instance->state = Gen4PollerStateSuccess;
             instance->state = Gen4PollerStateSuccess;
         }
         }
     } while(false);
     } while(false);
@@ -524,14 +430,11 @@ static NfcCommand gen4_poller_write_mf_ultralight(Gen4Poller* instance) {
 NfcCommand gen4_poller_write_handler(Gen4Poller* instance) {
 NfcCommand gen4_poller_write_handler(Gen4Poller* instance) {
     NfcCommand command = NfcCommandContinue;
     NfcCommand command = NfcCommandContinue;
 
 
-    memcpy(
-        instance->config.data_raw,
-        gen4_poller_default_config.data_raw,
-        GEN4_POLLER_DEFAULT_CONFIG_SIZE);
-
-    memcpy(
-        instance->config.data_parsed.password.bytes, instance->password.bytes, GEN4_PASSWORD_LEN);
-    memset(&instance->config.data_raw[7], 0, 17);
+    memcpy(instance->config, gen4_poller_default_config, sizeof(gen4_poller_default_config));
+    uint8_t password_arr[4] = {};
+    bit_lib_num_to_bytes_be(instance->password, sizeof(password_arr), password_arr);
+    memcpy(&instance->config[2], password_arr, sizeof(password_arr));
+    memset(&instance->config[7], 0, 17);
     if(instance->protocol == NfcProtocolMfClassic) {
     if(instance->protocol == NfcProtocolMfClassic) {
         command = gen4_poller_write_mf_classic(instance);
         command = gen4_poller_write_mf_classic(instance);
     } else if(instance->protocol == NfcProtocolMfUltralight) {
     } else if(instance->protocol == NfcProtocolMfUltralight) {
@@ -551,7 +454,7 @@ NfcCommand gen4_poller_change_password_handler(Gen4Poller* instance) {
         command = instance->callback(instance->gen4_event, instance->context);
         command = instance->callback(instance->gen4_event, instance->context);
         if(command != NfcCommandContinue) break;
         if(command != NfcCommandContinue) break;
 
 
-        Gen4Password new_password = instance->gen4_event_data.request_password.password;
+        uint32_t new_password = instance->gen4_event_data.request_password.password;
         Gen4PollerError error =
         Gen4PollerError error =
             gen4_poller_change_password(instance, instance->password, new_password);
             gen4_poller_change_password(instance, instance->password, new_password);
         if(error != Gen4PollerErrorNone) {
         if(error != Gen4PollerErrorNone) {
@@ -574,8 +477,8 @@ NfcCommand gen4_poller_set_default_cfg_handler(Gen4Poller* instance) {
         Gen4PollerError error = gen4_poller_set_config(
         Gen4PollerError error = gen4_poller_set_config(
             instance,
             instance,
             instance->password,
             instance->password,
-            &gen4_poller_default_config,
-            GEN4_POLLER_DEFAULT_CONFIG_SIZE,
+            gen4_poller_default_config,
+            sizeof(gen4_poller_default_config),
             false);
             false);
         if(error != Gen4PollerErrorNone) {
         if(error != Gen4PollerErrorNone) {
             FURI_LOG_E(TAG, "Failed to set default config: %d", error);
             FURI_LOG_E(TAG, "Failed to set default config: %d", error);
@@ -593,16 +496,16 @@ NfcCommand gen4_poller_get_current_cfg_handler(Gen4Poller* instance) {
     NfcCommand command = NfcCommandContinue;
     NfcCommand command = NfcCommandContinue;
 
 
     do {
     do {
-        Gen4Config config;
+        uint8_t config[32] = {};
 
 
-        Gen4PollerError error = gen4_poller_get_config(instance, instance->password, &config);
+        Gen4PollerError error = gen4_poller_get_config(instance, instance->password, config);
         if(error != Gen4PollerErrorNone) {
         if(error != Gen4PollerErrorNone) {
             FURI_LOG_E(TAG, "Failed to get current config: %d", error);
             FURI_LOG_E(TAG, "Failed to get current config: %d", error);
             instance->state = Gen4PollerStateFail;
             instance->state = Gen4PollerStateFail;
             break;
             break;
         }
         }
         // Copy config data to event data buffer
         // Copy config data to event data buffer
-        memcpy(instance->gen4_data->config.data_raw, config.data_raw, sizeof(config));
+        memcpy(instance->gen4_event_data.config_data, config, sizeof(config));
 
 
         instance->state = Gen4PollerStateSuccess;
         instance->state = Gen4PollerStateSuccess;
     } while(false);
     } while(false);
@@ -614,15 +517,15 @@ NfcCommand gen4_poller_get_revision_handler(Gen4Poller* instance) {
     NfcCommand command = NfcCommandContinue;
     NfcCommand command = NfcCommandContinue;
 
 
     do {
     do {
-        Gen4Revision revision;
-        Gen4PollerError error = gen4_poller_get_revision(instance, instance->password, &revision);
+        uint8_t revision[5] = {0};
+        Gen4PollerError error = gen4_poller_get_revision(instance, instance->password, revision);
         if(error != Gen4PollerErrorNone) {
         if(error != Gen4PollerErrorNone) {
             FURI_LOG_E(TAG, "Failed to get revision: %d", error);
             FURI_LOG_E(TAG, "Failed to get revision: %d", error);
             instance->state = Gen4PollerStateFail;
             instance->state = Gen4PollerStateFail;
             break;
             break;
         }
         }
         // Copy revision data to event data buffer
         // Copy revision data to event data buffer
-        memcpy(instance->gen4_data->revision.data, revision.data, sizeof(revision));
+        memcpy(instance->gen4_event_data.revision_data, revision, sizeof(revision));
 
 
         instance->state = Gen4PollerStateSuccess;
         instance->state = Gen4PollerStateSuccess;
     } while(false);
     } while(false);
@@ -634,25 +537,27 @@ NfcCommand gen4_poller_get_info_handler(Gen4Poller* instance) {
     NfcCommand command = NfcCommandContinue;
     NfcCommand command = NfcCommandContinue;
 
 
     do {
     do {
-        Gen4 gen4_data;
+        uint8_t revision[5] = {0};
+        uint8_t config[32] = {0};
 
 
-        Gen4PollerError error =
-            gen4_poller_get_revision(instance, instance->password, &gen4_data.revision);
+        Gen4PollerError error = gen4_poller_get_revision(instance, instance->password, revision);
         if(error != Gen4PollerErrorNone) {
         if(error != Gen4PollerErrorNone) {
             FURI_LOG_E(TAG, "Failed to get revision: %d", error);
             FURI_LOG_E(TAG, "Failed to get revision: %d", error);
             instance->state = Gen4PollerStateFail;
             instance->state = Gen4PollerStateFail;
             break;
             break;
         }
         }
 
 
-        error = gen4_poller_get_config(instance, instance->password, &gen4_data.config);
+        error = gen4_poller_get_config(instance, instance->password, config);
         if(error != Gen4PollerErrorNone) {
         if(error != Gen4PollerErrorNone) {
             FURI_LOG_E(TAG, "Failed to get current config: %d", error);
             FURI_LOG_E(TAG, "Failed to get current config: %d", error);
             instance->state = Gen4PollerStateFail;
             instance->state = Gen4PollerStateFail;
             break;
             break;
         }
         }
 
 
-        // Copy config&&revision data to event data buffer
-        gen4_copy(instance->gen4_data, &gen4_data);
+        // Copy config data to event data buffer
+        memcpy(instance->gen4_event_data.config_data, config, sizeof(config));
+        // Copy revision data to event data buffer
+        memcpy(instance->gen4_event_data.revision_data, revision, sizeof(revision));
 
 
         instance->state = Gen4PollerStateSuccess;
         instance->state = Gen4PollerStateSuccess;
     } while(false);
     } while(false);

+ 8 - 6
nfc_magic/lib/magic/protocols/gen4/gen4_poller.h

@@ -1,6 +1,5 @@
 #pragma once
 #pragma once
 
 
-#include "protocols/gen4/gen4.h"
 #include <nfc/nfc.h>
 #include <nfc/nfc.h>
 #include <nfc/protocols/nfc_protocol.h>
 #include <nfc/protocols/nfc_protocol.h>
 #include <nfc/protocols/mf_classic/mf_classic.h>
 #include <nfc/protocols/mf_classic/mf_classic.h>
@@ -10,10 +9,8 @@
 extern "C" {
 extern "C" {
 #endif
 #endif
 
 
-// TODO: cleanup, check gen4_poller_i.c defines
 #define GEN4_CMD_PREFIX (0xCF)
 #define GEN4_CMD_PREFIX (0xCF)
 #define GEN4_CMD_GET_CFG (0xC6)
 #define GEN4_CMD_GET_CFG (0xC6)
-#define GEN4_CMD_GET_REVISION (0xCC)
 #define GEN4_CMD_WRITE (0xCD)
 #define GEN4_CMD_WRITE (0xCD)
 #define GEN4_CMD_READ (0xCE)
 #define GEN4_CMD_READ (0xCE)
 #define GEN4_CMD_SET_CFG (0xF0)
 #define GEN4_CMD_SET_CFG (0xF0)
@@ -58,13 +55,18 @@ typedef struct {
 } Gen4PollerEventDataRequestDataToWrite;
 } Gen4PollerEventDataRequestDataToWrite;
 
 
 typedef struct {
 typedef struct {
-    Gen4Password password;
+    uint32_t password;
 } Gen4PollerEventDataRequestNewPassword;
 } Gen4PollerEventDataRequestNewPassword;
 
 
 typedef union {
 typedef union {
     Gen4PollerEventDataRequestMode request_mode;
     Gen4PollerEventDataRequestMode request_mode;
     Gen4PollerEventDataRequestDataToWrite request_data;
     Gen4PollerEventDataRequestDataToWrite request_data;
     Gen4PollerEventDataRequestNewPassword request_password;
     Gen4PollerEventDataRequestNewPassword request_password;
+
+    struct {
+        uint8_t config_data[32];
+        uint8_t revision_data[5];
+    };
 } Gen4PollerEventData;
 } Gen4PollerEventData;
 
 
 typedef struct {
 typedef struct {
@@ -76,13 +78,13 @@ typedef NfcCommand (*Gen4PollerCallback)(Gen4PollerEvent event, void* context);
 
 
 typedef struct Gen4Poller Gen4Poller;
 typedef struct Gen4Poller Gen4Poller;
 
 
-Gen4PollerError gen4_poller_detect(Nfc* nfc, Gen4Password password, Gen4* gen4_data);
+Gen4PollerError gen4_poller_detect(Nfc* nfc, uint32_t password);
 
 
 Gen4Poller* gen4_poller_alloc(Nfc* nfc);
 Gen4Poller* gen4_poller_alloc(Nfc* nfc);
 
 
 void gen4_poller_free(Gen4Poller* instance);
 void gen4_poller_free(Gen4Poller* instance);
 
 
-void gen4_poller_set_password(Gen4Poller* instance, Gen4Password password);
+void gen4_poller_set_password(Gen4Poller* instance, uint32_t password);
 
 
 void gen4_poller_start(Gen4Poller* instance, Gen4PollerCallback callback, void* context);
 void gen4_poller_start(Gen4Poller* instance, Gen4PollerCallback callback, void* context);
 
 

+ 95 - 59
nfc_magic/lib/magic/protocols/gen4/gen4_poller_i.c

@@ -1,7 +1,6 @@
 #include "gen4_poller_i.h"
 #include "gen4_poller_i.h"
 
 
 #include "bit_buffer.h"
 #include "bit_buffer.h"
-#include "protocols/gen4/gen4.h"
 #include "protocols/gen4/gen4_poller.h"
 #include "protocols/gen4/gen4_poller.h"
 #include <nfc/protocols/iso14443_3a/iso14443_3a_poller.h>
 #include <nfc/protocols/iso14443_3a/iso14443_3a_poller.h>
 
 
@@ -17,28 +16,40 @@
 #define GEN4_CMD_FUSE_CFG (0xF1)
 #define GEN4_CMD_FUSE_CFG (0xF1)
 #define GEN4_CMD_SET_PWD (0xFE)
 #define GEN4_CMD_SET_PWD (0xFE)
 
 
-#define GEN4_RESPONSE_SUCCESS (0x02)
+#define GEM4_RESPONSE_SUCCESS (0x02)
 
 
-static Gen4PollerError gen4_poller_process_error(Iso14443_3aError error) {
+#define CONFIG_SIZE_MAX (32)
+#define CONFIG_SIZE_MIN (30)
+#define REVISION_SIZE (5)
+
+static Gen4PollerError gen4_poller_process_error(Iso14443_3aError error)
+{
     Gen4PollerError ret = Gen4PollerErrorNone;
     Gen4PollerError ret = Gen4PollerErrorNone;
 
 
-    if(error == Iso14443_3aErrorNone) {
+    if (error == Iso14443_3aErrorNone)
+    {
         ret = Gen4PollerErrorNone;
         ret = Gen4PollerErrorNone;
-    } else {
+    }
+    else
+    {
         ret = Gen4PollerErrorTimeout;
         ret = Gen4PollerErrorTimeout;
     }
     }
 
 
     return ret;
     return ret;
 }
 }
 
 
-Gen4PollerError
-    gen4_poller_set_shadow_mode(Gen4Poller* instance, Gen4Password password, Gen4ShadowMode mode) {
+Gen4PollerError gen4_poller_set_shadow_mode(
+    Gen4Poller* instance,
+    uint32_t password,
+    Gen4PollerShadowMode mode) {
     Gen4PollerError ret = Gen4PollerErrorNone;
     Gen4PollerError ret = Gen4PollerErrorNone;
     bit_buffer_reset(instance->tx_buffer);
     bit_buffer_reset(instance->tx_buffer);
 
 
     do {
     do {
+        uint8_t password_arr[4] = {};
+        bit_lib_num_to_bytes_be(password, COUNT_OF(password_arr), password_arr);
         bit_buffer_append_byte(instance->tx_buffer, GEN4_CMD_PREFIX);
         bit_buffer_append_byte(instance->tx_buffer, GEN4_CMD_PREFIX);
-        bit_buffer_append_bytes(instance->tx_buffer, password.bytes, GEN4_PASSWORD_LEN);
+        bit_buffer_append_bytes(instance->tx_buffer, password_arr, COUNT_OF(password_arr));
         bit_buffer_append_byte(instance->tx_buffer, GEN4_CMD_SET_SHD_MODE);
         bit_buffer_append_byte(instance->tx_buffer, GEN4_CMD_SET_SHD_MODE);
         bit_buffer_append_byte(instance->tx_buffer, mode);
         bit_buffer_append_byte(instance->tx_buffer, mode);
 
 
@@ -54,7 +65,7 @@ Gen4PollerError
 
 
         FURI_LOG_D(TAG, "Card response: 0x%02X, Shadow mode set: 0x%02X", response, mode);
         FURI_LOG_D(TAG, "Card response: 0x%02X, Shadow mode set: 0x%02X", response, mode);
 
 
-        if(response != GEN4_RESPONSE_SUCCESS) {
+        if(response != GEM4_RESPONSE_SUCCESS) {
             ret = Gen4PollerErrorProtocol;
             ret = Gen4PollerErrorProtocol;
             break;
             break;
         }
         }
@@ -66,14 +77,16 @@ Gen4PollerError
 
 
 Gen4PollerError gen4_poller_set_direct_write_block_0_mode(
 Gen4PollerError gen4_poller_set_direct_write_block_0_mode(
     Gen4Poller* instance,
     Gen4Poller* instance,
-    Gen4Password password,
-    Gen4DirectWriteBlock0Mode mode) {
+    uint32_t password,
+    Gen4PollerDirectWriteBlock0Mode mode) {
     Gen4PollerError ret = Gen4PollerErrorNone;
     Gen4PollerError ret = Gen4PollerErrorNone;
     bit_buffer_reset(instance->tx_buffer);
     bit_buffer_reset(instance->tx_buffer);
 
 
     do {
     do {
+        uint8_t password_arr[4] = {};
+        bit_lib_num_to_bytes_be(password, COUNT_OF(password_arr), password_arr);
         bit_buffer_append_byte(instance->tx_buffer, GEN4_CMD_PREFIX);
         bit_buffer_append_byte(instance->tx_buffer, GEN4_CMD_PREFIX);
-        bit_buffer_append_bytes(instance->tx_buffer, password.bytes, GEN4_PASSWORD_LEN);
+        bit_buffer_append_bytes(instance->tx_buffer, password_arr, COUNT_OF(password_arr));
         bit_buffer_append_byte(instance->tx_buffer, GEN4_CMD_SET_DW_BLOCK_0);
         bit_buffer_append_byte(instance->tx_buffer, GEN4_CMD_SET_DW_BLOCK_0);
         bit_buffer_append_byte(instance->tx_buffer, mode);
         bit_buffer_append_byte(instance->tx_buffer, mode);
 
 
@@ -89,7 +102,7 @@ Gen4PollerError gen4_poller_set_direct_write_block_0_mode(
         FURI_LOG_D(
         FURI_LOG_D(
             TAG, "Card response: 0x%02X, Direct write to block 0 mode set: 0x%02X", response, mode);
             TAG, "Card response: 0x%02X, Direct write to block 0 mode set: 0x%02X", response, mode);
 
 
-        if(response != GEN4_RESPONSE_SUCCESS) {
+        if(response != GEM4_RESPONSE_SUCCESS) {
             ret = Gen4PollerErrorProtocol;
             ret = Gen4PollerErrorProtocol;
             break;
             break;
         }
         }
@@ -100,86 +113,99 @@ Gen4PollerError gen4_poller_set_direct_write_block_0_mode(
 }
 }
 
 
 Gen4PollerError
 Gen4PollerError
-    gen4_poller_get_config(Gen4Poller* instance, Gen4Password password, Gen4Config* config_result) {
+gen4_poller_get_config(Gen4Poller *instance, uint32_t password, uint8_t *config_result)
+{
     Gen4PollerError ret = Gen4PollerErrorNone;
     Gen4PollerError ret = Gen4PollerErrorNone;
     bit_buffer_reset(instance->tx_buffer);
     bit_buffer_reset(instance->tx_buffer);
 
 
-    do {
+    do
+    {
+        uint8_t password_arr[4] = {};
+        bit_lib_num_to_bytes_be(password, COUNT_OF(password_arr), password_arr);
         bit_buffer_append_byte(instance->tx_buffer, GEN4_CMD_PREFIX);
         bit_buffer_append_byte(instance->tx_buffer, GEN4_CMD_PREFIX);
-        bit_buffer_append_bytes(instance->tx_buffer, password.bytes, GEN4_PASSWORD_LEN);
+        bit_buffer_append_bytes(instance->tx_buffer, password_arr, COUNT_OF(password_arr));
         bit_buffer_append_byte(instance->tx_buffer, GEN4_CMD_GET_CFG);
         bit_buffer_append_byte(instance->tx_buffer, GEN4_CMD_GET_CFG);
 
 
         Iso14443_3aError error = iso14443_3a_poller_send_standard_frame(
         Iso14443_3aError error = iso14443_3a_poller_send_standard_frame(
             instance->iso3_poller, instance->tx_buffer, instance->rx_buffer, GEN4_POLLER_MAX_FWT);
             instance->iso3_poller, instance->tx_buffer, instance->rx_buffer, GEN4_POLLER_MAX_FWT);
 
 
-        if(error != Iso14443_3aErrorNone) {
+        if (error != Iso14443_3aErrorNone)
+        {
             ret = gen4_poller_process_error(error);
             ret = gen4_poller_process_error(error);
             break;
             break;
         }
         }
 
 
         size_t rx_bytes = bit_buffer_get_size_bytes(instance->rx_buffer);
         size_t rx_bytes = bit_buffer_get_size_bytes(instance->rx_buffer);
 
 
-        if((rx_bytes != GEN4_CONFIG_SIZE)) {
+        if((rx_bytes != CONFIG_SIZE_MAX) && (rx_bytes != CONFIG_SIZE_MIN)) {
             ret = Gen4PollerErrorProtocol;
             ret = Gen4PollerErrorProtocol;
             break;
             break;
         }
         }
-        bit_buffer_write_bytes(instance->rx_buffer, config_result->data_raw, GEN4_CONFIG_SIZE);
+        bit_buffer_write_bytes(instance->rx_buffer, config_result, CONFIG_SIZE_MAX);
     } while(false);
     } while(false);
 
 
     return ret;
     return ret;
 }
 }
 
 
-Gen4PollerError gen4_poller_get_revision(
-    Gen4Poller* instance,
-    Gen4Password password,
-    Gen4Revision* revision_result) {
+Gen4PollerError
+gen4_poller_get_revision(Gen4Poller *instance, uint32_t password, uint8_t *revision_result)
+{
     Gen4PollerError ret = Gen4PollerErrorNone;
     Gen4PollerError ret = Gen4PollerErrorNone;
     bit_buffer_reset(instance->tx_buffer);
     bit_buffer_reset(instance->tx_buffer);
 
 
-    do {
+    do
+    {
+        uint8_t password_arr[4] = {};
+        bit_lib_num_to_bytes_be(password, COUNT_OF(password_arr), password_arr);
         bit_buffer_append_byte(instance->tx_buffer, GEN4_CMD_PREFIX);
         bit_buffer_append_byte(instance->tx_buffer, GEN4_CMD_PREFIX);
-        bit_buffer_append_bytes(instance->tx_buffer, password.bytes, GEN4_PASSWORD_LEN);
+        bit_buffer_append_bytes(instance->tx_buffer, password_arr, COUNT_OF(password_arr));
         bit_buffer_append_byte(instance->tx_buffer, GEN4_CMD_GET_REVISION);
         bit_buffer_append_byte(instance->tx_buffer, GEN4_CMD_GET_REVISION);
 
 
         Iso14443_3aError error = iso14443_3a_poller_send_standard_frame(
         Iso14443_3aError error = iso14443_3a_poller_send_standard_frame(
             instance->iso3_poller, instance->tx_buffer, instance->rx_buffer, GEN4_POLLER_MAX_FWT);
             instance->iso3_poller, instance->tx_buffer, instance->rx_buffer, GEN4_POLLER_MAX_FWT);
 
 
-        if(error != Iso14443_3aErrorNone) {
+        if (error != Iso14443_3aErrorNone)
+        {
             ret = gen4_poller_process_error(error);
             ret = gen4_poller_process_error(error);
             break;
             break;
         }
         }
 
 
         size_t rx_bytes = bit_buffer_get_size_bytes(instance->rx_buffer);
         size_t rx_bytes = bit_buffer_get_size_bytes(instance->rx_buffer);
-        if(rx_bytes != GEN4_REVISION_SIZE) {
+        if(rx_bytes != REVISION_SIZE) {
             ret = Gen4PollerErrorProtocol;
             ret = Gen4PollerErrorProtocol;
             break;
             break;
         }
         }
-        bit_buffer_write_bytes(instance->rx_buffer, revision_result->data, GEN4_REVISION_SIZE);
-    } while(false);
+        bit_buffer_write_bytes(instance->rx_buffer, revision_result, REVISION_SIZE);
+    } while (false);
 
 
     return ret;
     return ret;
 }
 }
 
 
 Gen4PollerError gen4_poller_set_config(
 Gen4PollerError gen4_poller_set_config(
-    Gen4Poller* instance,
-    Gen4Password password,
-    const Gen4Config* config,
+    Gen4Poller *instance,
+    uint32_t password,
+    const uint8_t *config,
     size_t config_size,
     size_t config_size,
-    bool fuse) {
+    bool fuse)
+{
     Gen4PollerError ret = Gen4PollerErrorNone;
     Gen4PollerError ret = Gen4PollerErrorNone;
     bit_buffer_reset(instance->tx_buffer);
     bit_buffer_reset(instance->tx_buffer);
 
 
-    do {
+    do
+    {
+        uint8_t password_arr[4] = {};
+        bit_lib_num_to_bytes_be(password, COUNT_OF(password_arr), password_arr);
         bit_buffer_append_byte(instance->tx_buffer, GEN4_CMD_PREFIX);
         bit_buffer_append_byte(instance->tx_buffer, GEN4_CMD_PREFIX);
-        bit_buffer_append_bytes(instance->tx_buffer, password.bytes, GEN4_PASSWORD_LEN);
+        bit_buffer_append_bytes(instance->tx_buffer, password_arr, COUNT_OF(password_arr));
         uint8_t fuse_config = fuse ? GEN4_CMD_FUSE_CFG : GEN4_CMD_SET_CFG;
         uint8_t fuse_config = fuse ? GEN4_CMD_FUSE_CFG : GEN4_CMD_SET_CFG;
         bit_buffer_append_byte(instance->tx_buffer, fuse_config);
         bit_buffer_append_byte(instance->tx_buffer, fuse_config);
-        bit_buffer_append_bytes(instance->tx_buffer, config->data_raw, config_size);
+        bit_buffer_append_bytes(instance->tx_buffer, config, config_size);
 
 
         Iso14443_3aError error = iso14443_3a_poller_send_standard_frame(
         Iso14443_3aError error = iso14443_3a_poller_send_standard_frame(
             instance->iso3_poller, instance->tx_buffer, instance->rx_buffer, GEN4_POLLER_MAX_FWT);
             instance->iso3_poller, instance->tx_buffer, instance->rx_buffer, GEN4_POLLER_MAX_FWT);
 
 
-        if(error != Iso14443_3aErrorNone) {
+        if (error != Iso14443_3aErrorNone)
+        {
             ret = gen4_poller_process_error(error);
             ret = gen4_poller_process_error(error);
             break;
             break;
         }
         }
@@ -188,26 +214,30 @@ Gen4PollerError gen4_poller_set_config(
 
 
         FURI_LOG_D(TAG, "Card response to set default config command: 0x%02X", response);
         FURI_LOG_D(TAG, "Card response to set default config command: 0x%02X", response);
 
 
-        if(response != GEN4_RESPONSE_SUCCESS) {
+        if(response != GEM4_RESPONSE_SUCCESS) {
             ret = Gen4PollerErrorProtocol;
             ret = Gen4PollerErrorProtocol;
             break;
             break;
         }
         }
-    } while(false);
+    } while (false);
 
 
     return ret;
     return ret;
 }
 }
 
 
 Gen4PollerError gen4_poller_write_block(
 Gen4PollerError gen4_poller_write_block(
-    Gen4Poller* instance,
-    Gen4Password password,
+    Gen4Poller *instance,
+    uint32_t password,
     uint8_t block_num,
     uint8_t block_num,
-    const uint8_t* data) {
+    const uint8_t *data)
+{
     Gen4PollerError ret = Gen4PollerErrorNone;
     Gen4PollerError ret = Gen4PollerErrorNone;
     bit_buffer_reset(instance->tx_buffer);
     bit_buffer_reset(instance->tx_buffer);
 
 
-    do {
+    do
+    {
+        uint8_t password_arr[4] = {};
+        bit_lib_num_to_bytes_be(password, COUNT_OF(password_arr), password_arr);
         bit_buffer_append_byte(instance->tx_buffer, GEN4_CMD_PREFIX);
         bit_buffer_append_byte(instance->tx_buffer, GEN4_CMD_PREFIX);
-        bit_buffer_append_bytes(instance->tx_buffer, password.bytes, GEN4_PASSWORD_LEN);
+        bit_buffer_append_bytes(instance->tx_buffer, password_arr, COUNT_OF(password_arr));
         bit_buffer_append_byte(instance->tx_buffer, GEN4_CMD_WRITE);
         bit_buffer_append_byte(instance->tx_buffer, GEN4_CMD_WRITE);
         bit_buffer_append_byte(instance->tx_buffer, block_num);
         bit_buffer_append_byte(instance->tx_buffer, block_num);
         bit_buffer_append_bytes(instance->tx_buffer, data, GEN4_POLLER_BLOCK_SIZE);
         bit_buffer_append_bytes(instance->tx_buffer, data, GEN4_POLLER_BLOCK_SIZE);
@@ -215,39 +245,45 @@ Gen4PollerError gen4_poller_write_block(
         Iso14443_3aError error = iso14443_3a_poller_send_standard_frame(
         Iso14443_3aError error = iso14443_3a_poller_send_standard_frame(
             instance->iso3_poller, instance->tx_buffer, instance->rx_buffer, GEN4_POLLER_MAX_FWT);
             instance->iso3_poller, instance->tx_buffer, instance->rx_buffer, GEN4_POLLER_MAX_FWT);
 
 
-        if(error != Iso14443_3aErrorNone) {
+        if (error != Iso14443_3aErrorNone)
+        {
             ret = gen4_poller_process_error(error);
             ret = gen4_poller_process_error(error);
             break;
             break;
         }
         }
 
 
         size_t rx_bytes = bit_buffer_get_size_bytes(instance->rx_buffer);
         size_t rx_bytes = bit_buffer_get_size_bytes(instance->rx_buffer);
-        if(rx_bytes != 2) {
+        if (rx_bytes != 2)
+        {
             ret = Gen4PollerErrorProtocol;
             ret = Gen4PollerErrorProtocol;
             break;
             break;
         }
         }
-    } while(false);
+    } while (false);
 
 
     return ret;
     return ret;
 }
 }
 
 
-Gen4PollerError gen4_poller_change_password(
-    Gen4Poller* instance,
-    Gen4Password pwd_current,
-    Gen4Password pwd_new) {
+Gen4PollerError
+gen4_poller_change_password(Gen4Poller *instance, uint32_t pwd_current, uint32_t pwd_new)
+{
     Gen4PollerError ret = Gen4PollerErrorNone;
     Gen4PollerError ret = Gen4PollerErrorNone;
     bit_buffer_reset(instance->tx_buffer);
     bit_buffer_reset(instance->tx_buffer);
 
 
-    do {
+    do
+    {
+        uint8_t password_arr[4] = {};
+        bit_lib_num_to_bytes_be(pwd_current, COUNT_OF(password_arr), password_arr);
         bit_buffer_append_byte(instance->tx_buffer, GEN4_CMD_PREFIX);
         bit_buffer_append_byte(instance->tx_buffer, GEN4_CMD_PREFIX);
-        bit_buffer_append_bytes(instance->tx_buffer, pwd_current.bytes, GEN4_PASSWORD_LEN);
+        bit_buffer_append_bytes(instance->tx_buffer, password_arr, COUNT_OF(password_arr));
 
 
         bit_buffer_append_byte(instance->tx_buffer, GEN4_CMD_SET_PWD);
         bit_buffer_append_byte(instance->tx_buffer, GEN4_CMD_SET_PWD);
-        bit_buffer_append_bytes(instance->tx_buffer, pwd_new.bytes, GEN4_PASSWORD_LEN);
+        bit_lib_num_to_bytes_be(pwd_new, COUNT_OF(password_arr), password_arr);
+        bit_buffer_append_bytes(instance->tx_buffer, password_arr, COUNT_OF(password_arr));
 
 
         Iso14443_3aError error = iso14443_3a_poller_send_standard_frame(
         Iso14443_3aError error = iso14443_3a_poller_send_standard_frame(
             instance->iso3_poller, instance->tx_buffer, instance->rx_buffer, GEN4_POLLER_MAX_FWT);
             instance->iso3_poller, instance->tx_buffer, instance->rx_buffer, GEN4_POLLER_MAX_FWT);
 
 
-        if(error != Iso14443_3aErrorNone) {
+        if (error != Iso14443_3aErrorNone)
+        {
             ret = gen4_poller_process_error(error);
             ret = gen4_poller_process_error(error);
             break;
             break;
         }
         }
@@ -257,15 +293,15 @@ Gen4PollerError gen4_poller_change_password(
         FURI_LOG_D(
         FURI_LOG_D(
             TAG,
             TAG,
             "Trying to change password from 0x%08lX to 0x%08lX. Card response: 0x%02X",
             "Trying to change password from 0x%08lX to 0x%08lX. Card response: 0x%02X",
-            pwd_current.value,
-            pwd_new.value,
+            pwd_current,
+            pwd_new,
             response);
             response);
 
 
-        if(response != GEN4_RESPONSE_SUCCESS) {
+        if(response != GEM4_RESPONSE_SUCCESS) {
             ret = Gen4PollerErrorProtocol;
             ret = Gen4PollerErrorProtocol;
             break;
             break;
         }
         }
-    } while(false);
+    } while (false);
 
 
     return ret;
     return ret;
 }
 }

+ 56 - 26
nfc_magic/lib/magic/protocols/gen4/gen4_poller_i.h

@@ -1,10 +1,9 @@
 #pragma once
 #pragma once
 
 
-#include "gen4.h"
 #include "gen4_poller.h"
 #include "gen4_poller.h"
 #include <nfc/nfc_poller.h>
 #include <nfc/nfc_poller.h>
 #include <nfc/protocols/iso14443_3a/iso14443_3a_poller.h>
 #include <nfc/protocols/iso14443_3a/iso14443_3a_poller.h>
-#include <bit_lib/bit_lib.h>
+#include <bit_lib.h>
 
 
 #define TAG "Gen4Poller"
 #define TAG "Gen4Poller"
 
 
@@ -18,6 +17,43 @@ extern "C" {
 #define GEN4_POLLER_BLOCK_SIZE (16)
 #define GEN4_POLLER_BLOCK_SIZE (16)
 #define GEN4_POLLER_BLOCKS_TOTAL (256)
 #define GEN4_POLLER_BLOCKS_TOTAL (256)
 
 
+#define GEN4_POLLER_CONFIG_SIZE_MAX (30)
+
+typedef enum {
+    Gen4PollerUIDLengthSingle = 0x00,
+    Gen4PollerUIDLengthDouble = 0x01,
+    Gen4PollerUIDLengthTriple = 0x02
+} Gen4PollerUIDLength;
+
+typedef enum {
+    Gen4PollerUltralightModeUL_EV1 = 0x00,
+    Gen4PollerUltralightModeNTAG = 0x01,
+    Gen4PollerUltralightModeUL_C = 0x02,
+    Gen4PollerUltralightModeUL = 0x03
+} Gen4PollerUltralightMode;
+
+typedef enum {
+    // for writing original (shadow) data
+    Gen4PollerShadowModePreWrite = 0x00,
+    // written data can be read once before restored to original
+    Gen4PollerShadowModeRestore = 0x01,
+    // shadow mode disabled
+    Gen4PollerShadowModeDisabled = 0x02,
+    // apparently for UL?
+    Gen4PollerShadowModeHighSpeedDisabled = 0x03,
+    // work with new UMC. With old UMC is untested
+    Gen4PollerShadowModeSplit = 0x04,
+} Gen4PollerShadowMode;
+
+typedef enum {
+    // gen2 card behavour
+    Gen4PollerDirectWriteBlock0ModeEnabled = 0x00,
+    // common card behavour
+    Gen4PollerDirectWriteBlock0ModeDisabled = 0x01,
+    // default mode. same behavour as Gen4PollerDirectWriteBlock0ModeActivate
+    Gen4PollerDirectWriteBlock0ModeDefault = 0x02,
+} Gen4PollerDirectWriteBlock0Mode;
+
 typedef enum {
 typedef enum {
     Gen4PollerStateIdle,
     Gen4PollerStateIdle,
     Gen4PollerStateRequestMode,
     Gen4PollerStateRequestMode,
@@ -41,15 +77,7 @@ struct Gen4Poller {
     NfcPoller* poller;
     NfcPoller* poller;
     Iso14443_3aPoller* iso3_poller;
     Iso14443_3aPoller* iso3_poller;
     Gen4PollerState state;
     Gen4PollerState state;
-
-    Gen4* gen4_data;
-
-    Gen4Password password;
-
-    Gen4Password new_password;
-    Gen4Config config;
-    Gen4ShadowMode shadow_mode;
-    Gen4DirectWriteBlock0Mode direct_write_block_0_mode;
+    uint32_t password;
 
 
     BitBuffer* tx_buffer;
     BitBuffer* tx_buffer;
     BitBuffer* rx_buffer;
     BitBuffer* rx_buffer;
@@ -59,6 +87,12 @@ struct Gen4Poller {
 
 
     NfcProtocol protocol;
     NfcProtocol protocol;
     const NfcDeviceData* data;
     const NfcDeviceData* data;
+    uint32_t new_password;
+
+    uint8_t config[GEN4_POLLER_CONFIG_SIZE_MAX];
+
+    Gen4PollerShadowMode shadow_mode;
+    Gen4PollerDirectWriteBlock0Mode direct_write_block_0_mode;
 
 
     Gen4PollerEvent gen4_event;
     Gen4PollerEvent gen4_event;
     Gen4PollerEventData gen4_event_data;
     Gen4PollerEventData gen4_event_data;
@@ -69,37 +103,33 @@ struct Gen4Poller {
 
 
 Gen4PollerError gen4_poller_set_config(
 Gen4PollerError gen4_poller_set_config(
     Gen4Poller* instance,
     Gen4Poller* instance,
-    Gen4Password password,
-    const Gen4Config* config,
+    uint32_t password,
+    const uint8_t* config,
     size_t config_size,
     size_t config_size,
     bool fuse);
     bool fuse);
 
 
 Gen4PollerError gen4_poller_write_block(
 Gen4PollerError gen4_poller_write_block(
     Gen4Poller* instance,
     Gen4Poller* instance,
-    Gen4Password password,
+    uint32_t password,
     uint8_t block_num,
     uint8_t block_num,
     const uint8_t* data);
     const uint8_t* data);
 
 
-Gen4PollerError gen4_poller_change_password(
-    Gen4Poller* instance,
-    Gen4Password pwd_current,
-    Gen4Password pwd_new);
+Gen4PollerError
+    gen4_poller_change_password(Gen4Poller* instance, uint32_t pwd_current, uint32_t pwd_new);
 
 
-Gen4PollerError gen4_poller_get_revision(
-    Gen4Poller* instance,
-    Gen4Password password,
-    Gen4Revision* revision_result);
+Gen4PollerError
+    gen4_poller_get_revision(Gen4Poller* instance, uint32_t password, uint8_t* revision_result);
 
 
 Gen4PollerError
 Gen4PollerError
-    gen4_poller_get_config(Gen4Poller* instance, Gen4Password password, Gen4Config* config_result);
+    gen4_poller_get_config(Gen4Poller* instance, uint32_t password, uint8_t* config_result);
 
 
 Gen4PollerError
 Gen4PollerError
-    gen4_poller_set_shadow_mode(Gen4Poller* instance, Gen4Password password, Gen4ShadowMode mode);
+    gen4_poller_set_shadow_mode(Gen4Poller* instance, uint32_t password, Gen4PollerShadowMode mode);
 
 
 Gen4PollerError gen4_poller_set_direct_write_block_0_mode(
 Gen4PollerError gen4_poller_set_direct_write_block_0_mode(
     Gen4Poller* instance,
     Gen4Poller* instance,
-    Gen4Password password,
-    Gen4DirectWriteBlock0Mode mode);
+    uint32_t password,
+    Gen4PollerDirectWriteBlock0Mode mode);
 
 
 #ifdef __cplusplus
 #ifdef __cplusplus
 }
 }

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

@@ -3,8 +3,10 @@
 #include <furi/furi.h>
 #include <furi/furi.h>
 
 
 static const char* nfc_magic_protocol_names[NfcMagicProtocolNum] = {
 static const char* nfc_magic_protocol_names[NfcMagicProtocolNum] = {
-    [NfcMagicProtocolGen1] = "Classic Gen 1A/B",
-    [NfcMagicProtocolGen4] = "Gen 4 GTU",
+    [NfcMagicProtocolGen1] = "Gen1A/B",
+    [NfcMagicProtocolGen2] = "Gen2",
+    [NfcMagicProtocolClassic] = "MIFARE Classic",
+    [NfcMagicProtocolGen4] = "Gen4 GTU",
 };
 };
 
 
 const char* nfc_magic_protocols_get_name(NfcMagicProtocol protocol) {
 const char* nfc_magic_protocols_get_name(NfcMagicProtocol protocol) {

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

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

+ 29 - 6
nfc_magic/nfc_magic_app.c

@@ -1,5 +1,4 @@
 #include "nfc_magic_app_i.h"
 #include "nfc_magic_app_i.h"
-#include "protocols/gen4/gen4.h"
 
 
 bool nfc_magic_app_custom_event_callback(void* context, uint32_t event) {
 bool nfc_magic_app_custom_event_callback(void* context, uint32_t event) {
     furi_assert(context);
     furi_assert(context);
@@ -49,13 +48,16 @@ NfcMagicApp* nfc_magic_app_alloc() {
     view_dispatcher_set_tick_event_callback(
     view_dispatcher_set_tick_event_callback(
         instance->view_dispatcher, nfc_magic_app_tick_event_callback, 100);
         instance->view_dispatcher, nfc_magic_app_tick_event_callback, 100);
 
 
-    // Nfc device
+    // NFC source device (file)
     instance->source_dev = nfc_device_alloc();
     instance->source_dev = nfc_device_alloc();
     nfc_device_set_loading_callback(
     nfc_device_set_loading_callback(
         instance->source_dev, nfc_magic_app_show_loading_popup, instance);
         instance->source_dev, nfc_magic_app_show_loading_popup, instance);
     instance->file_path = furi_string_alloc_set(NFC_APP_FOLDER);
     instance->file_path = furi_string_alloc_set(NFC_APP_FOLDER);
     instance->file_name = furi_string_alloc();
     instance->file_name = furi_string_alloc();
 
 
+    // NFC target device (tag)
+    instance->target_dev = nfc_device_alloc();
+
     // Open GUI record
     // Open GUI record
     instance->gui = furi_record_open(RECORD_GUI);
     instance->gui = furi_record_open(RECORD_GUI);
     view_dispatcher_attach_to_gui(
     view_dispatcher_attach_to_gui(
@@ -104,7 +106,19 @@ NfcMagicApp* nfc_magic_app_alloc() {
     view_dispatcher_add_view(
     view_dispatcher_add_view(
         instance->view_dispatcher, NfcMagicAppViewWidget, widget_get_view(instance->widget));
         instance->view_dispatcher, NfcMagicAppViewWidget, widget_get_view(instance->widget));
 
 
-    instance->gen4_data = gen4_alloc();
+    // Dict attack
+    instance->dict_attack = dict_attack_alloc();
+    view_dispatcher_add_view(
+        instance->view_dispatcher,
+        NfcMagicAppViewDictAttack,
+        dict_attack_get_view(instance->dict_attack));
+
+    // Write problems
+    instance->write_problems = write_problems_alloc();
+    view_dispatcher_add_view(
+        instance->view_dispatcher,
+        NfcMagicAppViewWriteProblems,
+        write_problems_get_view(instance->write_problems));
 
 
     instance->nfc = nfc_alloc();
     instance->nfc = nfc_alloc();
     instance->scanner = nfc_magic_scanner_alloc(instance->nfc);
     instance->scanner = nfc_magic_scanner_alloc(instance->nfc);
@@ -115,11 +129,14 @@ NfcMagicApp* nfc_magic_app_alloc() {
 void nfc_magic_app_free(NfcMagicApp* instance) {
 void nfc_magic_app_free(NfcMagicApp* instance) {
     furi_assert(instance);
     furi_assert(instance);
 
 
-    // Nfc device
+    // Nfc source device
     nfc_device_free(instance->source_dev);
     nfc_device_free(instance->source_dev);
     furi_string_free(instance->file_name);
     furi_string_free(instance->file_name);
     furi_string_free(instance->file_path);
     furi_string_free(instance->file_path);
 
 
+    // Nfc target device
+    nfc_device_free(instance->target_dev);
+
     // Submenu
     // Submenu
     view_dispatcher_remove_view(instance->view_dispatcher, NfcMagicAppViewMenu);
     view_dispatcher_remove_view(instance->view_dispatcher, NfcMagicAppViewMenu);
     submenu_free(instance->submenu);
     submenu_free(instance->submenu);
@@ -144,6 +161,14 @@ void nfc_magic_app_free(NfcMagicApp* instance) {
     view_dispatcher_remove_view(instance->view_dispatcher, NfcMagicAppViewWidget);
     view_dispatcher_remove_view(instance->view_dispatcher, NfcMagicAppViewWidget);
     widget_free(instance->widget);
     widget_free(instance->widget);
 
 
+    // Dict attack
+    view_dispatcher_remove_view(instance->view_dispatcher, NfcMagicAppViewDictAttack);
+    dict_attack_free(instance->dict_attack);
+
+    // Write problems
+    view_dispatcher_remove_view(instance->view_dispatcher, NfcMagicAppViewWriteProblems);
+    write_problems_free(instance->write_problems);
+
     // View Dispatcher
     // View Dispatcher
     view_dispatcher_free(instance->view_dispatcher);
     view_dispatcher_free(instance->view_dispatcher);
 
 
@@ -166,8 +191,6 @@ void nfc_magic_app_free(NfcMagicApp* instance) {
     furi_record_close(RECORD_STORAGE);
     furi_record_close(RECORD_STORAGE);
     instance->storage = NULL;
     instance->storage = NULL;
 
 
-    gen4_free(instance->gen4_data);
-
     nfc_magic_scanner_free(instance->scanner);
     nfc_magic_scanner_free(instance->scanner);
     nfc_free(instance->nfc);
     nfc_free(instance->nfc);
 
 

+ 54 - 3
nfc_magic/nfc_magic_app_i.h

@@ -15,6 +15,8 @@
 #include <gui/modules/text_input.h>
 #include <gui/modules/text_input.h>
 #include <gui/modules/byte_input.h>
 #include <gui/modules/byte_input.h>
 #include <gui/modules/widget.h>
 #include <gui/modules/widget.h>
+#include <views/dict_attack.h>
+#include <views/write_problems.h>
 
 
 #include <input/input.h>
 #include <input/input.h>
 
 
@@ -26,18 +28,28 @@
 
 
 #include "nfc_magic_icons.h"
 #include "nfc_magic_icons.h"
 
 
+#include <assets_icons.h>
+
 #include <nfc/nfc.h>
 #include <nfc/nfc.h>
 #include <nfc/nfc_device.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/nfc_magic_scanner.h"
 #include "lib/magic/protocols/nfc_magic_protocols.h"
 #include "lib/magic/protocols/nfc_magic_protocols.h"
 #include "lib/magic/protocols/gen1a/gen1a_poller.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/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_FOLDER ANY_PATH("nfc")
 #define NFC_APP_EXTENSION ".nfc"
 #define NFC_APP_EXTENSION ".nfc"
 #define NFC_APP_SHADOW_EXTENSION ".shd"
 #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)
 #define NFC_MAGIC_APP_BYTE_INPUT_STORE_SIZE (4)
 
 
 enum NfcMagicAppCustomEvent {
 enum NfcMagicAppCustomEvent {
@@ -48,8 +60,33 @@ enum NfcMagicAppCustomEvent {
     NfcMagicAppCustomEventWorkerExit,
     NfcMagicAppCustomEventWorkerExit,
     NfcMagicAppCustomEventByteInputDone,
     NfcMagicAppCustomEventByteInputDone,
     NfcMagicAppCustomEventTextInputDone,
     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;
+
+typedef struct {
+    uint8_t problem_index;
+    uint8_t problem_index_abs;
+    uint8_t problems_total;
+    Gen2PollerWriteProblems problems;
+} NfcMagicAppWriteProblemsContext;
+
 struct NfcMagicApp {
 struct NfcMagicApp {
     ViewDispatcher* view_dispatcher;
     ViewDispatcher* view_dispatcher;
     Gui* gui;
     Gui* gui;
@@ -59,19 +96,31 @@ struct NfcMagicApp {
 
 
     SceneManager* scene_manager;
     SceneManager* scene_manager;
     NfcDevice* source_dev;
     NfcDevice* source_dev;
+    NfcDevice* target_dev;
     FuriString* file_name;
     FuriString* file_name;
     FuriString* file_path;
     FuriString* file_path;
 
 
     Nfc* nfc;
     Nfc* nfc;
     NfcMagicProtocol protocol;
     NfcMagicProtocol protocol;
     NfcMagicScanner* scanner;
     NfcMagicScanner* scanner;
+    NfcPoller* poller;
     Gen1aPoller* gen1a_poller;
     Gen1aPoller* gen1a_poller;
+
+    Gen2Poller* gen2_poller;
+    bool gen2_poller_is_wipe_mode;
+
     Gen4Poller* gen4_poller;
     Gen4Poller* gen4_poller;
 
 
-    Gen4* gen4_data;
+    NfcMagicAppMfClassicDictAttackContext nfc_dict_context;
+    DictAttack* dict_attack;
+    NfcMagicAppWriteProblemsContext write_problems_context;
+    WriteProblems* write_problems;
+
+    uint32_t gen4_password;
+    uint32_t gen4_password_new;
 
 
-    Gen4Password gen4_password;
-    Gen4Password gen4_password_new;
+    uint8_t gen4_config[32];
+    uint8_t gen4_revision[5];
 
 
     FuriString* text_box_store;
     FuriString* text_box_store;
     uint8_t byte_input_store[NFC_MAGIC_APP_BYTE_INPUT_STORE_SIZE];
     uint8_t byte_input_store[NFC_MAGIC_APP_BYTE_INPUT_STORE_SIZE];
@@ -92,6 +141,8 @@ typedef enum {
     NfcMagicAppViewTextInput,
     NfcMagicAppViewTextInput,
     NfcMagicAppViewByteInput,
     NfcMagicAppViewByteInput,
     NfcMagicAppViewWidget,
     NfcMagicAppViewWidget,
+    NfcMagicAppViewDictAttack,
+    NfcMagicAppViewWriteProblems,
 } NfcMagicAppView;
 } NfcMagicAppView;
 
 
 void nfc_magic_app_blink_start(NfcMagicApp* nfc_magic);
 void nfc_magic_app_blink_start(NfcMagicApp* nfc_magic);

+ 6 - 0
nfc_magic/scenes/nfc_magic_scene_config.h

@@ -11,6 +11,7 @@ ADD_SCENE(nfc_magic, gen4_show_cfg, Gen4ShowCfg)
 ADD_SCENE(nfc_magic, gen4_show_info, Gen4ShowInfo)
 ADD_SCENE(nfc_magic, gen4_show_info, Gen4ShowInfo)
 ADD_SCENE(nfc_magic, gen4_select_shd_mode, Gen4SelectShdMode)
 ADD_SCENE(nfc_magic, gen4_select_shd_mode, Gen4SelectShdMode)
 ADD_SCENE(nfc_magic, gen4_set_shd_mode, Gen4SetShdMode)
 ADD_SCENE(nfc_magic, gen4_set_shd_mode, Gen4SetShdMode)
+ADD_SCENE(nfc_magic, gen4_select_direct_write_block_0_mode, Gen4SelectDirectWriteBlock0Mode)
 ADD_SCENE(nfc_magic, gen4_set_direct_write_block_0_mode, Gen4SetDirectWriteBlock0Mode)
 ADD_SCENE(nfc_magic, gen4_set_direct_write_block_0_mode, Gen4SetDirectWriteBlock0Mode)
 ADD_SCENE(nfc_magic, gen4_fail, Gen4Fail)
 ADD_SCENE(nfc_magic, gen4_fail, Gen4Fail)
 ADD_SCENE(nfc_magic, wipe, Wipe)
 ADD_SCENE(nfc_magic, wipe, Wipe)
@@ -24,3 +25,8 @@ ADD_SCENE(nfc_magic, change_key, ChangeKey)
 ADD_SCENE(nfc_magic, change_key_fail, ChangeKeyFail)
 ADD_SCENE(nfc_magic, change_key_fail, ChangeKeyFail)
 ADD_SCENE(nfc_magic, wrong_card, WrongCard)
 ADD_SCENE(nfc_magic, wrong_card, WrongCard)
 ADD_SCENE(nfc_magic, not_magic, NotMagic)
 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);
                            (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;
     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_load_from_file_select(instance)) {
         if(nfc_magic_scene_file_select_is_file_suitable(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 {
         } else {
             scene_manager_next_scene(instance->scene_manager, NfcMagicSceneWrongCard);
             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);
+}

+ 130 - 0
nfc_magic/scenes/nfc_magic_scene_gen2_write_check.c

@@ -0,0 +1,130 @@
+#include "../nfc_magic_app_i.h"
+
+void nfc_magic_scene_gen2_write_check_view_callback(WriteProblemsEvent event, void* context) {
+    NfcMagicApp* instance = context;
+    NfcMagicAppWriteProblemsContext* problems_context = &instance->write_problems_context;
+
+    if(event == WriteProblemsEventCenterPressed) {
+        if(problems_context->problem_index == problems_context->problems_total - 1) {
+            // Continue to the next scene
+            if(instance->gen2_poller_is_wipe_mode) {
+                scene_manager_next_scene(instance->scene_manager, NfcMagicSceneWipe);
+            } else {
+                scene_manager_next_scene(instance->scene_manager, NfcMagicSceneWrite);
+            }
+        } else {
+            // Move to the next problem
+            problems_context->problem_index++;
+            problems_context->problem_index_abs++;
+            write_problems_set_problem_index(
+                instance->write_problems, problems_context->problem_index);
+
+            for(uint8_t i = problems_context->problem_index_abs;
+                i < GEN2_POLLER_WRITE_PROBLEMS_LEN;
+                i++) {
+                if(problems_context->problems.all_problems & (1 << i)) {
+                    write_problems_set_content(instance->write_problems, gen2_problem_strings[i]);
+                    problems_context->problem_index_abs = i;
+                    break;
+                }
+            }
+        }
+    } else if(event == WriteProblemsEventLeftPressed) {
+        if(problems_context->problem_index == 0) {
+            // Exit to the previous scene
+            scene_manager_search_and_switch_to_previous_scene(
+                instance->scene_manager, NfcMagicSceneMfClassicMenu);
+        } else {
+            // Move to the previous problem
+            problems_context->problem_index--;
+            problems_context->problem_index_abs--;
+            write_problems_set_problem_index(
+                instance->write_problems, problems_context->problem_index);
+
+            for(uint8_t i = problems_context->problem_index_abs;
+                i < GEN2_POLLER_WRITE_PROBLEMS_LEN;
+                i--) {
+                if(problems_context->problems.all_problems & (1 << i)) {
+                    write_problems_set_content(instance->write_problems, gen2_problem_strings[i]);
+                    problems_context->problem_index_abs = i;
+                    break;
+                }
+            }
+        }
+    }
+}
+
+void nfc_magic_scene_gen2_write_check_on_enter(void* context) {
+    NfcMagicApp* instance = context;
+
+    Gen2PollerWriteProblems problems = gen2_poller_check_target_problems(instance->target_dev);
+    if(!instance->gen2_poller_is_wipe_mode) {
+        problems.all_problems |=
+            gen2_poller_check_source_problems(instance->source_dev).all_problems;
+    }
+
+    WriteProblems* write_problems = instance->write_problems;
+    uint8_t problems_count = 0;
+    uint8_t current_problem = 0;
+    furi_assert(!problems.no_data, "No MFC data in nfc device");
+
+    if(problems.all_problems == 0) {
+        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;
+        }
+    }
+
+    // Count the total number of problems
+    for(uint8_t i = 0; i < GEN2_POLLER_WRITE_PROBLEMS_LEN; i++) {
+        if(problems.all_problems & (1 << i)) {
+            problems_count++;
+        }
+    }
+
+    // Init the view
+    write_problems_set_callback(
+        write_problems, nfc_magic_scene_gen2_write_check_view_callback, instance);
+    write_problems_set_problems_total(write_problems, problems_count);
+    write_problems_set_problem_index(write_problems, current_problem);
+
+    // Set the initial content to the first problem
+    for(uint8_t i = 0; i < GEN2_POLLER_WRITE_PROBLEMS_LEN; i++) {
+        if(problems.all_problems & (1 << i)) {
+            write_problems_set_content(write_problems, gen2_problem_strings[i]);
+            current_problem = i;
+            break;
+        }
+    }
+
+    // Save the context
+    instance->write_problems_context.problem_index = current_problem;
+    instance->write_problems_context.problems_total = problems_count;
+    instance->write_problems_context.problems = problems;
+
+    // Setup and start worker
+    view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewWriteProblems);
+}
+
+bool nfc_magic_scene_gen2_write_check_on_event(void* context, SceneManagerEvent event) {
+    NfcMagicApp* instance = context;
+    UNUSED(event);
+    UNUSED(context);
+    UNUSED(instance);
+    bool consumed = false;
+
+    return consumed;
+}
+
+void nfc_magic_scene_gen2_write_check_on_exit(void* context) {
+    NfcMagicApp* instance = context;
+
+    instance->write_problems_context.problem_index = 0;
+    instance->write_problems_context.problems_total = 0;
+    instance->write_problems_context.problems.all_problems = 0;
+
+    write_problems_reset(instance->write_problems);
+}

+ 3 - 8
nfc_magic/scenes/nfc_magic_scene_gen4_get_info.c

@@ -1,7 +1,4 @@
 #include "../nfc_magic_app_i.h"
 #include "../nfc_magic_app_i.h"
-//TODO: INCAPSULATE?
-#include "gui/scene_manager.h"
-#include "protocols/gen4/gen4_poller_i.h"
 
 
 enum {
 enum {
     NfcMagicSceneGen4GetInfoStateCardSearch,
     NfcMagicSceneGen4GetInfoStateCardSearch,
@@ -21,8 +18,9 @@ NfcCommand nfc_mafic_scene_gen4_get_info_poller_callback(Gen4PollerEvent event,
         event.data->request_mode.mode = Gen4PollerModeGetInfo;
         event.data->request_mode.mode = Gen4PollerModeGetInfo;
     } else if(event.type == Gen4PollerEventTypeSuccess) {
     } else if(event.type == Gen4PollerEventTypeSuccess) {
         // Copy data from event to main instance
         // Copy data from event to main instance
-        // TODO: From event data? or from poller?
-        gen4_copy(instance->gen4_data, instance->gen4_poller->gen4_data);
+        memcpy(
+            instance->gen4_revision, event.data->revision_data, sizeof(event.data->revision_data));
+        memcpy(instance->gen4_config, event.data->config_data, sizeof(event.data->config_data));
 
 
         view_dispatcher_send_custom_event(
         view_dispatcher_send_custom_event(
             instance->view_dispatcher, NfcMagicCustomEventWorkerSuccess);
             instance->view_dispatcher, NfcMagicCustomEventWorkerSuccess);
@@ -91,9 +89,6 @@ bool nfc_magic_scene_gen4_get_info_on_event(void* context, SceneManagerEvent eve
             nfc_magic_scene_gen4_get_info_setup_view(instance);
             nfc_magic_scene_gen4_get_info_setup_view(instance);
             consumed = true;
             consumed = true;
         } else if(event.event == NfcMagicCustomEventWorkerSuccess) {
         } else if(event.event == NfcMagicCustomEventWorkerSuccess) {
-            // for notification message
-            scene_manager_set_scene_state(
-                instance->scene_manager, NfcMagicSceneGen4ShowInfo, true);
             scene_manager_next_scene(instance->scene_manager, NfcMagicSceneGen4ShowInfo);
             scene_manager_next_scene(instance->scene_manager, NfcMagicSceneGen4ShowInfo);
             consumed = true;
             consumed = true;
         } else if(event.event == NfcMagicCustomEventWorkerFail) {
         } else if(event.event == NfcMagicCustomEventWorkerFail) {

+ 8 - 29
nfc_magic/scenes/nfc_magic_scene_gen4_menu.c

@@ -1,6 +1,4 @@
 #include "../nfc_magic_app_i.h"
 #include "../nfc_magic_app_i.h"
-#include "gui/scene_manager.h"
-#include "protocols/gen4/gen4.h"
 
 
 enum SubmenuIndex {
 enum SubmenuIndex {
     SubmenuIndexWrite,
     SubmenuIndexWrite,
@@ -35,31 +33,12 @@ void nfc_magic_scene_gen4_menu_on_enter(void* context) {
         SubmenuIndexSetShadowMode,
         SubmenuIndexSetShadowMode,
         nfc_magic_scene_gen4_menu_submenu_callback,
         nfc_magic_scene_gen4_menu_submenu_callback,
         instance);
         instance);
-    if(instance->gen4_data->config.data_parsed.direct_write_mode ==
-       Gen4DirectWriteBlock0ModeEnabled) {
-        submenu_add_item(
-            submenu,
-            "Disable Direct Write Mode",
-            SubmenuIndexSetDirectWriteBlock0Mode,
-            nfc_magic_scene_gen4_menu_submenu_callback,
-            instance);
-        scene_manager_set_scene_state(
-            instance->scene_manager,
-            NfcMagicSceneGen4SetDirectWriteBlock0Mode,
-            Gen4DirectWriteBlock0ModeDisabled);
-    } else {
-        submenu_add_item(
-            submenu,
-            "Enable Direct Write Mode",
-            SubmenuIndexSetDirectWriteBlock0Mode,
-            nfc_magic_scene_gen4_menu_submenu_callback,
-            instance);
-        scene_manager_set_scene_state(
-            instance->scene_manager,
-            NfcMagicSceneGen4SetDirectWriteBlock0Mode,
-            Gen4DirectWriteBlock0ModeEnabled);
-    }
-
+    submenu_add_item(
+        submenu,
+        "Set Gen2 Mode",
+        SubmenuIndexSetDirectWriteBlock0Mode,
+        nfc_magic_scene_gen4_menu_submenu_callback,
+        instance);
     submenu_add_item(
     submenu_add_item(
         submenu, "Wipe", SubmenuIndexWipe, nfc_magic_scene_gen4_menu_submenu_callback, instance);
         submenu, "Wipe", SubmenuIndexWipe, nfc_magic_scene_gen4_menu_submenu_callback, instance);
     submenu_add_item(
     submenu_add_item(
@@ -92,13 +71,13 @@ bool nfc_magic_scene_gen4_menu_on_event(void* context, SceneManagerEvent event)
             consumed = true;
             consumed = true;
         } else if(event.event == SubmenuIndexSetDirectWriteBlock0Mode) {
         } else if(event.event == SubmenuIndexSetDirectWriteBlock0Mode) {
             scene_manager_next_scene(
             scene_manager_next_scene(
-                instance->scene_manager, NfcMagicSceneGen4SetDirectWriteBlock0Mode);
+                instance->scene_manager, NfcMagicSceneGen4SelectDirectWriteBlock0Mode);
             consumed = true;
             consumed = true;
         }
         }
 
 
         scene_manager_set_scene_state(instance->scene_manager, NfcMagicSceneGen4Menu, event.event);
         scene_manager_set_scene_state(instance->scene_manager, NfcMagicSceneGen4Menu, event.event);
     } else if(event.type == SceneManagerEventTypeBack) {
     } else if(event.type == SceneManagerEventTypeBack) {
-        if(instance->gen4_password.value != 0) {
+        if(instance->gen4_password != 0) {
             consumed = scene_manager_search_and_switch_to_previous_scene(
             consumed = scene_manager_search_and_switch_to_previous_scene(
                 instance->scene_manager, NfcMagicSceneGen4ActionsMenu);
                 instance->scene_manager, NfcMagicSceneGen4ActionsMenu);
         } else {
         } else {

+ 96 - 0
nfc_magic/scenes/nfc_magic_scene_gen4_select_direct_write_block_0_mode.c

@@ -0,0 +1,96 @@
+#include "../nfc_magic_app_i.h"
+#include "furi_hal_rtc.h"
+#include "protocols/gen4/gen4_poller_i.h"
+
+enum SubmenuIndex {
+    SubmenuIndexEnable,
+    SubmenuIndexDisable,
+    SubmenuIndexDefault,
+};
+
+void nfc_magic_scene_gen4_select_direct_write_block_0_mode_submenu_callback(
+    void* context,
+    uint32_t index) {
+    NfcMagicApp* instance = context;
+
+    view_dispatcher_send_custom_event(instance->view_dispatcher, index);
+}
+
+void nfc_magic_scene_gen4_select_direct_write_block_0_mode_on_enter(void* context) {
+    NfcMagicApp* instance = context;
+
+    Submenu* submenu = instance->submenu;
+    submenu_add_item(
+        submenu,
+        "Enable",
+        SubmenuIndexEnable,
+        nfc_magic_scene_gen4_select_direct_write_block_0_mode_submenu_callback,
+        instance);
+    submenu_add_item(
+        submenu,
+        "Disable",
+        SubmenuIndexDisable,
+        nfc_magic_scene_gen4_select_direct_write_block_0_mode_submenu_callback,
+        instance);
+    if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
+        submenu_add_item(
+            submenu,
+            "Default",
+            SubmenuIndexDefault,
+            nfc_magic_scene_gen4_select_direct_write_block_0_mode_submenu_callback,
+            instance);
+    }
+    submenu_set_selected_item(
+        submenu,
+        scene_manager_get_scene_state(
+            instance->scene_manager, NfcMagicSceneGen4SelectDirectWriteBlock0Mode));
+    view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewMenu);
+}
+
+bool nfc_magic_scene_gen4_select_direct_write_block_0_mode_on_event(
+    void* context,
+    SceneManagerEvent event) {
+    NfcMagicApp* instance = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == SubmenuIndexEnable) {
+            scene_manager_set_scene_state(
+                instance->scene_manager,
+                NfcMagicSceneGen4SetDirectWriteBlock0Mode,
+                Gen4PollerDirectWriteBlock0ModeEnabled);
+            scene_manager_next_scene(
+                instance->scene_manager, NfcMagicSceneGen4SetDirectWriteBlock0Mode);
+            consumed = true;
+        } else if(event.event == SubmenuIndexDisable) {
+            scene_manager_set_scene_state(
+                instance->scene_manager,
+                NfcMagicSceneGen4SetDirectWriteBlock0Mode,
+                Gen4PollerDirectWriteBlock0ModeDisabled);
+            scene_manager_next_scene(
+                instance->scene_manager, NfcMagicSceneGen4SetDirectWriteBlock0Mode);
+            consumed = true;
+        } else if(event.event == SubmenuIndexDefault) {
+            scene_manager_set_scene_state(
+                instance->scene_manager,
+                NfcMagicSceneGen4SetDirectWriteBlock0Mode,
+                Gen4PollerDirectWriteBlock0ModeDefault);
+            scene_manager_next_scene(
+                instance->scene_manager, NfcMagicSceneGen4SetDirectWriteBlock0Mode);
+            consumed = true;
+        }
+        scene_manager_set_scene_state(
+            instance->scene_manager, NfcMagicSceneGen4SelectDirectWriteBlock0Mode, event.event);
+    } else if(event.type == SceneManagerEventTypeBack) {
+        consumed = scene_manager_search_and_switch_to_previous_scene(
+            instance->scene_manager, NfcMagicSceneGen4ActionsMenu);
+    }
+
+    return consumed;
+}
+
+void nfc_magic_scene_gen4_select_direct_write_block_0_mode_on_exit(void* context) {
+    NfcMagicApp* instance = context;
+
+    submenu_reset(instance->submenu);
+}

+ 9 - 5
nfc_magic/scenes/nfc_magic_scene_gen4_select_shd_mode.c

@@ -63,29 +63,33 @@ bool nfc_magic_scene_gen4_select_shd_mode_on_event(void* context, SceneManagerEv
     if(event.type == SceneManagerEventTypeCustom) {
     if(event.type == SceneManagerEventTypeCustom) {
         if(event.event == SubmenuIndexPreWriteMode) {
         if(event.event == SubmenuIndexPreWriteMode) {
             scene_manager_set_scene_state(
             scene_manager_set_scene_state(
-                instance->scene_manager, NfcMagicSceneGen4SetShdMode, Gen4ShadowModePreWrite);
+                instance->scene_manager,
+                NfcMagicSceneGen4SetShdMode,
+                Gen4PollerShadowModePreWrite);
             scene_manager_next_scene(instance->scene_manager, NfcMagicSceneGen4SetShdMode);
             scene_manager_next_scene(instance->scene_manager, NfcMagicSceneGen4SetShdMode);
             consumed = true;
             consumed = true;
         } else if(event.event == SubmenuIndexRestoreMode) {
         } else if(event.event == SubmenuIndexRestoreMode) {
             scene_manager_set_scene_state(
             scene_manager_set_scene_state(
-                instance->scene_manager, NfcMagicSceneGen4SetShdMode, Gen4ShadowModeRestore);
+                instance->scene_manager, NfcMagicSceneGen4SetShdMode, Gen4PollerShadowModeRestore);
             scene_manager_next_scene(instance->scene_manager, NfcMagicSceneGen4SetShdMode);
             scene_manager_next_scene(instance->scene_manager, NfcMagicSceneGen4SetShdMode);
             consumed = true;
             consumed = true;
         } else if(event.event == SubmenuIndexDisable) {
         } else if(event.event == SubmenuIndexDisable) {
             scene_manager_set_scene_state(
             scene_manager_set_scene_state(
-                instance->scene_manager, NfcMagicSceneGen4SetShdMode, Gen4ShadowModeDisabled);
+                instance->scene_manager,
+                NfcMagicSceneGen4SetShdMode,
+                Gen4PollerShadowModeDisabled);
             scene_manager_next_scene(instance->scene_manager, NfcMagicSceneGen4SetShdMode);
             scene_manager_next_scene(instance->scene_manager, NfcMagicSceneGen4SetShdMode);
             consumed = true;
             consumed = true;
         } else if(event.event == SubmenuIndexDisableHighSpeed) {
         } else if(event.event == SubmenuIndexDisableHighSpeed) {
             scene_manager_set_scene_state(
             scene_manager_set_scene_state(
                 instance->scene_manager,
                 instance->scene_manager,
                 NfcMagicSceneGen4SetShdMode,
                 NfcMagicSceneGen4SetShdMode,
-                Gen4ShadowModeHighSpeedDisabled);
+                Gen4PollerShadowModeHighSpeedDisabled);
             scene_manager_next_scene(instance->scene_manager, NfcMagicSceneGen4SetShdMode);
             scene_manager_next_scene(instance->scene_manager, NfcMagicSceneGen4SetShdMode);
             consumed = true;
             consumed = true;
         } else if(event.event == SubmenuIndexSplitMode) {
         } else if(event.event == SubmenuIndexSplitMode) {
             scene_manager_set_scene_state(
             scene_manager_set_scene_state(
-                instance->scene_manager, NfcMagicSceneGen4SetShdMode, Gen4ShadowModeSplit);
+                instance->scene_manager, NfcMagicSceneGen4SetShdMode, Gen4PollerShadowModeSplit);
             scene_manager_next_scene(instance->scene_manager, NfcMagicSceneGen4SetShdMode);
             scene_manager_next_scene(instance->scene_manager, NfcMagicSceneGen4SetShdMode);
             consumed = true;
             consumed = true;
         }
         }

+ 1 - 1
nfc_magic/scenes/nfc_magic_scene_gen4_set_default_cfg.c

@@ -42,7 +42,7 @@ static void nfc_magic_scene_gen4_set_default_cfg_setup_view(NfcMagicApp* instanc
             instance->popup, "Apply the\ncard\nto the back", 128, 32, AlignRight, AlignCenter);
             instance->popup, "Apply the\ncard\nto the back", 128, 32, AlignRight, AlignCenter);
     } else {
     } else {
         popup_set_icon(popup, 12, 23, &I_Loading_24);
         popup_set_icon(popup, 12, 23, &I_Loading_24);
-        popup_set_header(popup, "Configuring\nDon't move...", 52, 32, AlignLeft, AlignCenter);
+        popup_set_header(popup, "Writing\nDon't move...", 52, 32, AlignLeft, AlignCenter);
     }
     }
 
 
     view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewPopup);
     view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewPopup);

+ 1 - 2
nfc_magic/scenes/nfc_magic_scene_gen4_set_direct_write_block_0_mode.c

@@ -1,6 +1,5 @@
 #include "../nfc_magic_app_i.h"
 #include "../nfc_magic_app_i.h"
 #include "../lib/magic/protocols/gen4/gen4_poller_i.h"
 #include "../lib/magic/protocols/gen4/gen4_poller_i.h"
-#include "gui/scene_manager.h"
 
 
 enum {
 enum {
     NfcMagicSceneGen4SetDirectWriteBlock0ModeStateCardSearch,
     NfcMagicSceneGen4SetDirectWriteBlock0ModeStateCardSearch,
@@ -44,7 +43,7 @@ static void nfc_magic_scene_gen4_set_direct_write_block_0_mode_setup_view(NfcMag
             instance->popup, "Apply the\ncard\nto the back", 128, 32, AlignRight, AlignCenter);
             instance->popup, "Apply the\ncard\nto the back", 128, 32, AlignRight, AlignCenter);
     } else {
     } else {
         popup_set_icon(popup, 12, 23, &I_Loading_24);
         popup_set_icon(popup, 12, 23, &I_Loading_24);
-        popup_set_header(popup, "Configuring\nDon't move...", 52, 32, AlignLeft, AlignCenter);
+        popup_set_header(popup, "Writing\nDon't move...", 52, 32, AlignLeft, AlignCenter);
     }
     }
 
 
     view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewPopup);
     view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewPopup);

+ 1 - 1
nfc_magic/scenes/nfc_magic_scene_gen4_set_shd_mode.c

@@ -42,7 +42,7 @@ static void nfc_magic_scene_gen4_set_shd_mode_setup_view(NfcMagicApp* instance)
             instance->popup, "Apply the\ncard\nto the back", 128, 32, AlignRight, AlignCenter);
             instance->popup, "Apply the\ncard\nto the back", 128, 32, AlignRight, AlignCenter);
     } else {
     } else {
         popup_set_icon(popup, 12, 23, &I_Loading_24);
         popup_set_icon(popup, 12, 23, &I_Loading_24);
-        popup_set_header(popup, "Configuring\nDon't move...", 52, 32, AlignLeft, AlignCenter);
+        popup_set_header(popup, "Writing\nDon't move...", 52, 32, AlignLeft, AlignCenter);
     }
     }
 
 
     view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewPopup);
     view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewPopup);

+ 6 - 7
nfc_magic/scenes/nfc_magic_scene_gen4_show_cfg.c

@@ -1,6 +1,6 @@
 #include "../nfc_magic_app_i.h"
 #include "../nfc_magic_app_i.h"
-#include "protocols/gen4/gen4.h"
-#include "protocols/gen4/gen4_poller_i.h"
+
+#define CONFIG_SIZE (32)
 
 
 void nfc_magic_scene_gen4_show_cfg_widget_callback(
 void nfc_magic_scene_gen4_show_cfg_widget_callback(
     GuiButtonType result,
     GuiButtonType result,
@@ -19,11 +19,10 @@ void nfc_magic_scene_gen4_show_cfg_on_enter(void* context) {
 
 
     FuriString* output = furi_string_alloc();
     FuriString* output = furi_string_alloc();
 
 
-    Gen4Config* config = &instance->gen4_data->config;
-
-    for(size_t i = 0; i < GEN4_CONFIG_SIZE; i += 2) {
+    for(size_t i = 0; i < CONFIG_SIZE; i += 2) {
         if(i && !(i % 8)) furi_string_cat_printf(output, "\n");
         if(i && !(i % 8)) furi_string_cat_printf(output, "\n");
-        furi_string_cat_printf(output, "%02X%02X ", config->data_raw[i], config->data_raw[i + 1]);
+        furi_string_cat_printf(
+            output, "%02X%02X ", instance->gen4_config[i], instance->gen4_config[i + 1]);
     }
     }
 
 
     widget_add_string_element(widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "Gen4 Config");
     widget_add_string_element(widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "Gen4 Config");
@@ -40,7 +39,7 @@ bool nfc_magic_scene_gen4_show_cfg_on_event(void* context, SceneManagerEvent eve
 
 
     if(event.type == SceneManagerEventTypeBack) {
     if(event.type == SceneManagerEventTypeBack) {
         consumed = scene_manager_search_and_switch_to_previous_scene(
         consumed = scene_manager_search_and_switch_to_previous_scene(
-            instance->scene_manager, NfcMagicSceneGen4ShowInfo);
+            instance->scene_manager, NfcMagicSceneGen4GetInfo);
     }
     }
     return consumed;
     return consumed;
 }
 }

+ 56 - 28
nfc_magic/scenes/nfc_magic_scene_gen4_show_info.c

@@ -1,10 +1,51 @@
 #include "../nfc_magic_app_i.h"
 #include "../nfc_magic_app_i.h"
-#include "core/string.h"
 #include "gui/modules/widget_elements/widget_element.h"
 #include "gui/modules/widget_elements/widget_element.h"
 #include "gui/scene_manager.h"
 #include "gui/scene_manager.h"
-
+#include "protocols/gen4/gen4_poller_i.h"
 #include <bit_lib.h>
 #include <bit_lib.h>
-#include <stdbool.h>
+
+#define CONFIG_SIZE (32)
+
+static char* gen4_get_shadow_mode_name(Gen4PollerShadowMode mode) {
+    switch(mode) {
+    case Gen4PollerShadowModePreWrite:
+        return "Pre-Write";
+    case Gen4PollerShadowModeRestore:
+        return "Restore";
+    case Gen4PollerShadowModeDisabled:
+        return "Disabled";
+    case Gen4PollerShadowModeHighSpeedDisabled:
+        return "Disabled (High-speed)";
+    case Gen4PollerShadowModeSplit:
+        return "Split";
+    default:
+        return "Unknown";
+    }
+}
+
+static char* gen4_get_direct_write_mode_name(Gen4PollerDirectWriteBlock0Mode mode) {
+    switch(mode) {
+    case Gen4PollerDirectWriteBlock0ModeEnabled:
+        return "Enabled";
+    case Gen4PollerDirectWriteBlock0ModeDisabled:
+        return "Disabled";
+    case Gen4PollerDirectWriteBlock0ModeDefault:
+        return "Default";
+    default:
+        return "Unknown";
+    }
+}
+
+static char* gen4_get_uid_len(uint8_t byte) {
+    switch(byte) {
+    case 0:
+        return "4";
+    case 1:
+        return "7";
+    default:
+        return "Unknown";
+    }
+}
 
 
 void nfc_magic_scene_gen4_show_info_widget_callback(
 void nfc_magic_scene_gen4_show_info_widget_callback(
     GuiButtonType result,
     GuiButtonType result,
@@ -21,30 +62,21 @@ void nfc_magic_scene_gen4_show_info_on_enter(void* context) {
     NfcMagicApp* instance = context;
     NfcMagicApp* instance = context;
     Widget* widget = instance->widget;
     Widget* widget = instance->widget;
 
 
-    if(scene_manager_get_scene_state(instance->scene_manager, NfcMagicSceneGen4ShowInfo))
-        notification_message(instance->notifications, &sequence_success);
-    scene_manager_set_scene_state(instance->scene_manager, NfcMagicSceneGen4ShowInfo, false);
-
-    Gen4* gen4 = instance->gen4_data;
+    notification_message(instance->notifications, &sequence_success);
 
 
-    Gen4DirectWriteBlock0Mode dw_mode = gen4->config.data_parsed.direct_write_mode;
-    Gen4ShadowMode s_mode = gen4->config.data_parsed.gtu_mode;
-    uint8_t ats_len = gen4->config.data_parsed.ats_len;
+    Gen4PollerDirectWriteBlock0Mode dw_mode = instance->gen4_config[29];
+    Gen4PollerShadowMode s_mode = instance->gen4_config[6];
+    uint8_t ats_len = instance->gen4_config[7];
 
 
     FuriString* output = furi_string_alloc();
     FuriString* output = furi_string_alloc();
 
 
     // Revision
     // Revision
     furi_string_cat_printf(
     furi_string_cat_printf(
-        output, "Revision: %02X %02X\n", gen4->revision.data[3], gen4->revision.data[4]);
+        output, "Revision: %02X %02X\n", instance->gen4_revision[3], instance->gen4_revision[4]);
 
 
     // Password
     // Password
     furi_string_cat_printf(
     furi_string_cat_printf(
-        output,
-        "Password: %02X %02X %02X %02X\n",
-        gen4->config.data_parsed.password.bytes[0],
-        gen4->config.data_parsed.password.bytes[1],
-        gen4->config.data_parsed.password.bytes[2],
-        gen4->config.data_parsed.password.bytes[3]);
+        output, "Password: %08llX\n", bit_lib_bytes_to_num_be(instance->gen4_config + 2, 4));
 
 
     // Shadow mode
     // Shadow mode
     furi_string_cat_printf(output, "Shadow Mode: %s\n", gen4_get_shadow_mode_name(s_mode));
     furi_string_cat_printf(output, "Shadow Mode: %s\n", gen4_get_shadow_mode_name(s_mode));
@@ -57,18 +89,14 @@ void nfc_magic_scene_gen4_show_info_on_enter(void* context) {
     furi_string_cat_printf(output, ":::::::::::::[Configured As]::::::::::::::\n");
     furi_string_cat_printf(output, ":::::::::::::[Configured As]::::::::::::::\n");
     /////////////////////////////////////////////////////////////////////////////////////////////////////
     /////////////////////////////////////////////////////////////////////////////////////////////////////
 
 
-    // Configuration type:
-    furi_string_cat_printf(output, "%s\n", gen4_get_configuration_name(&gen4->config));
-
     // UID len
     // UID len
-    furi_string_cat_printf(
-        output, "UID Length: %s\n", gen4_get_uid_len_num(gen4->config.data_parsed.uid_len_code));
+    furi_string_cat_printf(output, "UID Length: %s\n", gen4_get_uid_len(instance->gen4_config[1]));
 
 
     // ATS
     // ATS
     furi_string_cat_printf(output, "ATS:");
     furi_string_cat_printf(output, "ATS:");
     if(ats_len)
     if(ats_len)
         for(uint8_t i = 0; i < ats_len; i++)
         for(uint8_t i = 0; i < ats_len; i++)
-            furi_string_cat_printf(output, " %02X", gen4->config.data_parsed.ats[i]);
+            furi_string_cat_printf(output, " %02X", instance->gen4_config[8 + i]);
     else
     else
         furi_string_cat_printf(output, " No");
         furi_string_cat_printf(output, " No");
     furi_string_cat_printf(output, "\n");
     furi_string_cat_printf(output, "\n");
@@ -77,17 +105,17 @@ void nfc_magic_scene_gen4_show_info_on_enter(void* context) {
     furi_string_cat_printf(
     furi_string_cat_printf(
         output,
         output,
         "ATQA: %02X %02X\n",
         "ATQA: %02X %02X\n",
-        gen4->config.data_parsed.atqa[0],
-        gen4->config.data_parsed.atqa[1]);
+        instance->gen4_config[25],
+        instance->gen4_config[24]); // invert for big endian
 
 
     // SAK
     // SAK
-    furi_string_cat_printf(output, "SAK: %02X\n", gen4->config.data_parsed.sak);
+    furi_string_cat_printf(output, "SAK: %02X\n", instance->gen4_config[26]);
 
 
     // Blocks
     // Blocks
     furi_string_cat_printf(
     furi_string_cat_printf(
         output,
         output,
         "Total blocks: %u",
         "Total blocks: %u",
-        gen4->config.data_parsed.total_blocks + 1); // config stores the number of the last block
+        instance->gen4_config[28] + 1); // config stores the number of the last block
 
 
     widget_add_string_element(widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "Gen4 Info");
     widget_add_string_element(widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "Gen4 Info");
 
 

+ 5 - 10
nfc_magic/scenes/nfc_magic_scene_key_input.c

@@ -1,8 +1,6 @@
 #include "../nfc_magic_app_i.h"
 #include "../nfc_magic_app_i.h"
 
 
-#include "protocols/gen4/gen4.h"
-#include <bit_lib.h>
-#include <string.h>
+#include <bit_lib/bit_lib.h>
 
 
 void nfc_magic_scene_key_input_byte_input_callback(void* context) {
 void nfc_magic_scene_key_input_byte_input_callback(void* context) {
     NfcMagicApp* instance = context;
     NfcMagicApp* instance = context;
@@ -33,16 +31,13 @@ bool nfc_magic_scene_key_input_on_event(void* context, SceneManagerEvent event)
 
 
     if(event.type == SceneManagerEventTypeCustom) {
     if(event.type == SceneManagerEventTypeCustom) {
         if(event.event == NfcMagicAppCustomEventByteInputDone) {
         if(event.event == NfcMagicAppCustomEventByteInputDone) {
-            // TODO: NEED TEST
             if(scene_manager_has_previous_scene(instance->scene_manager, NfcMagicSceneGen4Menu)) {
             if(scene_manager_has_previous_scene(instance->scene_manager, NfcMagicSceneGen4Menu)) {
-                memcpy(
-                    instance->gen4_password_new.bytes,
-                    instance->byte_input_store,
-                    GEN4_PASSWORD_LEN);
+                instance->gen4_password_new = bit_lib_bytes_to_num_be(
+                    instance->byte_input_store, NFC_MAGIC_APP_BYTE_INPUT_STORE_SIZE);
                 scene_manager_next_scene(instance->scene_manager, NfcMagicSceneChangeKey);
                 scene_manager_next_scene(instance->scene_manager, NfcMagicSceneChangeKey);
             } else {
             } else {
-                memcpy(
-                    instance->gen4_password.bytes, instance->byte_input_store, GEN4_PASSWORD_LEN);
+                instance->gen4_password = bit_lib_bytes_to_num_be(
+                    instance->byte_input_store, NFC_MAGIC_APP_BYTE_INPUT_STORE_SIZE);
                 scene_manager_next_scene(instance->scene_manager, NfcMagicSceneCheck);
                 scene_manager_next_scene(instance->scene_manager, NfcMagicSceneCheck);
             }
             }
             consumed = true;
             consumed = true;

+ 18 - 37
nfc_magic/scenes/nfc_magic_scene_magic_info.c

@@ -1,9 +1,4 @@
 #include "../nfc_magic_app_i.h"
 #include "../nfc_magic_app_i.h"
-#include "core/string.h"
-#include "gui/canvas.h"
-#include "gui/modules/widget.h"
-#include "lib/magic/nfc_magic_scanner.h"
-#include "protocols/gen4/gen4.h"
 
 
 void nfc_magic_scene_magic_info_widget_callback(
 void nfc_magic_scene_magic_info_widget_callback(
     GuiButtonType result,
     GuiButtonType result,
@@ -22,40 +17,20 @@ void nfc_magic_scene_magic_info_on_enter(void* context) {
 
 
     notification_message(instance->notifications, &sequence_success);
     notification_message(instance->notifications, &sequence_success);
 
 
-    //widget_add_icon_element(widget, 73, 17, &I_DolphinCommon_56x48);
-    widget_add_string_element(
-        widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "Magic card detected");
-    widget_add_string_element(
-        widget,
-        3,
-        17,
-        AlignLeft,
-        AlignTop,
-        FontSecondary,
-        nfc_magic_protocols_get_name(instance->protocol));
-    if(instance->protocol == NfcMagicProtocolGen4) {
-        gen4_copy(instance->gen4_data, nfc_magic_scanner_get_gen4_data(instance->scanner));
-
-        FuriString* message = furi_string_alloc();
-
-        furi_string_printf(
-            message,
-            "Revision: %02X %02X\n",
-            instance->gen4_data->revision.data[3],
-            instance->gen4_data->revision.data[4]);
+    FuriString* message = furi_string_alloc();
 
 
+    if(instance->protocol == NfcMagicProtocolClassic) {
         widget_add_string_element(
         widget_add_string_element(
-            widget, 55, 17, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(message));
-
-        furi_string_printf(
-            message,
-            "Configured As:\n%s",
-            gen4_get_configuration_name(&instance->gen4_data->config));
-
-        widget_add_string_multiline_element(
-            widget, 3, 27, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(message));
-        furi_string_free(message);
+            widget, 0, 0, AlignLeft, AlignTop, FontPrimary, "It Might Be a Magic Card");
+        furi_string_printf(message, "You can make sure the card is\nmagic by writing to it\n");
+    } else {
+        widget_add_string_element(
+            widget, 0, 0, AlignLeft, AlignTop, FontPrimary, "Magic card detected!");
     }
     }
+    furi_string_cat_printf(
+        message, "Magic Type: %s", nfc_magic_protocols_get_name(instance->protocol));
+    widget_add_text_box_element(
+        widget, 0, 10, 128, 54, AlignLeft, AlignTop, furi_string_get_cstr(message), false);
     widget_add_button_element(
     widget_add_button_element(
         widget, GuiButtonTypeLeft, "Retry", nfc_magic_scene_magic_info_widget_callback, instance);
         widget, GuiButtonTypeLeft, "Retry", nfc_magic_scene_magic_info_widget_callback, instance);
     widget_add_button_element(
     widget_add_button_element(
@@ -75,9 +50,15 @@ bool nfc_magic_scene_magic_info_on_event(void* context, SceneManagerEvent event)
             if(instance->protocol == NfcMagicProtocolGen1) {
             if(instance->protocol == NfcMagicProtocolGen1) {
                 scene_manager_next_scene(instance->scene_manager, NfcMagicSceneGen1Menu);
                 scene_manager_next_scene(instance->scene_manager, NfcMagicSceneGen1Menu);
                 consumed = true;
                 consumed = true;
-            } else {
+            } else if(instance->protocol == NfcMagicProtocolGen4) {
                 scene_manager_next_scene(instance->scene_manager, NfcMagicSceneGen4Menu);
                 scene_manager_next_scene(instance->scene_manager, NfcMagicSceneGen4Menu);
                 consumed = true;
                 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) {
     } 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);
+}

+ 124 - 0
nfc_magic/scenes/nfc_magic_scene_mf_classic_write_check.c

@@ -0,0 +1,124 @@
+#include "../nfc_magic_app_i.h"
+
+void nfc_magic_scene_mf_classic_write_check_view_callback(WriteProblemsEvent event, void* context) {
+    NfcMagicApp* instance = context;
+    NfcMagicAppWriteProblemsContext* problems_context = &instance->write_problems_context;
+
+    if(event == WriteProblemsEventCenterPressed) {
+        if(problems_context->problem_index == problems_context->problems_total - 1) {
+            // Continue to the next scene
+            if(instance->gen2_poller_is_wipe_mode) {
+                scene_manager_next_scene(instance->scene_manager, NfcMagicSceneWipe);
+            } else {
+                scene_manager_next_scene(instance->scene_manager, NfcMagicSceneWrite);
+            }
+        } else {
+            // Move to the next problem
+            problems_context->problem_index++;
+            problems_context->problem_index_abs++;
+            write_problems_set_problem_index(
+                instance->write_problems, problems_context->problem_index);
+
+            for(uint8_t i = problems_context->problem_index_abs;
+                i < GEN2_POLLER_WRITE_PROBLEMS_LEN;
+                i++) {
+                if(problems_context->problems.all_problems & (1 << i)) {
+                    write_problems_set_content(instance->write_problems, gen2_problem_strings[i]);
+                    problems_context->problem_index_abs = i;
+                    break;
+                }
+            }
+        }
+    } else if(event == WriteProblemsEventLeftPressed) {
+        if(problems_context->problem_index == 0) {
+            // Exit to the previous scene
+            scene_manager_search_and_switch_to_previous_scene(
+                instance->scene_manager, NfcMagicSceneMfClassicMenu);
+        } else {
+            // Move to the previous problem
+            problems_context->problem_index--;
+            problems_context->problem_index_abs--;
+            write_problems_set_problem_index(
+                instance->write_problems, problems_context->problem_index);
+
+            for(uint8_t i = problems_context->problem_index_abs;
+                i < GEN2_POLLER_WRITE_PROBLEMS_LEN;
+                i--) {
+                if(problems_context->problems.all_problems & (1 << i)) {
+                    write_problems_set_content(instance->write_problems, gen2_problem_strings[i]);
+                    problems_context->problem_index_abs = i;
+                    break;
+                }
+            }
+        }
+    }
+}
+
+void nfc_magic_scene_mf_classic_write_check_on_enter(void* context) {
+    NfcMagicApp* instance = context;
+
+    Gen2PollerWriteProblems problems = gen2_poller_check_target_problems(instance->target_dev);
+    if(!instance->gen2_poller_is_wipe_mode) {
+        problems.all_problems |=
+            gen2_poller_check_source_problems(instance->source_dev).all_problems;
+    }
+    FURI_LOG_D("GEN2", "Problems: %d", problems.all_problems);
+
+    WriteProblems* write_problems = instance->write_problems;
+    uint8_t problems_count = 0;
+    uint8_t current_problem = 0;
+    furi_assert(!problems.no_data, "No MFC data in nfc device");
+
+    // Set the uid_locked problem to true as we have a Mifare Classic card
+    problems.uid_locked = true;
+
+    // Count the number of problems
+    for(uint8_t i = 0; i < GEN2_POLLER_WRITE_PROBLEMS_LEN; i++) {
+        if(problems.all_problems & (1 << i)) {
+            problems_count++;
+        }
+    }
+
+    // Init the view
+    write_problems_set_callback(
+        write_problems, nfc_magic_scene_mf_classic_write_check_view_callback, instance);
+    write_problems_set_problems_total(write_problems, problems_count);
+    write_problems_set_problem_index(write_problems, current_problem);
+
+    // Set the first problem
+    for(uint8_t i = current_problem; i < GEN2_POLLER_WRITE_PROBLEMS_LEN; i++) {
+        if(problems.all_problems & (1 << i)) {
+            write_problems_set_content(instance->write_problems, gen2_problem_strings[i]);
+            instance->write_problems_context.problem_index_abs = i;
+            break;
+        }
+    }
+
+    // Save the problems context
+    instance->write_problems_context.problem_index = current_problem;
+    instance->write_problems_context.problems_total = problems_count;
+    instance->write_problems_context.problems = problems;
+
+    // Setup and start worker
+    view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewWriteProblems);
+}
+
+bool nfc_magic_scene_mf_classic_write_check_on_event(void* context, SceneManagerEvent event) {
+    NfcMagicApp* instance = context;
+    UNUSED(event);
+    UNUSED(context);
+    UNUSED(instance);
+    bool consumed = false;
+
+    return consumed;
+}
+
+void nfc_magic_scene_mf_classic_write_check_on_exit(void* context) {
+    NfcMagicApp* instance = context;
+
+    instance->write_problems_context.problem_index = 0;
+    instance->write_problems_context.problems_total = 0;
+    instance->write_problems_context.problems.all_problems = 0;
+
+    write_problems_reset(instance->write_problems);
+}

+ 1 - 1
nfc_magic/scenes/nfc_magic_scene_start.c

@@ -27,7 +27,7 @@ void nfc_magic_scene_start_on_enter(void* context) {
         nfc_magic_scene_start_submenu_callback,
         nfc_magic_scene_start_submenu_callback,
         instance);
         instance);
 
 
-    instance->gen4_password.value = 0;
+    instance->gen4_password = 0;
 
 
     submenu_set_selected_item(
     submenu_set_selected_item(
         submenu, scene_manager_get_scene_state(instance->scene_manager, NfcMagicSceneStart));
         submenu, scene_manager_get_scene_state(instance->scene_manager, NfcMagicSceneStart));

+ 2 - 2
nfc_magic/scenes/nfc_magic_scene_success.c

@@ -11,8 +11,8 @@ void nfc_magic_scene_success_on_enter(void* context) {
     notification_message(instance->notifications, &sequence_success);
     notification_message(instance->notifications, &sequence_success);
 
 
     Popup* popup = instance->popup;
     Popup* popup = instance->popup;
-    popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59);
-    popup_set_header(popup, "Success!", 10, 20, AlignLeft, AlignBottom);
+    popup_set_icon(popup, 0, 9, &I_DolphinSuccess_91x55);
+    popup_set_header(popup, "Success!", 75, 12, AlignLeft, AlignCenter);
     popup_set_timeout(popup, 1500);
     popup_set_timeout(popup, 1500);
     popup_set_context(popup, instance);
     popup_set_context(popup, instance);
     popup_set_callback(popup, nfc_magic_scene_success_popup_callback);
     popup_set_callback(popup, nfc_magic_scene_success_popup_callback);

+ 48 - 6
nfc_magic/scenes/nfc_magic_scene_wipe.c

@@ -5,7 +5,7 @@ enum {
     NfcMagicSceneWipeStateCardFound,
     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;
     NfcMagicApp* instance = context;
     furi_assert(event.data);
     furi_assert(event.data);
 
 
@@ -29,7 +29,35 @@ NfcCommand nfc_mafic_scene_wipe_gen1_poller_callback(Gen1aPollerEvent event, voi
     return command;
     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;
     NfcMagicApp* instance = context;
 
 
     NfcCommand command = NfcCommandContinue;
     NfcCommand command = NfcCommandContinue;
@@ -81,12 +109,20 @@ void nfc_magic_scene_wipe_on_enter(void* context) {
     if(instance->protocol == NfcMagicProtocolGen1) {
     if(instance->protocol == NfcMagicProtocolGen1) {
         instance->gen1a_poller = gen1a_poller_alloc(instance->nfc);
         instance->gen1a_poller = gen1a_poller_alloc(instance->nfc);
         gen1a_poller_start(
         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);
         instance->gen4_poller = gen4_poller_alloc(instance->nfc);
         gen4_poller_set_password(instance->gen4_poller, instance->gen4_password);
         gen4_poller_set_password(instance->gen4_poller, instance->gen4_password);
         gen4_poller_start(
         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) {
     if(instance->protocol == NfcMagicProtocolGen1) {
         gen1a_poller_stop(instance->gen1a_poller);
         gen1a_poller_stop(instance->gen1a_poller);
         gen1a_poller_free(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_stop(instance->gen4_poller);
         gen4_poller_free(instance->gen4_poller);
         gen4_poller_free(instance->gen4_poller);
     }
     }

+ 5 - 2
nfc_magic/scenes/nfc_magic_scene_wipe_fail.c

@@ -14,8 +14,11 @@ void nfc_magic_scene_wipe_fail_on_enter(void* context) {
     Widget* widget = instance->widget;
     Widget* widget = instance->widget;
     notification_message(instance->notifications, &sequence_error);
     notification_message(instance->notifications, &sequence_error);
 
 
-    widget_add_icon_element(widget, 73, 17, &I_DolphinCommon_56x48);
-    widget_add_string_element(widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "Wipe failed");
+    widget_add_icon_element(widget, 83, 22, &I_WarningDolphinFlip_45x42);
+    widget_add_string_element(widget, 64, 0, AlignCenter, AlignTop, FontPrimary, "Failed to Wipe");
+    widget_add_string_multiline_element(
+        widget, 0, 13, AlignLeft, AlignTop, FontSecondary, "Something went\nwrong while wiping");
+
     widget_add_button_element(
     widget_add_button_element(
         widget, GuiButtonTypeLeft, "Retry", nfc_magic_scene_wipe_fail_widget_callback, instance);
         widget, GuiButtonTypeLeft, "Retry", nfc_magic_scene_wipe_fail_widget_callback, instance);
 
 

+ 50 - 5
nfc_magic/scenes/nfc_magic_scene_write.c

@@ -5,7 +5,7 @@ enum {
     NfcMagicSceneWriteStateCardFound,
     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;
     NfcMagicApp* instance = context;
     furi_assert(event.data);
     furi_assert(event.data);
 
 
@@ -33,7 +33,39 @@ NfcCommand nfc_mafic_scene_write_gen1_poller_callback(Gen1aPollerEvent event, vo
     return command;
     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;
     NfcMagicApp* instance = context;
     furi_assert(event.data);
     furi_assert(event.data);
 
 
@@ -90,12 +122,20 @@ void nfc_magic_scene_write_on_enter(void* context) {
     if(instance->protocol == NfcMagicProtocolGen1) {
     if(instance->protocol == NfcMagicProtocolGen1) {
         instance->gen1a_poller = gen1a_poller_alloc(instance->nfc);
         instance->gen1a_poller = gen1a_poller_alloc(instance->nfc);
         gen1a_poller_start(
         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 {
     } else {
         instance->gen4_poller = gen4_poller_alloc(instance->nfc);
         instance->gen4_poller = gen4_poller_alloc(instance->nfc);
         gen4_poller_set_password(instance->gen4_poller, instance->gen4_password);
         gen4_poller_set_password(instance->gen4_poller, instance->gen4_password);
         gen4_poller_start(
         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) {
     if(instance->protocol == NfcMagicProtocolGen1) {
         gen1a_poller_stop(instance->gen1a_poller);
         gen1a_poller_stop(instance->gen1a_poller);
         gen1a_poller_free(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_stop(instance->gen4_poller);
         gen4_poller_free(instance->gen4_poller);
         gen4_poller_free(instance->gen4_poller);
     }
     }

+ 6 - 14
nfc_magic/scenes/nfc_magic_scene_write_fail.c

@@ -16,20 +16,14 @@ void nfc_magic_scene_write_fail_on_enter(void* context) {
 
 
     notification_message(instance->notifications, &sequence_error);
     notification_message(instance->notifications, &sequence_error);
 
 
-    widget_add_icon_element(widget, 72, 17, &I_DolphinCommon_56x48);
+    widget_add_icon_element(widget, 83, 22, &I_WarningDolphinFlip_45x42);
     widget_add_string_element(
     widget_add_string_element(
-        widget, 7, 4, AlignLeft, AlignTop, FontPrimary, "Writing gone wrong!");
+        widget, 64, 0, AlignCenter, AlignTop, FontPrimary, "Failed to Write");
     widget_add_string_multiline_element(
     widget_add_string_multiline_element(
-        widget,
-        7,
-        17,
-        AlignLeft,
-        AlignTop,
-        FontSecondary,
-        "Not all sectors\nwere written\ncorrectly.");
+        widget, 0, 13, AlignLeft, AlignTop, FontSecondary, "Something went\nwrong while\nwriting");
 
 
     widget_add_button_element(
     widget_add_button_element(
-        widget, GuiButtonTypeLeft, "Finish", nfc_magic_scene_write_fail_widget_callback, instance);
+        widget, GuiButtonTypeLeft, "Retry", nfc_magic_scene_write_fail_widget_callback, instance);
 
 
     // Setup and start worker
     // Setup and start worker
     view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewWidget);
     view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewWidget);
@@ -41,12 +35,10 @@ bool nfc_magic_scene_write_fail_on_event(void* context, SceneManagerEvent event)
 
 
     if(event.type == SceneManagerEventTypeCustom) {
     if(event.type == SceneManagerEventTypeCustom) {
         if(event.event == GuiButtonTypeLeft) {
         if(event.event == GuiButtonTypeLeft) {
-            consumed = scene_manager_search_and_switch_to_previous_scene(
-                instance->scene_manager, NfcMagicSceneStart);
+            consumed = scene_manager_previous_scene(instance->scene_manager);
         }
         }
     } else if(event.type == SceneManagerEventTypeBack) {
     } else if(event.type == SceneManagerEventTypeBack) {
-        consumed = scene_manager_search_and_switch_to_previous_scene(
-            instance->scene_manager, NfcMagicSceneStart);
+        consumed = scene_manager_previous_scene(instance->scene_manager);
     }
     }
     return consumed;
     return consumed;
 }
 }

+ 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

+ 160 - 0
nfc_magic/views/write_problems.c

@@ -0,0 +1,160 @@
+#include "write_problems.h"
+
+#include <gui/elements.h>
+#include "nfc_magic_icons.h"
+
+struct WriteProblems {
+    View* view;
+    WriteProblemsCallback callback;
+    void* context;
+};
+
+typedef struct {
+    uint8_t problem_index;
+    uint8_t problems_total;
+    FuriString* content;
+} WriteProblemsViewModel;
+
+static void write_problems_view_draw_callback(Canvas* canvas, void* _model) {
+    WriteProblemsViewModel* model = _model;
+    FuriString* header = furi_string_alloc();
+    canvas_clear(canvas);
+
+    // Header
+    if(model->problems_total > 1) {
+        furi_string_printf(
+            header, "Warnings: %d of %d\n", model->problem_index + 1, model->problems_total);
+    } else {
+        furi_string_printf(header, "Warning!");
+    }
+
+    canvas_set_font(canvas, FontPrimary);
+    canvas_draw_str_aligned(canvas, 64, 0, AlignCenter, AlignTop, furi_string_get_cstr(header));
+
+    // Warning message
+    canvas_set_font(canvas, FontSecondary);
+    elements_text_box(
+        canvas, 1, 13, 76, 42, AlignLeft, AlignTop, furi_string_get_cstr(model->content), false);
+
+    // Butttons
+    if(model->problem_index == model->problems_total - 1) {
+        elements_button_center(canvas, "Skip");
+        elements_button_left(canvas, "Retry");
+    } else {
+        elements_button_center(canvas, "Next");
+        elements_button_left(canvas, "Back");
+    }
+
+    // Dolphin
+    canvas_draw_icon(canvas, 83, 22, &I_WarningDolphinFlip_45x42);
+
+    furi_string_free(header);
+}
+
+static bool write_problems_input_callback(InputEvent* event, void* context) {
+    WriteProblems* instance = context;
+
+    if(event->type == InputTypeShort) {
+        if(event->key == InputKeyLeft) {
+            instance->callback(WriteProblemsEventLeftPressed, instance->context);
+            return true;
+        } else if(event->key == InputKeyOk) {
+            instance->callback(WriteProblemsEventCenterPressed, instance->context);
+            return true;
+        }
+    }
+    return false;
+}
+
+WriteProblems* write_problems_alloc() {
+    WriteProblems* instance = malloc(sizeof(WriteProblems));
+    instance->view = view_alloc();
+    view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(WriteProblemsViewModel));
+    view_set_draw_callback(instance->view, write_problems_view_draw_callback);
+    view_set_input_callback(instance->view, write_problems_input_callback);
+    view_set_context(instance->view, instance);
+    with_view_model(
+        instance->view,
+        WriteProblemsViewModel * model,
+        {
+            model->content = furi_string_alloc();
+            model->problem_index = 0;
+            model->problems_total = 0;
+        },
+        false);
+
+    return instance;
+}
+
+void write_problems_free(WriteProblems* instance) {
+    furi_assert(instance);
+
+    with_view_model(
+        instance->view,
+        WriteProblemsViewModel * model,
+        { furi_string_free(model->content); },
+        false);
+
+    view_free(instance->view);
+    free(instance);
+}
+
+void write_problems_reset(WriteProblems* instance) {
+    furi_assert(instance);
+
+    with_view_model(
+        instance->view,
+        WriteProblemsViewModel * model,
+        {
+            model->problem_index = 0;
+            model->problems_total = 0;
+            furi_string_reset(model->content);
+        },
+        true);
+}
+
+View* write_problems_get_view(WriteProblems* instance) {
+    furi_assert(instance);
+
+    return instance->view;
+}
+
+void write_problems_set_callback(
+    WriteProblems* instance,
+    WriteProblemsCallback callback,
+    void* context) {
+    furi_assert(instance);
+    instance->callback = callback;
+    instance->context = context;
+}
+
+void write_problems_set_content(WriteProblems* instance, const char* content) {
+    furi_assert(instance);
+    furi_assert(content);
+
+    with_view_model(
+        instance->view,
+        WriteProblemsViewModel * model,
+        { furi_string_set(model->content, content); },
+        true);
+}
+
+void write_problems_set_problems_total(WriteProblems* instance, uint8_t problems_total) {
+    furi_assert(instance);
+
+    with_view_model(
+        instance->view,
+        WriteProblemsViewModel * model,
+        { model->problems_total = problems_total; },
+        true);
+}
+
+void write_problems_set_problem_index(WriteProblems* instance, uint8_t problem_index) {
+    furi_assert(instance);
+
+    with_view_model(
+        instance->view,
+        WriteProblemsViewModel * model,
+        { model->problem_index = problem_index; },
+        true);
+}

+ 32 - 0
nfc_magic/views/write_problems.h

@@ -0,0 +1,32 @@
+#pragma once
+
+#include <stdint.h>
+#include <gui/view.h>
+
+typedef struct WriteProblems WriteProblems;
+
+typedef enum {
+    WriteProblemsEventCenterPressed,
+    WriteProblemsEventLeftPressed,
+} WriteProblemsEvent;
+
+typedef void (*WriteProblemsCallback)(WriteProblemsEvent event, void* context);
+
+WriteProblems* write_problems_alloc();
+
+void write_problems_free(WriteProblems* instance);
+
+void write_problems_reset(WriteProblems* instance);
+
+View* write_problems_get_view(WriteProblems* instance);
+
+void write_problems_set_callback(
+    WriteProblems* instance,
+    WriteProblemsCallback callback,
+    void* context);
+
+void write_problems_set_content(WriteProblems* instance, const char* content);
+
+void write_problems_set_problem_index(WriteProblems* instance, uint8_t index);
+
+void write_problems_set_problems_total(WriteProblems* instance, uint8_t total);