MX před 2 roky
revize
80ebd5a038

+ 14 - 0
application.fam

@@ -0,0 +1,14 @@
+App(
+    appid="t5577_multiwriter",
+    name="T5577 Multiwriter",
+    apptype=FlipperAppType.EXTERNAL,
+    targets=["f7"],
+    entry_point="t5577_multiwriter_app",
+    icon="A_125khz_14",
+    stack_size=2 * 1024,
+    order=20,
+    fap_description="Application for writing several keys to one t5577",
+    fap_version="0.1",
+    fap_icon="icon.png",
+    fap_category="RFID",
+)

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 4 - 0
assets/assets_icons.c


+ 235 - 0
assets/assets_icons.h

@@ -0,0 +1,235 @@
+#pragma once
+
+#include <gui/icon.h>
+
+extern const Icon I_Certification1_103x56;
+extern const Icon I_Certification2_46x33;
+extern const Icon A_Levelup1_128x64;
+extern const Icon A_Levelup2_128x64;
+extern const Icon I_125_10px;
+extern const Icon I_Apps_10px;
+extern const Icon I_Nfc_10px;
+extern const Icon I_back_10px;
+extern const Icon I_badusb_10px;
+extern const Icon I_dir_10px;
+extern const Icon I_ibutt_10px;
+extern const Icon I_ir_10px;
+extern const Icon I_keyboard_10px;
+extern const Icon I_loading_10px;
+extern const Icon I_music_10px;
+extern const Icon I_sub1_10px;
+extern const Icon I_subrem_10px;
+extern const Icon I_u2f_10px;
+extern const Icon I_unknown_10px;
+extern const Icon I_update_10px;
+extern const Icon I_BLE_Pairing_128x64;
+extern const Icon I_Ble_connected_15x15;
+extern const Icon I_Ble_disconnected_15x15;
+extern const Icon I_Button_18x18;
+extern const Icon I_Circles_47x47;
+extern const Icon I_Left_mouse_icon_9x9;
+extern const Icon I_Ok_btn_9x9;
+extern const Icon I_Ok_btn_pressed_13x13;
+extern const Icon I_Pressed_Button_13x13;
+extern const Icon I_Right_mouse_icon_9x9;
+extern const Icon I_Space_65x18;
+extern const Icon I_Voldwn_6x6;
+extern const Icon I_Volup_8x6;
+extern const Icon I_Clock_18x18;
+extern const Icon I_Error_18x18;
+extern const Icon I_EviSmile1_18x21;
+extern const Icon I_EviSmile2_18x21;
+extern const Icon I_EviWaiting1_18x21;
+extern const Icon I_EviWaiting2_18x21;
+extern const Icon I_Percent_10x14;
+extern const Icon I_Smile_18x18;
+extern const Icon I_UsbTree_48x22;
+extern const Icon I_ActiveConnection_50x64;
+extern const Icon I_ButtonCenter_7x7;
+extern const Icon I_ButtonDown_7x4;
+extern const Icon I_ButtonLeftSmall_3x5;
+extern const Icon I_ButtonLeft_4x7;
+extern const Icon I_ButtonRightSmall_3x5;
+extern const Icon I_ButtonRight_4x7;
+extern const Icon I_ButtonUp_7x4;
+extern const Icon I_DFU_128x50;
+extern const Icon I_Hashmark_7x7;
+extern const Icon I_More_data_placeholder_5x7;
+extern const Icon I_Warning_30x23;
+extern const Icon I_arrow_nano_down;
+extern const Icon I_arrow_nano_up;
+extern const Icon A_Loading_24;
+extern const Icon A_Round_loader_8x8;
+extern const Icon I_DolphinDone_80x58;
+extern const Icon I_DolphinMafia_119x62;
+extern const Icon I_DolphinReadingSuccess_59x63;
+extern const Icon I_DolphinSaved_92x58;
+extern const Icon I_DolphinSuccess_91x55;
+extern const Icon I_DolphinWait_61x59;
+extern const Icon I_WarningDolphinFlip_45x42;
+extern const Icon I_WarningDolphin_45x42;
+extern const Icon I_Erase_pin_128x64;
+extern const Icon I_ArrowUpEmpty_14x15;
+extern const Icon I_ArrowUpFilled_14x15;
+extern const Icon I_InfraredArrowDown_4x8;
+extern const Icon I_InfraredArrowUp_4x8;
+extern const Icon I_InfraredLearnShort_128x31;
+extern const Icon I_celsius_24x23;
+extern const Icon I_celsius_hover_24x23;
+extern const Icon I_ch_down_24x21;
+extern const Icon I_ch_down_hover_24x21;
+extern const Icon I_ch_text_31x34;
+extern const Icon I_ch_up_24x21;
+extern const Icon I_ch_up_hover_24x21;
+extern const Icon I_cool_30x51;
+extern const Icon I_dry_19x20;
+extern const Icon I_dry_hover_19x20;
+extern const Icon I_dry_text_15x5;
+extern const Icon I_fahren_24x23;
+extern const Icon I_fahren_hover_24x23;
+extern const Icon I_heat_30x51;
+extern const Icon I_hourglass0_24x24;
+extern const Icon I_hourglass1_24x24;
+extern const Icon I_hourglass2_24x24;
+extern const Icon I_hourglass3_24x24;
+extern const Icon I_hourglass4_24x24;
+extern const Icon I_hourglass5_24x24;
+extern const Icon I_hourglass6_24x24;
+extern const Icon I_max_24x23;
+extern const Icon I_max_hover_24x23;
+extern const Icon I_mode_19x20;
+extern const Icon I_mode_hover_19x20;
+extern const Icon I_mode_text_20x5;
+extern const Icon I_mute_19x20;
+extern const Icon I_mute_hover_19x20;
+extern const Icon I_mute_text_19x5;
+extern const Icon I_next_19x20;
+extern const Icon I_next_hover_19x20;
+extern const Icon I_next_text_19x6;
+extern const Icon I_off_19x20;
+extern const Icon I_off_hover_19x20;
+extern const Icon I_off_text_12x5;
+extern const Icon I_pause_19x20;
+extern const Icon I_pause_hover_19x20;
+extern const Icon I_pause_text_23x5;
+extern const Icon I_play_19x20;
+extern const Icon I_play_hover_19x20;
+extern const Icon I_play_text_19x5;
+extern const Icon I_power_19x20;
+extern const Icon I_power_hover_19x20;
+extern const Icon I_power_text_24x5;
+extern const Icon I_prev_19x20;
+extern const Icon I_prev_hover_19x20;
+extern const Icon I_prev_text_19x5;
+extern const Icon I_rotate_19x20;
+extern const Icon I_rotate_hover_19x20;
+extern const Icon I_rotate_text_24x5;
+extern const Icon I_speed_text_30x30;
+extern const Icon I_timer_19x20;
+extern const Icon I_timer_hover_19x20;
+extern const Icon I_timer_text_23x5;
+extern const Icon I_vol_ac_text_30x30;
+extern const Icon I_vol_tv_text_29x34;
+extern const Icon I_voldown_24x21;
+extern const Icon I_voldown_hover_24x21;
+extern const Icon I_volup_24x21;
+extern const Icon I_volup_hover_24x21;
+extern const Icon I_DoorLeft_70x55;
+extern const Icon I_DoorRight_70x55;
+extern const Icon I_SmallArrowDown_3x5;
+extern const Icon I_SmallArrowDown_4x7;
+extern const Icon I_SmallArrowUp_3x5;
+extern const Icon I_SmallArrowUp_4x7;
+extern const Icon I_KeyBackspaceSelected_16x9;
+extern const Icon I_KeyBackspace_16x9;
+extern const Icon I_KeySaveSelected_24x11;
+extern const Icon I_KeySave_24x11;
+extern const Icon A_125khz_14;
+extern const Icon A_BadUsb_14;
+extern const Icon A_Clock_14;
+extern const Icon A_Debug_14;
+extern const Icon A_FileManager_14;
+extern const Icon A_GPIO_14;
+extern const Icon A_Infrared_14;
+extern const Icon A_NFC_14;
+extern const Icon A_Plugins_14;
+extern const Icon A_Settings_14;
+extern const Icon A_Sub1ghz_14;
+extern const Icon A_SubGHzRemote_14;
+extern const Icon A_U2F_14;
+extern const Icon A_iButton_14;
+extern const Icon I_ArrowC_1_36x36;
+extern const Icon I_Detailed_chip_17x13;
+extern const Icon I_Keychain_39x36;
+extern const Icon I_Medium_chip_22x21;
+extern const Icon I_Modern_reader_18x34;
+extern const Icon I_Move_flipper_26x39;
+extern const Icon I_NFC_dolphin_emulation_47x61;
+extern const Icon I_NFC_manual_60x50;
+extern const Icon I_Release_arrow_18x15;
+extern const Icon I_check_big_20x17;
+extern const Icon I_Pin_arrow_up_7x9;
+extern const Icon I_Pin_attention_dpad_29x29;
+extern const Icon I_Pin_back_arrow_10x8;
+extern const Icon I_Pin_cell_13x13;
+extern const Icon I_Pin_pointer_5x3;
+extern const Icon I_Pin_star_7x7;
+extern const Icon I_passport_bad1_46x49;
+extern const Icon I_passport_bad2_46x49;
+extern const Icon I_passport_bad3_46x49;
+extern const Icon I_passport_bottom_128x18;
+extern const Icon I_passport_happy1_46x49;
+extern const Icon I_passport_happy2_46x49;
+extern const Icon I_passport_happy3_46x49;
+extern const Icon I_passport_left_6x46;
+extern const Icon I_passport_okay1_46x49;
+extern const Icon I_passport_okay2_46x49;
+extern const Icon I_passport_okay3_46x49;
+extern const Icon I_BatteryBody_52x28;
+extern const Icon I_Battery_16x16;
+extern const Icon I_FaceCharging_29x14;
+extern const Icon I_FaceConfused_29x14;
+extern const Icon I_FaceNopower_29x14;
+extern const Icon I_FaceNormal_29x14;
+extern const Icon I_Health_16x16;
+extern const Icon I_Temperature_16x16;
+extern const Icon I_Unplug_bg_bottom_128x10;
+extern const Icon I_Unplug_bg_top_128x14;
+extern const Icon I_Voltage_16x16;
+extern const Icon I_RFIDDolphinReceive_97x61;
+extern const Icon I_RFIDDolphinSend_97x61;
+extern const Icon I_RFIDSmallChip_14x14;
+extern const Icon I_SDQuestion_35x43;
+extern const Icon I_Cry_dolph_55x52;
+extern const Icon I_Alert_9x8;
+extern const Icon I_Attention_5x8;
+extern const Icon I_Background_128x11;
+extern const Icon I_Battery_26x8;
+extern const Icon I_Bluetooth_Connected_16x8;
+extern const Icon I_Bluetooth_Idle_5x8;
+extern const Icon I_Charging_lightning_9x10;
+extern const Icon I_Charging_lightning_mask_9x10;
+extern const Icon I_GameMode_11x8;
+extern const Icon I_Hidden_window_9x8;
+extern const Icon I_Muted_8x8;
+extern const Icon I_Rpc_active_7x8;
+extern const Icon I_SDcardFail_11x8;
+extern const Icon I_SDcardMounted_11x8;
+extern const Icon I_Cos_9x7;
+extern const Icon I_Dynamic_9x7;
+extern const Icon I_Fishing_123x52;
+extern const Icon I_Lock_7x8;
+extern const Icon I_MHz_25x11;
+extern const Icon I_Quest_7x8;
+extern const Icon I_Raw_9x7;
+extern const Icon I_Scanning_123x52;
+extern const Icon I_Static_9x7;
+extern const Icon I_Unlock_7x8;
+extern const Icon I_Auth_62x31;
+extern const Icon I_Connect_me_62x31;
+extern const Icon I_Connected_62x31;
+extern const Icon I_Drive_112x35;
+extern const Icon I_Error_62x31;
+extern const Icon I_Updating_32x40;
+extern const Icon I_iButtonDolphinVerySuccess_92x55;
+extern const Icon I_iButtonKey_49x44;

