فهرست منبع

[FL-1526] Mifare Ultralight emulation (#645)

* rfal: add discovery parameter for passing listen activation
* nfc: add discovery parameter to furi_hal_nfc_listen
* mifare_ul: add emulation parsing commands
* nfc: add mifare ul emulation
* nfc: switch to mifare ultralight emulation form menu
* furi-hal-nfc: add first frame reception in emulation mode
* nfc: change argument check
* nfc: rework nfc worker and mifare ul lib
* mifare_ul: add write and cnt increment commands
* nfc: add card modification check
* mifare_ul: add data changed flag
* nfc: add shodow files
* nfc: add restore original file

Co-authored-by: あく <alleteam@gmail.com>
gornekich 4 سال پیش
والد
کامیت
c122317468

+ 2 - 1
applications/nfc/nfc.c

@@ -151,9 +151,10 @@ void nfc_text_store_clear(Nfc* nfc) {
 
 int32_t nfc_app(void* p) {
     Nfc* nfc = nfc_alloc();
+    char* args = p;
 
     // Check argument and run corresponding scene
-    if(p && nfc_device_load(&nfc->dev, p)) {
+    if((*args != '\0') && nfc_device_load(&nfc->dev, p)) {
         scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateUid);
     } else {
         scene_manager_next_scene(nfc->scene_manager, NfcSceneStart);

+ 1 - 1
applications/nfc/nfc_cli.c

@@ -64,7 +64,7 @@ void nfc_cli_emulate(Cli* cli, string_t args, void* context) {
     };
 
     while(!cli_cmd_interrupt_received(cli)) {
-        if(furi_hal_nfc_listen(params.uid, params.uid_len, params.atqa, params.sak, 100)) {
+        if(furi_hal_nfc_listen(params.uid, params.uid_len, params.atqa, params.sak, false, 100)) {
             printf("Reader detected\r\n");
             furi_hal_nfc_deactivate();
         }

+ 86 - 7
applications/nfc/nfc_device.c

@@ -8,6 +8,7 @@
 
 static const char* nfc_app_folder = "/any/nfc";
 static const char* nfc_app_extension = ".nfc";
+static const char* nfc_app_shadow_extension = ".shd";
 
 static bool nfc_device_read_hex(string_t str, uint8_t* buff, uint16_t len) {
     string_strim(str);
@@ -259,7 +260,11 @@ void nfc_device_set_name(NfcDevice* dev, const char* name) {
     strlcpy(dev->dev_name, name, NFC_DEV_NAME_MAX_LEN);
 }
 
-bool nfc_device_save(NfcDevice* dev, const char* dev_name) {
+static bool nfc_device_save_file(
+    NfcDevice* dev,
+    const char* dev_name,
+    const char* folder,
+    const char* extension) {
     furi_assert(dev);
 
     FileWorker* file_worker = file_worker_alloc(false);
@@ -275,7 +280,7 @@ bool nfc_device_save(NfcDevice* dev, const char* dev_name) {
             break;
         };
         // First remove nfc device file if it was saved
-        string_printf(dev_file_name, "%s/%s%s", nfc_app_folder, dev_name, nfc_app_extension);
+        string_printf(dev_file_name, "%s/%s%s", folder, dev_name, extension);
         if(!file_worker_remove(file_worker, string_get_cstr(dev_file_name))) {
             break;
         };
@@ -316,16 +321,42 @@ bool nfc_device_save(NfcDevice* dev, const char* dev_name) {
     return true;
 }
 
+bool nfc_device_save(NfcDevice* dev, const char* dev_name) {
+    return nfc_device_save_file(dev, dev_name, nfc_app_folder, nfc_app_extension);
+}
+
+bool nfc_device_save_shadow(NfcDevice* dev, const char* dev_name) {
+    dev->shadow_file_exist = true;
+    return nfc_device_save_file(dev, dev_name, nfc_app_folder, nfc_app_shadow_extension);
+}
+
 static bool nfc_device_load_data(FileWorker* file_worker, string_t path, NfcDevice* dev) {
     string_t temp_string;
     string_init(temp_string);
     bool parsed = false;
 
     do {
-        // Open key file
-        if(!file_worker_open(file_worker, string_get_cstr(path), FSAM_READ, FSOM_OPEN_EXISTING)) {
+        // Check existance of shadow file
+        size_t ext_start = string_search_str(path, nfc_app_extension);
+        string_set_n(temp_string, path, 0, ext_start);
+        string_cat_printf(temp_string, "%s", nfc_app_shadow_extension);
+        if(!file_worker_is_file_exist(
+               file_worker, string_get_cstr(temp_string), &dev->shadow_file_exist)) {
             break;
         }
+        // Open shadow file if it exists. If not - open original
+        if(dev->shadow_file_exist) {
+            if(!file_worker_open(
+                   file_worker, string_get_cstr(temp_string), FSAM_READ, FSOM_OPEN_EXISTING)) {
+                break;
+            }
+        } else {
+            if(!file_worker_open(
+                   file_worker, string_get_cstr(path), FSAM_READ, FSOM_OPEN_EXISTING)) {
+                break;
+            }
+        }
+
         // Read and parse format from 1st line
         if(!file_worker_read_until(file_worker, temp_string, '\n')) {
             break;
@@ -427,13 +458,61 @@ void nfc_device_clear(NfcDevice* dev) {
 bool nfc_device_delete(NfcDevice* dev) {
     furi_assert(dev);
 
-    bool result = false;
+    bool result = true;
     FileWorker* file_worker = file_worker_alloc(false);
     string_t file_path;
-    string_init_printf(file_path, "%s/%s%s", nfc_app_folder, dev->dev_name, nfc_app_extension);
-    result = file_worker_remove(file_worker, string_get_cstr(file_path));
+
+    do {
+        // Delete original file
+        string_init_printf(file_path, "%s/%s%s", nfc_app_folder, dev->dev_name, nfc_app_extension);
+        if(!file_worker_remove(file_worker, string_get_cstr(file_path))) {
+            result = false;
+            break;
+        }
+        // Delete shadow file if it exists
+        if(dev->shadow_file_exist) {
+            string_clean(file_path);
+            string_printf(
+                file_path, "%s/%s%s", nfc_app_folder, dev->dev_name, nfc_app_shadow_extension);
+            if(!file_worker_remove(file_worker, string_get_cstr(file_path))) {
+                result = false;
+                break;
+            }
+        }
+    } while(0);
+
     string_clear(file_path);
     file_worker_close(file_worker);
     file_worker_free(file_worker);
     return result;
 }
+
+bool nfc_device_restore(NfcDevice* dev) {
+    furi_assert(dev);
+    furi_assert(dev->shadow_file_exist);
+
+    bool result = true;
+    FileWorker* file_worker = file_worker_alloc(false);
+    string_t path;
+
+    do {
+        string_init_printf(
+            path, "%s/%s%s", nfc_app_folder, dev->dev_name, nfc_app_shadow_extension);
+        if(!file_worker_remove(file_worker, string_get_cstr(path))) {
+            result = false;
+            break;
+        }
+        dev->shadow_file_exist = false;
+        string_clean(path);
+        string_printf(path, "%s/%s%s", nfc_app_folder, dev->dev_name, nfc_app_extension);
+        if(!nfc_device_load_data(file_worker, path, dev)) {
+            result = false;
+            break;
+        }
+    } while(0);
+
+    string_clear(path);
+    file_worker_close(file_worker);
+    file_worker_free(file_worker);
+    return result;
+}

+ 5 - 0
applications/nfc/nfc_device.h

@@ -59,12 +59,15 @@ typedef struct {
     char dev_name[NFC_DEV_NAME_MAX_LEN];
     char file_name[NFC_FILE_NAME_MAX_LEN];
     NfcDeviceSaveFormat format;
+    bool shadow_file_exist;
 } NfcDevice;
 
 void nfc_device_set_name(NfcDevice* dev, const char* name);
 
 bool nfc_device_save(NfcDevice* dev, const char* dev_name);
 
+bool nfc_device_save_shadow(NfcDevice* dev, const char* dev_name);
+
 bool nfc_device_load(NfcDevice* dev, const char* file_path);
 
 bool nfc_file_select(NfcDevice* dev);
@@ -72,3 +75,5 @@ bool nfc_file_select(NfcDevice* dev);
 void nfc_device_clear(NfcDevice* dev);
 
 bool nfc_device_delete(NfcDevice* dev);
+
+bool nfc_device_restore(NfcDevice* dev);

+ 37 - 18
applications/nfc/nfc_worker.c

@@ -143,7 +143,7 @@ void nfc_worker_detect(NfcWorker* nfc_worker) {
 void nfc_worker_emulate(NfcWorker* nfc_worker) {
     NfcDeviceCommomData* data = &nfc_worker->dev_data->nfc_data;
     while(nfc_worker->state == NfcWorkerStateEmulate) {
-        if(furi_hal_nfc_listen(data->uid, data->uid_len, data->atqa, data->sak, 100)) {
+        if(furi_hal_nfc_listen(data->uid, data->uid_len, data->atqa, data->sak, false, 100)) {
             FURI_LOG_I(NFC_WORKER_TAG, "Reader detected");
         }
         osDelay(10);
@@ -406,7 +406,7 @@ void nfc_worker_emulate_apdu(NfcWorker* nfc_worker) {
         0x00, 0x00};
 
     while(nfc_worker->state == NfcWorkerStateEmulateApdu) {
-        if(furi_hal_nfc_listen(params.uid, params.uid_len, params.atqa, params.sak, 300)) {
+        if(furi_hal_nfc_listen(params.uid, params.uid_len, params.atqa, params.sak, false, 300)) {
             FURI_LOG_I(NFC_WORKER_TAG, "POS terminal detected");
             // Read data from POS terminal
             err = furi_hal_nfc_data_exchange(NULL, 0, &rx_buff, &rx_len, false);
@@ -608,33 +608,52 @@ void nfc_worker_emulate_mifare_ul(NfcWorker* nfc_worker) {
     uint8_t* rx_buff;
     uint16_t* rx_len;
     NfcDeviceData* data = nfc_worker->dev_data;
-
+    MifareUlDevice mf_ul_emulate;
+    // Setup emulation parameters from mifare ultralight data structure
+    mf_ul_prepare_emulation(&mf_ul_emulate, &data->mf_ul_data);
     while(nfc_worker->state == NfcWorkerStateEmulateMifareUl) {
         if(furi_hal_nfc_listen(
                data->nfc_data.uid,
                data->nfc_data.uid_len,
                data->nfc_data.atqa,
                data->nfc_data.sak,
-               1000)) {
-            FURI_LOG_I(NFC_WORKER_TAG, "Hello my dudes");
-            // Prepare version answer
-            tx_len = sizeof(data->mf_ul_data.version);
-            memcpy(tx_buff, &data->mf_ul_data.version, tx_len);
-            err = furi_hal_nfc_data_exchange(tx_buff, tx_len, &rx_buff, &rx_len, false);
-            if(err == ERR_NONE) {
-                FURI_LOG_I(NFC_WORKER_TAG, "Received 1st message:");
-                for(uint16_t i = 0; i < *rx_len; i++) {
-                    printf("%02X ", rx_buff[i]);
+               true,
+               200)) {
+            FURI_LOG_D(NFC_WORKER_TAG, "Anticollision passed");
+            if(furi_hal_nfc_get_first_frame(&rx_buff, &rx_len)) {
+                // Data exchange loop
+                while(nfc_worker->state == NfcWorkerStateEmulateMifareUl) {
+                    tx_len = mf_ul_prepare_emulation_response(
+                        rx_buff, *rx_len, tx_buff, &mf_ul_emulate);
+                    if(tx_len > 0) {
+                        err =
+                            furi_hal_nfc_data_exchange(tx_buff, tx_len, &rx_buff, &rx_len, false);
+                        if(err == ERR_NONE) {
+                            continue;
+                        } else {
+                            FURI_LOG_E(NFC_WORKER_TAG, "Communication error: %d", err);
+                            break;
+                        }
+                    } else {
+                        FURI_LOG_W(NFC_WORKER_TAG, "Not valid command: %02X", rx_buff[0]);
+                        furi_hal_nfc_deactivate();
+                        break;
+                    }
                 }
-                printf("\r\n");
             } else {
-                FURI_LOG_E(NFC_WORKER_TAG, "Error in 1st data exchange: select PPSE");
+                FURI_LOG_W(NFC_WORKER_TAG, "Error in 1st data exchange");
                 furi_hal_nfc_deactivate();
-                continue;
             }
         }
-        FURI_LOG_W(NFC_WORKER_TAG, "Hello my dudes");
-        osDelay(10);
+        // Check if data was modified
+        if(mf_ul_emulate.data_changed) {
+            nfc_worker->dev_data->mf_ul_data = mf_ul_emulate.data;
+            if(nfc_worker->callback) {
+                nfc_worker->callback(nfc_worker->context);
+            }
+        }
+        FURI_LOG_W(NFC_WORKER_TAG, "Can't find reader");
+        osThreadYield();
     }
 }
 

+ 1 - 0
applications/nfc/scenes/nfc_scene_config.h

@@ -26,3 +26,4 @@ ADD_SCENE(nfc, run_emv_app_confirm, RunEmvAppConfirm)
 ADD_SCENE(nfc, read_emv_data, ReadEmvData)
 ADD_SCENE(nfc, read_emv_data_success, ReadEmvDataSuccess)
 ADD_SCENE(nfc, emulate_apdu_sequence, EmulateApduSequence)
+ADD_SCENE(nfc, restore_original, RestoreOriginal)

+ 27 - 24
applications/nfc/scenes/nfc_scene_emulate_mifare_ul.c

@@ -1,38 +1,33 @@
 #include "../nfc_i.h"
 
+#define NFC_MF_UL_DATA_NOT_CHANGED (0UL)
+#define NFC_MF_UL_DATA_CHANGED (1UL)
+
+void nfc_emulate_mifare_ul_worker_callback(void* context) {
+    Nfc* nfc = (Nfc*)context;
+    scene_manager_set_scene_state(
+        nfc->scene_manager, NfcSceneEmulateMifareUl, NFC_MF_UL_DATA_CHANGED);
+}
+
 const void nfc_scene_emulate_mifare_ul_on_enter(void* context) {
     Nfc* nfc = (Nfc*)context;
 
     // Setup view
     Popup* popup = nfc->popup;
-    NfcDeviceCommomData* data = &nfc->dev.dev_data.nfc_data;
-
     if(strcmp(nfc->dev.dev_name, "")) {
         nfc_text_store_set(nfc, "%s", nfc->dev.dev_name);
-    } else if(data->uid_len == 4) {
-        nfc_text_store_set(
-            nfc, "%02X %02X %02X %02X", data->uid[0], data->uid[1], data->uid[2], data->uid[3]);
-    } else if(data->uid_len == 7) {
-        nfc_text_store_set(
-            nfc,
-            "%02X %02X %02X %02X\n%02X %02X %02X",
-            data->uid[0],
-            data->uid[1],
-            data->uid[2],
-            data->uid[3],
-            data->uid[4],
-            data->uid[5],
-            data->uid[6]);
     }
-
     popup_set_icon(popup, 0, 3, &I_RFIDDolphinSend_97x61);
-    popup_set_header(popup, "Emulating Mf Ul", 56, 31, AlignLeft, AlignTop);
-    popup_set_text(popup, nfc->text_store, 56, 43, AlignLeft, AlignTop);
+    popup_set_header(popup, "Emulating\nMf Ultralight", 56, 31, AlignLeft, AlignTop);
 
     // Setup and start worker
-
     view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup);
-    nfc_worker_start(nfc->worker, NfcWorkerStateEmulateMifareUl, &nfc->dev.dev_data, NULL, nfc);
+    nfc_worker_start(
+        nfc->worker,
+        NfcWorkerStateEmulateMifareUl,
+        &nfc->dev.dev_data,
+        nfc_emulate_mifare_ul_worker_callback,
+        nfc);
 }
 
 const bool nfc_scene_emulate_mifare_ul_on_event(void* context, SceneManagerEvent event) {
@@ -42,6 +37,17 @@ const bool nfc_scene_emulate_mifare_ul_on_event(void* context, SceneManagerEvent
     if(event.type == SceneManagerEventTypeTick) {
         notification_message(nfc->notifications, &sequence_blink_blue_10);
         consumed = true;
+    } else if(event.type == SceneManagerEventTypeBack) {
+        // Stop worker
+        nfc_worker_stop(nfc->worker);
+        // Check if data changed and save in shadow file
+        if(scene_manager_get_scene_state(nfc->scene_manager, NfcSceneEmulateMifareUl) ==
+           NFC_MF_UL_DATA_CHANGED) {
+            scene_manager_set_scene_state(
+                nfc->scene_manager, NfcSceneEmulateMifareUl, NFC_MF_UL_DATA_NOT_CHANGED);
+            nfc_device_save_shadow(&nfc->dev, nfc->dev.dev_name);
+        }
+        consumed = false;
     }
     return consumed;
 }
@@ -49,9 +55,6 @@ const bool nfc_scene_emulate_mifare_ul_on_event(void* context, SceneManagerEvent
 const void nfc_scene_emulate_mifare_ul_on_exit(void* context) {
     Nfc* nfc = (Nfc*)context;
 
-    // Stop worker
-    nfc_worker_stop(nfc->worker);
-
     // Clear view
     Popup* popup = nfc->popup;
     popup_set_header(popup, NULL, 0, 0, AlignCenter, AlignBottom);

+ 1 - 1
applications/nfc/scenes/nfc_scene_mifare_ul_menu.c

@@ -38,7 +38,7 @@ const bool nfc_scene_mifare_ul_menu_on_event(void* context, SceneManagerEvent ev
         } else if(event.event == SubmenuIndexEmulate) {
             scene_manager_set_scene_state(
                 nfc->scene_manager, NfcSceneMifareUlMenu, SubmenuIndexEmulate);
-            scene_manager_next_scene(nfc->scene_manager, NfcSceneNotImplemented);
+            scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateMifareUl);
             return true;
         }
     } else if(event.type == SceneManagerEventTypeBack) {

+ 48 - 0
applications/nfc/scenes/nfc_scene_restore_original.c

@@ -0,0 +1,48 @@
+#include "../nfc_i.h"
+
+#define SCENE_RESTORE_ORIGINAL_CUSTOM_EVENT (0UL)
+
+void nfc_scene_restore_original_popup_callback(void* context) {
+    Nfc* nfc = (Nfc*)context;
+    view_dispatcher_send_custom_event(nfc->view_dispatcher, SCENE_RESTORE_ORIGINAL_CUSTOM_EVENT);
+}
+
+const void nfc_scene_restore_original_on_enter(void* context) {
+    Nfc* nfc = (Nfc*)context;
+
+    // Setup view
+    Popup* popup = nfc->popup;
+    popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59);
+    popup_set_header(popup, "Original file\nrestored", 13, 22, AlignLeft, AlignBottom);
+    popup_set_timeout(popup, 1500);
+    popup_set_context(popup, nfc);
+    popup_set_callback(popup, nfc_scene_restore_original_popup_callback);
+    popup_enable_timeout(popup);
+    view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup);
+}
+
+const bool nfc_scene_restore_original_on_event(void* context, SceneManagerEvent event) {
+    Nfc* nfc = (Nfc*)context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == SCENE_RESTORE_ORIGINAL_CUSTOM_EVENT) {
+            consumed = scene_manager_previous_scene(nfc->scene_manager);
+        }
+    }
+    return consumed;
+}
+
+const void nfc_scene_restore_original_on_exit(void* context) {
+    Nfc* nfc = (Nfc*)context;
+
+    // Clear view
+    Popup* popup = nfc->popup;
+    popup_set_header(popup, NULL, 0, 0, AlignCenter, AlignBottom);
+    popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignTop);
+    popup_set_icon(popup, 0, 0, NULL);
+    popup_set_callback(popup, NULL);
+    popup_set_context(popup, NULL);
+    popup_set_timeout(popup, 0);
+    popup_disable_timeout(popup);
+}

+ 29 - 12
applications/nfc/scenes/nfc_scene_saved_menu.c

@@ -5,6 +5,7 @@ enum SubmenuIndex {
     SubmenuIndexEdit,
     SubmenuIndexDelete,
     SubmenuIndexInfo,
+    SubmenuIndexRestoreOriginal,
 };
 
 void nfc_scene_saved_menu_submenu_callback(void* context, uint32_t index) {
@@ -29,36 +30,52 @@ const void nfc_scene_saved_menu_on_enter(void* context) {
         submenu, "Info", SubmenuIndexInfo, nfc_scene_saved_menu_submenu_callback, nfc);
     submenu_set_selected_item(
         nfc->submenu, scene_manager_get_scene_state(nfc->scene_manager, NfcSceneSavedMenu));
+    if(nfc->dev.shadow_file_exist) {
+        submenu_add_item(
+            submenu,
+            "Restore original",
+            SubmenuIndexRestoreOriginal,
+            nfc_scene_saved_menu_submenu_callback,
+            nfc);
+    }
 
     view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu);
 }
 
 const bool nfc_scene_saved_menu_on_event(void* context, SceneManagerEvent event) {
     Nfc* nfc = (Nfc*)context;
+    bool consumed = false;
 
     if(event.type == SceneManagerEventTypeCustom) {
+        scene_manager_set_scene_state(nfc->scene_manager, NfcSceneSavedMenu, event.event);
         if(event.event == SubmenuIndexEmulate) {
-            scene_manager_set_scene_state(
-                nfc->scene_manager, NfcSceneSavedMenu, SubmenuIndexEmulate);
-            scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateUid);
-            return true;
+            if(nfc->dev.format == NfcDeviceSaveFormatMifareUl) {
+                scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateMifareUl);
+            } else {
+                scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateUid);
+            }
+            consumed = true;
         } else if(event.event == SubmenuIndexEdit) {
-            scene_manager_set_scene_state(nfc->scene_manager, NfcSceneSavedMenu, SubmenuIndexEdit);
             scene_manager_next_scene(nfc->scene_manager, NfcSceneSetUid);
-            return true;
+            consumed = true;
         } else if(event.event == SubmenuIndexDelete) {
-            scene_manager_set_scene_state(
-                nfc->scene_manager, NfcSceneSavedMenu, SubmenuIndexDelete);
             scene_manager_next_scene(nfc->scene_manager, NfcSceneDelete);
-            return true;
+            consumed = true;
         } else if(event.event == SubmenuIndexInfo) {
-            scene_manager_set_scene_state(nfc->scene_manager, NfcSceneSavedMenu, SubmenuIndexInfo);
             scene_manager_next_scene(nfc->scene_manager, NfcSceneDeviceInfo);
-            return true;
+            consumed = true;
+        } else if(event.event == SubmenuIndexRestoreOriginal) {
+            if(!nfc_device_restore(&nfc->dev)) {
+                scene_manager_search_and_switch_to_previous_scene(
+                    nfc->scene_manager, NfcSceneStart);
+            } else {
+                scene_manager_next_scene(nfc->scene_manager, NfcSceneRestoreOriginal);
+            }
+            consumed = true;
         }
     }
 
-    return false;
+    return consumed;
 }
 
 const void nfc_scene_saved_menu_on_exit(void* context) {

+ 7 - 4
firmware/targets/f6/furi-hal/furi-hal-nfc.c

@@ -86,9 +86,8 @@ bool furi_hal_nfc_detect(rfalNfcDevice **dev_list, uint8_t* dev_cnt, uint32_t ti
     return true;
 }
 
-bool furi_hal_nfc_listen(uint8_t* uid, uint8_t uid_len, uint8_t* atqa, uint8_t sak, uint32_t timeout) {
+bool furi_hal_nfc_listen(uint8_t* uid, uint8_t uid_len, uint8_t* atqa, uint8_t sak, bool activate_after_sak, uint32_t timeout) {
     rfalNfcState state = rfalNfcGetState();
-
     if(state == RFAL_NFC_STATE_NOTINIT) {
         rfalNfcInitialize();
     } else if(state >= RFAL_NFC_STATE_ACTIVATED) {
@@ -107,6 +106,7 @@ bool furi_hal_nfc_listen(uint8_t* uid, uint8_t uid_len, uint8_t* atqa, uint8_t s
         .maxBR = RFAL_BR_KEEP,
         .GBLen = RFAL_NFCDEP_GB_MAX_LEN,
         .notifyCb = NULL,
+        .activate_after_sak = activate_after_sak,
     };
     params.lmConfigPA.nfcidLen = uid_len;
     memcpy(params.lmConfigPA.nfcid, uid, uid_len);
@@ -119,7 +119,6 @@ bool furi_hal_nfc_listen(uint8_t* uid, uint8_t uid_len, uint8_t* atqa, uint8_t s
     while(state != RFAL_NFC_STATE_ACTIVATED) {
         rfalNfcWorker();
         state = rfalNfcGetState();
-        FURI_LOG_D("HAL NFC", "Current state %d", state);
         if(DWT->CYCCNT - start > timeout * clocks_in_ms) {
             rfalNfcDeactivate(true);
             return false;
@@ -129,6 +128,11 @@ bool furi_hal_nfc_listen(uint8_t* uid, uint8_t uid_len, uint8_t* atqa, uint8_t s
     return true;
 }
 
+bool furi_hal_nfc_get_first_frame(uint8_t** rx_buff, uint16_t** rx_len) {
+    ReturnCode ret = rfalNfcDataExchangeStart(NULL, 0, rx_buff, rx_len, 0);
+    return ret == ERR_NONE;
+}
+
 ReturnCode furi_hal_nfc_data_exchange(uint8_t* tx_buff, uint16_t tx_len, uint8_t** rx_buff, uint16_t** rx_len, bool deactivate) {
     furi_assert(rx_buff);
     furi_assert(rx_len);
@@ -144,7 +148,6 @@ ReturnCode furi_hal_nfc_data_exchange(uint8_t* tx_buff, uint16_t tx_len, uint8_t
         rfalNfcWorker();
         state = rfalNfcGetState();
         ret = rfalNfcDataExchangeGetStatus();
-        FURI_LOG_D("HAL NFC", "Nfc st: %d Data st: %d", state, ret);
         if(ret > ERR_SLEEP_REQ) {
             return ret;
         }

+ 6 - 1
firmware/targets/furi-hal-include/furi-hal-nfc.h

@@ -49,7 +49,12 @@ bool furi_hal_nfc_detect(rfalNfcDevice** dev_list, uint8_t* dev_cnt, uint32_t ti
 /**
  * NFC listen
  */
-bool furi_hal_nfc_listen(uint8_t* uid, uint8_t uid_len, uint8_t* atqa, uint8_t sak, uint32_t timeout);
+bool furi_hal_nfc_listen(uint8_t* uid, uint8_t uid_len, uint8_t* atqa, uint8_t sak, bool activate_after_sak, uint32_t timeout);
+
+/**
+ * Get first command from reader after activation in emulation mode
+ */
+bool furi_hal_nfc_get_first_frame(uint8_t** rx_buff, uint16_t** rx_len);
 
 /**
  * NFC data exchange

+ 2 - 0
lib/ST25RFAL002/include/rfal_nfc.h

@@ -213,6 +213,8 @@ typedef struct{
     bool               wakeupEnabled;                   /*!< Enable Wake-Up mode before polling                    */
     bool               wakeupConfigDefault;             /*!< Wake-Up mode default configuration                    */
     rfalWakeUpConfig   wakeupConfig;                    /*!< Wake-Up mode configuration                            */
+
+    bool               activate_after_sak; // Set device to Active mode after SAK responce
 }rfalNfcDiscoverParam;
 
 

+ 8 - 0
lib/ST25RFAL002/source/rfal_nfc.c

@@ -575,6 +575,7 @@ ReturnCode rfalNfcDataExchangeStart( uint8_t *txData, uint16_t txDataLen, uint8_
             *rvdLen = (uint16_t*)&gNfcDev.rxLen;
             *rxData = (uint8_t*)(  (gNfcDev.activeDev->rfInterface == RFAL_NFC_INTERFACE_ISODEP) ? gNfcDev.rxBuf.isoDepBuf.apdu : 
                                   ((gNfcDev.activeDev->rfInterface == RFAL_NFC_INTERFACE_NFCDEP) ? gNfcDev.rxBuf.nfcDepBuf.pdu  : gNfcDev.rxBuf.rfBuf));
+            gNfcDev.state = RFAL_NFC_STATE_DATAEXCHANGE_DONE;
             return ERR_NONE;
         }
         
@@ -1579,6 +1580,7 @@ static ReturnCode rfalNfcListenActivation( void )
 
     
     lmSt = rfalListenGetState( &isDataRcvd, &bitRate );
+
     switch(lmSt)
     {
 
@@ -1595,6 +1597,12 @@ static ReturnCode rfalNfcListenActivation( void )
                     /* Set the Listen Mode in Sleep state */
                     EXIT_ON_ERR( ret, rfalListenSleepStart( RFAL_LM_STATE_SLEEP_A, gNfcDev.rxBuf.rfBuf, sizeof(gNfcDev.rxBuf.rfBuf), &gNfcDev.rxLen ) );
                 }
+
+                else if(gNfcDev.disc.activate_after_sak) {
+                    gNfcDev.devList->type = RFAL_NFC_POLL_TYPE_NFCA;
+                    rfalListenSetState(RFAL_LM_STATE_ACTIVE_A);
+                    return ERR_NONE;
+                }
                 
             #if RFAL_FEATURE_ISO_DEP && RFAL_FEATURE_ISO_DEP_LISTEN
                 /* Check if received data is a valid RATS */

+ 91 - 0
lib/nfc_protocols/mifare_ultralight.c

@@ -137,3 +137,94 @@ uint16_t mf_ul_prepare_write(uint8_t* dest, uint16_t page_addr, uint32_t data) {
     dest[5] = (uint8_t) data;
     return 6;
 }
+
+void mf_ul_prepare_emulation(MifareUlDevice* mf_ul_emulate, MifareUlData* data) {
+    mf_ul_emulate->data = *data;
+    mf_ul_emulate->data_changed = false;
+    if(data->version.storage_size == 0) {
+        mf_ul_emulate->type = MfUltralightTypeUnknown;
+        mf_ul_emulate->support_fast_read = false;
+    } else if(data->version.storage_size == 0x0B) {
+        mf_ul_emulate->type = MfUltralightTypeUL11;
+        mf_ul_emulate->support_fast_read = true;
+    } else if(data->version.storage_size == 0x0E) {
+        mf_ul_emulate->type = MfUltralightTypeUL21;
+        mf_ul_emulate->support_fast_read = true;
+    }
+}
+
+uint16_t mf_ul_prepare_emulation_response(uint8_t* buff_rx, uint16_t len_rx, uint8_t* buff_tx, MifareUlDevice* mf_ul_emulate) {
+    uint8_t cmd = buff_rx[0];
+    uint16_t page_num = mf_ul_emulate->data.data_size / 4;
+    uint16_t tx_len = 0;
+
+    if(cmd == MF_UL_GET_VERSION_CMD) {
+        if(mf_ul_emulate->type != MfUltralightTypeUnknown) {
+            tx_len = sizeof(mf_ul_emulate->data.version);
+            memcpy(buff_tx, &mf_ul_emulate->data.version, tx_len);
+        }
+    } else if(cmd == MF_UL_READ_CMD) {
+        uint8_t start_page = buff_rx[1];
+        if(start_page < page_num) {
+            tx_len = 16;
+            if(start_page + 4 > page_num) {
+                // Handle roll-over mechanism
+                uint8_t end_pages_num = page_num - start_page;
+                memcpy(buff_tx, &mf_ul_emulate->data.data[start_page * 4], end_pages_num * 4);
+                memcpy(&buff_tx[end_pages_num * 4], mf_ul_emulate->data.data, (4 - end_pages_num) * 4);
+            } else {
+                memcpy(buff_tx, &mf_ul_emulate->data.data[start_page * 4], tx_len);
+            }
+        }
+    } else if(cmd == MF_UL_FAST_READ_CMD) {
+        if(mf_ul_emulate->support_fast_read) {
+            uint8_t start_page = buff_rx[1];
+            uint8_t end_page = buff_rx[2];
+            if((start_page < page_num) &&
+               (end_page < page_num) && (start_page < end_page)) {
+                tx_len = (end_page - start_page) * 4;
+                memcpy(buff_tx, &mf_ul_emulate->data.data[start_page * 4], tx_len);
+               }
+        }
+    } else if(cmd == MF_UL_WRITE) {
+        uint8_t write_page = buff_rx[1];
+        if((write_page > 1) && (write_page < page_num - 2)) {
+            memcpy(&mf_ul_emulate->data.data[write_page * 4], &buff_rx[2], 4);
+            mf_ul_emulate->data_changed = true;
+            // TODO make 4-bit ACK
+            buff_tx[0] = 0x0A;
+            tx_len = 1;
+        }
+    } else if(cmd == MF_UL_READ_CNT) {
+        uint8_t cnt_num = buff_rx[1];
+        if(cnt_num < 3) {
+            buff_tx[0] = mf_ul_emulate->data.counter[cnt_num] >> 16;
+            buff_tx[1] = mf_ul_emulate->data.counter[cnt_num] >> 8;
+            buff_tx[2] = mf_ul_emulate->data.counter[cnt_num];
+            tx_len = 3;
+        }
+    } else if(cmd == MF_UL_INC_CNT) {
+        uint8_t cnt_num = buff_rx[1];
+        uint32_t inc = (buff_rx[2] | (buff_rx[3] << 8) | (buff_rx[4] << 16));
+        if((cnt_num < 3) && (mf_ul_emulate->data.counter[cnt_num] + inc < 0x00FFFFFF)) {
+            mf_ul_emulate->data.counter[cnt_num] += inc;
+            mf_ul_emulate->data_changed = true;
+            // TODO make 4-bit ACK
+            buff_tx[0] = 0x0A;
+            tx_len = 1;
+        }
+    } else if(cmd == MF_UL_READ_SIG) {
+        // Check 2nd byte = 0x00 - RFU
+        if(buff_rx[1] == 0x00) {
+            tx_len = sizeof(mf_ul_emulate->data.signature);
+            memcpy(buff_tx, mf_ul_emulate->data.signature, tx_len);
+        }
+    } else if(cmd == MF_UL_CHECK_TEARING) {
+        uint8_t cnt_num = buff_rx[1];
+        if(cnt_num < 3) {
+            buff_tx[0] = mf_ul_emulate->data.tearing[cnt_num];
+            tx_len = 1;
+        }
+    }
+    return tx_len;
+}

+ 3 - 0
lib/nfc_protocols/mifare_ultralight.h

@@ -65,6 +65,7 @@ typedef struct {
     uint8_t pages_to_read;
     uint8_t pages_readed;
     bool support_fast_read;
+    bool data_changed;
     MifareUlData data;
 } MifareUlDevice;
 
@@ -93,3 +94,5 @@ void mf_ul_parse_fast_read_response(uint8_t* buff, uint8_t start_page, uint8_t e
 
 uint16_t mf_ul_prepare_write(uint8_t* dest, uint16_t page_addr, uint32_t data);
 
+void mf_ul_prepare_emulation(MifareUlDevice* mf_ul_emulate, MifareUlData* data);
+uint16_t mf_ul_prepare_emulation_response(uint8_t* buff_rx, uint16_t len_rx, uint8_t* buff_tx, MifareUlDevice* mf_ul_emulate);