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

no gen4 ? :(

actually, gen4 changes will be finished and merged here soon, app is reverted to ofw current version only for testing purposes, please be patient and wait for next updates on gen4 improvements
MX 1 год назад
Родитель
Сommit
a6bf78714f
56 измененных файлов с 3472 добавлено и 1242 удалено
  1. 1 6
      application.fam
  2. BIN
      assets/DolphinSuccess_91x55.png
  3. BIN
      assets/WarningDolphinFlip_45x42.png
  4. 0 97
      lib/magic/protocols/gen4/gen4.c
  5. 0 101
      lib/magic/protocols/gen4/gen4.h
  6. 28 29
      magic/nfc_magic_scanner.c
  7. 1 4
      magic/nfc_magic_scanner.h
  8. 0 0
      magic/protocols/gen1a/gen1a_poller.c
  9. 0 0
      magic/protocols/gen1a/gen1a_poller.h
  10. 0 0
      magic/protocols/gen1a/gen1a_poller_i.c
  11. 0 0
      magic/protocols/gen1a/gen1a_poller_i.h
  12. 178 0
      magic/protocols/gen2/crypto1.c
  13. 45 0
      magic/protocols/gen2/crypto1.h
  14. 594 0
      magic/protocols/gen2/gen2_poller.c
  15. 97 0
      magic/protocols/gen2/gen2_poller.h
  16. 629 0
      magic/protocols/gen2/gen2_poller_i.c
  17. 139 0
      magic/protocols/gen2/gen2_poller_i.h
  18. 66 228
      magic/protocols/gen4/gen4_poller.c
  19. 9 11
      magic/protocols/gen4/gen4_poller.h
  20. 37 111
      magic/protocols/gen4/gen4_poller_i.c
  21. 39 32
      magic/protocols/gen4/gen4_poller_i.h
  22. 4 2
      magic/protocols/nfc_magic_protocols.c
  23. 2 0
      magic/protocols/nfc_magic_protocols.h
  24. 29 6
      nfc_magic_app.c
  25. 56 7
      nfc_magic_app_i.h
  26. 10 7
      scenes/nfc_magic_scene_config.h
  27. 15 1
      scenes/nfc_magic_scene_file_select.c
  28. 55 0
      scenes/nfc_magic_scene_gen2_menu.c
  29. 130 0
      scenes/nfc_magic_scene_gen2_write_check.c
  30. 28 6
      scenes/nfc_magic_scene_gen4_actions_menu.c
  31. 29 33
      scenes/nfc_magic_scene_gen4_get_cfg.c
  32. 1 50
      scenes/nfc_magic_scene_gen4_menu.c
  33. 28 32
      scenes/nfc_magic_scene_gen4_revision.c
  34. 0 106
      scenes/nfc_magic_scene_gen4_select_shd_mode.c
  35. 23 24
      scenes/nfc_magic_scene_gen4_set_cfg.c
  36. 0 123
      scenes/nfc_magic_scene_gen4_set_direct_write_block_0_mode.c
  37. 20 13
      scenes/nfc_magic_scene_gen4_show_cfg.c
  38. 0 127
      scenes/nfc_magic_scene_gen4_show_info.c
  39. 55 0
      scenes/nfc_magic_scene_gen4_show_rev.c
  40. 5 10
      scenes/nfc_magic_scene_key_input.c
  41. 20 40
      scenes/nfc_magic_scene_magic_info.c
  42. 311 0
      scenes/nfc_magic_scene_mf_classic_dict_attack.c
  43. 65 0
      scenes/nfc_magic_scene_mf_classic_menu.c
  44. 124 0
      scenes/nfc_magic_scene_mf_classic_write_check.c
  45. 0 3
      scenes/nfc_magic_scene_not_magic.c
  46. 1 1
      scenes/nfc_magic_scene_start.c
  47. 2 2
      scenes/nfc_magic_scene_success.c
  48. 48 6
      scenes/nfc_magic_scene_wipe.c
  49. 5 2
      scenes/nfc_magic_scene_wipe_fail.c
  50. 50 5
      scenes/nfc_magic_scene_write.c
  51. 6 14
      scenes/nfc_magic_scene_write_fail.c
  52. 0 3
      scenes/nfc_magic_scene_wrong_card.c
  53. 245 0
      views/dict_attack.c
  54. 50 0
      views/dict_attack.h
  55. 160 0
      views/write_problems.c
  56. 32 0
      views/write_problems.h

+ 1 - 6
application.fam

@@ -10,13 +10,8 @@ App(
     ],
     ],
     stack_size=4 * 1024,
     stack_size=4 * 1024,
     fap_description="Application for writing to NFC tags with modifiable sector 0",
     fap_description="Application for writing to NFC tags with modifiable sector 0",
-    fap_version="1.6",
+    fap_version="1.7",
     fap_icon="assets/Nfc_10px.png",
     fap_icon="assets/Nfc_10px.png",
     fap_category="NFC",
     fap_category="NFC",
-    fap_private_libs=[
-        Lib(
-            name="magic",
-        ),
-    ],
     fap_icon_assets="assets",
     fap_icon_assets="assets",
 )
 )

BIN
assets/DolphinSuccess_91x55.png


BIN
assets/WarningDolphinFlip_45x42.png


+ 0 - 97
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
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);

+ 28 - 29
lib/magic/nfc_magic_scanner.c → 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
lib/magic/nfc_magic_scanner.h → 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

+ 0 - 0
lib/magic/protocols/gen1a/gen1a_poller.c → magic/protocols/gen1a/gen1a_poller.c


+ 0 - 0
lib/magic/protocols/gen1a/gen1a_poller.h → magic/protocols/gen1a/gen1a_poller.h


+ 0 - 0
lib/magic/protocols/gen1a/gen1a_poller_i.c → magic/protocols/gen1a/gen1a_poller_i.c


+ 0 - 0
lib/magic/protocols/gen1a/gen1a_poller_i.h → magic/protocols/gen1a/gen1a_poller_i.h


+ 178 - 0
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
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
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 const 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 const 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},
+};
+
+const char* const 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,
+    const 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
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 const char* const 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
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
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

+ 66 - 228
lib/magic/protocols/gen4/gen4_poller.c → magic/protocols/gen4/gen4_poller.c

@@ -1,33 +1,30 @@
-#include "bit_buffer.h"
+#include "core/log.h"
 #include "gen4_poller_i.h"
 #include "gen4_poller_i.h"
-#include "protocols/gen4/gen4.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/helpers/nfc_util.h>
+#include <bit_lib/bit_lib.h>
 #include <nfc/nfc_poller.h>
 #include <nfc/nfc_poller.h>
-#include <bit_lib.h>
-#include <string.h>
+
+#include <furi/furi.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 +56,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 +67,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 +90,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 +107,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 +122,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 +146,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 +153,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;
@@ -222,14 +172,12 @@ NfcCommand gen4_poller_request_mode_handler(Gen4Poller* instance) {
         instance->state = Gen4PollerStateRequestWriteData;
         instance->state = Gen4PollerStateRequestWriteData;
     } else if(instance->gen4_event_data.request_mode.mode == Gen4PollerModeSetPassword) {
     } else if(instance->gen4_event_data.request_mode.mode == Gen4PollerModeSetPassword) {
         instance->state = Gen4PollerStateChangePassword;
         instance->state = Gen4PollerStateChangePassword;
-    } else if(instance->gen4_event_data.request_mode.mode == Gen4PollerModeGetInfo) {
-        instance->state = Gen4PollerStateGetInfo;
-    } else if(instance->gen4_event_data.request_mode.mode == Gen4PollerModeSetDefaultCfg) {
+    } else if(instance->gen4_event_data.request_mode.mode == Gen4PollerModeSetDefaultCFG) {
         instance->state = Gen4PollerStateSetDefaultConfig;
         instance->state = Gen4PollerStateSetDefaultConfig;
-    } else if(instance->gen4_event_data.request_mode.mode == Gen4PollerModeSetShadowMode) {
-        instance->state = Gen4PollerStateSetShadowMode;
-    } else if(instance->gen4_event_data.request_mode.mode == Gen4PollerModeSetDirectWriteBlock0Mode) {
-        instance->state = Gen4PollerStateSetDirectWriteBlock0;
+    } else if(instance->gen4_event_data.request_mode.mode == Gen4PollerModeGetCFG) {
+        instance->state = Gen4PollerStateGetCurrentConfig;
+    } else if(instance->gen4_event_data.request_mode.mode == Gen4PollerModeGetRevision) {
+        instance->state = Gen4PollerStateGetRevision;
     } else {
     } else {
         instance->state = Gen4PollerStateFail;
         instance->state = Gen4PollerStateFail;
     }
     }
@@ -246,15 +194,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 +257,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] = Gen4PollerShadowModeIgnore;
+            instance->config[24] = iso3_data->atqa[0];
+            instance->config[25] = iso3_data->atqa[1];
+            instance->config[26] = iso3_data->sak;
+            instance->config[27] = 0x00;
+            instance->config[28] = instance->total_blocks - 1;
+            instance->config[29] = 0x01;
 
 
             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 +316,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 +326,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 +335,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] = Gen4PollerShadowModeHighSpeedIgnore;
+            instance->config[24] = iso3_data->atqa[0];
+            instance->config[25] = iso3_data->atqa[1];
+            instance->config[26] = iso3_data->sak;
+            instance->config[27] = 0x00;
+            instance->config[28] = instance->total_blocks - 1;
+            instance->config[29] = 0x01;
 
 
             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,45 +421,6 @@ static NfcCommand gen4_poller_write_mf_ultralight(Gen4Poller* instance) {
                 break;
                 break;
             }
             }
 
 
-            // Password
-            MfUltralightConfigPages* config_pages = NULL;
-            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;
-            }
-
             instance->state = Gen4PollerStateSuccess;
             instance->state = Gen4PollerStateSuccess;
         }
         }
     } while(false);
     } while(false);
