Przeglądaj źródła

[FL-2933] Mf Classic initial write, update, detect reader (#1941)

* nfc: introduce nfc write
* nfc: add write logic
* nfc worker: add write state
* nfc: add mfc update logic
* nfc: add update success logic
* nfc: add custom card for detect reader
* nfc: update write logic
* nfc: add halt command, add notifications
* nfc: add write fail scene
* nfc: fixes and clean up
* nfc: fix navigation ad notifications
* nfc: fix detect reader nfc data setter

Co-authored-by: あく <alleteam@gmail.com>
gornekich 3 lat temu
rodzic
commit
93a6e17ce5

+ 6 - 0
applications/main/nfc/scenes/nfc_scene_config.h

@@ -36,6 +36,12 @@ ADD_SCENE(nfc, mf_classic_keys_list, MfClassicKeysList)
 ADD_SCENE(nfc, mf_classic_keys_delete, MfClassicKeysDelete)
 ADD_SCENE(nfc, mf_classic_keys_warn_duplicate, MfClassicKeysWarnDuplicate)
 ADD_SCENE(nfc, mf_classic_dict_attack, MfClassicDictAttack)
+ADD_SCENE(nfc, mf_classic_write, MfClassicWrite)
+ADD_SCENE(nfc, mf_classic_write_success, MfClassicWriteSuccess)
+ADD_SCENE(nfc, mf_classic_write_fail, MfClassicWriteFail)
+ADD_SCENE(nfc, mf_classic_update, MfClassicUpdate)
+ADD_SCENE(nfc, mf_classic_update_success, MfClassicUpdateSuccess)
+ADD_SCENE(nfc, mf_classic_wrong_card, MfClassicWrongCard)
 ADD_SCENE(nfc, emv_read_success, EmvReadSuccess)
 ADD_SCENE(nfc, emv_menu, EmvMenu)
 ADD_SCENE(nfc, emulate_apdu_sequence, EmulateApduSequence)

+ 5 - 0
applications/main/nfc/scenes/nfc_scene_detect_reader.c

@@ -28,6 +28,11 @@ void nfc_scene_detect_reader_on_enter(void* context) {
 
     detect_reader_set_callback(nfc->detect_reader, nfc_scene_detect_reader_callback, nfc);
     detect_reader_set_nonces_max(nfc->detect_reader, NFC_SCENE_DETECT_READER_PAIR_NONCES_MAX);
+    NfcDeviceData* dev_data = &nfc->dev->dev_data;
+    if(dev_data->nfc_data.uid_len) {
+        detect_reader_set_uid(
+            nfc->detect_reader, dev_data->nfc_data.uid, dev_data->nfc_data.uid_len);
+    }
 
     // Store number of collected nonces in scene state
     scene_manager_set_scene_state(nfc->scene_manager, NfcSceneDetectReader, 0);

+ 98 - 0
applications/main/nfc/scenes/nfc_scene_mf_classic_update.c

@@ -0,0 +1,98 @@
+#include "../nfc_i.h"
+#include <dolphin/dolphin.h>
+
+enum {
+    NfcSceneMfClassicUpdateStateCardSearch,
+    NfcSceneMfClassicUpdateStateCardFound,
+};
+
+bool nfc_mf_classic_update_worker_callback(NfcWorkerEvent event, void* context) {
+    furi_assert(context);
+
+    Nfc* nfc = context;
+    view_dispatcher_send_custom_event(nfc->view_dispatcher, event);
+
+    return true;
+}
+
+static void nfc_scene_mf_classic_update_setup_view(Nfc* nfc) {
+    Popup* popup = nfc->popup;
+    popup_reset(popup);
+    uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfClassicUpdate);
+
+    if(state == NfcSceneMfClassicUpdateStateCardSearch) {
+        popup_set_text(
+            nfc->popup, "Apply the initial\ncard only", 128, 32, AlignRight, AlignCenter);
+        popup_set_icon(nfc->popup, 0, 8, &I_NFC_manual_60x50);
+    } else {
+        popup_set_header(popup, "Updating\nDon't move...", 52, 32, AlignLeft, AlignCenter);
+        popup_set_icon(popup, 12, 23, &A_Loading_24);
+    }
+
+    view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup);
+}
+
+void nfc_scene_mf_classic_update_on_enter(void* context) {
+    Nfc* nfc = context;
+    DOLPHIN_DEED(DolphinDeedNfcEmulate);
+
+    scene_manager_set_scene_state(
+        nfc->scene_manager, NfcSceneMfClassicUpdate, NfcSceneMfClassicUpdateStateCardSearch);
+    nfc_scene_mf_classic_update_setup_view(nfc);
+
+    // Setup and start worker
+    nfc_worker_start(
+        nfc->worker,
+        NfcWorkerStateMfClassicUpdate,
+        &nfc->dev->dev_data,
+        nfc_mf_classic_update_worker_callback,
+        nfc);
+    nfc_blink_emulate_start(nfc);
+}
+
+bool nfc_scene_mf_classic_update_on_event(void* context, SceneManagerEvent event) {
+    Nfc* nfc = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == NfcWorkerEventSuccess) {
+            nfc_worker_stop(nfc->worker);
+            if(nfc_device_save_shadow(nfc->dev, nfc->dev->dev_name)) {
+                scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicUpdateSuccess);
+            } else {
+                scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicWrongCard);
+            }
+            consumed = true;
+        } else if(event.event == NfcWorkerEventWrongCard) {
+            nfc_worker_stop(nfc->worker);
+            scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicWrongCard);
+            consumed = true;
+        } else if(event.event == NfcWorkerEventCardDetected) {
+            scene_manager_set_scene_state(
+                nfc->scene_manager,
+                NfcSceneMfClassicUpdate,
+                NfcSceneMfClassicUpdateStateCardFound);
+            nfc_scene_mf_classic_update_setup_view(nfc);
+            consumed = true;
+        } else if(event.event == NfcWorkerEventNoCardDetected) {
+            scene_manager_set_scene_state(
+                nfc->scene_manager,
+                NfcSceneMfClassicUpdate,
+                NfcSceneMfClassicUpdateStateCardSearch);
+            nfc_scene_mf_classic_update_setup_view(nfc);
+            consumed = true;
+        }
+    }
+    return consumed;
+}
+
+void nfc_scene_mf_classic_update_on_exit(void* context) {
+    Nfc* nfc = context;
+    nfc_worker_stop(nfc->worker);
+    scene_manager_set_scene_state(
+        nfc->scene_manager, NfcSceneMfClassicUpdate, NfcSceneMfClassicUpdateStateCardSearch);
+    // Clear view
+    popup_reset(nfc->popup);
+
+    nfc_blink_stop(nfc);
+}

+ 44 - 0
applications/main/nfc/scenes/nfc_scene_mf_classic_update_success.c

@@ -0,0 +1,44 @@
+#include "../nfc_i.h"
+#include <dolphin/dolphin.h>
+
+void nfc_scene_mf_classic_update_success_popup_callback(void* context) {
+    Nfc* nfc = context;
+    view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit);
+}
+
+void nfc_scene_mf_classic_update_success_on_enter(void* context) {
+    Nfc* nfc = context;
+    DOLPHIN_DEED(DolphinDeedNfcSave);
+
+    notification_message(nfc->notifications, &sequence_success);
+
+    Popup* popup = nfc->popup;
+    popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59);
+    popup_set_header(popup, "Updated!", 11, 20, AlignLeft, AlignBottom);
+    popup_set_timeout(popup, 1500);
+    popup_set_context(popup, nfc);
+    popup_set_callback(popup, nfc_scene_mf_classic_update_success_popup_callback);
+    popup_enable_timeout(popup);
+
+    view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup);
+}
+
+bool nfc_scene_mf_classic_update_success_on_event(void* context, SceneManagerEvent event) {
+    Nfc* nfc = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == NfcCustomEventViewExit) {
+            consumed = scene_manager_search_and_switch_to_previous_scene(
+                nfc->scene_manager, NfcSceneFileSelect);
+        }
+    }
+    return consumed;
+}
+
+void nfc_scene_mf_classic_update_success_on_exit(void* context) {
+    Nfc* nfc = context;
+
+    // Clear view
+    popup_reset(nfc->popup);
+}