+ 143 - 0
helpers/t5577.c

@@ -0,0 +1,143 @@
+#include "t5577.h"
+#include <furi.h>
+#include <furi_hal_rfid.h>
+#include <stdint.h>
+
+#define T5577_TIMING_WAIT_TIME 400
+#define T5577_TIMING_START_GAP 30
+#define T5577_TIMING_WRITE_GAP 18
+#define T5577_TIMING_DATA_0 24
+#define T5577_TIMING_DATA_1 56
+#define T5577_TIMING_PROGRAM 700
+
+#define T5577_OPCODE_PAGE_0 0b10
+#define T5577_OPCODE_PAGE_1 0b11
+#define T5577_OPCODE_RESET 0b00
+
+static void t5577_start() {
+    furi_hal_rfid_tim_read_start(125000, 0.5);
+
+    // do not ground the antenna
+    furi_hal_rfid_pin_pull_release();
+}
+
+static void t5577_stop() {
+    furi_hal_rfid_tim_read_stop();
+    furi_hal_rfid_pins_reset();
+}
+
+static void t5577_write_gap(uint32_t gap_time) {
+    furi_hal_rfid_tim_read_pause();
+    furi_delay_us(gap_time * 8);
+    furi_hal_rfid_tim_read_continue();
+}
+
+static void t5577_write_bit(bool value) {
+    if(value) {
+        furi_delay_us(T5577_TIMING_DATA_1 * 8);
+    } else {
+        furi_delay_us(T5577_TIMING_DATA_0 * 8);
+    }
+    t5577_write_gap(T5577_TIMING_WRITE_GAP);
+}
+
+static void t5577_write_opcode(uint8_t value) {
+    t5577_write_bit((value >> 1) & 1);
+    t5577_write_bit((value >> 0) & 1);
+}
+
+static void t5577_write_reset() {
+    t5577_write_gap(T5577_TIMING_START_GAP);
+    t5577_write_bit(1);
+    t5577_write_bit(0);
+}
+
+static void t5577_write_block_pass(
+    uint8_t block,
+    bool lock_bit,
+    uint32_t data,
+    bool with_pass,
+    uint32_t password) {
+    furi_delay_us(T5577_TIMING_WAIT_TIME * 8);
+
+    // start gap
+    t5577_write_gap(T5577_TIMING_START_GAP);
+
+    // opcode for page 0
+    t5577_write_opcode(T5577_OPCODE_PAGE_0);
+
+    // password
+    if(with_pass) {
+        for(uint8_t i = 0; i < 32; i++) {
+            t5577_write_bit((password >> (31 - i)) & 1);
+        }
+    }
+
+    // lock bit
+    t5577_write_bit(lock_bit);
+
+    // data
+    for(uint8_t i = 0; i < 32; i++) {
+        t5577_write_bit((data >> (31 - i)) & 1);
+    }
+
+    // block address
+    t5577_write_bit((block >> 2) & 1);
+    t5577_write_bit((block >> 1) & 1);
+    t5577_write_bit((block >> 0) & 1);
+
+    furi_delay_us(T5577_TIMING_PROGRAM * 8);
+
+    furi_delay_us(T5577_TIMING_WAIT_TIME * 8);
+    t5577_write_reset();
+}
+
+static void t5577_write_block_simple(uint8_t block, bool lock_bit, uint32_t data) {
+    t5577_write_block_pass(block, lock_bit, data, false, 0);
+}
+
+void t5577_write(LFRFIDT5577Data* data) {
+    t5577_start();
+    FURI_CRITICAL_ENTER();
+    for(size_t i = 0; i < data->blocks_to_write; i++) {
+        t5577_write_block_simple(i, false, data->block[i]);
+    }
+    t5577_write_reset();
+    FURI_CRITICAL_EXIT();
+    t5577_stop();
+}
+
+void t5577_write_with_mask(LFRFIDT5577Data* data) {
+    t5577_start();
+    FURI_CRITICAL_ENTER();
+
+    uint8_t mask = data->mask;
+
+    for(size_t i = 0; i < data->blocks_to_write; i++) {
+        bool need_to_write = mask & 1;
+        mask >>= 1;
+        FURI_LOG_D(
+            "WRITIND",
+            "%08lX. i = %u. needtowrite: %u, mask: %u",
+            data->block[i],
+            i,
+            need_to_write,
+            mask);
+        if(!need_to_write) continue;
+        t5577_write_block_simple(i, false, data->block[i]);
+    }
+    t5577_write_reset();
+    FURI_CRITICAL_EXIT();
+    t5577_stop();
+}
+
+void t5577_write_with_pass(LFRFIDT5577Data* data, uint32_t password) {
+    t5577_start();
+    FURI_CRITICAL_ENTER();
+    for(size_t i = 0; i < data->blocks_to_write; i++) {
+        t5577_write_block_pass(i, false, data->block[i], true, password);
+    }
+    t5577_write_reset();
+    FURI_CRITICAL_EXIT();
+    t5577_stop();
+}

+ 58 - 0
helpers/t5577.h