@@ -522,14 +431,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) {
@@ -549,7 +455,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) {
@@ -572,8 +478,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);
@@ -591,16 +497,16 @@ NfcCommand gen4_poller_get_current_cfg_handler(Gen4Poller* instance) {
     NfcCommand command = NfcCommandContinue;
     NfcCommand command = NfcCommandContinue;
 
 
     do {
     do {
-        Gen4Config config;
+        uint8_t the_config[32] = {};
 
 
-        Gen4PollerError error = gen4_poller_get_config(instance, instance->password, &config);
+        Gen4PollerError error = gen4_poller_get_config(instance, instance->password, the_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.display_config, the_config, sizeof(the_config));
 
 
         instance->state = Gen4PollerStateSuccess;
         instance->state = Gen4PollerStateSuccess;
     } while(false);
     } while(false);
@@ -612,83 +518,16 @@ 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);
-        if(error != Gen4PollerErrorNone) {
-            FURI_LOG_E(TAG, "Failed to get revision: %d", error);
-            instance->state = Gen4PollerStateFail;
-            break;
-        }
-        // Copy revision data to event data buffer
-        memcpy(instance->gen4_data->revision.data, revision.data, sizeof(revision));
-
-        instance->state = Gen4PollerStateSuccess;
-    } while(false);
-
-    return command;
-}
-
-NfcCommand gen4_poller_get_info_handler(Gen4Poller* instance) {
-    NfcCommand command = NfcCommandContinue;
-
-    do {
-        Gen4 gen4_data;
-
+        uint8_t the_revision[5] = {0};
         Gen4PollerError error =
         Gen4PollerError error =
-            gen4_poller_get_revision(instance, instance->password, &gen4_data.revision);
+            gen4_poller_get_revision(instance, instance->password, the_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);
-        if(error != Gen4PollerErrorNone) {
-            FURI_LOG_E(TAG, "Failed to get current config: %d", error);
-            instance->state = Gen4PollerStateFail;
-            break;
-        }
-
-        // Copy config&&revision data to event data buffer
-        gen4_copy(instance->gen4_data, &gen4_data);
-
-        instance->state = Gen4PollerStateSuccess;
-    } while(false);
-
-    return command;
-}
-
-NfcCommand gen4_poller_set_shadow_mode_handler(Gen4Poller* instance) {
-    NfcCommand command = NfcCommandContinue;
-
-    do {
-        Gen4PollerError error =
-            gen4_poller_set_shadow_mode(instance, instance->password, instance->shadow_mode);
-
-        if(error != Gen4PollerErrorNone) {
-            FURI_LOG_E(TAG, "Failed to set shadow mode: %d", error);
-            instance->state = Gen4PollerStateFail;
-            break;
-        }
-
-        instance->state = Gen4PollerStateSuccess;
-    } while(false);
-
-    return command;
-}
-
-NfcCommand gen4_poller_set_direct_write_block_0_mode_handler(Gen4Poller* instance) {
-    NfcCommand command = NfcCommandContinue;
-
-    do {
-        Gen4PollerError error = gen4_poller_set_direct_write_block_0_mode(
-            instance, instance->password, instance->direct_write_block_0_mode);
-
-        if(error != Gen4PollerErrorNone) {
-            FURI_LOG_E(TAG, "Failed to set direct write to block 0 mode: %d", error);
-            instance->state = Gen4PollerStateFail;
-            break;
-        }
+        // Copy revision data to event data buffer
+        memcpy(instance->gen4_event_data.revision_data, the_revision, sizeof(the_revision));
 
 
         instance->state = Gen4PollerStateSuccess;
         instance->state = Gen4PollerStateSuccess;
     } while(false);
     } while(false);
@@ -727,10 +566,9 @@ static const Gen4PollerStateHandler gen4_poller_state_handlers[Gen4PollerStateNu
     [Gen4PollerStateWrite] = gen4_poller_write_handler,
     [Gen4PollerStateWrite] = gen4_poller_write_handler,
     [Gen4PollerStateWipe] = gen4_poller_wipe_handler,
     [Gen4PollerStateWipe] = gen4_poller_wipe_handler,
     [Gen4PollerStateChangePassword] = gen4_poller_change_password_handler,
     [Gen4PollerStateChangePassword] = gen4_poller_change_password_handler,
-    [Gen4PollerStateGetInfo] = gen4_poller_get_info_handler,
     [Gen4PollerStateSetDefaultConfig] = gen4_poller_set_default_cfg_handler,
     [Gen4PollerStateSetDefaultConfig] = gen4_poller_set_default_cfg_handler,
-    [Gen4PollerStateSetShadowMode] = gen4_poller_set_shadow_mode_handler,
-    [Gen4PollerStateSetDirectWriteBlock0] = gen4_poller_set_direct_write_block_0_mode_handler,
+    [Gen4PollerStateGetCurrentConfig] = gen4_poller_get_current_cfg_handler,
+    [Gen4PollerStateGetRevision] = gen4_poller_get_revision_handler,
     [Gen4PollerStateSuccess] = gen4_poller_success_handler,
     [Gen4PollerStateSuccess] = gen4_poller_success_handler,
     [Gen4PollerStateFail] = gen4_poller_fail_handler,
     [Gen4PollerStateFail] = gen4_poller_fail_handler,
 
 

+ 9 - 11
lib/magic/protocols/gen4/gen4_poller.h → 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)
@@ -41,11 +38,9 @@ typedef enum {
     Gen4PollerModeWrite,
     Gen4PollerModeWrite,
     Gen4PollerModeSetPassword,
     Gen4PollerModeSetPassword,
 
 
-    Gen4PollerModeGetInfo,
-
-    Gen4PollerModeSetDefaultCfg,
-    Gen4PollerModeSetShadowMode,
-    Gen4PollerModeSetDirectWriteBlock0Mode
+    Gen4PollerModeSetDefaultCFG,
+    Gen4PollerModeGetCFG,
+    Gen4PollerModeGetRevision,
 } Gen4PollerMode;
 } Gen4PollerMode;
 
 
 typedef struct {
 typedef struct {
@@ -58,13 +53,16 @@ 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;
+
+    uint8_t display_config[32];
+    uint8_t revision_data[5];
 } Gen4PollerEventData;
 } Gen4PollerEventData;
 
 
 typedef struct {
 typedef struct {
@@ -76,13 +74,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);
 
 

+ 37 - 111
lib/magic/protocols/gen4/gen4_poller_i.c → magic/protocols/gen4/gen4_poller_i.c

@@ -1,23 +1,21 @@
 #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 "core/log.h"
 #include <nfc/protocols/iso14443_3a/iso14443_3a_poller.h>
 #include <nfc/protocols/iso14443_3a/iso14443_3a_poller.h>
 
 
 #define GEN4_CMD_PREFIX (0xCF)
 #define GEN4_CMD_PREFIX (0xCF)
 
 
-#define GEN4_CMD_SET_SHD_MODE (0x32)
 #define GEN4_CMD_GET_CFG (0xC6)
 #define GEN4_CMD_GET_CFG (0xC6)
 #define GEN4_CMD_GET_REVISION (0xCC)
 #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_DW_BLOCK_0 (0xCF)
 #define GEN4_CMD_SET_CFG (0xF0)
 #define GEN4_CMD_SET_CFG (0xF0)
 #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 CONFIG_SIZE (32)
+#define REVISION_SIZE (5)
 
 
 static Gen4PollerError gen4_poller_process_error(Iso14443_3aError error) {
 static Gen4PollerError gen4_poller_process_error(Iso14443_3aError error) {
     Gen4PollerError ret = Gen4PollerErrorNone;
     Gen4PollerError ret = Gen4PollerErrorNone;
@@ -32,81 +30,15 @@ static Gen4PollerError gen4_poller_process_error(Iso14443_3aError error) {
 }
 }
 
 
 Gen4PollerError
 Gen4PollerError
-    gen4_poller_set_shadow_mode(Gen4Poller* instance, Gen4Password password, Gen4ShadowMode mode) {
+    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_byte(instance->tx_buffer, GEN4_CMD_SET_SHD_MODE);
-        bit_buffer_append_byte(instance->tx_buffer, mode);
-
-        Iso14443_3aError error = iso14443_3a_poller_send_standard_frame(
-            instance->iso3_poller, instance->tx_buffer, instance->rx_buffer, GEN4_POLLER_MAX_FWT);
-
-        if(error != Iso14443_3aErrorNone) {
-            ret = gen4_poller_process_error(error);
-            break;
-        }
-
-        size_t response = bit_buffer_get_size_bytes(instance->rx_buffer);
-
-        FURI_LOG_D(TAG, "Card response: 0x%02X, Shadow mode set: 0x%02X", response, mode);
-
-        if(response != GEN4_RESPONSE_SUCCESS) {
-            ret = Gen4PollerErrorProtocol;
-            break;
-        }
-
-    } while(false);
-
-    return ret;
-}
-
-Gen4PollerError gen4_poller_set_direct_write_block_0_mode(
-    Gen4Poller* instance,
-    Gen4Password password,
-    Gen4DirectWriteBlock0Mode mode) {
-    Gen4PollerError ret = Gen4PollerErrorNone;
-    bit_buffer_reset(instance->tx_buffer);
-
-    do {
-        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_byte(instance->tx_buffer, GEN4_CMD_SET_DW_BLOCK_0);
-        bit_buffer_append_byte(instance->tx_buffer, mode);
-
-        Iso14443_3aError error = iso14443_3a_poller_send_standard_frame(
-            instance->iso3_poller, instance->tx_buffer, instance->rx_buffer, GEN4_POLLER_MAX_FWT);
-
-        if(error != Iso14443_3aErrorNone) {
-            ret = gen4_poller_process_error(error);
-            break;
-        }
-        size_t response = bit_buffer_get_size_bytes(instance->rx_buffer);
-
-        FURI_LOG_D(
-            TAG, "Card response: 0x%02X, Direct write to block 0 mode set: 0x%02X", response, mode);
-
-        if(response != GEN4_RESPONSE_SUCCESS) {
-            ret = Gen4PollerErrorProtocol;
-            break;
-        }
-
-    } while(false);
-
-    return ret;
-}
-
-Gen4PollerError
-    gen4_poller_get_config(Gen4Poller* instance, Gen4Password password, Gen4Config* config_result) {
-    Gen4PollerError ret = Gen4PollerErrorNone;
-    bit_buffer_reset(instance->tx_buffer);
-
-    do {
-        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(
@@ -119,26 +51,26 @@ Gen4PollerError
 
 
         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) {
             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);
     } 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(
@@ -150,11 +82,11 @@ Gen4PollerError gen4_poller_get_revision(
         }
         }
 
 
         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 != 5) {
             ret = Gen4PollerErrorProtocol;
             ret = Gen4PollerErrorProtocol;
             break;
             break;
         }
         }