+ 92 - 0
applications/main/nfc/scenes/nfc_scene_mf_classic_write.c

@@ -0,0 +1,92 @@
+#include "../nfc_i.h"
+#include <dolphin/dolphin.h>
+
+enum {
+    NfcSceneMfClassicWriteStateCardSearch,
+    NfcSceneMfClassicWriteStateCardFound,
+};
+
+bool nfc_mf_classic_write_worker_callback(NfcWorkerEvent event, void* context) {
+    furi_assert(context);
+
+    Nfc* nfc = context;
+    view_dispatcher_send_custom_event(nfc->view_dispatcher, event);
+
+    return true;
+}
+
+static void nfc_scene_mf_classic_write_setup_view(Nfc* nfc) {
+    Popup* popup = nfc->popup;
+    popup_reset(popup);
+    uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfClassicWrite);
+
+    if(state == NfcSceneMfClassicWriteStateCardSearch) {
+        popup_set_text(
+            nfc->popup, "Apply the initial\ncard only", 128, 32, AlignRight, AlignCenter);
+        popup_set_icon(nfc->popup, 0, 8, &I_NFC_manual_60x50);
+    } else {
+        popup_set_header(popup, "Writing\nDon't move...", 52, 32, AlignLeft, AlignCenter);
+        popup_set_icon(popup, 12, 23, &A_Loading_24);
+    }
+
+    view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup);
+}
+
+void nfc_scene_mf_classic_write_on_enter(void* context) {
+    Nfc* nfc = context;
+    DOLPHIN_DEED(DolphinDeedNfcEmulate);
+
+    scene_manager_set_scene_state(
+        nfc->scene_manager, NfcSceneMfClassicWrite, NfcSceneMfClassicWriteStateCardSearch);
+    nfc_scene_mf_classic_write_setup_view(nfc);
+
+    // Setup and start worker
+    nfc_worker_start(
+        nfc->worker,
+        NfcWorkerStateMfClassicWrite,
+        &nfc->dev->dev_data,
+        nfc_mf_classic_write_worker_callback,
+        nfc);
+    nfc_blink_emulate_start(nfc);
+}
+
+bool nfc_scene_mf_classic_write_on_event(void* context, SceneManagerEvent event) {
+    Nfc* nfc = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == NfcWorkerEventSuccess) {
+            scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicWriteSuccess);
+            consumed = true;
+        } else if(event.event == NfcWorkerEventFail) {
+            scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicWriteFail);
+            consumed = true;
+        } else if(event.event == NfcWorkerEventWrongCard) {
+            scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicWrongCard);
+            consumed = true;
+        } else if(event.event == NfcWorkerEventCardDetected) {
+            scene_manager_set_scene_state(
+                nfc->scene_manager, NfcSceneMfClassicWrite, NfcSceneMfClassicWriteStateCardFound);
+            nfc_scene_mf_classic_write_setup_view(nfc);
+            consumed = true;
+        } else if(event.event == NfcWorkerEventNoCardDetected) {
+            scene_manager_set_scene_state(
+                nfc->scene_manager, NfcSceneMfClassicWrite, NfcSceneMfClassicWriteStateCardSearch);
+            nfc_scene_mf_classic_write_setup_view(nfc);
+            consumed = true;
+        }
+    }
+    return consumed;
+}
+
+void nfc_scene_mf_classic_write_on_exit(void* context) {
+    Nfc* nfc = context;
+
+    nfc_worker_stop(nfc->worker);
+    scene_manager_set_scene_state(
+        nfc->scene_manager, NfcSceneMfClassicWrite, NfcSceneMfClassicWriteStateCardSearch);
+    // Clear view
+    popup_reset(nfc->popup);
+
+    nfc_blink_stop(nfc);
+}

+ 58 - 0
applications/main/nfc/scenes/nfc_scene_mf_classic_write_fail.c

@@ -0,0 +1,58 @@
+#include "../nfc_i.h"
+
+void nfc_scene_mf_classic_write_fail_widget_callback(
+    GuiButtonType result,
+    InputType type,
+    void* context) {
+    Nfc* nfc = context;
+    if(type == InputTypeShort) {
+        view_dispatcher_send_custom_event(nfc->view_dispatcher, result);
+    }
+}
+
+void nfc_scene_mf_classic_write_fail_on_enter(void* context) {
+    Nfc* nfc = context;
+    Widget* widget = nfc->widget;
+
+    notification_message(nfc->notifications, &sequence_error);
+
+    widget_add_icon_element(widget, 72, 17, &I_DolphinCommon_56x48);
+    widget_add_string_element(
+        widget, 7, 4, AlignLeft, AlignTop, FontPrimary, "Writing gone wrong!");
+    widget_add_string_multiline_element(
+        widget,
+        7,
+        17,
+        AlignLeft,
+        AlignTop,
+        FontSecondary,
+        "Not all sectors\nwere written\ncorrectly.");
+
+    widget_add_button_element(
+        widget, GuiButtonTypeLeft, "Finish", nfc_scene_mf_classic_write_fail_widget_callback, nfc);
+
+    // Setup and start worker
+    view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget);
+}
+
+bool nfc_scene_mf_classic_write_fail_on_event(void* context, SceneManagerEvent event) {
+    Nfc* nfc = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == GuiButtonTypeLeft) {
+            consumed = scene_manager_search_and_switch_to_previous_scene(
+                nfc->scene_manager, NfcSceneFileSelect);
+        }
+    } else if(event.type == SceneManagerEventTypeBack) {
+        consumed = scene_manager_search_and_switch_to_previous_scene(
+            nfc->scene_manager, NfcSceneSavedMenu);
+    }
+    return consumed;
+}
+
+void nfc_scene_mf_classic_write_fail_on_exit(void* context) {
+    Nfc* nfc = context;
+
+    widget_reset(nfc->widget);
+}

+ 44 - 0
applications/main/nfc/scenes/nfc_scene_mf_classic_write_success.c

@@ -0,0 +1,44 @@
+#include "../nfc_i.h"
+#include <dolphin/dolphin.h>
+
+void nfc_scene_mf_classic_write_success_popup_callback(void* context) {
+    Nfc* nfc = context;
+    view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit);
+}
+
+void nfc_scene_mf_classic_write_success_on_enter(void* context) {
+    Nfc* nfc = context;
+    DOLPHIN_DEED(DolphinDeedNfcSave);
+
+    notification_message(nfc->notifications, &sequence_success);
+
+    Popup* popup = nfc->popup;
+    popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59);
+    popup_set_header(popup, "Successfully\nwritten", 13, 22, AlignLeft, AlignBottom);
+    popup_set_timeout(popup, 1500);
+    popup_set_context(popup, nfc);
+    popup_set_callback(popup, nfc_scene_mf_classic_write_success_popup_callback);
+    popup_enable_timeout(popup);
+
+    view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup);
+}
+
+bool nfc_scene_mf_classic_write_success_on_event(void* context, SceneManagerEvent event) {
+    Nfc* nfc = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == NfcCustomEventViewExit) {
+            consumed = scene_manager_search_and_switch_to_previous_scene(
+                nfc->scene_manager, NfcSceneFileSelect);
+        }
+    }
+    return consumed;
+}
+
+void nfc_scene_mf_classic_write_success_on_exit(void* context) {
+    Nfc* nfc = context;
+
+    // Clear view
+    popup_reset(nfc->popup);
+}

+ 53 - 0
applications/main/nfc/scenes/nfc_scene_mf_classic_wrong_card.c

