MX hai 1 ano
pai
achega
636441b6c3

+ 21 - 13
application.fam

@@ -7,26 +7,25 @@ App(
     entry_point="seader_app",
     cdefines=["APP_SEADER"],
     requires=[
-        "gui",
-        "storage",
-        "nfc",
+        "gui", "storage", "nfc",
     ],
-    stack_size=7 * 1024,
+    stack_size=5 * 1024,
     order=20,
     sources=[
-        "*.c",
-        "aeabi_uldivmod.sx",
+      "*.c",
+      "aeabi_uldivmod.sx",
+      "!plugin/*.c",
     ],
     fap_icon="icons/logo.png",
     fap_category="NFC",
-    fap_version="2.6",
+    fap_version="2.8",
     fap_author="bettse",
-    #    fap_extbuild=(
-    #        ExtFile(
-    #            path="${FAP_SRC_DIR}/lib/asn1/asn_system.h",
-    #            command="asn1c -D ${FAP_SRC_DIR}/lib/asn1 -no-gen-example -pdu=all ${FAP_SRC_DIR}/seader.asn1"
-    #        ),
-    #    ),
+#    fap_extbuild=(
+#        ExtFile(
+#            path="${FAP_SRC_DIR}/lib/asn1/asn_system.h",
+#            command="asn1c -D ${FAP_SRC_DIR}/lib/asn1 -no-gen-example -pdu=all ${FAP_SRC_DIR}/seader.asn1"
+#        ),
+#    ),
     fap_libs=["mbedtls"],
     fap_private_libs=[
         Lib(
@@ -41,3 +40,12 @@ App(
     fap_weburl="https://seader.ericbetts.dev",
     fap_icon_assets="icons",
 )
+
+App(
+    appid="plugin_wiegand",
+    apptype=FlipperAppType.PLUGIN,
+    entry_point="plugin_wiegand_ep",
+    requires=["seader"],
+    sources=["plugin/wiegand.c"],
+    fal_embedded=True,
+)

+ 14 - 0
plugin/README.md

@@ -0,0 +1,14 @@
+# Flipper zero wiegand plugin
+
+Add as git submodule: `git submodule add https://gitlab.com/bettse/flipper-wiegand-plugin.git plugin`
+
+Add to your `application.fam`
+```
+App(
+    appid="plugin_wiegand",
+    apptype=FlipperAppType.PLUGIN,
+    entry_point="plugin_wiegand_ep",
+    requires=["seader"],
+    sources=["plugin/wiegand.c"],
+)
+```

+ 20 - 0
plugin/interface.h

@@ -0,0 +1,20 @@
+/**
+ * @file plugin_interface.h
+ * @brief Example plugin interface.
+ *
+ * Common interface between a plugin and host application
+ */
+#pragma once
+
+#include <stdint.h>
+#include <stddef.h>
+#include <furi.h>
+
+#define PLUGIN_APP_ID "plugin_wiegand"
+#define PLUGIN_API_VERSION 1
+
+typedef struct {
+    const char* name;
+    int (*count)(uint8_t, uint64_t);
+    void (*description)(uint8_t, uint64_t, size_t, FuriString*);
+} PluginWiegand;

+ 152 - 0
plugin/wiegand.c

@@ -0,0 +1,152 @@
+
+#include "interface.h"
+
+#include <lib/bit_lib/bit_lib.h>
+#include <flipper_application/flipper_application.h>
+
+/*
+ * Huge thanks to the proxmark codebase:
+ * https://github.com/RfidResearchGroup/proxmark3/blob/master/client/src/wiegand_formats.c
+ */
+
+// Structure for packed wiegand messages
+// Always align lowest value (last transmitted) bit to ordinal position 0 (lowest valued bit bottom)
+typedef struct {
+    uint8_t Length; // Number of encoded bits in wiegand message (excluding headers and preamble)
+    uint32_t Top; // Bits in x<<64 positions
+    uint32_t Mid; // Bits in x<<32 positions
+    uint32_t Bot; // Lowest ordinal positions
+} wiegand_message_t;
+
+static inline uint8_t oddparity32(uint32_t x) {
+    return bit_lib_test_parity_32(x, BitLibParityOdd);
+}
+
+static inline uint8_t evenparity32(uint32_t x) {
+    return bit_lib_test_parity_32(x, BitLibParityEven);
+}
+
+static int wiegand_C1k35s_parse(uint8_t bit_length, uint64_t bits, FuriString* description) {
+    if(bit_length != 35) {
+        return 0;
+    }
+
+    wiegand_message_t value;
+    value.Mid = bits >> 32;
+    value.Bot = bits;
+    wiegand_message_t* packed = &value;
+
+    uint32_t cn = (packed->Bot >> 1) & 0x000FFFFF;
+    uint32_t fc = ((packed->Mid & 1) << 11) | ((packed->Bot >> 21));
+    bool valid = (evenparity32((packed->Mid & 0x1) ^ (packed->Bot & 0xB6DB6DB6)) ==
+                  ((packed->Mid >> 1) & 1)) &&
+                 (oddparity32((packed->Mid & 0x3) ^ (packed->Bot & 0x6DB6DB6C)) ==
+                  ((packed->Bot >> 0) & 1)) &&
+                 (oddparity32((packed->Mid & 0x3) ^ (packed->Bot & 0xFFFFFFFF)) ==
+                  ((packed->Mid >> 2) & 1));
+
+    if(valid) {
+        furi_string_cat_printf(description, "C1k35s\nFC: %ld CN: %ld\n", fc, cn);
+        return 1;
+    }
+
+    return 0;
+}
+
+static int wiegand_h10301_parse(uint8_t bit_length, uint64_t bits, FuriString* description) {
+    if(bit_length != 26) {
+        return 0;
+    }
+
+    //E XXXX XXXX XXXX
+    //XXXX XXXX XXXX O
+    uint32_t eBitMask = 0x02000000;
+    uint32_t oBitMask = 0x00000001;
+    uint32_t eParityMask = 0x01FFE000;
+    uint32_t oParityMask = 0x00001FFE;
+    uint8_t eBit = (eBitMask & bits) >> 25;
+    uint8_t oBit = (oBitMask & bits) >> 0;
+
+    bool eParity = bit_lib_test_parity_32((bits & eParityMask) >> 13, BitLibParityEven) ==
+                   (eBit == 1);
+    bool oParity = bit_lib_test_parity_32((bits & oParityMask) >> 1, BitLibParityOdd) ==
+                   (oBit == 1);
+
+    FURI_LOG_D(
+        PLUGIN_APP_ID,
+        "eBit: %d, oBit: %d, eParity: %d, oParity: %d",
+        eBit,
+        oBit,
+        eParity,
+        oParity);
+
+    if(eParity && oParity) {
+        uint32_t cnMask = 0x1FFFE;
+        uint16_t cn = ((bits & cnMask) >> 1);
+
+        uint32_t fcMask = 0x1FE0000;
+        uint16_t fc = ((bits & fcMask) >> 17);
+
+        furi_string_cat_printf(description, "H10301\nFC: %d CN: %d\n", fc, cn);
+        return 1;
+    }
+
+    return 0;
+}
+
+static int wiegand_format_count(uint8_t bit_length, uint64_t bits) {
+    UNUSED(bit_length);
+    UNUSED(bits);
+    int count = 0;
+    FuriString* ignore = furi_string_alloc();
+
+    count += wiegand_h10301_parse(bit_length, bits, ignore);
+    count += wiegand_C1k35s_parse(bit_length, bits, ignore);
+
+    furi_string_free(ignore);
+
+    FURI_LOG_I(PLUGIN_APP_ID, "count: %i", count);
+    return count;
+}
+
+static void wiegand_format_description(
+    uint8_t bit_length,
+    uint64_t bits,
+    size_t index,
+    FuriString* description) {
+    FURI_LOG_I(PLUGIN_APP_ID, "description %d", index);
+    UNUSED(bit_length);
+    UNUSED(bits);
+
+    size_t i = 0;
+
+    i += wiegand_h10301_parse(bit_length, bits, description);
+    if(i - 1 == index) {
+        return;
+    }
+    i += wiegand_C1k35s_parse(bit_length, bits, description);
+    if(i - 1 == index) {
+        return;
+    }
+
+    furi_string_cat_printf(description, "[%i] <name> FC: CN:", index);
+}
+
+/* Actual implementation of app<>plugin interface */
+static const PluginWiegand plugin_wiegand = {
+    .name = "Plugin Wiegand",
+    .count = &wiegand_format_count,
+    .description = &wiegand_format_description,
+};
+
+/* Plugin descriptor to comply with basic plugin specification */
+static const FlipperAppPluginDescriptor plugin_wiegand_descriptor = {
+    .appid = PLUGIN_APP_ID,
+    .ep_api_version = PLUGIN_API_VERSION,
+    .entry_point = &plugin_wiegand,
+};
+
+/* Plugin entry point - must return a pointer to const descriptor */
+const FlipperAppPluginDescriptor* plugin_wiegand_ep(void) {
+    return &plugin_wiegand_descriptor;
+}

+ 186 - 10
sam_api.c

@@ -1,6 +1,7 @@
 
 #include "sam_api.h"
 #include <toolbox/path.h>
+#include <bit_lib/bit_lib.h>
 
 #define TAG "SAMAPI"
 
@@ -728,6 +729,161 @@ void seader_iso14443a_transmit(
     bit_buffer_free(rx_buffer);
 }
 
+/* Assumes this is called in the context of the NFC API callback */
+#define MF_CLASSIC_FWT_FC (60000)
+void seader_mfc_transmit(
+    Seader* seader,
+    MfClassicPoller* mfc_poller,
+    uint8_t* buffer,
+    size_t len,
+    uint16_t timeout,
+    uint8_t format[3]) {
+    UNUSED(timeout);
+
+    furi_assert(seader);
+    furi_assert(buffer);
+    furi_assert(mfc_poller);
+    SeaderWorker* seader_worker = seader->worker;
+    SeaderUartBridge* seader_uart = seader_worker->uart;
+
+    BitBuffer* tx_buffer = bit_buffer_alloc(len);
+    BitBuffer* rx_buffer = bit_buffer_alloc(SEADER_POLLER_MAX_BUFFER_SIZE);
+
+    do {
+        if(format[0] == 0x00 && format[1] == 0xC0 && format[2] == 0x00) {
+            bit_buffer_append_bytes(tx_buffer, buffer, len);
+            MfClassicError error =
+                mf_classic_poller_send_frame(mfc_poller, tx_buffer, rx_buffer, MF_CLASSIC_FWT_FC);
+            if(error != MfClassicErrorNone) {
+                FURI_LOG_W(TAG, "mf_classic_poller_send_frame error %d", error);
+                seader_worker->stage = SeaderPollerEventTypeFail;
+                break;
+            }
+        } else if(
+            (format[0] == 0x00 && format[1] == 0x00 && format[2] == 0x40) ||
+            (format[0] == 0x00 && format[1] == 0x00 && format[2] == 0x24) ||
+            (format[0] == 0x00 && format[1] == 0x00 && format[2] == 0x44)) {
+            memset(display, 0, sizeof(display));
+            for(uint8_t i = 0; i < len; i++) {
+                snprintf(display + (i * 2), sizeof(display), "%02x", buffer[i]);
+            }
+            FURI_LOG_D(TAG, "NFC Send with parity %d: %s", len, display);
+
+            // Only handles message up to 8 data bytes
+            uint8_t tx_parity = 0;
+            uint8_t len_without_parity = len - 1;
+
+            // Don't forget to swap the bits of buffer[8]
+            for(size_t i = 0; i < len; i++) {
+                bit_lib_reverse_bits(buffer + i, 0, 8);
+            }
+
+            // Pull out parity bits
+            for(size_t i = 0; i < len_without_parity; i++) {
+                bool val = bit_lib_get_bit(buffer + i + 1, i);
+                bit_lib_set_bit(&tx_parity, i, val);
+            }
+
+            for(size_t i = 0; i < len_without_parity; i++) {
+                buffer[i] = (buffer[i] << i) | (buffer[i + 1] >> (8 - i));
+            }
+            bit_buffer_append_bytes(tx_buffer, buffer, len_without_parity);
+
+            for(size_t i = 0; i < len_without_parity; i++) {
+                bit_lib_reverse_bits(buffer + i, 0, 8);
+                bit_buffer_set_byte_with_parity(
+                    tx_buffer, i, buffer[i], bit_lib_get_bit(&tx_parity, i));
+            }
+
+            memset(display, 0, sizeof(display));
+            for(uint8_t i = 0; i < bit_buffer_get_size_bytes(tx_buffer); i++) {
+                snprintf(
+                    display + (i * 2), sizeof(display), "%02x", bit_buffer_get_byte(tx_buffer, i));
+            }
+            FURI_LOG_D(
+                TAG,
+                "NFC Send without parity %d: %s [%02x]",
+                bit_buffer_get_size_bytes(tx_buffer),
+                display,
+                tx_parity);
+
+            MfClassicError error = mf_classic_poller_send_custom_parity_frame(
+                mfc_poller, tx_buffer, rx_buffer, MF_CLASSIC_FWT_FC);
+            if(error != MfClassicErrorNone) {
+                FURI_LOG_W(TAG, "mf_classic_poller_send_encrypted_frame error %d", error);
+                seader_worker->stage = SeaderPollerEventTypeFail;
+                break;
+            }
+
+            size_t length = bit_buffer_get_size_bytes(rx_buffer);
+            const uint8_t* rx_parity = bit_buffer_get_parity(rx_buffer);
+
+            memset(display, 0, sizeof(display));
+            for(uint8_t i = 0; i < length; i++) {
+                snprintf(
+                    display + (i * 2), sizeof(display), "%02x", bit_buffer_get_byte(rx_buffer, i));
+            }
+            FURI_LOG_D(
+                TAG, "NFC Response without parity %d: %s [%02x]", length, display, rx_parity[0]);
+
+            uint8_t with_parity[SEADER_POLLER_MAX_BUFFER_SIZE];
+            memset(with_parity, 0, sizeof(with_parity));
+
+            for(size_t i = 0; i < length; i++) {
+                uint8_t b = bit_buffer_get_byte(rx_buffer, i);
+                bit_lib_reverse_bits(&b, 0, 8);
+                bit_buffer_set_byte(rx_buffer, i, b);
+            }
+
+            length = length + (length / 8) + 1;
+
+            uint8_t parts = 1 + length / 9;
+            for(size_t p = 0; p < parts; p++) {
+                uint8_t doffset = p * 9;
+                uint8_t soffset = p * 8;
+
+                for(size_t i = 0; i < 9; i++) {
+                    with_parity[i + doffset] = bit_buffer_get_byte(rx_buffer, i + soffset) >> i;
+                    if(i > 0) {
+                        with_parity[i + doffset] |= bit_buffer_get_byte(rx_buffer, i + soffset - 1)
+                                                    << (9 - i);
+                    }
+
+                    if(i > 0) {
+                        bool val = bit_lib_get_bit(rx_parity, i - 1);
+                        bit_lib_set_bit(with_parity + i, i - 1, val);
+                    }
+                }
+            }
+
+            for(size_t i = 0; i < length; i++) {
+                bit_lib_reverse_bits(with_parity + i, 0, 8);
+            }
+
+            bit_buffer_copy_bytes(rx_buffer, with_parity, length);
+
+            memset(display, 0, sizeof(display));
+            for(uint8_t i = 0; i < length; i++) {
+                snprintf(
+                    display + (i * 2), sizeof(display), "%02x", bit_buffer_get_byte(rx_buffer, i));
+            }
+            FURI_LOG_D(
+                TAG, "NFC Response with parity %d: %s [%02x]", length, display, rx_parity[0]);
+
+        } else {
+            FURI_LOG_W(TAG, "UNHANDLED FORMAT");
+        }
+
+        seader_send_nfc_rx(
+            seader_uart,
+            (uint8_t*)bit_buffer_get_data(rx_buffer),
+            bit_buffer_get_size_bytes(rx_buffer));
+
+    } while(false);
+    bit_buffer_free(tx_buffer);
+    bit_buffer_free(rx_buffer);
+}
+
 void seader_parse_nfc_command_transmit(
     Seader* seader,
     NFCSend_t* nfcSend,
@@ -757,13 +913,23 @@ void seader_parse_nfc_command_transmit(
         seader_iso15693_transmit(
             seader, spc->picopass_poller, nfcSend->data.buf, nfcSend->data.size);
     } else if(frameProtocol == FrameProtocol_nfc) {
-        seader_iso14443a_transmit(
-            seader,
-            spc->iso14443_4a_poller,
-            nfcSend->data.buf,
-            nfcSend->data.size,
-            (uint16_t)timeOut,
-            nfcSend->format->buf);
+        if(spc->iso14443_4a_poller) {
+            seader_iso14443a_transmit(
+                seader,
+                spc->iso14443_4a_poller,
+                nfcSend->data.buf,
+                nfcSend->data.size,
+                (uint16_t)timeOut,
+                nfcSend->format->buf);
+        } else if(spc->mfc_poller) {
+            seader_mfc_transmit(
+                seader,
+                spc->mfc_poller,
+                nfcSend->data.buf,
+                nfcSend->data.size,
+                (uint16_t)timeOut,
+                nfcSend->format->buf);
+        }
     } else {
         FURI_LOG_W(TAG, "unknown frame protocol %lx", frameProtocol);
     }
@@ -855,6 +1021,12 @@ bool seader_process_success_response_i(
     if(rval.code == RC_OK) {
 #ifdef ASN1_DEBUG
         if(online == false) {
+            memset(display, 0, sizeof(display));
+            for(uint8_t i = 0; i < len - 6; i++) {
+                snprintf(display + (i * 2), sizeof(display), "%02x", apdu[i + 6]);
+            }
+            FURI_LOG_D(TAG, "incoming APDU %s", display);
+
             char payloadDebug[384] = {0};
             memset(payloadDebug, 0, sizeof(payloadDebug));
             (&asn_DEF_Payload)
@@ -905,18 +1077,22 @@ NfcCommand seader_worker_card_detect(
     OCTET_STRING_t atqa_string = {.buf = atqa, .size = 2};
     uint8_t protocol_bytes[] = {0x00, 0x00};
 
-    if(sak == 0 && atqa == NULL) {
+    if(sak == 0 && atqa == NULL) { // picopass
         protocol_bytes[1] = FrameProtocol_iclass;
         OCTET_STRING_fromBuf(
             &cardDetails->protocol, (const char*)protocol_bytes, sizeof(protocol_bytes));
         memcpy(credential->diversifier, uid, uid_len);
         credential->diversifier_len = uid_len;
         credential->isDesfire = false;
-    } else {
+    } else if(atqa == 0) { // MFC
+        protocol_bytes[1] = FrameProtocol_nfc;
+        OCTET_STRING_fromBuf(
+            &cardDetails->protocol, (const char*)protocol_bytes, sizeof(protocol_bytes));
+        cardDetails->sak = &sak_string;
+    } else { // type 4
         protocol_bytes[1] = FrameProtocol_nfc;
         OCTET_STRING_fromBuf(
             &cardDetails->protocol, (const char*)protocol_bytes, sizeof(protocol_bytes));
-
         cardDetails->sak = &sak_string;
         cardDetails->atqa = &atqa_string;
         credential->isDesfire = seader_mf_df_check_card_type(atqa[0], atqa[1], sak);

+ 19 - 0
scenes/seader_scene_card_menu.c

@@ -1,6 +1,7 @@
 #include "../seader_i.h"
 
 enum SubmenuIndex {
+    SubmenuIndexParse,
     SubmenuIndexSave,
     SubmenuIndexSavePicopass,
     SubmenuIndexSaveRFID,
@@ -17,8 +18,21 @@ void seader_scene_card_menu_submenu_callback(void* context, uint32_t index) {
 void seader_scene_card_menu_on_enter(void* context) {
     Seader* seader = context;
     SeaderCredential* credential = seader->credential;
+    PluginWiegand* plugin = seader->plugin_wiegand;
     Submenu* submenu = seader->submenu;
 
+    if(plugin) {
+        size_t format_count = plugin->count(credential->bit_length, credential->credential);
+        if(format_count > 0) {
+            submenu_add_item(
+                submenu,
+                "Parse",
+                SubmenuIndexParse,
+                seader_scene_card_menu_submenu_callback,
+                seader);
+        }
+    }
+
     submenu_add_item(
         submenu, "Save", SubmenuIndexSave, seader_scene_card_menu_submenu_callback, seader);
     submenu_add_item(
@@ -85,6 +99,11 @@ bool seader_scene_card_menu_on_event(void* context, SceneManagerEvent event) {
             seader->credential->save_format = SeaderCredentialSaveFormatMFC;
             scene_manager_next_scene(seader->scene_manager, SeaderSceneSaveName);
             consumed = true;
+        } else if(event.event == SubmenuIndexParse) {
+            scene_manager_set_scene_state(
+                seader->scene_manager, SeaderSceneCardMenu, SubmenuIndexParse);
+            scene_manager_next_scene(seader->scene_manager, SeaderSceneFormats);
+            consumed = true;
         }
     } else if(event.type == SceneManagerEventTypeBack) {
         consumed = scene_manager_search_and_switch_to_previous_scene(

+ 2 - 0
scenes/seader_scene_config.h

@@ -15,3 +15,5 @@ ADD_SCENE(seader, delete_success, DeleteSuccess)
 ADD_SCENE(seader, credential_info, CredentialInfo)
 ADD_SCENE(seader, sam_info, SamInfo)
 ADD_SCENE(seader, virtual_credential, VirtualCredential)
+ADD_SCENE(seader, formats, Formats)
+ADD_SCENE(seader, read_mfc, ReadMfc)

+ 16 - 5
scenes/seader_scene_credential_info.c

@@ -16,6 +16,7 @@ void seader_scene_credential_info_widget_callback(
 void seader_scene_credential_info_on_enter(void* context) {
     Seader* seader = context;
     SeaderCredential* credential = seader->credential;
+    PluginWiegand* plugin = seader->plugin_wiegand;
     Widget* widget = seader->widget;
 
     FuriString* type_str = furi_string_alloc();
@@ -23,11 +24,6 @@ void seader_scene_credential_info_on_enter(void* context) {
     FuriString* credential_str = furi_string_alloc();
     FuriString* sio_str = furi_string_alloc();
 
-    dolphin_deed(DolphinDeedNfcReadSuccess);
-
-    // Send notification
-    notification_message(seader->notifications, &sequence_success);
-
     furi_string_set(credential_str, "");
     furi_string_set(bitlength_str, "");
     furi_string_set(sio_str, "");
@@ -53,6 +49,18 @@ void seader_scene_credential_info_on_enter(void* context) {
         seader_scene_credential_info_widget_callback,
         seader);
 
+    if(plugin) {
+        size_t format_count = plugin->count(credential->bit_length, credential->credential);
+        if(format_count > 0) {
+            widget_add_button_element(
+                seader->widget,
+                GuiButtonTypeCenter,
+                "Parse",
+                seader_scene_credential_info_widget_callback,
+                seader);
+        }
+    }
+
     widget_add_string_element(
         widget, 64, 5, AlignCenter, AlignCenter, FontPrimary, furi_string_get_cstr(type_str));
     widget_add_string_element(
@@ -93,6 +101,9 @@ bool seader_scene_credential_info_on_event(void* context, SceneManagerEvent even
     if(event.type == SceneManagerEventTypeCustom) {
         if(event.event == GuiButtonTypeLeft) {
             consumed = scene_manager_previous_scene(seader->scene_manager);
+        } else if(event.event == GuiButtonTypeCenter) {
+            scene_manager_next_scene(seader->scene_manager, SeaderSceneFormats);
+            consumed = true;
         } else if(event.event == SeaderCustomEventViewExit) {
             view_dispatcher_switch_to_view(seader->view_dispatcher, SeaderViewWidget);
             consumed = true;

+ 47 - 0
scenes/seader_scene_formats.c

@@ -0,0 +1,47 @@
+#include "../seader_i.h"
+#include <dolphin/dolphin.h>
+
+void seader_scene_formats_on_enter(void* context) {
+    Seader* seader = context;
+    PluginWiegand* plugin = seader->plugin_wiegand;
+    SeaderCredential* credential = seader->credential;
+
+    FuriString* str = seader->text_box_store;
+    furi_string_reset(str);
+
+    if(plugin) {
+        FuriString* description = furi_string_alloc();
+        size_t format_count = plugin->count(credential->bit_length, credential->credential);
+        for(size_t i = 0; i < format_count; i++) {
+            plugin->description(credential->bit_length, credential->credential, i, description);
+
+            furi_string_cat_printf(str, "%s\n", furi_string_get_cstr(description));
+        }
+        furi_string_free(description);
+    }
+
+    text_box_set_font(seader->text_box, TextBoxFontHex);
+    text_box_set_text(seader->text_box, furi_string_get_cstr(seader->text_box_store));
+    view_dispatcher_switch_to_view(seader->view_dispatcher, SeaderViewTextBox);
+}
+
+bool seader_scene_formats_on_event(void* context, SceneManagerEvent event) {
+    Seader* seader = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == GuiButtonTypeLeft) {
+            consumed = scene_manager_previous_scene(seader->scene_manager);
+        }
+    } else if(event.type == SceneManagerEventTypeBack) {
+        consumed = scene_manager_previous_scene(seader->scene_manager);
+    }
+    return consumed;
+}
+
+void seader_scene_formats_on_exit(void* context) {
+    Seader* seader = context;
+
+    // Clear views
+    text_box_reset(seader->text_box);
+}

+ 65 - 0
scenes/seader_scene_read_mfc.c

@@ -0,0 +1,65 @@
+#include "../seader_i.h"
+#include <dolphin/dolphin.h>
+
+#define TAG "SceneReadNfc"
+
+void seader_scene_read_mfc_on_enter(void* context) {
+    Seader* seader = context;
+    dolphin_deed(DolphinDeedNfcRead);
+
+    // Setup view
+    Popup* popup = seader->popup;
+    popup_set_header(popup, "Detecting\nMFC\ncard", 68, 30, AlignLeft, AlignTop);
+    popup_set_icon(popup, 0, 3, &I_RFIDDolphinReceive_97x61);
+
+    // Start worker
+    view_dispatcher_switch_to_view(seader->view_dispatcher, SeaderViewPopup);
+
+    seader->poller = nfc_poller_alloc(seader->nfc, NfcProtocolMfClassic);
+
+    seader->worker->stage = SeaderPollerEventTypeCardDetect;
+    seader_credential_clear(seader->credential);
+    seader->credential->type = SeaderCredentialTypeMifareClassic;
+
+    FURI_LOG_W(TAG, "Start poller");
+    nfc_poller_start(seader->poller, seader_worker_poller_callback_mfc, seader);
+
+    seader_blink_start(seader);
+}
+
+bool seader_scene_read_mfc_on_event(void* context, SceneManagerEvent event) {
+    Seader* seader = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == SeaderCustomEventWorkerExit) {
+            seader->credential->type = SeaderCredentialTypeMifareClassic;
+            scene_manager_next_scene(seader->scene_manager, SeaderSceneReadCardSuccess);
+            consumed = true;
+        } else if(event.event == SeaderCustomEventPollerSuccess) {
+            seader->credential->type = SeaderCredentialTypeMifareClassic;
+            scene_manager_next_scene(seader->scene_manager, SeaderSceneReadCardSuccess);
+            consumed = true;
+        }
+    } else if(event.type == SceneManagerEventTypeBack) {
+        scene_manager_search_and_switch_to_previous_scene(
+            seader->scene_manager, SeaderSceneSamPresent);
+        consumed = true;
+    }
+
+    return consumed;
+}
+
+void seader_scene_read_mfc_on_exit(void* context) {
+    Seader* seader = context;
+
+    if(seader->poller) {
+        nfc_poller_stop(seader->poller);
+        nfc_poller_free(seader->poller);
+    }
+
+    // Clear view
+    popup_reset(seader->popup);
+
+    seader_blink_stop(seader);
+}

+ 12 - 0
scenes/seader_scene_sam_present.c

@@ -2,6 +2,7 @@
 enum SubmenuIndex {
     SubmenuIndexReadPicopass,
     SubmenuIndexRead14a,
+    SubmenuIndexReadMfc,
     SubmenuIndexSaved,
     SubmenuIndexSamInfo,
     SubmenuIndexFwVersion,
@@ -33,6 +34,12 @@ void seader_scene_sam_present_on_update(void* context) {
         SubmenuIndexRead14a,
         seader_scene_sam_present_submenu_callback,
         seader);
+    submenu_add_item(
+        submenu,
+        "Read MFC",
+        SubmenuIndexReadMfc,
+        seader_scene_sam_present_submenu_callback,
+        seader);
     submenu_add_item(
         submenu, "Saved", SubmenuIndexSaved, seader_scene_sam_present_submenu_callback, seader);
 
@@ -75,6 +82,11 @@ bool seader_scene_sam_present_on_event(void* context, SceneManagerEvent event) {
                 seader->scene_manager, SeaderSceneSamPresent, SubmenuIndexRead14a);
             scene_manager_next_scene(seader->scene_manager, SeaderSceneRead14a);
             consumed = true;
+        } else if(event.event == SubmenuIndexReadMfc) {
+            scene_manager_set_scene_state(
+                seader->scene_manager, SeaderSceneSamPresent, SubmenuIndexReadMfc);
+            scene_manager_next_scene(seader->scene_manager, SeaderSceneReadMfc);
+            consumed = true;
         } else if(event.event == SubmenuIndexSamInfo) {
             scene_manager_set_scene_state(
                 seader->scene_manager, SeaderSceneSamPresent, SubmenuIndexSamInfo);

+ 3 - 3
scenes/seader_scene_saved_menu.c

@@ -1,8 +1,8 @@
 #include "../seader_i.h"
 
 enum SubmenuIndex {
-    SubmenuIndexDelete,
     SubmenuIndexInfo,
+    SubmenuIndexDelete,
     SubmenuIndexVirtual,
 };
 
@@ -17,10 +17,10 @@ void seader_scene_saved_menu_on_enter(void* context) {
     SeaderCredential* credential = seader->credential;
     Submenu* submenu = seader->submenu;
 
-    submenu_add_item(
-        submenu, "Delete", SubmenuIndexDelete, seader_scene_saved_menu_submenu_callback, seader);
     submenu_add_item(
         submenu, "Info", SubmenuIndexInfo, seader_scene_saved_menu_submenu_callback, seader);
+    submenu_add_item(
+        submenu, "Delete", SubmenuIndexDelete, seader_scene_saved_menu_submenu_callback, seader);
 
     if(credential->sio[0] == 0x30) {
         submenu_add_item(

+ 34 - 10
seader.c

@@ -1,5 +1,4 @@
 #include "seader_i.h"
-#include <expansion/expansion.h>
 
 #define TAG "Seader"
 
@@ -81,11 +80,38 @@ Seader* seader_alloc() {
     view_dispatcher_add_view(
         seader->view_dispatcher, SeaderViewTextInput, text_input_get_view(seader->text_input));
 
+    // TextBox
+    seader->text_box = text_box_alloc();
+    view_dispatcher_add_view(
+        seader->view_dispatcher, SeaderViewTextBox, text_box_get_view(seader->text_box));
+    seader->text_box_store = furi_string_alloc();
+
     // Custom Widget
     seader->widget = widget_alloc();
     view_dispatcher_add_view(
         seader->view_dispatcher, SeaderViewWidget, widget_get_view(seader->widget));
 
+    seader->plugin_manager =
+        plugin_manager_alloc(PLUGIN_APP_ID, PLUGIN_API_VERSION, firmware_api_interface);
+
+    seader->plugin_wiegand = NULL;
+    if(plugin_manager_load_all(seader->plugin_manager, APP_ASSETS_PATH("plugins")) !=
+       PluginManagerErrorNone) {
+        FURI_LOG_E(TAG, "Failed to load all libs");
+    } else {
+        uint32_t plugin_count = plugin_manager_get_count(seader->plugin_manager);
+        FURI_LOG_I(TAG, "Loaded %lu plugin(s)", plugin_count);
+
+        for(uint32_t i = 0; i < plugin_count; i++) {
+            const PluginWiegand* plugin = plugin_manager_get_ep(seader->plugin_manager, i);
+            FURI_LOG_I(TAG, "plugin name: %s", plugin->name);
+            if(strcmp(plugin->name, "Plugin Wiegand") == 0) {
+                // Have to cast to drop "const" qualifier
+                seader->plugin_wiegand = (PluginWiegand*)plugin;
+            }
+        }
+    }
+
     return seader;
 }
 
@@ -123,6 +149,11 @@ void seader_free(Seader* seader) {
     view_dispatcher_remove_view(seader->view_dispatcher, SeaderViewTextInput);
     text_input_free(seader->text_input);
 
+    // TextBox
+    view_dispatcher_remove_view(seader->view_dispatcher, SeaderViewTextBox);
+    text_box_free(seader->text_box);
+    furi_string_free(seader->text_box_store);
+
     // Custom Widget
     view_dispatcher_remove_view(seader->view_dispatcher, SeaderViewWidget);
     widget_free(seader->widget);
@@ -145,6 +176,8 @@ void seader_free(Seader* seader) {
     furi_record_close(RECORD_NOTIFICATION);
     seader->notifications = NULL;
 
+    plugin_manager_free(seader->plugin_manager);
+
     free(seader);
 }
 
@@ -196,11 +229,6 @@ void seader_show_loading_popup(void* context, bool show) {
 
 int32_t seader_app(void* p) {
     UNUSED(p);
-
-    // Disable expansion protocol to avoid interference with UART Handle
-    Expansion* expansion = furi_record_open(RECORD_EXPANSION);
-    expansion_disable(expansion);
-
     Seader* seader = seader_alloc();
 
     scene_manager_next_scene(seader->scene_manager, SeaderSceneStart);
@@ -209,9 +237,5 @@ int32_t seader_app(void* p) {
 
     seader_free(seader);
 
-    // Return previous state of expansion
-    expansion_enable(expansion);
-    furi_record_close(RECORD_EXPANSION);
-
     return 0;
 }

+ 1 - 1
seader_bridge.h

@@ -9,7 +9,7 @@
 #include <furi.h>
 #include <furi_hal.h>
 
-#define SEADER_UART_RX_BUF_SIZE (256)
+#define SEADER_UART_RX_BUF_SIZE (128)
 
 typedef struct {
     uint8_t uart_ch;

+ 1 - 0
seader_credential.c

@@ -397,6 +397,7 @@ bool seader_credential_save_picopass(SeaderCredential* cred, const char* name) {
     FlipperFormat* file = flipper_format_file_alloc(cred->storage);
     FuriString* temp_str = furi_string_alloc();
 
+    storage_simply_mkdir(cred->storage, EXT_PATH("apps_data/picopass"));
     if(use_load_path && !furi_string_empty(cred->load_path)) {
         // Get directory name
         path_extract_dirname(furi_string_get_cstr(cred->load_path), temp_str);

+ 12 - 3
seader_i.h

@@ -18,15 +18,13 @@
 #include <gui/modules/popup.h>
 #include <gui/modules/loading.h>
 #include <gui/modules/text_input.h>
+#include <gui/modules/text_box.h>
 #include <gui/modules/widget.h>
 
 #include <input/input.h>
 
 #include <lib/nfc/nfc.h>
-#include <lib/nfc/protocols/iso14443_3a/iso14443_3a.h>
-
 #include <nfc/nfc_poller.h>
-
 #include <nfc/nfc_device.h>
 #include <nfc/helpers/nfc_data_generator.h>
 
@@ -40,6 +38,11 @@
 #include <Payload.h>
 #include <FrameProtocol.h>
 
+#include "plugin/interface.h"
+#include <flipper_application/flipper_application.h>
+#include <flipper_application/plugins/plugin_manager.h>
+#include <loader/firmware_api/firmware_api.h>
+
 #include "protocol/picopass_poller.h"
 #include "scenes/seader_scene.h"
 
@@ -103,6 +106,7 @@ struct Seader {
     Popup* popup;
     Loading* loading;
     TextInput* text_input;
+    TextBox* text_box;
     Widget* widget;
 
     Nfc* nfc;
@@ -110,10 +114,14 @@ struct Seader {
     PicopassPoller* picopass_poller;
 
     NfcDevice* nfc_device;
+
+    PluginManager* plugin_manager;
+    PluginWiegand* plugin_wiegand;
 };
 
 struct SeaderPollerContainer {
     Iso14443_4aPoller* iso14443_4a_poller;
+    MfClassicPoller* mfc_poller;
     PicopassPoller* picopass_poller;
 };
 
@@ -122,6 +130,7 @@ typedef enum {
     SeaderViewPopup,
     SeaderViewLoading,
     SeaderViewTextInput,
+    SeaderViewTextBox,
     SeaderViewWidget,
     SeaderViewUart,
 } SeaderView;

+ 49 - 0
seader_worker.c

@@ -286,6 +286,55 @@ NfcCommand seader_worker_poller_callback_iso14443_4a(NfcGenericEvent event, void
         } else if(seader_worker->stage == SeaderPollerEventTypeComplete) {
             ret = NfcCommandStop;
         }
+    } else if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeError) {
+        Iso14443_4aPollerEventData* data = iso14443_4a_event->data;
+        Iso14443_4aError error = data->error;
+        FURI_LOG_W(TAG, "Iso14443_4aError %i", error);
+        // I was hoping to catch MFC here, but it seems to be treated the same (None) as no card being present.
+        switch(error) {
+        case Iso14443_4aErrorNone:
+            break;
+        case Iso14443_4aErrorNotPresent:
+            break;
+        case Iso14443_4aErrorProtocol:
+            ret = NfcCommandStop;
+            break;
+        case Iso14443_4aErrorTimeout:
+            break;
+        }
+    }
+
+    return ret;
+}
+
+NfcCommand seader_worker_poller_callback_mfc(NfcGenericEvent event, void* context) {
+    furi_assert(event.protocol == NfcProtocolMfClassic);
+    NfcCommand ret = NfcCommandContinue;
+
+    Seader* seader = context;
+    SeaderWorker* seader_worker = seader->worker;
+
+    MfClassicPollerEvent* mfc_event = event.event_data;
+    SeaderPollerContainer spc = {.mfc_poller = event.instance};
+
+    if(mfc_event->type == MfClassicPollerEventTypeSuccess) {
+        if(seader_worker->stage == SeaderPollerEventTypeCardDetect) {
+            const MfClassicData* mfc_data = nfc_poller_get_data(seader->poller);
+            uint8_t sak = iso14443_3a_get_sak(mfc_data->iso14443_3a_data);
+            size_t uid_len = 0;
+            const uint8_t* uid = mf_classic_get_uid(mfc_data, &uid_len);
+            seader_worker_card_detect(seader, sak, NULL, uid, uid_len, NULL, 0);
+            furi_thread_set_current_priority(FuriThreadPriorityLowest);
+            seader_worker->stage = SeaderPollerEventTypeConversation;
+        } else if(seader_worker->stage == SeaderPollerEventTypeConversation) {
+            seader_worker_poller_conversation(seader, &spc);
+        } else if(seader_worker->stage == SeaderPollerEventTypeComplete) {
+            ret = NfcCommandStop;
+        } else if(seader_worker->stage == SeaderPollerEventTypeFail) {
+            ret = NfcCommandStop;
+        }
+    } else if(mfc_event->type == MfClassicPollerEventTypeFail) {
+        ret = NfcCommandStop;
     }
 
     return ret;

+ 2 - 1
seader_worker.h

@@ -1,6 +1,7 @@
 #pragma once
 
 #include <lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h>
+#include <lib/nfc/protocols/mf_classic/mf_classic_poller.h>
 
 #include "sam_api.h"
 #include "seader_credential.h"
@@ -65,5 +66,5 @@ bool seader_worker_process_sam_message(Seader* seader, CCID_Message* message);
 void seader_worker_send_version(Seader* seader);
 
 NfcCommand seader_worker_poller_callback_iso14443_4a(NfcGenericEvent event, void* context);
-
+NfcCommand seader_worker_poller_callback_mfc(NfcGenericEvent event, void* context);
 NfcCommand seader_worker_poller_callback_picopass(PicopassPollerEvent event, void* context);