@@ -0,0 +1,58 @@
+#pragma once
+#include <stdint.h>
+#include <stdbool.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define LFRFID_T5577_BLOCK_COUNT 8
+
+// T5577 block 0 definitions, thanks proxmark3!
+#define LFRFID_T5577_POR_DELAY 0x00000001
+#define LFRFID_T5577_ST_TERMINATOR 0x00000008
+#define LFRFID_T5577_PWD 0x00000010
+#define LFRFID_T5577_MAXBLOCK_SHIFT 5
+#define LFRFID_T5577_AOR 0x00000200
+#define LFRFID_T5577_PSKCF_RF_2 0
+#define LFRFID_T5577_PSKCF_RF_4 0x00000400
+#define LFRFID_T5577_PSKCF_RF_8 0x00000800
+#define LFRFID_T5577_MODULATION_DIRECT 0
+#define LFRFID_T5577_MODULATION_PSK1 0x00001000
+#define LFRFID_T5577_MODULATION_PSK2 0x00002000
+#define LFRFID_T5577_MODULATION_PSK3 0x00003000
+#define LFRFID_T5577_MODULATION_FSK1 0x00004000
+#define LFRFID_T5577_MODULATION_FSK2 0x00005000
+#define LFRFID_T5577_MODULATION_FSK1a 0x00006000
+#define LFRFID_T5577_MODULATION_FSK2a 0x00007000
+#define LFRFID_T5577_MODULATION_MANCHESTER 0x00008000
+#define LFRFID_T5577_MODULATION_BIPHASE 0x00010000
+#define LFRFID_T5577_MODULATION_DIPHASE 0x00018000
+#define LFRFID_T5577_X_MODE 0x00020000
+#define LFRFID_T5577_BITRATE_RF_8 0
+#define LFRFID_T5577_BITRATE_RF_16 0x00040000
+#define LFRFID_T5577_BITRATE_RF_32 0x00080000
+#define LFRFID_T5577_BITRATE_RF_40 0x000C0000
+#define LFRFID_T5577_BITRATE_RF_50 0x00100000
+#define LFRFID_T5577_BITRATE_RF_64 0x00140000
+#define LFRFID_T5577_BITRATE_RF_100 0x00180000
+#define LFRFID_T5577_BITRATE_RF_128 0x001C0000
+#define LFRFID_T5577_TESTMODE_DISABLED 0x60000000
+
+typedef struct {
+    uint32_t block[LFRFID_T5577_BLOCK_COUNT];
+    uint32_t blocks_to_write;
+    uint8_t mask;
+} LFRFIDT5577Data;
+
+/**
+ * @brief Write T5577 tag data to tag
+ * 
+ * @param data 
+ */
+
+void t5577_write_with_mask(LFRFIDT5577Data* data);
+
+#ifdef __cplusplus
+}
+#endif

binární
icon.png


+ 111 - 0
protocols/EM41XX.c

@@ -0,0 +1,111 @@
+#include "EM41XX.h"
+#include "core/check.h"
+#include "core/log.h"
+#include <stdint.h>
+
+#define EM41XX_LINES (10)
+#define EM41XX_COLUMNS (4)
+
+#define EM41XX_CONFIG_1_KEY (0b00000000000101001000000001000000)
+#define EM41XX_CONFIG_2_KEYS (0b00000000000101001000000010000000)
+#define EM41XX_CONFIG_3_KEYS (0b00000000000101001000000011000000)
+#define EM41XX_CONFIG_BLANK (0b00000000000101001000000000000000)
+
+#define EM41XX_HEADER (0b111111111)
+
+#define T5577_MAX_BLOCKS (8)
+#define EM41XX_BLOCKS (2)
+
+bool get_parity(uint16_t data) {
+    bool result = 0;
+    for(int i = 0; i < 16; i++) result ^= ((data >> i) & 1);
+    return result;
+}
+
+bool get_line_parity_bit(uint8_t line_num, uint64_t data) {
+    uint8_t line = (data >> (EM41XX_COLUMNS * line_num)) & 0x0F;
+    return get_parity(line);
+}
+
+bool get_column_parity_bit(uint8_t column_num, uint64_t data) {
+    uint16_t column = 0;
+
+    for(int i = 0; i < EM41XX_LINES; i++) {
+        column <<= 1;
+        column |= (data >> (EM41XX_COLUMNS * i + column_num)) & 1;
+    }
+
+    return get_parity(column);
+}
+
+uint64_t em41xx_encode(uint64_t data) {
+    uint64_t result = EM41XX_HEADER;
+
+    for(int i = EM41XX_LINES - 1; i >= 0; i--) {
+        result <<= EM41XX_COLUMNS;
+        uint8_t line = (data >> (i * EM41XX_COLUMNS)) & 0x0F;
+        result |= line;
+
+        result <<= 1;
+        result |= get_line_parity_bit(i, data);
+    }
+
+    for(int i = EM41XX_COLUMNS - 1; i >= 0; i--) {
+        result <<= 1;
+        result |= get_column_parity_bit(i, data);
+    }
+
+    result <<= 1;
+
+    return result;
+}
+
+bool add_em41xx_data(LFRFIDT5577Data* data, uint64_t key, uint8_t from_index) {
+    if(from_index + EM41XX_BLOCKS > (T5577_MAX_BLOCKS - 1)) return false;
+
+    uint64_t blocks_data = em41xx_encode(key);
+    data->block[from_index] = blocks_data >> 32;
+    data->block[from_index + 1] = blocks_data & 0xFFFFFFFF;
+    data->blocks_to_write = T5577_MAX_BLOCKS;
+
+    uint8_t mask_addition = (1 << from_index);
+    mask_addition |= (1 << (from_index + 1));
+
+    data->mask |= mask_addition;
+
+    FURI_LOG_D("ADDDATA", "%u", data->mask);
+
+    return true;
+}
+
+uint32_t get_config(uint8_t keys_count) {
+    if(keys_count > 3) return 0;
+
+    uint32_t result = EM41XX_CONFIG_BLANK;
+    result |= ((keys_count * EM41XX_BLOCKS) << 5);
+
+    return result;
+}
+
+bool set_em41xx_config(LFRFIDT5577Data* data, uint8_t keys_count) {
+    if(keys_count > 3) return false;
+
+    data->block[0] = get_config(keys_count);
+
+    data->mask |= 1;
+    FURI_LOG_D("SETCONFIG", "%u", data->mask);
+
+    return true;
+}
+
+uint64_t bytes2num(const uint8_t* src, uint8_t len) {
+    furi_assert(src);
+    furi_assert(len <= 8);
+
+    uint64_t res = 0;
+    while(len--) {
+        res = (res << 8) | (*src);
+        src++;
+    }
+    return res;
+}

+ 7 - 0
protocols/EM41XX.h

@@ -0,0 +1,7 @@
+#include "../helpers/t5577.h"
+
+bool add_em41xx_data(LFRFIDT5577Data* data, uint64_t key, uint8_t from_index);
+
+bool set_em41xx_config(LFRFIDT5577Data* data, uint8_t keys_count);
+
+uint64_t bytes2num(const uint8_t* src, uint8_t len);

+ 30 - 0
scenes/t5577_multiwriter_scene.c