@@ -0,0 +1,53 @@
+#include "../nfc_i.h"
+
+void nfc_scene_mf_classic_wrong_card_widget_callback(
+    GuiButtonType result,
+    InputType type,
+    void* context) {
+    Nfc* nfc = context;
+    if(type == InputTypeShort) {
+        view_dispatcher_send_custom_event(nfc->view_dispatcher, result);
+    }
+}
+
+void nfc_scene_mf_classic_wrong_card_on_enter(void* context) {
+    Nfc* nfc = context;
+    Widget* widget = nfc->widget;
+
+    notification_message(nfc->notifications, &sequence_error);
+
+    widget_add_icon_element(widget, 73, 17, &I_DolphinCommon_56x48);
+    widget_add_string_element(
+        widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "This is wrong card");
+    widget_add_string_multiline_element(
+        widget,
+        4,
+        17,
+        AlignLeft,
+        AlignTop,
+        FontSecondary,
+        "Data management\nis only possible\nwith initial card");
+    widget_add_button_element(
+        widget, GuiButtonTypeLeft, "Retry", nfc_scene_mf_classic_wrong_card_widget_callback, nfc);
+
+    // Setup and start worker
+    view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget);
+}
+
+bool nfc_scene_mf_classic_wrong_card_on_event(void* context, SceneManagerEvent event) {
+    Nfc* nfc = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == GuiButtonTypeLeft) {
+            consumed = scene_manager_previous_scene(nfc->scene_manager);
+        }
+    }
+    return consumed;
+}
+
+void nfc_scene_mf_classic_wrong_card_on_exit(void* context) {
+    Nfc* nfc = context;
+
+    widget_reset(nfc->widget);
+}

+ 34 - 0
applications/main/nfc/scenes/nfc_scene_saved_menu.c

@@ -4,6 +4,9 @@
 enum SubmenuIndex {
     SubmenuIndexEmulate,
     SubmenuIndexEditUid,
+    SubmenuIndexDetectReader,
+    SubmenuIndexWrite,
+    SubmenuIndexUpdate,
     SubmenuIndexRename,
     SubmenuIndexDelete,
     SubmenuIndexInfo,
@@ -42,6 +45,28 @@ void nfc_scene_saved_menu_on_enter(void* context) {
         submenu_add_item(
             submenu, "Emulate", SubmenuIndexEmulate, nfc_scene_saved_menu_submenu_callback, nfc);
     }
+    if(nfc->dev->format == NfcDeviceSaveFormatMifareClassic) {
+        if(!mf_classic_is_card_read(&nfc->dev->dev_data.mf_classic_data)) {
+            submenu_add_item(
+                submenu,
+                "Detect reader",
+                SubmenuIndexDetectReader,
+                nfc_scene_saved_menu_submenu_callback,
+                nfc);
+        }
+        submenu_add_item(
+            submenu,
+            "Write To Initial Card",
+            SubmenuIndexWrite,
+            nfc_scene_saved_menu_submenu_callback,
+            nfc);
+        submenu_add_item(
+            submenu,
+            "Update From Initial Card",
+            SubmenuIndexUpdate,
+            nfc_scene_saved_menu_submenu_callback,
+            nfc);
+    }
     submenu_add_item(
         submenu, "Info", SubmenuIndexInfo, nfc_scene_saved_menu_submenu_callback, nfc);
     if(nfc->dev->shadow_file_exist) {
@@ -79,6 +104,15 @@ bool nfc_scene_saved_menu_on_event(void* context, SceneManagerEvent event) {
             }
             DOLPHIN_DEED(DolphinDeedNfcEmulate);
             consumed = true;
+        } else if(event.event == SubmenuIndexDetectReader) {
+            scene_manager_next_scene(nfc->scene_manager, NfcSceneDetectReader);
+            consumed = true;
+        } else if(event.event == SubmenuIndexWrite) {
+            scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicWrite);
+            consumed = true;
+        } else if(event.event == SubmenuIndexUpdate) {
+            scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicUpdate);
+            consumed = true;
         } else if(event.event == SubmenuIndexRename) {
             scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveName);
             consumed = true;

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