-        bit_buffer_write_bytes(instance->rx_buffer, revision_result->data, GEN4_REVISION_SIZE);
+        bit_buffer_write_bytes(instance->rx_buffer, revision_result, REVISION_SIZE);
     } while(false);
     } while(false);
 
 
     return ret;
     return ret;
@@ -162,19 +94,21 @@ Gen4PollerError gen4_poller_get_revision(
 
 
 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 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);
@@ -184,11 +118,8 @@ Gen4PollerError gen4_poller_set_config(
             break;
             break;
         }
         }
 
 
-        size_t response = bit_buffer_get_size_bytes(instance->rx_buffer);
-
-        FURI_LOG_D(TAG, "Card response to set default config command: 0x%02X", response);
-
-        if(response != GEN4_RESPONSE_SUCCESS) {
+        size_t rx_bytes = bit_buffer_get_size_bytes(instance->rx_buffer);
+        if(rx_bytes != 2) {
             ret = Gen4PollerErrorProtocol;
             ret = Gen4PollerErrorProtocol;
             break;
             break;
         }
         }
@@ -199,15 +130,17 @@ Gen4PollerError gen4_poller_set_config(
 
 
 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 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);
@@ -230,19 +163,20 @@ Gen4PollerError gen4_poller_write_block(
     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);
@@ -252,16 +186,8 @@ Gen4PollerError gen4_poller_change_password(
             break;
             break;
         }
         }
 
 
-        size_t response = bit_buffer_get_size_bytes(instance->rx_buffer);
-
-        FURI_LOG_D(
-            TAG,
-            "Trying to change password from 0x%08lX to 0x%08lX. Card response: 0x%02X",
-            pwd_current.value,
-            pwd_new.value,
-            response);
-
-        if(response != GEN4_RESPONSE_SUCCESS) {
+        size_t rx_bytes = bit_buffer_get_size_bytes(instance->rx_buffer);
+        if(rx_bytes != 2) {
             ret = Gen4PollerErrorProtocol;
             ret = Gen4PollerErrorProtocol;
             break;
             break;
         }
         }

+ 39 - 32
lib/magic/protocols/gen4/gen4_poller_i.h → magic/protocols/gen4/gen4_poller_i.h

@@ -1,6 +1,5 @@
 #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>
@@ -18,6 +17,32 @@ 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,
+    // written data is discarded
+    Gen4PollerShadowModeIgnore = 0x02,
+    // apparently for UL?
+    Gen4PollerShadowModeHighSpeedIgnore = 0x03
+} Gen4PollerShadowMode;
+
 typedef enum {
 typedef enum {
     Gen4PollerStateIdle,
     Gen4PollerStateIdle,
     Gen4PollerStateRequestMode,
     Gen4PollerStateRequestMode,
@@ -26,10 +51,9 @@ typedef enum {
     Gen4PollerStateWipe,
     Gen4PollerStateWipe,
     Gen4PollerStateChangePassword,
     Gen4PollerStateChangePassword,
 
 
-    Gen4PollerStateGetInfo,
     Gen4PollerStateSetDefaultConfig,
     Gen4PollerStateSetDefaultConfig,
-    Gen4PollerStateSetShadowMode,
-    Gen4PollerStateSetDirectWriteBlock0,
+    Gen4PollerStateGetCurrentConfig,
+    Gen4PollerStateGetRevision,
 
 
     Gen4PollerStateSuccess,
     Gen4PollerStateSuccess,
     Gen4PollerStateFail,
     Gen4PollerStateFail,
@@ -41,15 +65,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 +75,9 @@ struct Gen4Poller {
 
 
     NfcProtocol protocol;
     NfcProtocol protocol;
     const NfcDeviceData* data;
     const NfcDeviceData* data;
+    uint32_t new_password;
+
+    uint8_t config[GEN4_POLLER_CONFIG_SIZE_MAX];
 
 
     Gen4PollerEvent gen4_event;
     Gen4PollerEvent gen4_event;
     Gen4PollerEventData gen4_event_data;
     Gen4PollerEventData gen4_event_data;
@@ -69,37 +88,25 @@ 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_get_revision(
-    Gen4Poller* instance,
-    Gen4Password password,
-    Gen4Revision* revision_result);
-
 Gen4PollerError
 Gen4PollerError
-    gen4_poller_get_config(Gen4Poller* instance, Gen4Password password, Gen4Config* config_result);
+    gen4_poller_change_password(Gen4Poller* instance, uint32_t pwd_current, uint32_t pwd_new);
 
 
 Gen4PollerError
 Gen4PollerError
-    gen4_poller_set_shadow_mode(Gen4Poller* instance, Gen4Password password, Gen4ShadowMode mode);
+    gen4_poller_get_revision(Gen4Poller* instance, uint32_t password, uint8_t* revision_result);
 
 
-Gen4PollerError gen4_poller_set_direct_write_block_0_mode(
-    Gen4Poller* instance,
-    Gen4Password password,
-    Gen4DirectWriteBlock0Mode mode);
+Gen4PollerError
+    gen4_poller_get_config(Gen4Poller* instance, uint32_t password, uint8_t* config_result);
 
 
 #ifdef __cplusplus
 #ifdef __cplusplus
 }
 }

+ 4 - 2
lib/magic/protocols/nfc_magic_protocols.c → 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
lib/magic/protocols/nfc_magic_protocols.h → 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_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);
 
 

+ 56 - 7
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>
 
 
@@ -28,16 +30,24 @@
 
 
 #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/protocols/nfc_magic_protocols.h"
-#include "lib/magic/protocols/gen1a/gen1a_poller.h"
-#include "lib/magic/protocols/gen4/gen4_poller.h"
+#include "magic/nfc_magic_scanner.h"
+#include "magic/protocols/nfc_magic_protocols.h"
+#include "magic/protocols/gen1a/gen1a_poller.h"
+#include "magic/protocols/gen2/gen2_poller.h"
+#include "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 +58,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 +94,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_display[32];
+    uint8_t gen4_revision_display[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 +139,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);

+ 10 - 7
scenes/nfc_magic_scene_config.h

@@ -5,13 +5,11 @@ ADD_SCENE(nfc_magic, magic_info, MagicInfo)
 ADD_SCENE(nfc_magic, gen1_menu, Gen1Menu)
 ADD_SCENE(nfc_magic, gen1_menu, Gen1Menu)
 ADD_SCENE(nfc_magic, gen4_menu, Gen4Menu)
 ADD_SCENE(nfc_magic, gen4_menu, Gen4Menu)
 ADD_SCENE(nfc_magic, gen4_actions_menu, Gen4ActionsMenu)
 ADD_SCENE(nfc_magic, gen4_actions_menu, Gen4ActionsMenu)
-ADD_SCENE(nfc_magic, gen4_set_default_cfg, Gen4SetDefaultCfg)
-ADD_SCENE(nfc_magic, gen4_get_info, Gen4GetInfo)
-ADD_SCENE(nfc_magic, gen4_show_cfg, Gen4ShowCfg)
-ADD_SCENE(nfc_magic, gen4_show_info, Gen4ShowInfo)
-ADD_SCENE(nfc_magic, gen4_select_shd_mode, Gen4SelectShdMode)
-ADD_SCENE(nfc_magic, gen4_set_shd_mode, Gen4SetShdMode)
-ADD_SCENE(nfc_magic, gen4_set_direct_write_block_0_mode, Gen4SetDirectWriteBlock0Mode)
+ADD_SCENE(nfc_magic, gen4_get_cfg, Gen4GetCFG)
+ADD_SCENE(nfc_magic, gen4_set_cfg, Gen4SetCFG)
+ADD_SCENE(nfc_magic, gen4_revision, Gen4Revision)
+ADD_SCENE(nfc_magic, gen4_show_rev, Gen4ShowRev)
+ADD_SCENE(nfc_magic, gen4_show_cfg, Gen4ShowCFG)
 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)
 ADD_SCENE(nfc_magic, wipe_fail, WipeFail)
 ADD_SCENE(nfc_magic, wipe_fail, WipeFail)
@@ -24,3 +22,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
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
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
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);
+}

+ 28 - 6
scenes/nfc_magic_scene_gen4_actions_menu.c

@@ -1,8 +1,11 @@
 #include "../nfc_magic_app_i.h"
 #include "../nfc_magic_app_i.h"