@@ -0,0 +1,30 @@
+#include "t5577_multiwriter_scene.h"
+
+// Generate scene on_enter handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
+void (*const t5577_multiwriter_on_enter_handlers[])(void*) = {
+#include "t5577_multiwriter_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Generate scene on_event handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event,
+bool (*const t5577_multiwriter_on_event_handlers[])(void* context, SceneManagerEvent event) = {
+#include "t5577_multiwriter_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Generate scene on_exit handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit,
+void (*const t5577_multiwriter_on_exit_handlers[])(void* context) = {
+#include "t5577_multiwriter_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Initialize scene handlers configuration structure
+const SceneManagerHandlers t5577_multiwriter_scene_handlers = {
+    .on_enter_handlers = t5577_multiwriter_on_enter_handlers,
+    .on_event_handlers = t5577_multiwriter_on_event_handlers,
+    .on_exit_handlers = t5577_multiwriter_on_exit_handlers,
+    .scene_num = LfRfidSceneNum,
+};

+ 29 - 0
scenes/t5577_multiwriter_scene.h

@@ -0,0 +1,29 @@
+#pragma once
+
+#include <gui/scene_manager.h>
+
+// Generate scene id and total number
+#define ADD_SCENE(prefix, name, id) LfRfidScene##id,
+typedef enum {
+#include "t5577_multiwriter_scene_config.h"
+    LfRfidSceneNum,
+} LfRfidScene;
+#undef ADD_SCENE
+
+extern const SceneManagerHandlers t5577_multiwriter_scene_handlers;
+
+// Generate scene on_enter handlers declaration
+#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
+#include "t5577_multiwriter_scene_config.h"
+#undef ADD_SCENE
+
+// Generate scene on_event handlers declaration
+#define ADD_SCENE(prefix, name, id) \
+    bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event);
+#include "t5577_multiwriter_scene_config.h"
+#undef ADD_SCENE
+
+// Generate scene on_exit handlers declaration
+#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context);
+#include "t5577_multiwriter_scene_config.h"
+#undef ADD_SCENE

+ 9 - 0
scenes/t5577_multiwriter_scene_config.h

@@ -0,0 +1,9 @@
+ADD_SCENE(t5577_multiwriter, start, Start)
+ADD_SCENE(t5577_multiwriter, exit_confirm, ExitConfirm)
+ADD_SCENE(t5577_multiwriter, write_success, WriteSuccess)
+ADD_SCENE(t5577_multiwriter, select_first_key, SelectFirstKey)
+ADD_SCENE(t5577_multiwriter, write_first_key, WriteFirstKey)
+ADD_SCENE(t5577_multiwriter, select_second_key, SelectSecondKey)
+ADD_SCENE(t5577_multiwriter, write_second_key, WriteSecondKey)
+ADD_SCENE(t5577_multiwriter, select_third_key, SelectThirdKey)
+ADD_SCENE(t5577_multiwriter, write_third_key, WriteThirdKey)

+ 41 - 0
scenes/t5577_multiwriter_scene_exit_confirm.c

@@ -0,0 +1,41 @@
+#include "../t5577_multiwriter_i.h"
+
+void t5577_multiwriter_scene_exit_confirm_on_enter(void* context) {
+    LfRfid* app = context;
+    Widget* widget = app->widget;
+
+    widget_add_button_element(
+        widget, GuiButtonTypeLeft, "Exit", t5577_multiwriter_widget_callback, app);
+    widget_add_button_element(
+        widget, GuiButtonTypeRight, "Stay", t5577_multiwriter_widget_callback, app);
+    widget_add_string_element(
+        widget, 64, 19, AlignCenter, AlignBottom, FontPrimary, "Exit to RFID Menu?");
+    widget_add_string_element(
+        widget, 64, 31, AlignCenter, AlignBottom, FontSecondary, "All unsaved data will be lost!");
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewWidget);
+}
+
+bool t5577_multiwriter_scene_exit_confirm_on_event(void* context, SceneManagerEvent event) {
+    LfRfid* app = context;
+    SceneManager* scene_manager = app->scene_manager;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeBack) {
+        consumed = true; // Ignore Back button presses
+    } else if(event.type == SceneManagerEventTypeCustom) {
+        consumed = true;
+        if(event.event == GuiButtonTypeLeft) {
+            scene_manager_search_and_switch_to_previous_scene(scene_manager, LfRfidSceneStart);
+        } else if(event.event == GuiButtonTypeRight) {
+            scene_manager_previous_scene(scene_manager);
+        }
+    }
+
+    return consumed;
+}
+
+void t5577_multiwriter_scene_exit_confirm_on_exit(void* context) {
+    LfRfid* app = context;
+    widget_reset(app->widget);
+}

+ 22 - 0
scenes/t5577_multiwriter_scene_select_first_key.c

@@ -0,0 +1,22 @@
+#include "../t5577_multiwriter_i.h"
+
+void t5577_multiwriter_scene_select_first_key_on_enter(void* context) {
+    LfRfid* app = context;
+
+    if(t5577_multiwriter_load_key_from_file_select(app)) {
+        scene_manager_next_scene(app->scene_manager, LfRfidSceneWriteFirstKey);
+    } else {
+        scene_manager_previous_scene(app->scene_manager);
+    }
+}
+
+bool t5577_multiwriter_scene_select_first_key_on_event(void* context, SceneManagerEvent event) {
+    UNUSED(context);
+    UNUSED(event);
+    bool consumed = false;
+    return consumed;
+}
+
+void t5577_multiwriter_scene_select_first_key_on_exit(void* context) {
+    UNUSED(context);
+}

+ 22 - 0
scenes/t5577_multiwriter_scene_select_second_key.c

@@ -0,0 +1,22 @@
+#include "../t5577_multiwriter_i.h"
+
+void t5577_multiwriter_scene_select_second_key_on_enter(void* context) {
+    LfRfid* app = context;
+
+    if(t5577_multiwriter_load_key_from_file_select(app)) {
+        scene_manager_next_scene(app->scene_manager, LfRfidSceneWriteSecondKey);
+    } else {
+        scene_manager_previous_scene(app->scene_manager);
+    }
+}
+
+bool t5577_multiwriter_scene_select_second_key_on_event(void* context, SceneManagerEvent event) {
+    UNUSED(context);
+    UNUSED(event);
+    bool consumed = false;
+    return consumed;
+}
+
+void t5577_multiwriter_scene_select_second_key_on_exit(void* context) {
+    UNUSED(context);
+}

+ 22 - 0
scenes/t5577_multiwriter_scene_select_third_key.c

@@ -0,0 +1,22 @@
+#include "../t5577_multiwriter_i.h"
+
+void t5577_multiwriter_scene_select_third_key_on_enter(void* context) {
+    LfRfid* app = context;
+
+    if(t5577_multiwriter_load_key_from_file_select(app)) {
+        scene_manager_next_scene(app->scene_manager, LfRfidSceneWriteThirdKey);
+    } else {
+        scene_manager_previous_scene(app->scene_manager);
+    }
+}
+
+bool t5577_multiwriter_scene_select_third_key_on_event(void* context, SceneManagerEvent event) {
+    UNUSED(context);
+    UNUSED(event);
+    bool consumed = false;
+    return consumed;
+}
+
+void t5577_multiwriter_scene_select_third_key_on_exit(void* context) {
+    UNUSED(context);
+}

+ 80 - 0
scenes/t5577_multiwriter_scene_start.c

@@ -0,0 +1,80 @@
+#include "../t5577_multiwriter_i.h"
+#include <dolphin/dolphin.h>
+
+typedef enum {
+    SubmenuIndexWriteFirstKey,
+    SubmenuIndexWriteSecondKey,
+    SubmenuIndexWriteThirdKey,
+} SubmenuIndex;
+
+static void t5577_multiwriter_scene_start_submenu_callback(void* context, uint32_t index) {
+    LfRfid* app = context;
+
+    view_dispatcher_send_custom_event(app->view_dispatcher, index);
+}
+
+void t5577_multiwriter_scene_start_on_enter(void* context) {
+    LfRfid* app = context;
+    Submenu* submenu = app->submenu;
+
+    submenu_add_item(
+        submenu,
+        "Write first key",
+        SubmenuIndexWriteFirstKey,
+        t5577_multiwriter_scene_start_submenu_callback,
+        app);
+    submenu_add_item(
+        submenu,
+        "Write second key",
+        SubmenuIndexWriteSecondKey,
+        t5577_multiwriter_scene_start_submenu_callback,
+        app);
+    submenu_add_item(
+        submenu,
+        "Write third key",
+        SubmenuIndexWriteThirdKey,
+        t5577_multiwriter_scene_start_submenu_callback,
+        app);
+
+    submenu_set_selected_item(
+        submenu, scene_manager_get_scene_state(app->scene_manager, LfRfidSceneStart));
+
+    // clear key
+    furi_string_reset(app->file_name);
+    app->protocol_id = PROTOCOL_NO;
+    app->read_type = LFRFIDWorkerReadTypeAuto;
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewSubmenu);
+}
+
+bool t5577_multiwriter_scene_start_on_event(void* context, SceneManagerEvent event) {
+    LfRfid* app = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == SubmenuIndexWriteFirstKey) {
+            scene_manager_set_scene_state(
+                app->scene_manager, LfRfidSceneStart, SubmenuIndexWriteFirstKey);
+            scene_manager_next_scene(app->scene_manager, LfRfidSceneSelectFirstKey);
+            consumed = true;
+        } else if(event.event == SubmenuIndexWriteSecondKey) {
+            scene_manager_set_scene_state(
+                app->scene_manager, LfRfidSceneStart, SubmenuIndexWriteSecondKey);
+            scene_manager_next_scene(app->scene_manager, LfRfidSceneSelectSecondKey);
+            consumed = true;
+        } else if(event.event == SubmenuIndexWriteThirdKey) {
+            scene_manager_set_scene_state(
+                app->scene_manager, LfRfidSceneStart, SubmenuIndexWriteThirdKey);
+            scene_manager_next_scene(app->scene_manager, LfRfidSceneSelectThirdKey);
+            consumed = true;
+        }
+    }
+
+    return consumed;
+}
+
+void t5577_multiwriter_scene_start_on_exit(void* context) {
+    LfRfid* app = context;
+
+    submenu_reset(app->submenu);
+}

+ 103 - 0
scenes/t5577_multiwriter_scene_write_first_key.c