@@ -53,6 +53,7 @@ bool nfc_scene_start_on_event(void* context, SceneManagerEvent event) {
         } else if(event.event == SubmenuIndexDetectReader) {
             bool sd_exist = storage_sd_status(nfc->dev->storage) == FSE_OK;
             if(sd_exist) {
+                nfc_device_data_clear(&nfc->dev->dev_data);
                 scene_manager_next_scene(nfc->scene_manager, NfcSceneDetectReader);
                 DOLPHIN_DEED(DolphinDeedNfcDetectReader);
             } else {

+ 36 - 0
applications/main/nfc/views/detect_reader.c

@@ -2,6 +2,8 @@
 #include <assets_icons.h>
 #include <gui/elements.h>
 
+#define DETECT_READER_UID_MAX_LEN (10)
+
 struct DetectReader {
     View* view;
     DetectReaderDoneCallback callback;
@@ -12,6 +14,7 @@ typedef struct {
     uint16_t nonces;
     uint16_t nonces_max;
     DetectReaderState state;
+    FuriString* uid_str;
 } DetectReaderViewModel;
 
 static void detect_reader_draw_callback(Canvas* canvas, void* model) {
@@ -23,6 +26,10 @@ static void detect_reader_draw_callback(Canvas* canvas, void* model) {
     if(m->state == DetectReaderStateStart) {
         snprintf(text, sizeof(text), "Touch the reader");
         canvas_draw_icon(canvas, 21, 13, &I_Move_flipper_26x39);
+        if(furi_string_size(m->uid_str)) {
+            elements_multiline_text_aligned(
+                canvas, 64, 64, AlignCenter, AlignBottom, furi_string_get_cstr(m->uid_str));
+        }
     } else if(m->state == DetectReaderStateReaderDetected) {
         snprintf(text, sizeof(text), "Move the Flipper away");
         canvas_draw_icon(canvas, 24, 25, &I_Release_arrow_18x15);
@@ -86,12 +93,24 @@ DetectReader* detect_reader_alloc() {
     view_set_input_callback(detect_reader->view, detect_reader_input_callback);
     view_set_context(detect_reader->view, detect_reader);
 
+    with_view_model(
+        detect_reader->view,
+        DetectReaderViewModel * model,
+        { model->uid_str = furi_string_alloc(); },
+        false);
+
     return detect_reader;
 }
 
 void detect_reader_free(DetectReader* detect_reader) {
     furi_assert(detect_reader);
 
+    with_view_model(
+        detect_reader->view,
+        DetectReaderViewModel * model,
+        { furi_string_free(model->uid_str); },
+        false);
+
     view_free(detect_reader->view);
     free(detect_reader);
 }
@@ -106,6 +125,7 @@ void detect_reader_reset(DetectReader* detect_reader) {
             model->nonces = 0;
             model->nonces_max = 0;
             model->state = DetectReaderStateStart;
+            furi_string_reset(model->uid_str);
         },
         false);
 }
@@ -152,3 +172,19 @@ void detect_reader_set_state(DetectReader* detect_reader, DetectReaderState stat
     with_view_model(
         detect_reader->view, DetectReaderViewModel * model, { model->state = state; }, true);
 }
+
+void detect_reader_set_uid(DetectReader* detect_reader, uint8_t* uid, uint8_t uid_len) {
+    furi_assert(detect_reader);
+    furi_assert(uid);
+    furi_assert(uid_len < DETECT_READER_UID_MAX_LEN);
+    with_view_model(
+        detect_reader->view,
+        DetectReaderViewModel * model,
+        {
+            furi_string_set_str(model->uid_str, "UID:");
+            for(size_t i = 0; i < uid_len; i++) {
+                furi_string_cat_printf(model->uid_str, " %02X", uid[i]);
+            }
+        },
+        true);
+}

+ 2 - 0
applications/main/nfc/views/detect_reader.h

@@ -32,3 +32,5 @@ void detect_reader_set_nonces_max(DetectReader* detect_reader, uint16_t nonces_m
 void detect_reader_set_nonces_collected(DetectReader* detect_reader, uint16_t nonces_collected);
 
 void detect_reader_set_state(DetectReader* detect_reader, DetectReaderState state);
+
+void detect_reader_set_uid(DetectReader* detect_reader, uint8_t* uid, uint8_t uid_len);

+ 7 - 3
firmware/targets/f7/furi_hal/furi_hal_nfc.c

@@ -620,6 +620,10 @@ uint16_t furi_hal_nfc_bitstream_to_data_and_parity(
     uint16_t in_buff_bits,
     uint8_t* out_data,
     uint8_t* out_parity) {
+    if(in_buff_bits < 8) {
+        out_data[0] = in_buff[0];
+        return in_buff_bits;
+    }
     if(in_buff_bits % 9 != 0) {
         return 0;
     }
@@ -635,7 +639,7 @@ uint16_t furi_hal_nfc_bitstream_to_data_and_parity(
         bit_processed += 9;
         curr_byte++;
     }
-    return curr_byte;
+    return curr_byte * 8;
 }
 
 bool furi_hal_nfc_tx_rx(FuriHalNfcTxRxContext* tx_rx, uint16_t timeout_ms) {
@@ -692,8 +696,8 @@ bool furi_hal_nfc_tx_rx(FuriHalNfcTxRxContext* tx_rx, uint16_t timeout_ms) {
 
     if(tx_rx->tx_rx_type == FuriHalNfcTxRxTypeRaw ||
        tx_rx->tx_rx_type == FuriHalNfcTxRxTypeRxRaw) {
-        tx_rx->rx_bits = 8 * furi_hal_nfc_bitstream_to_data_and_parity(
-                                 temp_rx_buff, *temp_rx_bits, tx_rx->rx_data, tx_rx->rx_parity);
+        tx_rx->rx_bits = furi_hal_nfc_bitstream_to_data_and_parity(
+            temp_rx_buff, *temp_rx_bits, tx_rx->rx_data, tx_rx->rx_parity);
     } else {
         memcpy(tx_rx->rx_data, temp_rx_buff, MIN(*temp_rx_bits / 8, FURI_HAL_NFC_DATA_BUFF_SIZE));
         tx_rx->rx_bits = *temp_rx_bits;

+ 8 - 1
lib/nfc/helpers/reader_analyzer.c

@@ -201,10 +201,17 @@ NfcProtocol
 
 FuriHalNfcDevData* reader_analyzer_get_nfc_data(ReaderAnalyzer* instance) {
     furi_assert(instance);
-
+    instance->nfc_data = reader_analyzer_nfc_data[ReaderAnalyzerNfcDataMfClassic];
     return &instance->nfc_data;
 }
 
+void reader_analyzer_set_nfc_data(ReaderAnalyzer* instance, FuriHalNfcDevData* nfc_data) {
+    furi_assert(instance);
+    furi_assert(nfc_data);
+
+    memcpy(&instance->nfc_data, nfc_data, sizeof(FuriHalNfcDevData));
+}
+
 static void reader_analyzer_write(
     ReaderAnalyzer* instance,
     uint8_t* data,

+ 2 - 0
lib/nfc/helpers/reader_analyzer.h

@@ -35,6 +35,8 @@ NfcProtocol
 
 FuriHalNfcDevData* reader_analyzer_get_nfc_data(ReaderAnalyzer* instance);
 
+void reader_analyzer_set_nfc_data(ReaderAnalyzer* instance, FuriHalNfcDevData* nfc_data);
+
 void reader_analyzer_prepare_tx_rx(
     ReaderAnalyzer* instance,
     FuriHalNfcTxRxContext* tx_rx,

+ 7 - 0
lib/nfc/nfc_device.c

@@ -1122,6 +1122,13 @@ static bool nfc_device_load_data(NfcDevice* dev, FuriString* path, bool show_dia
         if(!flipper_format_read_hex(file, "UID", data->uid, data->uid_len)) break;
         if(!flipper_format_read_hex(file, "ATQA", data->atqa, 2)) break;
         if(!flipper_format_read_hex(file, "SAK", &data->sak, 1)) break;
+        // Load CUID
+        uint8_t* cuid_start = data->uid;
+        if(data->uid_len == 7) {
+            cuid_start = &data->uid[3];
+        }
+        data->cuid = (cuid_start[0] << 24) | (cuid_start[1] << 16) | (cuid_start[2] << 8) |
+                     (cuid_start[3]);
         // Parse other data
         if(dev->format == NfcDeviceSaveFormatMifareUl) {
             if(!nfc_device_load_mifare_ul_data(file, dev)) break;

+ 149 - 1
lib/nfc/nfc_worker.c

@@ -99,6 +99,10 @@ int32_t nfc_worker_task(void* context) {
         nfc_worker_emulate_mf_ultralight(nfc_worker);
     } else if(nfc_worker->state == NfcWorkerStateMfClassicEmulate) {
         nfc_worker_emulate_mf_classic(nfc_worker);
+    } else if(nfc_worker->state == NfcWorkerStateMfClassicWrite) {
+        nfc_worker_write_mf_classic(nfc_worker);
+    } else if(nfc_worker->state == NfcWorkerStateMfClassicUpdate) {
+        nfc_worker_update_mf_classic(nfc_worker);
     } else if(nfc_worker->state == NfcWorkerStateReadMfUltralightReadAuth) {
         nfc_worker_mf_ultralight_read_auth(nfc_worker);
     } else if(nfc_worker->state == NfcWorkerStateMfClassicDictAttack) {
@@ -666,6 +670,144 @@ void nfc_worker_emulate_mf_classic(NfcWorker* nfc_worker) {
     rfal_platform_spi_release();
 }
 
+void nfc_worker_write_mf_classic(NfcWorker* nfc_worker) {
+    FuriHalNfcTxRxContext tx_rx = {};
+    bool card_found_notified = false;
+    FuriHalNfcDevData nfc_data = {};
+    MfClassicData* src_data = &nfc_worker->dev_data->mf_classic_data;
+    MfClassicData dest_data = *src_data;
+
+    while(nfc_worker->state == NfcWorkerStateMfClassicWrite) {
+        if(furi_hal_nfc_detect(&nfc_data, 200)) {
+            if(!card_found_notified) {
+                nfc_worker->callback(NfcWorkerEventCardDetected, nfc_worker->context);
+                card_found_notified = true;
+            }
+            furi_hal_nfc_sleep();
+
+            FURI_LOG_I(TAG, "Check low level nfc data");
+            if(memcmp(&nfc_data, &nfc_worker->dev_data->nfc_data, sizeof(FuriHalNfcDevData))) {
+                FURI_LOG_E(TAG, "Wrong card");
+                nfc_worker->callback(NfcWorkerEventWrongCard, nfc_worker->context);
+                break;
+            }
+
+            FURI_LOG_I(TAG, "Check mf classic type");
+            MfClassicType type =
+                mf_classic_get_classic_type(nfc_data.atqa[0], nfc_data.atqa[1], nfc_data.sak);
+            if(type != nfc_worker->dev_data->mf_classic_data.type) {
+                FURI_LOG_E(TAG, "Wrong mf classic type");
+                nfc_worker->callback(NfcWorkerEventWrongCard, nfc_worker->context);
+                break;
+            }
+
+            // Set blocks not read
+            mf_classic_set_sector_data_not_read(&dest_data);
+            FURI_LOG_I(TAG, "Updating card sectors");
+            uint8_t total_sectors = mf_classic_get_total_sectors_num(type);
+            bool write_success = true;
+            for(uint8_t i = 0; i < total_sectors; i++) {
+                FURI_LOG_I(TAG, "Reading sector %d", i);
+                mf_classic_read_sector(&tx_rx, &dest_data, i);
+                bool old_data_read = mf_classic_is_sector_data_read(src_data, i);
+                bool new_data_read = mf_classic_is_sector_data_read(&dest_data, i);
+                if(old_data_read != new_data_read) {
+                    FURI_LOG_E(TAG, "Failed to update sector %d", i);
+                    write_success = false;
+                    break;
+                }
+                if(nfc_worker->state != NfcWorkerStateMfClassicWrite) break;
+                if(!mf_classic_write_sector(&tx_rx, &dest_data, src_data, i)) {
+                    FURI_LOG_E(TAG, "Failed to write %d sector", i);
+                    write_success = false;
+                    break;
+                }
+            }
+            if(nfc_worker->state != NfcWorkerStateMfClassicWrite) break;
+            if(write_success) {
+                nfc_worker->callback(NfcWorkerEventSuccess, nfc_worker->context);
+                break;
+            } else {
+                nfc_worker->callback(NfcWorkerEventFail, nfc_worker->context);
+                break;
+            }
+
+        } else {
+            if(card_found_notified) {
+                nfc_worker->callback(NfcWorkerEventNoCardDetected, nfc_worker->context);
+                card_found_notified = false;
+            }
+        }
+        furi_delay_ms(300);
+    }
+}
+
+void nfc_worker_update_mf_classic(NfcWorker* nfc_worker) {
+    FuriHalNfcTxRxContext tx_rx = {};
+    bool card_found_notified = false;
+    FuriHalNfcDevData nfc_data = {};
+    MfClassicData* old_data = &nfc_worker->dev_data->mf_classic_data;
+    MfClassicData new_data = *old_data;
+
+    while(nfc_worker->state == NfcWorkerStateMfClassicUpdate) {
+        if(furi_hal_nfc_detect(&nfc_data, 200)) {
+            if(!card_found_notified) {
+                nfc_worker->callback(NfcWorkerEventCardDetected, nfc_worker->context);
+                card_found_notified = true;
+            }
+            furi_hal_nfc_sleep();
+
+            FURI_LOG_I(TAG, "Check low level nfc data");
+            if(memcmp(&nfc_data, &nfc_worker->dev_data->nfc_data, sizeof(FuriHalNfcDevData))) {
+                FURI_LOG_E(TAG, "Low level nfc data mismatch");
+                nfc_worker->callback(NfcWorkerEventWrongCard, nfc_worker->context);
+                break;
+            }
+
+            FURI_LOG_I(TAG, "Check MF classic type");
+            MfClassicType type =
+                mf_classic_get_classic_type(nfc_data.atqa[0], nfc_data.atqa[1], nfc_data.sak);
+            if(type != nfc_worker->dev_data->mf_classic_data.type) {
+                FURI_LOG_E(TAG, "MF classic type mismatch");
+                nfc_worker->callback(NfcWorkerEventWrongCard, nfc_worker->context);
+                break;
+            }
+
+            // Set blocks not read
+            mf_classic_set_sector_data_not_read(&new_data);
+            FURI_LOG_I(TAG, "Updating card sectors");
+            uint8_t total_sectors = mf_classic_get_total_sectors_num(type);
+            bool update_success = true;
+            for(uint8_t i = 0; i < total_sectors; i++) {
+                FURI_LOG_I(TAG, "Reading sector %d", i);
+                mf_classic_read_sector(&tx_rx, &new_data, i);
+                bool old_data_read = mf_classic_is_sector_data_read(old_data, i);
+                bool new_data_read = mf_classic_is_sector_data_read(&new_data, i);
+                if(old_data_read != new_data_read) {
+                    FURI_LOG_E(TAG, "Failed to update sector %d", i);
+                    update_success = false;
+                    break;
+                }
+                if(nfc_worker->state != NfcWorkerStateMfClassicUpdate) break;
+            }
+            if(nfc_worker->state != NfcWorkerStateMfClassicUpdate) break;
+
+            // Check updated data
+            if(update_success) {
+                *old_data = new_data;
+                nfc_worker->callback(NfcWorkerEventSuccess, nfc_worker->context);
+                break;
+            }
+        } else {
+            if(card_found_notified) {
+                nfc_worker->callback(NfcWorkerEventNoCardDetected, nfc_worker->context);
+                card_found_notified = false;
+            }
+        }
+        furi_delay_ms(300);
+    }
+}
+
 void nfc_worker_mf_ultralight_read_auth(NfcWorker* nfc_worker) {
     furi_assert(nfc_worker);
     furi_assert(nfc_worker->callback);
@@ -758,7 +900,13 @@ void nfc_worker_analyze_reader(NfcWorker* nfc_worker) {
     FuriHalNfcTxRxContext tx_rx = {};
 
     ReaderAnalyzer* reader_analyzer = nfc_worker->reader_analyzer;
-    FuriHalNfcDevData* nfc_data = reader_analyzer_get_nfc_data(reader_analyzer);
+    FuriHalNfcDevData* nfc_data = NULL;
+    if(nfc_worker->dev_data->protocol == NfcDeviceProtocolMifareClassic) {
+        nfc_data = &nfc_worker->dev_data->nfc_data;
+        reader_analyzer_set_nfc_data(reader_analyzer, nfc_data);
+    } else {
+        nfc_data = reader_analyzer_get_nfc_data(reader_analyzer);
+    }
     MfClassicEmulator emulator = {
         .cuid = nfc_util_bytes2num(&nfc_data->uid[nfc_data->uid_len - 4], 4),
         .data = nfc_worker->dev_data->mf_classic_data,

+ 6 - 1
lib/nfc/nfc_worker.h

@@ -14,6 +14,8 @@ typedef enum {
     NfcWorkerStateUidEmulate,
     NfcWorkerStateMfUltralightEmulate,
     NfcWorkerStateMfClassicEmulate,
+    NfcWorkerStateMfClassicWrite,
+    NfcWorkerStateMfClassicUpdate,
     NfcWorkerStateReadMfUltralightReadAuth,
     NfcWorkerStateMfClassicDictAttack,
     NfcWorkerStateAnalyzeReader,
@@ -48,13 +50,16 @@ typedef enum {
     NfcWorkerEventNoCardDetected,
     NfcWorkerEventWrongCardDetected,
 
-    // Mifare Classic events
+    // Read Mifare Classic events
     NfcWorkerEventNoDictFound,
     NfcWorkerEventNewSector,
     NfcWorkerEventNewDictKeyBatch,
     NfcWorkerEventFoundKeyA,
     NfcWorkerEventFoundKeyB,
 
+    // Write Mifare Classic events
+    NfcWorkerEventWrongCard,
+
     // Detect Reader events
     NfcWorkerEventDetectReaderDetected,
     NfcWorkerEventDetectReaderLost,

+ 4 - 0
lib/nfc/nfc_worker_i.h

@@ -41,6 +41,10 @@ void nfc_worker_emulate_mf_ultralight(NfcWorker* nfc_worker);
 
 void nfc_worker_emulate_mf_classic(NfcWorker* nfc_worker);
 
+void nfc_worker_write_mf_classic(NfcWorker* nfc_worker);
+
+void nfc_worker_update_mf_classic(NfcWorker* nfc_worker);
+
 void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker);
 
 void nfc_worker_mf_ultralight_read_auth(NfcWorker* nfc_worker);

+ 52 - 0
lib/nfc/protocols/crypto1.c

@@ -73,3 +73,55 @@ uint32_t prng_successor(uint32_t x, uint32_t n) {
 
     return SWAPENDIAN(x);
 }
+
+void crypto1_decrypt(
+    Crypto1* crypto,
+    uint8_t* encrypted_data,
+    uint16_t encrypted_data_bits,
+    uint8_t* decrypted_data) {
+    furi_assert(crypto);
+    furi_assert(encrypted_data);
+    furi_assert(decrypted_data);
+
+    if(encrypted_data_bits < 8) {
+        uint8_t decrypted_byte = 0;
+        decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_data[0], 0)) << 0;
+        decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_data[0], 1)) << 1;
+        decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_data[0], 2)) << 2;
+        decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_data[0], 3)) << 3;
+        decrypted_data[0] = decrypted_byte;
+    } else {
+        for(size_t i = 0; i < encrypted_data_bits / 8; i++) {
+            decrypted_data[i] = crypto1_byte(crypto, 0, 0) ^ encrypted_data[i];
+        }
+    }
+}
+
+void crypto1_encrypt(
+    Crypto1* crypto,
+    uint8_t* keystream,
+    uint8_t* plain_data,
+    uint16_t plain_data_bits,
+    uint8_t* encrypted_data,
+    uint8_t* encrypted_parity) {
+    furi_assert(crypto);
+    furi_assert(plain_data);
+    furi_assert(encrypted_data);
+    furi_assert(encrypted_parity);
+
+    if(plain_data_bits < 8) {
+        encrypted_data[0] = 0;
+        for(size_t i = 0; i < plain_data_bits; i++) {
+            encrypted_data[0] |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(plain_data[0], i)) << i;
+        }
+    } else {
+        memset(encrypted_parity, 0, plain_data_bits / 8 + 1);
+        for(uint8_t i = 0; i < plain_data_bits / 8; i++) {
+            encrypted_data[i] = crypto1_byte(crypto, keystream ? keystream[i] : 0, 0) ^
+                                plain_data[i];
+            encrypted_parity[i / 8] |=
+                (((crypto1_filter(crypto->odd) ^ nfc_util_odd_parity8(plain_data[i])) & 0x01)
+                 << (7 - (i & 0x0007)));
+        }
+    }
+}

+ 14 - 0
lib/nfc/protocols/crypto1.h

@@ -21,3 +21,17 @@ uint32_t crypto1_word(Crypto1* crypto1, uint32_t in, int is_encrypted);
 uint32_t crypto1_filter(uint32_t in);
 
 uint32_t prng_successor(uint32_t x, uint32_t n);
+
+void crypto1_decrypt(
+    Crypto1* crypto,
+    uint8_t* encrypted_data,
+    uint16_t encrypted_data_bits,
+    uint8_t* decrypted_data);
+
+void crypto1_encrypt(
+    Crypto1* crypto,
+    uint8_t* keystream,
+    uint8_t* plain_data,
+    uint16_t plain_data_bits,
+    uint8_t* encrypted_data,
+    uint8_t* encrypted_parity);

+ 184 - 87
lib/nfc/protocols/mifare_classic.c

@@ -9,21 +9,8 @@
 
 #define MF_CLASSIC_AUTH_KEY_A_CMD (0x60U)
 #define MF_CLASSIC_AUTH_KEY_B_CMD (0x61U)
-#define MF_CLASSIC_READ_SECT_CMD (0x30)
-
-typedef enum {
-    MfClassicActionDataRead,
-    MfClassicActionDataWrite,
-    MfClassicActionDataInc,
-    MfClassicActionDataDec,
-
-    MfClassicActionKeyARead,
-    MfClassicActionKeyAWrite,
-    MfClassicActionKeyBRead,
-    MfClassicActionKeyBWrite,
-    MfClassicActionACRead,
-    MfClassicActionACWrite,
-} MfClassicAction;
+#define MF_CLASSIC_READ_BLOCK_CMD (0x30)
+#define MF_CLASSIC_WRITE_BLOCK_CMD (0xA0)
 
 const char* mf_classic_get_type_str(MfClassicType type) {
     if(type == MfClassicType1k) {
@@ -122,6 +109,24 @@ void mf_classic_set_block_read(MfClassicData* data, uint8_t block_num, MfClassic
     FURI_BIT_SET(data->block_read_mask[block_num / 32], block_num % 32);
 }
 
+bool mf_classic_is_sector_data_read(MfClassicData* data, uint8_t sector_num) {
+    furi_assert(data);
+
+    uint8_t first_block = mf_classic_get_first_block_num_of_sector(sector_num);
+    uint8_t total_blocks = mf_classic_get_blocks_num_in_sector(sector_num);
+    bool data_read = true;
+    for(size_t i = first_block; i < first_block + total_blocks; i++) {
+        data_read &= mf_classic_is_block_read(data, i);
+    }
+
+    return data_read;
+}
+
+void mf_classic_set_sector_data_not_read(MfClassicData* data) {
+    furi_assert(data);
+    memset(data->block_read_mask, 0, sizeof(data->block_read_mask));
+}
+
 bool mf_classic_is_key_found(MfClassicData* data, uint8_t sector_num, MfClassicKey key_type) {
     furi_assert(data);
 
@@ -190,6 +195,9 @@ void mf_classic_get_read_sectors_and_keys(
     uint8_t* sectors_read,
     uint8_t* keys_found) {
     furi_assert(data);
+    furi_assert(sectors_read);
+    furi_assert(keys_found);
+
     *sectors_read = 0;
     *keys_found = 0;
     uint8_t sectors_total = mf_classic_get_total_sectors_num(data->type);
@@ -225,12 +233,12 @@ bool mf_classic_is_card_read(MfClassicData* data) {
     return card_read;
 }
 
-static bool mf_classic_is_allowed_access_sector_trailer(
-    MfClassicEmulator* emulator,
+bool mf_classic_is_allowed_access_sector_trailer(
+    MfClassicData* data,
     uint8_t block_num,
     MfClassicKey key,
     MfClassicAction action) {
-    uint8_t* sector_trailer = emulator->data.block[block_num].value;
+    uint8_t* sector_trailer = data->block[block_num].value;
     uint8_t AC = ((sector_trailer[7] >> 5) & 0x04) | ((sector_trailer[8] >> 2) & 0x02) |
                  ((sector_trailer[8] >> 7) & 0x01);
     switch(action) {
@@ -266,13 +274,13 @@ static bool mf_classic_is_allowed_access_sector_trailer(
     return true;
 }
 
-static bool mf_classic_is_allowed_access_data_block(
-    MfClassicEmulator* emulator,
+bool mf_classic_is_allowed_access_data_block(
+    MfClassicData* data,
     uint8_t block_num,
     MfClassicKey key,
     MfClassicAction action) {
     uint8_t* sector_trailer =
-        emulator->data.block[mf_classic_get_sector_trailer_num_by_block(block_num)].value;
+        data->block[mf_classic_get_sector_trailer_num_by_block(block_num)].value;
 
     uint8_t sector_block;
     if(block_num <= 128) {
@@ -336,9 +344,10 @@ static bool mf_classic_is_allowed_access(
     MfClassicKey key,
     MfClassicAction action) {
     if(mf_classic_is_sector_trailer(block_num)) {
-        return mf_classic_is_allowed_access_sector_trailer(emulator, block_num, key, action);
+        return mf_classic_is_allowed_access_sector_trailer(
+            &emulator->data, block_num, key, action);
     } else {
-        return mf_classic_is_allowed_access_data_block(emulator, block_num, key, action);
+        return mf_classic_is_allowed_access_data_block(&emulator->data, block_num, key, action);
     }
 }
 
@@ -514,25 +523,17 @@ bool mf_classic_read_block(
     furi_assert(block);
 
     bool read_block_success = false;
-    uint8_t plain_cmd[4] = {MF_CLASSIC_READ_SECT_CMD, block_num, 0x00, 0x00};
+    uint8_t plain_cmd[4] = {MF_CLASSIC_READ_BLOCK_CMD, block_num, 0x00, 0x00};
     nfca_append_crc16(plain_cmd, 2);
-    memset(tx_rx->tx_data, 0, sizeof(tx_rx->tx_data));
-    memset(tx_rx->tx_parity, 0, sizeof(tx_rx->tx_parity));
 
-    for(uint8_t i = 0; i < 4; i++) {
-        tx_rx->tx_data[i] = crypto1_byte(crypto, 0x00, 0) ^ plain_cmd[i];
-        tx_rx->tx_parity[0] |=
-            ((crypto1_filter(crypto->odd) ^ nfc_util_odd_parity8(plain_cmd[i])) & 0x01) << (7 - i);
-    }
+    crypto1_encrypt(crypto, NULL, plain_cmd, 4 * 8, tx_rx->tx_data, tx_rx->tx_parity);
     tx_rx->tx_bits = 4 * 9;
     tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRaw;
 
     if(furi_hal_nfc_tx_rx(tx_rx, 50)) {
         if(tx_rx->rx_bits == 8 * (MF_CLASSIC_BLOCK_SIZE + 2)) {
             uint8_t block_received[MF_CLASSIC_BLOCK_SIZE + 2];
-            for(uint8_t i = 0; i < MF_CLASSIC_BLOCK_SIZE + 2; i++) {
-                block_received[i] = crypto1_byte(crypto, 0, 0) ^ tx_rx->rx_data[i];
-            }
+            crypto1_decrypt(crypto, tx_rx->rx_data, tx_rx->rx_bits, block_received);
             uint16_t crc_calc = nfca_get_crc16(block_received, MF_CLASSIC_BLOCK_SIZE);
             uint16_t crc_received = (block_received[MF_CLASSIC_BLOCK_SIZE + 1] << 8) |
                                     block_received[MF_CLASSIC_BLOCK_SIZE];
@@ -754,49 +755,6 @@ uint8_t mf_classic_update_card(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data
     return sectors_read;
 }
 
-void mf_crypto1_decrypt(
-    Crypto1* crypto,
-    uint8_t* encrypted_data,
-    uint16_t encrypted_data_bits,
-    uint8_t* decrypted_data) {
-    if(encrypted_data_bits < 8) {
-        uint8_t decrypted_byte = 0;
-        decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_data[0], 0)) << 0;
-        decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_data[0], 1)) << 1;
-        decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_data[0], 2)) << 2;
-        decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_data[0], 3)) << 3;
-        decrypted_data[0] = decrypted_byte;
-    } else {
-        for(size_t i = 0; i < encrypted_data_bits / 8; i++) {
-            decrypted_data[i] = crypto1_byte(crypto, 0, 0) ^ encrypted_data[i];
-        }
-    }
-}
-
-void mf_crypto1_encrypt(
-    Crypto1* crypto,
-    uint8_t* keystream,
-    uint8_t* plain_data,
-    uint16_t plain_data_bits,
-    uint8_t* encrypted_data,
-    uint8_t* encrypted_parity) {
-    if(plain_data_bits < 8) {
-        encrypted_data[0] = 0;
-        for(size_t i = 0; i < plain_data_bits; i++) {
-            encrypted_data[0] |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(plain_data[0], i)) << i;
-        }
-    } else {
-        memset(encrypted_parity, 0, plain_data_bits / 8 + 1);
-        for(uint8_t i = 0; i < plain_data_bits / 8; i++) {
-            encrypted_data[i] = crypto1_byte(crypto, keystream ? keystream[i] : 0, 0) ^
-                                plain_data[i];
-            encrypted_parity[i / 8] |=
-                (((crypto1_filter(crypto->odd) ^ nfc_util_odd_parity8(plain_data[i])) & 0x01)
-                 << (7 - (i & 0x0007)));
-        }
-    }
-}
-
 bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_rx) {
     furi_assert(emulator);
     furi_assert(tx_rx);
@@ -819,7 +777,7 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_
                     tx_rx->rx_bits);
                 break;
             }
-            mf_crypto1_decrypt(&emulator->crypto, tx_rx->rx_data, tx_rx->rx_bits, plain_data);
+            crypto1_decrypt(&emulator->crypto, tx_rx->rx_data, tx_rx->rx_bits, plain_data);
         }
 
         if(plain_data[0] == 0x50 && plain_data[1] == 0x00) {
@@ -857,7 +815,7 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_
                 tx_rx->tx_bits = sizeof(nt) * 8;
                 tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent;
             } else {
-                mf_crypto1_encrypt(
+                crypto1_encrypt(
                     &emulator->crypto,
                     nt_keystream,
                     nt,
@@ -904,7 +862,7 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_
             uint32_t ans = prng_successor(nonce, 96);
             uint8_t responce[4] = {};
             nfc_util_num2bytes(ans, 4, responce);
-            mf_crypto1_encrypt(
+            crypto1_encrypt(
                 &emulator->crypto,
                 NULL,
                 responce,
@@ -938,7 +896,7 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_
                     // Send NACK
                     uint8_t nack = 0x04;
                     if(is_encrypted) {
-                        mf_crypto1_encrypt(
+                        crypto1_encrypt(
                             &emulator->crypto, NULL, &nack, 4, tx_rx->tx_data, tx_rx->tx_parity);
                     } else {
                         tx_rx->tx_data[0] = nack;
@@ -951,7 +909,7 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_
             }
             nfca_append_crc16(block_data, 16);
 
-            mf_crypto1_encrypt(
+            crypto1_encrypt(
                 &emulator->crypto,
                 NULL,
                 block_data,
@@ -967,14 +925,14 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_
             }
             // Send ACK
             uint8_t ack = 0x0A;
-            mf_crypto1_encrypt(&emulator->crypto, NULL, &ack, 4, tx_rx->tx_data, tx_rx->tx_parity);
+            crypto1_encrypt(&emulator->crypto, NULL, &ack, 4, tx_rx->tx_data, tx_rx->tx_parity);
             tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent;
             tx_rx->tx_bits = 4;
 
             if(!furi_hal_nfc_tx_rx(tx_rx, 300)) break;
             if(tx_rx->rx_bits != 18 * 8) break;
 
-            mf_crypto1_decrypt(&emulator->crypto, tx_rx->rx_data, tx_rx->rx_bits, plain_data);
+            crypto1_decrypt(&emulator->crypto, tx_rx->rx_data, tx_rx->rx_bits, plain_data);
             uint8_t block_data[16] = {};
             memcpy(block_data, emulator->data.block[block].value, MF_CLASSIC_BLOCK_SIZE);
             if(mf_classic_is_sector_trailer(block)) {
@@ -1002,7 +960,7 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_
             }
             // Send ACK
             ack = 0x0A;
-            mf_crypto1_encrypt(&emulator->crypto, NULL, &ack, 4, tx_rx->tx_data, tx_rx->tx_parity);
+            crypto1_encrypt(&emulator->crypto, NULL, &ack, 4, tx_rx->tx_data, tx_rx->tx_parity);
             tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent;
             tx_rx->tx_bits = 4;
         } else {
@@ -1015,8 +973,7 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_
         // Send NACK
         uint8_t nack = 0x04;
         if(is_encrypted) {
-            mf_crypto1_encrypt(
-                &emulator->crypto, NULL, &nack, 4, tx_rx->tx_data, tx_rx->tx_parity);
+            crypto1_encrypt(&emulator->crypto, NULL, &nack, 4, tx_rx->tx_data, tx_rx->tx_parity);
         } else {
             tx_rx->tx_data[0] = nack;
         }
@@ -1027,3 +984,143 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_
 
     return true;
 }
+
+bool mf_classic_write_block(
+    FuriHalNfcTxRxContext* tx_rx,
+    MfClassicBlock* src_block,
+    uint8_t block_num,
+    MfClassicKey key_type,
+    uint64_t key) {
+    furi_assert(tx_rx);
+    furi_assert(src_block);
+
+    Crypto1 crypto = {};
+    uint8_t plain_data[18] = {};
+    uint8_t resp = 0;
+    bool write_success = false;
+
+    do {
+        furi_hal_nfc_sleep();
+        if(!mf_classic_auth(tx_rx, block_num, key, key_type, &crypto)) {
+            FURI_LOG_D(TAG, "Auth fail");
+            break;
+        }
+        // Send write command
+        plain_data[0] = MF_CLASSIC_WRITE_BLOCK_CMD;
+        plain_data[1] = block_num;
+        nfca_append_crc16(plain_data, 2);
+        crypto1_encrypt(&crypto, NULL, plain_data, 4 * 8, tx_rx->tx_data, tx_rx->tx_parity);
+        tx_rx->tx_bits = 4 * 8;
+        tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRaw;
+
+        if(furi_hal_nfc_tx_rx(tx_rx, 50)) {
+            if(tx_rx->rx_bits == 4) {
+                crypto1_decrypt(&crypto, tx_rx->rx_data, 4, &resp);
+                if(resp != 0x0A) {
+                    FURI_LOG_D(TAG, "NACK received on write cmd: %02X", resp);
+                    break;
+                }
+            } else {
+                FURI_LOG_D(TAG, "Not ACK received");
+                break;
+            }
+        } else {
+            FURI_LOG_D(TAG, "Failed to send write cmd");
+            break;
+        }
+
+        // Send data
+        memcpy(plain_data, src_block->value, MF_CLASSIC_BLOCK_SIZE);
+        nfca_append_crc16(plain_data, MF_CLASSIC_BLOCK_SIZE);
+        crypto1_encrypt(
+            &crypto,
+            NULL,
+            plain_data,
+            (MF_CLASSIC_BLOCK_SIZE + 2) * 8,
+            tx_rx->tx_data,
+            tx_rx->tx_parity);
+        tx_rx->tx_bits = (MF_CLASSIC_BLOCK_SIZE + 2) * 8;
+        tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRaw;
+        if(furi_hal_nfc_tx_rx(tx_rx, 50)) {
+            if(tx_rx->rx_bits == 4) {
+                crypto1_decrypt(&crypto, tx_rx->rx_data, 4, &resp);
+                if(resp != 0x0A) {
+                    FURI_LOG_D(TAG, "NACK received on sending data");
+                    break;
+                }
+            } else {
+                FURI_LOG_D(TAG, "Not ACK received");
+                break;
+            }
+        } else {
+            FURI_LOG_D(TAG, "Failed to send data");
+            break;
+        }
+        write_success = true;
+
+        // Send Halt
+        plain_data[0] = 0x50;
+        plain_data[1] = 0x00;
+        nfca_append_crc16(plain_data, 2);
+        crypto1_encrypt(&crypto, NULL, plain_data, 2 * 8, tx_rx->tx_data, tx_rx->tx_parity);
+        tx_rx->tx_bits = 2 * 8;
+        tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRaw;
+        // No response is expected
+        furi_hal_nfc_tx_rx(tx_rx, 50);
+    } while(false);
+
+    return write_success;
+}
+
+bool mf_classic_write_sector(
+    FuriHalNfcTxRxContext* tx_rx,
+    MfClassicData* dest_data,
+    MfClassicData* src_data,
+    uint8_t sec_num) {
+    furi_assert(tx_rx);
+    furi_assert(dest_data);
+    furi_assert(src_data);
+
+    uint8_t first_block = mf_classic_get_first_block_num_of_sector(sec_num);
+    uint8_t total_blocks = mf_classic_get_blocks_num_in_sector(sec_num);
+    MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(dest_data, sec_num);
+    bool key_a_found = mf_classic_is_key_found(dest_data, sec_num, MfClassicKeyA);
+    bool key_b_found = mf_classic_is_key_found(dest_data, sec_num, MfClassicKeyB);
+
+    bool write_success = true;
+    for(size_t i = first_block; i < first_block + total_blocks; i++) {
+        // Compare blocks
+        if(memcmp(dest_data->block[i].value, src_data->block[i].value, MF_CLASSIC_BLOCK_SIZE)) {
+            bool key_a_write_allowed = mf_classic_is_allowed_access_data_block(
+                dest_data, i, MfClassicKeyA, MfClassicActionDataWrite);
+            bool key_b_write_allowed = mf_classic_is_allowed_access_data_block(
+                dest_data, i, MfClassicKeyB, MfClassicActionDataWrite);
+
+            if(key_a_found && key_a_write_allowed) {
+                FURI_LOG_I(TAG, "Writing block %d with key A", i);
+                uint64_t key = nfc_util_bytes2num(sec_tr->key_a, 6);
+                if(!mf_classic_write_block(tx_rx, &src_data->block[i], i, MfClassicKeyA, key)) {
+                    FURI_LOG_E(TAG, "Failed to write block %d", i);
+                    write_success = false;
+                    break;
+                }
+            } else if(key_b_found && key_b_write_allowed) {
+                FURI_LOG_I(TAG, "Writing block %d with key A", i);
+                uint64_t key = nfc_util_bytes2num(sec_tr->key_b, 6);
+                if(!mf_classic_write_block(tx_rx, &src_data->block[i], i, MfClassicKeyB, key)) {
+                    FURI_LOG_E(TAG, "Failed to write block %d", i);
+                    write_success = false;
+                    break;
+                }
+            } else {
+                FURI_LOG_E(TAG, "Failed to find key with write access");
+                write_success = false;
+                break;
+            }
+        } else {
+            FURI_LOG_D(TAG, "Blocks %d are equal", i);
+        }
+    }
+
+    return write_success;
+}

+ 43 - 0
lib/nfc/protocols/mifare_classic.h

@@ -27,6 +27,20 @@ typedef enum {
     MfClassicKeyB,
 } MfClassicKey;
 
+typedef enum {
+    MfClassicActionDataRead,
+    MfClassicActionDataWrite,
+    MfClassicActionDataInc,
+    MfClassicActionDataDec,
+
+    MfClassicActionKeyARead,
+    MfClassicActionKeyAWrite,
+    MfClassicActionKeyBRead,
+    MfClassicActionKeyBWrite,
+    MfClassicActionACRead,
+    MfClassicActionACWrite,
+} MfClassicAction;
+
 typedef struct {
     uint8_t value[MF_CLASSIC_BLOCK_SIZE];
 } MfClassicBlock;
@@ -90,6 +104,18 @@ bool mf_classic_is_sector_trailer(uint8_t block);
 
 uint8_t mf_classic_get_sector_by_block(uint8_t block);
 
+bool mf_classic_is_allowed_access_sector_trailer(
+    MfClassicData* data,
+    uint8_t block_num,
+    MfClassicKey key,
+    MfClassicAction action);
+
+bool mf_classic_is_allowed_access_data_block(
+    MfClassicData* data,
+    uint8_t block_num,
+    MfClassicKey key,
+    MfClassicAction action);
+
 bool mf_classic_is_key_found(MfClassicData* data, uint8_t sector_num, MfClassicKey key_type);
 
 void mf_classic_set_key_found(
@@ -104,6 +130,10 @@ bool mf_classic_is_block_read(MfClassicData* data, uint8_t block_num);
 
 void mf_classic_set_block_read(MfClassicData* data, uint8_t block_num, MfClassicBlock* block_data);
 
+bool mf_classic_is_sector_data_read(MfClassicData* data, uint8_t sector_num);
+
+void mf_classic_set_sector_data_not_read(MfClassicData* data);
+
 bool mf_classic_is_sector_read(MfClassicData* data, uint8_t sector_num);
 
 bool mf_classic_is_card_read(MfClassicData* data);
@@ -145,3 +175,16 @@ uint8_t mf_classic_read_card(
 uint8_t mf_classic_update_card(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data);
 
 bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_rx);
+
+bool mf_classic_write_block(
+    FuriHalNfcTxRxContext* tx_rx,
+    MfClassicBlock* src_block,
+    uint8_t block_num,
+    MfClassicKey key_type,
+    uint64_t key);
+
+bool mf_classic_write_sector(
+    FuriHalNfcTxRxContext* tx_rx,
+    MfClassicData* dest_data,
+    MfClassicData* src_data,
+    uint8_t sec_num);