ソースを参照

add reworked picopass

MX 2 年 前
コミット
8969c186cc

+ 1 - 1
application.fam

@@ -10,7 +10,7 @@ App(
     ],
     stack_size=4 * 1024,
     fap_description="App to communicate with NFC tags using the PicoPass(iClass) format",
-    fap_version="1.6",
+    fap_version="1.7",
     fap_icon="125_10px.png",
     fap_category="NFC",
     fap_libs=["mbedtls"],

+ 4 - 5
picopass.c

@@ -23,7 +23,6 @@ void picopass_tick_event_callback(void* context) {
 Picopass* picopass_alloc() {
     Picopass* picopass = malloc(sizeof(Picopass));
 
-    picopass->worker = picopass_worker_alloc();
     picopass->view_dispatcher = view_dispatcher_alloc();
     picopass->scene_manager = scene_manager_alloc(&picopass_scene_handlers, picopass);
     view_dispatcher_enable_queue(picopass->view_dispatcher);
@@ -35,6 +34,8 @@ Picopass* picopass_alloc() {
     view_dispatcher_set_tick_event_callback(
         picopass->view_dispatcher, picopass_tick_event_callback, 100);
 
+    picopass->nfc = nfc_alloc();
+
     // Picopass device
     picopass->dev = picopass_device_alloc();
 
@@ -100,6 +101,8 @@ void picopass_free(Picopass* picopass) {
     picopass_device_free(picopass->dev);
     picopass->dev = NULL;
 
+    nfc_free(picopass->nfc);
+
     // Submenu
     view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewMenu);
     submenu_free(picopass->submenu);
@@ -130,10 +133,6 @@ void picopass_free(Picopass* picopass) {
     view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewLoclass);
     loclass_free(picopass->loclass);
 
-    // Worker
-    picopass_worker_stop(picopass->worker);
-    picopass_worker_free(picopass->worker);
-
     // View Dispatcher
     view_dispatcher_free(picopass->view_dispatcher);
 

+ 17 - 40
picopass_device.c

@@ -43,7 +43,7 @@ static bool picopass_device_save_file_lfrfid(PicopassDevice* dev, FuriString* fi
     bool result = false;
     uint64_t target = 0;
     uint64_t sentinel = 1ULL << pacs->bitLength;
-    memcpy(&target, pacs->credential, RFAL_PICOPASS_BLOCK_LEN);
+    memcpy(&target, pacs->credential, PICOPASS_BLOCK_LEN);
     target = __builtin_bswap64(target);
     FURI_LOG_D(TAG, "Original (%d): %016llx", pacs->bitLength, target);
 
@@ -132,8 +132,7 @@ static bool picopass_device_save_file(
             // Write header
             if(!flipper_format_write_header_cstr(file, picopass_file_header, picopass_file_version))
                 break;
-            if(!flipper_format_write_hex(
-                   file, "Credential", pacs->credential, RFAL_PICOPASS_BLOCK_LEN))
+            if(!flipper_format_write_hex(file, "Credential", pacs->credential, PICOPASS_BLOCK_LEN))
                 break;
 
             // TODO: Add elite
@@ -146,10 +145,7 @@ static bool picopass_device_save_file(
             for(size_t i = 0; i < app_limit; i++) {
                 furi_string_printf(temp_str, "Block %d", i);
                 if(!flipper_format_write_hex(
-                       file,
-                       furi_string_get_cstr(temp_str),
-                       AA1[i].data,
-                       RFAL_PICOPASS_BLOCK_LEN)) {
+                       file, furi_string_get_cstr(temp_str), AA1[i].data, PICOPASS_BLOCK_LEN)) {
                     block_saved = false;
                     break;
                 }
@@ -210,7 +206,7 @@ static bool picopass_device_load_data(PicopassDevice* dev, FuriString* path, boo
         for(size_t i = 0; i < 6; i++) {
             furi_string_printf(temp_str, "Block %d", i);
             if(!flipper_format_read_hex(
-                   file, furi_string_get_cstr(temp_str), AA1[i].data, RFAL_PICOPASS_BLOCK_LEN)) {
+                   file, furi_string_get_cstr(temp_str), AA1[i].data, PICOPASS_BLOCK_LEN)) {
                 block_read = false;
                 break;
             }
@@ -222,15 +218,15 @@ static bool picopass_device_load_data(PicopassDevice* dev, FuriString* path, boo
         for(size_t i = 6; i < app_limit; i++) {
             furi_string_printf(temp_str, "Block %d", i);
             if(!flipper_format_read_hex(
-                   file, furi_string_get_cstr(temp_str), AA1[i].data, RFAL_PICOPASS_BLOCK_LEN)) {
+                   file, furi_string_get_cstr(temp_str), AA1[i].data, PICOPASS_BLOCK_LEN)) {
                 block_read = false;
                 break;
             }
         }
         if(!block_read) break;
 
-        if(picopass_device_parse_credential(AA1, pacs) != ERR_NONE) break;
-        if(picopass_device_parse_wiegand(pacs->credential, pacs) != ERR_NONE) break;
+        picopass_device_parse_credential(AA1, pacs);
+        picopass_device_parse_wiegand(pacs->credential, pacs);
 
         parsed = true;
     } while(false);
@@ -347,7 +343,7 @@ void picopass_device_set_loading_callback(
     dev->loading_cb_ctx = context;
 }
 
-ReturnCode picopass_device_decrypt(uint8_t* enc_data, uint8_t* dec_data) {
+void picopass_device_decrypt(uint8_t* enc_data, uint8_t* dec_data) {
     uint8_t key[32] = {0};
     memcpy(key, picopass_iclass_decryptionkey, sizeof(picopass_iclass_decryptionkey));
     mbedtls_des3_context ctx;
@@ -355,40 +351,25 @@ ReturnCode picopass_device_decrypt(uint8_t* enc_data, uint8_t* dec_data) {
     mbedtls_des3_set2key_dec(&ctx, key);
     mbedtls_des3_crypt_ecb(&ctx, enc_data, dec_data);
     mbedtls_des3_free(&ctx);
-    return ERR_NONE;
 }
 
-ReturnCode picopass_device_parse_credential(PicopassBlock* AA1, PicopassPacs* pacs) {
-    ReturnCode err;
-
+void picopass_device_parse_credential(PicopassBlock* AA1, PicopassPacs* pacs) {
     pacs->biometrics = AA1[6].data[4];
     pacs->pin_length = AA1[6].data[6] & 0x0F;
     pacs->encryption = AA1[6].data[7];
 
     if(pacs->encryption == PicopassDeviceEncryption3DES) {
         FURI_LOG_D(TAG, "3DES Encrypted");
-        err = picopass_device_decrypt(AA1[7].data, pacs->credential);
-        if(err != ERR_NONE) {
-            FURI_LOG_E(TAG, "decrypt error %d", err);
-            return err;
-        }
+        picopass_device_decrypt(AA1[7].data, pacs->credential);
 
-        err = picopass_device_decrypt(AA1[8].data, pacs->pin0);
-        if(err != ERR_NONE) {
-            FURI_LOG_E(TAG, "decrypt error %d", err);
-            return err;
-        }
+        picopass_device_decrypt(AA1[8].data, pacs->pin0);
 
-        err = picopass_device_decrypt(AA1[9].data, pacs->pin1);
-        if(err != ERR_NONE) {
-            FURI_LOG_E(TAG, "decrypt error %d", err);
-            return err;
-        }
+        picopass_device_decrypt(AA1[9].data, pacs->pin1);
     } else if(pacs->encryption == PicopassDeviceEncryptionNone) {
         FURI_LOG_D(TAG, "No Encryption");
-        memcpy(pacs->credential, AA1[7].data, RFAL_PICOPASS_BLOCK_LEN);
-        memcpy(pacs->pin0, AA1[8].data, RFAL_PICOPASS_BLOCK_LEN);
-        memcpy(pacs->pin1, AA1[9].data, RFAL_PICOPASS_BLOCK_LEN);
+        memcpy(pacs->credential, AA1[7].data, PICOPASS_BLOCK_LEN);
+        memcpy(pacs->pin0, AA1[8].data, PICOPASS_BLOCK_LEN);
+        memcpy(pacs->pin1, AA1[9].data, PICOPASS_BLOCK_LEN);
     } else if(pacs->encryption == PicopassDeviceEncryptionDES) {
         FURI_LOG_D(TAG, "DES Encrypted");
     } else {
@@ -396,11 +377,9 @@ ReturnCode picopass_device_parse_credential(PicopassBlock* AA1, PicopassPacs* pa
     }
 
     pacs->sio = (AA1[10].data[0] == 0x30); // rough check
-
-    return ERR_NONE;
 }
 
-ReturnCode picopass_device_parse_wiegand(uint8_t* credential, PicopassPacs* pacs) {
+void picopass_device_parse_wiegand(uint8_t* credential, PicopassPacs* pacs) {
     uint32_t* halves = (uint32_t*)credential;
     if(halves[0] == 0) {
         uint8_t leading0s = __builtin_clz(REVERSE_BYTES_U32(halves[1]));
@@ -417,8 +396,6 @@ ReturnCode picopass_device_parse_wiegand(uint8_t* credential, PicopassPacs* pacs
     swapped = swapped ^ sentinel;
     memcpy(credential, &swapped, sizeof(uint64_t));
     FURI_LOG_D(TAG, "PACS: (%d) %016llx", pacs->bitLength, swapped);
-
-    return ERR_NONE;
 }
 
 bool picopass_device_hid_csn(PicopassDevice* dev) {
@@ -429,4 +406,4 @@ bool picopass_device_hid_csn(PicopassDevice* dev) {
     bool isHidRange = (memcmp(csn + 5, "\xFF\x12\xE0", 3) == 0) && ((csn[4] & 0xF0) == 0xF0);
 
     return isHidRange;
-}
+}

+ 4 - 5
picopass_device.h

@@ -96,13 +96,12 @@ typedef struct {
 } PicopassPacs;
 
 typedef struct {
-    uint8_t data[RFAL_PICOPASS_BLOCK_LEN];
+    uint8_t data[PICOPASS_BLOCK_LEN];
 } PicopassBlock;
 
 typedef struct {
     PicopassBlock AA1[PICOPASS_MAX_APP_LIMIT];
     PicopassPacs pacs;
-    IclassEliteDictAttackData iclass_elite_dict_attack_data;
 } PicopassDeviceData;
 
 typedef struct {
@@ -147,6 +146,6 @@ void picopass_device_set_loading_callback(
     PicopassLoadingCallback callback,
     void* context);
 
-ReturnCode picopass_device_parse_credential(PicopassBlock* AA1, PicopassPacs* pacs);
-ReturnCode picopass_device_parse_wiegand(uint8_t* credential, PicopassPacs* pacs);
-bool picopass_device_hid_csn(PicopassDevice* dev);
+void picopass_device_parse_credential(PicopassBlock* AA1, PicopassPacs* pacs);
+void picopass_device_parse_wiegand(uint8_t* credential, PicopassPacs* pacs);
+bool picopass_device_hid_csn(PicopassDevice* dev);

+ 41 - 3
picopass_i.h

@@ -1,7 +1,6 @@
 #pragma once
 
 #include "picopass.h"
-#include "picopass_worker.h"
 #include "picopass_device.h"
 
 #include "rfal_picopass.h"
@@ -29,8 +28,17 @@
 #include <lib/toolbox/path.h>
 #include <picopass_icons.h>
 
+#include <nfc/nfc.h>
+#include <nfc/helpers/nfc_dict.h>
+#include "protocol/picopass_poller.h"
+#include "protocol/picopass_listener.h"
+
 #define PICOPASS_TEXT_STORE_SIZE 128
 
+#define PICOPASS_ICLASS_ELITE_DICT_FLIPPER_NAME APP_ASSETS_PATH("iclass_elite_dict.txt")
+#define PICOPASS_ICLASS_STANDARD_DICT_FLIPPER_NAME APP_ASSETS_PATH("iclass_standard_dict.txt")
+#define PICOPASS_ICLASS_ELITE_DICT_USER_NAME APP_DATA_PATH("assets/iclass_elite_dict_user.txt")
+
 enum PicopassCustomEvent {
     // Reserve first 100 events for button types and indexes, starting from 0
     PicopassCustomEventReserved = 100,
@@ -40,6 +48,12 @@ enum PicopassCustomEvent {
     PicopassCustomEventByteInputDone,
     PicopassCustomEventTextInputDone,
     PicopassCustomEventDictAttackSkip,
+    PicopassCustomEventDictAttackUpdateView,
+    PicopassCustomEventLoclassGotMac,
+    PicopassCustomEventLoclassGotStandardKey,
+
+    PicopassCustomEventPollerSuccess,
+    PicopassCustomEventPollerFail,
 };
 
 typedef enum {
@@ -47,17 +61,37 @@ typedef enum {
     EventTypeKey,
 } EventType;
 
+typedef struct {
+    const char* name;
+    uint16_t total_keys;
+    uint16_t current_key;
+    bool card_detected;
+} PicopassDictAttackContext;
+
+typedef struct {
+    uint8_t key_to_write[PICOPASS_BLOCK_LEN];
+    bool is_elite;
+} PicopassWriteKeyContext;
+
+typedef struct {
+    size_t macs_collected;
+} PicopassLoclassContext;
+
 struct Picopass {
-    PicopassWorker* worker;
     ViewDispatcher* view_dispatcher;
     Gui* gui;
     NotificationApp* notifications;
     SceneManager* scene_manager;
     PicopassDevice* dev;
 
+    Nfc* nfc;
+    PicopassPoller* poller;
+    PicopassListener* listener;
+    NfcDict* dict;
+
     char text_store[PICOPASS_TEXT_STORE_SIZE + 1];
     FuriString* text_box_store;
-    uint8_t byte_input_store[RFAL_PICOPASS_BLOCK_LEN];
+    uint8_t byte_input_store[PICOPASS_BLOCK_LEN];
 
     // Common Views
     Submenu* submenu;
@@ -68,6 +102,10 @@ struct Picopass {
     Widget* widget;
     DictAttack* dict_attack;
     Loclass* loclass;
+
+    PicopassDictAttackContext dict_attack_ctx;
+    PicopassWriteKeyContext write_key_context;
+    PicopassLoclassContext loclass_context;
 };
 
 typedef enum {

+ 6 - 6
picopass_keys.h

@@ -2,9 +2,9 @@
 
 #include "picopass_device.h"
 
-extern const uint8_t picopass_iclass_key[RFAL_PICOPASS_BLOCK_LEN];
-extern const uint8_t picopass_factory_credit_key[RFAL_PICOPASS_BLOCK_LEN];
-extern const uint8_t picopass_factory_debit_key[RFAL_PICOPASS_BLOCK_LEN];
-extern const uint8_t picopass_xice_key[RFAL_PICOPASS_BLOCK_LEN];
-extern const uint8_t picopass_xicl_key[RFAL_PICOPASS_BLOCK_LEN];
-extern const uint8_t picopass_xics_key[RFAL_PICOPASS_BLOCK_LEN];
+extern const uint8_t picopass_iclass_key[PICOPASS_BLOCK_LEN];
+extern const uint8_t picopass_factory_credit_key[PICOPASS_BLOCK_LEN];
+extern const uint8_t picopass_factory_debit_key[PICOPASS_BLOCK_LEN];
+extern const uint8_t picopass_xice_key[PICOPASS_BLOCK_LEN];
+extern const uint8_t picopass_xicl_key[PICOPASS_BLOCK_LEN];
+extern const uint8_t picopass_xics_key[PICOPASS_BLOCK_LEN];

+ 0 - 1340
picopass_worker.c

@@ -1,1340 +0,0 @@
-#include "picopass_worker_i.h"
-
-#include <flipper_format/flipper_format.h>
-#include <lib/nfc/protocols/nfcv.h>
-
-#define TAG "PicopassWorker"
-
-#define HAS_MASK(x, b) ((x & b) == b)
-
-// CSNs from Proxmark3 repo
-static const uint8_t loclass_csns[LOCLASS_NUM_CSNS][RFAL_PICOPASS_BLOCK_LEN] = {
-    {0x01, 0x0A, 0x0F, 0xFF, 0xF7, 0xFF, 0x12, 0xE0},
-    {0x0C, 0x06, 0x0C, 0xFE, 0xF7, 0xFF, 0x12, 0xE0},
-    {0x10, 0x97, 0x83, 0x7B, 0xF7, 0xFF, 0x12, 0xE0},
-    {0x13, 0x97, 0x82, 0x7A, 0xF7, 0xFF, 0x12, 0xE0},
-    {0x07, 0x0E, 0x0D, 0xF9, 0xF7, 0xFF, 0x12, 0xE0},
-    {0x14, 0x96, 0x84, 0x76, 0xF7, 0xFF, 0x12, 0xE0},
-    {0x17, 0x96, 0x85, 0x71, 0xF7, 0xFF, 0x12, 0xE0},
-    {0xCE, 0xC5, 0x0F, 0x77, 0xF7, 0xFF, 0x12, 0xE0},
-    {0xD2, 0x5A, 0x82, 0xF8, 0xF7, 0xFF, 0x12, 0xE0},
-};
-
-static void picopass_worker_enable_field() {
-    furi_hal_nfc_exit_sleep();
-    furi_hal_nfc_ll_txrx_on();
-    furi_hal_nfc_ll_poll();
-}
-
-static ReturnCode picopass_worker_disable_field(ReturnCode rc) {
-    furi_hal_nfc_ll_txrx_off();
-    furi_hal_nfc_start_sleep();
-    return rc;
-}
-
-/***************************** Picopass Worker API *******************************/
-
-PicopassWorker* picopass_worker_alloc() {
-    PicopassWorker* picopass_worker = malloc(sizeof(PicopassWorker));
-
-    // Worker thread attributes
-    picopass_worker->thread =
-        furi_thread_alloc_ex("PicopassWorker", 8 * 1024, picopass_worker_task, picopass_worker);
-
-    picopass_worker->callback = NULL;
-    picopass_worker->context = NULL;
-    picopass_worker->storage = furi_record_open(RECORD_STORAGE);
-
-    picopass_worker_change_state(picopass_worker, PicopassWorkerStateReady);
-
-    return picopass_worker;
-}
-
-void picopass_worker_free(PicopassWorker* picopass_worker) {
-    furi_assert(picopass_worker);
-
-    furi_thread_free(picopass_worker->thread);
-
-    furi_record_close(RECORD_STORAGE);
-
-    free(picopass_worker);
-}
-
-PicopassWorkerState picopass_worker_get_state(PicopassWorker* picopass_worker) {
-    return picopass_worker->state;
-}
-
-void picopass_worker_start(
-    PicopassWorker* picopass_worker,
-    PicopassWorkerState state,
-    PicopassDeviceData* dev_data,
-    PicopassWorkerCallback callback,
-    void* context) {
-    furi_assert(picopass_worker);
-    furi_assert(dev_data);
-
-    picopass_worker->callback = callback;
-    picopass_worker->context = context;
-    picopass_worker->dev_data = dev_data;
-    picopass_worker_change_state(picopass_worker, state);
-    furi_thread_start(picopass_worker->thread);
-}
-
-void picopass_worker_stop(PicopassWorker* picopass_worker) {
-    furi_assert(picopass_worker);
-    furi_assert(picopass_worker->thread);
-
-    if(furi_thread_get_state(picopass_worker->thread) == FuriThreadStateStopped) {
-        return;
-    }
-
-    if(picopass_worker->state == PicopassWorkerStateBroken ||
-       picopass_worker->state == PicopassWorkerStateReady) {
-        return;
-    }
-
-    if(picopass_worker->state != PicopassWorkerStateEmulate &&
-       picopass_worker->state != PicopassWorkerStateLoclass) {
-        // Can't do this while emulating in transparent mode as SPI isn't active
-        picopass_worker_disable_field(ERR_NONE);
-    }
-
-    if(furi_thread_get_state(picopass_worker->thread) != FuriThreadStateStopped) {
-        picopass_worker_change_state(picopass_worker, PicopassWorkerStateStop);
-        furi_thread_join(picopass_worker->thread);
-    }
-}
-
-void picopass_worker_change_state(PicopassWorker* picopass_worker, PicopassWorkerState state) {
-    picopass_worker->state = state;
-}
-
-/***************************** Picopass Worker Thread *******************************/
-
-ReturnCode picopass_detect_card(int timeout) {
-    UNUSED(timeout);
-
-    ReturnCode err;
-
-    err = rfalPicoPassPollerInitialize();
-    if(err != ERR_NONE) {
-        FURI_LOG_E(TAG, "rfalPicoPassPollerInitialize error %d", err);
-        return err;
-    }
-
-    err = rfalFieldOnAndStartGT();
-    if(err != ERR_NONE) {
-        FURI_LOG_E(TAG, "rfalFieldOnAndStartGT error %d", err);
-        return err;
-    }
-
-    err = rfalPicoPassPollerCheckPresence();
-    if(err != ERR_RF_COLLISION) {
-        FURI_LOG_E(TAG, "rfalPicoPassPollerCheckPresence error %d", err);
-        return err;
-    }
-
-    return ERR_NONE;
-}
-
-ReturnCode picopass_read_preauth(PicopassBlock* AA1) {
-    rfalPicoPassIdentifyRes idRes;
-    rfalPicoPassSelectRes selRes;
-
-    ReturnCode err;
-
-    err = rfalPicoPassPollerIdentify(&idRes);
-    if(err != ERR_NONE) {
-        FURI_LOG_E(TAG, "rfalPicoPassPollerIdentify error %d", err);
-        return err;
-    }
-
-    err = rfalPicoPassPollerSelect(idRes.CSN, &selRes);
-    if(err != ERR_NONE) {
-        FURI_LOG_E(TAG, "rfalPicoPassPollerSelect error %d", err);
-        return err;
-    }
-
-    memcpy(AA1[PICOPASS_CSN_BLOCK_INDEX].data, selRes.CSN, sizeof(selRes.CSN));
-    FURI_LOG_D(
-        TAG,
-        "csn %02x%02x%02x%02x%02x%02x%02x%02x",
-        AA1[PICOPASS_CSN_BLOCK_INDEX].data[0],
-        AA1[PICOPASS_CSN_BLOCK_INDEX].data[1],
-        AA1[PICOPASS_CSN_BLOCK_INDEX].data[2],
-        AA1[PICOPASS_CSN_BLOCK_INDEX].data[3],
-        AA1[PICOPASS_CSN_BLOCK_INDEX].data[4],
-        AA1[PICOPASS_CSN_BLOCK_INDEX].data[5],
-        AA1[PICOPASS_CSN_BLOCK_INDEX].data[6],
-        AA1[PICOPASS_CSN_BLOCK_INDEX].data[7]);
-
-    rfalPicoPassReadBlockRes cfg = {0};
-    rfalPicoPassPollerReadBlock(PICOPASS_CONFIG_BLOCK_INDEX, &cfg);
-    memcpy(AA1[PICOPASS_CONFIG_BLOCK_INDEX].data, cfg.data, sizeof(cfg.data));
-    FURI_LOG_D(
-        TAG,
-        "config %02x%02x%02x%02x%02x%02x%02x%02x",
-        AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[0],
-        AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[1],
-        AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[2],
-        AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[3],
-        AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[4],
-        AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[5],
-        AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[6],
-        AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[7]);
-
-    rfalPicoPassReadBlockRes aia;
-    rfalPicoPassPollerReadBlock(PICOPASS_SECURE_AIA_BLOCK_INDEX, &aia);
-    memcpy(AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data, aia.data, sizeof(aia.data));
-    FURI_LOG_D(
-        TAG,
-        "aia %02x%02x%02x%02x%02x%02x%02x%02x",
-        AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[0],
-        AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[1],
-        AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[2],
-        AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[3],
-        AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[4],
-        AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[5],
-        AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[6],
-        AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[7]);
-
-    return ERR_NONE;
-}
-
-static ReturnCode
-    picopass_auth_dict(PicopassWorker* picopass_worker, IclassEliteDictType dict_type) {
-    rfalPicoPassReadCheckRes rcRes;
-    rfalPicoPassCheckRes chkRes;
-    bool elite = (dict_type != IclassStandardDictTypeFlipper);
-
-    PicopassDeviceData* dev_data = picopass_worker->dev_data;
-    PicopassBlock* AA1 = dev_data->AA1;
-    PicopassPacs* pacs = &dev_data->pacs;
-
-    uint8_t* csn = AA1[PICOPASS_CSN_BLOCK_INDEX].data;
-    uint8_t* div_key = AA1[PICOPASS_SECURE_KD_BLOCK_INDEX].data;
-
-    ReturnCode err = ERR_PARAM;
-
-    uint8_t mac[4] = {0};
-    uint8_t ccnr[12] = {0};
-
-    size_t index = 0;
-    uint8_t key[RFAL_PICOPASS_BLOCK_LEN] = {0};
-
-    if(!iclass_elite_dict_check_presence(dict_type)) {
-        FURI_LOG_E(TAG, "Dictionary not found");
-        return ERR_PARAM;
-    }
-
-    IclassEliteDict* dict = iclass_elite_dict_alloc(dict_type);
-    if(!dict) {
-        FURI_LOG_E(TAG, "Dictionary not allocated");
-        return ERR_PARAM;
-    }
-
-    FURI_LOG_D(TAG, "Loaded %lu keys", iclass_elite_dict_get_total_keys(dict));
-    while(iclass_elite_dict_get_next_key(dict, key)) {
-        FURI_LOG_D(
-            TAG,
-            "Try to %s auth with key %zu %02x%02x%02x%02x%02x%02x%02x%02x",
-            elite ? "elite" : "standard",
-            index++,
-            key[0],
-            key[1],
-            key[2],
-            key[3],
-            key[4],
-            key[5],
-            key[6],
-            key[7]);
-
-        err = rfalPicoPassPollerReadCheck(&rcRes);
-        if(err != ERR_NONE) {
-            FURI_LOG_E(TAG, "rfalPicoPassPollerReadCheck error %d", err);
-            break;
-        }
-        memcpy(ccnr, rcRes.CCNR, sizeof(rcRes.CCNR)); // last 4 bytes left 0
-
-        loclass_iclass_calc_div_key(csn, key, div_key, elite);
-        loclass_opt_doReaderMAC(ccnr, div_key, mac);
-
-        err = rfalPicoPassPollerCheck(mac, &chkRes);
-        if(err == ERR_NONE) {
-            memcpy(pacs->key, key, RFAL_PICOPASS_BLOCK_LEN);
-            break;
-        }
-
-        if(picopass_worker->state != PicopassWorkerStateDetect) break;
-    }
-
-    iclass_elite_dict_free(dict);
-
-    return err;
-}
-
-ReturnCode picopass_auth(PicopassWorker* picopass_worker) {
-    ReturnCode err;
-
-    FURI_LOG_I(TAG, "Starting system dictionary attack [Standard KDF]");
-    err = picopass_auth_dict(picopass_worker, IclassStandardDictTypeFlipper);
-    if(err == ERR_NONE) {
-        return ERR_NONE;
-    }
-
-    /* Because size of the user dictionary and could introduce confusing delay
-     * to the read screen (since there is no feedback), we omit checking it.
-     * It will be checked when the user uses Elite Dict. Attack, which has a progress bar
-     */
-
-    FURI_LOG_I(TAG, "Starting system dictionary attack [Elite KDF]");
-    err = picopass_auth_dict(picopass_worker, IclassEliteDictTypeFlipper);
-    if(err == ERR_NONE) {
-        return ERR_NONE;
-    }
-
-    return err;
-}
-
-ReturnCode picopass_read_card(PicopassBlock* AA1) {
-    ReturnCode err;
-
-    size_t app_limit = AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[0] < PICOPASS_MAX_APP_LIMIT ?
-                           AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[0] :
-                           PICOPASS_MAX_APP_LIMIT;
-
-    for(size_t i = 2; i < app_limit; i++) {
-        if(i == PICOPASS_SECURE_KD_BLOCK_INDEX) {
-            // Skip over Kd block which is populated earlier (READ of Kd returns all FF's)
-            continue;
-        }
-
-        rfalPicoPassReadBlockRes block;
-        err = rfalPicoPassPollerReadBlock(i, &block);
-        if(err != ERR_NONE) {
-            FURI_LOG_E(TAG, "rfalPicoPassPollerReadBlock error %d", err);
-            return err;
-        }
-
-        FURI_LOG_D(
-            TAG,
-            "rfalPicoPassPollerReadBlock %d %02x%02x%02x%02x%02x%02x%02x%02x",
-            i,
-            block.data[0],
-            block.data[1],
-            block.data[2],
-            block.data[3],
-            block.data[4],
-            block.data[5],
-            block.data[6],
-            block.data[7]);
-
-        memcpy(AA1[i].data, block.data, sizeof(block.data));
-    }
-
-    return ERR_NONE;
-}
-
-ReturnCode picopass_write_card(PicopassBlock* AA1) {
-    rfalPicoPassIdentifyRes idRes;
-    rfalPicoPassSelectRes selRes;
-    rfalPicoPassReadCheckRes rcRes;
-    rfalPicoPassCheckRes chkRes;
-
-    ReturnCode err;
-
-    uint8_t div_key[8] = {0};
-    uint8_t mac[4] = {0};
-    uint8_t ccnr[12] = {0};
-
-    err = rfalPicoPassPollerIdentify(&idRes);
-    if(err != ERR_NONE) {
-        FURI_LOG_E(TAG, "rfalPicoPassPollerIdentify error %d", err);
-        return err;
-    }
-
-    err = rfalPicoPassPollerSelect(idRes.CSN, &selRes);
-    if(err != ERR_NONE) {
-        FURI_LOG_E(TAG, "rfalPicoPassPollerSelect error %d", err);
-        return err;
-    }
-
-    err = rfalPicoPassPollerReadCheck(&rcRes);
-    if(err != ERR_NONE) {
-        FURI_LOG_E(TAG, "rfalPicoPassPollerReadCheck error %d", err);
-        return err;
-    }
-    memcpy(ccnr, rcRes.CCNR, sizeof(rcRes.CCNR)); // last 4 bytes left 0
-
-    loclass_iclass_calc_div_key(selRes.CSN, (uint8_t*)picopass_iclass_key, div_key, false);
-    loclass_opt_doReaderMAC(ccnr, div_key, mac);
-
-    err = rfalPicoPassPollerCheck(mac, &chkRes);
-    if(err != ERR_NONE) {
-        FURI_LOG_E(TAG, "rfalPicoPassPollerCheck error %d", err);
-        return err;
-    }
-
-    for(size_t i = 6; i < 10; i++) {
-        FURI_LOG_D(TAG, "rfalPicoPassPollerWriteBlock %d", i);
-        uint8_t data[9] = {0};
-        data[0] = i;
-        memcpy(data + 1, AA1[i].data, RFAL_PICOPASS_BLOCK_LEN);
-        loclass_doMAC_N(data, sizeof(data), div_key, mac);
-        FURI_LOG_D(
-            TAG,
-            "loclass_doMAC_N %d %02x%02x%02x%02x%02x%02x%02x%02x %02x%02x%02x%02x",
-            i,
-            data[1],
-            data[2],
-            data[3],
-            data[4],
-            data[5],
-            data[6],
-            data[7],
-            data[8],
-            mac[0],
-            mac[1],
-            mac[2],
-            mac[3]);
-
-        err = rfalPicoPassPollerWriteBlock(i, AA1[i].data, mac);
-        if(err != ERR_NONE) {
-            FURI_LOG_E(TAG, "rfalPicoPassPollerWriteBlock error %d", err);
-            return err;
-        }
-    }
-
-    return ERR_NONE;
-}
-
-ReturnCode picopass_write_block(PicopassBlock* AA1, uint8_t blockNo, uint8_t* newBlock) {
-    rfalPicoPassIdentifyRes idRes;
-    rfalPicoPassSelectRes selRes;
-    rfalPicoPassReadCheckRes rcRes;
-    rfalPicoPassCheckRes chkRes;
-
-    ReturnCode err;
-
-    uint8_t mac[4] = {0};
-    uint8_t ccnr[12] = {0};
-
-    err = rfalPicoPassPollerIdentify(&idRes);
-    if(err != ERR_NONE) {
-        FURI_LOG_E(TAG, "rfalPicoPassPollerIdentify error %d", err);
-        return err;
-    }
-
-    err = rfalPicoPassPollerSelect(idRes.CSN, &selRes);
-    if(err != ERR_NONE) {
-        FURI_LOG_E(TAG, "rfalPicoPassPollerSelect error %d", err);
-        return err;
-    }
-
-    err = rfalPicoPassPollerReadCheck(&rcRes);
-    if(err != ERR_NONE) {
-        FURI_LOG_E(TAG, "rfalPicoPassPollerReadCheck error %d", err);
-        return err;
-    }
-    memcpy(ccnr, rcRes.CCNR, sizeof(rcRes.CCNR)); // last 4 bytes left 0
-
-    if(memcmp(selRes.CSN, AA1[PICOPASS_CSN_BLOCK_INDEX].data, RFAL_PICOPASS_BLOCK_LEN) != 0) {
-        FURI_LOG_E(TAG, "Wrong CSN for write");
-        return ERR_REQUEST;
-    }
-
-    loclass_opt_doReaderMAC(ccnr, AA1[PICOPASS_SECURE_KD_BLOCK_INDEX].data, mac);
-    err = rfalPicoPassPollerCheck(mac, &chkRes);
-    if(err != ERR_NONE) {
-        FURI_LOG_E(TAG, "rfalPicoPassPollerCheck error %d", err);
-        return err;
-    }
-
-    FURI_LOG_D(TAG, "rfalPicoPassPollerWriteBlock %d", blockNo);
-    uint8_t data[9] = {
-        blockNo,
-        newBlock[0],
-        newBlock[1],
-        newBlock[2],
-        newBlock[3],
-        newBlock[4],
-        newBlock[5],
-        newBlock[6],
-        newBlock[7]};
-    loclass_doMAC_N(data, sizeof(data), AA1[PICOPASS_SECURE_KD_BLOCK_INDEX].data, mac);
-    FURI_LOG_D(
-        TAG,
-        "loclass_doMAC_N %d %02x%02x%02x%02x%02x%02x%02x%02x %02x%02x%02x%02x",
-        blockNo,
-        data[1],
-        data[2],
-        data[3],
-        data[4],
-        data[5],
-        data[6],
-        data[7],
-        data[8],
-        mac[0],
-        mac[1],
-        mac[2],
-        mac[3]);
-
-    err = rfalPicoPassPollerWriteBlock(data[0], data + 1, mac);
-    if(err != ERR_NONE) {
-        FURI_LOG_E(TAG, "rfalPicoPassPollerWriteBlock error %d", err);
-        return err;
-    }
-
-    return ERR_NONE;
-}
-
-void picopass_worker_elite_dict_attack(PicopassWorker* picopass_worker) {
-    furi_assert(picopass_worker);
-    furi_assert(picopass_worker->callback);
-
-    picopass_device_data_clear(picopass_worker->dev_data);
-    PicopassDeviceData* dev_data = picopass_worker->dev_data;
-    PicopassBlock* AA1 = dev_data->AA1;
-    PicopassPacs* pacs = &dev_data->pacs;
-
-    for(size_t i = 0; i < PICOPASS_MAX_APP_LIMIT; i++) {
-        memset(AA1[i].data, 0, sizeof(AA1[i].data));
-    }
-    memset(pacs, 0, sizeof(PicopassPacs));
-
-    IclassEliteDictAttackData* dict_attack_data =
-        &picopass_worker->dev_data->iclass_elite_dict_attack_data;
-    bool elite = (dict_attack_data->type != IclassStandardDictTypeFlipper);
-
-    rfalPicoPassReadCheckRes rcRes;
-    rfalPicoPassCheckRes chkRes;
-
-    ReturnCode err;
-    uint8_t mac[4] = {0};
-    uint8_t ccnr[12] = {0};
-
-    size_t index = 0;
-    uint8_t key[RFAL_PICOPASS_BLOCK_LEN] = {0};
-
-    // Load dictionary
-    IclassEliteDict* dict = dict_attack_data->dict;
-    if(!dict) {
-        FURI_LOG_E(TAG, "Dictionary not found");
-        picopass_worker->callback(PicopassWorkerEventNoDictFound, picopass_worker->context);
-        return;
-    }
-
-    do {
-        if(picopass_detect_card(1000) == ERR_NONE) {
-            picopass_worker->callback(PicopassWorkerEventCardDetected, picopass_worker->context);
-
-            // Process first found device
-            err = picopass_read_preauth(AA1);
-            if(err != ERR_NONE) {
-                FURI_LOG_E(TAG, "picopass_read_preauth error %d", err);
-                picopass_worker->callback(PicopassWorkerEventAborted, picopass_worker->context);
-                return;
-            }
-
-            // Thank you proxmark!
-            pacs->legacy = picopass_is_memset(AA1[5].data, 0xFF, 8);
-            pacs->se_enabled = (memcmp(AA1[5].data, "\xff\xff\xff\x00\x06\xff\xff\xff", 8) == 0);
-            if(pacs->se_enabled) {
-                FURI_LOG_D(TAG, "SE enabled");
-                picopass_worker->callback(PicopassWorkerEventAborted, picopass_worker->context);
-                return;
-            }
-
-            break;
-        } else {
-            picopass_worker->callback(PicopassWorkerEventNoCardDetected, picopass_worker->context);
-        }
-        if(picopass_worker->state != PicopassWorkerStateEliteDictAttack) break;
-
-        furi_delay_ms(100);
-    } while(true);
-
-    FURI_LOG_D(
-        TAG, "Start Dictionary attack, Key Count %lu", iclass_elite_dict_get_total_keys(dict));
-    while(iclass_elite_dict_get_next_key(dict, key)) {
-        FURI_LOG_T(TAG, "Key %zu", index);
-        if(++index % PICOPASS_DICT_KEY_BATCH_SIZE == 0) {
-            picopass_worker->callback(
-                PicopassWorkerEventNewDictKeyBatch, picopass_worker->context);
-        }
-
-        err = rfalPicoPassPollerReadCheck(&rcRes);
-        if(err != ERR_NONE) {
-            FURI_LOG_E(TAG, "rfalPicoPassPollerReadCheck error %d", err);
-            break;
-        }
-        memcpy(ccnr, rcRes.CCNR, sizeof(rcRes.CCNR)); // last 4 bytes left 0
-
-        uint8_t* csn = AA1[PICOPASS_CSN_BLOCK_INDEX].data;
-        uint8_t* div_key = AA1[PICOPASS_SECURE_KD_BLOCK_INDEX].data;
-
-        loclass_iclass_calc_div_key(csn, key, div_key, elite);
-        loclass_opt_doReaderMAC(ccnr, div_key, mac);
-
-        err = rfalPicoPassPollerCheck(mac, &chkRes);
-        if(err == ERR_NONE) {
-            FURI_LOG_I(
-                TAG,
-                "Found key: %02x%02x%02x%02x%02x%02x%02x%02x",
-                key[0],
-                key[1],
-                key[2],
-                key[3],
-                key[4],
-                key[5],
-                key[6],
-                key[7]);
-
-            memcpy(pacs->key, key, RFAL_PICOPASS_BLOCK_LEN);
-            pacs->elite_kdf = elite;
-            err = picopass_read_card(AA1);
-            if(err != ERR_NONE) {
-                FURI_LOG_E(TAG, "picopass_read_card error %d", err);
-                picopass_worker->callback(PicopassWorkerEventFail, picopass_worker->context);
-                break;
-            }
-
-            err = picopass_device_parse_credential(AA1, pacs);
-            if(err != ERR_NONE) {
-                FURI_LOG_E(TAG, "picopass_device_parse_credential error %d", err);
-                picopass_worker->callback(PicopassWorkerEventFail, picopass_worker->context);
-                break;
-            }
-
-            err = picopass_device_parse_wiegand(pacs->credential, pacs);
-            if(err != ERR_NONE) {
-                FURI_LOG_E(TAG, "picopass_device_parse_wiegand error %d", err);
-                picopass_worker->callback(PicopassWorkerEventFail, picopass_worker->context);
-                break;
-            }
-            picopass_worker->callback(PicopassWorkerEventAborted, picopass_worker->context);
-            break;
-        }
-
-        if(picopass_worker->state != PicopassWorkerStateEliteDictAttack) break;
-    }
-    FURI_LOG_D(TAG, "Dictionary complete");
-    if(picopass_worker->state == PicopassWorkerStateEliteDictAttack) {
-        picopass_worker->callback(PicopassWorkerEventSuccess, picopass_worker->context);
-    } else {
-        picopass_worker->callback(PicopassWorkerEventAborted, picopass_worker->context);
-    }
-}
-
-int32_t picopass_worker_task(void* context) {
-    PicopassWorker* picopass_worker = context;
-
-    if(picopass_worker->state == PicopassWorkerStateDetect) {
-        picopass_worker_enable_field();
-        picopass_worker_detect(picopass_worker);
-    } else if(picopass_worker->state == PicopassWorkerStateWrite) {
-        picopass_worker_enable_field();
-        picopass_worker_write(picopass_worker);
-    } else if(picopass_worker->state == PicopassWorkerStateWriteKey) {
-        picopass_worker_enable_field();
-        picopass_worker_write_key(picopass_worker);
-    } else if(picopass_worker->state == PicopassWorkerStateEliteDictAttack) {
-        picopass_worker_enable_field();
-        picopass_worker_elite_dict_attack(picopass_worker);
-    } else if(picopass_worker->state == PicopassWorkerStateEmulate) {
-        picopass_worker_emulate(picopass_worker, false);
-    } else if(picopass_worker->state == PicopassWorkerStateLoclass) {
-        picopass_worker_emulate(picopass_worker, true);
-    } else if(picopass_worker->state == PicopassWorkerStateStop) {
-        FURI_LOG_D(TAG, "Worker state stop");
-        // no-op
-    } else {
-        FURI_LOG_W(TAG, "Unknown state %d", picopass_worker->state);
-    }
-    picopass_worker_disable_field(ERR_NONE);
-    picopass_worker_change_state(picopass_worker, PicopassWorkerStateReady);
-
-    return 0;
-}
-
-void picopass_worker_detect(PicopassWorker* picopass_worker) {
-    picopass_device_data_clear(picopass_worker->dev_data);
-    PicopassDeviceData* dev_data = picopass_worker->dev_data;
-
-    PicopassBlock* AA1 = dev_data->AA1;
-    PicopassPacs* pacs = &dev_data->pacs;
-    ReturnCode err;
-
-    // reset device data
-    for(size_t i = 0; i < PICOPASS_MAX_APP_LIMIT; i++) {
-        memset(AA1[i].data, 0, sizeof(AA1[i].data));
-    }
-    memset(pacs, 0, sizeof(PicopassPacs));
-
-    PicopassWorkerEvent nextState = PicopassWorkerEventSuccess;
-
-    while(picopass_worker->state == PicopassWorkerStateDetect) {
-        if(picopass_detect_card(1000) == ERR_NONE) {
-            // Process first found device
-            err = picopass_read_preauth(AA1);
-            if(err != ERR_NONE) {
-                FURI_LOG_E(TAG, "picopass_read_preauth error %d", err);
-                nextState = PicopassWorkerEventFail;
-            }
-
-            // Thank you proxmark!
-            pacs->legacy = picopass_is_memset(AA1[5].data, 0xFF, 8);
-            pacs->se_enabled = (memcmp(AA1[5].data, "\xff\xff\xff\x00\x06\xff\xff\xff", 8) == 0);
-            if(pacs->se_enabled) {
-                FURI_LOG_D(TAG, "SE enabled");
-                nextState = PicopassWorkerEventFail;
-            }
-
-            if(nextState == PicopassWorkerEventSuccess) {
-                err = picopass_auth(picopass_worker);
-                if(err != ERR_NONE) {
-                    FURI_LOG_E(TAG, "picopass_try_auth error %d", err);
-                    nextState = PicopassWorkerEventFail;
-                }
-            }
-
-            if(nextState == PicopassWorkerEventSuccess) {
-                err = picopass_read_card(AA1);
-                if(err != ERR_NONE) {
-                    FURI_LOG_E(TAG, "picopass_read_card error %d", err);
-                    nextState = PicopassWorkerEventFail;
-                }
-            }
-
-            if(nextState == PicopassWorkerEventSuccess) {
-                err = picopass_device_parse_credential(AA1, pacs);
-                if(err != ERR_NONE) {
-                    FURI_LOG_E(TAG, "picopass_device_parse_credential error %d", err);
-                    nextState = PicopassWorkerEventFail;
-                }
-            }
-
-            if(nextState == PicopassWorkerEventSuccess) {
-                err = picopass_device_parse_wiegand(pacs->credential, pacs);
-                if(err != ERR_NONE) {
-                    FURI_LOG_E(TAG, "picopass_device_parse_wiegand error %d", err);
-                    nextState = PicopassWorkerEventFail;
-                }
-            }
-
-            // Notify caller and exit
-            if(picopass_worker->callback) {
-                picopass_worker->callback(nextState, picopass_worker->context);
-            }
-            break;
-        }
-        furi_delay_ms(100);
-    }
-}
-
-void picopass_worker_write(PicopassWorker* picopass_worker) {
-    PicopassDeviceData* dev_data = picopass_worker->dev_data;
-    PicopassBlock* AA1 = dev_data->AA1;
-    ReturnCode err;
-    PicopassWorkerEvent nextState = PicopassWorkerEventSuccess;
-
-    while(picopass_worker->state == PicopassWorkerStateWrite) {
-        if(picopass_detect_card(1000) == ERR_NONE) {
-            err = picopass_write_card(AA1);
-            if(err != ERR_NONE) {
-                FURI_LOG_E(TAG, "picopass_write_card error %d", err);
-                nextState = PicopassWorkerEventFail;
-            }
-
-            // Notify caller and exit
-            if(picopass_worker->callback) {
-                picopass_worker->callback(nextState, picopass_worker->context);
-            }
-            break;
-        }
-        furi_delay_ms(100);
-    }
-}
-
-void picopass_worker_write_key(PicopassWorker* picopass_worker) {
-    PicopassDeviceData* dev_data = picopass_worker->dev_data;
-    PicopassBlock* AA1 = dev_data->AA1;
-    PicopassPacs* pacs = &dev_data->pacs;
-    ReturnCode err;
-    PicopassWorkerEvent nextState = PicopassWorkerEventSuccess;
-
-    uint8_t* csn = AA1[PICOPASS_CSN_BLOCK_INDEX].data;
-    uint8_t* configBlock = AA1[PICOPASS_CONFIG_BLOCK_INDEX].data;
-    uint8_t fuses = configBlock[7];
-    uint8_t* oldKey = AA1[PICOPASS_SECURE_KD_BLOCK_INDEX].data;
-
-    uint8_t newKey[RFAL_PICOPASS_BLOCK_LEN] = {0};
-    loclass_iclass_calc_div_key(csn, pacs->key, newKey, pacs->elite_kdf);
-
-    if((fuses & 0x80) == 0x80) {
-        FURI_LOG_D(TAG, "Plain write for personalized mode key change");
-    } else {
-        FURI_LOG_D(TAG, "XOR write for application mode key change");
-        // XOR when in application mode
-        for(size_t i = 0; i < RFAL_PICOPASS_BLOCK_LEN; i++) {
-            newKey[i] ^= oldKey[i];
-        }
-    }
-
-    while(picopass_worker->state == PicopassWorkerStateWriteKey) {
-        if(picopass_detect_card(1000) == ERR_NONE) {
-            err = picopass_write_block(AA1, PICOPASS_SECURE_KD_BLOCK_INDEX, newKey);
-            if(err != ERR_NONE) {
-                FURI_LOG_E(TAG, "picopass_write_block error %d", err);
-                nextState = PicopassWorkerEventFail;
-            }
-
-            // Notify caller and exit
-            if(picopass_worker->callback) {
-                picopass_worker->callback(nextState, picopass_worker->context);
-            }
-            break;
-        }
-        furi_delay_ms(100);
-    }
-}
-
-// from proxmark3 armsrc/iclass.c rotateCSN
-static void picopass_anticoll_csn(uint8_t* rotated_csn, const uint8_t* original_csn) {
-    for(uint8_t i = 0; i < 8; i++) {
-        rotated_csn[i] = (original_csn[i] >> 3) | (original_csn[(i + 1) % 8] << 5);
-    }
-}
-
-static void picopass_append_crc(uint8_t* buf, uint16_t size) {
-    uint16_t crc = rfalPicoPassCalculateCcitt(0xE012, buf, size);
-
-    buf[size] = crc & 0xFF;
-    buf[size + 1] = crc >> 8;
-}
-
-static inline void picopass_emu_read_blocks(
-    NfcVData* nfcv_data,
-    uint8_t* buf,
-    uint8_t block_num,
-    uint8_t block_count) {
-    memcpy(
-        buf,
-        nfcv_data->data + (block_num * RFAL_PICOPASS_BLOCK_LEN),
-        block_count * RFAL_PICOPASS_BLOCK_LEN);
-}
-
-static inline void picopass_emu_write_blocks(
-    NfcVData* nfcv_data,
-    const uint8_t* buf,
-    uint8_t block_num,
-    uint8_t block_count) {
-    memcpy(
-        nfcv_data->data + (block_num * RFAL_PICOPASS_BLOCK_LEN),
-        buf,
-        block_count * RFAL_PICOPASS_BLOCK_LEN);
-}
-
-static void picopass_init_cipher_state_key(
-    NfcVData* nfcv_data,
-    PicopassEmulatorCtx* ctx,
-    const uint8_t key[RFAL_PICOPASS_BLOCK_LEN]) {
-    uint8_t cc[RFAL_PICOPASS_BLOCK_LEN];
-    picopass_emu_read_blocks(nfcv_data, cc, PICOPASS_SECURE_EPURSE_BLOCK_INDEX, 1);
-
-    ctx->cipher_state = loclass_opt_doTagMAC_1(cc, key);
-}
-
-static void picopass_init_cipher_state(NfcVData* nfcv_data, PicopassEmulatorCtx* ctx) {
-    uint8_t key[RFAL_PICOPASS_BLOCK_LEN];
-
-    picopass_emu_read_blocks(nfcv_data, key, ctx->key_block_num, 1);
-
-    picopass_init_cipher_state_key(nfcv_data, ctx, key);
-}
-
-static void
-    loclass_update_csn(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data, PicopassEmulatorCtx* ctx) {
-    // collect LOCLASS_NUM_PER_CSN nonces in a row for each CSN
-    const uint8_t* csn =
-        loclass_csns[(ctx->key_block_num / LOCLASS_NUM_PER_CSN) % LOCLASS_NUM_CSNS];
-    memcpy(nfc_data->uid, csn, RFAL_PICOPASS_BLOCK_LEN);
-    picopass_emu_write_blocks(nfcv_data, csn, PICOPASS_CSN_BLOCK_INDEX, 1);
-
-    uint8_t key[RFAL_PICOPASS_BLOCK_LEN];
-    loclass_iclass_calc_div_key(csn, picopass_iclass_key, key, false);
-    picopass_emu_write_blocks(nfcv_data, key, PICOPASS_SECURE_KD_BLOCK_INDEX, 1);
-
-    picopass_init_cipher_state_key(nfcv_data, ctx, key);
-}
-
-static void picopass_emu_handle_packet(
-    FuriHalNfcTxRxContext* tx_rx,
-    FuriHalNfcDevData* nfc_data,
-    void* nfcv_data_in) {
-    NfcVData* nfcv_data = (NfcVData*)nfcv_data_in;
-    PicopassEmulatorCtx* ctx = nfcv_data->emu_protocol_ctx;
-    uint8_t response[34];
-    uint8_t response_length = 0;
-    uint8_t key_block_num = PICOPASS_SECURE_KD_BLOCK_INDEX;
-
-    const uint8_t block_ff[8] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
-
-    if(nfcv_data->frame_length < 1 || ctx->state == PicopassEmulatorStateStopEmulation) {
-        return;
-    }
-
-    switch(nfcv_data->frame[0]) {
-    case RFAL_PICOPASS_CMD_ACTALL: // No args
-        if(nfcv_data->frame_length != 1) {
-            return;
-        }
-
-        if(ctx->state != PicopassEmulatorStateHalt) {
-            ctx->state = PicopassEmulatorStateActive;
-        }
-
-        // Send SOF only
-        break;
-    case RFAL_PICOPASS_CMD_ACT: // No args
-        if(nfcv_data->frame_length != 1 || ctx->state != PicopassEmulatorStateActive) {
-            return;
-        }
-
-        // Send SOF only
-        break;
-    case RFAL_PICOPASS_CMD_HALT: // No args
-        if(nfcv_data->frame_length != 1 || ctx->state != PicopassEmulatorStateSelected) {
-            return;
-        }
-
-        // Technically we should go to StateHalt, but since we can't detect the field dropping we drop to idle instead
-        ctx->state = PicopassEmulatorStateIdle;
-
-        // Send SOF only
-        break;
-    case RFAL_PICOPASS_CMD_READ_OR_IDENTIFY:
-        if(nfcv_data->frame_length == 1 &&
-           ctx->state == PicopassEmulatorStateActive) { // PICOPASS_CMD_IDENTIFY
-            // ASNB(8) CRC16(2)
-            picopass_anticoll_csn(response, nfc_data->uid);
-            picopass_append_crc(response, RFAL_PICOPASS_BLOCK_LEN);
-            response_length = RFAL_PICOPASS_BLOCK_LEN + 2;
-            break;
-        } else if(
-            nfcv_data->frame_length == 4 &&
-            ctx->state == PicopassEmulatorStateSelected) { // PICOPASS_CMD_READ ADDRESS(1) CRC16(2)
-            if(nfcv_data->frame[1] >= PICOPASS_MAX_APP_LIMIT) {
-                return;
-            }
-
-            // TODO: Check CRC?
-            // TODO: Check auth?
-
-            // DATA(8) CRC16(2)
-            if(nfcv_data->frame[1] == PICOPASS_SECURE_KD_BLOCK_INDEX ||
-               nfcv_data->frame[1] == PICOPASS_SECURE_KC_BLOCK_INDEX) {
-                // Reading Kd or Kc blocks always returns FF's
-                memcpy(response, block_ff, RFAL_PICOPASS_BLOCK_LEN);
-            } else {
-                picopass_emu_read_blocks(nfcv_data, response, nfcv_data->frame[1], 1);
-            }
-            picopass_append_crc(response, RFAL_PICOPASS_BLOCK_LEN);
-            response_length = RFAL_PICOPASS_BLOCK_LEN + 2;
-            break;
-        }
-
-        return;
-    case RFAL_PICOPASS_CMD_READ4: // ADDRESS(1) CRC16(2)
-        if(nfcv_data->frame_length != 4 || ctx->state != PicopassEmulatorStateSelected ||
-           nfcv_data->frame[1] + 4 >= PICOPASS_MAX_APP_LIMIT) {
-            return;
-        }
-
-        // TODO: Check CRC?
-        // TODO: Check auth?
-
-        uint8_t blockNum = nfcv_data->frame[1];
-
-        // DATA(32) CRC16(2)
-        picopass_emu_read_blocks(nfcv_data, response, blockNum, 4);
-        if(blockNum == 4) {
-            // Kc is block 4, so just redact first block of response
-            memcpy(response, block_ff, RFAL_PICOPASS_BLOCK_LEN);
-        } else if(blockNum < 4) {
-            // Kd is block 3
-            uint8_t* kdOffset = response + ((3 - blockNum) * RFAL_PICOPASS_BLOCK_LEN);
-            memcpy(kdOffset, block_ff, RFAL_PICOPASS_BLOCK_LEN);
-            if(blockNum != 0) {
-                // Redact Kc
-                memcpy(kdOffset + RFAL_PICOPASS_BLOCK_LEN, block_ff, RFAL_PICOPASS_BLOCK_LEN);
-            }
-        }
-        picopass_append_crc(response, RFAL_PICOPASS_BLOCK_LEN * 4);
-        response_length = (RFAL_PICOPASS_BLOCK_LEN * 4) + 2;
-        break;
-    case RFAL_PICOPASS_CMD_SELECT: // ASNB(8)|SERIALNB(8)
-        if(nfcv_data->frame_length != 9) {
-            return;
-        }
-
-        uint8_t select_csn[RFAL_PICOPASS_BLOCK_LEN];
-        if(ctx->state == PicopassEmulatorStateHalt || ctx->state == PicopassEmulatorStateIdle) {
-            memcpy(select_csn, nfc_data->uid, RFAL_PICOPASS_BLOCK_LEN);
-        } else {
-            picopass_anticoll_csn(select_csn, nfc_data->uid);
-        }
-
-        if(memcmp(nfcv_data->frame + 1, select_csn, RFAL_PICOPASS_BLOCK_LEN)) {
-            if(ctx->state == PicopassEmulatorStateActive) {
-                ctx->state = PicopassEmulatorStateIdle;
-            } else if(ctx->state == PicopassEmulatorStateSelected) {
-                // Technically we should go to StateHalt, but since we can't detect the field dropping we drop to idle instead
-                ctx->state = PicopassEmulatorStateIdle;
-            }
-
-            return;
-        }
-
-        ctx->state = PicopassEmulatorStateSelected;
-
-        // SERIALNB(8) CRC16(2)
-        memcpy(response, nfc_data->uid, RFAL_PICOPASS_BLOCK_LEN);
-        picopass_append_crc(response, RFAL_PICOPASS_BLOCK_LEN);
-
-        response_length = RFAL_PICOPASS_BLOCK_LEN + 2;
-        break;
-    case RFAL_PICOPASS_CMD_READCHECK_KC: // ADDRESS(1)
-        key_block_num = PICOPASS_SECURE_KC_BLOCK_INDEX;
-        // fallthrough
-    case RFAL_PICOPASS_CMD_READCHECK_KD: // ADDRESS(1)
-        if(nfcv_data->frame_length != 2 ||
-           nfcv_data->frame[1] != PICOPASS_SECURE_EPURSE_BLOCK_INDEX ||
-           ctx->state != PicopassEmulatorStateSelected) {
-            return;
-        }
-
-        // loclass mode doesn't do any card side crypto, just logs the readers crypto, so no-op in this mode
-        // we can also no-op if the key block is the same, CHECK re-inits if it failed already
-        if(ctx->key_block_num != key_block_num && !ctx->loclass_mode) {
-            ctx->key_block_num = key_block_num;
-            picopass_init_cipher_state(nfcv_data, ctx);
-        }
-
-        // DATA(8)
-        picopass_emu_read_blocks(nfcv_data, response, nfcv_data->frame[1], 1);
-        response_length = RFAL_PICOPASS_BLOCK_LEN;
-        break;
-    case RFAL_PICOPASS_CMD_CHECK: // CHALLENGE(4) READERSIGNATURE(4)
-        if(nfcv_data->frame_length != 9 || ctx->state != PicopassEmulatorStateSelected) {
-            return;
-        }
-
-        if(ctx->loclass_mode) {
-            // LOCLASS Reader attack mode
-
-            // Copy EPURSE
-            uint8_t cc[RFAL_PICOPASS_BLOCK_LEN];
-            picopass_emu_read_blocks(nfcv_data, cc, PICOPASS_SECURE_EPURSE_BLOCK_INDEX, 1);
-
-#ifndef PICOPASS_DEBUG_IGNORE_LOCLASS_STD_KEY
-            uint8_t key[RFAL_PICOPASS_BLOCK_LEN];
-            // loclass mode stores the derived standard debit key in Kd to check
-            picopass_emu_read_blocks(nfcv_data, key, PICOPASS_SECURE_KD_BLOCK_INDEX, 1);
-
-            uint8_t rmac[4];
-            loclass_opt_doReaderMAC_2(ctx->cipher_state, nfcv_data->frame + 1, rmac, key);
-
-            if(!memcmp(nfcv_data->frame + 5, rmac, 4)) {
-                // MAC from reader matches Standard Key, keyroll mode or non-elite keyed reader.
-                // Either way no point logging it.
-
-                FURI_LOG_W(TAG, "loclass: standard key detected during collection");
-                ctx->loclass_got_std_key = true;
-
-                // Don't reset the state as the reader may try a different key next without going through anticoll
-                // The reader is always free to redo the anticoll if it wants to anyway
-
-                return;
-            }
-#endif
-
-            // Save to buffer to defer flushing when we rotate CSN
-            memcpy(
-                ctx->loclass_mac_buffer + ((ctx->key_block_num % LOCLASS_NUM_PER_CSN) * 8),
-                nfcv_data->frame + 1,
-                8);
-
-            // Rotate to the next CSN/attempt
-            ctx->key_block_num++;
-
-            // CSN changed
-            if(ctx->key_block_num % LOCLASS_NUM_PER_CSN == 0) {
-                // Flush NR-MACs for this CSN to SD card
-                uint8_t cc[RFAL_PICOPASS_BLOCK_LEN];
-                picopass_emu_read_blocks(nfcv_data, cc, PICOPASS_SECURE_EPURSE_BLOCK_INDEX, 1);
-
-                for(int i = 0; i < LOCLASS_NUM_PER_CSN; i++) {
-                    loclass_writer_write_params(
-                        ctx->loclass_writer,
-                        ctx->key_block_num + i - LOCLASS_NUM_PER_CSN,
-                        nfc_data->uid,
-                        cc,
-                        ctx->loclass_mac_buffer + (i * 8),
-                        ctx->loclass_mac_buffer + (i * 8) + 4);
-                }
-
-                if(ctx->key_block_num < LOCLASS_NUM_CSNS * LOCLASS_NUM_PER_CSN) {
-                    loclass_update_csn(nfc_data, nfcv_data, ctx);
-                    // Only reset the state when we change to a new CSN for the same reason as when we get a standard key
-                    ctx->state = PicopassEmulatorStateIdle;
-                } else {
-                    ctx->state = PicopassEmulatorStateStopEmulation;
-                }
-            }
-
-            return;
-        }
-
-        uint8_t key[RFAL_PICOPASS_BLOCK_LEN];
-        picopass_emu_read_blocks(nfcv_data, key, ctx->key_block_num, 1);
-
-        uint8_t rmac[4];
-        loclass_opt_doBothMAC_2(ctx->cipher_state, nfcv_data->frame + 1, rmac, response, key);
-
-#ifndef PICOPASS_DEBUG_IGNORE_BAD_RMAC
-        if(memcmp(nfcv_data->frame + 5, rmac, 4)) {
-            // Bad MAC from reader, do not send a response.
-            FURI_LOG_I(TAG, "Got bad MAC from reader");
-            // Reset the cipher state since we don't do it in READCHECK
-            picopass_init_cipher_state(nfcv_data, ctx);
-            return;
-        }
-#endif
-
-        // CHIPRESPONSE(4)
-        response_length = 4;
-        break;
-    case RFAL_PICOPASS_CMD_UPDATE: // ADDRESS(1) DATA(8) SIGN(4)|CRC16(2)
-        if((nfcv_data->frame_length != 12 && nfcv_data->frame_length != 14) ||
-           ctx->state != PicopassEmulatorStateSelected || ctx->loclass_mode) {
-            return;
-        }
-
-        if(nfcv_data->frame[1] >= PICOPASS_MAX_APP_LIMIT) {
-            return;
-        }
-
-        uint8_t cfgBlock[RFAL_PICOPASS_BLOCK_LEN];
-        picopass_emu_read_blocks(nfcv_data, cfgBlock, PICOPASS_CONFIG_BLOCK_INDEX, 1);
-        bool persMode = HAS_MASK(cfgBlock[7], PICOPASS_FUSE_PERS);
-
-        if((nfcv_data->frame[1] == PICOPASS_CSN_BLOCK_INDEX) // CSN is always read only
-           ||
-           (!persMode &&
-            !HAS_MASK(cfgBlock[3], 0x80)) // Chip is in RO mode, no updated possible (even ePurse)
-           || (!persMode &&
-               nfcv_data->frame[1] ==
-                   PICOPASS_SECURE_AIA_BLOCK_INDEX) // AIA can only be set in personalisation mode
-           || (!persMode &&
-               (nfcv_data->frame[1] == PICOPASS_SECURE_KD_BLOCK_INDEX ||
-                nfcv_data->frame[1] == PICOPASS_SECURE_KC_BLOCK_INDEX) &&
-               (!HAS_MASK(cfgBlock[7], PICOPASS_FUSE_CRYPT10)))) {
-            return; // TODO: Is this the right response?
-        }
-
-        if(nfcv_data->frame[1] >= 6 && nfcv_data->frame[1] <= 12) {
-            if(!HAS_MASK(
-                   cfgBlock[3],
-                   1 << (nfcv_data->frame[1] - 6))) { // bit0 is block6, up to bit6 being block12
-                // Block is marked as read-only, deny writing
-                return; // TODO: Is this the right response?
-            }
-        }
-
-        // TODO: Check CRC/SIGN depending on if in secure mode
-        // Check correct key
-        // -> Kd only allows decrementing e-Purse
-        // -> per-app controlled by key access config
-        //bool keyAccess = HAS_MASK(cfgBlock[5], 0x01);
-        // -> must auth with that key to change it
-
-        uint8_t blockOffset = nfcv_data->frame[1];
-        uint8_t block[RFAL_PICOPASS_BLOCK_LEN];
-        switch(nfcv_data->frame[1]) {
-        case PICOPASS_CONFIG_BLOCK_INDEX:
-            block[0] = cfgBlock[0]; // Applications Limit
-            block[1] = cfgBlock[1] & nfcv_data->frame[3]; // OTP
-            block[2] = cfgBlock[2] & nfcv_data->frame[4]; // OTP
-            block[3] = cfgBlock[3] & nfcv_data->frame[5]; // Block Write Lock
-            block[4] = cfgBlock[4]; // Chip Config
-            block[5] = cfgBlock[5]; // Memory Config
-            block[6] = nfcv_data->frame[8]; // EAS
-            block[7] = cfgBlock[7]; // Fuses
-
-            // Some parts allow w (but not e) if in persMode
-            if(persMode) {
-                block[0] &= nfcv_data->frame[2]; // Applications Limit
-                block[4] &= nfcv_data->frame[6]; // Chip Config
-                block[5] &= nfcv_data->frame[7]; // Memory Config
-                block[7] &= nfcv_data->frame[9]; // Fuses
-            } else {
-                // Fuses allows setting Crypt1/0 from 1 to 0 only during application mode
-                block[7] &= nfcv_data->frame[9] | ~PICOPASS_FUSE_CRYPT10;
-            }
-            break;
-        case PICOPASS_SECURE_EPURSE_BLOCK_INDEX:
-            // ePurse updates swap first and second half of the block each update
-            memcpy(block + 4, nfcv_data->frame + 2, 4);
-            memcpy(block, nfcv_data->frame + 6, 4);
-            break;
-        case PICOPASS_SECURE_KD_BLOCK_INDEX:
-            // fallthrough
-        case PICOPASS_SECURE_KC_BLOCK_INDEX:
-            if(!persMode) {
-                picopass_emu_read_blocks(nfcv_data, block, blockOffset, 1);
-                for(uint8_t i = 0; i < sizeof(RFAL_PICOPASS_BLOCK_LEN); i++)
-                    block[i] ^= nfcv_data->frame[i + 2];
-                break;
-            }
-            // Use default case when in personalisation mode
-            // fallthrough
-        default:
-            memcpy(block, nfcv_data->frame + 2, RFAL_PICOPASS_BLOCK_LEN);
-            break;
-        }
-
-        picopass_emu_write_blocks(nfcv_data, block, blockOffset, 1);
-
-        if((nfcv_data->frame[1] == ctx->key_block_num ||
-            nfcv_data->frame[1] == PICOPASS_SECURE_EPURSE_BLOCK_INDEX) &&
-           !ctx->loclass_mode)
-            picopass_init_cipher_state(nfcv_data, ctx);
-
-        // DATA(8) CRC16(2)
-        if(nfcv_data->frame[1] == PICOPASS_SECURE_KD_BLOCK_INDEX ||
-           nfcv_data->frame[1] == PICOPASS_SECURE_KD_BLOCK_INDEX) {
-            // Key updates always return FF's
-            memcpy(response, block_ff, RFAL_PICOPASS_BLOCK_LEN);
-        } else {
-            memcpy(response, block, RFAL_PICOPASS_BLOCK_LEN);
-        }
-        picopass_append_crc(response, RFAL_PICOPASS_BLOCK_LEN);
-        response_length = RFAL_PICOPASS_BLOCK_LEN + 2;
-        break;
-    case RFAL_PICOPASS_CMD_PAGESEL: // PAGE(1) CRC16(2)
-        // Chips with a single page do not answer to this command
-        // BLOCK1(8) CRC16(2)
-        return;
-    case RFAL_PICOPASS_CMD_DETECT:
-        // TODO - not used by iClass though
-        return;
-    default:
-        return;
-    }
-
-    NfcVSendFlags flags = NfcVSendFlagsSof | NfcVSendFlagsOneSubcarrier | NfcVSendFlagsHighRate;
-    if(response_length > 0) {
-        flags |= NfcVSendFlagsEof;
-    }
-
-    nfcv_emu_send(
-        tx_rx,
-        nfcv_data,
-        response,
-        response_length,
-        flags,
-        nfcv_data->eof_timestamp + NFCV_FDT_FC(4000)); // 3650 is ~254uS 4000 is ~283uS
-}
-
-void picopass_worker_emulate(PicopassWorker* picopass_worker, bool loclass_mode) {
-    furi_hal_nfc_exit_sleep();
-
-    FuriHalNfcTxRxContext tx_rx = {};
-    PicopassEmulatorCtx emu_ctx = {
-        .state = PicopassEmulatorStateIdle,
-        .key_block_num = PICOPASS_SECURE_KD_BLOCK_INDEX,
-        .loclass_mode = loclass_mode,
-        .loclass_got_std_key = false,
-        .loclass_writer = NULL,
-    };
-    FuriHalNfcDevData nfc_data = {
-        .uid_len = RFAL_PICOPASS_UID_LEN,
-    };
-    NfcVData* nfcv_data = malloc(sizeof(NfcVData));
-    nfcv_data->block_size = RFAL_PICOPASS_BLOCK_LEN;
-    nfcv_data->emu_protocol_ctx = &emu_ctx;
-    nfcv_data->emu_protocol_handler = &picopass_emu_handle_packet;
-
-    PicopassDeviceData* dev_data = picopass_worker->dev_data;
-    PicopassBlock* blocks = dev_data->AA1;
-
-    if(loclass_mode) {
-        emu_ctx.loclass_writer = loclass_writer_alloc();
-        if(emu_ctx.loclass_writer == NULL) {
-            picopass_worker->callback(
-                PicopassWorkerEventLoclassFileError, picopass_worker->context);
-
-            while(picopass_worker->state == PicopassWorkerStateEmulate ||
-                  picopass_worker->state == PicopassWorkerStateLoclass) {
-                furi_delay_ms(1);
-            }
-
-            free(nfcv_data);
-
-            return;
-        }
-
-        // Setup blocks for loclass attack
-        uint8_t conf[8] = {0x12, 0xFF, 0xFF, 0xFF, 0x7F, 0x1F, 0xFF, 0x3C};
-        picopass_emu_write_blocks(nfcv_data, conf, PICOPASS_CONFIG_BLOCK_INDEX, 1);
-
-        uint8_t epurse[8] = {0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
-        picopass_emu_write_blocks(nfcv_data, epurse, PICOPASS_SECURE_EPURSE_BLOCK_INDEX, 1);
-
-        uint8_t aia[8] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
-        picopass_emu_write_blocks(nfcv_data, aia, PICOPASS_SECURE_AIA_BLOCK_INDEX, 1);
-
-        emu_ctx.key_block_num = 0;
-        loclass_update_csn(&nfc_data, nfcv_data, &emu_ctx);
-
-        loclass_writer_write_start_stop(emu_ctx.loclass_writer, true);
-    } else {
-        memcpy(nfc_data.uid, blocks[PICOPASS_CSN_BLOCK_INDEX].data, RFAL_PICOPASS_BLOCK_LEN);
-        memcpy(nfcv_data->data, blocks, sizeof(dev_data->AA1));
-        picopass_init_cipher_state(nfcv_data, &emu_ctx);
-    }
-
-    uint8_t last_loclass_csn_num = 0;
-    bool loclass_got_std_key = false;
-
-    nfcv_emu_init(&nfc_data, nfcv_data);
-    while(picopass_worker->state == PicopassWorkerStateEmulate ||
-          picopass_worker->state == PicopassWorkerStateLoclass) {
-        if(nfcv_emu_loop(&tx_rx, &nfc_data, nfcv_data, 500)) {
-            if(picopass_worker->callback) {
-                if((loclass_mode) && (last_loclass_csn_num != emu_ctx.key_block_num)) {
-                    last_loclass_csn_num = emu_ctx.key_block_num;
-                    picopass_worker->callback(
-                        PicopassWorkerEventLoclassGotMac, picopass_worker->context);
-                } else if((loclass_mode) && !loclass_got_std_key && emu_ctx.loclass_got_std_key) {
-                    loclass_got_std_key = true;
-                    picopass_worker->callback(
-                        PicopassWorkerEventLoclassGotStandardKey, picopass_worker->context);
-                } else {
-                    picopass_worker->callback(
-                        PicopassWorkerEventSuccess, picopass_worker->context);
-                }
-            }
-        }
-        furi_delay_us(1);
-    }
-
-    if(emu_ctx.loclass_writer) {
-        loclass_writer_write_start_stop(emu_ctx.loclass_writer, false);
-        loclass_writer_free(emu_ctx.loclass_writer);
-    }
-
-    nfcv_emu_deinit(nfcv_data);
-    free(nfcv_data);
-}

+ 0 - 57
picopass_worker.h

@@ -1,57 +0,0 @@
-#pragma once
-
-#include "picopass_device.h"
-#include "picopass_keys.h"
-
-typedef struct PicopassWorker PicopassWorker;
-
-typedef enum {
-    // Init states
-    PicopassWorkerStateNone,
-    PicopassWorkerStateBroken,
-    PicopassWorkerStateReady,
-    // Main worker states
-    PicopassWorkerStateDetect,
-    PicopassWorkerStateWrite,
-    PicopassWorkerStateWriteKey,
-    PicopassWorkerStateEliteDictAttack,
-    PicopassWorkerStateEmulate,
-    PicopassWorkerStateLoclass,
-    // Transition
-    PicopassWorkerStateStop,
-} PicopassWorkerState;
-
-typedef enum {
-    // Reserve first 50 events for application events
-    PicopassWorkerEventReserved = 50,
-
-    // Picopass worker common events
-    PicopassWorkerEventSuccess,
-    PicopassWorkerEventFail,
-    PicopassWorkerEventNoCardDetected,
-    PicopassWorkerEventSeEnabled,
-    PicopassWorkerEventAborted,
-    PicopassWorkerEventCardDetected,
-    PicopassWorkerEventNewDictKeyBatch,
-    PicopassWorkerEventNoDictFound,
-    PicopassWorkerEventLoclassGotMac,
-    PicopassWorkerEventLoclassGotStandardKey,
-    PicopassWorkerEventLoclassFileError,
-} PicopassWorkerEvent;
-
-typedef void (*PicopassWorkerCallback)(PicopassWorkerEvent event, void* context);
-
-PicopassWorker* picopass_worker_alloc();
-
-PicopassWorkerState picopass_worker_get_state(PicopassWorker* picopass_worker);
-
-void picopass_worker_free(PicopassWorker* picopass_worker);
-
-void picopass_worker_start(
-    PicopassWorker* picopass_worker,
-    PicopassWorkerState state,
-    PicopassDeviceData* dev_data,
-    PicopassWorkerCallback callback,
-    void* context);
-
-void picopass_worker_stop(PicopassWorker* picopass_worker);

+ 0 - 36
picopass_worker_i.h

@@ -1,36 +0,0 @@
-#pragma once
-
-#include "picopass_worker.h"
-#include "loclass_writer.h"
-#include "picopass_i.h"
-
-#include <furi.h>
-#include <lib/toolbox/stream/file_stream.h>
-
-#include <furi_hal.h>
-
-#include <stdlib.h>
-#include <rfal_rf.h>
-
-#include <platform.h>
-
-struct PicopassWorker {
-    FuriThread* thread;
-    Storage* storage;
-    Stream* dict_stream;
-
-    PicopassDeviceData* dev_data;
-    PicopassWorkerCallback callback;
-    void* context;
-
-    PicopassWorkerState state;
-};
-
-void picopass_worker_change_state(PicopassWorker* picopass_worker, PicopassWorkerState state);
-
-int32_t picopass_worker_task(void* context);
-
-void picopass_worker_detect(PicopassWorker* picopass_worker);
-void picopass_worker_write(PicopassWorker* picopass_worker);
-void picopass_worker_write_key(PicopassWorker* picopass_worker);
-void picopass_worker_emulate(PicopassWorker* picopass_worker, bool loclass_mode);

+ 667 - 0
protocol/picopass_listener.c

@@ -0,0 +1,667 @@
+#include "picopass_listener_i.h"
+#include "picopass_keys.h"
+
+#include <furi/furi.h>
+
+#define PICOPASS_LISTENER_HAS_MASK(x, b) ((x & b) == b)
+
+typedef enum {
+    PicopassListenerCommandProcessed,
+    PicopassListenerCommandSilent,
+    PicopassListenerCommandSendSoF,
+    PicopassListenerCommandStop,
+} PicopassListenerCommand;
+
+typedef PicopassListenerCommand (
+    *PicopassListenerCommandHandler)(PicopassListener* instance, BitBuffer* buf);
+
+typedef struct {
+    uint8_t start_byte_cmd;
+    size_t cmd_len_bits;
+    PicopassListenerCommandHandler handler;
+} PicopassListenerCmd;
+
+// CSNs from Proxmark3 repo
+static const uint8_t loclass_csns[LOCLASS_NUM_CSNS][PICOPASS_BLOCK_LEN] = {
+    {0x01, 0x0A, 0x0F, 0xFF, 0xF7, 0xFF, 0x12, 0xE0},
+    {0x0C, 0x06, 0x0C, 0xFE, 0xF7, 0xFF, 0x12, 0xE0},
+    {0x10, 0x97, 0x83, 0x7B, 0xF7, 0xFF, 0x12, 0xE0},
+    {0x13, 0x97, 0x82, 0x7A, 0xF7, 0xFF, 0x12, 0xE0},
+    {0x07, 0x0E, 0x0D, 0xF9, 0xF7, 0xFF, 0x12, 0xE0},
+    {0x14, 0x96, 0x84, 0x76, 0xF7, 0xFF, 0x12, 0xE0},
+    {0x17, 0x96, 0x85, 0x71, 0xF7, 0xFF, 0x12, 0xE0},
+    {0xCE, 0xC5, 0x0F, 0x77, 0xF7, 0xFF, 0x12, 0xE0},
+    {0xD2, 0x5A, 0x82, 0xF8, 0xF7, 0xFF, 0x12, 0xE0},
+};
+
+static void picopass_listener_reset(PicopassListener* instance) {
+    instance->state = PicopassListenerStateIdle;
+}
+
+static void picopass_listener_loclass_update_csn(PicopassListener* instance) {
+    // collect LOCLASS_NUM_PER_CSN nonces in a row for each CSN
+    const uint8_t* csn =
+        loclass_csns[(instance->key_block_num / LOCLASS_NUM_PER_CSN) % LOCLASS_NUM_CSNS];
+    memcpy(instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data, csn, sizeof(PicopassBlock));
+
+    uint8_t key[PICOPASS_BLOCK_LEN] = {};
+    loclass_iclass_calc_div_key(csn, picopass_iclass_key, key, false);
+    memcpy(instance->data->AA1[PICOPASS_SECURE_KD_BLOCK_INDEX].data, key, sizeof(PicopassBlock));
+
+    picopass_listener_init_cipher_state_key(instance, key);
+}
+
+PicopassListenerCommand
+    picopass_listener_actall_handler(PicopassListener* instance, BitBuffer* buf) {
+    UNUSED(buf);
+
+    if(instance->state != PicopassListenerStateHalt) {
+        instance->state = PicopassListenerStateActive;
+    }
+    // nfc_set_fdt_listen_fc(instance->nfc, 1000);
+
+    return PicopassListenerCommandSendSoF;
+}
+
+PicopassListenerCommand picopass_listener_act_handler(PicopassListener* instance, BitBuffer* buf) {
+    UNUSED(buf);
+
+    PicopassListenerCommand command = PicopassListenerCommandSendSoF;
+
+    if(instance->state != PicopassListenerStateActive) {
+        command = PicopassListenerCommandSilent;
+    }
+
+    return command;
+}
+
+PicopassListenerCommand
+    picopass_listener_halt_handler(PicopassListener* instance, BitBuffer* buf) {
+    UNUSED(buf);
+
+    PicopassListenerCommand command = PicopassListenerCommandSendSoF;
+
+    // Technically we should go to StateHalt, but since we can't detect the field dropping we drop to idle instead
+    instance->state = PicopassListenerStateIdle;
+
+    return command;
+}
+
+PicopassListenerCommand
+    picopass_listener_identify_handler(PicopassListener* instance, BitBuffer* buf) {
+    UNUSED(buf);
+
+    PicopassListenerCommand command = PicopassListenerCommandSilent;
+
+    do {
+        if(instance->state != PicopassListenerStateActive) break;
+        picopass_listener_write_anticoll_csn(instance, instance->tx_buffer);
+        PicopassError error = picopass_listener_send_frame(instance, instance->tx_buffer);
+        if(error != PicopassErrorNone) {
+            FURI_LOG_D(TAG, "Error sending CSN: %d", error);
+            break;
+        }
+
+        command = PicopassListenerCommandProcessed;
+    } while(false);
+
+    return command;
+}
+
+PicopassListenerCommand
+    picopass_listener_select_handler(PicopassListener* instance, BitBuffer* buf) {
+    PicopassListenerCommand command = PicopassListenerCommandSilent;
+
+    do {
+        if((instance->state == PicopassListenerStateHalt) ||
+           (instance->state == PicopassListenerStateIdle)) {
+            bit_buffer_copy_bytes(
+                instance->tmp_buffer,
+                instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data,
+                sizeof(PicopassBlock));
+        } else {
+            picopass_listener_write_anticoll_csn(instance, instance->tmp_buffer);
+        }
+        const uint8_t* listener_uid = bit_buffer_get_data(instance->tmp_buffer);
+        const uint8_t* received_data = bit_buffer_get_data(buf);
+
+        if(memcmp(listener_uid, &received_data[1], PICOPASS_BLOCK_LEN) != 0) {
+            if(instance->state == PicopassListenerStateActive) {
+                instance->state = PicopassListenerStateIdle;
+            } else if(instance->state == PicopassListenerStateSelected) {
+                // Technically we should go to StateHalt, but since we can't detect the field dropping we drop to idle instead
+                instance->state = PicopassListenerStateIdle;
+            }
+            break;
+        }
+
+        instance->state = PicopassListenerStateSelected;
+        bit_buffer_copy_bytes(
+            instance->tx_buffer,
+            instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data,
+            sizeof(PicopassBlock));
+
+        PicopassError error = picopass_listener_send_frame(instance, instance->tx_buffer);
+        if(error != PicopassErrorNone) {
+            FURI_LOG_D(TAG, "Error sending select response: %d", error);
+            break;
+        }
+
+        command = PicopassListenerCommandProcessed;
+    } while(false);
+
+    return command;
+}
+
+PicopassListenerCommand
+    picopass_listener_read_handler(PicopassListener* instance, BitBuffer* buf) {
+    PicopassListenerCommand command = PicopassListenerCommandSilent;
+
+    do {
+        uint8_t block_num = bit_buffer_get_byte(buf, 1);
+        if(block_num > PICOPASS_MAX_APP_LIMIT) break;
+
+        bit_buffer_reset(instance->tx_buffer);
+        if((block_num == PICOPASS_SECURE_KD_BLOCK_INDEX) ||
+           (block_num == PICOPASS_SECURE_KC_BLOCK_INDEX)) {
+            for(size_t i = 0; i < PICOPASS_BLOCK_LEN; i++) {
+                bit_buffer_append_byte(instance->tx_buffer, 0xff);
+            }
+        } else {
+            bit_buffer_copy_bytes(
+                instance->tx_buffer, instance->data->AA1[block_num].data, sizeof(PicopassBlock));
+        }
+        PicopassError error = picopass_listener_send_frame(instance, instance->tx_buffer);
+        if(error != PicopassErrorNone) {
+            FURI_LOG_D(TAG, "Failed to tx read block response: %d", error);
+            break;
+        }
+
+        command = PicopassListenerCommandProcessed;
+    } while(false);
+
+    return command;
+}
+
+static PicopassListenerCommand
+    picopass_listener_readcheck(PicopassListener* instance, BitBuffer* buf, uint8_t key_block_num) {
+    PicopassListenerCommand command = PicopassListenerCommandSilent;
+
+    do {
+        if(instance->state != PicopassListenerStateSelected) break;
+        uint8_t block_num = bit_buffer_get_byte(buf, 1);
+        if(block_num != PICOPASS_SECURE_EPURSE_BLOCK_INDEX) break;
+
+        // loclass mode doesn't do any card side crypto, just logs the readers crypto, so no-op in this mode
+        // we can also no-op if the key block is the same, CHECK re-inits if it failed already
+        if((instance->key_block_num != key_block_num) &&
+           (instance->mode != PicopassListenerModeLoclass)) {
+            instance->key_block_num = key_block_num;
+            picopass_listener_init_cipher_state(instance);
+        }
+
+        // DATA(8)
+        bit_buffer_copy_bytes(
+            instance->tx_buffer, instance->data->AA1[block_num].data, sizeof(PicopassBlock));
+        NfcError error = nfc_listener_tx(instance->nfc, instance->tx_buffer);
+        if(error != NfcErrorNone) {
+            FURI_LOG_D(TAG, "Failed to tx read check response: %d", error);
+            break;
+        }
+
+        command = PicopassListenerCommandProcessed;
+    } while(false);
+
+    return command;
+}
+
+PicopassListenerCommand
+    picopass_listener_readcheck_kd_handler(PicopassListener* instance, BitBuffer* buf) {
+    return picopass_listener_readcheck(instance, buf, PICOPASS_SECURE_KD_BLOCK_INDEX);
+}
+
+PicopassListenerCommand
+    picopass_listener_readcheck_kc_handler(PicopassListener* instance, BitBuffer* buf) {
+    return picopass_listener_readcheck(instance, buf, PICOPASS_SECURE_KC_BLOCK_INDEX);
+}
+
+PicopassListenerCommand
+    picopass_listener_check_handler_loclass(PicopassListener* instance, BitBuffer* buf) {
+    PicopassListenerCommand command = PicopassListenerCommandSilent;
+    NfcCommand callback_command = NfcCommandContinue;
+
+    // LOCLASS Reader attack mode
+    do {
+#ifndef PICOPASS_DEBUG_IGNORE_LOCLASS_STD_KEY
+        // loclass mode stores the derived standard debit key in Kd to check
+
+        PicopassBlock key = instance->data->AA1[PICOPASS_SECURE_KD_BLOCK_INDEX];
+        uint8_t rmac[4];
+        uint8_t rx_data[9] = {};
+        bit_buffer_write_bytes(buf, rx_data, sizeof(rx_data));
+        loclass_opt_doReaderMAC_2(instance->cipher_state, &rx_data[1], rmac, key.data);
+
+        if(!memcmp(&rx_data[5], rmac, 4)) {
+            // MAC from reader matches Standard Key, keyroll mode or non-elite keyed reader.
+            // Either way no point logging it.
+
+            FURI_LOG_W(TAG, "loclass: standard key detected during collection");
+            if(instance->callback) {
+                instance->event.type = PicopassListenerEventTypeLoclassGotStandardKey;
+                callback_command = instance->callback(instance->event, instance->context);
+                if(callback_command == NfcCommandStop) {
+                    command = PicopassListenerCommandStop;
+                }
+            }
+
+            // Don't reset the state as the reader may try a different key next without going through anticoll
+            // The reader is always free to redo the anticoll if it wants to anyway
+
+            break;
+        }
+#endif
+
+        // Save to buffer to defer flushing when we rotate CSN
+        memcpy(
+            instance->loclass_mac_buffer + ((instance->key_block_num % LOCLASS_NUM_PER_CSN) * 8),
+            &rx_data[1],
+            8);
+
+        // Rotate to the next CSN/attempt
+        instance->key_block_num++;
+
+        // CSN changed
+        if(instance->key_block_num % LOCLASS_NUM_PER_CSN == 0) {
+            // Flush NR-MACs for this CSN to SD card
+            for(int i = 0; i < LOCLASS_NUM_PER_CSN; i++) {
+                loclass_writer_write_params(
+                    instance->writer,
+                    instance->key_block_num + i - LOCLASS_NUM_PER_CSN,
+                    instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data,
+                    instance->data->AA1[PICOPASS_SECURE_EPURSE_BLOCK_INDEX].data,
+                    instance->loclass_mac_buffer + (i * 8),
+                    instance->loclass_mac_buffer + (i * 8) + 4);
+            }
+
+            if(instance->key_block_num < LOCLASS_NUM_CSNS * LOCLASS_NUM_PER_CSN) {
+                picopass_listener_loclass_update_csn(instance);
+                // Only reset the state when we change to a new CSN for the same reason as when we get a standard key
+                instance->state = PicopassListenerStateIdle;
+            }
+        }
+        if(instance->callback) {
+            instance->event.type = PicopassListenerEventTypeLoclassGotMac;
+            instance->callback(instance->event, instance->context);
+        }
+
+    } while(false);
+
+    return command;
+}
+
+PicopassListenerCommand
+    picopass_listener_check_handler_emulation(PicopassListener* instance, BitBuffer* buf) {
+    PicopassListenerCommand command = PicopassListenerCommandSilent;
+
+    do {
+        uint8_t rmac[4] = {};
+        uint8_t tmac[4] = {};
+        const uint8_t* key = instance->data->AA1[instance->key_block_num].data;
+        // Since nr isn't const in loclass_opt_doBothMAC_2() copy buffer
+        uint8_t rx_data[9] = {};
+        bit_buffer_write_bytes(buf, rx_data, sizeof(rx_data));
+        loclass_opt_doBothMAC_2(instance->cipher_state, &rx_data[1], rmac, tmac, key);
+
+#ifndef PICOPASS_DEBUG_IGNORE_BAD_RMAC
+        if(memcmp(&rx_data[5], rmac, 4)) {
+            // Bad MAC from reader, do not send a response.
+            FURI_LOG_I(TAG, "Got bad MAC from reader");
+            // Reset the cipher state since we don't do it in READCHECK
+            picopass_listener_init_cipher_state(instance);
+            break;
+        }
+#endif
+
+        bit_buffer_copy_bytes(instance->tx_buffer, tmac, sizeof(tmac));
+        NfcError error = nfc_listener_tx(instance->nfc, instance->tx_buffer);
+        if(error != NfcErrorNone) {
+            FURI_LOG_D(TAG, "Failed tx update response: %d", error);
+            break;
+        }
+
+        command = PicopassListenerCommandProcessed;
+    } while(false);
+
+    return command;
+}
+PicopassListenerCommand
+    picopass_listener_check_handler(PicopassListener* instance, BitBuffer* buf) {
+    PicopassListenerCommand command = PicopassListenerCommandSilent;
+
+    do {
+        if(instance->state != PicopassListenerStateSelected) break;
+        if(instance->mode == PicopassListenerModeLoclass) {
+            command = picopass_listener_check_handler_loclass(instance, buf);
+        } else {
+            command = picopass_listener_check_handler_emulation(instance, buf);
+        }
+    } while(false);
+
+    return command;
+}
+
+PicopassListenerCommand
+    picopass_listener_update_handler(PicopassListener* instance, BitBuffer* buf) {
+    PicopassListenerCommand command = PicopassListenerCommandSilent;
+
+    do {
+        if(instance->mode == PicopassListenerModeLoclass) break;
+        if(instance->state != PicopassListenerStateSelected) break;
+
+        PicopassBlock config_block = instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX];
+        bool pers_mode = PICOPASS_LISTENER_HAS_MASK(config_block.data[7], PICOPASS_FUSE_PERS);
+
+        const uint8_t* rx_data = bit_buffer_get_data(buf);
+        uint8_t block_num = rx_data[1];
+        if(block_num == PICOPASS_CSN_BLOCK_INDEX) break; // CSN is always read only
+        if(!pers_mode && PICOPASS_LISTENER_HAS_MASK(config_block.data[3], 0x80))
+            break; // Chip is in RO mode, no updated possible (even ePurse)
+        if(!pers_mode && (block_num == PICOPASS_SECURE_AIA_BLOCK_INDEX))
+            break; // AIA can only be set in personalisation mode
+        if(!pers_mode &&
+           ((block_num == PICOPASS_SECURE_KD_BLOCK_INDEX ||
+             block_num == PICOPASS_SECURE_KC_BLOCK_INDEX) &&
+            (!PICOPASS_LISTENER_HAS_MASK(config_block.data[7], PICOPASS_FUSE_CRYPT10))))
+            break;
+
+        if(block_num >= 6 && block_num <= 12) {
+            // bit0 is block6, up to bit6 being block12
+            if(!PICOPASS_LISTENER_HAS_MASK(config_block.data[3], (1 << (block_num - 6)))) {
+                // Block is marked as read-only, deny writing
+                break;
+            }
+        }
+        // TODO: Check CRC/SIGN depending on if in secure mode
+        // Check correct key
+        // -> Kd only allows decrementing e-Purse
+        // -> per-app controlled by key access config
+        // bool keyAccess = PICOPASS_LISTENER_HAS_MASK(config_block.data[5], 0x01);
+        // -> must auth with that key to change it
+
+        PicopassBlock new_block = {};
+        switch(block_num) {
+        case PICOPASS_CONFIG_BLOCK_INDEX:
+            new_block.data[0] = config_block.data[0]; // Applications Limit
+            new_block.data[1] = config_block.data[1] & rx_data[3]; // OTP
+            new_block.data[2] = config_block.data[2] & rx_data[4]; // OTP
+            new_block.data[3] = config_block.data[3] & rx_data[5]; // Block Write Lock
+            new_block.data[4] = config_block.data[4]; // Chip Config
+            new_block.data[5] = config_block.data[5]; // Memory Config
+            new_block.data[6] = rx_data[8]; // EAS
+            new_block.data[7] = config_block.data[7]; // Fuses
+
+            // Some parts allow w (but not e) if in persMode
+            if(pers_mode) {
+                new_block.data[0] &= rx_data[2]; // Applications Limit
+                new_block.data[4] &= rx_data[6]; // Chip Config
+                new_block.data[5] &= rx_data[7]; // Memory Config
+                new_block.data[7] &= rx_data[9]; // Fuses
+            } else {
+                // Fuses allows setting Crypt1/0 from 1 to 0 only during application mode
+                new_block.data[7] &= rx_data[9] | ~PICOPASS_FUSE_CRYPT10;
+            }
+            break;
+
+        case PICOPASS_SECURE_EPURSE_BLOCK_INDEX:
+            // ePurse updates swap first and second half of the block each update
+            memcpy(&new_block.data[4], &rx_data[2], 4);
+            memcpy(&new_block.data[0], &rx_data[6], 4);
+            break;
+
+        case PICOPASS_SECURE_KD_BLOCK_INDEX:
+            // fallthrough
+        case PICOPASS_SECURE_KC_BLOCK_INDEX:
+            if(!pers_mode) {
+                new_block = instance->data->AA1[block_num];
+                for(size_t i = 0; i < sizeof(PicopassBlock); i++) {
+                    new_block.data[i] ^= rx_data[i + 2];
+                }
+                break;
+            }
+            // Use default case when in personalisation mode
+            // fallthrough
+        default:
+            memcpy(new_block.data, &rx_data[2], sizeof(PicopassBlock));
+            break;
+        }
+
+        instance->data->AA1[block_num] = new_block;
+        if((block_num == instance->key_block_num) ||
+           (block_num == PICOPASS_SECURE_EPURSE_BLOCK_INDEX)) {
+            picopass_listener_init_cipher_state(instance);
+        }
+
+        bit_buffer_reset(instance->tx_buffer);
+        if((block_num == PICOPASS_SECURE_KD_BLOCK_INDEX) ||
+           (block_num == PICOPASS_SECURE_KC_BLOCK_INDEX)) {
+            // Key updates always return FF's
+            for(size_t i = 0; i < PICOPASS_BLOCK_LEN; i++) {
+                bit_buffer_append_byte(instance->tx_buffer, 0xff);
+            }
+        } else {
+            bit_buffer_copy_bytes(
+                instance->tx_buffer, instance->data->AA1[block_num].data, sizeof(PicopassBlock));
+        }
+
+        PicopassError error = picopass_listener_send_frame(instance, instance->tx_buffer);
+        if(error != PicopassErrorNone) {
+            FURI_LOG_D(TAG, "Failed to tx update response: %d", error);
+            break;
+        }
+
+        command = PicopassListenerCommandProcessed;
+    } while(false);
+
+    return command;
+}
+
+PicopassListenerCommand
+    picopass_listener_read4_handler(PicopassListener* instance, BitBuffer* buf) {
+    PicopassListenerCommand command = PicopassListenerCommandSilent;
+
+    do {
+        if(instance->state != PicopassListenerStateSelected) break;
+
+        uint8_t block_start = bit_buffer_get_byte(buf, 1);
+        if(block_start + 4 >= PICOPASS_MAX_APP_LIMIT) break;
+
+        // TODO: Check CRC?
+        // TODO: Check auth?
+
+        bit_buffer_reset(instance->tx_buffer);
+        for(uint8_t i = block_start; i < block_start + 4; i++) {
+            if((i == PICOPASS_SECURE_KD_BLOCK_INDEX) || (i == PICOPASS_SECURE_KC_BLOCK_INDEX)) {
+                for(size_t j = 0; j < sizeof(PicopassBlock); j++) {
+                    bit_buffer_append_byte(instance->tx_buffer, 0xff);
+                }
+            } else {
+                bit_buffer_append_bytes(
+                    instance->tx_buffer, instance->data->AA1[i].data, sizeof(PicopassBlock));
+            }
+        }
+
+        PicopassError error = picopass_listener_send_frame(instance, instance->tx_buffer);
+        if(error != PicopassErrorNone) {
+            FURI_LOG_D(TAG, "Failed to tx read4 response: %d", error);
+            break;
+        }
+
+        command = PicopassListenerCommandProcessed;
+    } while(false);
+
+    return command;
+}
+
+static const PicopassListenerCmd picopass_listener_cmd_handlers[] = {
+    {
+        .start_byte_cmd = RFAL_PICOPASS_CMD_ACTALL,
+        .cmd_len_bits = 8,
+        .handler = picopass_listener_actall_handler,
+    },
+    {
+        .start_byte_cmd = RFAL_PICOPASS_CMD_ACT,
+        .cmd_len_bits = 8,
+        .handler = picopass_listener_act_handler,
+    },
+    {
+        .start_byte_cmd = RFAL_PICOPASS_CMD_HALT,
+        .cmd_len_bits = 8,
+        .handler = picopass_listener_halt_handler,
+    },
+    {
+        .start_byte_cmd = RFAL_PICOPASS_CMD_READ_OR_IDENTIFY,
+        .cmd_len_bits = 8,
+        .handler = picopass_listener_identify_handler,
+    },
+    {
+        .start_byte_cmd = RFAL_PICOPASS_CMD_SELECT,
+        .cmd_len_bits = 8 * 9,
+        .handler = picopass_listener_select_handler,
+    },
+    {
+        .start_byte_cmd = RFAL_PICOPASS_CMD_READ_OR_IDENTIFY,
+        .cmd_len_bits = 8 * 4,
+        .handler = picopass_listener_read_handler,
+    },
+    {
+        .start_byte_cmd = RFAL_PICOPASS_CMD_READCHECK_KD,
+        .cmd_len_bits = 8 * 2,
+        .handler = picopass_listener_readcheck_kd_handler,
+    },
+    {
+        .start_byte_cmd = RFAL_PICOPASS_CMD_READCHECK_KC,
+        .cmd_len_bits = 8 * 2,
+        .handler = picopass_listener_readcheck_kc_handler,
+    },
+    {
+        .start_byte_cmd = RFAL_PICOPASS_CMD_CHECK,
+        .cmd_len_bits = 8 * 9,
+        .handler = picopass_listener_check_handler,
+    },
+    {
+        .start_byte_cmd = RFAL_PICOPASS_CMD_UPDATE,
+        .cmd_len_bits = 8 * 14,
+        .handler = picopass_listener_update_handler,
+    },
+    {
+        .start_byte_cmd = RFAL_PICOPASS_CMD_READ4,
+        .cmd_len_bits = 8 * 4,
+        .handler = picopass_listener_read4_handler,
+    },
+};
+
+PicopassListener* picopass_listener_alloc(Nfc* nfc, const PicopassDeviceData* data) {
+    furi_assert(nfc);
+    furi_assert(data);
+
+    PicopassListener* instance = malloc(sizeof(PicopassListener));
+    instance->nfc = nfc;
+    instance->data = malloc(sizeof(PicopassDeviceData));
+    mempcpy(instance->data, data, sizeof(PicopassDeviceData));
+
+    instance->tx_buffer = bit_buffer_alloc(PICOPASS_LISTENER_BUFFER_SIZE_MAX);
+    instance->tmp_buffer = bit_buffer_alloc(PICOPASS_LISTENER_BUFFER_SIZE_MAX);
+
+    nfc_set_fdt_listen_fc(instance->nfc, PICOPASS_FDT_LISTEN_FC);
+    nfc_config(instance->nfc, NfcModeListener, NfcTechIso15693);
+
+    return instance;
+}
+
+void picopass_listener_free(PicopassListener* instance) {
+    furi_assert(instance);
+
+    bit_buffer_free(instance->tx_buffer);
+    bit_buffer_free(instance->tmp_buffer);
+    free(instance->data);
+    if(instance->writer) {
+        loclass_writer_write_start_stop(instance->writer, false);
+        loclass_writer_free(instance->writer);
+    }
+    free(instance);
+}
+
+bool picopass_listener_set_mode(PicopassListener* instance, PicopassListenerMode mode) {
+    furi_assert(instance);
+    bool success = true;
+
+    instance->mode = mode;
+    if(instance->mode == PicopassListenerModeLoclass) {
+        instance->key_block_num = 0;
+        picopass_listener_loclass_update_csn(instance);
+        instance->writer = loclass_writer_alloc();
+        if(instance->writer) {
+            loclass_writer_write_start_stop(instance->writer, true);
+        } else {
+            success = false;
+        }
+    }
+
+    return success;
+}
+
+NfcCommand picopass_listener_start_callback(NfcEvent event, void* context) {
+    furi_assert(context);
+
+    NfcCommand command = NfcCommandContinue;
+    PicopassListener* instance = context;
+    BitBuffer* rx_buf = event.data.buffer;
+
+    PicopassListenerCommand picopass_cmd = PicopassListenerCommandSilent;
+    if(event.type == NfcEventTypeRxEnd) {
+        for(size_t i = 0; i < COUNT_OF(picopass_listener_cmd_handlers); i++) {
+            if(bit_buffer_get_size(rx_buf) != picopass_listener_cmd_handlers[i].cmd_len_bits) {
+                continue;
+            }
+            if(bit_buffer_get_byte(rx_buf, 0) !=
+               picopass_listener_cmd_handlers[i].start_byte_cmd) {
+                continue;
+            }
+            picopass_cmd = picopass_listener_cmd_handlers[i].handler(instance, rx_buf);
+            break;
+        }
+        if(picopass_cmd == PicopassListenerCommandSendSoF) {
+            nfc_iso15693_listener_tx_sof(instance->nfc);
+        } else if(picopass_cmd == PicopassListenerCommandStop) {
+            command = NfcCommandStop;
+        }
+    }
+
+    return command;
+}
+
+void picopass_listener_start(
+    PicopassListener* instance,
+    PicopassListenerCallback callback,
+    void* context) {
+    furi_assert(instance);
+    furi_assert(callback);
+
+    instance->callback = callback;
+    instance->context = context;
+
+    picopass_listener_reset(instance);
+    nfc_start(instance->nfc, picopass_listener_start_callback, instance);
+}
+
+void picopass_listener_stop(PicopassListener* instance) {
+    furi_assert(instance);
+
+    nfc_stop(instance->nfc);
+}
+
+const PicopassDeviceData* picopass_listener_get_data(PicopassListener* instance) {
+    furi_assert(instance);
+
+    return instance->data;
+}

+ 45 - 0
protocol/picopass_listener.h

@@ -0,0 +1,45 @@
+#pragma once
+
+#include <nfc/nfc.h>
+#include "picopass_protocol.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum {
+    PicopassListenerModeEmulation,
+    PicopassListenerModeLoclass,
+} PicopassListenerMode;
+
+typedef enum {
+    PicopassListenerEventTypeLoclassGotStandardKey,
+    PicopassListenerEventTypeLoclassGotMac,
+} PicopassListenerEventType;
+
+typedef struct {
+    PicopassListenerEventType type;
+} PicopassListenerEvent;
+
+typedef NfcCommand (*PicopassListenerCallback)(PicopassListenerEvent event, void* context);
+
+typedef struct PicopassListener PicopassListener;
+
+PicopassListener* picopass_listener_alloc(Nfc* nfc, const PicopassDeviceData* data);
+
+void picopass_listener_free(PicopassListener* instance);
+
+bool picopass_listener_set_mode(PicopassListener* instance, PicopassListenerMode mode);
+
+void picopass_listener_start(
+    PicopassListener* instance,
+    PicopassListenerCallback callback,
+    void* context);
+
+void picopass_listener_stop(PicopassListener* instance);
+
+const PicopassDeviceData* picopass_listener_get_data(PicopassListener* instance);
+
+#ifdef __cplusplus
+}
+#endif

+ 52 - 0
protocol/picopass_listener_i.c

@@ -0,0 +1,52 @@
+#include "picopass_listener_i.h"
+
+#include <furi/furi.h>
+
+static PicopassError picopass_listener_process_error(NfcError error) {
+    PicopassError ret = PicopassErrorNone;
+
+    switch(error) {
+    case NfcErrorNone:
+        ret = PicopassErrorNone;
+        break;
+
+    default:
+        ret = PicopassErrorTimeout;
+        break;
+    }
+
+    return ret;
+}
+
+void picopass_listener_init_cipher_state_key(PicopassListener* instance, const uint8_t* key) {
+    uint8_t cc[PICOPASS_BLOCK_LEN] = {};
+    memcpy(
+        cc, instance->data->AA1[PICOPASS_SECURE_EPURSE_BLOCK_INDEX].data, sizeof(PicopassBlock));
+
+    instance->cipher_state = loclass_opt_doTagMAC_1(cc, key);
+}
+
+void picopass_listener_init_cipher_state(PicopassListener* instance) {
+    uint8_t key[PICOPASS_BLOCK_LEN] = {};
+    memcpy(key, instance->data->AA1[instance->key_block_num].data, sizeof(PicopassBlock));
+
+    picopass_listener_init_cipher_state_key(instance, key);
+}
+
+PicopassError picopass_listener_send_frame(PicopassListener* instance, BitBuffer* tx_buffer) {
+    iso13239_crc_append(Iso13239CrcTypePicopass, tx_buffer);
+    NfcError error = nfc_listener_tx(instance->nfc, tx_buffer);
+
+    return picopass_listener_process_error(error);
+}
+
+// from proxmark3 armsrc/iclass.c rotateCSN
+PicopassError picopass_listener_write_anticoll_csn(PicopassListener* instance, BitBuffer* buffer) {
+    const uint8_t* uid = instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data;
+    bit_buffer_reset(buffer);
+    for(size_t i = 0; i < PICOPASS_BLOCK_LEN; i++) {
+        bit_buffer_append_byte(buffer, (uid[i] >> 3) | (uid[(i + 1) % 8] << 5));
+    }
+
+    return PicopassErrorNone;
+}

+ 47 - 0
protocol/picopass_listener_i.h

@@ -0,0 +1,47 @@
+#pragma once
+
+#include "picopass_listener.h"
+#include <nfc/helpers/iso13239_crc.h>
+
+#include <optimized_ikeys.h>
+#include <optimized_cipher.h>
+#include <loclass_writer.h>
+
+#define TAG "PicopassListener"
+
+#define PICOPASS_LISTENER_BUFFER_SIZE_MAX (255)
+
+typedef enum {
+    PicopassListenerStateIdle,
+    PicopassListenerStateHalt,
+    PicopassListenerStateActive,
+    PicopassListenerStateSelected,
+} PicopassListenerState;
+
+struct PicopassListener {
+    Nfc* nfc;
+    PicopassDeviceData* data;
+    PicopassListenerState state;
+
+    LoclassState_t cipher_state;
+    PicopassListenerMode mode;
+
+    BitBuffer* tx_buffer;
+    BitBuffer* tmp_buffer;
+    uint8_t key_block_num;
+
+    LoclassWriter* writer;
+    uint8_t loclass_mac_buffer[8 * LOCLASS_NUM_PER_CSN];
+
+    PicopassListenerEvent event;
+    PicopassListenerCallback callback;
+    void* context;
+};
+
+void picopass_listener_init_cipher_state_key(PicopassListener* instance, const uint8_t* key);
+
+void picopass_listener_init_cipher_state(PicopassListener* instance);
+
+PicopassError picopass_listener_send_frame(PicopassListener* instance, BitBuffer* tx_buffer);
+
+PicopassError picopass_listener_write_anticoll_csn(PicopassListener* instance, BitBuffer* buffer);

+ 543 - 0
protocol/picopass_poller.c

@@ -0,0 +1,543 @@
+#include "picopass_poller_i.h"
+
+#include "../loclass/optimized_cipher.h"
+
+#include <furi/furi.h>
+
+#define TAG "Picopass"
+
+typedef NfcCommand (*PicopassPollerStateHandler)(PicopassPoller* instance);
+
+static void picopass_poller_reset(PicopassPoller* instance) {
+    instance->current_block = 0;
+}
+
+static void picopass_poller_prepare_read(PicopassPoller* instance) {
+    instance->app_limit = instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[0] <
+                                  PICOPASS_MAX_APP_LIMIT ?
+                              instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[0] :
+                              PICOPASS_MAX_APP_LIMIT;
+    instance->current_block = 2;
+}
+
+NfcCommand picopass_poller_request_mode_handler(PicopassPoller* instance) {
+    NfcCommand command = NfcCommandContinue;
+
+    instance->event.type = PicopassPollerEventTypeRequestMode;
+    command = instance->callback(instance->event, instance->context);
+    instance->mode = instance->event_data.req_mode.mode;
+    instance->state = PicopassPollerStateDetect;
+
+    return command;
+}
+
+NfcCommand picopass_poller_detect_handler(PicopassPoller* instance) {
+    NfcCommand command = NfcCommandContinue;
+    PicopassError error = picopass_poller_actall(instance);
+
+    if(error == PicopassErrorNone) {
+        instance->state = PicopassPollerStateSelect;
+        instance->event.type = PicopassPollerEventTypeCardDetected;
+        command = instance->callback(instance->event, instance->context);
+    } else {
+        furi_delay_ms(100);
+    }
+
+    return command;
+}
+
+NfcCommand picopass_poller_select_handler(PicopassPoller* instance) {
+    NfcCommand command = NfcCommandContinue;
+
+    do {
+        PicopassError error = picopass_poller_identify(instance, &instance->col_res_serial_num);
+        if(error != PicopassErrorNone) {
+            instance->state = PicopassPollerStateFail;
+            break;
+        }
+
+        error =
+            picopass_poller_select(instance, &instance->col_res_serial_num, &instance->serial_num);
+        if(error != PicopassErrorNone) {
+            instance->state = PicopassPollerStateFail;
+            break;
+        }
+
+        if(instance->mode == PicopassPollerModeRead) {
+            instance->state = PicopassPollerStatePreAuth;
+        } else {
+            instance->state = PicopassPollerStateAuth;
+        }
+    } while(false);
+
+    return command;
+}
+
+NfcCommand picopass_poller_pre_auth_handler(PicopassPoller* instance) {
+    NfcCommand command = NfcCommandContinue;
+    PicopassError error = PicopassErrorNone;
+
+    do {
+        memcpy(
+            instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data,
+            instance->serial_num.data,
+            sizeof(PicopassSerialNum));
+        FURI_LOG_D(
+            TAG,
+            "csn %02x%02x%02x%02x%02x%02x%02x%02x",
+            instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data[0],
+            instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data[1],
+            instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data[2],
+            instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data[3],
+            instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data[4],
+            instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data[5],
+            instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data[6],
+            instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data[7]);
+
+        PicopassBlock block = {};
+        error = picopass_poller_read_block(instance, 1, &block);
+        if(error != PicopassErrorNone) {
+            instance->state = PicopassPollerStateFail;
+            break;
+        }
+        memcpy(
+            instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data,
+            block.data,
+            sizeof(PicopassBlock));
+        FURI_LOG_D(
+            TAG,
+            "config %02x%02x%02x%02x%02x%02x%02x%02x",
+            instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[0],
+            instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[1],
+            instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[2],
+            instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[3],
+            instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[4],
+            instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[5],
+            instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[6],
+            instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[7]);
+
+        error = picopass_poller_read_block(instance, 5, &block);
+        if(error != PicopassErrorNone) {
+            instance->state = PicopassPollerStateFail;
+            break;
+        }
+        memcpy(
+            instance->data->AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data,
+            block.data,
+            sizeof(PicopassBlock));
+        FURI_LOG_D(
+            TAG,
+            "aia %02x%02x%02x%02x%02x%02x%02x%02x",
+            instance->data->AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[0],
+            instance->data->AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[1],
+            instance->data->AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[2],
+            instance->data->AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[3],
+            instance->data->AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[4],
+            instance->data->AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[5],
+            instance->data->AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[6],
+            instance->data->AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[7]);
+
+        instance->state = PicopassPollerStateCheckSecurity;
+    } while(false);
+
+    return command;
+}
+
+NfcCommand picopass_poller_check_security(PicopassPoller* instance) {
+    NfcCommand command = NfcCommandContinue;
+
+    // Thank you proxmark!
+    PicopassBlock temp_block = {};
+    memset(temp_block.data, 0xff, sizeof(PicopassBlock));
+    instance->data->pacs.legacy =
+        (memcmp(
+             instance->data->AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data,
+             temp_block.data,
+             sizeof(PicopassBlock)) == 0);
+
+    temp_block.data[3] = 0x00;
+    temp_block.data[4] = 0x06;
+    instance->data->pacs.se_enabled =
+        (memcmp(
+             instance->data->AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data,
+             temp_block.data,
+             sizeof(PicopassBlock)) == 0);
+
+    if(instance->data->pacs.se_enabled) {
+        FURI_LOG_D(TAG, "SE enabled");
+        instance->state = PicopassPollerStateFail;
+    } else {
+        instance->state = PicopassPollerStateAuth;
+    }
+
+    return command;
+}
+
+NfcCommand picopass_poller_auth_handler(PicopassPoller* instance) {
+    NfcCommand command = NfcCommandContinue;
+
+    do {
+        // Request key
+        instance->event.type = PicopassPollerEventTypeRequestKey;
+        command = instance->callback(instance->event, instance->context);
+        if(command != NfcCommandContinue) break;
+
+        if(!instance->event_data.req_key.is_key_provided) {
+            instance->state = PicopassPollerStateFail;
+            break;
+        }
+
+        FURI_LOG_D(
+            TAG,
+            "Try to %s auth with key %02x%02x%02x%02x%02x%02x%02x%02x",
+            instance->event_data.req_key.is_elite_key ? "elite" : "standard",
+            instance->event_data.req_key.key[0],
+            instance->event_data.req_key.key[1],
+            instance->event_data.req_key.key[2],
+            instance->event_data.req_key.key[3],
+            instance->event_data.req_key.key[4],
+            instance->event_data.req_key.key[5],
+            instance->event_data.req_key.key[6],
+            instance->event_data.req_key.key[7]);
+
+        PicopassReadCheckResp read_check_resp = {};
+        uint8_t* csn = instance->serial_num.data;
+        memset(instance->div_key, 0, sizeof(instance->div_key));
+        uint8_t* div_key = NULL;
+
+        if(instance->mode == PicopassPollerModeRead) {
+            div_key = instance->data->AA1[PICOPASS_SECURE_KD_BLOCK_INDEX].data;
+        } else {
+            div_key = instance->div_key;
+        }
+
+        uint8_t ccnr[12] = {};
+        PicopassMac mac = {};
+
+        PicopassError error = picopass_poller_read_check(instance, &read_check_resp);
+        if(error == PicopassErrorTimeout) {
+            instance->event.type = PicopassPollerEventTypeCardLost;
+            command = instance->callback(instance->event, instance->context);
+            instance->state = PicopassPollerStateDetect;
+            break;
+        } else if(error != PicopassErrorNone) {
+            FURI_LOG_E(TAG, "Read check failed: %d", error);
+            break;
+        }
+        memcpy(ccnr, read_check_resp.data, sizeof(PicopassReadCheckResp)); // last 4 bytes left 0
+
+        loclass_iclass_calc_div_key(
+            csn,
+            instance->event_data.req_key.key,
+            div_key,
+            instance->event_data.req_key.is_elite_key);
+        loclass_opt_doReaderMAC(ccnr, div_key, mac.data);
+
+        PicopassCheckResp check_resp = {};
+        error = picopass_poller_check(instance, &mac, &check_resp);
+        if(error == PicopassErrorNone) {
+            FURI_LOG_I(TAG, "Found key");
+            memcpy(instance->mac.data, mac.data, sizeof(PicopassMac));
+            if(instance->mode == PicopassPollerModeRead) {
+                memcpy(
+                    instance->data->pacs.key, instance->event_data.req_key.key, PICOPASS_KEY_LEN);
+                instance->data->pacs.elite_kdf = instance->event_data.req_key.is_elite_key;
+                picopass_poller_prepare_read(instance);
+                instance->state = PicopassPollerStateReadBlock;
+            } else if(instance->mode == PicopassPollerModeWrite) {
+                instance->state = PicopassPollerStateWriteBlock;
+            } else {
+                instance->state = PicopassPollerStateWriteKey;
+            }
+        }
+
+    } while(false);
+
+    return command;
+}
+
+NfcCommand picopass_poller_read_block_handler(PicopassPoller* instance) {
+    NfcCommand command = NfcCommandContinue;
+
+    do {
+        if(instance->current_block == instance->app_limit) {
+            instance->state = PicopassPollerStateParseCredential;
+            break;
+        }
+
+        if(instance->current_block == PICOPASS_SECURE_KD_BLOCK_INDEX) {
+            // Skip over Kd block which is populated earlier (READ of Kd returns all FF's)
+            instance->current_block++;
+        }
+
+        PicopassBlock block = {};
+        PicopassError error =
+            picopass_poller_read_block(instance, instance->current_block, &block);
+        if(error != PicopassErrorNone) {
+            FURI_LOG_E(TAG, "Failed to read block %d: %d", instance->current_block, error);
+            instance->state = PicopassPollerStateFail;
+            break;
+        }
+        FURI_LOG_D(
+            TAG,
+            "Block %d: %02x%02x%02x%02x%02x%02x%02x%02x",
+            instance->current_block,
+            block.data[0],
+            block.data[1],
+            block.data[2],
+            block.data[3],
+            block.data[4],
+            block.data[5],
+            block.data[6],
+            block.data[7]);
+        memcpy(
+            instance->data->AA1[instance->current_block].data, block.data, sizeof(PicopassBlock));
+        instance->current_block++;
+    } while(false);
+
+    return command;
+}
+
+NfcCommand picopass_poller_parse_credential_handler(PicopassPoller* instance) {
+    NfcCommand command = NfcCommandContinue;
+
+    picopass_device_parse_credential(instance->data->AA1, &instance->data->pacs);
+    instance->state = PicopassPollerStateParseWiegand;
+    return command;
+}
+
+NfcCommand picopass_poller_parse_wiegand_handler(PicopassPoller* instance) {
+    NfcCommand command = NfcCommandContinue;
+
+    picopass_device_parse_wiegand(instance->data->pacs.credential, &instance->data->pacs);
+    instance->state = PicopassPollerStateSuccess;
+    return command;
+}
+
+NfcCommand picopass_poller_write_block_handler(PicopassPoller* instance) {
+    NfcCommand command = NfcCommandContinue;
+    PicopassError error = PicopassErrorNone;
+
+    do {
+        instance->event.type = PicopassPollerEventTypeRequestWriteBlock;
+        command = instance->callback(instance->event, instance->context);
+        if(command != NfcCommandContinue) break;
+
+        PicopassPollerEventDataRequestWriteBlock* write_block = &instance->event_data.req_write;
+        if(!write_block->perform_write) {
+            instance->state = PicopassPollerStateSuccess;
+            break;
+        }
+
+        FURI_LOG_D(TAG, "Writing %d block", write_block->block_num);
+        uint8_t data[9] = {};
+        data[0] = write_block->block_num;
+        memcpy(&data[1], write_block->block->data, PICOPASS_BLOCK_LEN);
+        loclass_doMAC_N(data, sizeof(data), instance->div_key, instance->mac.data);
+        FURI_LOG_D(
+            TAG,
+            "loclass_doMAC_N %d %02x%02x%02x%02x%02x%02x%02x%02x %02x%02x%02x%02x",
+            write_block->block_num,
+            data[1],
+            data[2],
+            data[3],
+            data[4],
+            data[5],
+            data[6],
+            data[7],
+            data[8],
+            instance->mac.data[0],
+            instance->mac.data[1],
+            instance->mac.data[2],
+            instance->mac.data[3]);
+        error = picopass_poller_write_block(
+            instance, write_block->block_num, write_block->block, &instance->mac);
+        if(error != PicopassErrorNone) {
+            FURI_LOG_E(TAG, "Failed to write block %d. Error %d", write_block->block_num, error);
+            instance->state = PicopassPollerStateFail;
+            break;
+        }
+
+    } while(false);
+
+    return command;
+}
+
+NfcCommand picopass_poller_write_key_handler(PicopassPoller* instance) {
+    NfcCommand command = NfcCommandContinue;
+    PicopassError error = PicopassErrorNone;
+
+    do {
+        instance->event.type = PicopassPollerEventTypeRequestWriteKey;
+        command = instance->callback(instance->event, instance->context);
+        if(command != NfcCommandContinue) break;
+
+        const PicopassDeviceData* picopass_data = instance->event_data.req_write_key.data;
+        const uint8_t* new_key = instance->event_data.req_write_key.key;
+        bool is_elite_key = instance->event_data.req_write_key.is_elite_key;
+
+        const uint8_t* csn = picopass_data->AA1[PICOPASS_CSN_BLOCK_INDEX].data;
+        const uint8_t* config_block = picopass_data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data;
+        uint8_t fuses = config_block[7];
+        const uint8_t* old_key = picopass_data->AA1[PICOPASS_SECURE_KD_BLOCK_INDEX].data;
+
+        PicopassBlock new_block = {};
+        loclass_iclass_calc_div_key(csn, new_key, new_block.data, is_elite_key);
+
+        if((fuses & 0x80) == 0x80) {
+            FURI_LOG_D(TAG, "Plain write for personalized mode key change");
+        } else {
+            FURI_LOG_D(TAG, "XOR write for application mode key change");
+            // XOR when in application mode
+            for(size_t i = 0; i < PICOPASS_BLOCK_LEN; i++) {
+                new_block.data[i] ^= old_key[i];
+            }
+        }
+
+        FURI_LOG_D(TAG, "Writing key to %d block", PICOPASS_SECURE_KD_BLOCK_INDEX);
+        uint8_t data[9] = {};
+        data[0] = PICOPASS_SECURE_KD_BLOCK_INDEX;
+        memcpy(&data[1], new_block.data, PICOPASS_BLOCK_LEN);
+        loclass_doMAC_N(data, sizeof(data), instance->div_key, instance->mac.data);
+        FURI_LOG_D(
+            TAG,
+            "loclass_doMAC_N %d %02x%02x%02x%02x%02x%02x%02x%02x %02x%02x%02x%02x",
+            PICOPASS_SECURE_KD_BLOCK_INDEX,
+            data[1],
+            data[2],
+            data[3],
+            data[4],
+            data[5],
+            data[6],
+            data[7],
+            data[8],
+            instance->mac.data[0],
+            instance->mac.data[1],
+            instance->mac.data[2],
+            instance->mac.data[3]);
+        error = picopass_poller_write_block(
+            instance, PICOPASS_SECURE_KD_BLOCK_INDEX, &new_block, &instance->mac);
+        if(error != PicopassErrorNone) {
+            FURI_LOG_E(
+                TAG, "Failed to write block %d. Error %d", PICOPASS_SECURE_KD_BLOCK_INDEX, error);
+            instance->state = PicopassPollerStateFail;
+            break;
+        }
+        instance->state = PicopassPollerStateSuccess;
+
+    } while(false);
+
+    return command;
+}
+
+NfcCommand picopass_poller_success_handler(PicopassPoller* instance) {
+    NfcCommand command = NfcCommandContinue;
+
+    instance->event.type = PicopassPollerEventTypeSuccess;
+    command = instance->callback(instance->event, instance->context);
+    furi_delay_ms(100);
+
+    return command;
+}
+
+NfcCommand picopass_poller_fail_handler(PicopassPoller* instance) {
+    NfcCommand command = NfcCommandReset;
+
+    instance->event.type = PicopassPollerEventTypeFail;
+    command = instance->callback(instance->event, instance->context);
+    picopass_poller_reset(instance);
+    instance->state = PicopassPollerStateDetect;
+
+    return command;
+}
+
+static const PicopassPollerStateHandler picopass_poller_state_handler[PicopassPollerStateNum] = {
+    [PicopassPollerStateRequestMode] = picopass_poller_request_mode_handler,
+    [PicopassPollerStateDetect] = picopass_poller_detect_handler,
+    [PicopassPollerStateSelect] = picopass_poller_select_handler,
+    [PicopassPollerStatePreAuth] = picopass_poller_pre_auth_handler,
+    [PicopassPollerStateCheckSecurity] = picopass_poller_check_security,
+    [PicopassPollerStateAuth] = picopass_poller_auth_handler,
+    [PicopassPollerStateReadBlock] = picopass_poller_read_block_handler,
+    [PicopassPollerStateWriteBlock] = picopass_poller_write_block_handler,
+    [PicopassPollerStateWriteKey] = picopass_poller_write_key_handler,
+    [PicopassPollerStateParseCredential] = picopass_poller_parse_credential_handler,
+    [PicopassPollerStateParseWiegand] = picopass_poller_parse_wiegand_handler,
+    [PicopassPollerStateSuccess] = picopass_poller_success_handler,
+    [PicopassPollerStateFail] = picopass_poller_fail_handler,
+};
+
+static NfcCommand picopass_poller_callback(NfcEvent event, void* context) {
+    furi_assert(context);
+
+    PicopassPoller* instance = context;
+    NfcCommand command = NfcCommandContinue;
+
+    if(event.type == NfcEventTypePollerReady) {
+        command = picopass_poller_state_handler[instance->state](instance);
+    }
+
+    if(instance->session_state == PicopassPollerSessionStateStopRequest) {
+        command = NfcCommandStop;
+    }
+
+    return command;
+}
+
+void picopass_poller_start(
+    PicopassPoller* instance,
+    PicopassPollerCallback callback,
+    void* context) {
+    furi_assert(instance);
+    furi_assert(instance->session_state == PicopassPollerSessionStateIdle);
+
+    instance->callback = callback;
+    instance->context = context;
+
+    instance->session_state = PicopassPollerSessionStateActive;
+    nfc_start(instance->nfc, picopass_poller_callback, instance);
+}
+
+void picopass_poller_stop(PicopassPoller* instance) {
+    furi_assert(instance);
+
+    instance->session_state = PicopassPollerSessionStateStopRequest;
+    nfc_stop(instance->nfc);
+    instance->session_state = PicopassPollerSessionStateIdle;
+}
+
+PicopassPoller* picopass_poller_alloc(Nfc* nfc) {
+    furi_assert(nfc);
+
+    PicopassPoller* instance = malloc(sizeof(PicopassPoller));
+    instance->nfc = nfc;
+    nfc_config(instance->nfc, NfcModePoller, NfcTechIso15693);
+    nfc_set_guard_time_us(instance->nfc, 10000);
+    nfc_set_fdt_poll_fc(instance->nfc, 5000);
+    nfc_set_fdt_poll_poll_us(instance->nfc, 1000);
+
+    instance->event.data = &instance->event_data;
+    instance->data = malloc(sizeof(PicopassDeviceData));
+
+    instance->tx_buffer = bit_buffer_alloc(PICOPASS_POLLER_BUFFER_SIZE);
+    instance->rx_buffer = bit_buffer_alloc(PICOPASS_POLLER_BUFFER_SIZE);
+    instance->tmp_buffer = bit_buffer_alloc(PICOPASS_POLLER_BUFFER_SIZE);
+
+    return instance;
+}
+
+void picopass_poller_free(PicopassPoller* instance) {
+    furi_assert(instance);
+
+    free(instance->data);
+    bit_buffer_free(instance->tx_buffer);
+    bit_buffer_free(instance->rx_buffer);
+    bit_buffer_free(instance->tmp_buffer);
+    free(instance);
+}
+
+const PicopassDeviceData* picopass_poller_get_data(PicopassPoller* instance) {
+    furi_assert(instance);
+
+    return instance->data;
+}

+ 80 - 0
protocol/picopass_poller.h

@@ -0,0 +1,80 @@
+#pragma once
+
+#include <nfc/nfc.h>
+#include "picopass_protocol.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum {
+    PicopassPollerEventTypeRequestMode,
+    PicopassPollerEventTypeCardDetected,
+    PicopassPollerEventTypeCardLost,
+    PicopassPollerEventTypeRequestKey,
+    PicopassPollerEventTypeRequestWriteBlock,
+    PicopassPollerEventTypeRequestWriteKey,
+    PicopassPollerEventTypeSuccess,
+    PicopassPollerEventTypeFail,
+} PicopassPollerEventType;
+
+typedef enum {
+    PicopassPollerModeRead,
+    PicopassPollerModeWrite,
+    PicopassPollerModeWriteKey,
+} PicopassPollerMode;
+
+typedef struct {
+    PicopassPollerMode mode;
+} PicopassPollerEventDataRequestMode;
+
+typedef struct {
+    uint8_t key[PICOPASS_KEY_LEN];
+    bool is_key_provided;
+    bool is_elite_key;
+} PicopassPollerEventDataRequestKey;
+
+typedef struct {
+    bool perform_write;
+    uint8_t block_num;
+    const PicopassBlock* block;
+} PicopassPollerEventDataRequestWriteBlock;
+
+typedef struct {
+    const PicopassDeviceData* data;
+    uint8_t key[PICOPASS_KEY_LEN];
+    bool is_elite_key;
+} PicopassPollerEventDataRequestWriteKey;
+
+typedef union {
+    PicopassPollerEventDataRequestMode req_mode;
+    PicopassPollerEventDataRequestKey req_key;
+    PicopassPollerEventDataRequestWriteBlock req_write;
+    PicopassPollerEventDataRequestWriteKey req_write_key;
+} PicopassPollerEventData;
+
+typedef struct {
+    PicopassPollerEventType type;
+    PicopassPollerEventData* data;
+} PicopassPollerEvent;
+
+typedef NfcCommand (*PicopassPollerCallback)(PicopassPollerEvent event, void* context);
+
+typedef struct PicopassPoller PicopassPoller;
+
+PicopassPoller* picopass_poller_alloc(Nfc* nfc);
+
+void picopass_poller_free(PicopassPoller* instance);
+
+void picopass_poller_start(
+    PicopassPoller* instance,
+    PicopassPollerCallback callback,
+    void* context);
+
+void picopass_poller_stop(PicopassPoller* instance);
+
+const PicopassDeviceData* picopass_poller_get_data(PicopassPoller* instance);
+
+#ifdef __cplusplus
+}
+#endif

+ 217 - 0
protocol/picopass_poller_i.c

@@ -0,0 +1,217 @@
+#include "picopass_poller_i.h"
+
+#include <nfc/helpers/iso14443_crc.h>
+
+#define PICOPASS_POLLER_FWT_FC (100000)
+
+#define TAG "Picopass"
+
+static PicopassError picopass_poller_process_error(NfcError error) {
+    PicopassError ret = PicopassErrorNone;
+
+    switch(error) {
+    case NfcErrorNone:
+        ret = PicopassErrorNone;
+        break;
+
+    default:
+        ret = PicopassErrorTimeout;
+        break;
+    }
+
+    return ret;
+}
+
+static PicopassError picopass_poller_send_frame(
+    PicopassPoller* instance,
+    BitBuffer* tx_buffer,
+    BitBuffer* rx_buffer,
+    uint32_t fwt_fc) {
+    PicopassError ret = PicopassErrorNone;
+
+    do {
+        NfcError error = nfc_poller_trx(instance->nfc, tx_buffer, rx_buffer, fwt_fc);
+        if(error != NfcErrorNone) {
+            ret = picopass_poller_process_error(error);
+            break;
+        }
+        if(!iso13239_crc_check(Iso13239CrcTypePicopass, rx_buffer)) {
+            ret = PicopassErrorIncorrectCrc;
+            break;
+        }
+        iso13239_crc_trim(instance->rx_buffer);
+    } while(false);
+
+    return ret;
+}
+
+PicopassError picopass_poller_actall(PicopassPoller* instance) {
+    PicopassError ret = PicopassErrorNone;
+
+    bit_buffer_reset(instance->tx_buffer);
+    bit_buffer_append_byte(instance->tx_buffer, RFAL_PICOPASS_CMD_ACTALL);
+
+    NfcError error = nfc_poller_trx(
+        instance->nfc, instance->tx_buffer, instance->rx_buffer, PICOPASS_POLLER_FWT_FC);
+    if(error != NfcErrorIncompleteFrame) {
+        ret = picopass_poller_process_error(error);
+    }
+
+    return ret;
+}
+
+PicopassError picopass_poller_identify(
+    PicopassPoller* instance,
+    PicopassColResSerialNum* col_res_serial_num) {
+    PicopassError ret = PicopassErrorNone;
+
+    do {
+        bit_buffer_reset(instance->tx_buffer);
+        bit_buffer_append_byte(instance->tx_buffer, RFAL_PICOPASS_CMD_READ_OR_IDENTIFY);
+        ret = picopass_poller_send_frame(
+            instance, instance->tx_buffer, instance->rx_buffer, PICOPASS_POLLER_FWT_FC);
+        if(ret != PicopassErrorNone) break;
+        if(bit_buffer_get_size_bytes(instance->rx_buffer) != sizeof(PicopassColResSerialNum)) {
+            ret = PicopassErrorProtocol;
+            break;
+        }
+        bit_buffer_write_bytes(
+            instance->rx_buffer, col_res_serial_num->data, sizeof(PicopassColResSerialNum));
+    } while(false);
+
+    return ret;
+}
+
+PicopassError picopass_poller_select(
+    PicopassPoller* instance,
+    PicopassColResSerialNum* col_res_serial_num,
+    PicopassSerialNum* serial_num) {
+    PicopassError ret = PicopassErrorNone;
+
+    do {
+        bit_buffer_reset(instance->tx_buffer);
+        bit_buffer_append_byte(instance->tx_buffer, RFAL_PICOPASS_CMD_SELECT);
+        bit_buffer_append_bytes(
+            instance->tx_buffer, col_res_serial_num->data, sizeof(PicopassColResSerialNum));
+        ret = picopass_poller_send_frame(
+            instance, instance->tx_buffer, instance->rx_buffer, PICOPASS_POLLER_FWT_FC);
+        if(ret != PicopassErrorNone) break;
+        if(bit_buffer_get_size_bytes(instance->rx_buffer) != sizeof(PicopassSerialNum)) {
+            ret = PicopassErrorProtocol;
+            break;
+        }
+        bit_buffer_write_bytes(instance->rx_buffer, serial_num->data, sizeof(PicopassSerialNum));
+    } while(false);
+
+    return ret;
+}
+
+PicopassError
+    picopass_poller_read_block(PicopassPoller* instance, uint8_t block_num, PicopassBlock* block) {
+    PicopassError ret = PicopassErrorNone;
+
+    do {
+        bit_buffer_reset(instance->tmp_buffer);
+        bit_buffer_append_byte(instance->tmp_buffer, block_num);
+        iso13239_crc_append(Iso13239CrcTypePicopass, instance->tmp_buffer);
+        bit_buffer_reset(instance->tx_buffer);
+        bit_buffer_append_byte(instance->tx_buffer, RFAL_PICOPASS_CMD_READ_OR_IDENTIFY);
+        bit_buffer_append(instance->tx_buffer, instance->tmp_buffer);
+
+        ret = picopass_poller_send_frame(
+            instance, instance->tx_buffer, instance->rx_buffer, PICOPASS_POLLER_FWT_FC);
+        if(ret != PicopassErrorNone) break;
+
+        if(bit_buffer_get_size_bytes(instance->rx_buffer) != sizeof(PicopassBlock)) {
+            ret = PicopassErrorProtocol;
+            break;
+        }
+        bit_buffer_write_bytes(instance->rx_buffer, block->data, sizeof(PicopassBlock));
+    } while(false);
+
+    return ret;
+}
+
+PicopassError
+    picopass_poller_read_check(PicopassPoller* instance, PicopassReadCheckResp* read_check_resp) {
+    PicopassError ret = PicopassErrorNone;
+
+    do {
+        bit_buffer_reset(instance->tx_buffer);
+        bit_buffer_append_byte(instance->tx_buffer, RFAL_PICOPASS_CMD_READCHECK_KD);
+        bit_buffer_append_byte(instance->tx_buffer, 0x02);
+
+        NfcError error = nfc_poller_trx(
+            instance->nfc, instance->tx_buffer, instance->rx_buffer, PICOPASS_POLLER_FWT_FC);
+        if(error != NfcErrorNone) {
+            ret = picopass_poller_process_error(error);
+            break;
+        }
+
+        if(bit_buffer_get_size_bytes(instance->rx_buffer) != sizeof(PicopassReadCheckResp)) {
+            ret = PicopassErrorProtocol;
+            break;
+        }
+        bit_buffer_write_bytes(
+            instance->rx_buffer, read_check_resp->data, sizeof(PicopassReadCheckResp));
+    } while(false);
+
+    return ret;
+}
+
+PicopassError picopass_poller_check(
+    PicopassPoller* instance,
+    PicopassMac* mac,
+    PicopassCheckResp* check_resp) {
+    PicopassError ret = PicopassErrorNone;
+
+    do {
+        bit_buffer_reset(instance->tx_buffer);
+        bit_buffer_append_byte(instance->tx_buffer, RFAL_PICOPASS_CMD_CHECK);
+        uint8_t null_arr[4] = {};
+        bit_buffer_append_bytes(instance->tx_buffer, null_arr, sizeof(null_arr));
+        bit_buffer_append_bytes(instance->tx_buffer, mac->data, sizeof(PicopassMac));
+
+        NfcError error = nfc_poller_trx(
+            instance->nfc, instance->tx_buffer, instance->rx_buffer, PICOPASS_POLLER_FWT_FC);
+        if(error != NfcErrorNone) {
+            ret = picopass_poller_process_error(error);
+            break;
+        }
+
+        if(bit_buffer_get_size_bytes(instance->rx_buffer) != sizeof(PicopassCheckResp)) {
+            ret = PicopassErrorProtocol;
+            break;
+        }
+        bit_buffer_write_bytes(instance->rx_buffer, check_resp->data, sizeof(PicopassCheckResp));
+
+    } while(false);
+
+    return ret;
+}
+
+PicopassError picopass_poller_write_block(
+    PicopassPoller* instance,
+    uint8_t block_num,
+    const PicopassBlock* block,
+    const PicopassMac* mac) {
+    PicopassError ret = PicopassErrorNone;
+
+    do {
+        bit_buffer_reset(instance->tx_buffer);
+        bit_buffer_append_byte(instance->tx_buffer, RFAL_PICOPASS_CMD_UPDATE);
+        bit_buffer_append_byte(instance->tx_buffer, block_num);
+        bit_buffer_append_bytes(instance->tx_buffer, block->data, sizeof(PicopassBlock));
+        bit_buffer_append_bytes(instance->tx_buffer, mac->data, sizeof(PicopassMac));
+
+        NfcError error = nfc_poller_trx(
+            instance->nfc, instance->tx_buffer, instance->rx_buffer, PICOPASS_POLLER_FWT_FC);
+        if(error != NfcErrorNone) {
+            ret = picopass_poller_process_error(error);
+            break;
+        }
+
+    } while(false);
+
+    return ret;
+}

+ 85 - 0
protocol/picopass_poller_i.h

@@ -0,0 +1,85 @@
+#pragma once
+
+#include "picopass_poller.h"
+#include "picopass_protocol.h"
+
+#include <nfc/helpers/iso13239_crc.h>
+
+#define PICOPASS_POLLER_BUFFER_SIZE (255)
+#define PICOPASS_CRC_SIZE (2)
+
+typedef enum {
+    PicopassPollerSessionStateIdle,
+    PicopassPollerSessionStateActive,
+    PicopassPollerSessionStateStopRequest,
+} PicopassPollerSessionState;
+
+typedef enum {
+    PicopassPollerStateRequestMode,
+    PicopassPollerStateDetect,
+    PicopassPollerStateSelect,
+    PicopassPollerStatePreAuth,
+    PicopassPollerStateCheckSecurity,
+    PicopassPollerStateAuth,
+    PicopassPollerStateReadBlock,
+    PicopassPollerStateWriteBlock,
+    PicopassPollerStateWriteKey,
+    PicopassPollerStateParseCredential,
+    PicopassPollerStateParseWiegand,
+    PicopassPollerStateSuccess,
+    PicopassPollerStateFail,
+
+    PicopassPollerStateNum,
+} PicopassPollerState;
+
+struct PicopassPoller {
+    Nfc* nfc;
+    PicopassPollerSessionState session_state;
+    PicopassPollerState state;
+    PicopassPollerMode mode;
+
+    PicopassColResSerialNum col_res_serial_num;
+    PicopassSerialNum serial_num;
+    PicopassMac mac;
+    uint8_t div_key[8];
+    uint8_t current_block;
+    uint8_t app_limit;
+
+    PicopassDeviceData* data;
+
+    BitBuffer* tx_buffer;
+    BitBuffer* rx_buffer;
+    BitBuffer* tmp_buffer;
+
+    PicopassPollerEvent event;
+    PicopassPollerEventData event_data;
+    PicopassPollerCallback callback;
+    void* context;
+};
+
+PicopassError picopass_poller_actall(PicopassPoller* instance);
+
+PicopassError
+    picopass_poller_identify(PicopassPoller* instance, PicopassColResSerialNum* col_res_serial_num);
+
+PicopassError picopass_poller_select(
+    PicopassPoller* instance,
+    PicopassColResSerialNum* col_res_serial_num,
+    PicopassSerialNum* serial_num);
+
+PicopassError
+    picopass_poller_read_block(PicopassPoller* instance, uint8_t block_num, PicopassBlock* block);
+
+PicopassError
+    picopass_poller_read_check(PicopassPoller* instance, PicopassReadCheckResp* read_check_resp);
+
+PicopassError picopass_poller_check(
+    PicopassPoller* instance,
+    PicopassMac* mac,
+    PicopassCheckResp* check_resp);
+
+PicopassError picopass_poller_write_block(
+    PicopassPoller* instance,
+    uint8_t block_num,
+    const PicopassBlock* block,
+    const PicopassMac* mac);

+ 48 - 0
protocol/picopass_protocol.h

@@ -0,0 +1,48 @@
+#pragma once
+
+#include "../picopass_device.h"
+
+#define PICOPASS_BLOCK_LEN 8
+#define PICOPASS_MAX_APP_LIMIT 32
+#define PICOPASS_UID_LEN 8
+#define PICOPASS_READ_CHECK_RESP_LEN 8
+#define PICOPASS_CHECK_RESP_LEN 4
+#define PICOPASS_MAC_LEN 4
+#define PICOPASS_KEY_LEN 8
+
+#define PICOPASS_FDT_LISTEN_FC (1000)
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum {
+    PicopassErrorNone,
+    PicopassErrorTimeout,
+    PicopassErrorIncorrectCrc,
+    PicopassErrorProtocol,
+} PicopassError;
+
+typedef struct {
+    uint8_t data[RFAL_PICOPASS_UID_LEN];
+} PicopassColResSerialNum;
+
+typedef struct {
+    uint8_t data[RFAL_PICOPASS_UID_LEN];
+} PicopassSerialNum;
+
+typedef struct {
+    uint8_t data[PICOPASS_READ_CHECK_RESP_LEN];
+} PicopassReadCheckResp;
+
+typedef struct {
+    uint8_t data[PICOPASS_CHECK_RESP_LEN];
+} PicopassCheckResp;
+
+typedef struct {
+    uint8_t data[PICOPASS_MAC_LEN];
+} PicopassMac;
+
+#ifdef __cplusplus
+}
+#endif

+ 0 - 213
rfal_picopass.c

@@ -1,213 +0,0 @@
-#include "rfal_picopass.h"
-
-#define RFAL_PICOPASS_TXRX_FLAGS                                                    \
-    (FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_TX_MANUAL | FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON | \
-     FURI_HAL_NFC_LL_TXRX_FLAGS_PAR_RX_REMV | FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_RX_KEEP)
-
-#define TAG "RFAL_PICOPASS"
-
-typedef struct {
-    uint8_t CMD;
-    uint8_t CSN[RFAL_PICOPASS_UID_LEN];
-} rfalPicoPassSelectReq;
-
-typedef struct {
-    uint8_t CMD;
-    uint8_t null[4];
-    uint8_t mac[4];
-} rfalPicoPassCheckReq;
-
-static uint16_t rfalPicoPassUpdateCcitt(uint16_t crcSeed, uint8_t dataByte) {
-    uint16_t crc = crcSeed;
-    uint8_t dat = dataByte;
-
-    dat ^= (uint8_t)(crc & 0xFFU);
-    dat ^= (dat << 4);
-
-    crc = (crc >> 8) ^ (((uint16_t)dat) << 8) ^ (((uint16_t)dat) << 3) ^ (((uint16_t)dat) >> 4);
-
-    return crc;
-}
-
-uint16_t rfalPicoPassCalculateCcitt(uint16_t preloadValue, const uint8_t* buf, uint16_t length) {
-    uint16_t crc = preloadValue;
-    uint16_t index;
-
-    for(index = 0; index < length; index++) {
-        crc = rfalPicoPassUpdateCcitt(crc, buf[index]);
-    }
-
-    return crc;
-}
-
-FuriHalNfcReturn rfalPicoPassPollerInitialize(void) {
-    FuriHalNfcReturn ret;
-
-    ret = furi_hal_nfc_ll_set_mode(
-        FuriHalNfcModePollPicopass, FuriHalNfcBitrate26p48, FuriHalNfcBitrate26p48);
-    if(ret != FuriHalNfcReturnOk) {
-        return ret;
-    };
-
-    furi_hal_nfc_ll_set_error_handling(FuriHalNfcErrorHandlingNfc);
-    furi_hal_nfc_ll_set_guard_time(FURI_HAL_NFC_LL_GT_PICOPASS);
-    furi_hal_nfc_ll_set_fdt_listen(FURI_HAL_NFC_LL_FDT_LISTEN_PICOPASS_POLLER);
-    furi_hal_nfc_ll_set_fdt_poll(FURI_HAL_NFC_LL_FDT_POLL_PICOPASS_POLLER);
-
-    return FuriHalNfcReturnOk;
-}
-
-FuriHalNfcReturn rfalPicoPassPollerCheckPresence(void) {
-    FuriHalNfcReturn ret;
-    uint8_t txBuf[1] = {RFAL_PICOPASS_CMD_ACTALL};
-    uint8_t rxBuf[32] = {0};
-    uint16_t recvLen = 0;
-    uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS;
-    uint32_t fwt = furi_hal_nfc_ll_ms2fc(20);
-
-    ret = furi_hal_nfc_ll_txrx(txBuf, 1, rxBuf, 32, &recvLen, flags, fwt);
-    return ret;
-}
-
-FuriHalNfcReturn rfalPicoPassPollerIdentify(rfalPicoPassIdentifyRes* idRes) {
-    FuriHalNfcReturn ret;
-
-    uint8_t txBuf[1] = {RFAL_PICOPASS_CMD_READ_OR_IDENTIFY};
-    uint16_t recvLen = 0;
-    uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS;
-    uint32_t fwt = furi_hal_nfc_ll_ms2fc(20);
-
-    ret = furi_hal_nfc_ll_txrx(
-        txBuf,
-        sizeof(txBuf),
-        (uint8_t*)idRes,
-        sizeof(rfalPicoPassIdentifyRes),
-        &recvLen,
-        flags,
-        fwt);
-    // printf("identify rx: %d %s\n", recvLen, hex2Str(idRes->CSN, RFAL_PICOPASS_UID_LEN));
-
-    return ret;
-}
-
-FuriHalNfcReturn rfalPicoPassPollerSelect(uint8_t* csn, rfalPicoPassSelectRes* selRes) {
-    FuriHalNfcReturn ret;
-
-    rfalPicoPassSelectReq selReq;
-    selReq.CMD = RFAL_PICOPASS_CMD_SELECT;
-    memcpy(selReq.CSN, csn, RFAL_PICOPASS_UID_LEN);
-    uint16_t recvLen = 0;
-    uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS;
-    uint32_t fwt = furi_hal_nfc_ll_ms2fc(20);
-
-    ret = furi_hal_nfc_ll_txrx(
-        (uint8_t*)&selReq,
-        sizeof(rfalPicoPassSelectReq),
-        (uint8_t*)selRes,
-        sizeof(rfalPicoPassSelectRes),
-        &recvLen,
-        flags,
-        fwt);
-    // printf("select rx: %d %s\n", recvLen, hex2Str(selRes->CSN, RFAL_PICOPASS_UID_LEN));
-    if(ret == FuriHalNfcReturnTimeout) {
-        return FuriHalNfcReturnOk;
-    }
-
-    return ret;
-}
-
-FuriHalNfcReturn rfalPicoPassPollerReadCheck(rfalPicoPassReadCheckRes* rcRes) {
-    FuriHalNfcReturn ret;
-    uint8_t txBuf[2] = {RFAL_PICOPASS_CMD_READCHECK_KD, 0x02};
-    uint16_t recvLen = 0;
-    uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS;
-    uint32_t fwt = furi_hal_nfc_ll_ms2fc(20);
-
-    ret = furi_hal_nfc_ll_txrx(
-        txBuf,
-        sizeof(txBuf),
-        (uint8_t*)rcRes,
-        sizeof(rfalPicoPassReadCheckRes),
-        &recvLen,
-        flags,
-        fwt);
-    // printf("readcheck rx: %d %s\n", recvLen, hex2Str(rcRes->CCNR, 8));
-
-    if(ret == FuriHalNfcReturnCrc) {
-        return FuriHalNfcReturnOk;
-    }
-
-    return ret;
-}
-
-FuriHalNfcReturn rfalPicoPassPollerCheck(uint8_t* mac, rfalPicoPassCheckRes* chkRes) {
-    FuriHalNfcReturn ret;
-    rfalPicoPassCheckReq chkReq;
-    chkReq.CMD = RFAL_PICOPASS_CMD_CHECK;
-    memcpy(chkReq.mac, mac, 4);
-    memset(chkReq.null, 0, 4);
-    uint16_t recvLen = 0;
-    uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS;
-    uint32_t fwt = furi_hal_nfc_ll_ms2fc(20);
-
-    // printf("check tx: %s\n", hex2Str((uint8_t *)&chkReq, sizeof(rfalPicoPassCheckReq)));
-    ret = furi_hal_nfc_ll_txrx(
-        (uint8_t*)&chkReq,
-        sizeof(rfalPicoPassCheckReq),
-        (uint8_t*)chkRes,
-        sizeof(rfalPicoPassCheckRes),
-        &recvLen,
-        flags,
-        fwt);
-    // printf("check rx: %d %s\n", recvLen, hex2Str(chkRes->mac, 4));
-    if(ret == FuriHalNfcReturnCrc) {
-        return FuriHalNfcReturnOk;
-    }
-
-    return ret;
-}
-
-FuriHalNfcReturn rfalPicoPassPollerReadBlock(uint8_t blockNum, rfalPicoPassReadBlockRes* readRes) {
-    FuriHalNfcReturn ret;
-
-    uint8_t txBuf[4] = {RFAL_PICOPASS_CMD_READ_OR_IDENTIFY, 0, 0, 0};
-    txBuf[1] = blockNum;
-    uint16_t crc = rfalPicoPassCalculateCcitt(0xE012, txBuf + 1, 1);
-    memcpy(txBuf + 2, &crc, sizeof(uint16_t));
-
-    uint16_t recvLen = 0;
-    uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS;
-    uint32_t fwt = furi_hal_nfc_ll_ms2fc(20);
-
-    ret = furi_hal_nfc_ll_txrx(
-        txBuf,
-        sizeof(txBuf),
-        (uint8_t*)readRes,
-        sizeof(rfalPicoPassReadBlockRes),
-        &recvLen,
-        flags,
-        fwt);
-    return ret;
-}
-
-FuriHalNfcReturn rfalPicoPassPollerWriteBlock(uint8_t blockNum, uint8_t data[8], uint8_t mac[4]) {
-    FuriHalNfcReturn ret;
-
-    uint8_t txBuf[14] = {RFAL_PICOPASS_CMD_UPDATE, blockNum, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
-    memcpy(txBuf + 2, data, RFAL_PICOPASS_BLOCK_LEN);
-    memcpy(txBuf + 10, mac, 4);
-
-    uint16_t recvLen = 0;
-    uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS;
-    uint32_t fwt = furi_hal_nfc_ll_ms2fc(20);
-    rfalPicoPassReadBlockRes block;
-
-    ret = furi_hal_nfc_ll_txrx(
-        txBuf, sizeof(txBuf), (uint8_t*)&block, sizeof(block), &recvLen, flags, fwt);
-
-    if(ret == FuriHalNfcReturnOk) {
-        // TODO: compare response
-    }
-
-    return ret;
-}

+ 2 - 13
rfal_picopass.h

@@ -3,7 +3,7 @@
 #include <furi_hal_nfc.h>
 
 #define RFAL_PICOPASS_UID_LEN 8
-#define RFAL_PICOPASS_BLOCK_LEN 8
+#define PICOPASS_BLOCK_LEN 8
 
 enum {
     // PicoPass command bytes:
@@ -59,17 +59,6 @@ typedef struct {
 } rfalPicoPassCheckRes;
 
 typedef struct {
-    uint8_t data[RFAL_PICOPASS_BLOCK_LEN];
+    uint8_t data[PICOPASS_BLOCK_LEN];
     uint8_t crc[2];
 } rfalPicoPassReadBlockRes;
-
-uint16_t rfalPicoPassCalculateCcitt(uint16_t preloadValue, const uint8_t* buf, uint16_t length);
-
-FuriHalNfcReturn rfalPicoPassPollerInitialize(void);
-FuriHalNfcReturn rfalPicoPassPollerCheckPresence(void);
-FuriHalNfcReturn rfalPicoPassPollerIdentify(rfalPicoPassIdentifyRes* idRes);
-FuriHalNfcReturn rfalPicoPassPollerSelect(uint8_t* csn, rfalPicoPassSelectRes* selRes);
-FuriHalNfcReturn rfalPicoPassPollerReadCheck(rfalPicoPassReadCheckRes* rcRes);
-FuriHalNfcReturn rfalPicoPassPollerCheck(uint8_t* mac, rfalPicoPassCheckRes* chkRes);
-FuriHalNfcReturn rfalPicoPassPollerReadBlock(uint8_t blockNum, rfalPicoPassReadBlockRes* readRes);
-FuriHalNfcReturn rfalPicoPassPollerWriteBlock(uint8_t blockNum, uint8_t data[8], uint8_t mac[4]);

+ 4 - 4
scenes/picopass_scene_device_info.c

@@ -25,9 +25,9 @@ void picopass_scene_device_info_on_enter(void* context) {
     PicopassPacs* pacs = &picopass->dev->dev_data.pacs;
     Widget* widget = picopass->widget;
 
-    uint8_t csn[RFAL_PICOPASS_BLOCK_LEN] = {0};
-    memcpy(csn, AA1[PICOPASS_CSN_BLOCK_INDEX].data, RFAL_PICOPASS_BLOCK_LEN);
-    for(uint8_t i = 0; i < RFAL_PICOPASS_BLOCK_LEN; i++) {
+    uint8_t csn[PICOPASS_BLOCK_LEN] = {0};
+    memcpy(csn, AA1[PICOPASS_CSN_BLOCK_INDEX].data, PICOPASS_BLOCK_LEN);
+    for(uint8_t i = 0; i < PICOPASS_BLOCK_LEN; i++) {
         furi_string_cat_printf(csn_str, "%02X ", csn[i]);
     }
 
@@ -41,7 +41,7 @@ void picopass_scene_device_info_on_enter(void* context) {
             bytesLength++;
         }
         furi_string_set(credential_str, "");
-        for(uint8_t i = RFAL_PICOPASS_BLOCK_LEN - bytesLength; i < RFAL_PICOPASS_BLOCK_LEN; i++) {
+        for(uint8_t i = PICOPASS_BLOCK_LEN - bytesLength; i < PICOPASS_BLOCK_LEN; i++) {
             furi_string_cat_printf(credential_str, "%02X", pacs->credential[i]);
         }
         furi_string_cat_printf(wiegand_str, "%d bits", pacs->bitLength);

+ 192 - 126
scenes/picopass_scene_elite_dict_attack.c

@@ -1,172 +1,238 @@
 #include "../picopass_i.h"
 #include <dolphin/dolphin.h>
+#include "../picopass_keys.h"
 
-#define TAG "IclassEliteDictAttack"
+#define PICOPASS_SCENE_DICT_ATTACK_KEYS_BATCH_UPDATE (10)
 
-typedef enum {
-    DictAttackStateIdle,
-    DictAttackStateUserDictInProgress,
-    DictAttackStateFlipperDictInProgress,
-    DictAttackStateStandardDictInProgress,
-} DictAttackState;
+enum {
+    PicopassSceneEliteDictAttackDictEliteUser,
+    PicopassSceneEliteDictAttackDictStandart,
+    PicopassSceneEliteDictAttackDictElite,
+};
 
-void picopass_dict_attack_worker_callback(PicopassWorkerEvent event, void* context) {
-    furi_assert(context);
-    Picopass* picopass = context;
-    view_dispatcher_send_custom_event(picopass->view_dispatcher, event);
+const char* picopass_dict_name[] = {
+    [PicopassSceneEliteDictAttackDictEliteUser] = "Elite User Dictionary",
+    [PicopassSceneEliteDictAttackDictStandart] = "Standard System Dictionary",
+    [PicopassSceneEliteDictAttackDictElite] = "Elite System Dictionary",
+};
+
+static bool picopass_elite_dict_attack_change_dict(Picopass* picopass) {
+    bool success = false;
+
+    do {
+        uint32_t scene_state =
+            scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneEliteDictAttack);
+        nfc_dict_free(picopass->dict);
+        picopass->dict = NULL;
+        if(scene_state == PicopassSceneEliteDictAttackDictElite) break;
+        if(scene_state == PicopassSceneEliteDictAttackDictEliteUser) {
+            if(!nfc_dict_check_presence(PICOPASS_ICLASS_STANDARD_DICT_FLIPPER_NAME)) break;
+            picopass->dict = nfc_dict_alloc(
+                PICOPASS_ICLASS_STANDARD_DICT_FLIPPER_NAME,
+                NfcDictModeOpenExisting,
+                PICOPASS_KEY_LEN);
+            scene_state = PicopassSceneEliteDictAttackDictStandart;
+        } else if(scene_state == PicopassSceneEliteDictAttackDictStandart) {
+            if(!nfc_dict_check_presence(PICOPASS_ICLASS_ELITE_DICT_FLIPPER_NAME)) break;
+            picopass->dict = nfc_dict_alloc(
+                PICOPASS_ICLASS_ELITE_DICT_FLIPPER_NAME,
+                NfcDictModeOpenExisting,
+                PICOPASS_KEY_LEN);
+            scene_state = PicopassSceneEliteDictAttackDictElite;
+        }
+        picopass->dict_attack_ctx.card_detected = true;
+        picopass->dict_attack_ctx.total_keys = nfc_dict_get_total_keys(picopass->dict);
+        picopass->dict_attack_ctx.current_key = 0;
+        picopass->dict_attack_ctx.name = picopass_dict_name[scene_state];
+        scene_manager_set_scene_state(
+            picopass->scene_manager, PicopassSceneEliteDictAttack, scene_state);
+        success = true;
+    } while(false);
+
+    return success;
 }
 
-void picopass_dict_attack_result_callback(void* context) {
+NfcCommand picopass_elite_dict_attack_worker_callback(PicopassPollerEvent event, void* context) {
     furi_assert(context);
+    NfcCommand command = NfcCommandContinue;
+
     Picopass* picopass = context;
-    view_dispatcher_send_custom_event(
-        picopass->view_dispatcher, PicopassCustomEventDictAttackSkip);
-}
 
-static void
-    picopass_scene_elite_dict_attack_prepare_view(Picopass* picopass, DictAttackState state) {
-    IclassEliteDictAttackData* dict_attack_data =
-        &picopass->dev->dev_data.iclass_elite_dict_attack_data;
-    PicopassWorkerState worker_state = PicopassWorkerStateReady;
-    IclassEliteDict* dict = NULL;
-
-    // Identify scene state
-    if(state == DictAttackStateIdle) {
-        if(iclass_elite_dict_check_presence(IclassEliteDictTypeUser)) {
-            FURI_LOG_D(TAG, "Starting with user dictionary");
-            state = DictAttackStateUserDictInProgress;
-        } else {
-            FURI_LOG_D(TAG, "Starting with standard dictionary");
-            state = DictAttackStateStandardDictInProgress;
+    if(event.type == PicopassPollerEventTypeRequestMode) {
+        event.data->req_mode.mode = PicopassPollerModeRead;
+    } else if(event.type == PicopassPollerEventTypeRequestKey) {
+        uint8_t key[PICOPASS_KEY_LEN] = {};
+        bool is_key_provided = true;
+        if(!nfc_dict_get_next_key(picopass->dict, key, PICOPASS_KEY_LEN)) {
+            if(picopass_elite_dict_attack_change_dict(picopass)) {
+                is_key_provided = nfc_dict_get_next_key(picopass->dict, key, PICOPASS_KEY_LEN);
+                view_dispatcher_send_custom_event(
+                    picopass->view_dispatcher, PicopassCustomEventDictAttackUpdateView);
+            } else {
+                is_key_provided = false;
+            }
         }
-    } else if(state == DictAttackStateUserDictInProgress) {
-        FURI_LOG_D(TAG, "Moving from user dictionary to standard dictionary");
-        state = DictAttackStateStandardDictInProgress;
-    } else if(state == DictAttackStateStandardDictInProgress) {
-        FURI_LOG_D(TAG, "Moving from standard dictionary to elite dictionary");
-        state = DictAttackStateFlipperDictInProgress;
-    }
-
-    // Setup view
-    if(state == DictAttackStateUserDictInProgress) {
-        worker_state = PicopassWorkerStateEliteDictAttack;
-        dict_attack_set_header(picopass->dict_attack, "Elite User Dictionary");
-        dict_attack_data->type = IclassEliteDictTypeUser;
-        dict = iclass_elite_dict_alloc(IclassEliteDictTypeUser);
-
-        // If failed to load user dictionary - try the system dictionary
-        if(!dict) {
-            FURI_LOG_E(TAG, "User dictionary not found");
-            state = DictAttackStateStandardDictInProgress;
+        uint32_t scene_state =
+            scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneEliteDictAttack);
+        memcpy(event.data->req_key.key, key, PICOPASS_KEY_LEN);
+        event.data->req_key.is_elite_key =
+            (scene_state != PicopassSceneEliteDictAttackDictStandart);
+        event.data->req_key.is_key_provided = is_key_provided;
+        if(is_key_provided) {
+            picopass->dict_attack_ctx.current_key++;
+            if(picopass->dict_attack_ctx.current_key %
+                   PICOPASS_SCENE_DICT_ATTACK_KEYS_BATCH_UPDATE ==
+               0) {
+                view_dispatcher_send_custom_event(
+                    picopass->view_dispatcher, PicopassCustomEventDictAttackUpdateView);
+            }
         }
+    } else if(event.type == PicopassPollerEventTypeSuccess) {
+        const PicopassDeviceData* data = picopass_poller_get_data(picopass->poller);
+        memcpy(&picopass->dev->dev_data, data, sizeof(PicopassDeviceData));
+        view_dispatcher_send_custom_event(
+            picopass->view_dispatcher, PicopassCustomEventPollerSuccess);
+    } else if(event.type == PicopassPollerEventTypeFail) {
+        const PicopassDeviceData* data = picopass_poller_get_data(picopass->poller);
+        memcpy(&picopass->dev->dev_data, data, sizeof(PicopassDeviceData));
+        view_dispatcher_send_custom_event(
+            picopass->view_dispatcher, PicopassCustomEventPollerSuccess);
+    } else if(event.type == PicopassPollerEventTypeCardLost) {
+        picopass->dict_attack_ctx.card_detected = false;
+        view_dispatcher_send_custom_event(
+            picopass->view_dispatcher, PicopassCustomEventDictAttackUpdateView);
+    } else if(event.type == PicopassPollerEventTypeCardDetected) {
+        picopass->dict_attack_ctx.card_detected = true;
+        view_dispatcher_send_custom_event(
+            picopass->view_dispatcher, PicopassCustomEventDictAttackUpdateView);
     }
-    if(state == DictAttackStateStandardDictInProgress) {
-        worker_state = PicopassWorkerStateEliteDictAttack;
-        dict_attack_set_header(picopass->dict_attack, "Standard System Dictionary");
-        dict_attack_data->type = IclassStandardDictTypeFlipper;
-        dict = iclass_elite_dict_alloc(IclassStandardDictTypeFlipper);
-
-        if(!dict) {
-            FURI_LOG_E(TAG, "Flipper standard dictionary not found");
-            state = DictAttackStateFlipperDictInProgress;
-        }
+
+    return command;
+}
+
+static void picopass_scene_elite_dict_attack_update_view(Picopass* instance) {
+    if(instance->dict_attack_ctx.card_detected) {
+        dict_attack_set_card_detected(instance->dict_attack);
+        dict_attack_set_header(instance->dict_attack, instance->dict_attack_ctx.name);
+        dict_attack_set_total_dict_keys(
+            instance->dict_attack, instance->dict_attack_ctx.total_keys);
+        dict_attack_set_current_dict_key(
+            instance->dict_attack, instance->dict_attack_ctx.current_key);
+    } else {
+        dict_attack_set_card_removed(instance->dict_attack);
     }
-    if(state == DictAttackStateFlipperDictInProgress) {
-        worker_state = PicopassWorkerStateEliteDictAttack;
-        dict_attack_set_header(picopass->dict_attack, "Elite System Dictionary");
-        dict_attack_data->type = IclassEliteDictTypeFlipper;
-        dict = iclass_elite_dict_alloc(IclassEliteDictTypeFlipper);
-        if(!dict) {
-            FURI_LOG_E(TAG, "Flipper Elite dictionary not found");
-            // Pass through to let the worker handle the failure
+}
+
+static void picopass_scene_elite_dict_attack_callback(void* context) {
+    Picopass* instance = context;
+
+    view_dispatcher_send_custom_event(
+        instance->view_dispatcher, PicopassCustomEventDictAttackSkip);
+}
+
+void picopass_scene_elite_dict_attack_on_enter(void* context) {
+    Picopass* picopass = context;
+    dolphin_deed(DolphinDeedNfcRead);
+
+    // Setup dict attack context
+    uint32_t state = PicopassSceneEliteDictAttackDictEliteUser;
+
+    bool use_user_dict = nfc_dict_check_presence(PICOPASS_ICLASS_ELITE_DICT_USER_NAME);
+    if(use_user_dict) {
+        picopass->dict = nfc_dict_alloc(
+            PICOPASS_ICLASS_ELITE_DICT_USER_NAME, NfcDictModeOpenExisting, PICOPASS_KEY_LEN);
+        if(nfc_dict_get_total_keys(picopass->dict) == 0) {
+            nfc_dict_free(picopass->dict);
+            use_user_dict = false;
         }
     }
-    // Free previous dictionary
-    if(dict_attack_data->dict) {
-        iclass_elite_dict_free(dict_attack_data->dict);
+    if(use_user_dict) {
+        state = PicopassSceneEliteDictAttackDictEliteUser;
+    } else {
+        picopass->dict = nfc_dict_alloc(
+            PICOPASS_ICLASS_STANDARD_DICT_FLIPPER_NAME, NfcDictModeOpenExisting, PICOPASS_KEY_LEN);
+        state = PicopassSceneEliteDictAttackDictStandart;
     }
-    dict_attack_data->dict = dict;
+    picopass->dict_attack_ctx.card_detected = true;
+    picopass->dict_attack_ctx.total_keys = nfc_dict_get_total_keys(picopass->dict);
+    picopass->dict_attack_ctx.current_key = 0;
+    picopass->dict_attack_ctx.name = picopass_dict_name[state];
     scene_manager_set_scene_state(picopass->scene_manager, PicopassSceneEliteDictAttack, state);
+
+    // Setup view
+    picopass_scene_elite_dict_attack_update_view(picopass);
     dict_attack_set_callback(
-        picopass->dict_attack, picopass_dict_attack_result_callback, picopass);
-    dict_attack_set_current_sector(picopass->dict_attack, 0);
-    dict_attack_set_card_detected(picopass->dict_attack);
-    dict_attack_set_total_dict_keys(
-        picopass->dict_attack, dict ? iclass_elite_dict_get_total_keys(dict) : 0);
-    picopass_worker_start(
-        picopass->worker,
-        worker_state,
-        &picopass->dev->dev_data,
-        picopass_dict_attack_worker_callback,
-        picopass);
-}
+        picopass->dict_attack, picopass_scene_elite_dict_attack_callback, picopass);
+
+    // Start worker
+    picopass->poller = picopass_poller_alloc(picopass->nfc);
+    picopass_poller_start(picopass->poller, picopass_elite_dict_attack_worker_callback, picopass);
 
-void picopass_scene_elite_dict_attack_on_enter(void* context) {
-    Picopass* picopass = context;
-    picopass_scene_elite_dict_attack_prepare_view(picopass, DictAttackStateIdle);
     view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewDictAttack);
     picopass_blink_start(picopass);
-    notification_message(picopass->notifications, &sequence_display_backlight_enforce_on);
 }
 
 bool picopass_scene_elite_dict_attack_on_event(void* context, SceneManagerEvent event) {
     Picopass* picopass = context;
     bool consumed = false;
 
-    uint32_t state =
-        scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneEliteDictAttack);
     if(event.type == SceneManagerEventTypeCustom) {
-        if(event.event == PicopassWorkerEventSuccess) {
-            if(state == DictAttackStateUserDictInProgress ||
-               state == DictAttackStateStandardDictInProgress) {
-                picopass_worker_stop(picopass->worker);
-                picopass_scene_elite_dict_attack_prepare_view(picopass, state);
-                consumed = true;
+        if(event.event == PicopassCustomEventPollerSuccess) {
+            if(memcmp(
+                   picopass->dev->dev_data.pacs.key,
+                   picopass_factory_debit_key,
+                   PICOPASS_BLOCK_LEN) == 0) {
+                scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadFactorySuccess);
             } else {
                 scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadCardSuccess);
-                consumed = true;
             }
-        } else if(event.event == PicopassWorkerEventAborted) {
-            scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadCardSuccess);
-            consumed = true;
-        } else if(event.event == PicopassWorkerEventCardDetected) {
-            dict_attack_set_card_detected(picopass->dict_attack);
             consumed = true;
-        } else if(event.event == PicopassWorkerEventNoCardDetected) {
-            dict_attack_set_card_removed(picopass->dict_attack);
-            consumed = true;
-        } else if(event.event == PicopassWorkerEventNewDictKeyBatch) {
-            dict_attack_inc_current_dict_key(picopass->dict_attack, PICOPASS_DICT_KEY_BATCH_SIZE);
+        } else if(event.event == PicopassCustomEventDictAttackUpdateView) {
+            picopass_scene_elite_dict_attack_update_view(picopass);
             consumed = true;
         } else if(event.event == PicopassCustomEventDictAttackSkip) {
-            if(state == DictAttackStateUserDictInProgress) {
-                picopass_worker_stop(picopass->worker);
-                consumed = true;
-            } else if(state == DictAttackStateFlipperDictInProgress) {
-                picopass_worker_stop(picopass->worker);
-                consumed = true;
-            } else if(state == DictAttackStateStandardDictInProgress) {
-                picopass_worker_stop(picopass->worker);
-                consumed = true;
+            uint32_t scene_state = scene_manager_get_scene_state(
+                picopass->scene_manager, PicopassSceneEliteDictAttack);
+            if(scene_state != PicopassSceneEliteDictAttackDictElite) {
+                picopass_elite_dict_attack_change_dict(picopass);
+                picopass_scene_elite_dict_attack_update_view(picopass);
+            } else {
+                if(memcmp(
+                       picopass->dev->dev_data.pacs.key,
+                       picopass_factory_debit_key,
+                       PICOPASS_BLOCK_LEN) == 0) {
+                    scene_manager_next_scene(
+                        picopass->scene_manager, PicopassSceneReadFactorySuccess);
+                } else {
+                    scene_manager_next_scene(
+                        picopass->scene_manager, PicopassSceneReadCardSuccess);
+                }
             }
+            consumed = true;
         }
-    } else if(event.type == SceneManagerEventTypeBack) {
-        consumed = scene_manager_previous_scene(picopass->scene_manager);
     }
     return consumed;
 }
 
 void picopass_scene_elite_dict_attack_on_exit(void* context) {
     Picopass* picopass = context;
-    IclassEliteDictAttackData* dict_attack_data =
-        &picopass->dev->dev_data.iclass_elite_dict_attack_data;
-    // Stop worker
-    picopass_worker_stop(picopass->worker);
-    if(dict_attack_data->dict) {
-        iclass_elite_dict_free(dict_attack_data->dict);
-        dict_attack_data->dict = NULL;
+
+    if(picopass->dict) {
+        nfc_dict_free(picopass->dict);
+        picopass->dict = NULL;
     }
-    dict_attack_reset(picopass->dict_attack);
+    picopass->dict_attack_ctx.current_key = 0;
+    picopass->dict_attack_ctx.total_keys = 0;
+
+    picopass_poller_stop(picopass->poller);
+    picopass_poller_free(picopass->poller);
+
+    // Clear view
+    popup_reset(picopass->popup);
+    scene_manager_set_scene_state(
+        picopass->scene_manager,
+        PicopassSceneEliteDictAttack,
+        PicopassSceneEliteDictAttackDictEliteUser);
+
     picopass_blink_stop(picopass);
-    notification_message(picopass->notifications, &sequence_display_backlight_enforce_auto);
 }

+ 10 - 17
scenes/picopass_scene_emulate.c

@@ -1,10 +1,11 @@
 #include "../picopass_i.h"
 #include <dolphin/dolphin.h>
 
-void picopass_emulate_worker_callback(PicopassWorkerEvent event, void* context) {
-    furi_assert(context);
-    Picopass* picopass = context;
-    view_dispatcher_send_custom_event(picopass->view_dispatcher, event);
+NfcCommand picopass_scene_listener_callback(PicopassListenerEvent event, void* context) {
+    UNUSED(event);
+    UNUSED(context);
+
+    return NfcCommandContinue;
 }
 
 void picopass_scene_emulate_on_enter(void* context) {
@@ -17,18 +18,11 @@ void picopass_scene_emulate_on_enter(void* context) {
     widget_add_string_element(widget, 89, 32, AlignCenter, AlignTop, FontPrimary, "Emulating");
     widget_add_string_element(widget, 89, 42, AlignCenter, AlignTop, FontPrimary, "PicoPass");
 
-    // Setup view
     view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewWidget);
-
-    // Start worker
-    picopass_worker_start(
-        picopass->worker,
-        PicopassWorkerStateEmulate,
-        &picopass->dev->dev_data,
-        picopass_emulate_worker_callback,
-        picopass);
-
     picopass_blink_emulate_start(picopass);
+
+    picopass->listener = picopass_listener_alloc(picopass->nfc, &picopass->dev->dev_data);
+    picopass_listener_start(picopass->listener, picopass_scene_listener_callback, picopass);
 }
 
 bool picopass_scene_emulate_on_event(void* context, SceneManagerEvent event) {
@@ -49,9 +43,8 @@ void picopass_scene_emulate_on_exit(void* context) {
     Picopass* picopass = context;
 
     picopass_blink_stop(picopass);
-
-    // Stop worker
-    picopass_worker_stop(picopass->worker);
+    picopass_listener_stop(picopass->listener);
+    picopass_listener_free(picopass->listener);
 
     // Clear view
     widget_reset(picopass->widget);

+ 3 - 3
scenes/picopass_scene_key_input.c

@@ -6,8 +6,8 @@
 void picopass_scene_key_input_text_input_callback(void* context) {
     Picopass* picopass = context;
 
-    picopass->dev->dev_data.pacs.elite_kdf = true;
-    memcpy(picopass->dev->dev_data.pacs.key, picopass->byte_input_store, RFAL_PICOPASS_BLOCK_LEN);
+    memcpy(picopass->write_key_context.key_to_write, picopass->byte_input_store, PICOPASS_KEY_LEN);
+    picopass->write_key_context.is_elite = true;
     view_dispatcher_send_custom_event(picopass->view_dispatcher, PicopassCustomEventByteInputDone);
 }
 
@@ -22,7 +22,7 @@ void picopass_scene_key_input_on_enter(void* context) {
         NULL,
         picopass,
         picopass->byte_input_store,
-        RFAL_PICOPASS_BLOCK_LEN);
+        PICOPASS_BLOCK_LEN);
     view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewByteInput);
 }
 

+ 9 - 8
scenes/picopass_scene_key_menu.c

@@ -65,29 +65,30 @@ bool picopass_scene_key_menu_on_event(void* context, SceneManagerEvent event) {
         if(event.event == SubmenuIndexWriteStandard) {
             scene_manager_set_scene_state(
                 picopass->scene_manager, PicopassSceneKeyMenu, SubmenuIndexWriteStandard);
-            memcpy(picopass->dev->dev_data.pacs.key, picopass_iclass_key, RFAL_PICOPASS_BLOCK_LEN);
-            picopass->dev->dev_data.pacs.elite_kdf = false;
+            memcpy(
+                picopass->write_key_context.key_to_write, picopass_iclass_key, PICOPASS_KEY_LEN);
+            picopass->write_key_context.is_elite = false;
             scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteKey);
             consumed = true;
         } else if(event.event == SubmenuIndexWriteiCE) {
             scene_manager_set_scene_state(
                 picopass->scene_manager, PicopassSceneKeyMenu, SubmenuIndexWriteiCE);
-            memcpy(picopass->dev->dev_data.pacs.key, picopass_xice_key, RFAL_PICOPASS_BLOCK_LEN);
-            picopass->dev->dev_data.pacs.elite_kdf = true;
+            memcpy(picopass->write_key_context.key_to_write, picopass_xice_key, PICOPASS_KEY_LEN);
+            picopass->write_key_context.is_elite = true;
             scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteKey);
             consumed = true;
         } else if(event.event == SubmenuIndexWriteiCL) {
             scene_manager_set_scene_state(
                 picopass->scene_manager, PicopassSceneKeyMenu, SubmenuIndexWriteiCL);
-            memcpy(picopass->dev->dev_data.pacs.key, picopass_xicl_key, RFAL_PICOPASS_BLOCK_LEN);
-            picopass->dev->dev_data.pacs.elite_kdf = false;
+            memcpy(picopass->write_key_context.key_to_write, picopass_xicl_key, PICOPASS_KEY_LEN);
+            picopass->write_key_context.is_elite = false;
             scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteKey);
             consumed = true;
         } else if(event.event == SubmenuIndexWriteiCS) {
             scene_manager_set_scene_state(
                 picopass->scene_manager, PicopassSceneKeyMenu, SubmenuIndexWriteiCS);
-            memcpy(picopass->dev->dev_data.pacs.key, picopass_xics_key, RFAL_PICOPASS_BLOCK_LEN);
-            picopass->dev->dev_data.pacs.elite_kdf = false;
+            memcpy(picopass->write_key_context.key_to_write, picopass_xics_key, PICOPASS_KEY_LEN);
+            picopass->write_key_context.is_elite = false;
             scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteKey);
             consumed = true;
         } else if(event.event == SubmenuIndexWriteCustom) {

+ 55 - 40
scenes/picopass_scene_loclass.c

@@ -1,88 +1,103 @@
 #include "../picopass_i.h"
 #include <dolphin/dolphin.h>
 
-void picopass_loclass_worker_callback(PicopassWorkerEvent event, void* context) {
-    furi_assert(context);
+static NfcCommand
+    picopass_scene_loclass_listener_callback(PicopassListenerEvent event, void* context) {
+    NfcCommand command = NfcCommandContinue;
+
     Picopass* picopass = context;
-    view_dispatcher_send_custom_event(picopass->view_dispatcher, event);
+
+    if(event.type == PicopassListenerEventTypeLoclassGotMac) {
+        picopass->loclass_context.macs_collected++;
+        view_dispatcher_send_custom_event(
+            picopass->view_dispatcher, PicopassCustomEventLoclassGotMac);
+    } else if(event.type == PicopassListenerEventTypeLoclassGotStandardKey) {
+        view_dispatcher_send_custom_event(
+            picopass->view_dispatcher, PicopassCustomEventLoclassGotStandardKey);
+        command = NfcCommandStop;
+    }
+
+    return command;
 }
 
-void picopass_loclass_result_callback(void* context) {
+static void picopass_loclass_result_callback(void* context) {
     furi_assert(context);
+
     Picopass* picopass = context;
     view_dispatcher_send_custom_event(picopass->view_dispatcher, PicopassCustomEventViewExit);
 }
 
 void picopass_scene_loclass_on_enter(void* context) {
     Picopass* picopass = context;
+
     dolphin_deed(DolphinDeedNfcEmulate);
 
     scene_manager_set_scene_state(picopass->scene_manager, PicopassSceneLoclass, 0);
 
     loclass_set_callback(picopass->loclass, picopass_loclass_result_callback, picopass);
-
-    // Start worker
-    picopass_worker_start(
-        picopass->worker,
-        PicopassWorkerStateLoclass,
-        &picopass->dev->dev_data,
-        picopass_loclass_worker_callback,
-        picopass);
-
-    picopass_blink_emulate_start(picopass);
-
     loclass_set_header(picopass->loclass, "Loclass");
 
+    picopass_blink_emulate_start(picopass);
     view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewLoclass);
+
+    PicopassDeviceData* data = malloc(sizeof(PicopassDeviceData));
+    const uint8_t config_block[PICOPASS_BLOCK_LEN] = {
+        0x12, 0xFF, 0xFF, 0xFF, 0x7F, 0x1F, 0xFF, 0x3C};
+    memcpy(data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data, config_block, sizeof(config_block));
+
+    const uint8_t epurse[PICOPASS_BLOCK_LEN] = {0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
+    memcpy(data->AA1[PICOPASS_SECURE_EPURSE_BLOCK_INDEX].data, epurse, sizeof(epurse));
+
+    const uint8_t aia[PICOPASS_BLOCK_LEN] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
+    memcpy(data->AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data, aia, sizeof(aia));
+
+    picopass->listener = picopass_listener_alloc(picopass->nfc, data);
+    free(data);
+    if(picopass_listener_set_mode(picopass->listener, PicopassListenerModeLoclass)) {
+        picopass_listener_start(
+            picopass->listener, picopass_scene_loclass_listener_callback, picopass);
+    } else {
+        loclass_set_num_macs(picopass->loclass, 255);
+        loclass_set_header(picopass->loclass, "Error Opening Log File");
+        picopass_listener_free(picopass->listener);
+        picopass->listener = NULL;
+    }
 }
 
 bool picopass_scene_loclass_on_event(void* context, SceneManagerEvent event) {
-    Picopass* picopass = context;
     bool consumed = false;
+    Picopass* picopass = context;
 
     if(event.type == SceneManagerEventTypeCustom) {
-        if(event.event == PicopassWorkerEventLoclassGotMac) {
-            uint32_t loclass_macs_collected =
-                scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneLoclass);
-            loclass_macs_collected++;
+        if(event.event == PicopassCustomEventLoclassGotMac) {
             notification_message(picopass->notifications, &sequence_single_vibro);
-            scene_manager_set_scene_state(
-                picopass->scene_manager, PicopassSceneLoclass, loclass_macs_collected);
-            loclass_set_num_macs(picopass->loclass, loclass_macs_collected);
-            if(loclass_macs_collected >= LOCLASS_MACS_TO_COLLECT) {
+            loclass_set_num_macs(picopass->loclass, picopass->loclass_context.macs_collected);
+            if(picopass->loclass_context.macs_collected >= LOCLASS_MACS_TO_COLLECT) {
                 notification_message(picopass->notifications, &sequence_double_vibro);
                 scene_manager_previous_scene(picopass->scene_manager);
             }
             consumed = true;
-        } else if(event.event == PicopassWorkerEventLoclassGotStandardKey) {
+        } else if(event.event == PicopassCustomEventLoclassGotStandardKey) {
             loclass_set_header(picopass->loclass, "Loclass (Got Std Key)");
             notification_message(picopass->notifications, &sequence_error);
             consumed = true;
-        } else if(event.event == PicopassWorkerEventLoclassFileError) {
-            scene_manager_set_scene_state(picopass->scene_manager, PicopassSceneLoclass, 255);
-            loclass_set_num_macs(picopass->loclass, 255);
-            loclass_set_header(picopass->loclass, "Error Opening Log File");
-            picopass_blink_stop(picopass);
-            consumed = true;
         } else if(event.event == PicopassCustomEventViewExit) {
             consumed = scene_manager_previous_scene(picopass->scene_manager);
         }
-    } else if(event.type == SceneManagerEventTypeBack) {
-        consumed = scene_manager_previous_scene(picopass->scene_manager);
     }
+
     return consumed;
 }
 
 void picopass_scene_loclass_on_exit(void* context) {
     Picopass* picopass = context;
 
+    if(picopass->listener) {
+        picopass_listener_stop(picopass->listener);
+        picopass_listener_free(picopass->listener);
+    }
+    picopass->loclass_context.macs_collected = 0;
     picopass_blink_stop(picopass);
 
-    // Stop worker
-    picopass_worker_stop(picopass->worker);
-
     loclass_reset(picopass->loclass);
-
-    // Clear view
-    widget_reset(picopass->widget);
-}
+}

+ 79 - 14
scenes/picopass_scene_read_card.c

@@ -2,10 +2,68 @@
 #include <dolphin/dolphin.h>
 #include "../picopass_keys.h"
 
-void picopass_read_card_worker_callback(PicopassWorkerEvent event, void* context) {
-    UNUSED(event);
+enum {
+    PicopassSceneReadCardDictStandart,
+    PicopassSceneReadCardDictElite,
+};
+
+static bool picopass_read_card_change_dict(Picopass* picopass) {
+    bool success = false;
+
+    do {
+        uint32_t scene_state =
+            scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneReadCard);
+        nfc_dict_free(picopass->dict);
+        picopass->dict = NULL;
+        if(scene_state == PicopassSceneReadCardDictElite) break;
+        if(!nfc_dict_check_presence(PICOPASS_ICLASS_ELITE_DICT_FLIPPER_NAME)) break;
+
+        picopass->dict = nfc_dict_alloc(
+            PICOPASS_ICLASS_ELITE_DICT_FLIPPER_NAME, NfcDictModeOpenExisting, PICOPASS_KEY_LEN);
+        scene_manager_set_scene_state(
+            picopass->scene_manager, PicopassSceneReadCard, PicopassSceneReadCardDictElite);
+        success = true;
+    } while(false);
+
+    return success;
+}
+
+NfcCommand picopass_read_card_worker_callback(PicopassPollerEvent event, void* context) {
+    furi_assert(context);
+    NfcCommand command = NfcCommandContinue;
+
     Picopass* picopass = context;
-    view_dispatcher_send_custom_event(picopass->view_dispatcher, PicopassCustomEventWorkerExit);
+
+    if(event.type == PicopassPollerEventTypeRequestMode) {
+        event.data->req_mode.mode = PicopassPollerModeRead;
+    } else if(event.type == PicopassPollerEventTypeRequestKey) {
+        uint8_t key[PICOPASS_KEY_LEN] = {};
+        bool is_key_provided = true;
+        if(!nfc_dict_get_next_key(picopass->dict, key, PICOPASS_KEY_LEN)) {
+            if(picopass_read_card_change_dict(picopass)) {
+                is_key_provided = nfc_dict_get_next_key(picopass->dict, key, PICOPASS_KEY_LEN);
+            } else {
+                is_key_provided = false;
+            }
+        }
+        uint32_t scene_state =
+            scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneReadCard);
+        memcpy(event.data->req_key.key, key, PICOPASS_KEY_LEN);
+        event.data->req_key.is_elite_key = (scene_state == PicopassSceneReadCardDictElite);
+        event.data->req_key.is_key_provided = is_key_provided;
+    } else if(event.type == PicopassPollerEventTypeSuccess) {
+        const PicopassDeviceData* data = picopass_poller_get_data(picopass->poller);
+        memcpy(&picopass->dev->dev_data, data, sizeof(PicopassDeviceData));
+        view_dispatcher_send_custom_event(
+            picopass->view_dispatcher, PicopassCustomEventPollerSuccess);
+    } else if(event.type == PicopassPollerEventTypeFail) {
+        const PicopassDeviceData* data = picopass_poller_get_data(picopass->poller);
+        memcpy(&picopass->dev->dev_data, data, sizeof(PicopassDeviceData));
+        view_dispatcher_send_custom_event(
+            picopass->view_dispatcher, PicopassCustomEventPollerSuccess);
+    }
+
+    return command;
 }
 
 void picopass_scene_read_card_on_enter(void* context) {
@@ -17,15 +75,15 @@ void picopass_scene_read_card_on_enter(void* context) {
     popup_set_header(popup, "Detecting\npicopass\ncard", 68, 30, AlignLeft, AlignTop);
     popup_set_icon(popup, 0, 3, &I_RFIDDolphinReceive_97x61);
 
+    picopass->dict = nfc_dict_alloc(
+        PICOPASS_ICLASS_STANDARD_DICT_FLIPPER_NAME, NfcDictModeOpenExisting, PICOPASS_KEY_LEN);
+    scene_manager_set_scene_state(
+        picopass->scene_manager, PicopassSceneReadCard, PicopassSceneReadCardDictStandart);
     // Start worker
-    view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewPopup);
-    picopass_worker_start(
-        picopass->worker,
-        PicopassWorkerStateDetect,
-        &picopass->dev->dev_data,
-        picopass_read_card_worker_callback,
-        picopass);
+    picopass->poller = picopass_poller_alloc(picopass->nfc);
+    picopass_poller_start(picopass->poller, picopass_read_card_worker_callback, picopass);
 
+    view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewPopup);
     picopass_blink_start(picopass);
 }
 
@@ -34,11 +92,11 @@ bool picopass_scene_read_card_on_event(void* context, SceneManagerEvent event) {
     bool consumed = false;
 
     if(event.type == SceneManagerEventTypeCustom) {
-        if(event.event == PicopassCustomEventWorkerExit) {
+        if(event.event == PicopassCustomEventPollerSuccess) {
             if(memcmp(
                    picopass->dev->dev_data.pacs.key,
                    picopass_factory_debit_key,
-                   RFAL_PICOPASS_BLOCK_LEN) == 0) {
+                   PICOPASS_BLOCK_LEN) == 0) {
                 scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadFactorySuccess);
             } else {
                 scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadCardSuccess);
@@ -52,10 +110,17 @@ bool picopass_scene_read_card_on_event(void* context, SceneManagerEvent event) {
 void picopass_scene_read_card_on_exit(void* context) {
     Picopass* picopass = context;
 
-    // Stop worker
-    picopass_worker_stop(picopass->worker);
+    if(picopass->dict) {
+        nfc_dict_free(picopass->dict);
+        picopass->dict = NULL;
+    }
+    picopass_poller_stop(picopass->poller);
+    picopass_poller_free(picopass->poller);
+
     // Clear view
     popup_reset(picopass->popup);
+    scene_manager_set_scene_state(
+        picopass->scene_manager, PicopassSceneReadCard, PicopassSceneReadCardDictStandart);
 
     picopass_blink_stop(picopass);
 }

+ 11 - 10
scenes/picopass_scene_read_card_success.c

@@ -1,5 +1,6 @@
 #include "../picopass_i.h"
 #include <dolphin/dolphin.h>
+#include <picopass_keys.h>
 
 void picopass_scene_read_card_success_widget_callback(
     GuiButtonType result,
@@ -31,17 +32,17 @@ void picopass_scene_read_card_success_on_enter(void* context) {
     PicopassPacs* pacs = &picopass->dev->dev_data.pacs;
     Widget* widget = picopass->widget;
 
-    uint8_t csn[RFAL_PICOPASS_BLOCK_LEN] = {0};
-    memcpy(csn, AA1[PICOPASS_CSN_BLOCK_INDEX].data, RFAL_PICOPASS_BLOCK_LEN);
-    for(uint8_t i = 0; i < RFAL_PICOPASS_BLOCK_LEN; i++) {
+    uint8_t csn[PICOPASS_BLOCK_LEN] = {0};
+    memcpy(csn, AA1[PICOPASS_CSN_BLOCK_INDEX].data, PICOPASS_BLOCK_LEN);
+    for(uint8_t i = 0; i < PICOPASS_BLOCK_LEN; i++) {
         furi_string_cat_printf(csn_str, "%02X", csn[i]);
     }
 
     // We can't test the pacs->key in case it is intentionally all 0's and we can't test the key block since it is populated with the diversified key before each key test, so we approximate with the PACS config block being blank.
     bool no_key = picopass_is_memset(
-        AA1[PICOPASS_ICLASS_PACS_CFG_BLOCK_INDEX].data, 0x00, RFAL_PICOPASS_BLOCK_LEN);
+        AA1[PICOPASS_ICLASS_PACS_CFG_BLOCK_INDEX].data, 0x00, PICOPASS_BLOCK_LEN);
     bool empty = picopass_is_memset(
-        AA1[PICOPASS_ICLASS_PACS_CFG_BLOCK_INDEX].data, 0xFF, RFAL_PICOPASS_BLOCK_LEN);
+        AA1[PICOPASS_ICLASS_PACS_CFG_BLOCK_INDEX].data, 0xFF, PICOPASS_BLOCK_LEN);
 
     if(no_key) {
         furi_string_cat_printf(wiegand_str, "Read Failed");
@@ -83,7 +84,7 @@ void picopass_scene_read_card_success_on_enter(void* context) {
     } else {
         size_t bytesLength = 1 + pacs->bitLength / 8;
         furi_string_set(credential_str, "");
-        for(uint8_t i = RFAL_PICOPASS_BLOCK_LEN - bytesLength; i < RFAL_PICOPASS_BLOCK_LEN; i++) {
+        for(uint8_t i = PICOPASS_BLOCK_LEN - bytesLength; i < PICOPASS_BLOCK_LEN; i++) {
             furi_string_cat_printf(credential_str, "%02X", pacs->credential[i]);
         }
         furi_string_cat_printf(wiegand_str, "%d bits", pacs->bitLength);
@@ -94,12 +95,12 @@ void picopass_scene_read_card_success_on_enter(void* context) {
 
         if(pacs->key) {
             furi_string_cat_printf(key_str, "Key: ");
-            uint8_t key[RFAL_PICOPASS_BLOCK_LEN];
-            memcpy(key, &pacs->key, RFAL_PICOPASS_BLOCK_LEN);
+            uint8_t key[PICOPASS_BLOCK_LEN];
+            memcpy(key, &pacs->key, PICOPASS_BLOCK_LEN);
 
             bool standard_key = true;
             // Handle DES key being 56bits with parity in LSB
-            for(uint8_t i = 0; i < RFAL_PICOPASS_BLOCK_LEN; i++) {
+            for(uint8_t i = 0; i < PICOPASS_BLOCK_LEN; i++) {
                 if((key[i] & 0xFE) != (picopass_iclass_key[i] & 0xFE)) {
                     standard_key = false;
                     break;
@@ -109,7 +110,7 @@ void picopass_scene_read_card_success_on_enter(void* context) {
             if(standard_key) {
                 furi_string_cat_printf(key_str, "Standard");
             } else {
-                for(uint8_t i = 0; i < RFAL_PICOPASS_BLOCK_LEN; i++) {
+                for(uint8_t i = 0; i < PICOPASS_BLOCK_LEN; i++) {
                     furi_string_cat_printf(key_str, "%02X", key[i]);
                 }
             }

+ 3 - 1
scenes/picopass_scene_read_factory_success.c

@@ -64,7 +64,9 @@ bool picopass_scene_read_factory_success_on_event(void* context, SceneManagerEve
         if(event.event == GuiButtonTypeLeft) {
             consumed = scene_manager_previous_scene(picopass->scene_manager);
         } else if(event.event == GuiButtonTypeCenter) {
-            memcpy(picopass->dev->dev_data.pacs.key, picopass_iclass_key, RFAL_PICOPASS_BLOCK_LEN);
+            memcpy(
+                picopass->write_key_context.key_to_write, picopass_iclass_key, PICOPASS_BLOCK_LEN);
+            picopass->write_key_context.is_elite = false;
             scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteKey);
             consumed = true;
         }

+ 44 - 12
scenes/picopass_scene_write_card.c

@@ -1,10 +1,42 @@
 #include "../picopass_i.h"
 #include <dolphin/dolphin.h>
+#include "../picopass_keys.h"
 
-void picopass_write_card_worker_callback(PicopassWorkerEvent event, void* context) {
-    UNUSED(event);
+#define PICOPASS_SCENE_WRITE_BLOCK_START 6
+#define PICOPASS_SCENE_WRITE_BLOCK_STOP 10
+
+NfcCommand picopass_scene_write_poller_callback(PicopassPollerEvent event, void* context) {
     Picopass* picopass = context;
-    view_dispatcher_send_custom_event(picopass->view_dispatcher, event);
+    NfcCommand command = NfcCommandContinue;
+
+    if(event.type == PicopassPollerEventTypeRequestMode) {
+        event.data->req_mode.mode = PicopassPollerModeWrite;
+    } else if(event.type == PicopassPollerEventTypeRequestKey) {
+        memcpy(event.data->req_key.key, picopass_iclass_key, sizeof(picopass_iclass_key));
+        event.data->req_key.is_elite_key = false;
+        event.data->req_key.is_key_provided = true;
+    } else if(event.type == PicopassPollerEventTypeRequestWriteBlock) {
+        uint8_t block_num =
+            scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneWriteCard);
+        if(block_num == PICOPASS_SCENE_WRITE_BLOCK_STOP) {
+            event.data->req_write.perform_write = false;
+        } else {
+            event.data->req_write.block_num = block_num;
+            event.data->req_write.block = &picopass->dev->dev_data.AA1[block_num];
+            event.data->req_write.perform_write = true;
+            block_num++;
+            scene_manager_set_scene_state(
+                picopass->scene_manager, PicopassSceneWriteCard, block_num);
+        }
+    } else if(event.type == PicopassPollerEventTypeSuccess) {
+        view_dispatcher_send_custom_event(
+            picopass->view_dispatcher, PicopassCustomEventPollerSuccess);
+    } else if(event.type == PicopassPollerEventTypeFail) {
+        view_dispatcher_send_custom_event(
+            picopass->view_dispatcher, PicopassCustomEventPollerFail);
+    }
+
+    return command;
 }
 
 void picopass_scene_write_card_on_enter(void* context) {
@@ -15,15 +47,14 @@ void picopass_scene_write_card_on_enter(void* context) {
     Popup* popup = picopass->popup;
     popup_set_header(popup, "Writing\npicopass\ncard", 68, 30, AlignLeft, AlignTop);
     popup_set_icon(popup, 0, 3, &I_RFIDDolphinSend_97x61);
+    scene_manager_set_scene_state(
+        picopass->scene_manager, PicopassSceneWriteCard, PICOPASS_SCENE_WRITE_BLOCK_START);
 
     // Start worker
     view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewPopup);
-    picopass_worker_start(
-        picopass->worker,
-        PicopassWorkerStateWrite,
-        &picopass->dev->dev_data,
-        picopass_write_card_worker_callback,
-        picopass);
+
+    picopass->poller = picopass_poller_alloc(picopass->nfc);
+    picopass_poller_start(picopass->poller, picopass_scene_write_poller_callback, picopass);
 
     picopass_blink_start(picopass);
 }
@@ -33,10 +64,10 @@ bool picopass_scene_write_card_on_event(void* context, SceneManagerEvent event)
     bool consumed = false;
 
     if(event.type == SceneManagerEventTypeCustom) {
-        if(event.event == PicopassWorkerEventFail) {
+        if(event.event == PicopassCustomEventPollerFail) {
             scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteCardFailure);
             consumed = true;
-        } else if(event.event == PicopassWorkerEventSuccess) {
+        } else if(event.event == PicopassCustomEventPollerSuccess) {
             scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteCardSuccess);
             consumed = true;
         }
@@ -48,7 +79,8 @@ void picopass_scene_write_card_on_exit(void* context) {
     Picopass* picopass = context;
 
     // Stop worker
-    picopass_worker_stop(picopass->worker);
+    picopass_poller_stop(picopass->poller);
+    picopass_poller_free(picopass->poller);
     // Clear view
     popup_reset(picopass->popup);
 

+ 35 - 12
scenes/picopass_scene_write_key.c

@@ -1,10 +1,32 @@
 #include "../picopass_i.h"
 #include <dolphin/dolphin.h>
 
-void picopass_write_key_worker_callback(PicopassWorkerEvent event, void* context) {
-    UNUSED(event);
+NfcCommand picopass_scene_write_key_poller_callback(PicopassPollerEvent event, void* context) {
+    NfcCommand command = NfcCommandContinue;
     Picopass* picopass = context;
-    view_dispatcher_send_custom_event(picopass->view_dispatcher, PicopassCustomEventWorkerExit);
+
+    if(event.type == PicopassPollerEventTypeRequestMode) {
+        event.data->req_mode.mode = PicopassPollerModeWriteKey;
+    } else if(event.type == PicopassPollerEventTypeRequestKey) {
+        event.data->req_key.is_key_provided = true;
+        memcpy(event.data->req_key.key, picopass->dev->dev_data.pacs.key, PICOPASS_KEY_LEN);
+        event.data->req_key.is_elite_key = picopass->dev->dev_data.pacs.elite_kdf;
+    } else if(event.type == PicopassPollerEventTypeRequestWriteKey) {
+        event.data->req_write_key.data = &picopass->dev->dev_data;
+        memcpy(
+            event.data->req_write_key.key,
+            picopass->write_key_context.key_to_write,
+            PICOPASS_KEY_LEN);
+        event.data->req_write_key.is_elite_key = picopass->write_key_context.is_elite;
+    } else if(event.type == PicopassPollerEventTypeSuccess) {
+        view_dispatcher_send_custom_event(
+            picopass->view_dispatcher, PicopassCustomEventPollerSuccess);
+    } else if(event.type == PicopassPollerEventTypeFail) {
+        view_dispatcher_send_custom_event(
+            picopass->view_dispatcher, PicopassCustomEventPollerFail);
+    }
+
+    return command;
 }
 
 void picopass_scene_write_key_on_enter(void* context) {
@@ -18,14 +40,10 @@ void picopass_scene_write_key_on_enter(void* context) {
 
     // Start worker
     view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewPopup);
-    picopass_worker_start(
-        picopass->worker,
-        PicopassWorkerStateWriteKey,
-        &picopass->dev->dev_data,
-        picopass_write_key_worker_callback,
-        picopass);
-
     picopass_blink_start(picopass);
+
+    picopass->poller = picopass_poller_alloc(picopass->nfc);
+    picopass_poller_start(picopass->poller, picopass_scene_write_key_poller_callback, picopass);
 }
 
 bool picopass_scene_write_key_on_event(void* context, SceneManagerEvent event) {
@@ -33,9 +51,12 @@ bool picopass_scene_write_key_on_event(void* context, SceneManagerEvent event) {
     bool consumed = false;
 
     if(event.type == SceneManagerEventTypeCustom) {
-        if(event.event == PicopassCustomEventWorkerExit) {
+        if(event.event == PicopassCustomEventPollerSuccess) {
             scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteCardSuccess);
             consumed = true;
+        } else if(event.event == PicopassCustomEventPollerFail) {
+            scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteCardFailure);
+            consumed = true;
         }
     }
     return consumed;
@@ -45,7 +66,9 @@ void picopass_scene_write_key_on_exit(void* context) {
     Picopass* picopass = context;
 
     // Stop worker
-    picopass_worker_stop(picopass->worker);
+    picopass_poller_stop(picopass->poller);
+    picopass_poller_free(picopass->poller);
+
     // Clear view
     popup_reset(picopass->popup);
 

+ 9 - 2
views/dict_attack.c

@@ -15,7 +15,6 @@ struct DictAttack {
 
 typedef struct {
     DictAttackState state;
-    MfClassicType type;
     FuriString* header;
     uint8_t sectors_total;
     uint8_t sectors_read;
@@ -123,7 +122,6 @@ void dict_attack_reset(DictAttack* dict_attack) {
         DictAttackViewModel * model,
         {
             model->state = DictAttackStateRead;
-            model->type = MfClassicType1k;
             model->sectors_total = 1;
             model->sectors_read = 0;
             model->sector_current = 0;
@@ -242,6 +240,15 @@ void dict_attack_set_total_dict_keys(DictAttack* dict_attack, uint16_t dict_keys
         true);
 }
 
+void dict_attack_set_current_dict_key(DictAttack* dict_attack, uint16_t current_key) {
+    furi_assert(dict_attack);
+    with_view_model(
+        dict_attack->view,
+        DictAttackViewModel * model,
+        { model->dict_keys_current = current_key; },
+        true);
+}
+
 void dict_attack_inc_current_dict_key(DictAttack* dict_attack, uint16_t keys_tried) {
     furi_assert(dict_attack);
     with_view_model(

+ 2 - 2
views/dict_attack.h

@@ -3,8 +3,6 @@
 #include <gui/view.h>
 #include <gui/modules/widget.h>
 
-#include <lib/nfc/protocols/mifare_classic.h>
-
 typedef struct DictAttack DictAttack;
 
 typedef void (*DictAttackCallback)(void* context);
@@ -37,6 +35,8 @@ void dict_attack_inc_keys_found(DictAttack* dict_attack);
 
 void dict_attack_set_total_dict_keys(DictAttack* dict_attack, uint16_t dict_keys_total);
 
+void dict_attack_set_current_dict_key(DictAttack* dict_attack, uint16_t current_key);
+
 void dict_attack_inc_current_dict_key(DictAttack* dict_attack, uint16_t keys_tried);
 
 void dict_attack_set_key_attack(DictAttack* dict_attack, bool is_key_attack, uint8_t sector);

+ 2 - 1
views/loclass.c

@@ -1,5 +1,6 @@
 #include "loclass.h"
-#include "../picopass_worker_i.h"
+
+#include <picopass_device.h>
 
 #include <gui/elements.h>