@@ -0,0 +1,103 @@
+#include "../t5577_multiwriter_i.h"
+#include "../protocols/EM41XX.h"
+#include "../t5577_multiwriter/helpers/t5577.h"
+
+static void
+    t5577_multiwriter_write_first_key_callback(LFRFIDWorkerWriteResult result, void* context) {
+    LfRfid* app = context;
+    uint32_t event = 0;
+
+    if(result == LFRFIDWorkerWriteOK) {
+        event = LfRfidEventWriteOK;
+    } else if(result == LFRFIDWorkerWriteProtocolCannotBeWritten) {
+        event = LfRfidEventWriteProtocolCannotBeWritten;
+    } else if(result == LFRFIDWorkerWriteFobCannotBeWritten) {
+        event = LfRfidEventWriteFobCannotBeWritten;
+    } else if(result == LFRFIDWorkerWriteTooLongToWrite) {
+        event = LfRfidEventWriteTooLongToWrite;
+    }
+
+    view_dispatcher_send_custom_event(app->view_dispatcher, event);
+}
+
+void t5577_multiwriter_scene_write_first_key_on_enter(void* context) {
+    LfRfid* app = context;
+    Popup* popup = app->popup;
+
+    popup_set_header(popup, "Writing", 89, 30, AlignCenter, AlignTop);
+    if(!furi_string_empty(app->file_name)) {
+        popup_set_text(popup, furi_string_get_cstr(app->file_name), 89, 43, AlignCenter, AlignTop);
+    } else {
+        popup_set_text(
+            popup,
+            protocol_dict_get_name(app->dict, app->protocol_id),
+            89,
+            43,
+            AlignCenter,
+            AlignTop);
+    }
+    popup_set_icon(popup, 0, 3, &I_RFIDDolphinSend_97x61);
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewPopup);
+    notification_message(app->notifications, &sequence_blink_start_magenta);
+
+    size_t size = protocol_dict_get_data_size(app->dict, app->protocol_id);
+
+    uint8_t* data = (uint8_t*)malloc(size);
+    protocol_dict_get_data(app->dict, app->protocol_id, data, size);
+
+    uint64_t key = bytes2num(data, size);
+    LFRFIDT5577Data data_to_write = {0};
+
+    add_em41xx_data(&data_to_write, key, 1);
+    set_em41xx_config(&data_to_write, 1);
+
+    t5577_write_with_mask(&data_to_write);
+
+    t5577_multiwriter_write_first_key_callback(LFRFIDWorkerWriteOK, context);
+}
+
+bool t5577_multiwriter_scene_write_first_key_on_event(void* context, SceneManagerEvent event) {
+    LfRfid* app = context;
+    Popup* popup = app->popup;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == LfRfidEventWriteOK) {
+            notification_message(app->notifications, &sequence_success);
+            scene_manager_next_scene(app->scene_manager, LfRfidSceneWriteSuccess);
+            consumed = true;
+        } else if(event.event == LfRfidEventWriteProtocolCannotBeWritten) {
+            popup_set_icon(popup, 72, 17, &I_WarningDolphinFlip_45x42);
+            popup_set_header(popup, "Error", 64, 3, AlignCenter, AlignTop);
+            popup_set_text(popup, "This protocol\ncannot be written", 3, 17, AlignLeft, AlignTop);
+            notification_message(app->notifications, &sequence_blink_start_red);
+            consumed = true;
+        } else if(
+            (event.event == LfRfidEventWriteFobCannotBeWritten) ||
+            (event.event == LfRfidEventWriteTooLongToWrite)) {
+            popup_set_icon(popup, 72, 17, &I_WarningDolphinFlip_45x42);
+            popup_set_header(popup, "Still trying to write...", 64, 3, AlignCenter, AlignTop);
+            popup_set_text(
+                popup,
+                "Make sure this\ncard is writable\nand not\nprotected.",
+                3,
+                17,
+                AlignLeft,
+                AlignTop);
+            notification_message(app->notifications, &sequence_blink_start_yellow);
+            consumed = true;
+        }
+    }
+
+    return consumed;
+}
+
+void t5577_multiwriter_scene_write_first_key_on_exit(void* context) {
+    LfRfid* app = context;
+    notification_message(app->notifications, &sequence_blink_stop);
+    popup_reset(app->popup);
+
+    size_t size = protocol_dict_get_data_size(app->dict, app->protocol_id);
+    protocol_dict_set_data(app->dict, app->protocol_id, app->old_key_data, size);
+}

+ 103 - 0
scenes/t5577_multiwriter_scene_write_second_key.c

@@ -0,0 +1,103 @@
+#include "../t5577_multiwriter_i.h"
+#include "../protocols/EM41XX.h"
+#include "../t5577_multiwriter/helpers/t5577.h"
+
+static void
+    t5577_multiwriter_write_second_key_callback(LFRFIDWorkerWriteResult result, void* context) {
+    LfRfid* app = context;
+    uint32_t event = 0;
+
+    if(result == LFRFIDWorkerWriteOK) {
+        event = LfRfidEventWriteOK;
+    } else if(result == LFRFIDWorkerWriteProtocolCannotBeWritten) {
+        event = LfRfidEventWriteProtocolCannotBeWritten;
+    } else if(result == LFRFIDWorkerWriteFobCannotBeWritten) {
+        event = LfRfidEventWriteFobCannotBeWritten;
+    } else if(result == LFRFIDWorkerWriteTooLongToWrite) {
+        event = LfRfidEventWriteTooLongToWrite;
+    }
+
+    view_dispatcher_send_custom_event(app->view_dispatcher, event);
+}
+
+void t5577_multiwriter_scene_write_second_key_on_enter(void* context) {
+    LfRfid* app = context;
+    Popup* popup = app->popup;
+
+    popup_set_header(popup, "Writing", 89, 30, AlignCenter, AlignTop);
+    if(!furi_string_empty(app->file_name)) {
+        popup_set_text(popup, furi_string_get_cstr(app->file_name), 89, 43, AlignCenter, AlignTop);
+    } else {
+        popup_set_text(
+            popup,
+            protocol_dict_get_name(app->dict, app->protocol_id),
+            89,
+            43,
+            AlignCenter,
+            AlignTop);
+    }
+    popup_set_icon(popup, 0, 3, &I_RFIDDolphinSend_97x61);
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewPopup);
+    notification_message(app->notifications, &sequence_blink_start_magenta);
+
+    size_t size = protocol_dict_get_data_size(app->dict, app->protocol_id);
+
+    uint8_t* data = (uint8_t*)malloc(size);
+    protocol_dict_get_data(app->dict, app->protocol_id, data, size);
+
+    uint64_t key = bytes2num(data, size);
+    LFRFIDT5577Data data_to_write = {0};
+
+    add_em41xx_data(&data_to_write, key, 3);
+    set_em41xx_config(&data_to_write, 2);
+
+    t5577_write_with_mask(&data_to_write);
+
+    t5577_multiwriter_write_second_key_callback(LFRFIDWorkerWriteOK, context);
+}
+
+bool t5577_multiwriter_scene_write_second_key_on_event(void* context, SceneManagerEvent event) {
+    LfRfid* app = context;
+    Popup* popup = app->popup;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == LfRfidEventWriteOK) {
+            notification_message(app->notifications, &sequence_success);
+            scene_manager_next_scene(app->scene_manager, LfRfidSceneWriteSuccess);
+            consumed = true;
+        } else if(event.event == LfRfidEventWriteProtocolCannotBeWritten) {
+            popup_set_icon(popup, 72, 17, &I_WarningDolphinFlip_45x42);
+            popup_set_header(popup, "Error", 64, 3, AlignCenter, AlignTop);
+            popup_set_text(popup, "This protocol\ncannot be written", 3, 17, AlignLeft, AlignTop);
+            notification_message(app->notifications, &sequence_blink_start_red);
+            consumed = true;
+        } else if(
+            (event.event == LfRfidEventWriteFobCannotBeWritten) ||
+            (event.event == LfRfidEventWriteTooLongToWrite)) {
+            popup_set_icon(popup, 72, 17, &I_WarningDolphinFlip_45x42);
+            popup_set_header(popup, "Still trying to write...", 64, 3, AlignCenter, AlignTop);
+            popup_set_text(
+                popup,
+                "Make sure this\ncard is writable\nand not\nprotected.",
+                3,
+                17,
+                AlignLeft,
+                AlignTop);
+            notification_message(app->notifications, &sequence_blink_start_yellow);
+            consumed = true;
+        }
+    }
+
+    return consumed;
+}
+
+void t5577_multiwriter_scene_write_second_key_on_exit(void* context) {
+    LfRfid* app = context;
+    notification_message(app->notifications, &sequence_blink_stop);
+    popup_reset(app->popup);
+
+    size_t size = protocol_dict_get_data_size(app->dict, app->protocol_id);
+    protocol_dict_set_data(app->dict, app->protocol_id, app->old_key_data, size);
+}

+ 38 - 0
scenes/t5577_multiwriter_scene_write_success.c