+#include "furi_hal_rtc.h"
 
 
 enum SubmenuIndex {
 enum SubmenuIndex {
     SubmenuIndexAuthenticate,
     SubmenuIndexAuthenticate,
-    SubmenuIndexSetDefaultConfig,
+    SubmenuIndexSetStandardConfig,
+    SubmenuIndexGetConfig,
+    SubmenuIndexGetRevision
 };
 };
 
 
 void nfc_magic_scene_gen4_actions_menu_submenu_callback(void* context, uint32_t index) {
 void nfc_magic_scene_gen4_actions_menu_submenu_callback(void* context, uint32_t index) {
@@ -23,10 +26,24 @@ void nfc_magic_scene_gen4_actions_menu_on_enter(void* context) {
         instance);
         instance);
     submenu_add_item(
     submenu_add_item(
         submenu,
         submenu,
-        "Set Default Config",
-        SubmenuIndexSetDefaultConfig,
+        "Get Revision",
+        SubmenuIndexGetRevision,
         nfc_magic_scene_gen4_actions_menu_submenu_callback,
         nfc_magic_scene_gen4_actions_menu_submenu_callback,
         instance);
         instance);
+    submenu_add_item(
+        submenu,
+        "Set Standard Config",
+        SubmenuIndexSetStandardConfig,
+        nfc_magic_scene_gen4_actions_menu_submenu_callback,
+        instance);
+    if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
+        submenu_add_item(
+            submenu,
+            "Get Config",
+            SubmenuIndexGetConfig,
+            nfc_magic_scene_gen4_actions_menu_submenu_callback,
+            instance);
+    }
 
 
     submenu_set_selected_item(
     submenu_set_selected_item(
         submenu,
         submenu,
@@ -42,11 +59,16 @@ bool nfc_magic_scene_gen4_actions_menu_on_event(void* context, SceneManagerEvent
         if(event.event == SubmenuIndexAuthenticate) {
         if(event.event == SubmenuIndexAuthenticate) {
             scene_manager_next_scene(instance->scene_manager, NfcMagicSceneKeyInput);
             scene_manager_next_scene(instance->scene_manager, NfcMagicSceneKeyInput);
             consumed = true;
             consumed = true;
-        } else if(event.event == SubmenuIndexSetDefaultConfig) {
-            scene_manager_next_scene(instance->scene_manager, NfcMagicSceneGen4SetDefaultCfg);
+        } else if(event.event == SubmenuIndexSetStandardConfig) {
+            scene_manager_next_scene(instance->scene_manager, NfcMagicSceneGen4SetCFG);
+            consumed = true;
+        } else if(event.event == SubmenuIndexGetConfig) {
+            scene_manager_next_scene(instance->scene_manager, NfcMagicSceneGen4GetCFG);
+            consumed = true;
+        } else if(event.event == SubmenuIndexGetRevision) {
+            scene_manager_next_scene(instance->scene_manager, NfcMagicSceneGen4Revision);
             consumed = true;
             consumed = true;
         }
         }
-
         scene_manager_set_scene_state(
         scene_manager_set_scene_state(
             instance->scene_manager, NfcMagicSceneGen4ActionsMenu, event.event);
             instance->scene_manager, NfcMagicSceneGen4ActionsMenu, event.event);
     } else if(event.type == SceneManagerEventTypeBack) {
     } else if(event.type == SceneManagerEventTypeBack) {

+ 29 - 33
scenes/nfc_magic_scene_gen4_set_shd_mode.c → scenes/nfc_magic_scene_gen4_get_cfg.c

@@ -1,23 +1,28 @@
 #include "../nfc_magic_app_i.h"
 #include "../nfc_magic_app_i.h"
-#include "../lib/magic/protocols/gen4/gen4_poller_i.h"
 
 
 enum {
 enum {
-    NfcMagicSceneGen4SetShadowModeStateCardSearch,
-    NfcMagicSceneGen4SetShadowModeStateCardFound,
+    NfcMagicSceneGen4GetCFGStateCardSearch,
+    NfcMagicSceneGen4GetCFGStateCardFound,
 };
 };
 
 
-NfcCommand
-    nfc_magic_scene_gen4_set_shd_mode_poller_callback(Gen4PollerEvent event, void* context) {
+NfcCommand nfc_mafic_scene_gen4_get_cfg_poller_callback(Gen4PollerEvent event, void* context) {
     NfcMagicApp* instance = context;
     NfcMagicApp* instance = context;
     furi_assert(event.data);
     furi_assert(event.data);
 
 
     NfcCommand command = NfcCommandContinue;
     NfcCommand command = NfcCommandContinue;
+
     if(event.type == Gen4PollerEventTypeCardDetected) {
     if(event.type == Gen4PollerEventTypeCardDetected) {
         view_dispatcher_send_custom_event(
         view_dispatcher_send_custom_event(
             instance->view_dispatcher, NfcMagicCustomEventCardDetected);
             instance->view_dispatcher, NfcMagicCustomEventCardDetected);
     } else if(event.type == Gen4PollerEventTypeRequestMode) {
     } else if(event.type == Gen4PollerEventTypeRequestMode) {
-        event.data->request_mode.mode = Gen4PollerModeSetShadowMode;
+        event.data->request_mode.mode = Gen4PollerModeGetCFG;
     } else if(event.type == Gen4PollerEventTypeSuccess) {
     } else if(event.type == Gen4PollerEventTypeSuccess) {
+        // Copy config from event to main instance to display it on success scene
+        memcpy(
+            instance->gen4_config_display,
+            event.data->display_config,
+            sizeof(event.data->display_config));
+
         view_dispatcher_send_custom_event(
         view_dispatcher_send_custom_event(
             instance->view_dispatcher, NfcMagicCustomEventWorkerSuccess);
             instance->view_dispatcher, NfcMagicCustomEventWorkerSuccess);
         command = NfcCommandStop;
         command = NfcCommandStop;
@@ -30,47 +35,40 @@ NfcCommand
     return command;
     return command;
 }
 }
 
 
-static void nfc_magic_scene_gen4_set_shd_mode_setup_view(NfcMagicApp* instance) {
+static void nfc_magic_scene_gen4_get_cfg_setup_view(NfcMagicApp* instance) {
     Popup* popup = instance->popup;
     Popup* popup = instance->popup;
     popup_reset(popup);
     popup_reset(popup);
     uint32_t state =
     uint32_t state =
-        scene_manager_get_scene_state(instance->scene_manager, NfcMagicSceneGen4SetShdMode);
+        scene_manager_get_scene_state(instance->scene_manager, NfcMagicSceneGen4GetCFG);
 
 
-    if(state == NfcMagicSceneGen4SetShadowModeStateCardSearch) {
+    if(state == NfcMagicSceneGen4GetCFGStateCardSearch) {
         popup_set_icon(instance->popup, 0, 8, &I_NFC_manual_60x50);
         popup_set_icon(instance->popup, 0, 8, &I_NFC_manual_60x50);
         popup_set_text(
         popup_set_text(
             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, "Reading\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);
 }
 }
 
 
-void nfc_magic_scene_gen4_set_shd_mode_on_enter(void* context) {
+void nfc_magic_scene_gen4_get_cfg_on_enter(void* context) {
     NfcMagicApp* instance = context;
     NfcMagicApp* instance = context;
 
 
-    uint8_t shadow_mode =
-        scene_manager_get_scene_state(instance->scene_manager, NfcMagicSceneGen4SetShdMode);
-
     scene_manager_set_scene_state(
     scene_manager_set_scene_state(
-        instance->scene_manager,
-        NfcMagicSceneGen4SetShdMode,
-        NfcMagicSceneGen4SetShadowModeStateCardSearch);
-    nfc_magic_scene_gen4_set_shd_mode_setup_view(instance);
+        instance->scene_manager, NfcMagicSceneGen4GetCFG, NfcMagicSceneGen4GetCFGStateCardSearch);
+    nfc_magic_scene_gen4_get_cfg_setup_view(instance);
 
 
     nfc_magic_app_blink_start(instance);
     nfc_magic_app_blink_start(instance);
 
 
     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);
-    instance->gen4_poller->shadow_mode = shadow_mode;
-
     gen4_poller_start(
     gen4_poller_start(
-        instance->gen4_poller, nfc_magic_scene_gen4_set_shd_mode_poller_callback, instance);
+        instance->gen4_poller, nfc_mafic_scene_gen4_get_cfg_poller_callback, instance);
 }
 }
 
 
-bool nfc_magic_scene_gen4_set_shd_mode_on_event(void* context, SceneManagerEvent event) {
+bool nfc_magic_scene_gen4_get_cfg_on_event(void* context, SceneManagerEvent event) {
     NfcMagicApp* instance = context;
     NfcMagicApp* instance = context;
     bool consumed = false;
     bool consumed = false;
 
 
@@ -78,19 +76,19 @@ bool nfc_magic_scene_gen4_set_shd_mode_on_event(void* context, SceneManagerEvent
         if(event.event == NfcMagicCustomEventCardDetected) {
         if(event.event == NfcMagicCustomEventCardDetected) {
             scene_manager_set_scene_state(
             scene_manager_set_scene_state(
                 instance->scene_manager,
                 instance->scene_manager,
-                NfcMagicSceneGen4SetShdMode,
-                NfcMagicSceneGen4SetShadowModeStateCardFound);
-            nfc_magic_scene_gen4_set_shd_mode_setup_view(instance);
+                NfcMagicSceneGen4GetCFG,
+                NfcMagicSceneGen4GetCFGStateCardFound);
+            nfc_magic_scene_gen4_get_cfg_setup_view(instance);
             consumed = true;
             consumed = true;
         } else if(event.event == NfcMagicCustomEventCardLost) {
         } else if(event.event == NfcMagicCustomEventCardLost) {
             scene_manager_set_scene_state(
             scene_manager_set_scene_state(
                 instance->scene_manager,
                 instance->scene_manager,
-                NfcMagicSceneGen4SetShdMode,
-                NfcMagicSceneGen4SetShadowModeStateCardSearch);
-            nfc_magic_scene_gen4_set_shd_mode_setup_view(instance);
+                NfcMagicSceneGen4GetCFG,
+                NfcMagicSceneGen4GetCFGStateCardSearch);
+            nfc_magic_scene_gen4_get_cfg_setup_view(instance);
             consumed = true;
             consumed = true;
         } else if(event.event == NfcMagicCustomEventWorkerSuccess) {
         } else if(event.event == NfcMagicCustomEventWorkerSuccess) {
-            scene_manager_next_scene(instance->scene_manager, NfcMagicSceneSuccess);
+            scene_manager_next_scene(instance->scene_manager, NfcMagicSceneGen4ShowCFG);
             consumed = true;
             consumed = true;
         } else if(event.event == NfcMagicCustomEventWorkerFail) {
         } else if(event.event == NfcMagicCustomEventWorkerFail) {
             scene_manager_next_scene(instance->scene_manager, NfcMagicSceneGen4Fail);
             scene_manager_next_scene(instance->scene_manager, NfcMagicSceneGen4Fail);
@@ -101,15 +99,13 @@ bool nfc_magic_scene_gen4_set_shd_mode_on_event(void* context, SceneManagerEvent
     return consumed;
     return consumed;
 }
 }
 
 
-void nfc_magic_scene_gen4_set_shd_mode_on_exit(void* context) {
+void nfc_magic_scene_gen4_get_cfg_on_exit(void* context) {
     NfcMagicApp* instance = context;
     NfcMagicApp* instance = context;
 
 
     gen4_poller_stop(instance->gen4_poller);
     gen4_poller_stop(instance->gen4_poller);
     gen4_poller_free(instance->gen4_poller);
     gen4_poller_free(instance->gen4_poller);
     scene_manager_set_scene_state(
     scene_manager_set_scene_state(
-        instance->scene_manager,
-        NfcMagicSceneGen4SetShdMode,
-        NfcMagicSceneGen4SetShadowModeStateCardSearch);
+        instance->scene_manager, NfcMagicSceneGen4GetCFG, NfcMagicSceneGen4GetCFGStateCardSearch);
     // Clear view
     // Clear view
     popup_reset(instance->popup);
     popup_reset(instance->popup);
 
 

+ 1 - 50
scenes/nfc_magic_scene_gen4_menu.c

@@ -1,13 +1,8 @@
 #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,
     SubmenuIndexChangePassword,
     SubmenuIndexChangePassword,
-    SubmenuIndexSetShadowMode,
-    SubmenuIndexSetDirectWriteBlock0Mode,
-    SubmenuIndexInfo,
     SubmenuIndexWipe,
     SubmenuIndexWipe,
 };
 };
 
 
@@ -29,41 +24,8 @@ void nfc_magic_scene_gen4_menu_on_enter(void* context) {
         SubmenuIndexChangePassword,
         SubmenuIndexChangePassword,
         nfc_magic_scene_gen4_menu_submenu_callback,
         nfc_magic_scene_gen4_menu_submenu_callback,
         instance);
         instance);
-    submenu_add_item(
-        submenu,
-        "Set Shadow Mode",
-        SubmenuIndexSetShadowMode,
-        nfc_magic_scene_gen4_menu_submenu_callback,
-        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_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, "Info", SubmenuIndexInfo, nfc_magic_scene_gen4_menu_submenu_callback, instance);
 
 
     submenu_set_selected_item(
     submenu_set_selected_item(
         submenu, scene_manager_get_scene_state(instance->scene_manager, NfcMagicSceneGen4Menu));
         submenu, scene_manager_get_scene_state(instance->scene_manager, NfcMagicSceneGen4Menu));
@@ -84,21 +46,10 @@ bool nfc_magic_scene_gen4_menu_on_event(void* context, SceneManagerEvent event)
         } else if(event.event == SubmenuIndexWipe) {
         } else if(event.event == SubmenuIndexWipe) {
             scene_manager_next_scene(instance->scene_manager, NfcMagicSceneWipe);
             scene_manager_next_scene(instance->scene_manager, NfcMagicSceneWipe);
             consumed = true;
             consumed = true;
-        } else if(event.event == SubmenuIndexInfo) {
-            scene_manager_next_scene(instance->scene_manager, NfcMagicSceneGen4GetInfo);
-            consumed = true;
-        } else if(event.event == SubmenuIndexSetShadowMode) {
-            scene_manager_next_scene(instance->scene_manager, NfcMagicSceneGen4SelectShdMode);
-            consumed = true;
-        } else if(event.event == SubmenuIndexSetDirectWriteBlock0Mode) {
-            scene_manager_next_scene(
-                instance->scene_manager, NfcMagicSceneGen4SetDirectWriteBlock0Mode);
-            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 {

+ 28 - 32
scenes/nfc_magic_scene_gen4_get_info.c → scenes/nfc_magic_scene_gen4_revision.c

@@ -1,14 +1,11 @@
 #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,
-    NfcMagicSceneGen4GetInfoStateCardFound,
+    NfcMagicSceneGen4RevisionStateCardSearch,
+    NfcMagicSceneGen4RevisionStateCardFound,
 };
 };
 
 
-NfcCommand nfc_mafic_scene_gen4_get_info_poller_callback(Gen4PollerEvent event, void* context) {
+NfcCommand nfc_mafic_scene_gen4_revision_poller_callback(Gen4PollerEvent event, void* context) {
     NfcMagicApp* instance = context;
     NfcMagicApp* instance = context;
     furi_assert(event.data);
     furi_assert(event.data);
 
 
@@ -18,11 +15,13 @@ NfcCommand nfc_mafic_scene_gen4_get_info_poller_callback(Gen4PollerEvent event,
         view_dispatcher_send_custom_event(
         view_dispatcher_send_custom_event(
             instance->view_dispatcher, NfcMagicCustomEventCardDetected);
             instance->view_dispatcher, NfcMagicCustomEventCardDetected);
     } else if(event.type == Gen4PollerEventTypeRequestMode) {
     } else if(event.type == Gen4PollerEventTypeRequestMode) {
-        event.data->request_mode.mode = Gen4PollerModeGetInfo;
+        event.data->request_mode.mode = Gen4PollerModeGetRevision;
     } else if(event.type == Gen4PollerEventTypeSuccess) {
     } else if(event.type == Gen4PollerEventTypeSuccess) {
-        // Copy data from event to main instance
-        // TODO: From event data? or from poller?
-        gen4_copy(instance->gen4_data, instance->gen4_poller->gen4_data);
+        // Copy revision from event to main instance to display it on success scene
+        memcpy(
+            instance->gen4_revision_display,
+            event.data->revision_data,
+            sizeof(event.data->revision_data));
 
 
         view_dispatcher_send_custom_event(
         view_dispatcher_send_custom_event(
             instance->view_dispatcher, NfcMagicCustomEventWorkerSuccess);
             instance->view_dispatcher, NfcMagicCustomEventWorkerSuccess);
@@ -36,13 +35,13 @@ NfcCommand nfc_mafic_scene_gen4_get_info_poller_callback(Gen4PollerEvent event,
     return command;
     return command;
 }
 }
 
 
-static void nfc_magic_scene_gen4_get_info_setup_view(NfcMagicApp* instance) {
+static void nfc_magic_scene_gen4_revision_setup_view(NfcMagicApp* instance) {
     Popup* popup = instance->popup;
     Popup* popup = instance->popup;
     popup_reset(popup);
     popup_reset(popup);
     uint32_t state =
     uint32_t state =
-        scene_manager_get_scene_state(instance->scene_manager, NfcMagicSceneGen4GetInfo);
+        scene_manager_get_scene_state(instance->scene_manager, NfcMagicSceneGen4Revision);
 
 
-    if(state == NfcMagicSceneGen4GetInfoStateCardSearch) {
+    if(state == NfcMagicSceneGen4RevisionStateCardSearch) {
         popup_set_icon(instance->popup, 0, 8, &I_NFC_manual_60x50);
         popup_set_icon(instance->popup, 0, 8, &I_NFC_manual_60x50);
         popup_set_text(
         popup_set_text(
             instance->popup, "Apply the\ncard\nto the back", 128, 32, AlignRight, AlignCenter);
             instance->popup, "Apply the\ncard\nto the back", 128, 32, AlignRight, AlignCenter);
@@ -54,24 +53,24 @@ static void nfc_magic_scene_gen4_get_info_setup_view(NfcMagicApp* instance) {
     view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewPopup);
     view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewPopup);
 }
 }
 
 
-void nfc_magic_scene_gen4_get_info_on_enter(void* context) {
+void nfc_magic_scene_gen4_revision_on_enter(void* context) {
     NfcMagicApp* instance = context;
     NfcMagicApp* instance = context;
 
 
     scene_manager_set_scene_state(
     scene_manager_set_scene_state(
         instance->scene_manager,
         instance->scene_manager,
-        NfcMagicSceneGen4GetInfo,
-        NfcMagicSceneGen4GetInfoStateCardSearch);
-    nfc_magic_scene_gen4_get_info_setup_view(instance);
+        NfcMagicSceneGen4Revision,
+        NfcMagicSceneGen4RevisionStateCardSearch);
+    nfc_magic_scene_gen4_revision_setup_view(instance);
 
 
     nfc_magic_app_blink_start(instance);
     nfc_magic_app_blink_start(instance);
 
 
     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_gen4_get_info_poller_callback, instance);
+        instance->gen4_poller, nfc_mafic_scene_gen4_revision_poller_callback, instance);
 }
 }
 
 
-bool nfc_magic_scene_gen4_get_info_on_event(void* context, SceneManagerEvent event) {
+bool nfc_magic_scene_gen4_revision_on_event(void* context, SceneManagerEvent event) {
     NfcMagicApp* instance = context;
     NfcMagicApp* instance = context;
     bool consumed = false;
     bool consumed = false;
 
 
@@ -79,22 +78,19 @@ bool nfc_magic_scene_gen4_get_info_on_event(void* context, SceneManagerEvent eve
         if(event.event == NfcMagicCustomEventCardDetected) {
         if(event.event == NfcMagicCustomEventCardDetected) {
             scene_manager_set_scene_state(
             scene_manager_set_scene_state(
                 instance->scene_manager,
                 instance->scene_manager,
-                NfcMagicSceneGen4GetInfo,
-                NfcMagicSceneGen4GetInfoStateCardFound);
-            nfc_magic_scene_gen4_get_info_setup_view(instance);
+                NfcMagicSceneGen4Revision,
+                NfcMagicSceneGen4RevisionStateCardFound);
+            nfc_magic_scene_gen4_revision_setup_view(instance);
             consumed = true;
             consumed = true;
         } else if(event.event == NfcMagicCustomEventCardLost) {
         } else if(event.event == NfcMagicCustomEventCardLost) {
             scene_manager_set_scene_state(
             scene_manager_set_scene_state(
                 instance->scene_manager,
                 instance->scene_manager,
-                NfcMagicSceneGen4GetInfo,
-                NfcMagicSceneGen4GetInfoStateCardSearch);
-            nfc_magic_scene_gen4_get_info_setup_view(instance);
+                NfcMagicSceneGen4Revision,
+                NfcMagicSceneGen4RevisionStateCardSearch);
+            nfc_magic_scene_gen4_revision_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, NfcMagicSceneGen4ShowRev);
             consumed = true;
             consumed = true;
         } else if(event.event == NfcMagicCustomEventWorkerFail) {
         } else if(event.event == NfcMagicCustomEventWorkerFail) {
             scene_manager_next_scene(instance->scene_manager, NfcMagicSceneGen4Fail);
             scene_manager_next_scene(instance->scene_manager, NfcMagicSceneGen4Fail);
@@ -105,15 +101,15 @@ bool nfc_magic_scene_gen4_get_info_on_event(void* context, SceneManagerEvent eve
     return consumed;
     return consumed;
 }
 }
 
 
-void nfc_magic_scene_gen4_get_info_on_exit(void* context) {
+void nfc_magic_scene_gen4_revision_on_exit(void* context) {
     NfcMagicApp* instance = context;
     NfcMagicApp* instance = context;
 
 
     gen4_poller_stop(instance->gen4_poller);
     gen4_poller_stop(instance->gen4_poller);
     gen4_poller_free(instance->gen4_poller);
     gen4_poller_free(instance->gen4_poller);
     scene_manager_set_scene_state(
     scene_manager_set_scene_state(
         instance->scene_manager,
         instance->scene_manager,
-        NfcMagicSceneGen4GetInfo,
-        NfcMagicSceneGen4GetInfoStateCardSearch);
+        NfcMagicSceneGen4Revision,
+        NfcMagicSceneGen4RevisionStateCardSearch);
     // Clear view
     // Clear view
     popup_reset(instance->popup);
     popup_reset(instance->popup);
 
 

+ 0 - 106
scenes/nfc_magic_scene_gen4_select_shd_mode.c

@@ -1,106 +0,0 @@
-#include "../nfc_magic_app_i.h"
-#include "protocols/gen4/gen4_poller_i.h"
-
-enum SubmenuIndex {
-    SubmenuIndexPreWriteMode,
-    SubmenuIndexRestoreMode,
-    SubmenuIndexDisable,
-    SubmenuIndexDisableHighSpeed,
-    SubmenuIndexSplitMode,
-};
-
-void nfc_magic_scene_gen4_select_shd_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_shd_mode_on_enter(void* context) {
-    NfcMagicApp* instance = context;
-
-    Submenu* submenu = instance->submenu;
-    submenu_add_item(
-        submenu,
-        "Pre-Write",
-        SubmenuIndexPreWriteMode,
-        nfc_magic_scene_gen4_select_shd_mode_submenu_callback,
-        instance);
-    submenu_add_item(
-        submenu,
-        "Restore",
-        SubmenuIndexRestoreMode,
-        nfc_magic_scene_gen4_select_shd_mode_submenu_callback,
-        instance);
-    submenu_add_item(
-        submenu,
-        "Disable",
-        SubmenuIndexDisable,
-        nfc_magic_scene_gen4_select_shd_mode_submenu_callback,
-        instance);
-    submenu_add_item(
-        submenu,
-        "Disable (High-Speed)",
-        SubmenuIndexDisableHighSpeed,
-        nfc_magic_scene_gen4_select_shd_mode_submenu_callback,
-        instance);
-    submenu_add_item(
-        submenu,
-        "Split",
-        SubmenuIndexSplitMode,
-        nfc_magic_scene_gen4_select_shd_mode_submenu_callback,
-        instance);
-
-    submenu_set_selected_item(
-        submenu,
-        scene_manager_get_scene_state(instance->scene_manager, NfcMagicSceneGen4SelectShdMode));
-    view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewMenu);
-}
-
-bool nfc_magic_scene_gen4_select_shd_mode_on_event(void* context, SceneManagerEvent event) {
-    NfcMagicApp* instance = context;
-    bool consumed = false;
-
-    if(event.type == SceneManagerEventTypeCustom) {
-        if(event.event == SubmenuIndexPreWriteMode) {
-            scene_manager_set_scene_state(
-                instance->scene_manager, NfcMagicSceneGen4SetShdMode, Gen4ShadowModePreWrite);
-            scene_manager_next_scene(instance->scene_manager, NfcMagicSceneGen4SetShdMode);
-            consumed = true;
-        } else if(event.event == SubmenuIndexRestoreMode) {
-            scene_manager_set_scene_state(
-                instance->scene_manager, NfcMagicSceneGen4SetShdMode, Gen4ShadowModeRestore);
-            scene_manager_next_scene(instance->scene_manager, NfcMagicSceneGen4SetShdMode);
-            consumed = true;
-        } else if(event.event == SubmenuIndexDisable) {
-            scene_manager_set_scene_state(
-                instance->scene_manager, NfcMagicSceneGen4SetShdMode, Gen4ShadowModeDisabled);
-            scene_manager_next_scene(instance->scene_manager, NfcMagicSceneGen4SetShdMode);
-            consumed = true;
-        } else if(event.event == SubmenuIndexDisableHighSpeed) {
-            scene_manager_set_scene_state(
-                instance->scene_manager,
-                NfcMagicSceneGen4SetShdMode,
-                Gen4ShadowModeHighSpeedDisabled);
-            scene_manager_next_scene(instance->scene_manager, NfcMagicSceneGen4SetShdMode);
-            consumed = true;
-        } else if(event.event == SubmenuIndexSplitMode) {
-            scene_manager_set_scene_state(
-                instance->scene_manager, NfcMagicSceneGen4SetShdMode, Gen4ShadowModeSplit);
-            scene_manager_next_scene(instance->scene_manager, NfcMagicSceneGen4SetShdMode);
-            consumed = true;
-        }
-        scene_manager_set_scene_state(
-            instance->scene_manager, NfcMagicSceneGen4SelectShdMode, 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_shd_mode_on_exit(void* context) {
-    NfcMagicApp* instance = context;
-
-    submenu_reset(instance->submenu);
-}

+ 23 - 24
scenes/nfc_magic_scene_gen4_set_default_cfg.c → scenes/nfc_magic_scene_gen4_set_cfg.c

@@ -1,12 +1,11 @@
 #include "../nfc_magic_app_i.h"
 #include "../nfc_magic_app_i.h"
 
 
 enum {
 enum {
-    NfcMagicSceneGen4SetDefCfgStateCardSearch,
-    NfcMagicSceneGen4SetDefCfgStateCardFound,
+    NfcMagicSceneGen4SetDefCFGStateCardSearch,
+    NfcMagicSceneGen4SetDefCFGStateCardFound,
 };
 };
 
 
-NfcCommand
-    nfc_mafic_scene_gen4_set_default_cfg_poller_callback(Gen4PollerEvent event, void* context) {
+NfcCommand nfc_mafic_scene_gen4_set_cfg_poller_callback(Gen4PollerEvent event, void* context) {
     NfcMagicApp* instance = context;
     NfcMagicApp* instance = context;
     furi_assert(event.data);
     furi_assert(event.data);
 
 
@@ -16,7 +15,7 @@ NfcCommand
         view_dispatcher_send_custom_event(
         view_dispatcher_send_custom_event(
             instance->view_dispatcher, NfcMagicCustomEventCardDetected);
             instance->view_dispatcher, NfcMagicCustomEventCardDetected);
     } else if(event.type == Gen4PollerEventTypeRequestMode) {
     } else if(event.type == Gen4PollerEventTypeRequestMode) {
-        event.data->request_mode.mode = Gen4PollerModeSetDefaultCfg;
+        event.data->request_mode.mode = Gen4PollerModeSetDefaultCFG;
     } else if(event.type == Gen4PollerEventTypeSuccess) {
     } else if(event.type == Gen4PollerEventTypeSuccess) {
         view_dispatcher_send_custom_event(
         view_dispatcher_send_custom_event(
             instance->view_dispatcher, NfcMagicCustomEventWorkerSuccess);
             instance->view_dispatcher, NfcMagicCustomEventWorkerSuccess);
@@ -30,42 +29,42 @@ NfcCommand
     return command;
     return command;
 }
 }
 
 
-static void nfc_magic_scene_gen4_set_default_cfg_setup_view(NfcMagicApp* instance) {
+static void nfc_magic_scene_gen4_set_cfg_setup_view(NfcMagicApp* instance) {
     Popup* popup = instance->popup;
     Popup* popup = instance->popup;
     popup_reset(popup);
     popup_reset(popup);
     uint32_t state =
     uint32_t state =
-        scene_manager_get_scene_state(instance->scene_manager, NfcMagicSceneGen4SetDefaultCfg);
+        scene_manager_get_scene_state(instance->scene_manager, NfcMagicSceneGen4SetCFG);
 
 
-    if(state == NfcMagicSceneGen4SetDefCfgStateCardSearch) {
+    if(state == NfcMagicSceneGen4SetDefCFGStateCardSearch) {
         popup_set_icon(instance->popup, 0, 8, &I_NFC_manual_60x50);
         popup_set_icon(instance->popup, 0, 8, &I_NFC_manual_60x50);
         popup_set_text(
         popup_set_text(
             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);
 }
 }
 
 
-void nfc_magic_scene_gen4_set_default_cfg_on_enter(void* context) {
+void nfc_magic_scene_gen4_set_cfg_on_enter(void* context) {
     NfcMagicApp* instance = context;
     NfcMagicApp* instance = context;
 
 
     scene_manager_set_scene_state(
     scene_manager_set_scene_state(
         instance->scene_manager,
         instance->scene_manager,
-        NfcMagicSceneGen4SetDefaultCfg,
-        NfcMagicSceneGen4SetDefCfgStateCardSearch);
-    nfc_magic_scene_gen4_set_default_cfg_setup_view(instance);
+        NfcMagicSceneGen4SetCFG,
+        NfcMagicSceneGen4SetDefCFGStateCardSearch);
+    nfc_magic_scene_gen4_set_cfg_setup_view(instance);
 
 
     nfc_magic_app_blink_start(instance);
     nfc_magic_app_blink_start(instance);
 
 
     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_gen4_set_default_cfg_poller_callback, instance);
+        instance->gen4_poller, nfc_mafic_scene_gen4_set_cfg_poller_callback, instance);
 }
 }
 
 
-bool nfc_magic_scene_gen4_set_default_cfg_on_event(void* context, SceneManagerEvent event) {
+bool nfc_magic_scene_gen4_set_cfg_on_event(void* context, SceneManagerEvent event) {
     NfcMagicApp* instance = context;
     NfcMagicApp* instance = context;
     bool consumed = false;
     bool consumed = false;
 
 
@@ -73,16 +72,16 @@ bool nfc_magic_scene_gen4_set_default_cfg_on_event(void* context, SceneManagerEv
         if(event.event == NfcMagicCustomEventCardDetected) {
         if(event.event == NfcMagicCustomEventCardDetected) {
             scene_manager_set_scene_state(
             scene_manager_set_scene_state(
                 instance->scene_manager,
                 instance->scene_manager,
-                NfcMagicSceneGen4SetDefaultCfg,
-                NfcMagicSceneGen4SetDefCfgStateCardFound);
-            nfc_magic_scene_gen4_set_default_cfg_setup_view(instance);
+                NfcMagicSceneGen4SetCFG,
+                NfcMagicSceneGen4SetDefCFGStateCardFound);
+            nfc_magic_scene_gen4_set_cfg_setup_view(instance);
             consumed = true;
             consumed = true;
         } else if(event.event == NfcMagicCustomEventCardLost) {
         } else if(event.event == NfcMagicCustomEventCardLost) {
             scene_manager_set_scene_state(
             scene_manager_set_scene_state(
                 instance->scene_manager,
                 instance->scene_manager,
-                NfcMagicSceneGen4SetDefaultCfg,
-                NfcMagicSceneGen4SetDefCfgStateCardSearch);
-            nfc_magic_scene_gen4_set_default_cfg_setup_view(instance);
+                NfcMagicSceneGen4SetCFG,
+                NfcMagicSceneGen4SetDefCFGStateCardSearch);
+            nfc_magic_scene_gen4_set_cfg_setup_view(instance);
             consumed = true;
             consumed = true;
         } else if(event.event == NfcMagicCustomEventWorkerSuccess) {
         } else if(event.event == NfcMagicCustomEventWorkerSuccess) {
             scene_manager_next_scene(instance->scene_manager, NfcMagicSceneSuccess);
             scene_manager_next_scene(instance->scene_manager, NfcMagicSceneSuccess);
@@ -96,15 +95,15 @@ bool nfc_magic_scene_gen4_set_default_cfg_on_event(void* context, SceneManagerEv
     return consumed;
     return consumed;
 }
 }
 
 
-void nfc_magic_scene_gen4_set_default_cfg_on_exit(void* context) {
+void nfc_magic_scene_gen4_set_cfg_on_exit(void* context) {
     NfcMagicApp* instance = context;
     NfcMagicApp* instance = context;
 
 
     gen4_poller_stop(instance->gen4_poller);
     gen4_poller_stop(instance->gen4_poller);
     gen4_poller_free(instance->gen4_poller);
     gen4_poller_free(instance->gen4_poller);
     scene_manager_set_scene_state(
     scene_manager_set_scene_state(
         instance->scene_manager,
         instance->scene_manager,
-        NfcMagicSceneGen4SetDefaultCfg,
-        NfcMagicSceneGen4SetDefCfgStateCardSearch);
+        NfcMagicSceneGen4SetCFG,
+        NfcMagicSceneGen4SetDefCFGStateCardSearch);
     // Clear view
     // Clear view
     popup_reset(instance->popup);
     popup_reset(instance->popup);
 
 

+ 0 - 123
scenes/nfc_magic_scene_gen4_set_direct_write_block_0_mode.c

@@ -1,123 +0,0 @@
-#include "../nfc_magic_app_i.h"
-#include "../lib/magic/protocols/gen4/gen4_poller_i.h"
-#include "gui/scene_manager.h"
-
-enum {
-    NfcMagicSceneGen4SetDirectWriteBlock0ModeStateCardSearch,
-    NfcMagicSceneGen4SetDirectWriteBlock0ModeStateCardFound,
-};
-
-NfcCommand nfc_magic_scene_gen4_set_direct_write_block_0_mode_poller_callback(
-    Gen4PollerEvent event,
-    void* context) {
-    NfcMagicApp* instance = context;
-    furi_assert(event.data);
-
-    NfcCommand command = NfcCommandContinue;
-    if(event.type == Gen4PollerEventTypeCardDetected) {
-        view_dispatcher_send_custom_event(
-            instance->view_dispatcher, NfcMagicCustomEventCardDetected);
-    } else if(event.type == Gen4PollerEventTypeRequestMode) {
-        event.data->request_mode.mode = Gen4PollerModeSetDirectWriteBlock0Mode;
-    } else if(event.type == Gen4PollerEventTypeSuccess) {
-        view_dispatcher_send_custom_event(
-            instance->view_dispatcher, NfcMagicCustomEventWorkerSuccess);
-        command = NfcCommandStop;
-    } else if(event.type == Gen4PollerEventTypeFail) {
-        view_dispatcher_send_custom_event(
-            instance->view_dispatcher, NfcMagicCustomEventWorkerFail);
-        command = NfcCommandStop;
-    }
-
-    return command;
-}
-
-static void nfc_magic_scene_gen4_set_direct_write_block_0_mode_setup_view(NfcMagicApp* instance) {
-    Popup* popup = instance->popup;
-    popup_reset(popup);
-    uint32_t state = scene_manager_get_scene_state(
-        instance->scene_manager, NfcMagicSceneGen4SetDirectWriteBlock0Mode);
-
-    if(state == NfcMagicSceneGen4SetDirectWriteBlock0ModeStateCardSearch) {
-        popup_set_icon(instance->popup, 0, 8, &I_NFC_manual_60x50);
-        popup_set_text(
-            instance->popup, "Apply the\ncard\nto the back", 128, 32, AlignRight, AlignCenter);
-    } else {
-        popup_set_icon(popup, 12, 23, &I_Loading_24);
-        popup_set_header(popup, "Configuring\nDon't move...", 52, 32, AlignLeft, AlignCenter);
-    }
-
-    view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewPopup);
-}
-
-void nfc_magic_scene_gen4_set_direct_write_block_0_mode_on_enter(void* context) {
-    NfcMagicApp* instance = context;
-
-    uint8_t direct_write_block_0_mode = scene_manager_get_scene_state(
-        instance->scene_manager, NfcMagicSceneGen4SetDirectWriteBlock0Mode);
-
-    scene_manager_set_scene_state(
-        instance->scene_manager,
-        NfcMagicSceneGen4SetDirectWriteBlock0Mode,
-        NfcMagicSceneGen4SetDirectWriteBlock0ModeStateCardSearch);
-    nfc_magic_scene_gen4_set_direct_write_block_0_mode_setup_view(instance);
-
-    nfc_magic_app_blink_start(instance);
-
-    instance->gen4_poller = gen4_poller_alloc(instance->nfc);
-    gen4_poller_set_password(instance->gen4_poller, instance->gen4_password);
-    instance->gen4_poller->direct_write_block_0_mode = direct_write_block_0_mode;
-
-    gen4_poller_start(
-        instance->gen4_poller,
-        nfc_magic_scene_gen4_set_direct_write_block_0_mode_poller_callback,
-        instance);
-}
-
-bool nfc_magic_scene_gen4_set_direct_write_block_0_mode_on_event(
-    void* context,
-    SceneManagerEvent event) {
-    NfcMagicApp* instance = context;
-    bool consumed = false;
-
-    if(event.type == SceneManagerEventTypeCustom) {
-        if(event.event == NfcMagicCustomEventCardDetected) {
-            scene_manager_set_scene_state(
-                instance->scene_manager,
-                NfcMagicSceneGen4SetDirectWriteBlock0Mode,
-                NfcMagicSceneGen4SetDirectWriteBlock0ModeStateCardFound);
-            nfc_magic_scene_gen4_set_direct_write_block_0_mode_setup_view(instance);
-            consumed = true;
-        } else if(event.event == NfcMagicCustomEventCardLost) {
-            scene_manager_set_scene_state(
-                instance->scene_manager,
-                NfcMagicSceneGen4SetDirectWriteBlock0Mode,
-                NfcMagicSceneGen4SetDirectWriteBlock0ModeStateCardSearch);
-            nfc_magic_scene_gen4_set_direct_write_block_0_mode_setup_view(instance);
-            consumed = true;
-        } else if(event.event == NfcMagicCustomEventWorkerSuccess) {
-            scene_manager_next_scene(instance->scene_manager, NfcMagicSceneSuccess);
-            consumed = true;
-        } else if(event.event == NfcMagicCustomEventWorkerFail) {
-            scene_manager_next_scene(instance->scene_manager, NfcMagicSceneGen4Fail);
-            consumed = true;
-        }
-    }
-
-    return consumed;
-}
-
-void nfc_magic_scene_gen4_set_direct_write_block_0_mode_on_exit(void* context) {
-    NfcMagicApp* instance = context;
-
-    gen4_poller_stop(instance->gen4_poller);
-    gen4_poller_free(instance->gen4_poller);
-    scene_manager_set_scene_state(
-        instance->scene_manager,
-        NfcMagicSceneGen4SetDirectWriteBlock0Mode,
-        NfcMagicSceneGen4SetDirectWriteBlock0ModeStateCardSearch);
-    // Clear view
-    popup_reset(instance->popup);
-
-    nfc_magic_app_blink_stop(instance);
-}

+ 20 - 13
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,
@@ -17,20 +17,25 @@ void nfc_magic_scene_gen4_show_cfg_on_enter(void* context) {
     NfcMagicApp* instance = context;
     NfcMagicApp* instance = context;
     Widget* widget = instance->widget;
     Widget* widget = instance->widget;
 
 
-    FuriString* output = furi_string_alloc();
-
-    Gen4Config* config = &instance->gen4_data->config;
+    notification_message(instance->notifications, &sequence_success);
 
 
-    for(size_t i = 0; i < GEN4_CONFIG_SIZE; i += 2) {
-        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]);
+    FuriString* temp_config = furi_string_alloc();
+    for(size_t i = 0; i < CONFIG_SIZE; i++) {
+        if((i != 0) && (i % 8 == 0)) {
+            furi_string_cat_printf(temp_config, "\n");
+        }
+        furi_string_cat_printf(temp_config, "%02X ", instance->gen4_config_display[i]);
     }
     }
+    furi_string_cat_printf(temp_config, "\n");
 
 
     widget_add_string_element(widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "Gen4 Config");
     widget_add_string_element(widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "Gen4 Config");
 
 
-    widget_add_text_scroll_element(widget, 3, 17, 124, 50, furi_string_get_cstr(output));
+    widget_add_text_scroll_element(widget, 3, 17, 124, 50, furi_string_get_cstr(temp_config));
+
+    widget_add_button_element(
+        widget, GuiButtonTypeLeft, "Exit", nfc_magic_scene_gen4_show_cfg_widget_callback, instance);
 
 
-    furi_string_free(output);
+    furi_string_free(temp_config);
     view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewWidget);
     view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewWidget);
 }
 }
 
 
@@ -38,9 +43,11 @@ bool nfc_magic_scene_gen4_show_cfg_on_event(void* context, SceneManagerEvent eve
     NfcMagicApp* instance = context;
     NfcMagicApp* instance = context;
     bool consumed = false;
     bool consumed = false;
 
 
-    if(event.type == SceneManagerEventTypeBack) {
-        consumed = scene_manager_search_and_switch_to_previous_scene(
-            instance->scene_manager, NfcMagicSceneGen4ShowInfo);
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == GuiButtonTypeLeft) {
+            consumed = scene_manager_search_and_switch_to_previous_scene(
+                instance->scene_manager, NfcMagicSceneGen4ActionsMenu);
+        }
     }
     }
     return consumed;
     return consumed;
 }
 }

+ 0 - 127
scenes/nfc_magic_scene_gen4_show_info.c

@@ -1,127 +0,0 @@
-#include "../nfc_magic_app_i.h"
-#include "core/string.h"
-#include "gui/modules/widget_elements/widget_element.h"
-#include "gui/scene_manager.h"
-
-#include <bit_lib.h>
-#include <stdbool.h>
-
-void nfc_magic_scene_gen4_show_info_widget_callback(
-    GuiButtonType result,
-    InputType type,
-    void* context) {
-    NfcMagicApp* instance = context;
-
-    if(type == InputTypeShort) {
-        view_dispatcher_send_custom_event(instance->view_dispatcher, result);
-    }
-}
-
-void nfc_magic_scene_gen4_show_info_on_enter(void* context) {
-    NfcMagicApp* instance = context;
-    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;
-
-    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;
-
-    FuriString* output = furi_string_alloc();
-
-    // Revision
-    furi_string_cat_printf(
-        output, "Revision: %02X %02X\n", gen4->revision.data[3], gen4->revision.data[4]);
-
-    // Password
-    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]);
-
-    // Shadow mode
-    furi_string_cat_printf(output, "Shadow Mode: %s\n", gen4_get_shadow_mode_name(s_mode));
-
-    // Direct write to block 0 mode
-    furi_string_cat_printf(
-        output, "Direct Write Mode: %s\n", gen4_get_direct_write_mode_name(dw_mode));
-
-    /////////////////////////////////////////////////////////////////////////////////////////////////////
-    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
-    furi_string_cat_printf(
-        output, "UID Length: %s\n", gen4_get_uid_len_num(gen4->config.data_parsed.uid_len_code));
-
-    // ATS
-    furi_string_cat_printf(output, "ATS:");
-    if(ats_len)
-        for(uint8_t i = 0; i < ats_len; i++)
-            furi_string_cat_printf(output, " %02X", gen4->config.data_parsed.ats[i]);
-    else
-        furi_string_cat_printf(output, " No");
-    furi_string_cat_printf(output, "\n");
-
-    // ATQA
-    furi_string_cat_printf(
-        output,
-        "ATQA: %02X %02X\n",
-        gen4->config.data_parsed.atqa[0],
-        gen4->config.data_parsed.atqa[1]);
-
-    // SAK
-    furi_string_cat_printf(output, "SAK: %02X\n", gen4->config.data_parsed.sak);
-
-    // Blocks
-    furi_string_cat_printf(
-        output,
-        "Total blocks: %u",
-        gen4->config.data_parsed.total_blocks + 1); // config stores the number of the last block
-
-    widget_add_string_element(widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "Gen4 Info");
-
-    widget_add_text_scroll_element(widget, 3, 17, 124, 50, furi_string_get_cstr(output));
-
-    widget_add_button_element(
-        widget,
-        GuiButtonTypeRight,
-        "More",
-        nfc_magic_scene_gen4_show_info_widget_callback,
-        instance);
-
-    furi_string_free(output);
-    view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewWidget);
-}
-
-bool nfc_magic_scene_gen4_show_info_on_event(void* context, SceneManagerEvent event) {
-    NfcMagicApp* instance = context;
-    bool consumed = false;
-
-    if(event.type == SceneManagerEventTypeCustom) {
-        if(event.event == GuiButtonTypeRight) {
-            scene_manager_next_scene(instance->scene_manager, NfcMagicSceneGen4ShowCfg);
-            consumed = true;
-        }
-    } else if(event.type == SceneManagerEventTypeBack) {
-        consumed = scene_manager_search_and_switch_to_previous_scene(
-            instance->scene_manager, NfcMagicSceneGen4Menu);
-    }
-    return consumed;
-}
-
-void nfc_magic_scene_gen4_show_info_on_exit(void* context) {
-    NfcMagicApp* instance = context;
-
-    widget_reset(instance->widget);
-}

+ 55 - 0
scenes/nfc_magic_scene_gen4_show_rev.c

@@ -0,0 +1,55 @@
+#include "../nfc_magic_app_i.h"
+
+#define REVISION_SIZE (5)
+
+void nfc_magic_scene_gen4_show_rev_widget_callback(
+    GuiButtonType result,
+    InputType type,
+    void* context) {
+    NfcMagicApp* instance = context;
+
+    if(type == InputTypeShort) {
+        view_dispatcher_send_custom_event(instance->view_dispatcher, result);
+    }
+}
+
+void nfc_magic_scene_gen4_show_rev_on_enter(void* context) {
+    NfcMagicApp* instance = context;
+    Widget* widget = instance->widget;
+
+    notification_message(instance->notifications, &sequence_success);
+
+    FuriString* temp_revision = furi_string_alloc();
+    for(size_t i = REVISION_SIZE - 2; i < REVISION_SIZE; i++) {
+        furi_string_cat_printf(temp_revision, "%02X ", instance->gen4_revision_display[i]);
+    }
+
+    widget_add_icon_element(widget, 73, 17, &I_DolphinCommon_56x48);
+    widget_add_string_element(widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "Gen4 Revision");
+    widget_add_string_multiline_element(
+        widget, 3, 17, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(temp_revision));
+    widget_add_button_element(
+        widget, GuiButtonTypeLeft, "Exit", nfc_magic_scene_gen4_show_rev_widget_callback, instance);
+
+    furi_string_free(temp_revision);
+    view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewWidget);
+}
+
+bool nfc_magic_scene_gen4_show_rev_on_event(void* context, SceneManagerEvent event) {
+    NfcMagicApp* instance = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == GuiButtonTypeLeft) {
+            consumed = scene_manager_search_and_switch_to_previous_scene(
+                instance->scene_manager, NfcMagicSceneGen4ActionsMenu);
+        }
+    }
+    return consumed;
+}
+
+void nfc_magic_scene_gen4_show_rev_on_exit(void* context) {
+    NfcMagicApp* instance = context;
+
+    widget_reset(instance->widget);
+}

+ 5 - 10
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;

+ 20 - 40
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,45 +17,27 @@ 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(
         widget, GuiButtonTypeRight, "More", nfc_magic_scene_magic_info_widget_callback, instance);
         widget, GuiButtonTypeRight, "More", nfc_magic_scene_magic_info_widget_callback, instance);
 
 
+    furi_string_free(message);
+
     view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewWidget);
     view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewWidget);
 }
 }
 
 
@@ -75,14 +52,17 @@ 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) {
-        consumed = scene_manager_search_and_switch_to_previous_scene(
-            instance->scene_manager, NfcMagicSceneStart);
     }
     }
     return consumed;
     return consumed;
 }
 }

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

+ 0 - 3
scenes/nfc_magic_scene_not_magic.c

@@ -38,9 +38,6 @@ bool nfc_magic_scene_not_magic_on_event(void* context, SceneManagerEvent event)
         if(event.event == GuiButtonTypeLeft) {
         if(event.event == GuiButtonTypeLeft) {
             consumed = scene_manager_previous_scene(instance->scene_manager);
             consumed = scene_manager_previous_scene(instance->scene_manager);
         }
         }
-    } else if(event.type == SceneManagerEventTypeBack) {
-        consumed = scene_manager_search_and_switch_to_previous_scene(
-            instance->scene_manager, NfcMagicSceneStart);
     }
     }
     return consumed;
     return consumed;
 }
 }

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

+ 0 - 3
scenes/nfc_magic_scene_wrong_card.c

@@ -42,9 +42,6 @@ bool nfc_magic_scene_wrong_card_on_event(void* context, SceneManagerEvent event)
         if(event.event == GuiButtonTypeLeft) {
         if(event.event == GuiButtonTypeLeft) {
             consumed = scene_manager_previous_scene(instance->scene_manager);
             consumed = scene_manager_previous_scene(instance->scene_manager);
         }
         }
-    } else if(event.type == SceneManagerEventTypeBack) {
-        consumed = scene_manager_search_and_switch_to_previous_scene(
-            instance->scene_manager, NfcMagicSceneStart);
     }
     }
     return consumed;
     return consumed;
 }
 }

+ 245 - 0
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
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
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
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);