Browse Source

[FL-1501] NFC: read Mifare Ultralight (#571)

* nfc: add scripts menu scene
* canvas: add glyph width api
* app_scene: add state to Scene template
* gui: introduce TextBox view
* nfc: add mifare ultralight read scenes
* nfc: add mifare ultralight menu scene
* nfc: fix scene functions declaration
* Gui: use size_t for sizes.

Co-authored-by: あく <alleteam@gmail.com>
gornekich 4 năm trước cách đây
mục cha
commit
20fe544b4f

+ 5 - 0
applications/gui/canvas.c

@@ -176,6 +176,11 @@ uint16_t canvas_string_width(Canvas* canvas, const char* str) {
     return u8g2_GetStrWidth(&canvas->fb, str);
     return u8g2_GetStrWidth(&canvas->fb, str);
 }
 }
 
 
+uint8_t canvas_glyph_width(Canvas* canvas, char symbol) {
+    furi_assert(canvas);
+    return u8g2_GetGlyphWidth(&canvas->fb, symbol);
+}
+
 void canvas_draw_icon_animation(
 void canvas_draw_icon_animation(
     Canvas* canvas,
     Canvas* canvas,
     uint8_t x,
     uint8_t x,

+ 5 - 0
applications/gui/canvas.h

@@ -91,6 +91,11 @@ void canvas_draw_str_aligned(
  */
  */
 uint16_t canvas_string_width(Canvas* canvas, const char* str);
 uint16_t canvas_string_width(Canvas* canvas, const char* str);
 
 
+/** Get glyph width
+ * @return width in pixels
+ */
+uint8_t canvas_glyph_width(Canvas* canvas, char symbol);
+
 /** Draw animation at position defined by x,y.
 /** Draw animation at position defined by x,y.
  * @param canvas - canvas instance
  * @param canvas - canvas instance
  * @param x - x coordinate
  * @param x - x coordinate

+ 212 - 0
applications/gui/modules/text_box.c

@@ -0,0 +1,212 @@
+#include "text_box.h"
+#include "gui/canvas.h"
+#include <m-string.h>
+#include <furi.h>
+#include <gui/elements.h>
+#include <stdint.h>
+
+struct TextBox {
+    View* view;
+    void* context;
+    TextBoxExitCallback callback;
+};
+
+typedef struct {
+    const char* text;
+    char* text_pos;
+    string_t text_formatted;
+    size_t scroll_pos;
+    size_t scroll_num;
+    TextBoxFont font;
+    bool formatted;
+} TextBoxModel;
+
+static void text_box_process_down(TextBox* text_box) {
+    with_view_model(
+        text_box->view, (TextBoxModel * model) {
+            if(model->scroll_pos < model->scroll_num - 1) {
+                model->scroll_pos++;
+                // Search next line start
+                while(*model->text_pos++ != '\n')
+                    ;
+            }
+            return true;
+        });
+}
+
+static void text_box_process_up(TextBox* text_box) {
+    with_view_model(
+        text_box->view, (TextBoxModel * model) {
+            if(model->scroll_pos > 0) {
+                model->scroll_pos--;
+                // Reach last symbol of previous line
+                model->text_pos--;
+                // Search prevous line start
+                while((model->text_pos != model->text) && (*(--model->text_pos) != '\n'))
+                    ;
+                if(*model->text_pos == '\n') {
+                    model->text_pos++;
+                }
+            }
+            return true;
+        });
+}
+
+static void text_box_process_back(TextBox* text_box) {
+    if(text_box->callback) {
+        text_box->callback(text_box->context);
+    }
+}
+
+static void text_box_insert_endline(Canvas* canvas, TextBoxModel* model) {
+    size_t i = 0;
+    size_t line_width = 0;
+    const char* str = model->text;
+    size_t line_num = 0;
+
+    const size_t text_width = 140;
+
+    while(str[i] != '\0') {
+        char symb = str[i++];
+        if(symb != '\n') {
+            line_width += canvas_glyph_width(canvas, symb) + 1;
+            if(line_width > text_width) {
+                line_num++;
+                line_width = 0;
+                string_push_back(model->text_formatted, '\n');
+            }
+        } else {
+            line_num++;
+            line_width = 0;
+        }
+        string_push_back(model->text_formatted, symb);
+    }
+    line_num++;
+    model->text = string_get_cstr(model->text_formatted);
+    model->text_pos = (char*)model->text;
+    model->scroll_num = MAX(line_num - 4, 0);
+    model->scroll_pos = 0;
+}
+
+static void text_box_view_draw_callback(Canvas* canvas, void* _model) {
+    TextBoxModel* model = _model;
+
+    if(!model->formatted) {
+        text_box_insert_endline(canvas, model);
+        model->formatted = true;
+    }
+
+    canvas_clear(canvas);
+    elements_slightly_rounded_frame(canvas, 0, 0, 124, 64);
+    if(model->font == TextBoxFontText) {
+        canvas_set_font(canvas, FontSecondary);
+    } else if(model->font == TextBoxFontHex) {
+        canvas_set_font(canvas, FontKeyboard);
+    }
+    elements_multiline_text(canvas, 3, 11, model->text_pos);
+    elements_scrollbar(canvas, model->scroll_pos, model->scroll_num);
+}
+
+static bool text_box_view_input_callback(InputEvent* event, void* context) {
+    furi_assert(context);
+
+    TextBox* text_box = context;
+    bool consumed = false;
+    if(event->type == InputTypeShort) {
+        if(event->key == InputKeyDown) {
+            text_box_process_down(text_box);
+            consumed = true;
+        } else if(event->key == InputKeyUp) {
+            text_box_process_up(text_box);
+            consumed = true;
+        } else if(event->key == InputKeyBack) {
+            text_box_process_back(text_box);
+            consumed = true;
+        }
+    }
+    return consumed;
+}
+
+TextBox* text_box_alloc() {
+    TextBox* text_box = furi_alloc(sizeof(TextBox));
+    text_box->view = view_alloc();
+    view_set_context(text_box->view, text_box);
+    view_allocate_model(text_box->view, ViewModelTypeLocking, sizeof(TextBoxModel));
+    view_set_draw_callback(text_box->view, text_box_view_draw_callback);
+    view_set_input_callback(text_box->view, text_box_view_input_callback);
+
+    with_view_model(
+        text_box->view, (TextBoxModel * model) {
+            model->text = NULL;
+            string_init_set_str(model->text_formatted, "");
+            model->formatted = false;
+            model->font = TextBoxFontText;
+            return true;
+        });
+
+    return text_box;
+}
+
+void text_box_free(TextBox* text_box) {
+    furi_assert(text_box);
+
+    with_view_model(
+        text_box->view, (TextBoxModel * model) {
+            string_clear(model->text_formatted);
+            return true;
+        });
+    view_free(text_box->view);
+    free(text_box);
+}
+
+View* text_box_get_view(TextBox* text_box) {
+    furi_assert(text_box);
+    return text_box->view;
+}
+
+void text_box_clean(TextBox* text_box) {
+    furi_assert(text_box);
+
+    with_view_model(
+        text_box->view, (TextBoxModel * model) {
+            model->text = NULL;
+            string_set_str(model->text_formatted, "");
+            model->font = TextBoxFontText;
+            return true;
+        });
+    text_box->context = NULL;
+    text_box->callback = NULL;
+}
+
+void text_box_set_text(TextBox* text_box, const char* text) {
+    furi_assert(text_box);
+    furi_assert(text);
+
+    with_view_model(
+        text_box->view, (TextBoxModel * model) {
+            model->text = text;
+            string_reserve(model->text_formatted, strlen(text));
+            model->formatted = false;
+            return true;
+        });
+}
+
+void text_box_set_font(TextBox* text_box, TextBoxFont font) {
+    furi_assert(text_box);
+
+    with_view_model(
+        text_box->view, (TextBoxModel * model) {
+            model->font = font;
+            return true;
+        });
+}
+
+void text_box_set_context(TextBox* text_box, void* context) {
+    furi_assert(text_box);
+    text_box->context = context;
+}
+
+void text_box_set_exit_callback(TextBox* text_box, TextBoxExitCallback callback) {
+    furi_assert(text_box);
+    text_box->callback = callback;
+}

+ 63 - 0
applications/gui/modules/text_box.h

@@ -0,0 +1,63 @@
+#pragma once
+#include <gui/view.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* TextBox anonymous structure */
+typedef struct TextBox TextBox;
+typedef void (*TextBoxExitCallback)(void* context);
+
+typedef enum {
+    TextBoxFontText,
+    TextBoxFontHex,
+} TextBoxFont;
+
+/** Allocate and initialize text_box
+ */
+TextBox* text_box_alloc();
+
+/** Deinitialize and free text_box
+ * @param text_box text_box instance
+ */
+void text_box_free(TextBox* text_box);
+
+/** Get text_box view
+ * @param text_box TextBox instance
+ * @return View instance that can be used for embedding
+ */
+View* text_box_get_view(TextBox* text_box);
+
+/** Clean text_box
+ * @param text_box TextBox instance
+ */
+void text_box_clean(TextBox* text_box);
+
+/** Set text for text_box
+ * @param text_box TextBox instance
+ * @param text text to set
+ */
+void text_box_set_text(TextBox* text_box, const char* text);
+
+/** Set TextBox font
+ * @param text_box TextBox instance
+ * @param font TextBoxFont instance
+ */
+void text_box_set_font(TextBox* text_box, TextBoxFont font);
+
+/** Set text_box context
+ * @param text_box TextBox instance
+ * @param context context pointer
+ */
+void text_box_set_context(TextBox* text_box, void* context);
+
+/** Set exit callback
+ * @param text_box TextBox instance
+ * @param callback TextBoxExitCallback callback pointer
+ */
+void text_box_set_exit_callback(TextBox* text_box, TextBoxExitCallback callback);
+
+#ifdef __cplusplus
+}
+#endif

+ 19 - 0
applications/nfc/nfc.c

@@ -43,6 +43,12 @@ Nfc* nfc_alloc() {
     view_dispatcher_add_view(
     view_dispatcher_add_view(
         nfc->nfc_common.view_dispatcher, NfcViewByteInput, byte_input_get_view(nfc->byte_input));
         nfc->nfc_common.view_dispatcher, NfcViewByteInput, byte_input_get_view(nfc->byte_input));
 
 
+    // TextBox
+    nfc->text_box = text_box_alloc();
+    view_dispatcher_add_view(
+        nfc->nfc_common.view_dispatcher, NfcViewTextBox, text_box_get_view(nfc->text_box));
+    string_init(nfc->text_box_store);
+
     // Detect
     // Detect
     nfc->nfc_detect = nfc_detect_alloc(&nfc->nfc_common);
     nfc->nfc_detect = nfc_detect_alloc(&nfc->nfc_common);
     view_dispatcher_add_view(
     view_dispatcher_add_view(
@@ -85,6 +91,10 @@ Nfc* nfc_alloc() {
     nfc->scene_set_sak = nfc_scene_set_sak_alloc();
     nfc->scene_set_sak = nfc_scene_set_sak_alloc();
     nfc->scene_set_atqa = nfc_scene_set_atqa_alloc();
     nfc->scene_set_atqa = nfc_scene_set_atqa_alloc();
     nfc->scene_set_uid = nfc_scene_set_uid_alloc();
     nfc->scene_set_uid = nfc_scene_set_uid_alloc();
+    nfc->scene_scripts_menu = nfc_scene_scripts_menu_alloc();
+    nfc->scene_read_mifare_ul = nfc_scene_read_mifare_ul_alloc();
+    nfc->scene_read_mifare_ul_success = nfc_scene_read_mifare_ul_success_alloc();
+    nfc->scene_mifare_ul_menu = nfc_scene_mifare_ul_menu_alloc();
 
 
     view_dispatcher_add_scene(nfc->nfc_common.view_dispatcher, nfc->scene_start);
     view_dispatcher_add_scene(nfc->nfc_common.view_dispatcher, nfc->scene_start);
 
 
@@ -114,6 +124,11 @@ void nfc_free(Nfc* nfc) {
     view_dispatcher_remove_view(nfc->nfc_common.view_dispatcher, NfcViewByteInput);
     view_dispatcher_remove_view(nfc->nfc_common.view_dispatcher, NfcViewByteInput);
     byte_input_free(nfc->byte_input);
     byte_input_free(nfc->byte_input);
 
 
+    // TextBox
+    view_dispatcher_remove_view(nfc->nfc_common.view_dispatcher, NfcViewTextBox);
+    text_box_free(nfc->text_box);
+    string_clear(nfc->text_box_store);
+
     // Detect
     // Detect
     view_dispatcher_remove_view(nfc->nfc_common.view_dispatcher, NfcViewDetect);
     view_dispatcher_remove_view(nfc->nfc_common.view_dispatcher, NfcViewDetect);
     nfc_detect_free(nfc->nfc_detect);
     nfc_detect_free(nfc->nfc_detect);
@@ -154,6 +169,10 @@ void nfc_free(Nfc* nfc) {
     nfc_scene_set_sak_free(nfc->scene_set_sak);
     nfc_scene_set_sak_free(nfc->scene_set_sak);
     nfc_scene_set_atqa_free(nfc->scene_set_atqa);
     nfc_scene_set_atqa_free(nfc->scene_set_atqa);
     nfc_scene_set_uid_free(nfc->scene_set_uid);
     nfc_scene_set_uid_free(nfc->scene_set_uid);
+    nfc_scene_scripts_menu_free(nfc->scene_scripts_menu);
+    nfc_scene_read_mifare_ul_free(nfc->scene_read_mifare_ul);
+    nfc_scene_read_mifare_ul_success_free(nfc->scene_read_mifare_ul_success);
+    nfc_scene_mifare_ul_menu_free(nfc->scene_mifare_ul_menu);
 
 
     // View Dispatcher
     // View Dispatcher
     view_dispatcher_free(nfc->nfc_common.view_dispatcher);
     view_dispatcher_free(nfc->nfc_common.view_dispatcher);

+ 5 - 0
applications/nfc/nfc_device.h

@@ -6,6 +6,8 @@
 #define NFC_DEV_NAME_MAX_LEN 22
 #define NFC_DEV_NAME_MAX_LEN 22
 #define NFC_FILE_NAME_MAX_LEN 120
 #define NFC_FILE_NAME_MAX_LEN 120
 
 
+#define NFC_MIFARE_UL_MAX_SIZE 256
+
 typedef enum {
 typedef enum {
     NfcDeviceNfca,
     NfcDeviceNfca,
     NfcDeviceNfcb,
     NfcDeviceNfcb,
@@ -36,6 +38,9 @@ typedef struct {
 
 
 typedef struct {
 typedef struct {
     NfcDeviceData nfc_data;
     NfcDeviceData nfc_data;
+    uint8_t full_dump[NFC_MIFARE_UL_MAX_SIZE];
+    uint16_t dump_size;
+    // TODO delete with debug view
     uint8_t man_block[12];
     uint8_t man_block[12];
     uint8_t otp[4];
     uint8_t otp[4];
 } NfcMifareUlData;
 } NfcMifareUlData;

+ 16 - 0
applications/nfc/nfc_i.h

@@ -18,6 +18,7 @@
 #include <gui/modules/popup.h>
 #include <gui/modules/popup.h>
 #include <gui/modules/text_input.h>
 #include <gui/modules/text_input.h>
 #include <gui/modules/byte_input.h>
 #include <gui/modules/byte_input.h>
+#include <gui/modules/text_box.h>
 
 
 #include "views/nfc_detect.h"
 #include "views/nfc_detect.h"
 #include "views/nfc_emulate.h"
 #include "views/nfc_emulate.h"
@@ -38,6 +39,10 @@
 #include "scenes/nfc_scene_set_sak.h"
 #include "scenes/nfc_scene_set_sak.h"
 #include "scenes/nfc_scene_set_atqa.h"
 #include "scenes/nfc_scene_set_atqa.h"
 #include "scenes/nfc_scene_set_uid.h"
 #include "scenes/nfc_scene_set_uid.h"
+#include "scenes/nfc_scene_scripts_menu.h"
+#include "scenes/nfc_scene_read_mifare_ul.h"
+#include "scenes/nfc_scene_read_mifare_ul_success.h"
+#include "scenes/nfc_scene_mifare_ul_menu.h"
 
 
 // TODO delete debug scenes
 // TODO delete debug scenes
 #include "scenes/nfc_scene_debug_menu.h"
 #include "scenes/nfc_scene_debug_menu.h"
@@ -55,6 +60,7 @@ struct Nfc {
     NfcDevice device;
     NfcDevice device;
 
 
     char text_store[NFC_TEXT_STORE_SIZE + 1];
     char text_store[NFC_TEXT_STORE_SIZE + 1];
+    string_t text_box_store;
 
 
     // Nfc Views
     // Nfc Views
     NfcDetect* nfc_detect;
     NfcDetect* nfc_detect;
@@ -68,6 +74,7 @@ struct Nfc {
     Popup* popup;
     Popup* popup;
     TextInput* text_input;
     TextInput* text_input;
     ByteInput* byte_input;
     ByteInput* byte_input;
+    TextBox* text_box;
 
 
     // Scenes
     // Scenes
     AppScene* scene_start;
     AppScene* scene_start;
@@ -84,6 +91,10 @@ struct Nfc {
     AppScene* scene_set_sak;
     AppScene* scene_set_sak;
     AppScene* scene_set_atqa;
     AppScene* scene_set_atqa;
     AppScene* scene_set_uid;
     AppScene* scene_set_uid;
+    AppScene* scene_scripts_menu;
+    AppScene* scene_read_mifare_ul;
+    AppScene* scene_read_mifare_ul_success;
+    AppScene* scene_mifare_ul_menu;
 
 
     // TODO delete debug scenes
     // TODO delete debug scenes
     AppScene* scene_debug_menu;
     AppScene* scene_debug_menu;
@@ -99,6 +110,7 @@ typedef enum {
     NfcViewPopup,
     NfcViewPopup,
     NfcViewTextInput,
     NfcViewTextInput,
     NfcViewByteInput,
     NfcViewByteInput,
+    NfcViewTextBox,
     NfcViewDetect,
     NfcViewDetect,
     NfcViewEmulate,
     NfcViewEmulate,
     NfcViewEmv,
     NfcViewEmv,
@@ -125,6 +137,10 @@ typedef enum {
     NfcSceneSetSak,
     NfcSceneSetSak,
     NfcSceneSetAtqa,
     NfcSceneSetAtqa,
     NfcSceneSetUid,
     NfcSceneSetUid,
+    NfcSceneScriptsMenu,
+    NfcSceneReadMifareUl,
+    NfcSceneReadMifareUlSuccess,
+    NfcSceneReadMifareUlMenu,
 } NfcScene;
 } NfcScene;
 
 
 Nfc* nfc_alloc();
 Nfc* nfc_alloc();

+ 4 - 1
applications/nfc/nfc_worker.c

@@ -383,7 +383,7 @@ void nfc_worker_read_mf_ultralight(NfcWorker* nfc_worker) {
                     mf_ul_set_default_version(&mf_ul_read);
                     mf_ul_set_default_version(&mf_ul_read);
                     // Reinit device
                     // Reinit device
                     api_hal_nfc_deactivate();
                     api_hal_nfc_deactivate();
-                    if(!api_hal_nfc_detect(&dev_list, &dev_cnt, 1000, false)) {
+                    if(!api_hal_nfc_detect(&dev_list, &dev_cnt, 300, false)) {
                         FURI_LOG_E(NFC_WORKER_TAG, "Lost connection. Restarting search");
                         FURI_LOG_E(NFC_WORKER_TAG, "Lost connection. Restarting search");
                         continue;
                         continue;
                     }
                     }
@@ -439,6 +439,9 @@ void nfc_worker_read_mf_ultralight(NfcWorker* nfc_worker) {
                     result->nfc_data.uid, dev_list[0].dev.nfca.nfcId1, result->nfc_data.uid_len);
                     result->nfc_data.uid, dev_list[0].dev.nfca.nfcId1, result->nfc_data.uid_len);
                 memcpy(result->man_block, mf_ul_read.dump, 4 * 3);
                 memcpy(result->man_block, mf_ul_read.dump, 4 * 3);
                 memcpy(result->otp, &mf_ul_read.dump[4 * 3], 4);
                 memcpy(result->otp, &mf_ul_read.dump[4 * 3], 4);
+                result->dump_size = mf_ul_read.pages_readed * 4;
+                memcpy(result->full_dump, mf_ul_read.dump, result->dump_size);
+
                 for(uint8_t i = 0; i < mf_ul_read.pages_readed * 4; i += 4) {
                 for(uint8_t i = 0; i < mf_ul_read.pages_readed * 4; i += 4) {
                     printf("Page %2d: ", i / 4);
                     printf("Page %2d: ", i / 4);
                     for(uint8_t j = 0; j < 4; j++) {
                     for(uint8_t j = 0; j < 4; j++) {

+ 69 - 0
applications/nfc/scenes/nfc_scene_mifare_ul_menu.c

@@ -0,0 +1,69 @@
+#include "nfc_scene_mifare_ul_menu.h"
+#include "../nfc_i.h"
+
+#include <furi.h>
+
+enum SubmenuIndex {
+    SubmenuIndexSave,
+    SubmenuIndexEmulate,
+};
+
+void nfc_scene_mifare_ul_menu_submenu_callback(void* context, uint32_t index) {
+    Nfc* nfc = (Nfc*)context;
+
+    view_dispatcher_send_custom_event(nfc->nfc_common.view_dispatcher, index);
+}
+
+const void nfc_scene_mifare_ul_menu_on_enter(void* context) {
+    Nfc* nfc = (Nfc*)context;
+    Submenu* submenu = nfc->submenu;
+
+    submenu_add_item(
+        submenu, "Name and save", SubmenuIndexSave, nfc_scene_mifare_ul_menu_submenu_callback, nfc);
+    submenu_add_item(
+        submenu, "Emulate", SubmenuIndexEmulate, nfc_scene_mifare_ul_menu_submenu_callback, nfc);
+
+    view_dispatcher_switch_to_view(nfc->nfc_common.view_dispatcher, NfcViewMenu);
+}
+
+const bool nfc_scene_mifare_ul_menu_on_event(void* context, uint32_t event) {
+    Nfc* nfc = (Nfc*)context;
+
+    if(event == SubmenuIndexSave) {
+        view_dispatcher_add_scene(nfc->nfc_common.view_dispatcher, nfc->scene_not_implemented);
+        view_dispatcher_send_navigation_event(
+            nfc->nfc_common.view_dispatcher, ViewNavigatorEventNext);
+        return true;
+    } else if(event == SubmenuIndexEmulate) {
+        view_dispatcher_add_scene(nfc->nfc_common.view_dispatcher, nfc->scene_not_implemented);
+        view_dispatcher_send_navigation_event(
+            nfc->nfc_common.view_dispatcher, ViewNavigatorEventNext);
+        return true;
+    } else if(event == ViewNavigatorEventBack) {
+        view_dispatcher_send_back_search_scene_event(
+            nfc->nfc_common.view_dispatcher, NfcSceneStart);
+        return true;
+    }
+
+    return false;
+}
+
+const void nfc_scene_mifare_ul_menu_on_exit(void* context) {
+    Nfc* nfc = (Nfc*)context;
+
+    submenu_clean(nfc->submenu);
+}
+
+AppScene* nfc_scene_mifare_ul_menu_alloc() {
+    AppScene* scene = furi_alloc(sizeof(AppScene));
+    scene->id = NfcSceneReadMifareUlMenu;
+    scene->on_enter = nfc_scene_mifare_ul_menu_on_enter;
+    scene->on_event = nfc_scene_mifare_ul_menu_on_event;
+    scene->on_exit = nfc_scene_mifare_ul_menu_on_exit;
+
+    return scene;
+}
+
+void nfc_scene_mifare_ul_menu_free(AppScene* scene) {
+    free(scene);
+}

+ 7 - 0
applications/nfc/scenes/nfc_scene_mifare_ul_menu.h

@@ -0,0 +1,7 @@
+#pragma once
+
+#include "app_scene.h"
+
+AppScene* nfc_scene_mifare_ul_menu_alloc();
+
+void nfc_scene_mifare_ul_menu_free(AppScene* scene);

+ 67 - 0
applications/nfc/scenes/nfc_scene_read_mifare_ul.c

@@ -0,0 +1,67 @@
+#include <nfc/scenes/nfc_scene_read_mifare_ul.h>
+#include <furi.h>
+#include "../nfc_i.h"
+
+void nfc_read_mifare_ul_worker_callback(void* context) {
+    Nfc* nfc = (Nfc*)context;
+    view_dispatcher_send_custom_event(nfc->nfc_common.view_dispatcher, NfcEventMifareUl);
+}
+
+const void nfc_scene_read_mifare_ul_on_enter(void* context) {
+    Nfc* nfc = (Nfc*)context;
+
+    // Setup view
+    Popup* popup = nfc->popup;
+    popup_set_header(popup, "Detecting\nultralight", 70, 34, AlignLeft, AlignTop);
+    popup_set_icon(popup, 0, 3, &I_RFIDDolphinReceive_97x61);
+
+    // Start worker
+    nfc_worker_start(
+        nfc->nfc_common.worker,
+        NfcWorkerStateReadMfUltralight,
+        &nfc->nfc_common.worker_result,
+        nfc_read_mifare_ul_worker_callback,
+        nfc);
+    view_dispatcher_switch_to_view(nfc->nfc_common.view_dispatcher, NfcViewPopup);
+}
+
+const bool nfc_scene_read_mifare_ul_on_event(void* context, uint32_t event) {
+    Nfc* nfc = (Nfc*)context;
+
+    if(event == NfcEventMifareUl) {
+        nfc->device.data = nfc->nfc_common.worker_result.nfc_detect_data;
+        view_dispatcher_add_scene(
+            nfc->nfc_common.view_dispatcher, nfc->scene_read_mifare_ul_success);
+        view_dispatcher_send_navigation_event(
+            nfc->nfc_common.view_dispatcher, ViewNavigatorEventNext);
+        return true;
+    }
+    return false;
+}
+
+const void nfc_scene_read_mifare_ul_on_exit(void* context) {
+    Nfc* nfc = (Nfc*)context;
+
+    // Stop worker
+    nfc_worker_stop(nfc->nfc_common.worker);
+
+    // 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);
+}
+
+AppScene* nfc_scene_read_mifare_ul_alloc() {
+    AppScene* scene = furi_alloc(sizeof(AppScene));
+    scene->id = NfcSceneReadMifareUl;
+    scene->on_enter = nfc_scene_read_mifare_ul_on_enter;
+    scene->on_event = nfc_scene_read_mifare_ul_on_event;
+    scene->on_exit = nfc_scene_read_mifare_ul_on_exit;
+
+    return scene;
+}
+
+void nfc_scene_read_mifare_ul_free(AppScene* scene) {
+    free(scene);
+}

+ 7 - 0
applications/nfc/scenes/nfc_scene_read_mifare_ul.h

@@ -0,0 +1,7 @@
+#pragma once
+
+#include "app_scene.h"
+
+AppScene* nfc_scene_read_mifare_ul_alloc();
+
+void nfc_scene_read_mifare_ul_free(AppScene* scene);

+ 152 - 0
applications/nfc/scenes/nfc_scene_read_mifare_ul_success.c

@@ -0,0 +1,152 @@
+#include "nfc_scene_read_mifare_ul_success.h"
+#include "../nfc_i.h"
+
+#include <furi.h>
+#include <gui/modules/dialog_ex.h>
+#include <gui/view_dispatcher.h>
+
+#define NFC_SCENE_READ_SUCCESS_SHIFT "              "
+#define NFC_SCENE_READ_MF_UL_CUSTOM_EVENT (0UL)
+
+enum {
+    ReadMifareUlStateShowUID,
+    ReadMifareUlStateShowData,
+};
+
+void nfc_scene_read_mifare_ul_success_dialog_callback(DialogExResult result, void* context) {
+    Nfc* nfc = (Nfc*)context;
+
+    view_dispatcher_send_custom_event(nfc->nfc_common.view_dispatcher, result);
+}
+
+void nfc_scene_read_mifare_ul_success_text_box_callback(void* context) {
+    Nfc* nfc = (Nfc*)context;
+
+    view_dispatcher_send_custom_event(
+        nfc->nfc_common.view_dispatcher, NFC_SCENE_READ_MF_UL_CUSTOM_EVENT);
+}
+
+const void nfc_scene_read_mifare_ul_success_on_enter(void* context) {
+    Nfc* nfc = (Nfc*)context;
+
+    // Clear device name
+    nfc_device_set_name(&nfc->device, "");
+
+    // Send notification
+    notification_message(nfc->notifications, &sequence_success);
+
+    // Setup dialog view
+    NfcDeviceData* data =
+        (NfcDeviceData*)&nfc->nfc_common.worker_result.nfc_mifare_ul_data.nfc_data;
+    DialogEx* dialog_ex = nfc->dialog_ex;
+    dialog_ex_set_left_button_text(dialog_ex, "Retry");
+    dialog_ex_set_right_button_text(dialog_ex, "More");
+    dialog_ex_set_center_button_text(dialog_ex, "Data");
+    dialog_ex_set_header(dialog_ex, "Mifare Ultralight", 22, 8, AlignLeft, AlignCenter);
+    dialog_ex_set_icon(dialog_ex, 8, 13, &I_Medium_chip_22x21);
+    // Display UID
+    nfc_set_text_store(
+        nfc,
+        NFC_SCENE_READ_SUCCESS_SHIFT "ATQA: %02X%02X\n" NFC_SCENE_READ_SUCCESS_SHIFT
+                                     "SAK: %02X\nUID: %02X %02X %02X %02X %02X %02X %02X",
+        data->atqa[0],
+        data->atqa[1],
+        data->sak,
+        data->uid[0],
+        data->uid[1],
+        data->uid[2],
+        data->uid[3],
+        data->uid[4],
+        data->uid[5],
+        data->uid[6]);
+    dialog_ex_set_text(dialog_ex, nfc->text_store, 8, 16, AlignLeft, AlignTop);
+    dialog_ex_set_context(dialog_ex, nfc);
+    dialog_ex_set_result_callback(dialog_ex, nfc_scene_read_mifare_ul_success_dialog_callback);
+
+    // Setup TextBox view
+    NfcMifareUlData* mf_ul_data =
+        (NfcMifareUlData*)&nfc->nfc_common.worker_result.nfc_mifare_ul_data;
+    TextBox* text_box = nfc->text_box;
+    text_box_set_context(text_box, nfc);
+    text_box_set_exit_callback(text_box, nfc_scene_read_mifare_ul_success_text_box_callback);
+    text_box_set_font(text_box, TextBoxFontHex);
+    for(uint16_t i = 0; i < mf_ul_data->dump_size; i += 2) {
+        if(!(i % 8) && i) {
+            string_push_back(nfc->text_box_store, '\n');
+        }
+        string_cat_printf(
+            nfc->text_box_store,
+            "%02X%02X ",
+            mf_ul_data->full_dump[i],
+            mf_ul_data->full_dump[i + 1]);
+    }
+    text_box_set_text(text_box, string_get_cstr(nfc->text_box_store));
+
+    nfc->scene_read_mifare_ul_success->state = ReadMifareUlStateShowUID;
+    view_dispatcher_switch_to_view(nfc->nfc_common.view_dispatcher, NfcViewDialogEx);
+}
+
+const bool nfc_scene_read_mifare_ul_success_on_event(void* context, uint32_t event) {
+    Nfc* nfc = (Nfc*)context;
+
+    if((nfc->scene_read_mifare_ul_success->state == ReadMifareUlStateShowUID) &&
+       (event == DialogExResultLeft)) {
+        view_dispatcher_send_navigation_event(
+            nfc->nfc_common.view_dispatcher, ViewNavigatorEventBack);
+        return true;
+    } else if(
+        (nfc->scene_read_mifare_ul_success->state == ReadMifareUlStateShowUID) &&
+        (event == DialogExResultRight)) {
+        view_dispatcher_add_scene(nfc->nfc_common.view_dispatcher, nfc->scene_mifare_ul_menu);
+        view_dispatcher_send_navigation_event(
+            nfc->nfc_common.view_dispatcher, ViewNavigatorEventNext);
+        return true;
+    } else if(
+        (nfc->scene_read_mifare_ul_success->state == ReadMifareUlStateShowUID) &&
+        (event == DialogExResultCenter)) {
+        view_dispatcher_switch_to_view(nfc->nfc_common.view_dispatcher, NfcViewTextBox);
+        nfc->scene_read_mifare_ul_success->state = ReadMifareUlStateShowData;
+        return true;
+    } else if(
+        (nfc->scene_read_mifare_ul_success->state == ReadMifareUlStateShowData) &&
+        (event == NFC_SCENE_READ_MF_UL_CUSTOM_EVENT)) {
+        view_dispatcher_switch_to_view(nfc->nfc_common.view_dispatcher, NfcViewDialogEx);
+        nfc->scene_read_mifare_ul_success->state = ReadMifareUlStateShowUID;
+        return true;
+    }
+    return false;
+}
+
+const void nfc_scene_read_mifare_ul_success_on_exit(void* context) {
+    Nfc* nfc = (Nfc*)context;
+
+    // Clean dialog
+    DialogEx* dialog_ex = nfc->dialog_ex;
+    dialog_ex_set_header(dialog_ex, NULL, 0, 0, AlignCenter, AlignCenter);
+    dialog_ex_set_text(dialog_ex, NULL, 0, 0, AlignCenter, AlignTop);
+    dialog_ex_set_icon(dialog_ex, 0, 0, NULL);
+    dialog_ex_set_left_button_text(dialog_ex, NULL);
+    dialog_ex_set_right_button_text(dialog_ex, NULL);
+    dialog_ex_set_center_button_text(dialog_ex, NULL);
+    dialog_ex_set_result_callback(dialog_ex, NULL);
+    dialog_ex_set_context(dialog_ex, NULL);
+
+    // Clean TextBox
+    TextBox* text_box = nfc->text_box;
+    text_box_clean(text_box);
+    string_clean(nfc->text_box_store);
+}
+
+AppScene* nfc_scene_read_mifare_ul_success_alloc() {
+    AppScene* scene = furi_alloc(sizeof(AppScene));
+    scene->id = NfcSceneReadMifareUlSuccess;
+    scene->on_enter = nfc_scene_read_mifare_ul_success_on_enter;
+    scene->on_event = nfc_scene_read_mifare_ul_success_on_event;
+    scene->on_exit = nfc_scene_read_mifare_ul_success_on_exit;
+
+    return scene;
+}
+
+void nfc_scene_read_mifare_ul_success_free(AppScene* scene) {
+    free(scene);
+}

+ 7 - 0
applications/nfc/scenes/nfc_scene_read_mifare_ul_success.h

@@ -0,0 +1,7 @@
+#pragma once
+
+#include "app_scene.h"
+
+AppScene* nfc_scene_read_mifare_ul_success_alloc();
+
+void nfc_scene_read_mifare_ul_success_free(AppScene* scene);

+ 75 - 0
applications/nfc/scenes/nfc_scene_scripts_menu.c

@@ -0,0 +1,75 @@
+#include "nfc_scene_scripts_menu.h"
+#include "../nfc_i.h"
+
+#include <furi.h>
+#include <gui/modules/submenu.h>
+#include <gui/view_dispatcher.h>
+
+enum SubmenuIndex {
+    SubmenuIndexBankCard,
+    SubmenuIndexMifareUltralight,
+};
+
+void nfc_scene_scripts_menu_submenu_callback(void* context, uint32_t index) {
+    Nfc* nfc = (Nfc*)context;
+
+    view_dispatcher_send_custom_event(nfc->nfc_common.view_dispatcher, index);
+}
+
+const void nfc_scene_scripts_menu_on_enter(void* context) {
+    Nfc* nfc = (Nfc*)context;
+    Submenu* submenu = nfc->submenu;
+
+    submenu_add_item(
+        submenu,
+        "Read bank card",
+        SubmenuIndexBankCard,
+        nfc_scene_scripts_menu_submenu_callback,
+        nfc);
+    submenu_add_item(
+        submenu,
+        "Read Mifare Ultralight",
+        SubmenuIndexMifareUltralight,
+        nfc_scene_scripts_menu_submenu_callback,
+        nfc);
+
+    view_dispatcher_switch_to_view(nfc->nfc_common.view_dispatcher, NfcViewMenu);
+}
+
+const bool nfc_scene_scripts_menu_on_event(void* context, uint32_t event) {
+    Nfc* nfc = (Nfc*)context;
+
+    if(event == SubmenuIndexBankCard) {
+        view_dispatcher_add_scene(nfc->nfc_common.view_dispatcher, nfc->scene_not_implemented);
+        view_dispatcher_send_navigation_event(
+            nfc->nfc_common.view_dispatcher, ViewNavigatorEventNext);
+        return true;
+    } else if(event == SubmenuIndexMifareUltralight) {
+        view_dispatcher_add_scene(nfc->nfc_common.view_dispatcher, nfc->scene_read_mifare_ul);
+        view_dispatcher_send_navigation_event(
+            nfc->nfc_common.view_dispatcher, ViewNavigatorEventNext);
+        return true;
+    }
+
+    return false;
+}
+
+const void nfc_scene_scripts_menu_on_exit(void* context) {
+    Nfc* nfc = (Nfc*)context;
+
+    submenu_clean(nfc->submenu);
+}
+
+AppScene* nfc_scene_scripts_menu_alloc() {
+    AppScene* scene = furi_alloc(sizeof(AppScene));
+    scene->id = NfcSceneScriptsMenu;
+    scene->on_enter = nfc_scene_scripts_menu_on_enter;
+    scene->on_event = nfc_scene_scripts_menu_on_event;
+    scene->on_exit = nfc_scene_scripts_menu_on_exit;
+
+    return scene;
+}
+
+void nfc_scene_scripts_menu_free(AppScene* scene) {
+    free(scene);
+}

+ 7 - 0
applications/nfc/scenes/nfc_scene_scripts_menu.h

@@ -0,0 +1,7 @@
+#pragma once
+
+#include "app_scene.h"
+
+AppScene* nfc_scene_scripts_menu_alloc();
+
+void nfc_scene_scripts_menu_free(AppScene* scene);

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

@@ -49,7 +49,7 @@ const bool nfc_scene_start_on_event(void* context, uint32_t event) {
             nfc->nfc_common.view_dispatcher, ViewNavigatorEventNext);
             nfc->nfc_common.view_dispatcher, ViewNavigatorEventNext);
         return true;
         return true;
     } else if(event == SubmenuIndexRunScript) {
     } else if(event == SubmenuIndexRunScript) {
-        view_dispatcher_add_scene(nfc->nfc_common.view_dispatcher, nfc->scene_not_implemented);
+        view_dispatcher_add_scene(nfc->nfc_common.view_dispatcher, nfc->scene_scripts_menu);
         view_dispatcher_send_navigation_event(
         view_dispatcher_send_navigation_event(
             nfc->nfc_common.view_dispatcher, ViewNavigatorEventNext);
             nfc->nfc_common.view_dispatcher, ViewNavigatorEventNext);
         return true;
         return true;

+ 1 - 0
lib/app_scene_template/app_scene.h

@@ -5,6 +5,7 @@
 
 
 typedef struct {
 typedef struct {
     uint32_t id;
     uint32_t id;
+    uint32_t state;
     const void (*on_enter)(void* context);
     const void (*on_enter)(void* context);
     const bool (*on_event)(void* context, uint32_t event);
     const bool (*on_event)(void* context, uint32_t event);
     const void (*on_exit)(void* context);
     const void (*on_exit)(void* context);