@@ -0,0 +1,38 @@
+#include "../t5577_multiwriter_i.h"
+
+void t5577_multiwriter_scene_write_success_on_enter(void* context) {
+    LfRfid* app = context;
+    Popup* popup = app->popup;
+
+    popup_set_header(popup, "Successfully\nwritten!", 94, 3, AlignCenter, AlignTop);
+    popup_set_icon(popup, 0, 6, &I_DolphinSuccess_91x55);
+    popup_set_context(popup, app);
+    popup_set_callback(popup, t5577_multiwriter_popup_timeout_callback);
+    popup_set_timeout(popup, 1500);
+    popup_enable_timeout(popup);
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewPopup);
+    notification_message_block(app->notifications, &sequence_set_green_255);
+}
+
+bool t5577_multiwriter_scene_write_success_on_event(void* context, SceneManagerEvent event) {
+    LfRfid* app = context;
+    bool consumed = false;
+
+    const uint32_t prev_scenes[] = {LfRfidSceneStart};
+
+    if((event.type == SceneManagerEventTypeBack) ||
+       ((event.type == SceneManagerEventTypeCustom) && (event.event == LfRfidEventPopupClosed))) {
+        scene_manager_search_and_switch_to_previous_scene_one_of(
+            app->scene_manager, prev_scenes, COUNT_OF(prev_scenes));
+        consumed = true;
+    }
+
+    return consumed;
+}
+
+void t5577_multiwriter_scene_write_success_on_exit(void* context) {
+    LfRfid* app = context;
+    notification_message_block(app->notifications, &sequence_reset_green);
+    popup_reset(app->popup);
+}

+ 103 - 0
scenes/t5577_multiwriter_scene_write_third_key.c

@@ -0,0 +1,103 @@
+#include "../t5577_multiwriter_i.h"
+#include "../protocols/EM41XX.h"
+#include "../t5577_multiwriter/helpers/t5577.h"
+
+static void
+    t5577_multiwriter_write_third_key_callback(LFRFIDWorkerWriteResult result, void* context) {
+    LfRfid* app = context;
+    uint32_t event = 0;
+
+    if(result == LFRFIDWorkerWriteOK) {
+        event = LfRfidEventWriteOK;
+    } else if(result == LFRFIDWorkerWriteProtocolCannotBeWritten) {
+        event = LfRfidEventWriteProtocolCannotBeWritten;
+    } else if(result == LFRFIDWorkerWriteFobCannotBeWritten) {
+        event = LfRfidEventWriteFobCannotBeWritten;
+    } else if(result == LFRFIDWorkerWriteTooLongToWrite) {
+        event = LfRfidEventWriteTooLongToWrite;
+    }
+
+    view_dispatcher_send_custom_event(app->view_dispatcher, event);
+}
+
+void t5577_multiwriter_scene_write_third_key_on_enter(void* context) {
+    LfRfid* app = context;
+    Popup* popup = app->popup;
+
+    popup_set_header(popup, "Writing", 89, 30, AlignCenter, AlignTop);
+    if(!furi_string_empty(app->file_name)) {
+        popup_set_text(popup, furi_string_get_cstr(app->file_name), 89, 43, AlignCenter, AlignTop);
+    } else {
+        popup_set_text(
+            popup,
+            protocol_dict_get_name(app->dict, app->protocol_id),
+            89,
+            43,
+            AlignCenter,
+            AlignTop);
+    }
+    popup_set_icon(popup, 0, 3, &I_RFIDDolphinSend_97x61);
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewPopup);
+    notification_message(app->notifications, &sequence_blink_start_magenta);
+
+    size_t size = protocol_dict_get_data_size(app->dict, app->protocol_id);
+
+    uint8_t* data = (uint8_t*)malloc(size);
+    protocol_dict_get_data(app->dict, app->protocol_id, data, size);
+
+    uint64_t key = bytes2num(data, size);
+    LFRFIDT5577Data data_to_write = {0};
+
+    add_em41xx_data(&data_to_write, key, 5);
+    set_em41xx_config(&data_to_write, 3);
+
+    t5577_write_with_mask(&data_to_write);
+
+    t5577_multiwriter_write_third_key_callback(LFRFIDWorkerWriteOK, context);
+}
+
+bool t5577_multiwriter_scene_write_third_key_on_event(void* context, SceneManagerEvent event) {
+    LfRfid* app = context;
+    Popup* popup = app->popup;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == LfRfidEventWriteOK) {
+            notification_message(app->notifications, &sequence_success);
+            scene_manager_next_scene(app->scene_manager, LfRfidSceneWriteSuccess);
+            consumed = true;
+        } else if(event.event == LfRfidEventWriteProtocolCannotBeWritten) {
+            popup_set_icon(popup, 72, 17, &I_WarningDolphinFlip_45x42);
+            popup_set_header(popup, "Error", 64, 3, AlignCenter, AlignTop);
+            popup_set_text(popup, "This protocol\ncannot be written", 3, 17, AlignLeft, AlignTop);
+            notification_message(app->notifications, &sequence_blink_start_red);
+            consumed = true;
+        } else if(
+            (event.event == LfRfidEventWriteFobCannotBeWritten) ||
+            (event.event == LfRfidEventWriteTooLongToWrite)) {
+            popup_set_icon(popup, 72, 17, &I_WarningDolphinFlip_45x42);
+            popup_set_header(popup, "Still trying to write...", 64, 3, AlignCenter, AlignTop);
+            popup_set_text(
+                popup,
+                "Make sure this\ncard is writable\nand not\nprotected.",
+                3,
+                17,
+                AlignLeft,
+                AlignTop);
+            notification_message(app->notifications, &sequence_blink_start_yellow);
+            consumed = true;
+        }
+    }
+
+    return consumed;
+}
+
+void t5577_multiwriter_scene_write_third_key_on_exit(void* context) {
+    LfRfid* app = context;
+    notification_message(app->notifications, &sequence_blink_stop);
+    popup_reset(app->popup);
+
+    size_t size = protocol_dict_get_data_size(app->dict, app->protocol_id);
+    protocol_dict_set_data(app->dict, app->protocol_id, app->old_key_data, size);
+}

+ 258 - 0
t5577_multiwriter.c

@@ -0,0 +1,258 @@
+#include "core/core_defines.h"
+#include "t5577_multiwriter_i.h"
+#include <dolphin/dolphin.h>
+
+static bool t5577_multiwriter_debug_custom_event_callback(void* context, uint32_t event) {
+    furi_assert(context);
+    LfRfid* app = context;
+    return scene_manager_handle_custom_event(app->scene_manager, event);
+}
+
+static bool t5577_multiwriter_debug_back_event_callback(void* context) {
+    furi_assert(context);
+    LfRfid* app = context;
+    return scene_manager_handle_back_event(app->scene_manager);
+}
+
+static LfRfid* t5577_multiwriter_alloc() {
+    LfRfid* t5577_multiwriter = malloc(sizeof(LfRfid));
+
+    t5577_multiwriter->storage = furi_record_open(RECORD_STORAGE);
+    t5577_multiwriter->dialogs = furi_record_open(RECORD_DIALOGS);
+
+    t5577_multiwriter->file_name = furi_string_alloc();
+    t5577_multiwriter->file_path = furi_string_alloc_set(LFRFID_APP_FOLDER);
+
+    t5577_multiwriter->dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax);
+
+    size_t size = protocol_dict_get_max_data_size(t5577_multiwriter->dict);
+    t5577_multiwriter->new_key_data = (uint8_t*)malloc(size);
+    t5577_multiwriter->old_key_data = (uint8_t*)malloc(size);
+
+    t5577_multiwriter->lfworker = lfrfid_worker_alloc(t5577_multiwriter->dict);
+
+    t5577_multiwriter->view_dispatcher = view_dispatcher_alloc();
+    t5577_multiwriter->scene_manager =
+        scene_manager_alloc(&t5577_multiwriter_scene_handlers, t5577_multiwriter);
+    view_dispatcher_enable_queue(t5577_multiwriter->view_dispatcher);
+    view_dispatcher_set_event_callback_context(
+        t5577_multiwriter->view_dispatcher, t5577_multiwriter);
+    view_dispatcher_set_custom_event_callback(
+        t5577_multiwriter->view_dispatcher, t5577_multiwriter_debug_custom_event_callback);
+    view_dispatcher_set_navigation_event_callback(
+        t5577_multiwriter->view_dispatcher, t5577_multiwriter_debug_back_event_callback);
+
+    // Open GUI record
+    t5577_multiwriter->gui = furi_record_open(RECORD_GUI);
+
+    // Open Notification record
+    t5577_multiwriter->notifications = furi_record_open(RECORD_NOTIFICATION);
+
+    // Submenu
+    t5577_multiwriter->submenu = submenu_alloc();
+    view_dispatcher_add_view(
+        t5577_multiwriter->view_dispatcher,
+        LfRfidViewSubmenu,
+        submenu_get_view(t5577_multiwriter->submenu));
+
+    // Dialog
+    t5577_multiwriter->dialog_ex = dialog_ex_alloc();
+    view_dispatcher_add_view(
+        t5577_multiwriter->view_dispatcher,
+        LfRfidViewDialogEx,
+        dialog_ex_get_view(t5577_multiwriter->dialog_ex));
+
+    // Popup
+    t5577_multiwriter->popup = popup_alloc();
+    view_dispatcher_add_view(
+        t5577_multiwriter->view_dispatcher,
+        LfRfidViewPopup,
+        popup_get_view(t5577_multiwriter->popup));
+
+    // Widget
+    t5577_multiwriter->widget = widget_alloc();
+    view_dispatcher_add_view(
+        t5577_multiwriter->view_dispatcher,
+        LfRfidViewWidget,
+        widget_get_view(t5577_multiwriter->widget));
+
+    // Text Input
+    t5577_multiwriter->text_input = text_input_alloc();
+    view_dispatcher_add_view(
+        t5577_multiwriter->view_dispatcher,
+        LfRfidViewTextInput,
+        text_input_get_view(t5577_multiwriter->text_input));
+
+    // Byte Input
+    t5577_multiwriter->byte_input = byte_input_alloc();
+    view_dispatcher_add_view(
+        t5577_multiwriter->view_dispatcher,
+        LfRfidViewByteInput,
+        byte_input_get_view(t5577_multiwriter->byte_input));
+
+    // Read custom view
+    t5577_multiwriter->read_view = t5577_multiwriter_view_read_alloc();
+    view_dispatcher_add_view(
+        t5577_multiwriter->view_dispatcher,
+        LfRfidViewRead,
+        t5577_multiwriter_view_read_get_view(t5577_multiwriter->read_view));
+
+    return t5577_multiwriter;
+} //-V773
+
+static void t5577_multiwriter_free(LfRfid* t5577_multiwriter) {
+    furi_assert(t5577_multiwriter);
+
+    furi_string_free(t5577_multiwriter->file_name);
+    furi_string_free(t5577_multiwriter->file_path);
+    protocol_dict_free(t5577_multiwriter->dict);
+
+    lfrfid_worker_free(t5577_multiwriter->lfworker);
+
+    free(t5577_multiwriter->new_key_data);
+    free(t5577_multiwriter->old_key_data);
+
+    // Submenu
+    view_dispatcher_remove_view(t5577_multiwriter->view_dispatcher, LfRfidViewSubmenu);
+    submenu_free(t5577_multiwriter->submenu);
+
+    // DialogEx
+    view_dispatcher_remove_view(t5577_multiwriter->view_dispatcher, LfRfidViewDialogEx);
+    dialog_ex_free(t5577_multiwriter->dialog_ex);
+
+    // Popup
+    view_dispatcher_remove_view(t5577_multiwriter->view_dispatcher, LfRfidViewPopup);
+    popup_free(t5577_multiwriter->popup);
+
+    // Widget
+    view_dispatcher_remove_view(t5577_multiwriter->view_dispatcher, LfRfidViewWidget);
+    widget_free(t5577_multiwriter->widget);
+
+    // TextInput
+    view_dispatcher_remove_view(t5577_multiwriter->view_dispatcher, LfRfidViewTextInput);
+    text_input_free(t5577_multiwriter->text_input);
+
+    // ByteInput
+    view_dispatcher_remove_view(t5577_multiwriter->view_dispatcher, LfRfidViewByteInput);
+    byte_input_free(t5577_multiwriter->byte_input);
+
+    // Read custom view
+    view_dispatcher_remove_view(t5577_multiwriter->view_dispatcher, LfRfidViewRead);
+    t5577_multiwriter_view_read_free(t5577_multiwriter->read_view);
+
+    // View Dispatcher
+    view_dispatcher_free(t5577_multiwriter->view_dispatcher);
+
+    // Scene Manager
+    scene_manager_free(t5577_multiwriter->scene_manager);
+
+    // GUI
+    furi_record_close(RECORD_GUI);
+    t5577_multiwriter->gui = NULL;
+
+    // Notifications
+    furi_record_close(RECORD_NOTIFICATION);
+    t5577_multiwriter->notifications = NULL;
+
+    furi_record_close(RECORD_STORAGE);
+    furi_record_close(RECORD_DIALOGS);
+
+    free(t5577_multiwriter);
+}
+
+int32_t t5577_multiwriter_app(void* p) {
+    LfRfid* app = t5577_multiwriter_alloc();
+    UNUSED(p);
+
+    t5577_multiwriter_make_app_folder(app);
+
+    {
+        view_dispatcher_attach_to_gui(
+            app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
+        scene_manager_next_scene(app->scene_manager, LfRfidSceneStart);
+    }
+
+    view_dispatcher_run(app->view_dispatcher);
+
+    t5577_multiwriter_free(app);
+
+    return 0;
+}
+
+bool t5577_multiwriter_load_key_from_file_select(LfRfid* app) {
+    furi_assert(app);
+
+    DialogsFileBrowserOptions browser_options;
+    dialog_file_browser_set_basic_options(
+        &browser_options, LFRFID_APP_FILENAME_EXTENSION, &I_125_10px);
+    browser_options.base_path = LFRFID_APP_FOLDER;
+
+    // Input events and views are managed by file_browser
+    bool result =
+        dialog_file_browser_show(app->dialogs, app->file_path, app->file_path, &browser_options);
+
+    if(result) {
+        result = t5577_multiwriter_load_key_data(app, app->file_path, true);
+    }
+
+    return result;
+}
+
+bool t5577_multiwriter_load_key_data(LfRfid* app, FuriString* path, bool show_dialog) {
+    bool result = false;
+
+    do {
+        app->protocol_id = lfrfid_dict_file_load(app->dict, furi_string_get_cstr(path));
+        if(app->protocol_id == PROTOCOL_NO) break;
+        if(app->protocol_id != LFRFIDProtocolEM4100) break;
+
+        path_extract_filename(path, app->file_name, true);
+        result = true;
+    } while(0);
+
+    if((!result) && (show_dialog)) {
+        dialog_message_show_storage_error(app->dialogs, "Cannot load\nkey file");
+    }
+
+    return result;
+}
+
+void t5577_multiwriter_make_app_folder(LfRfid* app) {
+    furi_assert(app);
+
+    if(!storage_simply_mkdir(app->storage, LFRFID_APP_FOLDER)) {
+        dialog_message_show_storage_error(app->dialogs, "Cannot create\napp folder");
+    }
+}
+
+void t5577_multiwriter_text_store_set(LfRfid* app, const char* text, ...) {
+    furi_assert(app);
+    va_list args;
+    va_start(args, text);
+
+    vsnprintf(app->text_store, LFRFID_TEXT_STORE_SIZE, text, args);
+
+    va_end(args);
+}
+
+void t5577_multiwriter_text_store_clear(LfRfid* app) {
+    furi_assert(app);
+    memset(app->text_store, 0, sizeof(app->text_store));
+}
+
+void t5577_multiwriter_popup_timeout_callback(void* context) {
+    LfRfid* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, LfRfidEventPopupClosed);
+}
+
+void t5577_multiwriter_widget_callback(GuiButtonType result, InputType type, void* context) {
+    LfRfid* app = context;
+    if(type == InputTypeShort) {
+        view_dispatcher_send_custom_event(app->view_dispatcher, result);
+    }
+}
+
+void t5577_multiwriter_text_input_callback(void* context) {
+    LfRfid* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, LfRfidEventNext);
+}

+ 121 - 0
t5577_multiwriter_i.h

@@ -0,0 +1,121 @@
+#pragma once
+
+#include <furi.h>
+#include <furi_hal.h>
+
+#include <gui/gui.h>
+#include <gui/view.h>
+#include "assets/assets_icons.h"
+#include <gui/view_dispatcher.h>
+#include <gui/scene_manager.h>
+#include <cli/cli.h>
+#include <notification/notification_messages.h>
+
+#include <gui/modules/submenu.h>
+#include <gui/modules/dialog_ex.h>
+#include <gui/modules/popup.h>
+#include <gui/modules/text_input.h>
+#include <gui/modules/byte_input.h>
+#include <gui/modules/widget.h>
+
+#include "views/t5577_multiwriter_view_read.h"
+
+#include <notification/notification_messages.h>
+#include <dialogs/dialogs.h>
+#include <storage/storage.h>
+#include <flipper_format/flipper_format.h>
+
+#include <rpc/rpc_app.h>
+
+#include <toolbox/protocols/protocol_dict.h>
+#include <toolbox/path.h>
+#include <lfrfid/lfrfid_dict_file.h>
+#include <lfrfid/protocols/lfrfid_protocols.h>
+#include <lfrfid/lfrfid_worker.h>
+
+#include "scenes/t5577_multiwriter_scene.h"
+
+#define LFRFID_KEY_NAME_SIZE 22
+#define LFRFID_TEXT_STORE_SIZE 40
+
+#define LFRFID_APP_FOLDER ANY_PATH("lfrfid")
+#define LFRFID_SD_FOLDER EXT_PATH("lfrfid")
+#define LFRFID_APP_FILENAME_PREFIX "RFID"
+#define LFRFID_APP_FILENAME_EXTENSION ".rfid"
+#define LFRFID_APP_SHADOW_FILENAME_EXTENSION ".shd"
+
+enum LfRfidCustomEvent {
+    LfRfidEventNext = 100,
+    LfRfidEventExit,
+    LfRfidEventPopupClosed,
+    LfRfidEventWriteOK,
+    LfRfidEventWriteProtocolCannotBeWritten,
+    LfRfidEventWriteFobCannotBeWritten,
+    LfRfidEventWriteTooLongToWrite,
+};
+
+typedef enum {
+    LfRfidRpcStateIdle,
+    LfRfidRpcStateEmulating,
+} LfRfidRpcState;
+
+typedef struct LfRfid LfRfid;
+
+struct LfRfid {
+    LFRFIDWorker* lfworker;
+    ViewDispatcher* view_dispatcher;
+    Gui* gui;
+    NotificationApp* notifications;
+    SceneManager* scene_manager;
+    Storage* storage;
+    DialogsApp* dialogs;
+    Widget* widget;
+
+    char text_store[LFRFID_TEXT_STORE_SIZE + 1];
+    FuriString* file_path;
+    FuriString* file_name;
+
+    ProtocolDict* dict;
+    ProtocolId protocol_id;
+    ProtocolId protocol_id_next;
+    LFRFIDWorkerReadType read_type;
+
+    uint8_t* old_key_data;
+    uint8_t* new_key_data;
+
+    // Common Views
+    Submenu* submenu;
+    DialogEx* dialog_ex;
+    Popup* popup;
+    TextInput* text_input;
+    ByteInput* byte_input;
+
+    // Custom views
+    LfRfidReadView* read_view;
+};
+
+typedef enum {
+    LfRfidViewSubmenu,
+    LfRfidViewDialogEx,
+    LfRfidViewPopup,
+    LfRfidViewWidget,
+    LfRfidViewTextInput,
+    LfRfidViewByteInput,
+    LfRfidViewRead,
+} LfRfidView;
+
+bool t5577_multiwriter_load_key_from_file_select(LfRfid* app);
+
+bool t5577_multiwriter_load_key_data(LfRfid* app, FuriString* path, bool show_dialog);
+
+void t5577_multiwriter_make_app_folder(LfRfid* app);
+
+void t5577_multiwriter_text_store_set(LfRfid* app, const char* text, ...);
+
+void t5577_multiwriter_text_store_clear(LfRfid* app);
+
+void t5577_multiwriter_popup_timeout_callback(void* context);
+
+void t5577_multiwriter_widget_callback(GuiButtonType result, InputType type, void* context);
+
+void t5577_multiwriter_text_input_callback(void* context);

+ 113 - 0
views/t5577_multiwriter_view_read.c

@@ -0,0 +1,113 @@
+#include "t5577_multiwriter_view_read.h"
+#include <gui/elements.h>
+#include "../assets/assets_icons.h"
+
+#define TEMP_STR_LEN 128
+
+struct LfRfidReadView {
+    View* view;
+};
+
+typedef struct {
+    IconAnimation* icon;
+    LfRfidReadViewMode read_mode;
+} LfRfidReadViewModel;
+
+static void t5577_multiwriter_view_read_draw_callback(Canvas* canvas, void* _model) {
+    LfRfidReadViewModel* model = _model;
+    canvas_set_color(canvas, ColorBlack);
+
+    canvas_draw_icon(canvas, 0, 8, &I_NFC_manual_60x50);
+
+    canvas_set_font(canvas, FontPrimary);
+
+    if(model->read_mode == LfRfidReadAsk) {
+        canvas_draw_str(canvas, 70, 16, "Reading 1/2");
+
+        canvas_draw_str(canvas, 77, 29, "ASK");
+        canvas_draw_icon(canvas, 70, 22, &I_ButtonRight_4x7);
+        canvas_draw_icon_animation(canvas, 102, 21, model->icon);
+
+        canvas_set_font(canvas, FontSecondary);
+        canvas_draw_str(canvas, 77, 43, "PSK");
+    } else if(model->read_mode == LfRfidReadPsk) {
+        canvas_draw_str(canvas, 70, 16, "Reading 2/2");
+
+        canvas_draw_str(canvas, 77, 43, "PSK");
+        canvas_draw_icon(canvas, 70, 36, &I_ButtonRight_4x7);
+        canvas_draw_icon_animation(canvas, 102, 35, model->icon);
+
+        canvas_set_font(canvas, FontSecondary);
+        canvas_draw_str(canvas, 77, 29, "ASK");
+    } else {
+        canvas_draw_str(canvas, 72, 16, "Reading");
+
+        if(model->read_mode == LfRfidReadAskOnly) {
+            canvas_draw_str(canvas, 77, 35, "ASK");
+        } else {
+            canvas_draw_str(canvas, 77, 35, "PSK");
+        }
+        canvas_draw_icon_animation(canvas, 102, 27, model->icon);
+    }
+
+    canvas_set_font(canvas, FontSecondary);
+    canvas_draw_str(canvas, 61, 56, "Don't move card");
+}
+
+void t5577_multiwriter_view_read_enter(void* context) {
+    LfRfidReadView* read_view = context;
+    with_view_model(
+        read_view->view, LfRfidReadViewModel * model, { icon_animation_start(model->icon); }, true);
+}
+
+void t5577_multiwriter_view_read_exit(void* context) {
+    LfRfidReadView* read_view = context;
+    with_view_model(
+        read_view->view, LfRfidReadViewModel * model, { icon_animation_stop(model->icon); }, false);
+}
+
+LfRfidReadView* t5577_multiwriter_view_read_alloc() {
+    LfRfidReadView* read_view = malloc(sizeof(LfRfidReadView));
+    read_view->view = view_alloc();
+    view_set_context(read_view->view, read_view);
+    view_allocate_model(read_view->view, ViewModelTypeLocking, sizeof(LfRfidReadViewModel));
+
+    with_view_model(
+        read_view->view,
+        LfRfidReadViewModel * model,
+        {
+            model->icon = icon_animation_alloc(&A_Round_loader_8x8);
+            view_tie_icon_animation(read_view->view, model->icon);
+        },
+        false);
+
+    view_set_draw_callback(read_view->view, t5577_multiwriter_view_read_draw_callback);
+    view_set_enter_callback(read_view->view, t5577_multiwriter_view_read_enter);
+    view_set_exit_callback(read_view->view, t5577_multiwriter_view_read_exit);
+
+    return read_view;
+}
+
+void t5577_multiwriter_view_read_free(LfRfidReadView* read_view) {
+    with_view_model(
+        read_view->view, LfRfidReadViewModel * model, { icon_animation_free(model->icon); }, false);
+
+    view_free(read_view->view);
+    free(read_view);
+}
+
+View* t5577_multiwriter_view_read_get_view(LfRfidReadView* read_view) {
+    return read_view->view;
+}
+
+void t5577_multiwriter_view_read_set_read_mode(LfRfidReadView* read_view, LfRfidReadViewMode mode) {
+    with_view_model(
+        read_view->view,
+        LfRfidReadViewModel * model,
+        {
+            icon_animation_stop(model->icon);
+            icon_animation_start(model->icon);
+            model->read_mode = mode;
+        },
+        true);
+}

+ 19 - 0
views/t5577_multiwriter_view_read.h

@@ -0,0 +1,19 @@
+#pragma once
+#include <gui/view.h>
+
+typedef enum {
+    LfRfidReadAsk,
+    LfRfidReadPsk,
+    LfRfidReadAskOnly,
+    LfRfidReadPskOnly
+} LfRfidReadViewMode;
+
+typedef struct LfRfidReadView LfRfidReadView;
+
+LfRfidReadView* t5577_multiwriter_view_read_alloc();
+
+void t5577_multiwriter_view_read_free(LfRfidReadView* read_view);
+
+View* t5577_multiwriter_view_read_get_view(LfRfidReadView* read_view);
+
+void t5577_multiwriter_view_read_set_read_mode(LfRfidReadView* read_view, LfRfidReadViewMode mode);

Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů