Просмотр исходного кода

Merge branch 'dev' of github.com:0xchocolate/flipperzero-firmware-with-wifi-marauder-companion into feature_wifi_marauder_app

0xchocolate 2 лет назад
Родитель
Сommit
5361104a0c
100 измененных файлов с 1767 добавлено и 1113 удалено
  1. 1 1
      .github/workflows/unit_tests.yml
  2. 2 2
      .github/workflows/updater_test.yml
  3. 3 0
      .pvsconfig
  4. 3 2
      .vscode/example/launch.json
  5. 34 27
      applications/debug/unit_tests/power/power_test.c
  6. 4 5
      applications/debug/unit_tests/rpc/rpc_test.c
  7. 4 4
      applications/debug/unit_tests/storage/dirwalk_test.c
  8. 291 1
      applications/debug/unit_tests/storage/storage_test.c
  9. 18 0
      applications/examples/example_apps_data/README.md
  10. 9 0
      applications/examples/example_apps_data/application.fam
  11. 40 0
      applications/examples/example_apps_data/example_apps_data.c
  12. 5 1
      applications/main/archive/helpers/archive_browser.c
  13. 1 1
      applications/main/archive/helpers/archive_favorites.c
  14. 1 1
      applications/main/archive/helpers/archive_files.c
  15. 4 4
      applications/main/bad_usb/bad_usb_app.c
  16. 61 20
      applications/main/bad_usb/bad_usb_script.c
  17. 0 2
      applications/main/bad_usb/scenes/bad_usb_scene_file_select.c
  18. 3 1
      applications/main/bad_usb/scenes/bad_usb_scene_work.c
  19. 18 5
      applications/main/bad_usb/views/bad_usb_view.c
  20. 2 0
      applications/main/bad_usb/views/bad_usb_view.h
  21. 7 0
      applications/main/fap_loader/fap_loader_app.c
  22. 87 143
      applications/main/ibutton/ibutton.c
  23. 85 91
      applications/main/ibutton/ibutton_cli.c
  24. 1 0
      applications/main/ibutton/ibutton_custom_event.h
  25. 31 19
      applications/main/ibutton/ibutton_i.h
  26. 27 33
      applications/main/ibutton/scenes/ibutton_scene_add_type.c
  27. 25 18
      applications/main/ibutton/scenes/ibutton_scene_add_value.c
  28. 2 2
      applications/main/ibutton/scenes/ibutton_scene_config.h
  29. 15 59
      applications/main/ibutton/scenes/ibutton_scene_delete_confirm.c
  30. 18 48
      applications/main/ibutton/scenes/ibutton_scene_emulate.c
  31. 30 42
      applications/main/ibutton/scenes/ibutton_scene_info.c
  32. 7 19
      applications/main/ibutton/scenes/ibutton_scene_read.c
  33. 0 70
      applications/main/ibutton/scenes/ibutton_scene_read_crc_error.c
  34. 58 0
      applications/main/ibutton/scenes/ibutton_scene_read_error.c
  35. 46 11
      applications/main/ibutton/scenes/ibutton_scene_read_key_menu.c
  36. 0 71
      applications/main/ibutton/scenes/ibutton_scene_read_not_key_error.c
  37. 30 48
      applications/main/ibutton/scenes/ibutton_scene_read_success.c
  38. 15 27
      applications/main/ibutton/scenes/ibutton_scene_rpc.c
  39. 20 24
      applications/main/ibutton/scenes/ibutton_scene_save_name.c
  40. 35 37
      applications/main/ibutton/scenes/ibutton_scene_saved_key_menu.c
  41. 3 3
      applications/main/ibutton/scenes/ibutton_scene_select_key.c
  42. 5 12
      applications/main/ibutton/scenes/ibutton_scene_start.c
  43. 26 0
      applications/main/ibutton/scenes/ibutton_scene_view_data.c
  44. 32 46
      applications/main/ibutton/scenes/ibutton_scene_write.c
  45. 13 8
      applications/main/infrared/infrared.c
  46. 1 0
      applications/main/infrared/infrared_i.h
  47. 1 1
      applications/main/infrared/scenes/infrared_scene_universal.c
  48. 1 1
      applications/main/nfc/nfc_cli.c
  49. 1 0
      applications/main/nfc/scenes/nfc_scene_config.h
  50. 106 0
      applications/main/nfc/scenes/nfc_scene_mf_classic_data.c
  51. 5 1
      applications/main/nfc/scenes/nfc_scene_nfc_data_info.c
  52. 3 0
      applications/main/nfc/scenes/nfc_scene_read_card_success.c
  53. 2 0
      applications/main/nfc/scenes/nfc_scene_saved_menu.c
  54. 1 0
      applications/main/subghz/helpers/subghz_types.h
  55. 2 1
      applications/main/subghz/scenes/subghz_scene_receiver_info.c
  56. 3 2
      applications/main/subghz/scenes/subghz_scene_set_type.c
  57. 18 20
      applications/main/subghz/scenes/subghz_scene_transmitter.c
  58. 17 4
      applications/main/subghz/subghz_i.c
  59. 2 2
      applications/plugins/clock/clock_app.c
  60. 1 1
      applications/plugins/dap_link/application.fam
  61. 4 4
      applications/plugins/hid_app/application.fam
  62. 11 1
      applications/plugins/hid_app/hid.c
  63. 1 1
      applications/plugins/hid_app/hid.h
  64. 1 1
      applications/plugins/music_player/application.fam
  65. 8 3
      applications/plugins/music_player/music_player.c
  66. 1 1
      applications/plugins/nfc_magic/application.fam
  67. 2 2
      applications/plugins/picopass/application.fam
  68. 10 10
      applications/plugins/picopass/helpers/iclass_elite_dict.c
  69. 18 0
      applications/plugins/picopass/picopass.c
  70. 6 10
      applications/plugins/picopass/picopass_device.c
  71. 1 2
      applications/plugins/picopass/picopass_device.h
  72. 12 0
      applications/plugins/picopass/picopass_i.h
  73. 160 5
      applications/plugins/picopass/picopass_worker.c
  74. 1 0
      applications/plugins/picopass/picopass_worker.h
  75. 1 0
      applications/plugins/picopass/picopass_worker_i.h
  76. 2 0
      applications/plugins/picopass/scenes/picopass_scene_config.h
  77. 10 1
      applications/plugins/picopass/scenes/picopass_scene_read_card.c
  78. 23 18
      applications/plugins/picopass/scenes/picopass_scene_read_card_success.c
  79. 78 0
      applications/plugins/picopass/scenes/picopass_scene_read_factory_success.c
  80. 1 3
      applications/plugins/picopass/scenes/picopass_scene_save_name.c
  81. 13 0
      applications/plugins/picopass/scenes/picopass_scene_write_card_success.c
  82. 53 0
      applications/plugins/picopass/scenes/picopass_scene_write_key.c
  83. 1 1
      applications/plugins/signal_generator/application.fam
  84. 1 1
      applications/plugins/spi_mem_manager/application.fam
  85. 1 1
      applications/plugins/spi_mem_manager/scenes/spi_mem_scene_start.c
  86. 5 5
      applications/plugins/spi_mem_manager/spi_mem_app.c
  87. 0 1
      applications/plugins/spi_mem_manager/spi_mem_app_i.h
  88. 1 7
      applications/plugins/spi_mem_manager/spi_mem_files.c
  89. 0 1
      applications/plugins/spi_mem_manager/spi_mem_files.h
  90. 1 1
      applications/plugins/weather_station/application.fam
  91. 1 1
      applications/plugins/weather_station/helpers/weather_station_types.h
  92. 7 15
      applications/plugins/weather_station/protocols/acurite_592txr.c
  93. 5 4
      applications/plugins/weather_station/protocols/acurite_592txr.h
  94. 7 15
      applications/plugins/weather_station/protocols/acurite_606tx.c
  95. 5 4
      applications/plugins/weather_station/protocols/acurite_606tx.h
  96. 7 15
      applications/plugins/weather_station/protocols/acurite_609txc.c
  97. 5 4
      applications/plugins/weather_station/protocols/acurite_609txc.h
  98. 7 15
      applications/plugins/weather_station/protocols/ambient_weather.c
  99. 5 4
      applications/plugins/weather_station/protocols/ambient_weather.h
  100. 5 15
      applications/plugins/weather_station/protocols/auriol_hg0601a.c

+ 1 - 1
.github/workflows/unit_tests.yml

@@ -10,7 +10,7 @@ env:
 
 
 jobs:
 jobs:
   run_units_on_bench:
   run_units_on_bench:
-    runs-on: [self-hosted, FlipperZeroTest]
+    runs-on: [self-hosted, FlipperZeroUnitTest]
     steps:
     steps:
       - name: 'Decontaminate previous build leftovers'
       - name: 'Decontaminate previous build leftovers'
         run: |
         run: |

+ 2 - 2
.github/workflows/updater_test.yml

@@ -10,7 +10,7 @@ env:
 
 
 jobs:
 jobs:
   test_updater_on_bench:
   test_updater_on_bench:
-    runs-on: [self-hosted, FlipperZeroTestMac1]
+    runs-on: [self-hosted, FlipperZeroUpdaterTest]
     steps:
     steps:
       - name: 'Decontaminate previous build leftovers'
       - name: 'Decontaminate previous build leftovers'
         run: |
         run: |
@@ -27,7 +27,7 @@ jobs:
       - name: 'Get flipper from device manager (mock)'
       - name: 'Get flipper from device manager (mock)'
         id: device
         id: device
         run: |
         run: |
-          echo "flipper=/dev/tty.usbmodemflip_Rekigyn1" >> $GITHUB_OUTPUT
+          echo "flipper=Rekigyn" >> $GITHUB_OUTPUT
           echo "stlink=0F020D026415303030303032" >> $GITHUB_OUTPUT
           echo "stlink=0F020D026415303030303032" >> $GITHUB_OUTPUT
 
 
       - name: 'Flashing target firmware'
       - name: 'Flashing target firmware'

+ 3 - 0
.pvsconfig

@@ -44,3 +44,6 @@
 
 
 # Functions that always return the same error code
 # Functions that always return the same error code
 //-V:picopass_device_decrypt:1048
 //-V:picopass_device_decrypt:1048
+
+# Examples
+//V_EXCLUDE_PATH applications/examples/

+ 3 - 2
.vscode/example/launch.json

@@ -11,9 +11,10 @@
             "args": {
             "args": {
                 "useSingleResult": true,
                 "useSingleResult": true,
                 "env": {
                 "env": {
-                    "PATH": "${workspaceFolder};${env:PATH}"
+                    "PATH": "${workspaceFolder};${env:PATH}",
+                    "FBT_QUIET": 1
                 },
                 },
-                "command": "./fbt get_blackmagic",
+                "command": "fbt get_blackmagic",
                 "description": "Get Blackmagic device",
                 "description": "Get Blackmagic device",
             }
             }
         }
         }

+ 34 - 27
applications/debug/unit_tests/power/power_test.c

@@ -3,56 +3,63 @@
 #include "../minunit.h"
 #include "../minunit.h"
 
 
 static void power_test_deinit(void) {
 static void power_test_deinit(void) {
-    // Try to reset to default charging voltage
-    furi_hal_power_set_battery_charging_voltage(4.208f);
+    // Try to reset to default charge voltage limit
+    furi_hal_power_set_battery_charge_voltage_limit(4.208f);
 }
 }
 
 
-MU_TEST(test_power_charge_voltage_exact) {
-    // Power of 16mV charge voltages get applied exactly
+MU_TEST(test_power_charge_voltage_limit_exact) {
+    // Power of 16mV charge voltage limits get applied exactly
     // (bq25896 charge controller works in 16mV increments)
     // (bq25896 charge controller works in 16mV increments)
     //
     //
     // This test may need adapted if other charge controllers are used in the future.
     // This test may need adapted if other charge controllers are used in the future.
     for(uint16_t charge_mv = 3840; charge_mv <= 4208; charge_mv += 16) {
     for(uint16_t charge_mv = 3840; charge_mv <= 4208; charge_mv += 16) {
         float charge_volt = (float)charge_mv / 1000.0f;
         float charge_volt = (float)charge_mv / 1000.0f;
-        furi_hal_power_set_battery_charging_voltage(charge_volt);
-        mu_assert_double_eq(charge_volt, furi_hal_power_get_battery_charging_voltage());
+        furi_hal_power_set_battery_charge_voltage_limit(charge_volt);
+        mu_assert_double_eq(charge_volt, furi_hal_power_get_battery_charge_voltage_limit());
     }
     }
 }
 }
 
 
-MU_TEST(test_power_charge_voltage_floating_imprecision) {
+MU_TEST(test_power_charge_voltage_limit_floating_imprecision) {
     // 4.016f should act as 4.016 V, even with floating point imprecision
     // 4.016f should act as 4.016 V, even with floating point imprecision
-    furi_hal_power_set_battery_charging_voltage(4.016f);
-    mu_assert_double_eq(4.016f, furi_hal_power_get_battery_charging_voltage());
+    furi_hal_power_set_battery_charge_voltage_limit(4.016f);
+    mu_assert_double_eq(4.016f, furi_hal_power_get_battery_charge_voltage_limit());
 }
 }
 
 
-MU_TEST(test_power_charge_voltage_inexact) {
-    // Charge voltages that are not power of 16mV get truncated down
-    furi_hal_power_set_battery_charging_voltage(3.841f);
-    mu_assert_double_eq(3.840, furi_hal_power_get_battery_charging_voltage());
+MU_TEST(test_power_charge_voltage_limit_inexact) {
+    // Charge voltage limits that are not power of 16mV get truncated down
+    furi_hal_power_set_battery_charge_voltage_limit(3.841f);
+    mu_assert_double_eq(3.840, furi_hal_power_get_battery_charge_voltage_limit());
 
 
-    furi_hal_power_set_battery_charging_voltage(3.900f);
-    mu_assert_double_eq(3.888, furi_hal_power_get_battery_charging_voltage());
+    furi_hal_power_set_battery_charge_voltage_limit(3.900f);
+    mu_assert_double_eq(3.888, furi_hal_power_get_battery_charge_voltage_limit());
 
 
-    furi_hal_power_set_battery_charging_voltage(4.200f);
-    mu_assert_double_eq(4.192, furi_hal_power_get_battery_charging_voltage());
+    furi_hal_power_set_battery_charge_voltage_limit(4.200f);
+    mu_assert_double_eq(4.192, furi_hal_power_get_battery_charge_voltage_limit());
 }
 }
 
 
-MU_TEST(test_power_charge_voltage_invalid_clamped) {
-    // Out-of-range charge voltages get clamped to 3.840 V and 4.208 V
-    furi_hal_power_set_battery_charging_voltage(3.808f);
-    mu_assert_double_eq(3.840, furi_hal_power_get_battery_charging_voltage());
+MU_TEST(test_power_charge_voltage_limit_invalid_clamped) {
+    // Out-of-range charge voltage limits get clamped to 3.840 V and 4.208 V
+    furi_hal_power_set_battery_charge_voltage_limit(3.808f);
+    mu_assert_double_eq(3.840, furi_hal_power_get_battery_charge_voltage_limit());
+    furi_hal_power_set_battery_charge_voltage_limit(1.0f);
+    mu_assert_double_eq(3.840, furi_hal_power_get_battery_charge_voltage_limit());
 
 
     // NOTE: Intentionally picking a small increment above 4.208 V to reduce the risk of an
     // NOTE: Intentionally picking a small increment above 4.208 V to reduce the risk of an
     // unhappy battery if this fails.
     // unhappy battery if this fails.
-    furi_hal_power_set_battery_charging_voltage(4.240f);
-    mu_assert_double_eq(4.208, furi_hal_power_get_battery_charging_voltage());
+    furi_hal_power_set_battery_charge_voltage_limit(4.240f);
+    mu_assert_double_eq(4.208, furi_hal_power_get_battery_charge_voltage_limit());
+    // Likewise, picking a number that the uint8_t wraparound in the driver would result in a
+    // VREG value under 23 if this test fails.
+    // E.g. (uint8_t)((8105-3840)/16) -> 10
+    furi_hal_power_set_battery_charge_voltage_limit(8.105f);
+    mu_assert_double_eq(4.208, furi_hal_power_get_battery_charge_voltage_limit());
 }
 }
 
 
 MU_TEST_SUITE(test_power_suite) {
 MU_TEST_SUITE(test_power_suite) {
-    MU_RUN_TEST(test_power_charge_voltage_exact);
-    MU_RUN_TEST(test_power_charge_voltage_floating_imprecision);
-    MU_RUN_TEST(test_power_charge_voltage_inexact);
-    MU_RUN_TEST(test_power_charge_voltage_invalid_clamped);
+    MU_RUN_TEST(test_power_charge_voltage_limit_exact);
+    MU_RUN_TEST(test_power_charge_voltage_limit_floating_imprecision);
+    MU_RUN_TEST(test_power_charge_voltage_limit_inexact);
+    MU_RUN_TEST(test_power_charge_voltage_limit_invalid_clamped);
     power_test_deinit();
     power_test_deinit();
 }
 }
 
 

+ 4 - 5
applications/debug/unit_tests/rpc/rpc_test.c

@@ -191,7 +191,7 @@ static void clean_directory(Storage* fs_api, const char* clean_dir) {
             size_t size = strlen(clean_dir) + strlen(name) + 1 + 1;
             size_t size = strlen(clean_dir) + strlen(name) + 1 + 1;
             char* fullname = malloc(size);
             char* fullname = malloc(size);
             snprintf(fullname, size, "%s/%s", clean_dir, name);
             snprintf(fullname, size, "%s/%s", clean_dir, name);
-            if(fileinfo.flags & FSF_DIRECTORY) {
+            if(file_info_is_dir(&fileinfo)) {
                 clean_directory(fs_api, fullname);
                 clean_directory(fs_api, fullname);
             }
             }
             FS_Error error = storage_common_remove(fs_api, fullname);
             FS_Error error = storage_common_remove(fs_api, fullname);
@@ -608,9 +608,8 @@ static void test_rpc_storage_list_create_expected_list(
             }
             }
 
 
             if(path_contains_only_ascii(name)) {
             if(path_contains_only_ascii(name)) {
-                list->file[i].type = (fileinfo.flags & FSF_DIRECTORY) ?
-                                         PB_Storage_File_FileType_DIR :
-                                         PB_Storage_File_FileType_FILE;
+                list->file[i].type = file_info_is_dir(&fileinfo) ? PB_Storage_File_FileType_DIR :
+                                                                   PB_Storage_File_FileType_FILE;
                 list->file[i].size = fileinfo.size;
                 list->file[i].size = fileinfo.size;
                 list->file[i].data = NULL;
                 list->file[i].data = NULL;
                 /* memory free inside rpc_encode_and_send() -> pb_release() */
                 /* memory free inside rpc_encode_and_send() -> pb_release() */
@@ -873,7 +872,7 @@ static void test_rpc_storage_stat_run(const char* path, uint32_t command_id) {
     if(error == FSE_OK) {
     if(error == FSE_OK) {
         response->which_content = PB_Main_storage_stat_response_tag;
         response->which_content = PB_Main_storage_stat_response_tag;
         response->content.storage_stat_response.has_file = true;
         response->content.storage_stat_response.has_file = true;
-        response->content.storage_stat_response.file.type = (fileinfo.flags & FSF_DIRECTORY) ?
+        response->content.storage_stat_response.file.type = file_info_is_dir(&fileinfo) ?
                                                                 PB_Storage_File_FileType_DIR :
                                                                 PB_Storage_File_FileType_DIR :
                                                                 PB_Storage_File_FileType_FILE;
                                                                 PB_Storage_File_FileType_FILE;
         response->content.storage_stat_response.file.size = fileinfo.size;
         response->content.storage_stat_response.file.size = fileinfo.size;

+ 4 - 4
applications/debug/unit_tests/storage/dirwalk_test.c

@@ -179,7 +179,7 @@ MU_TEST_1(test_dirwalk_full, Storage* storage) {
 
 
     while(dir_walk_read(dir_walk, path, &fileinfo) == DirWalkOK) {
     while(dir_walk_read(dir_walk, path, &fileinfo) == DirWalkOK) {
         furi_string_right(path, strlen(EXT_PATH("dirwalk/")));
         furi_string_right(path, strlen(EXT_PATH("dirwalk/")));
-        mu_check(storage_test_paths_mark(paths, path, (fileinfo.flags & FSF_DIRECTORY)));
+        mu_check(storage_test_paths_mark(paths, path, file_info_is_dir(&fileinfo)));
     }
     }
 
 
     dir_walk_free(dir_walk);
     dir_walk_free(dir_walk);
@@ -204,7 +204,7 @@ MU_TEST_1(test_dirwalk_no_recursive, Storage* storage) {
 
 
     while(dir_walk_read(dir_walk, path, &fileinfo) == DirWalkOK) {
     while(dir_walk_read(dir_walk, path, &fileinfo) == DirWalkOK) {
         furi_string_right(path, strlen(EXT_PATH("dirwalk/")));
         furi_string_right(path, strlen(EXT_PATH("dirwalk/")));
-        mu_check(storage_test_paths_mark(paths, path, (fileinfo.flags & FSF_DIRECTORY)));
+        mu_check(storage_test_paths_mark(paths, path, file_info_is_dir(&fileinfo)));
     }
     }
 
 
     dir_walk_free(dir_walk);
     dir_walk_free(dir_walk);
@@ -219,7 +219,7 @@ static bool test_dirwalk_filter_no_folder_ext(const char* name, FileInfo* filein
     UNUSED(ctx);
     UNUSED(ctx);
 
 
     // only files
     // only files
-    if(!(fileinfo->flags & FSF_DIRECTORY)) {
+    if(!file_info_is_dir(fileinfo)) {
         // with ".test" in name
         // with ".test" in name
         if(strstr(name, ".test") != NULL) {
         if(strstr(name, ".test") != NULL) {
             return true;
             return true;
@@ -243,7 +243,7 @@ MU_TEST_1(test_dirwalk_filter, Storage* storage) {
 
 
     while(dir_walk_read(dir_walk, path, &fileinfo) == DirWalkOK) {
     while(dir_walk_read(dir_walk, path, &fileinfo) == DirWalkOK) {
         furi_string_right(path, strlen(EXT_PATH("dirwalk/")));
         furi_string_right(path, strlen(EXT_PATH("dirwalk/")));
-        mu_check(storage_test_paths_mark(paths, path, (fileinfo.flags & FSF_DIRECTORY)));
+        mu_check(storage_test_paths_mark(paths, path, file_info_is_dir(&fileinfo)));
     }
     }
 
 
     dir_walk_free(dir_walk);
     dir_walk_free(dir_walk);

+ 291 - 1
applications/debug/unit_tests/storage/storage_test.c

@@ -2,9 +2,40 @@
 #include <furi.h>
 #include <furi.h>
 #include <storage/storage.h>
 #include <storage/storage.h>
 
 
+// DO NOT USE THIS IN PRODUCTION CODE
+// This is a hack to access internal storage functions and definitions
+#include <storage/storage_i.h>
+
+#define UNIT_TESTS_PATH(path) EXT_PATH("unit_tests/" path)
+
 #define STORAGE_LOCKED_FILE EXT_PATH("locked_file.test")
 #define STORAGE_LOCKED_FILE EXT_PATH("locked_file.test")
 #define STORAGE_LOCKED_DIR STORAGE_INT_PATH_PREFIX
 #define STORAGE_LOCKED_DIR STORAGE_INT_PATH_PREFIX
 
 
+#define STORAGE_TEST_DIR UNIT_TESTS_PATH("test_dir")
+
+static bool storage_file_create(Storage* storage, const char* path, const char* data) {
+    File* file = storage_file_alloc(storage);
+    bool result = false;
+    do {
+        if(!storage_file_open(file, path, FSAM_WRITE, FSOM_CREATE_NEW)) {
+            break;
+        }
+
+        if(storage_file_write(file, data, strlen(data)) != strlen(data)) {
+            break;
+        }
+
+        if(!storage_file_close(file)) {
+            break;
+        }
+
+        result = true;
+    } while(0);
+
+    storage_file_free(file);
+    return result;
+}
+
 static void storage_file_open_lock_setup() {
 static void storage_file_open_lock_setup() {
     Storage* storage = furi_record_open(RECORD_STORAGE);
     Storage* storage = furi_record_open(RECORD_STORAGE);
     File* file = storage_file_alloc(storage);
     File* file = storage_file_alloc(storage);
@@ -115,7 +146,7 @@ static int32_t storage_dir_locker(void* ctx) {
     File* file = storage_file_alloc(storage);
     File* file = storage_file_alloc(storage);
     furi_check(storage_dir_open(file, STORAGE_LOCKED_DIR));
     furi_check(storage_dir_open(file, STORAGE_LOCKED_DIR));
     furi_semaphore_release(semaphore);
     furi_semaphore_release(semaphore);
-    furi_delay_ms(1000);
+    furi_delay_ms(100);
 
 
     furi_check(storage_dir_close(file));
     furi_check(storage_dir_close(file));
     furi_record_close(RECORD_STORAGE);
     furi_record_close(RECORD_STORAGE);
@@ -152,9 +183,21 @@ MU_TEST(storage_dir_open_lock) {
     mu_assert(result, "cannot open locked dir");
     mu_assert(result, "cannot open locked dir");
 }
 }
 
 
+MU_TEST(storage_dir_exists_test) {
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+
+    mu_check(!storage_dir_exists(storage, STORAGE_TEST_DIR));
+    mu_assert_int_eq(FSE_OK, storage_common_mkdir(storage, STORAGE_TEST_DIR));
+    mu_check(storage_dir_exists(storage, STORAGE_TEST_DIR));
+    mu_assert_int_eq(FSE_OK, storage_common_remove(storage, STORAGE_TEST_DIR));
+
+    furi_record_close(RECORD_STORAGE);
+}
+
 MU_TEST_SUITE(storage_dir) {
 MU_TEST_SUITE(storage_dir) {
     MU_RUN_TEST(storage_dir_open_close);
     MU_RUN_TEST(storage_dir_open_close);
     MU_RUN_TEST(storage_dir_open_lock);
     MU_RUN_TEST(storage_dir_open_lock);
+    MU_RUN_TEST(storage_dir_exists_test);
 }
 }
 
 
 static const char* const storage_copy_test_paths[] = {
 static const char* const storage_copy_test_paths[] = {
@@ -303,9 +346,256 @@ MU_TEST_SUITE(storage_rename) {
     furi_record_close(RECORD_STORAGE);
     furi_record_close(RECORD_STORAGE);
 }
 }
 
 
+#define APPSDATA_APP_PATH(path) APPS_DATA_PATH "/" path
+
+static const char* storage_test_apps[] = {
+    "-_twilight_-",
+    "-_rainbow_-",
+    "-_pinkie_-",
+    "-_apple_-",
+    "-_flutter_-",
+    "-_rare_-",
+};
+
+static size_t storage_test_apps_count = COUNT_OF(storage_test_apps);
+
+static int32_t storage_test_app(void* arg) {
+    UNUSED(arg);
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    storage_common_remove(storage, "/app/test");
+    int32_t ret = storage_file_create(storage, "/app/test", "test");
+    furi_record_close(RECORD_STORAGE);
+    return ret;
+}
+
+MU_TEST(test_storage_data_path_apps) {
+    for(size_t i = 0; i < storage_test_apps_count; i++) {
+        FuriThread* thread =
+            furi_thread_alloc_ex(storage_test_apps[i], 1024, storage_test_app, NULL);
+        furi_thread_set_appid(thread, storage_test_apps[i]);
+        furi_thread_start(thread);
+        furi_thread_join(thread);
+
+        mu_assert_int_eq(true, furi_thread_get_return_code(thread));
+
+        // Check if app data dir and file exists
+        Storage* storage = furi_record_open(RECORD_STORAGE);
+        FuriString* expected = furi_string_alloc();
+        furi_string_printf(expected, APPSDATA_APP_PATH("%s"), storage_test_apps[i]);
+
+        mu_check(storage_dir_exists(storage, furi_string_get_cstr(expected)));
+        furi_string_cat(expected, "/test");
+        mu_check(storage_file_exists(storage, furi_string_get_cstr(expected)));
+
+        furi_string_printf(expected, APPSDATA_APP_PATH("%s"), storage_test_apps[i]);
+        storage_simply_remove_recursive(storage, furi_string_get_cstr(expected));
+
+        furi_record_close(RECORD_STORAGE);
+
+        furi_string_free(expected);
+        furi_thread_free(thread);
+    }
+}
+
+MU_TEST(test_storage_data_path) {
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+
+    File* file = storage_file_alloc(storage);
+    mu_check(storage_dir_open(file, "/app"));
+    mu_check(storage_dir_close(file));
+    storage_file_free(file);
+
+    // check that appsdata folder exists
+    mu_check(storage_dir_exists(storage, APPS_DATA_PATH));
+
+    // check that cli folder exists
+    mu_check(storage_dir_exists(storage, APPSDATA_APP_PATH("cli")));
+
+    storage_simply_remove(storage, APPSDATA_APP_PATH("cli"));
+
+    furi_record_close(RECORD_STORAGE);
+}
+
+MU_TEST(test_storage_common_migrate) {
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+
+    // Setup test folders
+    storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_old"));
+    storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_new"));
+
+    // Test migration from non existing
+    mu_assert_int_eq(
+        FSE_OK,
+        storage_common_migrate(
+            storage, UNIT_TESTS_PATH("migrate_old"), UNIT_TESTS_PATH("migrate_new")));
+
+    // Test migration from existing folder to non existing
+    mu_assert_int_eq(FSE_OK, storage_common_mkdir(storage, UNIT_TESTS_PATH("migrate_old")));
+    mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_old/file1"), "test1"));
+    mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_old/file2.ext"), "test2"));
+    mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_old/file3.ext.ext"), "test3"));
+    mu_assert_int_eq(
+        FSE_OK,
+        storage_common_migrate(
+            storage, UNIT_TESTS_PATH("migrate_old"), UNIT_TESTS_PATH("migrate_new")));
+
+    mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new/file1")));
+    mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new/file2.ext")));
+    mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new/file3.ext.ext")));
+    mu_check(storage_dir_exists(storage, UNIT_TESTS_PATH("migrate_new")));
+    mu_check(!storage_dir_exists(storage, UNIT_TESTS_PATH("migrate_old")));
+
+    // Test migration from existing folder to existing folder
+    mu_assert_int_eq(FSE_OK, storage_common_mkdir(storage, UNIT_TESTS_PATH("migrate_old")));
+    mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_old/file1"), "test1"));
+    mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_old/file2.ext"), "test2"));
+    mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_old/file3.ext.ext"), "test3"));
+
+    mu_assert_int_eq(
+        FSE_OK,
+        storage_common_migrate(
+            storage, UNIT_TESTS_PATH("migrate_old"), UNIT_TESTS_PATH("migrate_new")));
+
+    mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new/file1")));
+    mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new/file2.ext")));
+    mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new/file3.ext.ext")));
+    mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new/file11")));
+    mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new/file21.ext")));
+    mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new/file3.ext1.ext")));
+    mu_check(storage_dir_exists(storage, UNIT_TESTS_PATH("migrate_new")));
+    mu_check(!storage_dir_exists(storage, UNIT_TESTS_PATH("migrate_old")));
+
+    storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_old"));
+    storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_new"));
+
+    // Test migration from empty folder to existing file
+    // Expected result: FSE_OK, folder removed, file untouched
+    mu_assert_int_eq(FSE_OK, storage_common_mkdir(storage, UNIT_TESTS_PATH("migrate_old")));
+    mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_new"), "test1"));
+
+    mu_assert_int_eq(
+        FSE_OK,
+        storage_common_migrate(
+            storage, UNIT_TESTS_PATH("migrate_old"), UNIT_TESTS_PATH("migrate_new")));
+
+    mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new")));
+    mu_check(!storage_dir_exists(storage, UNIT_TESTS_PATH("migrate_old")));
+
+    storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_old"));
+    storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_new"));
+
+    // Test migration from empty folder to existing folder
+    // Expected result: FSE_OK, old folder removed, new folder untouched
+    mu_assert_int_eq(FSE_OK, storage_common_mkdir(storage, UNIT_TESTS_PATH("migrate_old")));
+    mu_assert_int_eq(FSE_OK, storage_common_mkdir(storage, UNIT_TESTS_PATH("migrate_new")));
+
+    mu_assert_int_eq(
+        FSE_OK,
+        storage_common_migrate(
+            storage, UNIT_TESTS_PATH("migrate_old"), UNIT_TESTS_PATH("migrate_new")));
+
+    mu_check(storage_dir_exists(storage, UNIT_TESTS_PATH("migrate_new")));
+    mu_check(!storage_dir_exists(storage, UNIT_TESTS_PATH("migrate_old")));
+
+    storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_old"));
+    storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_new"));
+
+    // Test migration from existing file to non existing, no extension
+    mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_old"), "test1"));
+
+    mu_assert_int_eq(
+        FSE_OK,
+        storage_common_migrate(
+            storage, UNIT_TESTS_PATH("migrate_old"), UNIT_TESTS_PATH("migrate_new")));
+
+    mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new")));
+    mu_check(!storage_file_exists(storage, UNIT_TESTS_PATH("migrate_old")));
+
+    storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_old"));
+    storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_new"));
+
+    // Test migration from existing file to non existing, with extension
+    mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_old.file"), "test1"));
+
+    mu_assert_int_eq(
+        FSE_OK,
+        storage_common_migrate(
+            storage, UNIT_TESTS_PATH("migrate_old.file"), UNIT_TESTS_PATH("migrate_new.file")));
+
+    mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new.file")));
+    mu_check(!storage_file_exists(storage, UNIT_TESTS_PATH("migrate_old.file")));
+
+    storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_old.file"));
+    storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_new.file"));
+
+    // Test migration from existing file to existing file, no extension
+    mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_old"), "test1"));
+    mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_new"), "test2"));
+
+    mu_assert_int_eq(
+        FSE_OK,
+        storage_common_migrate(
+            storage, UNIT_TESTS_PATH("migrate_old"), UNIT_TESTS_PATH("migrate_new")));
+
+    mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new")));
+    mu_check(!storage_file_exists(storage, UNIT_TESTS_PATH("migrate_old")));
+    mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new1")));
+
+    storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_old"));
+    storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_new"));
+    storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_new1"));
+
+    // Test migration from existing file to existing file, with extension
+    mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_old.file"), "test1"));
+    mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_new.file"), "test2"));
+
+    mu_assert_int_eq(
+        FSE_OK,
+        storage_common_migrate(
+            storage, UNIT_TESTS_PATH("migrate_old.file"), UNIT_TESTS_PATH("migrate_new.file")));
+
+    mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new.file")));
+    mu_check(!storage_file_exists(storage, UNIT_TESTS_PATH("migrate_old.file")));
+    mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new1.file")));
+
+    storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_old.file"));
+    storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_new.file"));
+    storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_new1.file"));
+
+    // Test migration from existing file to existing folder
+    mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_old"), "test1"));
+    mu_assert_int_eq(FSE_OK, storage_common_mkdir(storage, UNIT_TESTS_PATH("migrate_new")));
+
+    mu_assert_int_eq(
+        FSE_OK,
+        storage_common_migrate(
+            storage, UNIT_TESTS_PATH("migrate_old"), UNIT_TESTS_PATH("migrate_new")));
+
+    mu_check(storage_dir_exists(storage, UNIT_TESTS_PATH("migrate_new")));
+    mu_check(!storage_file_exists(storage, UNIT_TESTS_PATH("migrate_old")));
+    mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new1")));
+
+    storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_old"));
+    storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_new"));
+    storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_new1"));
+
+    furi_record_close(RECORD_STORAGE);
+}
+
+MU_TEST_SUITE(test_data_path) {
+    MU_RUN_TEST(test_storage_data_path);
+    MU_RUN_TEST(test_storage_data_path_apps);
+}
+
+MU_TEST_SUITE(test_storage_common) {
+    MU_RUN_TEST(test_storage_common_migrate);
+}
+
 int run_minunit_test_storage() {
 int run_minunit_test_storage() {
     MU_RUN_SUITE(storage_file);
     MU_RUN_SUITE(storage_file);
     MU_RUN_SUITE(storage_dir);
     MU_RUN_SUITE(storage_dir);
     MU_RUN_SUITE(storage_rename);
     MU_RUN_SUITE(storage_rename);
+    MU_RUN_SUITE(test_data_path);
+    MU_RUN_SUITE(test_storage_common);
     return MU_EXIT_CODE;
     return MU_EXIT_CODE;
 }
 }

+ 18 - 0
applications/examples/example_apps_data/README.md

@@ -0,0 +1,18 @@
+# Apps Data folder Example
+
+This example demonstrates how to utilize the Apps Data folder to store data that is not part of the app itself, such as user data, configuration files, and so forth.
+
+## What is the Apps Data Folder?
+
+The **Apps Data** folder is a folder used to store data for external apps that are not part of the main firmware. 
+
+The path to the current application folder is related to the `appid` of the app. The `appid` is used to identify the app in the app store and is stored in the `application.fam` file. 
+The Apps Data folder is located only on the external storage, the SD card.
+
+For example, if the `appid` of the app is `snake_game`, the path to the Apps Data folder will be `/ext/apps_data/snake_game`. But using raw paths is not recommended, because the path to the Apps Data folder can change in the future. Use the `/app` alias instead.
+
+## How to get the path to the Apps Data folder?
+
+You can use `/app` alias to get the path to the current application data folder. For example, if you want to open a file `config.txt` in the Apps Data folder, you can use the next path: `/app/config.txt`. But this way is not recommended, because even the `/app` alias can change in the future.
+
+We recommend to use the `APP_DATA_PATH` macro to get the path to the Apps Data folder. For example, if you want to open a file `config.txt` in the Apps Data folder, you can use the next path: `APP_DATA_PATH("config.txt")`.

+ 9 - 0
applications/examples/example_apps_data/application.fam

@@ -0,0 +1,9 @@
+App(
+    appid="example_apps_data",
+    name="Example: Apps Data",
+    apptype=FlipperAppType.EXTERNAL,
+    entry_point="example_apps_data_main",
+    requires=["gui"],
+    stack_size=1 * 1024,
+    fap_category="Examples",
+)

+ 40 - 0
applications/examples/example_apps_data/example_apps_data.c

@@ -0,0 +1,40 @@
+#include <furi.h>
+#include <storage/storage.h>
+
+// Define log tag
+#define TAG "example_apps_data"
+
+// Application entry point
+int32_t example_apps_data_main(void* p) {
+    // Mark argument as unused
+    UNUSED(p);
+
+    // Open storage
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+
+    // Allocate file
+    File* file = storage_file_alloc(storage);
+
+    // Get the path to the current application data folder
+    // That is: /ext/apps_data/<app_name>
+    // And it will create folders in the path if they don't exist
+    // In this example it will create /ext/apps_data/example_apps_data
+    // And file will be /ext/apps_data/example_apps_data/test.txt
+
+    // Open file, write data and close it
+    if(!storage_file_open(file, APP_DATA_PATH("test.txt"), FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
+        FURI_LOG_E(TAG, "Failed to open file");
+    }
+    if(!storage_file_write(file, "Hello World!", strlen("Hello World!"))) {
+        FURI_LOG_E(TAG, "Failed to write to file");
+    }
+    storage_file_close(file);
+
+    // Deallocate file
+    storage_file_free(file);
+
+    // Close storage
+    furi_record_close(RECORD_STORAGE);
+
+    return 0;
+}

+ 5 - 1
applications/main/archive/helpers/archive_browser.c

@@ -436,7 +436,7 @@ static bool archive_is_dir_exists(FuriString* path) {
     FileInfo file_info;
     FileInfo file_info;
     Storage* storage = furi_record_open(RECORD_STORAGE);
     Storage* storage = furi_record_open(RECORD_STORAGE);
     if(storage_common_stat(storage, furi_string_get_cstr(path), &file_info) == FSE_OK) {
     if(storage_common_stat(storage, furi_string_get_cstr(path), &file_info) == FSE_OK) {
-        if(file_info.flags & FSF_DIRECTORY) {
+        if(file_info_is_dir(&file_info)) {
             state = true;
             state = true;
         }
         }
     }
     }
@@ -510,12 +510,16 @@ void archive_enter_dir(ArchiveBrowserView* browser, FuriString* path) {
         browser->view, ArchiveBrowserViewModel * model, { idx_temp = model->item_idx; }, false);
         browser->view, ArchiveBrowserViewModel * model, { idx_temp = model->item_idx; }, false);
 
 
     furi_string_set(browser->path, path);
     furi_string_set(browser->path, path);
+
     file_browser_worker_folder_enter(browser->worker, path, idx_temp);
     file_browser_worker_folder_enter(browser->worker, path, idx_temp);
 }
 }
 
 
 void archive_leave_dir(ArchiveBrowserView* browser) {
 void archive_leave_dir(ArchiveBrowserView* browser) {
     furi_assert(browser);
     furi_assert(browser);
 
 
+    size_t dirname_start = furi_string_search_rchar(browser->path, '/');
+    furi_string_left(browser->path, dirname_start);
+
     file_browser_worker_folder_exit(browser->worker);
     file_browser_worker_folder_exit(browser->worker);
 }
 }
 
 

+ 1 - 1
applications/main/archive/helpers/archive_favorites.c

@@ -160,7 +160,7 @@ bool archive_favorites_read(void* context) {
                 if(storage_file_exists(storage, furi_string_get_cstr(buffer))) {
                 if(storage_file_exists(storage, furi_string_get_cstr(buffer))) {
                     storage_common_stat(storage, furi_string_get_cstr(buffer), &file_info);
                     storage_common_stat(storage, furi_string_get_cstr(buffer), &file_info);
                     archive_add_file_item(
                     archive_add_file_item(
-                        browser, (file_info.flags & FSF_DIRECTORY), furi_string_get_cstr(buffer));
+                        browser, file_info_is_dir(&file_info), furi_string_get_cstr(buffer));
                     file_count++;
                     file_count++;
                 } else {
                 } else {
                     need_refresh = true;
                     need_refresh = true;

+ 1 - 1
applications/main/archive/helpers/archive_files.c

@@ -91,7 +91,7 @@ void archive_delete_file(void* context, const char* format, ...) {
 
 
     bool res = false;
     bool res = false;
 
 
-    if(fileinfo.flags & FSF_DIRECTORY) {
+    if(file_info_is_dir(&fileinfo)) {
         res = storage_simply_remove_recursive(fs_api, furi_string_get_cstr(filename));
         res = storage_simply_remove_recursive(fs_api, furi_string_get_cstr(filename));
     } else {
     } else {
         res = (storage_common_remove(fs_api, furi_string_get_cstr(filename)) == FSE_OK);
         res = (storage_common_remove(fs_api, furi_string_get_cstr(filename)) == FSE_OK);

+ 4 - 4
applications/main/bad_usb/bad_usb_app.c

@@ -142,10 +142,6 @@ void bad_usb_app_free(BadUsbApp* app) {
         app->bad_usb_script = NULL;
         app->bad_usb_script = NULL;
     }
     }
 
 
-    if(app->usb_if_prev) {
-        furi_check(furi_hal_usb_set_config(app->usb_if_prev, NULL));
-    }
-
     // Views
     // Views
     view_dispatcher_remove_view(app->view_dispatcher, BadUsbAppViewWork);
     view_dispatcher_remove_view(app->view_dispatcher, BadUsbAppViewWork);
     bad_usb_free(app->bad_usb_view);
     bad_usb_free(app->bad_usb_view);
@@ -172,6 +168,10 @@ void bad_usb_app_free(BadUsbApp* app) {
     furi_string_free(app->file_path);
     furi_string_free(app->file_path);
     furi_string_free(app->keyboard_layout);
     furi_string_free(app->keyboard_layout);
 
 
+    if(app->usb_if_prev) {
+        furi_check(furi_hal_usb_set_config(app->usb_if_prev, NULL));
+    }
+
     free(app);
     free(app);
 }
 }
 
 

+ 61 - 20
applications/main/bad_usb/bad_usb_script.c

@@ -32,6 +32,7 @@ struct BadUsbScript {
     FuriString* file_path;
     FuriString* file_path;
     uint32_t defdelay;
     uint32_t defdelay;
     uint16_t layout[128];
     uint16_t layout[128];
+    uint32_t stringdelay;
     FuriThread* thread;
     FuriThread* thread;
     uint8_t file_buf[FILE_BUFFER_LEN + 1];
     uint8_t file_buf[FILE_BUFFER_LEN + 1];
     uint8_t buf_start;
     uint8_t buf_start;
@@ -113,6 +114,8 @@ static const char ducky_cmd_delay[] = {"DELAY "};
 static const char ducky_cmd_string[] = {"STRING "};
 static const char ducky_cmd_string[] = {"STRING "};
 static const char ducky_cmd_defdelay_1[] = {"DEFAULT_DELAY "};
 static const char ducky_cmd_defdelay_1[] = {"DEFAULT_DELAY "};
 static const char ducky_cmd_defdelay_2[] = {"DEFAULTDELAY "};
 static const char ducky_cmd_defdelay_2[] = {"DEFAULTDELAY "};
+static const char ducky_cmd_stringdelay_1[] = {"STRINGDELAY "};
+static const char ducky_cmd_stringdelay_2[] = {"STRING_DELAY "};
 static const char ducky_cmd_repeat[] = {"REPEAT "};
 static const char ducky_cmd_repeat[] = {"REPEAT "};
 static const char ducky_cmd_sysrq[] = {"SYSRQ "};
 static const char ducky_cmd_sysrq[] = {"SYSRQ "};
 
 
@@ -211,14 +214,19 @@ static bool ducky_altstring(const char* param) {
 
 
 static bool ducky_string(BadUsbScript* bad_usb, const char* param) {
 static bool ducky_string(BadUsbScript* bad_usb, const char* param) {
     uint32_t i = 0;
     uint32_t i = 0;
+
     while(param[i] != '\0') {
     while(param[i] != '\0') {
         uint16_t keycode = BADUSB_ASCII_TO_KEY(bad_usb, param[i]);
         uint16_t keycode = BADUSB_ASCII_TO_KEY(bad_usb, param[i]);
         if(keycode != HID_KEYBOARD_NONE) {
         if(keycode != HID_KEYBOARD_NONE) {
             furi_hal_hid_kb_press(keycode);
             furi_hal_hid_kb_press(keycode);
             furi_hal_hid_kb_release(keycode);
             furi_hal_hid_kb_release(keycode);
+            if(bad_usb->stringdelay > 0) {
+                furi_delay_ms(bad_usb->stringdelay);
+            }
         }
         }
         i++;
         i++;
     }
     }
+    bad_usb->stringdelay = 0;
     return true;
     return true;
 }
 }
 
 
@@ -277,6 +285,20 @@ static int32_t
             snprintf(error, error_len, "Invalid number %s", line_tmp);
             snprintf(error, error_len, "Invalid number %s", line_tmp);
         }
         }
         return (state) ? (0) : SCRIPT_STATE_ERROR;
         return (state) ? (0) : SCRIPT_STATE_ERROR;
+    } else if(
+        (strncmp(line_tmp, ducky_cmd_stringdelay_1, strlen(ducky_cmd_stringdelay_1)) == 0) ||
+        (strncmp(line_tmp, ducky_cmd_stringdelay_2, strlen(ducky_cmd_stringdelay_2)) == 0)) {
+        //STRINGDELAY, finally it's here
+        line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
+        state = ducky_get_number(line_tmp, &bad_usb->stringdelay);
+        if((state) && (bad_usb->stringdelay > 0)) {
+            return state;
+        }
+        if(error != NULL) {
+            snprintf(error, error_len, "Invalid number %s", line_tmp);
+        }
+        return SCRIPT_STATE_ERROR;
+
     } else if(strncmp(line_tmp, ducky_cmd_string, strlen(ducky_cmd_string)) == 0) {
     } else if(strncmp(line_tmp, ducky_cmd_string, strlen(ducky_cmd_string)) == 0) {
         // STRING
         // STRING
         line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
         line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
@@ -484,6 +506,19 @@ static void bad_usb_hid_state_callback(bool state, void* context) {
         furi_thread_flags_set(furi_thread_get_id(bad_usb->thread), WorkerEvtDisconnect);
         furi_thread_flags_set(furi_thread_get_id(bad_usb->thread), WorkerEvtDisconnect);
 }
 }
 
 
+static uint32_t bad_usb_flags_get(uint32_t flags_mask, uint32_t timeout) {
+    uint32_t flags = furi_thread_flags_get();
+    furi_check((flags & FuriFlagError) == 0);
+    if(flags == 0) {
+        flags = furi_thread_flags_wait(flags_mask, FuriFlagWaitAny, timeout);
+        furi_check(((flags & FuriFlagError) == 0) || (flags == (unsigned)FuriFlagErrorTimeout));
+    } else {
+        uint32_t state = furi_thread_flags_clear(flags);
+        furi_check((state & FuriFlagError) == 0);
+    }
+    return flags;
+}
+
 static int32_t bad_usb_worker(void* context) {
 static int32_t bad_usb_worker(void* context) {
     BadUsbScript* bad_usb = context;
     BadUsbScript* bad_usb = context;
 
 
@@ -520,11 +555,9 @@ static int32_t bad_usb_worker(void* context) {
             bad_usb->st.state = worker_state;
             bad_usb->st.state = worker_state;
 
 
         } else if(worker_state == BadUsbStateNotConnected) { // State: USB not connected
         } else if(worker_state == BadUsbStateNotConnected) { // State: USB not connected
-            uint32_t flags = furi_thread_flags_wait(
-                WorkerEvtEnd | WorkerEvtConnect | WorkerEvtToggle,
-                FuriFlagWaitAny,
-                FuriWaitForever);
-            furi_check((flags & FuriFlagError) == 0);
+            uint32_t flags = bad_usb_flags_get(
+                WorkerEvtEnd | WorkerEvtConnect | WorkerEvtToggle, FuriWaitForever);
+
             if(flags & WorkerEvtEnd) {
             if(flags & WorkerEvtEnd) {
                 break;
                 break;
             } else if(flags & WorkerEvtConnect) {
             } else if(flags & WorkerEvtConnect) {
@@ -535,11 +568,9 @@ static int32_t bad_usb_worker(void* context) {
             bad_usb->st.state = worker_state;
             bad_usb->st.state = worker_state;
 
 
         } else if(worker_state == BadUsbStateIdle) { // State: ready to start
         } else if(worker_state == BadUsbStateIdle) { // State: ready to start
-            uint32_t flags = furi_thread_flags_wait(
-                WorkerEvtEnd | WorkerEvtToggle | WorkerEvtDisconnect,
-                FuriFlagWaitAny,
-                FuriWaitForever);
-            furi_check((flags & FuriFlagError) == 0);
+            uint32_t flags = bad_usb_flags_get(
+                WorkerEvtEnd | WorkerEvtToggle | WorkerEvtDisconnect, FuriWaitForever);
+
             if(flags & WorkerEvtEnd) {
             if(flags & WorkerEvtEnd) {
                 break;
                 break;
             } else if(flags & WorkerEvtToggle) { // Start executing script
             } else if(flags & WorkerEvtToggle) { // Start executing script
@@ -548,6 +579,7 @@ static int32_t bad_usb_worker(void* context) {
                 bad_usb->buf_len = 0;
                 bad_usb->buf_len = 0;
                 bad_usb->st.line_cur = 0;
                 bad_usb->st.line_cur = 0;
                 bad_usb->defdelay = 0;
                 bad_usb->defdelay = 0;
+                bad_usb->stringdelay = 0;
                 bad_usb->repeat_cnt = 0;
                 bad_usb->repeat_cnt = 0;
                 bad_usb->file_end = false;
                 bad_usb->file_end = false;
                 storage_file_seek(script_file, 0, true);
                 storage_file_seek(script_file, 0, true);
@@ -558,11 +590,9 @@ static int32_t bad_usb_worker(void* context) {
             bad_usb->st.state = worker_state;
             bad_usb->st.state = worker_state;
 
 
         } else if(worker_state == BadUsbStateWillRun) { // State: start on connection
         } else if(worker_state == BadUsbStateWillRun) { // State: start on connection
-            uint32_t flags = furi_thread_flags_wait(
-                WorkerEvtEnd | WorkerEvtConnect | WorkerEvtToggle,
-                FuriFlagWaitAny,
-                FuriWaitForever);
-            furi_check((flags & FuriFlagError) == 0);
+            uint32_t flags = bad_usb_flags_get(
+                WorkerEvtEnd | WorkerEvtConnect | WorkerEvtToggle, FuriWaitForever);
+
             if(flags & WorkerEvtEnd) {
             if(flags & WorkerEvtEnd) {
                 break;
                 break;
             } else if(flags & WorkerEvtConnect) { // Start executing script
             } else if(flags & WorkerEvtConnect) { // Start executing script
@@ -571,12 +601,22 @@ static int32_t bad_usb_worker(void* context) {
                 bad_usb->buf_len = 0;
                 bad_usb->buf_len = 0;
                 bad_usb->st.line_cur = 0;
                 bad_usb->st.line_cur = 0;
                 bad_usb->defdelay = 0;
                 bad_usb->defdelay = 0;
+                bad_usb->stringdelay = 0;
                 bad_usb->repeat_cnt = 0;
                 bad_usb->repeat_cnt = 0;
                 bad_usb->file_end = false;
                 bad_usb->file_end = false;
                 storage_file_seek(script_file, 0, true);
                 storage_file_seek(script_file, 0, true);
                 // extra time for PC to recognize Flipper as keyboard
                 // extra time for PC to recognize Flipper as keyboard
-                furi_thread_flags_wait(0, FuriFlagWaitAny, 1500);
-                worker_state = BadUsbStateRunning;
+                flags = furi_thread_flags_wait(
+                    WorkerEvtEnd | WorkerEvtDisconnect | WorkerEvtToggle,
+                    FuriFlagWaitAny | FuriFlagNoClear,
+                    1500);
+                if(flags == (unsigned)FuriFlagErrorTimeout) {
+                    // If nothing happened - start script execution
+                    worker_state = BadUsbStateRunning;
+                } else if(flags & WorkerEvtToggle) {
+                    worker_state = BadUsbStateIdle;
+                    furi_thread_flags_clear(WorkerEvtToggle);
+                }
             } else if(flags & WorkerEvtToggle) { // Cancel scheduled execution
             } else if(flags & WorkerEvtToggle) { // Cancel scheduled execution
                 worker_state = BadUsbStateNotConnected;
                 worker_state = BadUsbStateNotConnected;
             }
             }
@@ -586,6 +626,7 @@ static int32_t bad_usb_worker(void* context) {
             uint16_t delay_cur = (delay_val > 1000) ? (1000) : (delay_val);
             uint16_t delay_cur = (delay_val > 1000) ? (1000) : (delay_val);
             uint32_t flags = furi_thread_flags_wait(
             uint32_t flags = furi_thread_flags_wait(
                 WorkerEvtEnd | WorkerEvtToggle | WorkerEvtDisconnect, FuriFlagWaitAny, delay_cur);
                 WorkerEvtEnd | WorkerEvtToggle | WorkerEvtDisconnect, FuriFlagWaitAny, delay_cur);
+
             delay_val -= delay_cur;
             delay_val -= delay_cur;
             if(!(flags & FuriFlagError)) {
             if(!(flags & FuriFlagError)) {
                 if(flags & WorkerEvtEnd) {
                 if(flags & WorkerEvtEnd) {
@@ -629,9 +670,9 @@ static int32_t bad_usb_worker(void* context) {
         } else if(
         } else if(
             (worker_state == BadUsbStateFileError) ||
             (worker_state == BadUsbStateFileError) ||
             (worker_state == BadUsbStateScriptError)) { // State: error
             (worker_state == BadUsbStateScriptError)) { // State: error
-            uint32_t flags = furi_thread_flags_wait(
-                WorkerEvtEnd, FuriFlagWaitAny, FuriWaitForever); // Waiting for exit command
-            furi_check((flags & FuriFlagError) == 0);
+            uint32_t flags =
+                bad_usb_flags_get(WorkerEvtEnd, FuriWaitForever); // Waiting for exit command
+
             if(flags & WorkerEvtEnd) {
             if(flags & WorkerEvtEnd) {
                 break;
                 break;
             }
             }

+ 0 - 2
applications/main/bad_usb/scenes/bad_usb_scene_file_select.c

@@ -22,7 +22,6 @@ static bool bad_usb_file_select(BadUsbApp* bad_usb) {
 void bad_usb_scene_file_select_on_enter(void* context) {
 void bad_usb_scene_file_select_on_enter(void* context) {
     BadUsbApp* bad_usb = context;
     BadUsbApp* bad_usb = context;
 
 
-    furi_hal_usb_disable();
     if(bad_usb->bad_usb_script) {
     if(bad_usb->bad_usb_script) {
         bad_usb_script_close(bad_usb->bad_usb_script);
         bad_usb_script_close(bad_usb->bad_usb_script);
         bad_usb->bad_usb_script = NULL;
         bad_usb->bad_usb_script = NULL;
@@ -34,7 +33,6 @@ void bad_usb_scene_file_select_on_enter(void* context) {
 
 
         scene_manager_next_scene(bad_usb->scene_manager, BadUsbSceneWork);
         scene_manager_next_scene(bad_usb->scene_manager, BadUsbSceneWork);
     } else {
     } else {
-        furi_hal_usb_enable();
         view_dispatcher_stop(bad_usb->view_dispatcher);
         view_dispatcher_stop(bad_usb->view_dispatcher);
     }
     }
 }
 }

+ 3 - 1
applications/main/bad_usb/scenes/bad_usb_scene_work.c

@@ -16,7 +16,9 @@ bool bad_usb_scene_work_on_event(void* context, SceneManagerEvent event) {
 
 
     if(event.type == SceneManagerEventTypeCustom) {
     if(event.type == SceneManagerEventTypeCustom) {
         if(event.event == InputKeyLeft) {
         if(event.event == InputKeyLeft) {
-            scene_manager_next_scene(app->scene_manager, BadUsbSceneConfig);
+            if(bad_usb_is_idle_state(app->bad_usb_view)) {
+                scene_manager_next_scene(app->scene_manager, BadUsbSceneConfig);
+            }
             consumed = true;
             consumed = true;
         } else if(event.event == InputKeyOk) {
         } else if(event.event == InputKeyOk) {
             bad_usb_script_toggle(app->bad_usb_script);
             bad_usb_script_toggle(app->bad_usb_script);

+ 18 - 5
applications/main/bad_usb/views/bad_usb_view.c

@@ -48,17 +48,13 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) {
     if((model->state.state == BadUsbStateIdle) || (model->state.state == BadUsbStateDone) ||
     if((model->state.state == BadUsbStateIdle) || (model->state.state == BadUsbStateDone) ||
        (model->state.state == BadUsbStateNotConnected)) {
        (model->state.state == BadUsbStateNotConnected)) {
         elements_button_center(canvas, "Run");
         elements_button_center(canvas, "Run");
+        elements_button_left(canvas, "Config");
     } else if((model->state.state == BadUsbStateRunning) || (model->state.state == BadUsbStateDelay)) {
     } else if((model->state.state == BadUsbStateRunning) || (model->state.state == BadUsbStateDelay)) {
         elements_button_center(canvas, "Stop");
         elements_button_center(canvas, "Stop");
     } else if(model->state.state == BadUsbStateWillRun) {
     } else if(model->state.state == BadUsbStateWillRun) {
         elements_button_center(canvas, "Cancel");
         elements_button_center(canvas, "Cancel");
     }
     }
 
 
-    if((model->state.state == BadUsbStateNotConnected) ||
-       (model->state.state == BadUsbStateIdle) || (model->state.state == BadUsbStateDone)) {
-        elements_button_left(canvas, "Config");
-    }
-
     if(model->state.state == BadUsbStateNotConnected) {
     if(model->state.state == BadUsbStateNotConnected) {
         canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18);
         canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18);
         canvas_set_font(canvas, FontPrimary);
         canvas_set_font(canvas, FontPrimary);
@@ -203,6 +199,7 @@ void bad_usb_set_layout(BadUsb* bad_usb, const char* layout) {
         { strlcpy(model->layout, layout, MAX_NAME_LEN); },
         { strlcpy(model->layout, layout, MAX_NAME_LEN); },
         true);
         true);
 }
 }
+
 void bad_usb_set_state(BadUsb* bad_usb, BadUsbState* st) {
 void bad_usb_set_state(BadUsb* bad_usb, BadUsbState* st) {
     furi_assert(st);
     furi_assert(st);
     with_view_model(
     with_view_model(
@@ -214,3 +211,19 @@ void bad_usb_set_state(BadUsb* bad_usb, BadUsbState* st) {
         },
         },
         true);
         true);
 }
 }
+
+bool bad_usb_is_idle_state(BadUsb* bad_usb) {
+    bool is_idle = false;
+    with_view_model(
+        bad_usb->view,
+        BadUsbModel * model,
+        {
+            if((model->state.state == BadUsbStateIdle) ||
+               (model->state.state == BadUsbStateDone) ||
+               (model->state.state == BadUsbStateNotConnected)) {
+                is_idle = true;
+            }
+        },
+        false);
+    return is_idle;
+}

+ 2 - 0
applications/main/bad_usb/views/bad_usb_view.h

@@ -19,3 +19,5 @@ void bad_usb_set_file_name(BadUsb* bad_usb, const char* name);
 void bad_usb_set_layout(BadUsb* bad_usb, const char* layout);
 void bad_usb_set_layout(BadUsb* bad_usb, const char* layout);
 
 
 void bad_usb_set_state(BadUsb* bad_usb, BadUsbState* st);
 void bad_usb_set_state(BadUsb* bad_usb, BadUsbState* st);
+
+bool bad_usb_is_idle_state(BadUsb* bad_usb);

+ 7 - 0
applications/main/fap_loader/fap_loader_app.c

@@ -5,6 +5,7 @@
 #include <storage/storage.h>
 #include <storage/storage.h>
 #include <gui/modules/loading.h>
 #include <gui/modules/loading.h>
 #include <dialogs/dialogs.h>
 #include <dialogs/dialogs.h>
+#include <toolbox/path.h>
 #include <flipper_application/flipper_application.h>
 #include <flipper_application/flipper_application.h>
 #include "elf_cpp/elf_hashtable.h"
 #include "elf_cpp/elf_hashtable.h"
 #include "fap_loader_app.h"
 #include "fap_loader_app.h"
@@ -105,6 +106,12 @@ static bool fap_loader_run_selected_app(FapLoader* loader) {
         FURI_LOG_I(TAG, "FAP Loader is starting app");
         FURI_LOG_I(TAG, "FAP Loader is starting app");
 
 
         FuriThread* thread = flipper_application_spawn(loader->app, NULL);
         FuriThread* thread = flipper_application_spawn(loader->app, NULL);
+
+        FuriString* app_name = furi_string_alloc();
+        path_extract_filename_no_ext(furi_string_get_cstr(loader->fap_path), app_name);
+        furi_thread_set_appid(thread, furi_string_get_cstr(app_name));
+        furi_string_free(app_name);
+
         furi_thread_start(thread);
         furi_thread_start(thread);
         furi_thread_join(thread);
         furi_thread_join(thread);
 
 

+ 87 - 143
applications/main/ibutton/ibutton.c

@@ -1,10 +1,6 @@
-#include "ibutton.h"
-#include "assets_icons.h"
 #include "ibutton_i.h"
 #include "ibutton_i.h"
-#include "ibutton/scenes/ibutton_scene.h"
+
 #include <toolbox/path.h>
 #include <toolbox/path.h>
-#include <flipper_format/flipper_format.h>
-#include <rpc/rpc_app.h>
 #include <dolphin/dolphin.h>
 #include <dolphin/dolphin.h>
 
 
 #define TAG "iButtonApp"
 #define TAG "iButtonApp"
@@ -34,50 +30,13 @@ static const NotificationSequence* ibutton_notification_sequences[] = {
 };
 };
 
 
 static void ibutton_make_app_folder(iButton* ibutton) {
 static void ibutton_make_app_folder(iButton* ibutton) {
-    if(!storage_simply_mkdir(ibutton->storage, IBUTTON_APP_FOLDER)) {
-        dialog_message_show_storage_error(ibutton->dialogs, "Cannot create\napp folder");
-    }
-}
-
-bool ibutton_load_key_data(iButton* ibutton, FuriString* key_path, bool show_dialog) {
-    FlipperFormat* file = flipper_format_file_alloc(ibutton->storage);
-    bool result = false;
-    FuriString* data;
-    data = furi_string_alloc();
-
-    do {
-        if(!flipper_format_file_open_existing(file, furi_string_get_cstr(key_path))) break;
-
-        // header
-        uint32_t version;
-        if(!flipper_format_read_header(file, data, &version)) break;
-        if(furi_string_cmp_str(data, IBUTTON_APP_FILE_TYPE) != 0) break;
-        if(version != 1) break;
+    Storage* storage = furi_record_open(RECORD_STORAGE);
 
 
-        // key type
-        iButtonKeyType type;
-        if(!flipper_format_read_string(file, "Key type", data)) break;
-        if(!ibutton_key_get_type_by_string(furi_string_get_cstr(data), &type)) break;
-
-        // key data
-        uint8_t key_data[IBUTTON_KEY_DATA_SIZE] = {0};
-        if(!flipper_format_read_hex(file, "Data", key_data, ibutton_key_get_size_by_type(type)))
-            break;
-
-        ibutton_key_set_type(ibutton->key, type);
-        ibutton_key_set_data(ibutton->key, key_data, IBUTTON_KEY_DATA_SIZE);
-
-        result = true;
-    } while(false);
-
-    flipper_format_free(file);
-    furi_string_free(data);
-
-    if((!result) && (show_dialog)) {
-        dialog_message_show_storage_error(ibutton->dialogs, "Cannot load\nkey file");
+    if(!storage_simply_mkdir(storage, IBUTTON_APP_FOLDER)) {
+        dialog_message_show_storage_error(ibutton->dialogs, "Cannot create\napp folder");
     }
     }
 
 
-    return result;
+    furi_record_close(RECORD_STORAGE);
 }
 }
 
 
 static void ibutton_rpc_command_callback(RpcAppSystemEvent event, void* context) {
 static void ibutton_rpc_command_callback(RpcAppSystemEvent event, void* context) {
@@ -87,14 +46,14 @@ static void ibutton_rpc_command_callback(RpcAppSystemEvent event, void* context)
     if(event == RpcAppEventSessionClose) {
     if(event == RpcAppEventSessionClose) {
         view_dispatcher_send_custom_event(
         view_dispatcher_send_custom_event(
             ibutton->view_dispatcher, iButtonCustomEventRpcSessionClose);
             ibutton->view_dispatcher, iButtonCustomEventRpcSessionClose);
-        rpc_system_app_set_callback(ibutton->rpc_ctx, NULL, NULL);
-        ibutton->rpc_ctx = NULL;
+        rpc_system_app_set_callback(ibutton->rpc, NULL, NULL);
+        ibutton->rpc = NULL;
     } else if(event == RpcAppEventAppExit) {
     } else if(event == RpcAppEventAppExit) {
         view_dispatcher_send_custom_event(ibutton->view_dispatcher, iButtonCustomEventRpcExit);
         view_dispatcher_send_custom_event(ibutton->view_dispatcher, iButtonCustomEventRpcExit);
     } else if(event == RpcAppEventLoadFile) {
     } else if(event == RpcAppEventLoadFile) {
         view_dispatcher_send_custom_event(ibutton->view_dispatcher, iButtonCustomEventRpcLoad);
         view_dispatcher_send_custom_event(ibutton->view_dispatcher, iButtonCustomEventRpcLoad);
     } else {
     } else {
-        rpc_system_app_confirm(ibutton->rpc_ctx, event, false);
+        rpc_system_app_confirm(ibutton->rpc, event, false);
     }
     }
 }
 }
 
 
@@ -135,13 +94,13 @@ iButton* ibutton_alloc() {
 
 
     ibutton->gui = furi_record_open(RECORD_GUI);
     ibutton->gui = furi_record_open(RECORD_GUI);
 
 
-    ibutton->storage = furi_record_open(RECORD_STORAGE);
     ibutton->dialogs = furi_record_open(RECORD_DIALOGS);
     ibutton->dialogs = furi_record_open(RECORD_DIALOGS);
     ibutton->notifications = furi_record_open(RECORD_NOTIFICATION);
     ibutton->notifications = furi_record_open(RECORD_NOTIFICATION);
 
 
-    ibutton->key = ibutton_key_alloc();
-    ibutton->key_worker = ibutton_worker_alloc();
-    ibutton_worker_start_thread(ibutton->key_worker);
+    ibutton->protocols = ibutton_protocols_alloc();
+    ibutton->key = ibutton_key_alloc(ibutton_protocols_get_max_data_size(ibutton->protocols));
+    ibutton->worker = ibutton_worker_alloc(ibutton->protocols);
+    ibutton_worker_start_thread(ibutton->worker);
 
 
     ibutton->submenu = submenu_alloc();
     ibutton->submenu = submenu_alloc();
     view_dispatcher_add_view(
     view_dispatcher_add_view(
@@ -163,9 +122,9 @@ iButton* ibutton_alloc() {
     view_dispatcher_add_view(
     view_dispatcher_add_view(
         ibutton->view_dispatcher, iButtonViewWidget, widget_get_view(ibutton->widget));
         ibutton->view_dispatcher, iButtonViewWidget, widget_get_view(ibutton->widget));
 
 
-    ibutton->dialog_ex = dialog_ex_alloc();
+    ibutton->loading = loading_alloc();
     view_dispatcher_add_view(
     view_dispatcher_add_view(
-        ibutton->view_dispatcher, iButtonViewDialogEx, dialog_ex_get_view(ibutton->dialog_ex));
+        ibutton->view_dispatcher, iButtonViewLoading, loading_get_view(ibutton->loading));
 
 
     return ibutton;
     return ibutton;
 }
 }
@@ -173,8 +132,8 @@ iButton* ibutton_alloc() {
 void ibutton_free(iButton* ibutton) {
 void ibutton_free(iButton* ibutton) {
     furi_assert(ibutton);
     furi_assert(ibutton);
 
 
-    view_dispatcher_remove_view(ibutton->view_dispatcher, iButtonViewDialogEx);
-    dialog_ex_free(ibutton->dialog_ex);
+    view_dispatcher_remove_view(ibutton->view_dispatcher, iButtonViewLoading);
+    loading_free(ibutton->loading);
 
 
     view_dispatcher_remove_view(ibutton->view_dispatcher, iButtonViewWidget);
     view_dispatcher_remove_view(ibutton->view_dispatcher, iButtonViewWidget);
     widget_free(ibutton->widget);
     widget_free(ibutton->widget);
@@ -194,9 +153,6 @@ void ibutton_free(iButton* ibutton) {
     view_dispatcher_free(ibutton->view_dispatcher);
     view_dispatcher_free(ibutton->view_dispatcher);
     scene_manager_free(ibutton->scene_manager);
     scene_manager_free(ibutton->scene_manager);
 
 
-    furi_record_close(RECORD_STORAGE);
-    ibutton->storage = NULL;
-
     furi_record_close(RECORD_NOTIFICATION);
     furi_record_close(RECORD_NOTIFICATION);
     ibutton->notifications = NULL;
     ibutton->notifications = NULL;
 
 
@@ -206,103 +162,83 @@ void ibutton_free(iButton* ibutton) {
     furi_record_close(RECORD_GUI);
     furi_record_close(RECORD_GUI);
     ibutton->gui = NULL;
     ibutton->gui = NULL;
 
 
-    ibutton_worker_stop_thread(ibutton->key_worker);
-    ibutton_worker_free(ibutton->key_worker);
+    ibutton_worker_stop_thread(ibutton->worker);
+    ibutton_worker_free(ibutton->worker);
     ibutton_key_free(ibutton->key);
     ibutton_key_free(ibutton->key);
+    ibutton_protocols_free(ibutton->protocols);
 
 
     furi_string_free(ibutton->file_path);
     furi_string_free(ibutton->file_path);
 
 
     free(ibutton);
     free(ibutton);
 }
 }
 
 
-bool ibutton_file_select(iButton* ibutton) {
-    DialogsFileBrowserOptions browser_options;
-    dialog_file_browser_set_basic_options(&browser_options, IBUTTON_APP_EXTENSION, &I_ibutt_10px);
-    browser_options.base_path = IBUTTON_APP_FOLDER;
-
-    bool success = dialog_file_browser_show(
-        ibutton->dialogs, ibutton->file_path, ibutton->file_path, &browser_options);
+bool ibutton_load_key(iButton* ibutton) {
+    view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewLoading);
 
 
-    if(success) {
-        success = ibutton_load_key_data(ibutton, ibutton->file_path, true);
-    }
-
-    return success;
-}
-
-bool ibutton_save_key(iButton* ibutton, const char* key_name) {
-    // Create ibutton directory if necessary
-    ibutton_make_app_folder(ibutton);
-
-    FlipperFormat* file = flipper_format_file_alloc(ibutton->storage);
-    iButtonKey* key = ibutton->key;
+    const bool success = ibutton_protocols_load(
+        ibutton->protocols, ibutton->key, furi_string_get_cstr(ibutton->file_path));
 
 
-    bool result = false;
+    if(!success) {
+        dialog_message_show_storage_error(ibutton->dialogs, "Cannot load\nkey file");
 
 
-    do {
-        // Check if we has old key
-        if(furi_string_end_with(ibutton->file_path, IBUTTON_APP_EXTENSION)) {
-            // First remove old key
-            ibutton_delete_key(ibutton);
+    } else {
+        FuriString* tmp = furi_string_alloc();
 
 
-            // Remove old key name from path
-            size_t filename_start = furi_string_search_rchar(ibutton->file_path, '/');
-            furi_string_left(ibutton->file_path, filename_start);
-        }
+        path_extract_filename(ibutton->file_path, tmp, true);
+        strncpy(ibutton->key_name, furi_string_get_cstr(tmp), IBUTTON_KEY_NAME_SIZE);
 
 
-        furi_string_cat_printf(ibutton->file_path, "/%s%s", key_name, IBUTTON_APP_EXTENSION);
+        furi_string_free(tmp);
+    }
 
 
-        // Open file for write
-        if(!flipper_format_file_open_always(file, furi_string_get_cstr(ibutton->file_path))) break;
+    return success;
+}
 
 
-        // Write header
-        if(!flipper_format_write_header_cstr(file, IBUTTON_APP_FILE_TYPE, 1)) break;
+bool ibutton_select_and_load_key(iButton* ibutton) {
+    DialogsFileBrowserOptions browser_options;
+    dialog_file_browser_set_basic_options(&browser_options, IBUTTON_APP_EXTENSION, &I_ibutt_10px);
+    browser_options.base_path = IBUTTON_APP_FOLDER;
 
 
-        // Write key type
-        if(!flipper_format_write_comment_cstr(file, "Key type can be Cyfral, Dallas or Metakom"))
-            break;
-        const char* key_type = ibutton_key_get_string_by_type(ibutton_key_get_type(key));
-        if(!flipper_format_write_string_cstr(file, "Key type", key_type)) break;
+    if(furi_string_empty(ibutton->file_path)) {
+        furi_string_set(ibutton->file_path, browser_options.base_path);
+    }
 
 
-        // Write data
-        if(!flipper_format_write_comment_cstr(
-               file, "Data size for Cyfral is 2, for Metakom is 4, for Dallas is 8"))
-            break;
+    return dialog_file_browser_show(
+               ibutton->dialogs, ibutton->file_path, ibutton->file_path, &browser_options) &&
+           ibutton_load_key(ibutton);
+}
 
 
-        if(!flipper_format_write_hex(
-               file, "Data", ibutton_key_get_data_p(key), ibutton_key_get_data_size(key)))
-            break;
-        result = true;
+bool ibutton_save_key(iButton* ibutton) {
+    view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewLoading);
 
 
-    } while(false);
+    ibutton_make_app_folder(ibutton);
 
 
-    flipper_format_free(file);
+    iButtonKey* key = ibutton->key;
+    const bool success =
+        ibutton_protocols_save(ibutton->protocols, key, furi_string_get_cstr(ibutton->file_path));
 
 
-    if(!result) { //-V547
+    if(!success) {
         dialog_message_show_storage_error(ibutton->dialogs, "Cannot save\nkey file");
         dialog_message_show_storage_error(ibutton->dialogs, "Cannot save\nkey file");
     }
     }
 
 
-    return result;
+    return success;
 }
 }
 
 
 bool ibutton_delete_key(iButton* ibutton) {
 bool ibutton_delete_key(iButton* ibutton) {
     bool result = false;
     bool result = false;
-    result = storage_simply_remove(ibutton->storage, furi_string_get_cstr(ibutton->file_path));
 
 
-    return result;
-}
-
-void ibutton_text_store_set(iButton* ibutton, const char* text, ...) {
-    va_list args;
-    va_start(args, text);
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    result = storage_simply_remove(storage, furi_string_get_cstr(ibutton->file_path));
+    furi_record_close(RECORD_STORAGE);
 
 
-    vsnprintf(ibutton->text_store, IBUTTON_TEXT_STORE_SIZE, text, args);
+    ibutton_reset_key(ibutton);
 
 
-    va_end(args);
+    return result;
 }
 }
 
 
-void ibutton_text_store_clear(iButton* ibutton) {
-    memset(ibutton->text_store, 0, IBUTTON_TEXT_STORE_SIZE + 1);
+void ibutton_reset_key(iButton* ibutton) {
+    memset(ibutton->key_name, 0, IBUTTON_KEY_NAME_SIZE + 1);
+    furi_string_reset(ibutton->file_path);
+    ibutton_key_reset(ibutton->key);
 }
 }
 
 
 void ibutton_notification_message(iButton* ibutton, uint32_t message) {
 void ibutton_notification_message(iButton* ibutton, uint32_t message) {
@@ -310,36 +246,44 @@ void ibutton_notification_message(iButton* ibutton, uint32_t message) {
     notification_message(ibutton->notifications, ibutton_notification_sequences[message]);
     notification_message(ibutton->notifications, ibutton_notification_sequences[message]);
 }
 }
 
 
-int32_t ibutton_app(void* p) {
+void ibutton_submenu_callback(void* context, uint32_t index) {
+    iButton* ibutton = context;
+    view_dispatcher_send_custom_event(ibutton->view_dispatcher, index);
+}
+
+void ibutton_widget_callback(GuiButtonType result, InputType type, void* context) {
+    iButton* ibutton = context;
+    if(type == InputTypeShort) {
+        view_dispatcher_send_custom_event(ibutton->view_dispatcher, result);
+    }
+}
+
+int32_t ibutton_app(void* arg) {
     iButton* ibutton = ibutton_alloc();
     iButton* ibutton = ibutton_alloc();
 
 
     ibutton_make_app_folder(ibutton);
     ibutton_make_app_folder(ibutton);
 
 
     bool key_loaded = false;
     bool key_loaded = false;
-    bool rpc_mode = false;
 
 
-    if(p && strlen(p)) {
-        uint32_t rpc_ctx = 0;
-        if(sscanf(p, "RPC %lX", &rpc_ctx) == 1) {
+    if((arg != NULL) && (strlen(arg) != 0)) {
+        if(sscanf(arg, "RPC %lX", (uint32_t*)&ibutton->rpc) == 1) {
             FURI_LOG_D(TAG, "Running in RPC mode");
             FURI_LOG_D(TAG, "Running in RPC mode");
-            ibutton->rpc_ctx = (void*)rpc_ctx;
-            rpc_mode = true;
-            rpc_system_app_set_callback(ibutton->rpc_ctx, ibutton_rpc_command_callback, ibutton);
-            rpc_system_app_send_started(ibutton->rpc_ctx);
+
+            rpc_system_app_set_callback(ibutton->rpc, ibutton_rpc_command_callback, ibutton);
+            rpc_system_app_send_started(ibutton->rpc);
+
         } else {
         } else {
-            furi_string_set(ibutton->file_path, (const char*)p);
-            if(ibutton_load_key_data(ibutton, ibutton->file_path, true)) {
-                key_loaded = true;
-                // TODO: Display an error if the key from p could not be loaded
-            }
+            furi_string_set(ibutton->file_path, (const char*)arg);
+            key_loaded = ibutton_load_key(ibutton);
         }
         }
     }
     }
 
 
-    if(rpc_mode) {
+    if(ibutton->rpc != NULL) {
         view_dispatcher_attach_to_gui(
         view_dispatcher_attach_to_gui(
             ibutton->view_dispatcher, ibutton->gui, ViewDispatcherTypeDesktop);
             ibutton->view_dispatcher, ibutton->gui, ViewDispatcherTypeDesktop);
         scene_manager_next_scene(ibutton->scene_manager, iButtonSceneRpc);
         scene_manager_next_scene(ibutton->scene_manager, iButtonSceneRpc);
         DOLPHIN_DEED(DolphinDeedIbuttonEmulate);
         DOLPHIN_DEED(DolphinDeedIbuttonEmulate);
+
     } else {
     } else {
         view_dispatcher_attach_to_gui(
         view_dispatcher_attach_to_gui(
             ibutton->view_dispatcher, ibutton->gui, ViewDispatcherTypeFullscreen);
             ibutton->view_dispatcher, ibutton->gui, ViewDispatcherTypeFullscreen);
@@ -353,9 +297,9 @@ int32_t ibutton_app(void* p) {
 
 
     view_dispatcher_run(ibutton->view_dispatcher);
     view_dispatcher_run(ibutton->view_dispatcher);
 
 
-    if(ibutton->rpc_ctx) {
-        rpc_system_app_set_callback(ibutton->rpc_ctx, NULL, NULL);
-        rpc_system_app_send_exited(ibutton->rpc_ctx);
+    if(ibutton->rpc) {
+        rpc_system_app_set_callback(ibutton->rpc, NULL, NULL);
+        rpc_system_app_send_exited(ibutton->rpc);
     }
     }
     ibutton_free(ibutton);
     ibutton_free(ibutton);
     return 0;
     return 0;

+ 85 - 91
applications/main/ibutton/ibutton_cli.c

@@ -1,11 +1,15 @@
 #include <furi.h>
 #include <furi.h>
 #include <furi_hal.h>
 #include <furi_hal.h>
-#include <stdarg.h>
+
 #include <cli/cli.h>
 #include <cli/cli.h>
-#include <lib/toolbox/args.h>
-#include <one_wire/ibutton/ibutton_worker.h>
+#include <toolbox/args.h>
+
 #include <one_wire/one_wire_host.h>
 #include <one_wire/one_wire_host.h>
 
 
+#include <one_wire/ibutton/ibutton_key.h>
+#include <one_wire/ibutton/ibutton_worker.h>
+#include <one_wire/ibutton/ibutton_protocols.h>
+
 static void ibutton_cli(Cli* cli, FuriString* args, void* context);
 static void ibutton_cli(Cli* cli, FuriString* args, void* context);
 static void onewire_cli(Cli* cli, FuriString* args, void* context);
 static void onewire_cli(Cli* cli, FuriString* args, void* context);
 
 
@@ -22,7 +26,7 @@ void ibutton_on_system_start() {
 #endif
 #endif
 }
 }
 
 
-void ibutton_cli_print_usage() {
+static void ibutton_cli_print_usage() {
     printf("Usage:\r\n");
     printf("Usage:\r\n");
     printf("ikey read\r\n");
     printf("ikey read\r\n");
     printf("ikey emulate <key_type> <key_data>\r\n");
     printf("ikey emulate <key_type> <key_data>\r\n");
@@ -34,30 +38,52 @@ void ibutton_cli_print_usage() {
     printf("\t<key_data> are hex-formatted\r\n");
     printf("\t<key_data> are hex-formatted\r\n");
 };
 };
 
 
-bool ibutton_cli_get_key_type(FuriString* data, iButtonKeyType* type) {
+static bool ibutton_cli_parse_key(iButtonProtocols* protocols, iButtonKey* key, FuriString* args) {
     bool result = false;
     bool result = false;
+    FuriString* name = furi_string_alloc();
+
+    do {
+        // Read protocol name
+        if(!args_read_string_and_trim(args, name)) break;
+
+        // Make the protocol name uppercase
+        const char first = furi_string_get_char(name, 0);
+        furi_string_set_char(name, 0, toupper((int)first));
+
+        const iButtonProtocolId id =
+            ibutton_protocols_get_id_by_name(protocols, furi_string_get_cstr(name));
+        if(id == iButtonProtocolIdInvalid) break;
+
+        ibutton_key_set_protocol_id(key, id);
+
+        // Get the data pointer
+        iButtonEditableData data;
+        ibutton_protocols_get_editable_data(protocols, key, &data);
+
+        // Read data
+        if(!args_read_hex_bytes(args, data.ptr, data.size)) break;
 
 
-    if(furi_string_cmp_str(data, "Dallas") == 0 || furi_string_cmp_str(data, "dallas") == 0) {
-        result = true;
-        *type = iButtonKeyDS1990;
-    } else if(furi_string_cmp_str(data, "Cyfral") == 0 || furi_string_cmp_str(data, "cyfral") == 0) {
-        result = true;
-        *type = iButtonKeyCyfral;
-    } else if(furi_string_cmp_str(data, "Metakom") == 0 || furi_string_cmp_str(data, "metakom") == 0) {
         result = true;
         result = true;
-        *type = iButtonKeyMetakom;
-    }
+    } while(false);
 
 
+    furi_string_free(name);
     return result;
     return result;
 }
 }
 
 
-void ibutton_cli_print_key_data(iButtonKey* key) {
-    const uint8_t* key_data = ibutton_key_get_data_p(key);
-    iButtonKeyType type = ibutton_key_get_type(key);
+static void ibutton_cli_print_key(iButtonProtocols* protocols, iButtonKey* key) {
+    const char* name = ibutton_protocols_get_name(protocols, ibutton_key_get_protocol_id(key));
 
 
-    printf("%s ", ibutton_key_get_string_by_type(type));
-    for(size_t i = 0; i < ibutton_key_get_size_by_type(type); i++) {
-        printf("%02X", key_data[i]);
+    if(strncmp(name, "DS", 2) == 0) {
+        name = "Dallas";
+    }
+
+    printf("%s ", name);
+
+    iButtonEditableData data;
+    ibutton_protocols_get_editable_data(protocols, key, &data);
+
+    for(size_t i = 0; i < data.size; i++) {
+        printf("%02X", data.ptr[i]);
     }
     }
 
 
     printf("\r\n");
     printf("\r\n");
@@ -71,9 +97,10 @@ static void ibutton_cli_worker_read_cb(void* context) {
     furi_event_flag_set(event, EVENT_FLAG_IBUTTON_COMPLETE);
     furi_event_flag_set(event, EVENT_FLAG_IBUTTON_COMPLETE);
 }
 }
 
 
-void ibutton_cli_read(Cli* cli) {
-    iButtonKey* key = ibutton_key_alloc();
-    iButtonWorker* worker = ibutton_worker_alloc();
+static void ibutton_cli_read(Cli* cli) {
+    iButtonProtocols* protocols = ibutton_protocols_alloc();
+    iButtonWorker* worker = ibutton_worker_alloc(protocols);
+    iButtonKey* key = ibutton_key_alloc(ibutton_protocols_get_max_data_size(protocols));
     FuriEventFlag* event = furi_event_flag_alloc();
     FuriEventFlag* event = furi_event_flag_alloc();
 
 
     ibutton_worker_start_thread(worker);
     ibutton_worker_start_thread(worker);
@@ -81,32 +108,25 @@ void ibutton_cli_read(Cli* cli) {
 
 
     printf("Reading iButton...\r\nPress Ctrl+C to abort\r\n");
     printf("Reading iButton...\r\nPress Ctrl+C to abort\r\n");
     ibutton_worker_read_start(worker, key);
     ibutton_worker_read_start(worker, key);
+
     while(true) {
     while(true) {
         uint32_t flags =
         uint32_t flags =
             furi_event_flag_wait(event, EVENT_FLAG_IBUTTON_COMPLETE, FuriFlagWaitAny, 100);
             furi_event_flag_wait(event, EVENT_FLAG_IBUTTON_COMPLETE, FuriFlagWaitAny, 100);
 
 
         if(flags & EVENT_FLAG_IBUTTON_COMPLETE) {
         if(flags & EVENT_FLAG_IBUTTON_COMPLETE) {
-            ibutton_cli_print_key_data(key);
-
-            if(ibutton_key_get_type(key) == iButtonKeyDS1990) {
-                if(!ibutton_key_dallas_crc_is_valid(key)) {
-                    printf("Warning: invalid CRC\r\n");
-                }
-
-                if(!ibutton_key_dallas_is_1990_key(key)) {
-                    printf("Warning: not a key\r\n");
-                }
-            }
+            ibutton_cli_print_key(protocols, key);
             break;
             break;
         }
         }
 
 
         if(cli_cmd_interrupt_received(cli)) break;
         if(cli_cmd_interrupt_received(cli)) break;
     }
     }
-    ibutton_worker_stop(worker);
 
 
+    ibutton_worker_stop(worker);
     ibutton_worker_stop_thread(worker);
     ibutton_worker_stop_thread(worker);
-    ibutton_worker_free(worker);
+
     ibutton_key_free(key);
     ibutton_key_free(key);
+    ibutton_worker_free(worker);
+    ibutton_protocols_free(protocols);
 
 
     furi_event_flag_free(event);
     furi_event_flag_free(event);
 };
 };
@@ -124,48 +144,33 @@ static void ibutton_cli_worker_write_cb(void* context, iButtonWorkerWriteResult
 }
 }
 
 
 void ibutton_cli_write(Cli* cli, FuriString* args) {
 void ibutton_cli_write(Cli* cli, FuriString* args) {
-    iButtonKey* key = ibutton_key_alloc();
-    iButtonWorker* worker = ibutton_worker_alloc();
-    iButtonKeyType type;
-    iButtonWriteContext write_context;
-    uint8_t key_data[IBUTTON_KEY_DATA_SIZE];
-    FuriString* data;
+    iButtonProtocols* protocols = ibutton_protocols_alloc();
+    iButtonWorker* worker = ibutton_worker_alloc(protocols);
+    iButtonKey* key = ibutton_key_alloc(ibutton_protocols_get_max_data_size(protocols));
 
 
+    iButtonWriteContext write_context;
     write_context.event = furi_event_flag_alloc();
     write_context.event = furi_event_flag_alloc();
 
 
-    data = furi_string_alloc();
     ibutton_worker_start_thread(worker);
     ibutton_worker_start_thread(worker);
     ibutton_worker_write_set_callback(worker, ibutton_cli_worker_write_cb, &write_context);
     ibutton_worker_write_set_callback(worker, ibutton_cli_worker_write_cb, &write_context);
 
 
     do {
     do {
-        if(!args_read_string_and_trim(args, data)) {
-            ibutton_cli_print_usage();
-            break;
-        }
-
-        if(!ibutton_cli_get_key_type(data, &type)) {
-            ibutton_cli_print_usage();
-            break;
-        }
-
-        if(type != iButtonKeyDS1990) {
+        if(!ibutton_cli_parse_key(protocols, key, args)) {
             ibutton_cli_print_usage();
             ibutton_cli_print_usage();
             break;
             break;
         }
         }
 
 
-        if(!args_read_hex_bytes(args, key_data, ibutton_key_get_size_by_type(type))) {
+        if(!(ibutton_protocols_get_features(protocols, ibutton_key_get_protocol_id(key)) &
+             iButtonProtocolFeatureWriteBlank)) {
             ibutton_cli_print_usage();
             ibutton_cli_print_usage();
             break;
             break;
         }
         }
 
 
-        ibutton_key_set_type(key, type);
-        ibutton_key_set_data(key, key_data, ibutton_key_get_size_by_type(type));
-
         printf("Writing key ");
         printf("Writing key ");
-        ibutton_cli_print_key_data(key);
+        ibutton_cli_print_key(protocols, key);
         printf("Press Ctrl+C to abort\r\n");
         printf("Press Ctrl+C to abort\r\n");
 
 
-        ibutton_worker_write_start(worker, key);
+        ibutton_worker_write_blank_start(worker, key);
         while(true) {
         while(true) {
             uint32_t flags = furi_event_flag_wait(
             uint32_t flags = furi_event_flag_wait(
                 write_context.event, EVENT_FLAG_IBUTTON_COMPLETE, FuriFlagWaitAny, 100);
                 write_context.event, EVENT_FLAG_IBUTTON_COMPLETE, FuriFlagWaitAny, 100);
@@ -183,64 +188,53 @@ void ibutton_cli_write(Cli* cli, FuriString* args) {
 
 
             if(cli_cmd_interrupt_received(cli)) break;
             if(cli_cmd_interrupt_received(cli)) break;
         }
         }
-        ibutton_worker_stop(worker);
     } while(false);
     } while(false);
 
 
-    furi_string_free(data);
+    ibutton_worker_stop(worker);
     ibutton_worker_stop_thread(worker);
     ibutton_worker_stop_thread(worker);
-    ibutton_worker_free(worker);
+
     ibutton_key_free(key);
     ibutton_key_free(key);
+    ibutton_worker_free(worker);
+    ibutton_protocols_free(protocols);
 
 
     furi_event_flag_free(write_context.event);
     furi_event_flag_free(write_context.event);
-};
+}
 
 
 void ibutton_cli_emulate(Cli* cli, FuriString* args) {
 void ibutton_cli_emulate(Cli* cli, FuriString* args) {
-    iButtonKey* key = ibutton_key_alloc();
-    iButtonWorker* worker = ibutton_worker_alloc();
-    iButtonKeyType type;
-    uint8_t key_data[IBUTTON_KEY_DATA_SIZE];
-    FuriString* data;
+    iButtonProtocols* protocols = ibutton_protocols_alloc();
+    iButtonWorker* worker = ibutton_worker_alloc(protocols);
+    iButtonKey* key = ibutton_key_alloc(ibutton_protocols_get_max_data_size(protocols));
 
 
-    data = furi_string_alloc();
     ibutton_worker_start_thread(worker);
     ibutton_worker_start_thread(worker);
 
 
     do {
     do {
-        if(!args_read_string_and_trim(args, data)) {
+        if(!ibutton_cli_parse_key(protocols, key, args)) {
             ibutton_cli_print_usage();
             ibutton_cli_print_usage();
             break;
             break;
         }
         }
 
 
-        if(!ibutton_cli_get_key_type(data, &type)) {
-            ibutton_cli_print_usage();
-            break;
-        }
-
-        if(!args_read_hex_bytes(args, key_data, ibutton_key_get_size_by_type(type))) {
-            ibutton_cli_print_usage();
-            break;
-        }
-
-        ibutton_key_set_type(key, type);
-        ibutton_key_set_data(key, key_data, ibutton_key_get_size_by_type(type));
-
         printf("Emulating key ");
         printf("Emulating key ");
-        ibutton_cli_print_key_data(key);
+        ibutton_cli_print_key(protocols, key);
         printf("Press Ctrl+C to abort\r\n");
         printf("Press Ctrl+C to abort\r\n");
 
 
         ibutton_worker_emulate_start(worker, key);
         ibutton_worker_emulate_start(worker, key);
+
         while(!cli_cmd_interrupt_received(cli)) {
         while(!cli_cmd_interrupt_received(cli)) {
             furi_delay_ms(100);
             furi_delay_ms(100);
         };
         };
-        ibutton_worker_stop(worker);
+
     } while(false);
     } while(false);
 
 
-    furi_string_free(data);
+    ibutton_worker_stop(worker);
     ibutton_worker_stop_thread(worker);
     ibutton_worker_stop_thread(worker);
-    ibutton_worker_free(worker);
+
     ibutton_key_free(key);
     ibutton_key_free(key);
+    ibutton_worker_free(worker);
+    ibutton_protocols_free(protocols);
 };
 };
 
 
-static void ibutton_cli(Cli* cli, FuriString* args, void* context) {
+void ibutton_cli(Cli* cli, FuriString* args, void* context) {
+    UNUSED(cli);
     UNUSED(context);
     UNUSED(context);
     FuriString* cmd;
     FuriString* cmd;
     cmd = furi_string_alloc();
     cmd = furi_string_alloc();
@@ -264,7 +258,7 @@ static void ibutton_cli(Cli* cli, FuriString* args, void* context) {
     furi_string_free(cmd);
     furi_string_free(cmd);
 }
 }
 
 
-void onewire_cli_print_usage() {
+static void onewire_cli_print_usage() {
     printf("Usage:\r\n");
     printf("Usage:\r\n");
     printf("onewire search\r\n");
     printf("onewire search\r\n");
 };
 };
@@ -281,7 +275,7 @@ static void onewire_cli_search(Cli* cli) {
     furi_hal_power_enable_otg();
     furi_hal_power_enable_otg();
 
 
     while(!done) {
     while(!done) {
-        if(onewire_host_search(onewire, address, NORMAL_SEARCH) != 1) {
+        if(onewire_host_search(onewire, address, OneWireHostSearchModeNormal) != 1) {
             printf("Search finished\r\n");
             printf("Search finished\r\n");
             onewire_host_reset_search(onewire);
             onewire_host_reset_search(onewire);
             done = true;
             done = true;

+ 1 - 0
applications/main/ibutton/ibutton_custom_event.h

@@ -6,6 +6,7 @@ enum iButtonCustomEvent {
 
 
     iButtonCustomEventBack,
     iButtonCustomEventBack,
     iButtonCustomEventTextEditResult,
     iButtonCustomEventTextEditResult,
+    iButtonCustomEventByteEditChanged,
     iButtonCustomEventByteEditResult,
     iButtonCustomEventByteEditResult,
     iButtonCustomEventWorkerEmulated,
     iButtonCustomEventWorkerEmulated,
     iButtonCustomEventWorkerRead,
     iButtonCustomEventWorkerRead,

+ 31 - 19
applications/main/ibutton/ibutton_i.h

@@ -4,31 +4,40 @@
 
 
 #include <gui/gui.h>
 #include <gui/gui.h>
 #include <gui/view.h>
 #include <gui/view.h>
-#include <assets_icons.h>
-#include <gui/view_dispatcher.h>
 #include <gui/scene_manager.h>
 #include <gui/scene_manager.h>
-#include <notification/notification_messages.h>
+#include <gui/view_dispatcher.h>
 
 
 #include <one_wire/ibutton/ibutton_worker.h>
 #include <one_wire/ibutton/ibutton_worker.h>
+#include <one_wire/ibutton/ibutton_protocols.h>
+
+#include <rpc/rpc_app.h>
 #include <storage/storage.h>
 #include <storage/storage.h>
 #include <dialogs/dialogs.h>
 #include <dialogs/dialogs.h>
+#include <notification/notification.h>
+#include <notification/notification_messages.h>
 
 
 #include <gui/modules/submenu.h>
 #include <gui/modules/submenu.h>
 #include <gui/modules/popup.h>
 #include <gui/modules/popup.h>
-#include <gui/modules/dialog_ex.h>
 #include <gui/modules/text_input.h>
 #include <gui/modules/text_input.h>
 #include <gui/modules/byte_input.h>
 #include <gui/modules/byte_input.h>
 #include <gui/modules/widget.h>
 #include <gui/modules/widget.h>
+#include <gui/modules/loading.h>
+
+#include <assets_icons.h>
 
 
 #include "ibutton_custom_event.h"
 #include "ibutton_custom_event.h"
 #include "scenes/ibutton_scene.h"
 #include "scenes/ibutton_scene.h"
 
 
-#define IBUTTON_FILE_NAME_SIZE 100
-#define IBUTTON_TEXT_STORE_SIZE 128
-
 #define IBUTTON_APP_FOLDER ANY_PATH("ibutton")
 #define IBUTTON_APP_FOLDER ANY_PATH("ibutton")
 #define IBUTTON_APP_EXTENSION ".ibtn"
 #define IBUTTON_APP_EXTENSION ".ibtn"
-#define IBUTTON_APP_FILE_TYPE "Flipper iButton key"
+
+#define IBUTTON_KEY_NAME_SIZE 22
+
+typedef enum {
+    iButtonWriteModeInvalid,
+    iButtonWriteModeBlank,
+    iButtonWriteModeCopy,
+} iButtonWriteMode;
 
 
 struct iButton {
 struct iButton {
     SceneManager* scene_manager;
     SceneManager* scene_manager;
@@ -38,21 +47,22 @@ struct iButton {
     Storage* storage;
     Storage* storage;
     DialogsApp* dialogs;
     DialogsApp* dialogs;
     NotificationApp* notifications;
     NotificationApp* notifications;
+    RpcAppSystem* rpc;
 
 
-    iButtonWorker* key_worker;
     iButtonKey* key;
     iButtonKey* key;
+    iButtonWorker* worker;
+    iButtonProtocols* protocols;
+    iButtonWriteMode write_mode;
 
 
     FuriString* file_path;
     FuriString* file_path;
-    char text_store[IBUTTON_TEXT_STORE_SIZE + 1];
+    char key_name[IBUTTON_KEY_NAME_SIZE + 1];
 
 
     Submenu* submenu;
     Submenu* submenu;
     ByteInput* byte_input;
     ByteInput* byte_input;
     TextInput* text_input;
     TextInput* text_input;
     Popup* popup;
     Popup* popup;
     Widget* widget;
     Widget* widget;
-    DialogEx* dialog_ex;
-
-    void* rpc_ctx;
+    Loading* loading;
 };
 };
 
 
 typedef enum {
 typedef enum {
@@ -61,7 +71,7 @@ typedef enum {
     iButtonViewTextInput,
     iButtonViewTextInput,
     iButtonViewPopup,
     iButtonViewPopup,
     iButtonViewWidget,
     iButtonViewWidget,
-    iButtonViewDialogEx,
+    iButtonViewLoading,
 } iButtonView;
 } iButtonView;
 
 
 typedef enum {
 typedef enum {
@@ -78,10 +88,12 @@ typedef enum {
     iButtonNotificationMessageBlinkStop,
     iButtonNotificationMessageBlinkStop,
 } iButtonNotificationMessage;
 } iButtonNotificationMessage;
 
 
-bool ibutton_file_select(iButton* ibutton);
-bool ibutton_load_key_data(iButton* ibutton, FuriString* key_path, bool show_dialog);
-bool ibutton_save_key(iButton* ibutton, const char* key_name);
+bool ibutton_select_and_load_key(iButton* ibutton);
+bool ibutton_load_key(iButton* ibutton);
+bool ibutton_save_key(iButton* ibutton);
 bool ibutton_delete_key(iButton* ibutton);
 bool ibutton_delete_key(iButton* ibutton);
-void ibutton_text_store_set(iButton* ibutton, const char* text, ...);
-void ibutton_text_store_clear(iButton* ibutton);
+void ibutton_reset_key(iButton* ibutton);
 void ibutton_notification_message(iButton* ibutton, uint32_t message);
 void ibutton_notification_message(iButton* ibutton, uint32_t message);
+
+void ibutton_submenu_callback(void* context, uint32_t index);
+void ibutton_widget_callback(GuiButtonType result, InputType type, void* context);

+ 27 - 33
applications/main/ibutton/scenes/ibutton_scene_add_type.c

@@ -1,54 +1,48 @@
 #include "../ibutton_i.h"
 #include "../ibutton_i.h"
 
 
-enum SubmenuIndex {
-    SubmenuIndexCyfral,
-    SubmenuIndexDallas,
-    SubmenuIndexMetakom,
-};
-
-void ibutton_scene_add_type_submenu_callback(void* context, uint32_t index) {
-    iButton* ibutton = context;
-    view_dispatcher_send_custom_event(ibutton->view_dispatcher, index);
-}
-
 void ibutton_scene_add_type_on_enter(void* context) {
 void ibutton_scene_add_type_on_enter(void* context) {
     iButton* ibutton = context;
     iButton* ibutton = context;
     Submenu* submenu = ibutton->submenu;
     Submenu* submenu = ibutton->submenu;
 
 
-    submenu_add_item(
-        submenu, "Cyfral", SubmenuIndexCyfral, ibutton_scene_add_type_submenu_callback, ibutton);
-    submenu_add_item(
-        submenu, "Dallas", SubmenuIndexDallas, ibutton_scene_add_type_submenu_callback, ibutton);
-    submenu_add_item(
-        submenu, "Metakom", SubmenuIndexMetakom, ibutton_scene_add_type_submenu_callback, ibutton);
+    FuriString* tmp = furi_string_alloc();
+
+    for(uint32_t protocol_id = 0; protocol_id < ibutton_protocols_get_protocol_count();
+        ++protocol_id) {
+        furi_string_printf(
+            tmp,
+            "%s %s",
+            ibutton_protocols_get_manufacturer(ibutton->protocols, protocol_id),
+            ibutton_protocols_get_name(ibutton->protocols, protocol_id));
+
+        submenu_add_item(
+            submenu, furi_string_get_cstr(tmp), protocol_id, ibutton_submenu_callback, context);
+    }
 
 
-    submenu_set_selected_item(
-        submenu, scene_manager_get_scene_state(ibutton->scene_manager, iButtonSceneAddType));
+    const uint32_t prev_protocol_id =
+        scene_manager_get_scene_state(ibutton->scene_manager, iButtonSceneAddType);
+    submenu_set_selected_item(submenu, prev_protocol_id);
 
 
     view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewSubmenu);
     view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewSubmenu);
+    furi_string_free(tmp);
 }
 }
 
 
 bool ibutton_scene_add_type_on_event(void* context, SceneManagerEvent event) {
 bool ibutton_scene_add_type_on_event(void* context, SceneManagerEvent event) {
     iButton* ibutton = context;
     iButton* ibutton = context;
     iButtonKey* key = ibutton->key;
     iButtonKey* key = ibutton->key;
+
     bool consumed = false;
     bool consumed = false;
 
 
     if(event.type == SceneManagerEventTypeCustom) {
     if(event.type == SceneManagerEventTypeCustom) {
-        scene_manager_set_scene_state(ibutton->scene_manager, iButtonSceneAddType, event.event);
-        consumed = true;
-        if(event.event == SubmenuIndexCyfral) {
-            ibutton_key_set_type(key, iButtonKeyCyfral);
-        } else if(event.event == SubmenuIndexDallas) {
-            ibutton_key_set_type(key, iButtonKeyDS1990);
-        } else if(event.event == SubmenuIndexMetakom) {
-            ibutton_key_set_type(key, iButtonKeyMetakom);
-        } else {
-            furi_crash("Unknown key type");
-        }
-
-        furi_string_set(ibutton->file_path, IBUTTON_APP_FOLDER);
-        ibutton_key_clear_data(key);
+        const iButtonProtocolId protocol_id = event.event;
+
+        ibutton_key_reset(key);
+        ibutton_key_set_protocol_id(key, protocol_id);
+        ibutton_protocols_apply_edits(ibutton->protocols, key);
+
+        scene_manager_set_scene_state(ibutton->scene_manager, iButtonSceneAddType, protocol_id);
         scene_manager_next_scene(ibutton->scene_manager, iButtonSceneAddValue);
         scene_manager_next_scene(ibutton->scene_manager, iButtonSceneAddValue);
+
+        consumed = true;
     }
     }
 
 
     return consumed;
     return consumed;

+ 25 - 18
applications/main/ibutton/scenes/ibutton_scene_add_value.c

@@ -1,42 +1,52 @@
 #include "../ibutton_i.h"
 #include "../ibutton_i.h"
 
 
-void ibutton_scene_add_type_byte_input_callback(void* context) {
+static void ibutton_scene_add_type_byte_input_callback(void* context) {
     iButton* ibutton = context;
     iButton* ibutton = context;
     view_dispatcher_send_custom_event(ibutton->view_dispatcher, iButtonCustomEventByteEditResult);
     view_dispatcher_send_custom_event(ibutton->view_dispatcher, iButtonCustomEventByteEditResult);
 }
 }
 
 
+static void ibutton_scene_add_type_byte_changed_callback(void* context) {
+    iButton* ibutton = context;
+    view_dispatcher_send_custom_event(ibutton->view_dispatcher, iButtonCustomEventByteEditChanged);
+}
+
 void ibutton_scene_add_value_on_enter(void* context) {
 void ibutton_scene_add_value_on_enter(void* context) {
     iButton* ibutton = context;
     iButton* ibutton = context;
-    iButtonKey* key = ibutton->key;
-    uint8_t* new_key_data = malloc(IBUTTON_KEY_DATA_SIZE);
+    byte_input_set_header_text(ibutton->byte_input, "Enter the key");
 
 
-    scene_manager_set_scene_state(
-        ibutton->scene_manager, iButtonSceneAddValue, (uint32_t)new_key_data);
-    memcpy(new_key_data, ibutton_key_get_data_p(key), ibutton_key_get_data_size(key));
+    iButtonEditableData editable_data;
+    ibutton_protocols_get_editable_data(ibutton->protocols, ibutton->key, &editable_data);
 
 
     byte_input_set_result_callback(
     byte_input_set_result_callback(
         ibutton->byte_input,
         ibutton->byte_input,
         ibutton_scene_add_type_byte_input_callback,
         ibutton_scene_add_type_byte_input_callback,
-        NULL,
-        ibutton,
-        new_key_data,
-        ibutton_key_get_data_size(key));
+        ibutton_scene_add_type_byte_changed_callback,
+        context,
+        editable_data.ptr,
+        editable_data.size);
 
 
-    byte_input_set_header_text(ibutton->byte_input, "Enter the key");
     view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewByteInput);
     view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewByteInput);
 }
 }
 
 
 bool ibutton_scene_add_value_on_event(void* context, SceneManagerEvent event) {
 bool ibutton_scene_add_value_on_event(void* context, SceneManagerEvent event) {
     iButton* ibutton = context;
     iButton* ibutton = context;
-    uint8_t* new_key_data =
-        (uint8_t*)scene_manager_get_scene_state(ibutton->scene_manager, iButtonSceneAddValue);
+    SceneManager* scene_manager = ibutton->scene_manager;
     bool consumed = false;
     bool consumed = false;
 
 
     if(event.type == SceneManagerEventTypeCustom) {
     if(event.type == SceneManagerEventTypeCustom) {
         consumed = true;
         consumed = true;
         if(event.event == iButtonCustomEventByteEditResult) {
         if(event.event == iButtonCustomEventByteEditResult) {
-            ibutton_key_set_data(ibutton->key, new_key_data, IBUTTON_KEY_DATA_SIZE);
-            scene_manager_next_scene(ibutton->scene_manager, iButtonSceneSaveName);
+            scene_manager_next_scene(scene_manager, iButtonSceneSaveName);
+        } else if(event.event == iButtonCustomEventByteEditChanged) {
+            ibutton_protocols_apply_edits(ibutton->protocols, ibutton->key);
+        }
+    } else if(event.type == SceneManagerEventTypeBack) {
+        // User cancelled editing, reload the key from storage
+        if(scene_manager_has_previous_scene(scene_manager, iButtonSceneSavedKeyMenu)) {
+            if(!ibutton_load_key(ibutton)) {
+                consumed = scene_manager_search_and_switch_to_previous_scene(
+                    scene_manager, iButtonSceneStart);
+            }
         }
         }
     }
     }
 
 
@@ -45,10 +55,7 @@ bool ibutton_scene_add_value_on_event(void* context, SceneManagerEvent event) {
 
 
 void ibutton_scene_add_value_on_exit(void* context) {
 void ibutton_scene_add_value_on_exit(void* context) {
     iButton* ibutton = context;
     iButton* ibutton = context;
-    uint8_t* new_key_data =
-        (uint8_t*)scene_manager_get_scene_state(ibutton->scene_manager, iButtonSceneAddValue);
 
 
     byte_input_set_result_callback(ibutton->byte_input, NULL, NULL, NULL, NULL, 0);
     byte_input_set_result_callback(ibutton->byte_input, NULL, NULL, NULL, NULL, 0);
     byte_input_set_header_text(ibutton->byte_input, NULL);
     byte_input_set_header_text(ibutton->byte_input, NULL);
-    free(new_key_data);
 }
 }

+ 2 - 2
applications/main/ibutton/scenes/ibutton_scene_config.h

@@ -6,8 +6,7 @@ ADD_SCENE(ibutton, info, Info)
 ADD_SCENE(ibutton, read, Read)
 ADD_SCENE(ibutton, read, Read)
 ADD_SCENE(ibutton, read_key_menu, ReadKeyMenu)
 ADD_SCENE(ibutton, read_key_menu, ReadKeyMenu)
 ADD_SCENE(ibutton, read_success, ReadSuccess)
 ADD_SCENE(ibutton, read_success, ReadSuccess)
-ADD_SCENE(ibutton, read_crc_error, ReadCRCError)
-ADD_SCENE(ibutton, read_not_key_error, ReadNotKeyError)
+ADD_SCENE(ibutton, read_error, ReadError)
 ADD_SCENE(ibutton, select_key, SelectKey)
 ADD_SCENE(ibutton, select_key, SelectKey)
 ADD_SCENE(ibutton, add_type, AddType)
 ADD_SCENE(ibutton, add_type, AddType)
 ADD_SCENE(ibutton, add_value, AddValue)
 ADD_SCENE(ibutton, add_value, AddValue)
@@ -18,4 +17,5 @@ ADD_SCENE(ibutton, delete_confirm, DeleteConfirm)
 ADD_SCENE(ibutton, delete_success, DeleteSuccess)
 ADD_SCENE(ibutton, delete_success, DeleteSuccess)
 ADD_SCENE(ibutton, retry_confirm, RetryConfirm)
 ADD_SCENE(ibutton, retry_confirm, RetryConfirm)
 ADD_SCENE(ibutton, exit_confirm, ExitConfirm)
 ADD_SCENE(ibutton, exit_confirm, ExitConfirm)
+ADD_SCENE(ibutton, view_data, ViewData)
 ADD_SCENE(ibutton, rpc, Rpc)
 ADD_SCENE(ibutton, rpc, Rpc)

+ 15 - 59
applications/main/ibutton/scenes/ibutton_scene_delete_confirm.c

@@ -1,74 +1,29 @@
 #include "../ibutton_i.h"
 #include "../ibutton_i.h"
 #include <toolbox/path.h>
 #include <toolbox/path.h>
 
 
-static void ibutton_scene_delete_confirm_widget_callback(
-    GuiButtonType result,
-    InputType type,
-    void* context) {
-    iButton* ibutton = context;
-    if(type == InputTypeShort) {
-        view_dispatcher_send_custom_event(ibutton->view_dispatcher, result);
-    }
-}
-
 void ibutton_scene_delete_confirm_on_enter(void* context) {
 void ibutton_scene_delete_confirm_on_enter(void* context) {
     iButton* ibutton = context;
     iButton* ibutton = context;
-    Widget* widget = ibutton->widget;
     iButtonKey* key = ibutton->key;
     iButtonKey* key = ibutton->key;
-    const uint8_t* key_data = ibutton_key_get_data_p(key);
+    Widget* widget = ibutton->widget;
 
 
-    FuriString* key_name;
-    key_name = furi_string_alloc();
-    path_extract_filename(ibutton->file_path, key_name, true);
+    FuriString* tmp = furi_string_alloc();
 
 
-    ibutton_text_store_set(ibutton, "\e#Delete %s?\e#", furi_string_get_cstr(key_name));
-    widget_add_text_box_element(
-        widget, 0, 0, 128, 27, AlignCenter, AlignCenter, ibutton->text_store, true);
+    widget_add_button_element(widget, GuiButtonTypeLeft, "Back", ibutton_widget_callback, context);
     widget_add_button_element(
     widget_add_button_element(
-        widget, GuiButtonTypeLeft, "Cancel", ibutton_scene_delete_confirm_widget_callback, ibutton);
-    widget_add_button_element(
-        widget,
-        GuiButtonTypeRight,
-        "Delete",
-        ibutton_scene_delete_confirm_widget_callback,
-        ibutton);
+        widget, GuiButtonTypeRight, "Delete", ibutton_widget_callback, context);
 
 
-    switch(ibutton_key_get_type(key)) {
-    case iButtonKeyDS1990:
-        ibutton_text_store_set(
-            ibutton,
-            "%02X %02X %02X %02X %02X %02X %02X %02X",
-            key_data[0],
-            key_data[1],
-            key_data[2],
-            key_data[3],
-            key_data[4],
-            key_data[5],
-            key_data[6],
-            key_data[7]);
-        widget_add_string_element(
-            widget, 64, 34, AlignCenter, AlignBottom, FontSecondary, "Dallas");
-        break;
+    furi_string_printf(tmp, "Delete %s?", ibutton->key_name);
+    widget_add_string_element(
+        widget, 128 / 2, 0, AlignCenter, AlignTop, FontPrimary, furi_string_get_cstr(tmp));
 
 
-    case iButtonKeyCyfral:
-        ibutton_text_store_set(ibutton, "%02X %02X", key_data[0], key_data[1]);
-        widget_add_string_element(
-            widget, 64, 34, AlignCenter, AlignBottom, FontSecondary, "Cyfral");
-        break;
+    furi_string_reset(tmp);
+    ibutton_protocols_render_brief_data(ibutton->protocols, key, tmp);
 
 
-    case iButtonKeyMetakom:
-        ibutton_text_store_set(
-            ibutton, "%02X %02X %02X %02X", key_data[0], key_data[1], key_data[2], key_data[3]);
-        widget_add_string_element(
-            widget, 64, 34, AlignCenter, AlignBottom, FontSecondary, "Metakom");
-        break;
-    }
-    widget_add_string_element(
-        widget, 64, 46, AlignCenter, AlignBottom, FontSecondary, ibutton->text_store);
+    widget_add_string_multiline_element(
+        widget, 128 / 2, 16, AlignCenter, AlignTop, FontSecondary, furi_string_get_cstr(tmp));
 
 
     view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget);
     view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget);
-
-    furi_string_free(key_name);
+    furi_string_free(tmp);
 }
 }
 
 
 bool ibutton_scene_delete_confirm_on_event(void* context, SceneManagerEvent event) {
 bool ibutton_scene_delete_confirm_on_event(void* context, SceneManagerEvent event) {
@@ -81,8 +36,10 @@ bool ibutton_scene_delete_confirm_on_event(void* context, SceneManagerEvent even
         if(event.event == GuiButtonTypeRight) {
         if(event.event == GuiButtonTypeRight) {
             if(ibutton_delete_key(ibutton)) {
             if(ibutton_delete_key(ibutton)) {
                 scene_manager_next_scene(scene_manager, iButtonSceneDeleteSuccess);
                 scene_manager_next_scene(scene_manager, iButtonSceneDeleteSuccess);
+            } else {
+                dialog_message_show_storage_error(ibutton->dialogs, "Cannot delete\nkey file");
+                scene_manager_previous_scene(scene_manager);
             }
             }
-            //TODO: What if the key could not be deleted?
         } else if(event.event == GuiButtonTypeLeft) {
         } else if(event.event == GuiButtonTypeLeft) {
             scene_manager_previous_scene(scene_manager);
             scene_manager_previous_scene(scene_manager);
         }
         }
@@ -93,6 +50,5 @@ bool ibutton_scene_delete_confirm_on_event(void* context, SceneManagerEvent even
 
 
 void ibutton_scene_delete_confirm_on_exit(void* context) {
 void ibutton_scene_delete_confirm_on_exit(void* context) {
     iButton* ibutton = context;
     iButton* ibutton = context;
-    ibutton_text_store_clear(ibutton);
     widget_reset(ibutton->widget);
     widget_reset(ibutton->widget);
 }
 }

+ 18 - 48
applications/main/ibutton/scenes/ibutton_scene_emulate.c

@@ -14,61 +14,32 @@ static void ibutton_scene_emulate_callback(void* context, bool emulated) {
 
 
 void ibutton_scene_emulate_on_enter(void* context) {
 void ibutton_scene_emulate_on_enter(void* context) {
     iButton* ibutton = context;
     iButton* ibutton = context;
-    Widget* widget = ibutton->widget;
     iButtonKey* key = ibutton->key;
     iButtonKey* key = ibutton->key;
 
 
-    const uint8_t* key_data = ibutton_key_get_data_p(key);
+    Widget* widget = ibutton->widget;
+    FuriString* tmp = furi_string_alloc();
 
 
-    FuriString* key_name;
-    key_name = furi_string_alloc();
-    if(furi_string_end_with(ibutton->file_path, IBUTTON_APP_EXTENSION)) {
-        path_extract_filename(ibutton->file_path, key_name, true);
-    }
+    widget_add_icon_element(widget, 3, 10, &I_iButtonKey_49x44);
 
 
-    // check that stored key has name
-    if(!furi_string_empty(key_name)) {
-        ibutton_text_store_set(ibutton, "%s", furi_string_get_cstr(key_name));
-    } else {
-        // if not, show key data
-        switch(ibutton_key_get_type(key)) {
-        case iButtonKeyDS1990:
-            ibutton_text_store_set(
-                ibutton,
-                "%02X %02X %02X %02X\n%02X %02X %02X %02X",
-                key_data[0],
-                key_data[1],
-                key_data[2],
-                key_data[3],
-                key_data[4],
-                key_data[5],
-                key_data[6],
-                key_data[7]);
-            break;
-        case iButtonKeyCyfral:
-            ibutton_text_store_set(ibutton, "%02X %02X", key_data[0], key_data[1]);
-            break;
-        case iButtonKeyMetakom:
-            ibutton_text_store_set(
-                ibutton, "%02X %02X %02X %02X", key_data[0], key_data[1], key_data[2], key_data[3]);
-            break;
-        }
-    }
+    furi_string_printf(
+        tmp,
+        "%s\n[%s]",
+        furi_string_empty(ibutton->file_path) ? "Unsaved Key" : ibutton->key_name,
+        ibutton_protocols_get_name(ibutton->protocols, ibutton_key_get_protocol_id(key)));
 
 
-    widget_add_string_multiline_element(
-        widget, 90, 10, AlignCenter, AlignTop, FontPrimary, "iButton\nemulating");
-    widget_add_icon_element(widget, 3, 10, &I_iButtonKey_49x44);
     widget_add_text_box_element(
     widget_add_text_box_element(
-        widget, 54, 39, 75, 22, AlignCenter, AlignCenter, ibutton->text_store, true);
-
-    view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget);
+        widget, 52, 38, 75, 26, AlignCenter, AlignCenter, furi_string_get_cstr(tmp), true);
 
 
-    ibutton_worker_emulate_set_callback(
-        ibutton->key_worker, ibutton_scene_emulate_callback, ibutton);
-    ibutton_worker_emulate_start(ibutton->key_worker, key);
+    widget_add_string_multiline_element(
+        widget, 88, 10, AlignCenter, AlignTop, FontPrimary, "iButton\nemulating");
 
 
-    furi_string_free(key_name);
+    ibutton_worker_emulate_set_callback(ibutton->worker, ibutton_scene_emulate_callback, ibutton);
+    ibutton_worker_emulate_start(ibutton->worker, key);
 
 
     ibutton_notification_message(ibutton, iButtonNotificationMessageEmulateStart);
     ibutton_notification_message(ibutton, iButtonNotificationMessageEmulateStart);
+    view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget);
+
+    furi_string_free(tmp);
 }
 }
 
 
 bool ibutton_scene_emulate_on_event(void* context, SceneManagerEvent event) {
 bool ibutton_scene_emulate_on_event(void* context, SceneManagerEvent event) {
@@ -78,8 +49,7 @@ bool ibutton_scene_emulate_on_event(void* context, SceneManagerEvent event) {
     if(event.type == SceneManagerEventTypeTick) {
     if(event.type == SceneManagerEventTypeTick) {
         uint32_t cnt = scene_manager_get_scene_state(ibutton->scene_manager, iButtonSceneEmulate);
         uint32_t cnt = scene_manager_get_scene_state(ibutton->scene_manager, iButtonSceneEmulate);
         if(cnt > 0) {
         if(cnt > 0) {
-            cnt--;
-            if(cnt == 0) {
+            if(--cnt == 0) {
                 ibutton_notification_message(ibutton, iButtonNotificationMessageEmulateBlink);
                 ibutton_notification_message(ibutton, iButtonNotificationMessageEmulateBlink);
             }
             }
             scene_manager_set_scene_state(ibutton->scene_manager, iButtonSceneEmulate, cnt);
             scene_manager_set_scene_state(ibutton->scene_manager, iButtonSceneEmulate, cnt);
@@ -101,7 +71,7 @@ bool ibutton_scene_emulate_on_event(void* context, SceneManagerEvent event) {
 
 
 void ibutton_scene_emulate_on_exit(void* context) {
 void ibutton_scene_emulate_on_exit(void* context) {
     iButton* ibutton = context;
     iButton* ibutton = context;
-    ibutton_worker_stop(ibutton->key_worker);
+    ibutton_worker_stop(ibutton->worker);
     widget_reset(ibutton->widget);
     widget_reset(ibutton->widget);
     ibutton_notification_message(ibutton, iButtonNotificationMessageBlinkStop);
     ibutton_notification_message(ibutton, iButtonNotificationMessageBlinkStop);
 }
 }

+ 30 - 42
applications/main/ibutton/scenes/ibutton_scene_info.c

@@ -1,66 +1,54 @@
 #include "../ibutton_i.h"
 #include "../ibutton_i.h"
-#include <toolbox/path.h>
 
 
 void ibutton_scene_info_on_enter(void* context) {
 void ibutton_scene_info_on_enter(void* context) {
     iButton* ibutton = context;
     iButton* ibutton = context;
-    Widget* widget = ibutton->widget;
     iButtonKey* key = ibutton->key;
     iButtonKey* key = ibutton->key;
+    Widget* widget = ibutton->widget;
+
+    const iButtonProtocolId protocol_id = ibutton_key_get_protocol_id(key);
 
 
-    const uint8_t* key_data = ibutton_key_get_data_p(key);
+    FuriString* tmp = furi_string_alloc();
 
 
-    FuriString* key_name;
-    key_name = furi_string_alloc();
-    path_extract_filename(ibutton->file_path, key_name, true);
+    furi_string_printf(
+        tmp,
+        "\e#%s [%s]\e#",
+        ibutton->key_name,
+        ibutton_protocols_get_name(ibutton->protocols, protocol_id));
 
 
-    ibutton_text_store_set(ibutton, "%s", furi_string_get_cstr(key_name));
     widget_add_text_box_element(
     widget_add_text_box_element(
-        widget, 0, 0, 128, 23, AlignCenter, AlignCenter, ibutton->text_store, true);
+        widget, 0, 2, 128, 12, AlignLeft, AlignTop, furi_string_get_cstr(tmp), true);
 
 
-    switch(ibutton_key_get_type(key)) {
-    case iButtonKeyDS1990:
-        ibutton_text_store_set(
-            ibutton,
-            "%02X %02X %02X %02X %02X %02X %02X %02X",
-            key_data[0],
-            key_data[1],
-            key_data[2],
-            key_data[3],
-            key_data[4],
-            key_data[5],
-            key_data[6],
-            key_data[7]);
-        widget_add_string_element(widget, 64, 36, AlignCenter, AlignBottom, FontPrimary, "Dallas");
-        break;
+    furi_string_reset(tmp);
+    ibutton_protocols_render_brief_data(ibutton->protocols, key, tmp);
 
 
-    case iButtonKeyMetakom:
-        ibutton_text_store_set(
-            ibutton, "%02X %02X %02X %02X", key_data[0], key_data[1], key_data[2], key_data[3]);
-        widget_add_string_element(
-            widget, 64, 36, AlignCenter, AlignBottom, FontPrimary, "Metakom");
-        break;
+    widget_add_string_multiline_element(
+        widget, 0, 16, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(tmp));
 
 
-    case iButtonKeyCyfral:
-        ibutton_text_store_set(ibutton, "%02X %02X", key_data[0], key_data[1]);
-        widget_add_string_element(widget, 64, 36, AlignCenter, AlignBottom, FontPrimary, "Cyfral");
-        break;
+    if(ibutton_protocols_get_features(ibutton->protocols, protocol_id) &
+       iButtonProtocolFeatureExtData) {
+        widget_add_button_element(
+            widget, GuiButtonTypeRight, "More", ibutton_widget_callback, context);
     }
     }
 
 
-    widget_add_string_element(
-        widget, 64, 50, AlignCenter, AlignBottom, FontSecondary, ibutton->text_store);
-
     view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget);
     view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget);
-
-    furi_string_free(key_name);
+    furi_string_free(tmp);
 }
 }
 
 
 bool ibutton_scene_info_on_event(void* context, SceneManagerEvent event) {
 bool ibutton_scene_info_on_event(void* context, SceneManagerEvent event) {
-    UNUSED(context);
-    UNUSED(event);
-    return false;
+    iButton* ibutton = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        consumed = true;
+        if(event.event == GuiButtonTypeRight) {
+            scene_manager_next_scene(ibutton->scene_manager, iButtonSceneViewData);
+        }
+    }
+
+    return consumed;
 }
 }
 
 
 void ibutton_scene_info_on_exit(void* context) {
 void ibutton_scene_info_on_exit(void* context) {
     iButton* ibutton = context;
     iButton* ibutton = context;
-    ibutton_text_store_clear(ibutton);
     widget_reset(ibutton->widget);
     widget_reset(ibutton->widget);
 }
 }

+ 7 - 19
applications/main/ibutton/scenes/ibutton_scene_read.c

@@ -10,14 +10,13 @@ void ibutton_scene_read_on_enter(void* context) {
     iButton* ibutton = context;
     iButton* ibutton = context;
     Popup* popup = ibutton->popup;
     Popup* popup = ibutton->popup;
     iButtonKey* key = ibutton->key;
     iButtonKey* key = ibutton->key;
-    iButtonWorker* worker = ibutton->key_worker;
+    iButtonWorker* worker = ibutton->worker;
 
 
     popup_set_header(popup, "iButton", 95, 26, AlignCenter, AlignBottom);
     popup_set_header(popup, "iButton", 95, 26, AlignCenter, AlignBottom);
     popup_set_text(popup, "Waiting\nfor key ...", 95, 30, AlignCenter, AlignTop);
     popup_set_text(popup, "Waiting\nfor key ...", 95, 30, AlignCenter, AlignTop);
     popup_set_icon(popup, 0, 5, &I_DolphinWait_61x59);
     popup_set_icon(popup, 0, 5, &I_DolphinWait_61x59);
 
 
     view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewPopup);
     view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewPopup);
-    furi_string_set(ibutton->file_path, IBUTTON_APP_FOLDER);
 
 
     ibutton_worker_read_set_callback(worker, ibutton_scene_read_callback, ibutton);
     ibutton_worker_read_set_callback(worker, ibutton_scene_read_callback, ibutton);
     ibutton_worker_read_start(worker, key);
     ibutton_worker_read_start(worker, key);
@@ -35,25 +34,14 @@ bool ibutton_scene_read_on_event(void* context, SceneManagerEvent event) {
     } else if(event.type == SceneManagerEventTypeCustom) {
     } else if(event.type == SceneManagerEventTypeCustom) {
         consumed = true;
         consumed = true;
         if(event.event == iButtonCustomEventWorkerRead) {
         if(event.event == iButtonCustomEventWorkerRead) {
-            bool success = false;
-            iButtonKey* key = ibutton->key;
-
-            if(ibutton_key_get_type(key) == iButtonKeyDS1990) {
-                if(!ibutton_key_dallas_crc_is_valid(key)) {
-                    scene_manager_next_scene(scene_manager, iButtonSceneReadCRCError);
-                } else if(!ibutton_key_dallas_is_1990_key(key)) {
-                    scene_manager_next_scene(scene_manager, iButtonSceneReadNotKeyError);
-                } else {
-                    success = true;
-                }
-            } else {
-                success = true;
-            }
-
-            if(success) {
+            if(ibutton_protocols_is_valid(ibutton->protocols, ibutton->key)) {
                 ibutton_notification_message(ibutton, iButtonNotificationMessageSuccess);
                 ibutton_notification_message(ibutton, iButtonNotificationMessageSuccess);
                 scene_manager_next_scene(scene_manager, iButtonSceneReadSuccess);
                 scene_manager_next_scene(scene_manager, iButtonSceneReadSuccess);
+
                 DOLPHIN_DEED(DolphinDeedIbuttonReadSuccess);
                 DOLPHIN_DEED(DolphinDeedIbuttonReadSuccess);
+
+            } else {
+                scene_manager_next_scene(scene_manager, iButtonSceneReadError);
             }
             }
         }
         }
     }
     }
@@ -64,7 +52,7 @@ bool ibutton_scene_read_on_event(void* context, SceneManagerEvent event) {
 void ibutton_scene_read_on_exit(void* context) {
 void ibutton_scene_read_on_exit(void* context) {
     iButton* ibutton = context;
     iButton* ibutton = context;
     Popup* popup = ibutton->popup;
     Popup* popup = ibutton->popup;
-    ibutton_worker_stop(ibutton->key_worker);
+    ibutton_worker_stop(ibutton->worker);
     popup_set_header(popup, NULL, 0, 0, AlignCenter, AlignBottom);
     popup_set_header(popup, NULL, 0, 0, AlignCenter, AlignBottom);
     popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignTop);
     popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignTop);
     popup_set_icon(popup, 0, 0, NULL);
     popup_set_icon(popup, 0, 0, NULL);

+ 0 - 70
applications/main/ibutton/scenes/ibutton_scene_read_crc_error.c

@@ -1,70 +0,0 @@
-#include "../ibutton_i.h"
-#include <one_wire/maxim_crc.h>
-
-static void ibutton_scene_read_crc_error_dialog_ex_callback(DialogExResult result, void* context) {
-    iButton* ibutton = context;
-    view_dispatcher_send_custom_event(ibutton->view_dispatcher, result);
-}
-
-void ibutton_scene_read_crc_error_on_enter(void* context) {
-    iButton* ibutton = context;
-    DialogEx* dialog_ex = ibutton->dialog_ex;
-    iButtonKey* key = ibutton->key;
-    const uint8_t* key_data = ibutton_key_get_data_p(key);
-
-    ibutton_text_store_set(
-        ibutton,
-        "%02X %02X %02X %02X %02X %02X %02X %02X\nExpected CRC: %X",
-        key_data[0],
-        key_data[1],
-        key_data[2],
-        key_data[3],
-        key_data[4],
-        key_data[5],
-        key_data[6],
-        key_data[7],
-        maxim_crc8(key_data, 7, MAXIM_CRC8_INIT));
-
-    dialog_ex_set_header(dialog_ex, "CRC ERROR", 64, 10, AlignCenter, AlignCenter);
-    dialog_ex_set_text(dialog_ex, ibutton->text_store, 64, 19, AlignCenter, AlignTop);
-    dialog_ex_set_left_button_text(dialog_ex, "Retry");
-    dialog_ex_set_right_button_text(dialog_ex, "More");
-    dialog_ex_set_result_callback(dialog_ex, ibutton_scene_read_crc_error_dialog_ex_callback);
-    dialog_ex_set_context(dialog_ex, ibutton);
-
-    view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewDialogEx);
-
-    ibutton_notification_message(ibutton, iButtonNotificationMessageError);
-    ibutton_notification_message(ibutton, iButtonNotificationMessageRedOn);
-}
-
-bool ibutton_scene_read_crc_error_on_event(void* context, SceneManagerEvent event) {
-    iButton* ibutton = context;
-    SceneManager* scene_manager = ibutton->scene_manager;
-    bool consumed = false;
-
-    if(event.type == SceneManagerEventTypeBack) {
-        consumed = true;
-        scene_manager_next_scene(scene_manager, iButtonSceneExitConfirm);
-    } else if(event.type == SceneManagerEventTypeCustom) {
-        consumed = true;
-        if(event.event == DialogExResultRight) {
-            scene_manager_next_scene(scene_manager, iButtonSceneReadKeyMenu);
-        } else if(event.event == DialogExResultLeft) {
-            scene_manager_previous_scene(scene_manager);
-        }
-    }
-
-    return consumed;
-}
-
-void ibutton_scene_read_crc_error_on_exit(void* context) {
-    iButton* ibutton = context;
-    DialogEx* dialog_ex = ibutton->dialog_ex;
-
-    ibutton_text_store_clear(ibutton);
-
-    dialog_ex_reset(dialog_ex);
-
-    ibutton_notification_message(ibutton, iButtonNotificationMessageRedOff);
-}

+ 58 - 0
applications/main/ibutton/scenes/ibutton_scene_read_error.c

@@ -0,0 +1,58 @@
+#include "../ibutton_i.h"
+#include <one_wire/maxim_crc.h>
+
+void ibutton_scene_read_error_on_enter(void* context) {
+    iButton* ibutton = context;
+    iButtonKey* key = ibutton->key;
+
+    Widget* widget = ibutton->widget;
+
+    FuriString* tmp = furi_string_alloc();
+
+    widget_add_button_element(
+        widget, GuiButtonTypeLeft, "Retry", ibutton_widget_callback, context);
+    widget_add_button_element(
+        widget, GuiButtonTypeRight, "More", ibutton_widget_callback, context);
+
+    widget_add_string_element(
+        widget, 128 / 2, 2, AlignCenter, AlignTop, FontPrimary, "Read Error");
+
+    ibutton_protocols_render_error(ibutton->protocols, key, tmp);
+
+    widget_add_string_multiline_element(
+        widget, 128 / 2, 16, AlignCenter, AlignTop, FontSecondary, furi_string_get_cstr(tmp));
+
+    ibutton_notification_message(ibutton, iButtonNotificationMessageError);
+    ibutton_notification_message(ibutton, iButtonNotificationMessageRedOn);
+
+    view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget);
+    furi_string_free(tmp);
+}
+
+bool ibutton_scene_read_error_on_event(void* context, SceneManagerEvent event) {
+    iButton* ibutton = context;
+    SceneManager* scene_manager = ibutton->scene_manager;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeBack) {
+        consumed = true;
+        scene_manager_next_scene(scene_manager, iButtonSceneExitConfirm);
+
+    } else if(event.type == SceneManagerEventTypeCustom) {
+        consumed = true;
+        if(event.event == GuiButtonTypeLeft) {
+            scene_manager_previous_scene(scene_manager);
+        } else if(event.event == GuiButtonTypeRight) {
+            scene_manager_next_scene(scene_manager, iButtonSceneReadKeyMenu);
+        }
+    }
+
+    return consumed;
+}
+
+void ibutton_scene_read_error_on_exit(void* context) {
+    iButton* ibutton = context;
+
+    ibutton_notification_message(ibutton, iButtonNotificationMessageRedOff);
+    widget_reset(ibutton->widget);
+}

+ 46 - 11
applications/main/ibutton/scenes/ibutton_scene_read_key_menu.c

@@ -4,7 +4,9 @@
 typedef enum {
 typedef enum {
     SubmenuIndexSave,
     SubmenuIndexSave,
     SubmenuIndexEmulate,
     SubmenuIndexEmulate,
-    SubmenuIndexWrite,
+    SubmenuIndexViewData,
+    SubmenuIndexWriteBlank,
+    SubmenuIndexWriteCopy,
 } SubmenuIndex;
 } SubmenuIndex;
 
 
 void ibutton_scene_read_key_menu_submenu_callback(void* context, uint32_t index) {
 void ibutton_scene_read_key_menu_submenu_callback(void* context, uint32_t index) {
@@ -16,6 +18,9 @@ void ibutton_scene_read_key_menu_on_enter(void* context) {
     iButton* ibutton = context;
     iButton* ibutton = context;
     Submenu* submenu = ibutton->submenu;
     Submenu* submenu = ibutton->submenu;
 
 
+    const iButtonProtocolId protocol_id = ibutton_key_get_protocol_id(ibutton->key);
+    const uint32_t features = ibutton_protocols_get_features(ibutton->protocols, protocol_id);
+
     submenu_add_item(
     submenu_add_item(
         submenu, "Save", SubmenuIndexSave, ibutton_scene_read_key_menu_submenu_callback, ibutton);
         submenu, "Save", SubmenuIndexSave, ibutton_scene_read_key_menu_submenu_callback, ibutton);
     submenu_add_item(
     submenu_add_item(
@@ -24,36 +29,66 @@ void ibutton_scene_read_key_menu_on_enter(void* context) {
         SubmenuIndexEmulate,
         SubmenuIndexEmulate,
         ibutton_scene_read_key_menu_submenu_callback,
         ibutton_scene_read_key_menu_submenu_callback,
         ibutton);
         ibutton);
-    if(ibutton_key_get_type(ibutton->key) == iButtonKeyDS1990) {
+
+    if(features & iButtonProtocolFeatureExtData) {
+        submenu_add_item(
+            submenu,
+            "View Data",
+            SubmenuIndexViewData,
+            ibutton_scene_read_key_menu_submenu_callback,
+            ibutton);
+    }
+
+    if(features & iButtonProtocolFeatureWriteBlank) {
         submenu_add_item(
         submenu_add_item(
             submenu,
             submenu,
-            "Write",
-            SubmenuIndexWrite,
+            "Write Blank",
+            SubmenuIndexWriteBlank,
             ibutton_scene_read_key_menu_submenu_callback,
             ibutton_scene_read_key_menu_submenu_callback,
             ibutton);
             ibutton);
     }
     }
+
+    if(features & iButtonProtocolFeatureWriteCopy) {
+        submenu_add_item(
+            submenu,
+            "Write Copy",
+            SubmenuIndexWriteCopy,
+            ibutton_scene_read_key_menu_submenu_callback,
+            ibutton);
+    }
+
     submenu_set_selected_item(
     submenu_set_selected_item(
         submenu, scene_manager_get_scene_state(ibutton->scene_manager, iButtonSceneReadKeyMenu));
         submenu, scene_manager_get_scene_state(ibutton->scene_manager, iButtonSceneReadKeyMenu));
-
     view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewSubmenu);
     view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewSubmenu);
 }
 }
 
 
 bool ibutton_scene_read_key_menu_on_event(void* context, SceneManagerEvent event) {
 bool ibutton_scene_read_key_menu_on_event(void* context, SceneManagerEvent event) {
     iButton* ibutton = context;
     iButton* ibutton = context;
+    SceneManager* scene_manager = ibutton->scene_manager;
     bool consumed = false;
     bool consumed = false;
 
 
     if(event.type == SceneManagerEventTypeCustom) {
     if(event.type == SceneManagerEventTypeCustom) {
-        scene_manager_set_scene_state(
-            ibutton->scene_manager, iButtonSceneReadKeyMenu, event.event);
+        scene_manager_set_scene_state(scene_manager, iButtonSceneReadKeyMenu, event.event);
         consumed = true;
         consumed = true;
+
         if(event.event == SubmenuIndexSave) {
         if(event.event == SubmenuIndexSave) {
-            scene_manager_next_scene(ibutton->scene_manager, iButtonSceneSaveName);
+            scene_manager_next_scene(scene_manager, iButtonSceneSaveName);
         } else if(event.event == SubmenuIndexEmulate) {
         } else if(event.event == SubmenuIndexEmulate) {
-            scene_manager_next_scene(ibutton->scene_manager, iButtonSceneEmulate);
+            scene_manager_next_scene(scene_manager, iButtonSceneEmulate);
             DOLPHIN_DEED(DolphinDeedIbuttonEmulate);
             DOLPHIN_DEED(DolphinDeedIbuttonEmulate);
-        } else if(event.event == SubmenuIndexWrite) {
-            scene_manager_next_scene(ibutton->scene_manager, iButtonSceneWrite);
+        } else if(event.event == SubmenuIndexViewData) {
+            scene_manager_next_scene(scene_manager, iButtonSceneViewData);
+        } else if(event.event == SubmenuIndexWriteBlank) {
+            ibutton->write_mode = iButtonWriteModeBlank;
+            scene_manager_next_scene(scene_manager, iButtonSceneWrite);
+        } else if(event.event == SubmenuIndexWriteCopy) {
+            ibutton->write_mode = iButtonWriteModeCopy;
+            scene_manager_next_scene(scene_manager, iButtonSceneWrite);
         }
         }
+    } else if(event.event == SceneManagerEventTypeBack) {
+        scene_manager_set_scene_state(
+            ibutton->scene_manager, iButtonSceneReadKeyMenu, SubmenuIndexSave);
+        // Event is not consumed
     }
     }
 
 
     return consumed;
     return consumed;

+ 0 - 71
applications/main/ibutton/scenes/ibutton_scene_read_not_key_error.c

@@ -1,71 +0,0 @@
-#include "../ibutton_i.h"
-#include <one_wire/maxim_crc.h>
-
-static void
-    ibutton_scene_read_not_key_error_dialog_ex_callback(DialogExResult result, void* context) {
-    iButton* ibutton = context;
-    view_dispatcher_send_custom_event(ibutton->view_dispatcher, result);
-}
-
-void ibutton_scene_read_not_key_error_on_enter(void* context) {
-    iButton* ibutton = context;
-    DialogEx* dialog_ex = ibutton->dialog_ex;
-    iButtonKey* key = ibutton->key;
-    const uint8_t* key_data = ibutton_key_get_data_p(key);
-
-    ibutton_text_store_set(
-        ibutton,
-        "THIS IS NOT A KEY\n%02X %02X %02X %02X %02X %02X %02X %02X",
-        key_data[0],
-        key_data[1],
-        key_data[2],
-        key_data[3],
-        key_data[4],
-        key_data[5],
-        key_data[6],
-        key_data[7],
-        maxim_crc8(key_data, 7, MAXIM_CRC8_INIT));
-
-    dialog_ex_set_header(dialog_ex, "CRC ERROR", 64, 10, AlignCenter, AlignCenter);
-    dialog_ex_set_text(dialog_ex, ibutton->text_store, 64, 19, AlignCenter, AlignTop);
-    dialog_ex_set_left_button_text(dialog_ex, "Retry");
-    dialog_ex_set_right_button_text(dialog_ex, "More");
-    dialog_ex_set_result_callback(dialog_ex, ibutton_scene_read_not_key_error_dialog_ex_callback);
-    dialog_ex_set_context(dialog_ex, ibutton);
-
-    view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewDialogEx);
-
-    ibutton_notification_message(ibutton, iButtonNotificationMessageError);
-    ibutton_notification_message(ibutton, iButtonNotificationMessageRedOn);
-}
-
-bool ibutton_scene_read_not_key_error_on_event(void* context, SceneManagerEvent event) {
-    iButton* ibutton = context;
-    SceneManager* scene_manager = ibutton->scene_manager;
-    bool consumed = false;
-
-    if(event.type == SceneManagerEventTypeBack) {
-        consumed = true;
-        scene_manager_next_scene(scene_manager, iButtonSceneExitConfirm);
-    } else if(event.type == SceneManagerEventTypeCustom) {
-        consumed = true;
-        if(event.event == DialogExResultRight) {
-            scene_manager_next_scene(scene_manager, iButtonSceneReadKeyMenu);
-        } else if(event.event == DialogExResultLeft) {
-            scene_manager_previous_scene(scene_manager);
-        }
-    }
-
-    return consumed;
-}
-
-void ibutton_scene_read_not_key_error_on_exit(void* context) {
-    iButton* ibutton = context;
-    DialogEx* dialog_ex = ibutton->dialog_ex;
-
-    ibutton_text_store_clear(ibutton);
-
-    dialog_ex_reset(dialog_ex);
-
-    ibutton_notification_message(ibutton, iButtonNotificationMessageRedOff);
-}

+ 30 - 48
applications/main/ibutton/scenes/ibutton_scene_read_success.c

@@ -1,55 +1,40 @@
 #include "../ibutton_i.h"
 #include "../ibutton_i.h"
-#include <dolphin/dolphin.h>
 
 
-static void ibutton_scene_read_success_dialog_ex_callback(DialogExResult result, void* context) {
-    iButton* ibutton = context;
-    view_dispatcher_send_custom_event(ibutton->view_dispatcher, result);
-}
+#include <dolphin/dolphin.h>
 
 
 void ibutton_scene_read_success_on_enter(void* context) {
 void ibutton_scene_read_success_on_enter(void* context) {
     iButton* ibutton = context;
     iButton* ibutton = context;
-    DialogEx* dialog_ex = ibutton->dialog_ex;
     iButtonKey* key = ibutton->key;
     iButtonKey* key = ibutton->key;
-    const uint8_t* key_data = ibutton_key_get_data_p(key);
-
-    switch(ibutton_key_get_type(key)) {
-    case iButtonKeyDS1990:
-        ibutton_text_store_set(
-            ibutton,
-            "Dallas\n%02X %02X %02X %02X\n%02X %02X %02X %02X",
-            key_data[0],
-            key_data[1],
-            key_data[2],
-            key_data[3],
-            key_data[4],
-            key_data[5],
-            key_data[6],
-            key_data[7]);
-        break;
-    case iButtonKeyCyfral:
-        ibutton_text_store_set(ibutton, "Cyfral\n%02X %02X", key_data[0], key_data[1]);
-        break;
-    case iButtonKeyMetakom:
-        ibutton_text_store_set(
-            ibutton,
-            "Metakom\n%02X %02X %02X %02X",
-            key_data[0],
-            key_data[1],
-            key_data[2],
-            key_data[3]);
-        break;
-    }
+    Widget* widget = ibutton->widget;
+
+    FuriString* tmp = furi_string_alloc();
 
 
-    dialog_ex_set_text(dialog_ex, ibutton->text_store, 95, 30, AlignCenter, AlignCenter);
-    dialog_ex_set_left_button_text(dialog_ex, "Retry");
-    dialog_ex_set_right_button_text(dialog_ex, "More");
-    dialog_ex_set_icon(dialog_ex, 0, 1, &I_DolphinReadingSuccess_59x63);
-    dialog_ex_set_result_callback(dialog_ex, ibutton_scene_read_success_dialog_ex_callback);
-    dialog_ex_set_context(dialog_ex, ibutton);
+    const iButtonProtocolId protocol_id = ibutton_key_get_protocol_id(key);
 
 
-    view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewDialogEx);
+    widget_add_button_element(
+        widget, GuiButtonTypeLeft, "Retry", ibutton_widget_callback, context);
+    widget_add_button_element(
+        widget, GuiButtonTypeRight, "More", ibutton_widget_callback, context);
 
 
+    furi_string_printf(
+        tmp,
+        "%s[%s]",
+        ibutton_protocols_get_name(ibutton->protocols, protocol_id),
+        ibutton_protocols_get_manufacturer(ibutton->protocols, protocol_id));
+
+    widget_add_string_element(
+        widget, 0, 2, AlignLeft, AlignTop, FontPrimary, furi_string_get_cstr(tmp));
+
+    furi_string_reset(tmp);
+    ibutton_protocols_render_brief_data(ibutton->protocols, key, tmp);
+
+    widget_add_string_multiline_element(
+        widget, 0, 16, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(tmp));
+
+    view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget);
     ibutton_notification_message(ibutton, iButtonNotificationMessageGreenOn);
     ibutton_notification_message(ibutton, iButtonNotificationMessageGreenOn);
+
+    furi_string_free(tmp);
 }
 }
 
 
 bool ibutton_scene_read_success_on_event(void* context, SceneManagerEvent event) {
 bool ibutton_scene_read_success_on_event(void* context, SceneManagerEvent event) {
@@ -62,9 +47,9 @@ bool ibutton_scene_read_success_on_event(void* context, SceneManagerEvent event)
         scene_manager_next_scene(scene_manager, iButtonSceneExitConfirm);
         scene_manager_next_scene(scene_manager, iButtonSceneExitConfirm);
     } else if(event.type == SceneManagerEventTypeCustom) {
     } else if(event.type == SceneManagerEventTypeCustom) {
         consumed = true;
         consumed = true;
-        if(event.event == DialogExResultRight) {
+        if(event.event == GuiButtonTypeRight) {
             scene_manager_next_scene(scene_manager, iButtonSceneReadKeyMenu);
             scene_manager_next_scene(scene_manager, iButtonSceneReadKeyMenu);
-        } else if(event.event == DialogExResultLeft) {
+        } else if(event.event == GuiButtonTypeLeft) {
             scene_manager_next_scene(scene_manager, iButtonSceneRetryConfirm);
             scene_manager_next_scene(scene_manager, iButtonSceneRetryConfirm);
         }
         }
     }
     }
@@ -74,11 +59,8 @@ bool ibutton_scene_read_success_on_event(void* context, SceneManagerEvent event)
 
 
 void ibutton_scene_read_success_on_exit(void* context) {
 void ibutton_scene_read_success_on_exit(void* context) {
     iButton* ibutton = context;
     iButton* ibutton = context;
-    DialogEx* dialog_ex = ibutton->dialog_ex;
-
-    ibutton_text_store_clear(ibutton);
 
 
-    dialog_ex_reset(dialog_ex);
+    widget_reset(ibutton->widget);
 
 
     ibutton_notification_message(ibutton, iButtonNotificationMessageGreenOff);
     ibutton_notification_message(ibutton, iButtonNotificationMessageGreenOff);
 }
 }

+ 15 - 27
applications/main/ibutton/scenes/ibutton_scene_rpc.c

@@ -1,6 +1,4 @@
 #include "../ibutton_i.h"
 #include "../ibutton_i.h"
-#include <toolbox/path.h>
-#include <rpc/rpc_app.h>
 
 
 void ibutton_scene_rpc_on_enter(void* context) {
 void ibutton_scene_rpc_on_enter(void* context) {
     iButton* ibutton = context;
     iButton* ibutton = context;
@@ -17,8 +15,6 @@ void ibutton_scene_rpc_on_enter(void* context) {
 }
 }
 
 
 bool ibutton_scene_rpc_on_event(void* context, SceneManagerEvent event) {
 bool ibutton_scene_rpc_on_event(void* context, SceneManagerEvent event) {
-    UNUSED(context);
-    UNUSED(event);
     iButton* ibutton = context;
     iButton* ibutton = context;
     Popup* popup = ibutton->popup;
     Popup* popup = ibutton->popup;
 
 
@@ -26,40 +22,32 @@ bool ibutton_scene_rpc_on_event(void* context, SceneManagerEvent event) {
 
 
     if(event.type == SceneManagerEventTypeCustom) {
     if(event.type == SceneManagerEventTypeCustom) {
         consumed = true;
         consumed = true;
+
         if(event.event == iButtonCustomEventRpcLoad) {
         if(event.event == iButtonCustomEventRpcLoad) {
-            const char* arg = rpc_system_app_get_data(ibutton->rpc_ctx);
             bool result = false;
             bool result = false;
-            if(arg && (furi_string_empty(ibutton->file_path))) {
-                furi_string_set(ibutton->file_path, arg);
-                if(ibutton_load_key_data(ibutton, ibutton->file_path, false)) {
-                    ibutton_worker_emulate_start(ibutton->key_worker, ibutton->key);
-                    FuriString* key_name;
-                    key_name = furi_string_alloc();
-                    if(furi_string_end_with(ibutton->file_path, IBUTTON_APP_EXTENSION)) {
-                        path_extract_filename(ibutton->file_path, key_name, true);
-                    }
-
-                    if(!furi_string_empty(key_name)) {
-                        ibutton_text_store_set(
-                            ibutton, "emulating\n%s", furi_string_get_cstr(key_name));
-                    } else {
-                        ibutton_text_store_set(ibutton, "emulating");
-                    }
-                    popup_set_text(popup, ibutton->text_store, 82, 32, AlignCenter, AlignTop);
+            const char* file_path = rpc_system_app_get_data(ibutton->rpc);
+
+            if(file_path && (furi_string_empty(ibutton->file_path))) {
+                furi_string_set(ibutton->file_path, file_path);
+
+                if(ibutton_load_key(ibutton)) {
+                    popup_set_text(popup, ibutton->key_name, 82, 32, AlignCenter, AlignTop);
+                    view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewPopup);
 
 
                     ibutton_notification_message(ibutton, iButtonNotificationMessageEmulateStart);
                     ibutton_notification_message(ibutton, iButtonNotificationMessageEmulateStart);
+                    ibutton_worker_emulate_start(ibutton->worker, ibutton->key);
 
 
-                    furi_string_free(key_name);
                     result = true;
                     result = true;
-                } else {
-                    furi_string_reset(ibutton->file_path);
                 }
                 }
             }
             }
-            rpc_system_app_confirm(ibutton->rpc_ctx, RpcAppEventLoadFile, result);
+
+            rpc_system_app_confirm(ibutton->rpc, RpcAppEventLoadFile, result);
+
         } else if(event.event == iButtonCustomEventRpcExit) {
         } else if(event.event == iButtonCustomEventRpcExit) {
-            rpc_system_app_confirm(ibutton->rpc_ctx, RpcAppEventAppExit, true);
+            rpc_system_app_confirm(ibutton->rpc, RpcAppEventAppExit, true);
             scene_manager_stop(ibutton->scene_manager);
             scene_manager_stop(ibutton->scene_manager);
             view_dispatcher_stop(ibutton->view_dispatcher);
             view_dispatcher_stop(ibutton->view_dispatcher);
+
         } else if(event.event == iButtonCustomEventRpcSessionClose) {
         } else if(event.event == iButtonCustomEventRpcSessionClose) {
             scene_manager_stop(ibutton->scene_manager);
             scene_manager_stop(ibutton->scene_manager);
             view_dispatcher_stop(ibutton->view_dispatcher);
             view_dispatcher_stop(ibutton->view_dispatcher);

+ 20 - 24
applications/main/ibutton/scenes/ibutton_scene_save_name.c

@@ -1,6 +1,8 @@
 #include "../ibutton_i.h"
 #include "../ibutton_i.h"
-#include <lib/toolbox/random_name.h>
+
+#include <toolbox/random_name.h>
 #include <toolbox/path.h>
 #include <toolbox/path.h>
+
 #include <dolphin/dolphin.h>
 #include <dolphin/dolphin.h>
 
 
 static void ibutton_scene_save_name_text_input_callback(void* context) {
 static void ibutton_scene_save_name_text_input_callback(void* context) {
@@ -12,17 +14,10 @@ void ibutton_scene_save_name_on_enter(void* context) {
     iButton* ibutton = context;
     iButton* ibutton = context;
     TextInput* text_input = ibutton->text_input;
     TextInput* text_input = ibutton->text_input;
 
 
-    FuriString* key_name;
-    key_name = furi_string_alloc();
-    if(furi_string_end_with(ibutton->file_path, IBUTTON_APP_EXTENSION)) {
-        path_extract_filename(ibutton->file_path, key_name, true);
-    }
+    const bool is_new_file = furi_string_empty(ibutton->file_path);
 
 
-    const bool key_name_is_empty = furi_string_empty(key_name);
-    if(key_name_is_empty) {
-        set_random_name(ibutton->text_store, IBUTTON_TEXT_STORE_SIZE);
-    } else {
-        ibutton_text_store_set(ibutton, "%s", furi_string_get_cstr(key_name));
+    if(is_new_file) {
+        set_random_name(ibutton->key_name, IBUTTON_KEY_NAME_SIZE);
     }
     }
 
 
     text_input_set_header_text(text_input, "Name the key");
     text_input_set_header_text(text_input, "Name the key");
@@ -30,23 +25,15 @@ void ibutton_scene_save_name_on_enter(void* context) {
         text_input,
         text_input,
         ibutton_scene_save_name_text_input_callback,
         ibutton_scene_save_name_text_input_callback,
         ibutton,
         ibutton,
-        ibutton->text_store,
+        ibutton->key_name,
         IBUTTON_KEY_NAME_SIZE,
         IBUTTON_KEY_NAME_SIZE,
-        key_name_is_empty);
-
-    FuriString* folder_path;
-    folder_path = furi_string_alloc();
+        is_new_file);
 
 
-    path_extract_dirname(furi_string_get_cstr(ibutton->file_path), folder_path);
-
-    ValidatorIsFile* validator_is_file = validator_is_file_alloc_init(
-        furi_string_get_cstr(folder_path), IBUTTON_APP_EXTENSION, furi_string_get_cstr(key_name));
+    ValidatorIsFile* validator_is_file =
+        validator_is_file_alloc_init(IBUTTON_APP_FOLDER, IBUTTON_APP_EXTENSION, ibutton->key_name);
     text_input_set_validator(text_input, validator_is_file_callback, validator_is_file);
     text_input_set_validator(text_input, validator_is_file_callback, validator_is_file);
 
 
     view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewTextInput);
     view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewTextInput);
-
-    furi_string_free(key_name);
-    furi_string_free(folder_path);
 }
 }
 
 
 bool ibutton_scene_save_name_on_event(void* context, SceneManagerEvent event) {
 bool ibutton_scene_save_name_on_event(void* context, SceneManagerEvent event) {
@@ -56,8 +43,16 @@ bool ibutton_scene_save_name_on_event(void* context, SceneManagerEvent event) {
     if(event.type == SceneManagerEventTypeCustom) {
     if(event.type == SceneManagerEventTypeCustom) {
         consumed = true;
         consumed = true;
         if(event.event == iButtonCustomEventTextEditResult) {
         if(event.event == iButtonCustomEventTextEditResult) {
-            if(ibutton_save_key(ibutton, ibutton->text_store)) {
+            furi_string_printf(
+                ibutton->file_path,
+                "%s/%s%s",
+                IBUTTON_APP_FOLDER,
+                ibutton->key_name,
+                IBUTTON_APP_EXTENSION);
+
+            if(ibutton_save_key(ibutton)) {
                 scene_manager_next_scene(ibutton->scene_manager, iButtonSceneSaveSuccess);
                 scene_manager_next_scene(ibutton->scene_manager, iButtonSceneSaveSuccess);
+
                 if(scene_manager_has_previous_scene(
                 if(scene_manager_has_previous_scene(
                        ibutton->scene_manager, iButtonSceneSavedKeyMenu)) {
                        ibutton->scene_manager, iButtonSceneSavedKeyMenu)) {
                     // Nothing, do not count editing as saving
                     // Nothing, do not count editing as saving
@@ -67,6 +62,7 @@ bool ibutton_scene_save_name_on_event(void* context, SceneManagerEvent event) {
                 } else {
                 } else {
                     DOLPHIN_DEED(DolphinDeedIbuttonSave);
                     DOLPHIN_DEED(DolphinDeedIbuttonSave);
                 }
                 }
+
             } else {
             } else {
                 const uint32_t possible_scenes[] = {
                 const uint32_t possible_scenes[] = {
                     iButtonSceneReadKeyMenu, iButtonSceneSavedKeyMenu, iButtonSceneAddType};
                     iButtonSceneReadKeyMenu, iButtonSceneSavedKeyMenu, iButtonSceneAddType};

+ 35 - 37
applications/main/ibutton/scenes/ibutton_scene_saved_key_menu.c

@@ -3,72 +3,70 @@
 
 
 enum SubmenuIndex {
 enum SubmenuIndex {
     SubmenuIndexEmulate,
     SubmenuIndexEmulate,
-    SubmenuIndexWrite,
+    SubmenuIndexWriteBlank,
+    SubmenuIndexWriteCopy,
     SubmenuIndexEdit,
     SubmenuIndexEdit,
     SubmenuIndexDelete,
     SubmenuIndexDelete,
     SubmenuIndexInfo,
     SubmenuIndexInfo,
 };
 };
 
 
-void ibutton_scene_saved_key_menu_submenu_callback(void* context, uint32_t index) {
-    iButton* ibutton = context;
-    view_dispatcher_send_custom_event(ibutton->view_dispatcher, index);
-}
-
 void ibutton_scene_saved_key_menu_on_enter(void* context) {
 void ibutton_scene_saved_key_menu_on_enter(void* context) {
     iButton* ibutton = context;
     iButton* ibutton = context;
     Submenu* submenu = ibutton->submenu;
     Submenu* submenu = ibutton->submenu;
 
 
-    submenu_add_item(
-        submenu,
-        "Emulate",
-        SubmenuIndexEmulate,
-        ibutton_scene_saved_key_menu_submenu_callback,
-        ibutton);
-    if(ibutton_key_get_type(ibutton->key) == iButtonKeyDS1990) {
+    const uint32_t features = ibutton_protocols_get_features(
+        ibutton->protocols, ibutton_key_get_protocol_id(ibutton->key));
+
+    submenu_add_item(submenu, "Emulate", SubmenuIndexEmulate, ibutton_submenu_callback, ibutton);
+
+    if(features & iButtonProtocolFeatureWriteBlank) {
         submenu_add_item(
         submenu_add_item(
-            submenu,
-            "Write",
-            SubmenuIndexWrite,
-            ibutton_scene_saved_key_menu_submenu_callback,
-            ibutton);
+            submenu, "Write Blank", SubmenuIndexWriteBlank, ibutton_submenu_callback, ibutton);
     }
     }
-    submenu_add_item(
-        submenu, "Edit", SubmenuIndexEdit, ibutton_scene_saved_key_menu_submenu_callback, ibutton);
-    submenu_add_item(
-        submenu,
-        "Delete",
-        SubmenuIndexDelete,
-        ibutton_scene_saved_key_menu_submenu_callback,
-        ibutton);
-    submenu_add_item(
-        submenu, "Info", SubmenuIndexInfo, ibutton_scene_saved_key_menu_submenu_callback, ibutton);
+
+    if(features & iButtonProtocolFeatureWriteCopy) {
+        submenu_add_item(
+            submenu, "Write Copy", SubmenuIndexWriteCopy, ibutton_submenu_callback, ibutton);
+    }
+
+    submenu_add_item(submenu, "Edit", SubmenuIndexEdit, ibutton_submenu_callback, ibutton);
+    submenu_add_item(submenu, "Delete", SubmenuIndexDelete, ibutton_submenu_callback, ibutton);
+    submenu_add_item(submenu, "Info", SubmenuIndexInfo, ibutton_submenu_callback, ibutton);
 
 
     submenu_set_selected_item(
     submenu_set_selected_item(
         submenu, scene_manager_get_scene_state(ibutton->scene_manager, iButtonSceneSavedKeyMenu));
         submenu, scene_manager_get_scene_state(ibutton->scene_manager, iButtonSceneSavedKeyMenu));
-
     view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewSubmenu);
     view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewSubmenu);
 }
 }
 
 
 bool ibutton_scene_saved_key_menu_on_event(void* context, SceneManagerEvent event) {
 bool ibutton_scene_saved_key_menu_on_event(void* context, SceneManagerEvent event) {
     iButton* ibutton = context;
     iButton* ibutton = context;
+    SceneManager* scene_manager = ibutton->scene_manager;
     bool consumed = false;
     bool consumed = false;
 
 
     if(event.type == SceneManagerEventTypeCustom) {
     if(event.type == SceneManagerEventTypeCustom) {
-        scene_manager_set_scene_state(
-            ibutton->scene_manager, iButtonSceneSavedKeyMenu, event.event);
+        scene_manager_set_scene_state(scene_manager, iButtonSceneSavedKeyMenu, event.event);
         consumed = true;
         consumed = true;
         if(event.event == SubmenuIndexEmulate) {
         if(event.event == SubmenuIndexEmulate) {
-            scene_manager_next_scene(ibutton->scene_manager, iButtonSceneEmulate);
+            scene_manager_next_scene(scene_manager, iButtonSceneEmulate);
             DOLPHIN_DEED(DolphinDeedIbuttonEmulate);
             DOLPHIN_DEED(DolphinDeedIbuttonEmulate);
-        } else if(event.event == SubmenuIndexWrite) {
-            scene_manager_next_scene(ibutton->scene_manager, iButtonSceneWrite);
+        } else if(event.event == SubmenuIndexWriteBlank) {
+            ibutton->write_mode = iButtonWriteModeBlank;
+            scene_manager_next_scene(scene_manager, iButtonSceneWrite);
+        } else if(event.event == SubmenuIndexWriteCopy) {
+            ibutton->write_mode = iButtonWriteModeCopy;
+            scene_manager_next_scene(scene_manager, iButtonSceneWrite);
         } else if(event.event == SubmenuIndexEdit) {
         } else if(event.event == SubmenuIndexEdit) {
-            scene_manager_next_scene(ibutton->scene_manager, iButtonSceneAddValue);
+            scene_manager_next_scene(scene_manager, iButtonSceneAddValue);
         } else if(event.event == SubmenuIndexDelete) {
         } else if(event.event == SubmenuIndexDelete) {
-            scene_manager_next_scene(ibutton->scene_manager, iButtonSceneDeleteConfirm);
+            scene_manager_next_scene(scene_manager, iButtonSceneDeleteConfirm);
         } else if(event.event == SubmenuIndexInfo) {
         } else if(event.event == SubmenuIndexInfo) {
-            scene_manager_next_scene(ibutton->scene_manager, iButtonSceneInfo);
+            scene_manager_next_scene(scene_manager, iButtonSceneInfo);
         }
         }
+
+    } else if(event.type == SceneManagerEventTypeBack) {
+        scene_manager_set_scene_state(
+            scene_manager, iButtonSceneSavedKeyMenu, SubmenuIndexEmulate);
+        // Event is not consumed
     }
     }
 
 
     return consumed;
     return consumed;

+ 3 - 3
applications/main/ibutton/scenes/ibutton_scene_select_key.c

@@ -3,11 +3,11 @@
 void ibutton_scene_select_key_on_enter(void* context) {
 void ibutton_scene_select_key_on_enter(void* context) {
     iButton* ibutton = context;
     iButton* ibutton = context;
 
 
-    if(!ibutton_file_select(ibutton)) {
+    if(ibutton_select_and_load_key(ibutton)) {
+        scene_manager_next_scene(ibutton->scene_manager, iButtonSceneSavedKeyMenu);
+    } else {
         scene_manager_search_and_switch_to_previous_scene(
         scene_manager_search_and_switch_to_previous_scene(
             ibutton->scene_manager, iButtonSceneStart);
             ibutton->scene_manager, iButtonSceneStart);
-    } else {
-        scene_manager_next_scene(ibutton->scene_manager, iButtonSceneSavedKeyMenu);
     }
     }
 }
 }
 
 

+ 5 - 12
applications/main/ibutton/scenes/ibutton_scene_start.c

@@ -8,21 +8,15 @@ enum SubmenuIndex {
     SubmenuIndexAdd,
     SubmenuIndexAdd,
 };
 };
 
 
-void ibutton_scene_start_submenu_callback(void* context, uint32_t index) {
-    iButton* ibutton = context;
-    view_dispatcher_send_custom_event(ibutton->view_dispatcher, index);
-}
-
 void ibutton_scene_start_on_enter(void* context) {
 void ibutton_scene_start_on_enter(void* context) {
     iButton* ibutton = context;
     iButton* ibutton = context;
     Submenu* submenu = ibutton->submenu;
     Submenu* submenu = ibutton->submenu;
 
 
-    submenu_add_item(
-        submenu, "Read", SubmenuIndexRead, ibutton_scene_start_submenu_callback, ibutton);
-    submenu_add_item(
-        submenu, "Saved", SubmenuIndexSaved, ibutton_scene_start_submenu_callback, ibutton);
-    submenu_add_item(
-        submenu, "Add Manually", SubmenuIndexAdd, ibutton_scene_start_submenu_callback, ibutton);
+    ibutton_reset_key(ibutton);
+
+    submenu_add_item(submenu, "Read", SubmenuIndexRead, ibutton_submenu_callback, ibutton);
+    submenu_add_item(submenu, "Saved", SubmenuIndexSaved, ibutton_submenu_callback, ibutton);
+    submenu_add_item(submenu, "Add Manually", SubmenuIndexAdd, ibutton_submenu_callback, ibutton);
 
 
     submenu_set_selected_item(
     submenu_set_selected_item(
         submenu, scene_manager_get_scene_state(ibutton->scene_manager, iButtonSceneStart));
         submenu, scene_manager_get_scene_state(ibutton->scene_manager, iButtonSceneStart));
@@ -41,7 +35,6 @@ bool ibutton_scene_start_on_event(void* context, SceneManagerEvent event) {
             scene_manager_next_scene(ibutton->scene_manager, iButtonSceneRead);
             scene_manager_next_scene(ibutton->scene_manager, iButtonSceneRead);
             DOLPHIN_DEED(DolphinDeedIbuttonRead);
             DOLPHIN_DEED(DolphinDeedIbuttonRead);
         } else if(event.event == SubmenuIndexSaved) {
         } else if(event.event == SubmenuIndexSaved) {
-            furi_string_set(ibutton->file_path, IBUTTON_APP_FOLDER);
             scene_manager_next_scene(ibutton->scene_manager, iButtonSceneSelectKey);
             scene_manager_next_scene(ibutton->scene_manager, iButtonSceneSelectKey);
         } else if(event.event == SubmenuIndexAdd) {
         } else if(event.event == SubmenuIndexAdd) {
             scene_manager_next_scene(ibutton->scene_manager, iButtonSceneAddType);
             scene_manager_next_scene(ibutton->scene_manager, iButtonSceneAddType);

+ 26 - 0
applications/main/ibutton/scenes/ibutton_scene_view_data.c

@@ -0,0 +1,26 @@
+#include "../ibutton_i.h"
+
+void ibutton_scene_view_data_on_enter(void* context) {
+    iButton* ibutton = context;
+    iButtonKey* key = ibutton->key;
+    Widget* widget = ibutton->widget;
+
+    FuriString* tmp = furi_string_alloc();
+    ibutton_protocols_render_data(ibutton->protocols, key, tmp);
+
+    widget_add_text_scroll_element(widget, 0, 0, 128, 64, furi_string_get_cstr(tmp));
+
+    view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget);
+    furi_string_free(tmp);
+}
+
+bool ibutton_scene_view_data_on_event(void* context, SceneManagerEvent event) {
+    UNUSED(context);
+    UNUSED(event);
+    return false;
+}
+
+void ibutton_scene_view_data_on_exit(void* context) {
+    iButton* ibutton = context;
+    widget_reset(ibutton->widget);
+}

+ 32 - 46
applications/main/ibutton/scenes/ibutton_scene_write.c

@@ -1,5 +1,4 @@
 #include "../ibutton_i.h"
 #include "../ibutton_i.h"
-#include "toolbox/path.h"
 
 
 typedef enum {
 typedef enum {
     iButtonSceneWriteStateDefault,
     iButtonSceneWriteStateDefault,
@@ -13,61 +12,46 @@ static void ibutton_scene_write_callback(void* context, iButtonWorkerWriteResult
 
 
 void ibutton_scene_write_on_enter(void* context) {
 void ibutton_scene_write_on_enter(void* context) {
     iButton* ibutton = context;
     iButton* ibutton = context;
+    furi_assert(ibutton->write_mode != iButtonWriteModeInvalid);
+
     iButtonKey* key = ibutton->key;
     iButtonKey* key = ibutton->key;
-    Widget* widget = ibutton->widget;
-    iButtonWorker* worker = ibutton->key_worker;
+    iButtonWorker* worker = ibutton->worker;
+    const iButtonProtocolId protocol_id = ibutton_key_get_protocol_id(key);
 
 
-    const uint8_t* key_data = ibutton_key_get_data_p(key);
+    Widget* widget = ibutton->widget;
+    FuriString* tmp = furi_string_alloc();
 
 
-    FuriString* key_name;
-    key_name = furi_string_alloc();
-    if(furi_string_end_with(ibutton->file_path, IBUTTON_APP_EXTENSION)) {
-        path_extract_filename(ibutton->file_path, key_name, true);
-    }
+    widget_add_icon_element(widget, 3, 10, &I_iButtonKey_49x44);
 
 
-    // check that stored key has name
-    if(!furi_string_empty(key_name)) {
-        ibutton_text_store_set(ibutton, "%s", furi_string_get_cstr(key_name));
-    } else {
-        // if not, show key data
-        switch(ibutton_key_get_type(key)) {
-        case iButtonKeyDS1990:
-            ibutton_text_store_set(
-                ibutton,
-                "%02X %02X %02X %02X\n%02X %02X %02X %02X",
-                key_data[0],
-                key_data[1],
-                key_data[2],
-                key_data[3],
-                key_data[4],
-                key_data[5],
-                key_data[6],
-                key_data[7]);
-            break;
-        case iButtonKeyCyfral:
-            ibutton_text_store_set(ibutton, "%02X %02X", key_data[0], key_data[1]);
-            break;
-        case iButtonKeyMetakom:
-            ibutton_text_store_set(
-                ibutton, "%02X %02X %02X %02X", key_data[0], key_data[1], key_data[2], key_data[3]);
-            break;
-        }
-    }
+    furi_string_printf(
+        tmp,
+        "%s\n[%s]",
+        ibutton->key_name,
+        ibutton_protocols_get_name(ibutton->protocols, protocol_id));
 
 
-    widget_add_string_multiline_element(
-        widget, 90, 10, AlignCenter, AlignTop, FontPrimary, "iButton\nwriting");
-    widget_add_icon_element(widget, 3, 10, &I_iButtonKey_49x44);
     widget_add_text_box_element(
     widget_add_text_box_element(
-        widget, 54, 39, 75, 22, AlignCenter, AlignCenter, ibutton->text_store, true);
-
-    view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget);
+        widget, 52, 38, 75, 26, AlignCenter, AlignCenter, furi_string_get_cstr(tmp), true);
 
 
     ibutton_worker_write_set_callback(worker, ibutton_scene_write_callback, ibutton);
     ibutton_worker_write_set_callback(worker, ibutton_scene_write_callback, ibutton);
-    ibutton_worker_write_start(worker, key);
 
 
-    furi_string_free(key_name);
+    furi_string_set(tmp, "iButton\nwriting ");
+
+    if(ibutton->write_mode == iButtonWriteModeBlank) {
+        furi_string_cat(tmp, "Blank");
+        ibutton_worker_write_blank_start(worker, key);
+
+    } else if(ibutton->write_mode == iButtonWriteModeCopy) {
+        furi_string_cat(tmp, "Copy");
+        ibutton_worker_write_copy_start(worker, key);
+    }
+
+    widget_add_string_multiline_element(
+        widget, 88, 10, AlignCenter, AlignTop, FontPrimary, furi_string_get_cstr(tmp));
 
 
     ibutton_notification_message(ibutton, iButtonNotificationMessageEmulateStart);
     ibutton_notification_message(ibutton, iButtonNotificationMessageEmulateStart);
+    view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget);
+
+    furi_string_free(tmp);
 }
 }
 
 
 bool ibutton_scene_write_on_event(void* context, SceneManagerEvent event) {
 bool ibutton_scene_write_on_event(void* context, SceneManagerEvent event) {
@@ -94,7 +78,9 @@ bool ibutton_scene_write_on_event(void* context, SceneManagerEvent event) {
 
 
 void ibutton_scene_write_on_exit(void* context) {
 void ibutton_scene_write_on_exit(void* context) {
     iButton* ibutton = context;
     iButton* ibutton = context;
-    ibutton_worker_stop(ibutton->key_worker);
+    ibutton->write_mode = iButtonWriteModeInvalid;
+
+    ibutton_worker_stop(ibutton->worker);
     widget_reset(ibutton->widget);
     widget_reset(ibutton->widget);
 
 
     ibutton_notification_message(ibutton, iButtonNotificationMessageBlinkStop);
     ibutton_notification_message(ibutton, iButtonNotificationMessageBlinkStop);

+ 13 - 8
applications/main/infrared/infrared.c

@@ -3,6 +3,8 @@
 #include <string.h>
 #include <string.h>
 #include <dolphin/dolphin.h>
 #include <dolphin/dolphin.h>
 
 
+#define INFRARED_TX_MIN_INTERVAL_MS 50U
+
 static const NotificationSequence* infrared_notification_sequences[] = {
 static const NotificationSequence* infrared_notification_sequences[] = {
     &sequence_success,
     &sequence_success,
     &sequence_set_only_green_255,
     &sequence_set_only_green_255,
@@ -299,10 +301,13 @@ bool infrared_rename_current_remote(Infrared* infrared, const char* name) {
 
 
 void infrared_tx_start_signal(Infrared* infrared, InfraredSignal* signal) {
 void infrared_tx_start_signal(Infrared* infrared, InfraredSignal* signal) {
     if(infrared->app_state.is_transmitting) {
     if(infrared->app_state.is_transmitting) {
-        FURI_LOG_D(INFRARED_LOG_TAG, "Transmitter is already active");
         return;
         return;
-    } else {
-        infrared->app_state.is_transmitting = true;
+    }
+
+    const uint32_t time_elapsed = furi_get_tick() - infrared->app_state.last_transmit_time;
+
+    if(time_elapsed < INFRARED_TX_MIN_INTERVAL_MS) {
+        return;
     }
     }
 
 
     if(infrared_signal_is_raw(signal)) {
     if(infrared_signal_is_raw(signal)) {
@@ -319,6 +324,8 @@ void infrared_tx_start_signal(Infrared* infrared, InfraredSignal* signal) {
     infrared_worker_tx_set_get_signal_callback(
     infrared_worker_tx_set_get_signal_callback(
         infrared->worker, infrared_worker_tx_get_signal_steady_callback, infrared);
         infrared->worker, infrared_worker_tx_get_signal_steady_callback, infrared);
     infrared_worker_tx_start(infrared->worker);
     infrared_worker_tx_start(infrared->worker);
+
+    infrared->app_state.is_transmitting = true;
 }
 }
 
 
 void infrared_tx_start_button_index(Infrared* infrared, size_t button_index) {
 void infrared_tx_start_button_index(Infrared* infrared, size_t button_index) {
@@ -328,26 +335,24 @@ void infrared_tx_start_button_index(Infrared* infrared, size_t button_index) {
     InfraredSignal* signal = infrared_remote_button_get_signal(button);
     InfraredSignal* signal = infrared_remote_button_get_signal(button);
 
 
     infrared_tx_start_signal(infrared, signal);
     infrared_tx_start_signal(infrared, signal);
-    infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStartSend);
 }
 }
 
 
 void infrared_tx_start_received(Infrared* infrared) {
 void infrared_tx_start_received(Infrared* infrared) {
     infrared_tx_start_signal(infrared, infrared->received_signal);
     infrared_tx_start_signal(infrared, infrared->received_signal);
-    infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStartSend);
 }
 }
 
 
 void infrared_tx_stop(Infrared* infrared) {
 void infrared_tx_stop(Infrared* infrared) {
     if(!infrared->app_state.is_transmitting) {
     if(!infrared->app_state.is_transmitting) {
-        FURI_LOG_D(INFRARED_LOG_TAG, "Transmitter is already stopped");
         return;
         return;
-    } else {
-        infrared->app_state.is_transmitting = false;
     }
     }
 
 
     infrared_worker_tx_stop(infrared->worker);
     infrared_worker_tx_stop(infrared->worker);
     infrared_worker_tx_set_get_signal_callback(infrared->worker, NULL, NULL);
     infrared_worker_tx_set_get_signal_callback(infrared->worker, NULL, NULL);
 
 
     infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStop);
     infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStop);
+
+    infrared->app_state.is_transmitting = false;
+    infrared->app_state.last_transmit_time = furi_get_tick();
 }
 }
 
 
 void infrared_text_store_set(Infrared* infrared, uint32_t bank, const char* text, ...) {
 void infrared_text_store_set(Infrared* infrared, uint32_t bank, const char* text, ...) {

+ 1 - 0
applications/main/infrared/infrared_i.h

@@ -69,6 +69,7 @@ typedef struct {
     InfraredEditTarget edit_target : 8;
     InfraredEditTarget edit_target : 8;
     InfraredEditMode edit_mode : 8;
     InfraredEditMode edit_mode : 8;
     int32_t current_button_index;
     int32_t current_button_index;
+    uint32_t last_transmit_time;
 } InfraredAppState;
 } InfraredAppState;
 
 
 struct Infrared {
 struct Infrared {

+ 1 - 1
applications/main/infrared/scenes/infrared_scene_universal.c

@@ -74,4 +74,4 @@ bool infrared_scene_universal_on_event(void* context, SceneManagerEvent event) {
 void infrared_scene_universal_on_exit(void* context) {
 void infrared_scene_universal_on_exit(void* context) {
     Infrared* infrared = context;
     Infrared* infrared = context;
     submenu_reset(infrared->submenu);
     submenu_reset(infrared->submenu);
-}
+}

+ 1 - 1
applications/main/nfc/nfc_cli.c

@@ -32,7 +32,7 @@ static void nfc_cli_detect(Cli* cli, FuriString* args) {
     while(!cmd_exit) {
     while(!cmd_exit) {
         cmd_exit |= cli_cmd_interrupt_received(cli);
         cmd_exit |= cli_cmd_interrupt_received(cli);
         if(furi_hal_nfc_detect(&dev_data, 400)) {
         if(furi_hal_nfc_detect(&dev_data, 400)) {
-            printf("found: %s ", nfc_get_dev_type(dev_data.type));
+            printf("Found: %s ", nfc_get_dev_type(dev_data.type));
             printf("UID length: %d, UID:", dev_data.uid_len);
             printf("UID length: %d, UID:", dev_data.uid_len);
             for(size_t i = 0; i < dev_data.uid_len; i++) {
             for(size_t i = 0; i < dev_data.uid_len; i++) {
                 printf("%02X", dev_data.uid[i]);
                 printf("%02X", dev_data.uid[i]);

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

@@ -29,6 +29,7 @@ ADD_SCENE(nfc, mf_desfire_menu, MfDesfireMenu)
 ADD_SCENE(nfc, mf_desfire_data, MfDesfireData)
 ADD_SCENE(nfc, mf_desfire_data, MfDesfireData)
 ADD_SCENE(nfc, mf_desfire_app, MfDesfireApp)
 ADD_SCENE(nfc, mf_desfire_app, MfDesfireApp)
 ADD_SCENE(nfc, mf_classic_read_success, MfClassicReadSuccess)
 ADD_SCENE(nfc, mf_classic_read_success, MfClassicReadSuccess)
+ADD_SCENE(nfc, mf_classic_data, MfClassicData)
 ADD_SCENE(nfc, mf_classic_menu, MfClassicMenu)
 ADD_SCENE(nfc, mf_classic_menu, MfClassicMenu)
 ADD_SCENE(nfc, mf_classic_emulate, MfClassicEmulate)
 ADD_SCENE(nfc, mf_classic_emulate, MfClassicEmulate)
 ADD_SCENE(nfc, mf_classic_keys, MfClassicKeys)
 ADD_SCENE(nfc, mf_classic_keys, MfClassicKeys)

+ 106 - 0
applications/main/nfc/scenes/nfc_scene_mf_classic_data.c

@@ -0,0 +1,106 @@
+#include "../nfc_i.h"
+
+void nfc_scene_mf_classic_data_on_enter(void* context) {
+    Nfc* nfc = context;
+    MfClassicType type = nfc->dev->dev_data.mf_classic_data.type;
+    MfClassicData* data = &nfc->dev->dev_data.mf_classic_data;
+    TextBox* text_box = nfc->text_box;
+
+    text_box_set_font(text_box, TextBoxFontHex);
+
+    int card_blocks = 0;
+    if(type == MfClassicType1k) {
+        card_blocks = MF_CLASSIC_1K_TOTAL_SECTORS_NUM * 4;
+    } else if(type == MfClassicType4k) {
+        // 16 sectors of 4 blocks each plus 8 sectors of 16 blocks each
+        card_blocks = MF_CLASSIC_1K_TOTAL_SECTORS_NUM * 4 + 8 * 16;
+    } else if(type == MfClassicTypeMini) {
+        card_blocks = MF_MINI_TOTAL_SECTORS_NUM * 4;
+    }
+
+    int bytes_written = 0;
+    for(int block_num = 0; block_num < card_blocks; block_num++) {
+        bool is_sec_trailer = mf_classic_is_sector_trailer(block_num);
+        if(is_sec_trailer) {
+            uint8_t sector_num = mf_classic_get_sector_by_block(block_num);
+            MfClassicSectorTrailer* sec_tr =
+                mf_classic_get_sector_trailer_by_sector(data, sector_num);
+            // Key A
+            for(size_t i = 0; i < sizeof(sec_tr->key_a); i += 2) {
+                if((bytes_written % 8 == 0) && (bytes_written != 0)) {
+                    furi_string_push_back(nfc->text_box_store, '\n');
+                }
+                if(mf_classic_is_key_found(data, sector_num, MfClassicKeyA)) {
+                    furi_string_cat_printf(
+                        nfc->text_box_store, "%02X%02X ", sec_tr->key_a[i], sec_tr->key_a[i + 1]);
+                } else {
+                    furi_string_cat_printf(nfc->text_box_store, "???? ");
+                }
+                bytes_written += 2;
+            }
+            // Access bytes
+            for(size_t i = 0; i < MF_CLASSIC_ACCESS_BYTES_SIZE; i += 2) {
+                if((bytes_written % 8 == 0) && (bytes_written != 0)) {
+                    furi_string_push_back(nfc->text_box_store, '\n');
+                }
+                if(mf_classic_is_block_read(data, block_num)) {
+                    furi_string_cat_printf(
+                        nfc->text_box_store,
+                        "%02X%02X ",
+                        sec_tr->access_bits[i],
+                        sec_tr->access_bits[i + 1]);
+                } else {
+                    furi_string_cat_printf(nfc->text_box_store, "???? ");
+                }
+                bytes_written += 2;
+            }
+            // Key B
+            for(size_t i = 0; i < sizeof(sec_tr->key_b); i += 2) {
+                if((bytes_written % 8 == 0) && (bytes_written != 0)) {
+                    furi_string_push_back(nfc->text_box_store, '\n');
+                }
+                if(mf_classic_is_key_found(data, sector_num, MfClassicKeyB)) {
+                    furi_string_cat_printf(
+                        nfc->text_box_store, "%02X%02X ", sec_tr->key_b[i], sec_tr->key_b[i + 1]);
+                } else {
+                    furi_string_cat_printf(nfc->text_box_store, "???? ");
+                }
+                bytes_written += 2;
+            }
+        } else {
+            // Write data block
+            for(size_t i = 0; i < MF_CLASSIC_BLOCK_SIZE; i += 2) {
+                if((bytes_written % 8 == 0) && (bytes_written != 0)) {
+                    furi_string_push_back(nfc->text_box_store, '\n');
+                }
+                if(mf_classic_is_block_read(data, block_num)) {
+                    furi_string_cat_printf(
+                        nfc->text_box_store,
+                        "%02X%02X ",
+                        data->block[block_num].value[i],
+                        data->block[block_num].value[i + 1]);
+                } else {
+                    furi_string_cat_printf(nfc->text_box_store, "???? ");
+                }
+                bytes_written += 2;
+            }
+        }
+    }
+    text_box_set_text(text_box, furi_string_get_cstr(nfc->text_box_store));
+
+    view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextBox);
+}
+
+bool nfc_scene_mf_classic_data_on_event(void* context, SceneManagerEvent event) {
+    UNUSED(context);
+    UNUSED(event);
+    return false;
+}
+
+void nfc_scene_mf_classic_data_on_exit(void* context) {
+    Nfc* nfc = context;
+
+    // Clean view
+    text_box_reset(nfc->text_box);
+    furi_string_reset(nfc->text_box_store);
+}

+ 5 - 1
applications/main/nfc/scenes/nfc_scene_nfc_data_info.c

@@ -14,7 +14,8 @@ void nfc_scene_nfc_data_info_on_enter(void* context) {
     NfcDeviceData* dev_data = &nfc->dev->dev_data;
     NfcDeviceData* dev_data = &nfc->dev->dev_data;
     NfcProtocol protocol = dev_data->protocol;
     NfcProtocol protocol = dev_data->protocol;
     uint8_t text_scroll_height = 0;
     uint8_t text_scroll_height = 0;
-    if((protocol == NfcDeviceProtocolMifareDesfire) || (protocol == NfcDeviceProtocolMifareUl)) {
+    if((protocol == NfcDeviceProtocolMifareDesfire) || (protocol == NfcDeviceProtocolMifareUl) ||
+       (protocol == NfcDeviceProtocolMifareClassic)) {
         widget_add_button_element(
         widget_add_button_element(
             widget, GuiButtonTypeRight, "More", nfc_scene_nfc_data_info_widget_callback, nfc);
             widget, GuiButtonTypeRight, "More", nfc_scene_nfc_data_info_widget_callback, nfc);
         text_scroll_height = 52;
         text_scroll_height = 52;
@@ -136,6 +137,9 @@ bool nfc_scene_nfc_data_info_on_event(void* context, SceneManagerEvent event) {
             } else if(protocol == NfcDeviceProtocolMifareUl) {
             } else if(protocol == NfcDeviceProtocolMifareUl) {
                 scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightData);
                 scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightData);
                 consumed = true;
                 consumed = true;
+            } else if(protocol == NfcDeviceProtocolMifareClassic) {
+                scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicData);
+                consumed = true;
             }
             }
         }
         }
     }
     }

+ 3 - 0
applications/main/nfc/scenes/nfc_scene_read_card_success.c

@@ -46,6 +46,9 @@ bool nfc_scene_read_card_success_on_event(void* context, SceneManagerEvent event
         if(event.event == GuiButtonTypeLeft) {
         if(event.event == GuiButtonTypeLeft) {
             consumed = scene_manager_previous_scene(nfc->scene_manager);
             consumed = scene_manager_previous_scene(nfc->scene_manager);
         }
         }
+    } else if(event.type == SceneManagerEventTypeBack) {
+        consumed =
+            scene_manager_search_and_switch_to_previous_scene(nfc->scene_manager, NfcSceneStart);
     }
     }
     return consumed;
     return consumed;
 }
 }

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

@@ -150,6 +150,8 @@ bool nfc_scene_saved_menu_on_event(void* context, SceneManagerEvent event) {
                 application_info_present = nfc_supported_card_verify_and_parse(dev_data);
                 application_info_present = nfc_supported_card_verify_and_parse(dev_data);
             }
             }
 
 
+            FURI_LOG_I("nfc", "application_info_present: %d", application_info_present);
+
             if(application_info_present) {
             if(application_info_present) {
                 scene_manager_next_scene(nfc->scene_manager, NfcSceneDeviceInfo);
                 scene_manager_next_scene(nfc->scene_manager, NfcSceneDeviceInfo);
             } else {
             } else {

+ 1 - 0
applications/main/subghz/helpers/subghz_types.h

@@ -53,6 +53,7 @@ typedef enum {
     SubGhzLoadKeyStateUnknown,
     SubGhzLoadKeyStateUnknown,
     SubGhzLoadKeyStateOK,
     SubGhzLoadKeyStateOK,
     SubGhzLoadKeyStateParseErr,
     SubGhzLoadKeyStateParseErr,
+    SubGhzLoadKeyStateProtocolDescriptionErr,
 } SubGhzLoadKeyState;
 } SubGhzLoadKeyState;
 
 
 /** SubGhzLock */
 /** SubGhzLock */

+ 2 - 1
applications/main/subghz/scenes/subghz_scene_receiver_info.c

@@ -22,7 +22,9 @@ static bool subghz_scene_receiver_info_update_parser(void* context) {
     subghz->txrx->decoder_result = subghz_receiver_search_decoder_base_by_name(
     subghz->txrx->decoder_result = subghz_receiver_search_decoder_base_by_name(
         subghz->txrx->receiver,
         subghz->txrx->receiver,
         subghz_history_get_protocol_name(subghz->txrx->history, subghz->txrx->idx_menu_chosen));
         subghz_history_get_protocol_name(subghz->txrx->history, subghz->txrx->idx_menu_chosen));
+
     if(subghz->txrx->decoder_result) {
     if(subghz->txrx->decoder_result) {
+        //todo we are trying to deserialize without checking for errors, since it is assumed that we just received this chignal
         subghz_protocol_decoder_base_deserialize(
         subghz_protocol_decoder_base_deserialize(
             subghz->txrx->decoder_result,
             subghz->txrx->decoder_result,
             subghz_history_get_raw_data(subghz->txrx->history, subghz->txrx->idx_menu_chosen));
             subghz_history_get_raw_data(subghz->txrx->history, subghz->txrx->idx_menu_chosen));
@@ -128,7 +130,6 @@ bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event)
                        subghz,
                        subghz,
                        subghz_history_get_raw_data(
                        subghz_history_get_raw_data(
                            subghz->txrx->history, subghz->txrx->idx_menu_chosen))) {
                            subghz->txrx->history, subghz->txrx->idx_menu_chosen))) {
-                    scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowOnlyRx);
                     if(subghz->txrx->txrx_state == SubGhzTxRxStateTx) {
                     if(subghz->txrx->txrx_state == SubGhzTxRxStateTx) {
                         subghz_tx_stop(subghz);
                         subghz_tx_stop(subghz);
                     }
                     }

+ 3 - 2
applications/main/subghz/scenes/subghz_scene_set_type.c

@@ -34,8 +34,9 @@ bool subghz_scene_set_type_submenu_gen_data_protocol(
     do {
     do {
         Stream* fff_data_stream = flipper_format_get_raw_stream(subghz->txrx->fff_data);
         Stream* fff_data_stream = flipper_format_get_raw_stream(subghz->txrx->fff_data);
         stream_clean(fff_data_stream);
         stream_clean(fff_data_stream);
-        if(!subghz_protocol_decoder_base_serialize(
-               subghz->txrx->decoder_result, subghz->txrx->fff_data, subghz->txrx->preset)) {
+        if(subghz_protocol_decoder_base_serialize(
+               subghz->txrx->decoder_result, subghz->txrx->fff_data, subghz->txrx->preset) !=
+           SubGhzProtocolStatusOk) {
             FURI_LOG_E(TAG, "Unable to serialize");
             FURI_LOG_E(TAG, "Unable to serialize");
             break;
             break;
         }
         }

+ 18 - 20
applications/main/subghz/scenes/subghz_scene_transmitter.c

@@ -9,9 +9,8 @@ void subghz_scene_transmitter_callback(SubGhzCustomEvent event, void* context) {
 }
 }
 
 
 bool subghz_scene_transmitter_update_data_show(void* context) {
 bool subghz_scene_transmitter_update_data_show(void* context) {
-    //ToDo Fix
     SubGhz* subghz = context;
     SubGhz* subghz = context;
-
+    bool ret = false;
     if(subghz->txrx->decoder_result) {
     if(subghz->txrx->decoder_result) {
         FuriString* key_str;
         FuriString* key_str;
         FuriString* frequency_str;
         FuriString* frequency_str;
@@ -22,30 +21,29 @@ bool subghz_scene_transmitter_update_data_show(void* context) {
         modulation_str = furi_string_alloc();
         modulation_str = furi_string_alloc();
         uint8_t show_button = 0;
         uint8_t show_button = 0;
 
 
-        subghz_protocol_decoder_base_deserialize(
-            subghz->txrx->decoder_result, subghz->txrx->fff_data);
-        subghz_protocol_decoder_base_get_string(subghz->txrx->decoder_result, key_str);
-
-        if((subghz->txrx->decoder_result->protocol->flag & SubGhzProtocolFlag_Send) ==
-           SubGhzProtocolFlag_Send) {
-            show_button = 1;
-        }
+        if(subghz_protocol_decoder_base_deserialize(
+               subghz->txrx->decoder_result, subghz->txrx->fff_data) == SubGhzProtocolStatusOk) {
+            subghz_protocol_decoder_base_get_string(subghz->txrx->decoder_result, key_str);
 
 
-        subghz_get_frequency_modulation(subghz, frequency_str, modulation_str);
-        subghz_view_transmitter_add_data_to_show(
-            subghz->subghz_transmitter,
-            furi_string_get_cstr(key_str),
-            furi_string_get_cstr(frequency_str),
-            furi_string_get_cstr(modulation_str),
-            show_button);
+            if((subghz->txrx->decoder_result->protocol->flag & SubGhzProtocolFlag_Send) ==
+               SubGhzProtocolFlag_Send) {
+                show_button = 1;
+            }
 
 
+            subghz_get_frequency_modulation(subghz, frequency_str, modulation_str);
+            subghz_view_transmitter_add_data_to_show(
+                subghz->subghz_transmitter,
+                furi_string_get_cstr(key_str),
+                furi_string_get_cstr(frequency_str),
+                furi_string_get_cstr(modulation_str),
+                show_button);
+            ret = true;
+        }
         furi_string_free(frequency_str);
         furi_string_free(frequency_str);
         furi_string_free(modulation_str);
         furi_string_free(modulation_str);
         furi_string_free(key_str);
         furi_string_free(key_str);
-
-        return true;
     }
     }
-    return false;
+    return ret;
 }
 }
 
 
 void subghz_scene_transmitter_on_enter(void* context) {
 void subghz_scene_transmitter_on_enter(void* context) {

+ 17 - 4
applications/main/subghz/subghz_i.c

@@ -153,7 +153,6 @@ bool subghz_tx_start(SubGhz* subghz, FlipperFormat* flipper_format) {
             FURI_LOG_E(TAG, "Missing Protocol");
             FURI_LOG_E(TAG, "Missing Protocol");
             break;
             break;
         }
         }
-        //ToDo FIX
         if(!flipper_format_insert_or_update_uint32(flipper_format, "Repeat", &repeat, 1)) {
         if(!flipper_format_insert_or_update_uint32(flipper_format, "Repeat", &repeat, 1)) {
             FURI_LOG_E(TAG, "Unable Repeat");
             FURI_LOG_E(TAG, "Unable Repeat");
             break;
             break;
@@ -163,7 +162,8 @@ bool subghz_tx_start(SubGhz* subghz, FlipperFormat* flipper_format) {
             subghz->txrx->environment, furi_string_get_cstr(temp_str));
             subghz->txrx->environment, furi_string_get_cstr(temp_str));
 
 
         if(subghz->txrx->transmitter) {
         if(subghz->txrx->transmitter) {
-            if(subghz_transmitter_deserialize(subghz->txrx->transmitter, flipper_format)) {
+            if(subghz_transmitter_deserialize(subghz->txrx->transmitter, flipper_format) ==
+               SubGhzProtocolStatusOk) {
                 if(strcmp(furi_string_get_cstr(subghz->txrx->preset->name), "") != 0) {
                 if(strcmp(furi_string_get_cstr(subghz->txrx->preset->name), "") != 0) {
                     subghz_begin(
                     subghz_begin(
                         subghz,
                         subghz,
@@ -186,7 +186,12 @@ bool subghz_tx_start(SubGhz* subghz, FlipperFormat* flipper_format) {
                     //Start TX
                     //Start TX
                     furi_hal_subghz_start_async_tx(
                     furi_hal_subghz_start_async_tx(
                         subghz_transmitter_yield, subghz->txrx->transmitter);
                         subghz_transmitter_yield, subghz->txrx->transmitter);
+                } else {
+                    subghz_dialog_message_show_only_rx(subghz);
                 }
                 }
+            } else {
+                dialog_message_show_storage_error(
+                    subghz->dialogs, "Error in protocol\nparameters\ndescription");
             }
             }
         }
         }
         if(!ret) {
         if(!ret) {
@@ -333,8 +338,10 @@ bool subghz_key_load(SubGhz* subghz, const char* file_path, bool show_dialog) {
         subghz->txrx->decoder_result = subghz_receiver_search_decoder_base_by_name(
         subghz->txrx->decoder_result = subghz_receiver_search_decoder_base_by_name(
             subghz->txrx->receiver, furi_string_get_cstr(temp_str));
             subghz->txrx->receiver, furi_string_get_cstr(temp_str));
         if(subghz->txrx->decoder_result) {
         if(subghz->txrx->decoder_result) {
-            if(!subghz_protocol_decoder_base_deserialize(
-                   subghz->txrx->decoder_result, subghz->txrx->fff_data)) {
+            SubGhzProtocolStatus status = subghz_protocol_decoder_base_deserialize(
+                subghz->txrx->decoder_result, subghz->txrx->fff_data);
+            if(status != SubGhzProtocolStatusOk) {
+                load_key_state = SubGhzLoadKeyStateProtocolDescriptionErr;
                 break;
                 break;
             }
             }
         } else {
         } else {
@@ -355,6 +362,12 @@ bool subghz_key_load(SubGhz* subghz, const char* file_path, bool show_dialog) {
             dialog_message_show_storage_error(subghz->dialogs, "Cannot parse\nfile");
             dialog_message_show_storage_error(subghz->dialogs, "Cannot parse\nfile");
         }
         }
         return false;
         return false;
+    case SubGhzLoadKeyStateProtocolDescriptionErr:
+        if(show_dialog) {
+            dialog_message_show_storage_error(
+                subghz->dialogs, "Error in protocol\nparameters\ndescription");
+        }
+        return false;
 
 
     case SubGhzLoadKeyStateOK:
     case SubGhzLoadKeyStateOK:
         return true;
         return true;

+ 2 - 2
applications/plugins/clock/clock_app.c

@@ -56,7 +56,7 @@ static void clock_render_callback(Canvas* canvas, void* ctx) {
             31,
             31,
             AlignLeft,
             AlignLeft,
             AlignCenter,
             AlignCenter,
-            (data->datetime.hour > 12) ? "PM" : "AM");
+            (data->datetime.hour > 11) ? "PM" : "AM");
     }
     }
 
 
     canvas_set_font(canvas, FontSecondary);
     canvas_set_font(canvas, FontSecondary);
@@ -133,4 +133,4 @@ int32_t clock_app(void* p) {
     free(clock);
     free(clock);
 
 
     return 0;
     return 0;
-}
+}

+ 1 - 1
applications/plugins/dap_link/application.fam

@@ -10,7 +10,7 @@ App(
     stack_size=4 * 1024,
     stack_size=4 * 1024,
     order=20,
     order=20,
     fap_icon="dap_link.png",
     fap_icon="dap_link.png",
-    fap_category="Tools",
+    fap_category="GPIO",
     fap_private_libs=[
     fap_private_libs=[
         Lib(
         Lib(
             name="free-dap",
             name="free-dap",

+ 4 - 4
applications/plugins/hid_app/application.fam

@@ -1,10 +1,10 @@
 App(
 App(
     appid="hid_usb",
     appid="hid_usb",
-    name="USB Remote",
+    name="Remote",
     apptype=FlipperAppType.PLUGIN,
     apptype=FlipperAppType.PLUGIN,
     entry_point="hid_usb_app",
     entry_point="hid_usb_app",
     stack_size=1 * 1024,
     stack_size=1 * 1024,
-    fap_category="Tools",
+    fap_category="USB",
     fap_icon="hid_usb_10px.png",
     fap_icon="hid_usb_10px.png",
     fap_icon_assets="assets",
     fap_icon_assets="assets",
     fap_icon_assets_symbol="hid",
     fap_icon_assets_symbol="hid",
@@ -13,11 +13,11 @@ App(
 
 
 App(
 App(
     appid="hid_ble",
     appid="hid_ble",
-    name="Bluetooth Remote",
+    name="Remote",
     apptype=FlipperAppType.PLUGIN,
     apptype=FlipperAppType.PLUGIN,
     entry_point="hid_ble_app",
     entry_point="hid_ble_app",
     stack_size=1 * 1024,
     stack_size=1 * 1024,
-    fap_category="Tools",
+    fap_category="Bluetooth",
     fap_icon="hid_ble_10px.png",
     fap_icon="hid_ble_10px.png",
     fap_icon_assets="assets",
     fap_icon_assets="assets",
     fap_icon_assets_symbol="hid",
     fap_icon_assets_symbol="hid",

+ 11 - 1
applications/plugins/hid_app/hid.c

@@ -376,7 +376,17 @@ int32_t hid_ble_app(void* p) {
     // Wait 2nd core to update nvm storage
     // Wait 2nd core to update nvm storage
     furi_delay_ms(200);
     furi_delay_ms(200);
 
 
-    bt_keys_storage_set_storage_path(app->bt, HID_BT_KEYS_STORAGE_PATH);
+    // Migrate data from old sd-card folder
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+
+    storage_common_migrate(
+        storage,
+        EXT_PATH("apps/Tools/" HID_BT_KEYS_STORAGE_NAME),
+        APP_DATA_PATH(HID_BT_KEYS_STORAGE_NAME));
+
+    bt_keys_storage_set_storage_path(app->bt, APP_DATA_PATH(HID_BT_KEYS_STORAGE_NAME));
+
+    furi_record_close(RECORD_STORAGE);
 
 
     if(!bt_set_profile(app->bt, BtProfileHidKeyboard)) {
     if(!bt_set_profile(app->bt, BtProfileHidKeyboard)) {
         FURI_LOG_E(TAG, "Failed to switch to HID profile");
         FURI_LOG_E(TAG, "Failed to switch to HID profile");

+ 1 - 1
applications/plugins/hid_app/hid.h

@@ -23,7 +23,7 @@
 #include "views/hid_mouse_jiggler.h"
 #include "views/hid_mouse_jiggler.h"
 #include "views/hid_tiktok.h"
 #include "views/hid_tiktok.h"
 
 
-#define HID_BT_KEYS_STORAGE_PATH EXT_PATH("apps/Tools/.bt_hid.keys")
+#define HID_BT_KEYS_STORAGE_NAME ".bt_hid.keys"
 
 
 typedef enum {
 typedef enum {
     HidTransportUsb,
     HidTransportUsb,

+ 1 - 1
applications/plugins/music_player/application.fam

@@ -12,7 +12,7 @@ App(
     stack_size=2 * 1024,
     stack_size=2 * 1024,
     order=20,
     order=20,
     fap_icon="icons/music_10px.png",
     fap_icon="icons/music_10px.png",
-    fap_category="Misc",
+    fap_category="Media",
     fap_icon_assets="icons",
     fap_icon_assets="icons",
 )
 )
 
 

+ 8 - 3
applications/plugins/music_player/music_player.c

@@ -10,7 +10,6 @@
 
 
 #define TAG "MusicPlayer"
 #define TAG "MusicPlayer"
 
 
-#define MUSIC_PLAYER_APP_PATH_FOLDER ANY_PATH("music_player")
 #define MUSIC_PLAYER_APP_EXTENSION "*"
 #define MUSIC_PLAYER_APP_EXTENSION "*"
 
 
 #define MUSIC_PLAYER_SEMITONE_HISTORY_SIZE 4
 #define MUSIC_PLAYER_SEMITONE_HISTORY_SIZE 4
@@ -307,18 +306,24 @@ int32_t music_player_app(void* p) {
         if(p && strlen(p)) {
         if(p && strlen(p)) {
             furi_string_set(file_path, (const char*)p);
             furi_string_set(file_path, (const char*)p);
         } else {
         } else {
-            furi_string_set(file_path, MUSIC_PLAYER_APP_PATH_FOLDER);
+            Storage* storage = furi_record_open(RECORD_STORAGE);
+            storage_common_migrate(
+                storage, EXT_PATH("music_player"), STORAGE_APP_DATA_PATH_PREFIX);
+            furi_record_close(RECORD_STORAGE);
+
+            furi_string_set(file_path, STORAGE_APP_DATA_PATH_PREFIX);
 
 
             DialogsFileBrowserOptions browser_options;
             DialogsFileBrowserOptions browser_options;
             dialog_file_browser_set_basic_options(
             dialog_file_browser_set_basic_options(
                 &browser_options, MUSIC_PLAYER_APP_EXTENSION, &I_music_10px);
                 &browser_options, MUSIC_PLAYER_APP_EXTENSION, &I_music_10px);
             browser_options.hide_ext = false;
             browser_options.hide_ext = false;
-            browser_options.base_path = MUSIC_PLAYER_APP_PATH_FOLDER;
+            browser_options.base_path = STORAGE_APP_DATA_PATH_PREFIX;
 
 
             DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
             DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
             bool res = dialog_file_browser_show(dialogs, file_path, file_path, &browser_options);
             bool res = dialog_file_browser_show(dialogs, file_path, file_path, &browser_options);
 
 
             furi_record_close(RECORD_DIALOGS);
             furi_record_close(RECORD_DIALOGS);
+
             if(!res) {
             if(!res) {
                 FURI_LOG_E(TAG, "No file selected");
                 FURI_LOG_E(TAG, "No file selected");
                 break;
                 break;

+ 1 - 1
applications/plugins/nfc_magic/application.fam

@@ -11,7 +11,7 @@ App(
     stack_size=4 * 1024,
     stack_size=4 * 1024,
     order=30,
     order=30,
     fap_icon="../../../assets/icons/Archive/125_10px.png",
     fap_icon="../../../assets/icons/Archive/125_10px.png",
-    fap_category="Tools",
+    fap_category="NFC",
     fap_private_libs=[
     fap_private_libs=[
         Lib(
         Lib(
             name="magic",
             name="magic",

+ 2 - 2
applications/plugins/picopass/application.fam

@@ -1,6 +1,6 @@
 App(
 App(
     appid="picopass",
     appid="picopass",
-    name="PicoPass Reader",
+    name="PicoPass",
     apptype=FlipperAppType.EXTERNAL,
     apptype=FlipperAppType.EXTERNAL,
     targets=["f7"],
     targets=["f7"],
     entry_point="picopass_app",
     entry_point="picopass_app",
@@ -11,7 +11,7 @@ App(
     stack_size=4 * 1024,
     stack_size=4 * 1024,
     order=30,
     order=30,
     fap_icon="125_10px.png",
     fap_icon="125_10px.png",
-    fap_category="Tools",
+    fap_category="NFC",
     fap_libs=["mbedtls"],
     fap_libs=["mbedtls"],
     fap_private_libs=[
     fap_private_libs=[
         Lib(
         Lib(

+ 10 - 10
applications/plugins/picopass/helpers/iclass_elite_dict.c

@@ -3,8 +3,8 @@
 #include <lib/toolbox/args.h>
 #include <lib/toolbox/args.h>
 #include <lib/flipper_format/flipper_format.h>
 #include <lib/flipper_format/flipper_format.h>
 
 
-#define ICLASS_ELITE_DICT_FLIPPER_PATH EXT_PATH("picopass/assets/iclass_elite_dict.txt")
-#define ICLASS_ELITE_DICT_USER_PATH EXT_PATH("picopass/assets/iclass_elite_dict_user.txt")
+#define ICLASS_ELITE_DICT_FLIPPER_NAME APP_DATA_PATH("assets/iclass_elite_dict.txt")
+#define ICLASS_ELITE_DICT_USER_NAME APP_DATA_PATH("assets/iclass_elite_dict_user.txt")
 
 
 #define TAG "IclassEliteDict"
 #define TAG "IclassEliteDict"
 
 
@@ -21,10 +21,10 @@ bool iclass_elite_dict_check_presence(IclassEliteDictType dict_type) {
 
 
     bool dict_present = false;
     bool dict_present = false;
     if(dict_type == IclassEliteDictTypeFlipper) {
     if(dict_type == IclassEliteDictTypeFlipper) {
-        dict_present = storage_common_stat(storage, ICLASS_ELITE_DICT_FLIPPER_PATH, NULL) ==
-                       FSE_OK;
+        dict_present =
+            (storage_common_stat(storage, ICLASS_ELITE_DICT_FLIPPER_NAME, NULL) == FSE_OK);
     } else if(dict_type == IclassEliteDictTypeUser) {
     } else if(dict_type == IclassEliteDictTypeUser) {
-        dict_present = storage_common_stat(storage, ICLASS_ELITE_DICT_USER_PATH, NULL) == FSE_OK;
+        dict_present = (storage_common_stat(storage, ICLASS_ELITE_DICT_USER_NAME, NULL) == FSE_OK);
     }
     }
 
 
     furi_record_close(RECORD_STORAGE);
     furi_record_close(RECORD_STORAGE);
@@ -36,27 +36,26 @@ IclassEliteDict* iclass_elite_dict_alloc(IclassEliteDictType dict_type) {
     IclassEliteDict* dict = malloc(sizeof(IclassEliteDict));
     IclassEliteDict* dict = malloc(sizeof(IclassEliteDict));
     Storage* storage = furi_record_open(RECORD_STORAGE);
     Storage* storage = furi_record_open(RECORD_STORAGE);
     dict->stream = buffered_file_stream_alloc(storage);
     dict->stream = buffered_file_stream_alloc(storage);
-    furi_record_close(RECORD_STORAGE);
     FuriString* next_line = furi_string_alloc();
     FuriString* next_line = furi_string_alloc();
 
 
     bool dict_loaded = false;
     bool dict_loaded = false;
     do {
     do {
         if(dict_type == IclassEliteDictTypeFlipper) {
         if(dict_type == IclassEliteDictTypeFlipper) {
             if(!buffered_file_stream_open(
             if(!buffered_file_stream_open(
-                   dict->stream, ICLASS_ELITE_DICT_FLIPPER_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) {
+                   dict->stream, ICLASS_ELITE_DICT_FLIPPER_NAME, FSAM_READ, FSOM_OPEN_EXISTING)) {
                 buffered_file_stream_close(dict->stream);
                 buffered_file_stream_close(dict->stream);
                 break;
                 break;
             }
             }
         } else if(dict_type == IclassEliteDictTypeUser) {
         } else if(dict_type == IclassEliteDictTypeUser) {
             if(!buffered_file_stream_open(
             if(!buffered_file_stream_open(
-                   dict->stream, ICLASS_ELITE_DICT_USER_PATH, FSAM_READ_WRITE, FSOM_OPEN_ALWAYS)) {
+                   dict->stream, ICLASS_ELITE_DICT_USER_NAME, FSAM_READ_WRITE, FSOM_OPEN_ALWAYS)) {
                 buffered_file_stream_close(dict->stream);
                 buffered_file_stream_close(dict->stream);
                 break;
                 break;
             }
             }
         }
         }
 
 
         // Read total amount of keys
         // Read total amount of keys
-        while(true) {
+        while(true) { //-V547
             if(!stream_read_line(dict->stream, next_line)) break;
             if(!stream_read_line(dict->stream, next_line)) break;
             if(furi_string_get_char(next_line, 0) == '#') continue;
             if(furi_string_get_char(next_line, 0) == '#') continue;
             if(furi_string_size(next_line) != ICLASS_ELITE_KEY_LINE_LEN) continue;
             if(furi_string_size(next_line) != ICLASS_ELITE_KEY_LINE_LEN) continue;
@@ -69,12 +68,13 @@ IclassEliteDict* iclass_elite_dict_alloc(IclassEliteDictType dict_type) {
         FURI_LOG_I(TAG, "Loaded dictionary with %lu keys", dict->total_keys);
         FURI_LOG_I(TAG, "Loaded dictionary with %lu keys", dict->total_keys);
     } while(false);
     } while(false);
 
 
-    if(!dict_loaded) {
+    if(!dict_loaded) { //-V547
         buffered_file_stream_close(dict->stream);
         buffered_file_stream_close(dict->stream);
         free(dict);
         free(dict);
         dict = NULL;
         dict = NULL;
     }
     }
 
 
+    furi_record_close(RECORD_STORAGE);
     furi_string_free(next_line);
     furi_string_free(next_line);
 
 
     return dict;
     return dict;

+ 18 - 0
applications/plugins/picopass/picopass.c

@@ -171,8 +171,26 @@ void picopass_show_loading_popup(void* context, bool show) {
     }
     }
 }
 }
 
 
+static void picopass_migrate_from_old_folder() {
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    storage_common_migrate(storage, "/ext/picopass", STORAGE_APP_DATA_PATH_PREFIX);
+    furi_record_close(RECORD_STORAGE);
+}
+
+bool picopass_is_memset(const uint8_t* data, const uint8_t pattern, size_t size) {
+    bool result = size > 0;
+    while(size > 0) {
+        result &= (*data == pattern);
+        data++;
+        size--;
+    }
+    return result;
+}
+
 int32_t picopass_app(void* p) {
 int32_t picopass_app(void* p) {
     UNUSED(p);
     UNUSED(p);
+    picopass_migrate_from_old_folder();
+
     Picopass* picopass = picopass_alloc();
     Picopass* picopass = picopass_alloc();
 
 
     scene_manager_next_scene(picopass->scene_manager, PicopassSceneStart);
     scene_manager_next_scene(picopass->scene_manager, PicopassSceneStart);

+ 6 - 10
applications/plugins/picopass/picopass_device.c

@@ -48,13 +48,9 @@ static bool picopass_device_save_file(
         if(use_load_path && !furi_string_empty(dev->load_path)) {
         if(use_load_path && !furi_string_empty(dev->load_path)) {
             // Get directory name
             // Get directory name
             path_extract_dirname(furi_string_get_cstr(dev->load_path), temp_str);
             path_extract_dirname(furi_string_get_cstr(dev->load_path), temp_str);
-            // Create picopass directory if necessary
-            if(!storage_simply_mkdir(dev->storage, furi_string_get_cstr(temp_str))) break;
             // Make path to file to save
             // Make path to file to save
             furi_string_cat_printf(temp_str, "/%s%s", dev_name, extension);
             furi_string_cat_printf(temp_str, "/%s%s", dev_name, extension);
         } else {
         } else {
-            // Create picopass directory if necessary
-            if(!storage_simply_mkdir(dev->storage, PICOPASS_APP_FOLDER)) break;
             // First remove picopass device file if it was saved
             // First remove picopass device file if it was saved
             furi_string_printf(temp_str, "%s/%s%s", folder, dev_name, extension);
             furi_string_printf(temp_str, "%s/%s%s", folder, dev_name, extension);
         }
         }
@@ -126,10 +122,11 @@ static bool picopass_device_save_file(
 bool picopass_device_save(PicopassDevice* dev, const char* dev_name) {
 bool picopass_device_save(PicopassDevice* dev, const char* dev_name) {
     if(dev->format == PicopassDeviceSaveFormatHF) {
     if(dev->format == PicopassDeviceSaveFormatHF) {
         return picopass_device_save_file(
         return picopass_device_save_file(
-            dev, dev_name, PICOPASS_APP_FOLDER, PICOPASS_APP_EXTENSION, true);
+            dev, dev_name, STORAGE_APP_DATA_PATH_PREFIX, PICOPASS_APP_EXTENSION, true);
     } else if(dev->format == PicopassDeviceSaveFormatLF) {
     } else if(dev->format == PicopassDeviceSaveFormatLF) {
         return picopass_device_save_file(dev, dev_name, ANY_PATH("lfrfid"), ".rfid", true);
         return picopass_device_save_file(dev, dev_name, ANY_PATH("lfrfid"), ".rfid", true);
     }
     }
+
     return false;
     return false;
 }
 }
 
 
@@ -225,13 +222,12 @@ void picopass_device_free(PicopassDevice* picopass_dev) {
 bool picopass_file_select(PicopassDevice* dev) {
 bool picopass_file_select(PicopassDevice* dev) {
     furi_assert(dev);
     furi_assert(dev);
 
 
-    // Input events and views are managed by file_browser
     FuriString* picopass_app_folder;
     FuriString* picopass_app_folder;
-    picopass_app_folder = furi_string_alloc_set(PICOPASS_APP_FOLDER);
+    picopass_app_folder = furi_string_alloc_set(STORAGE_APP_DATA_PATH_PREFIX);
 
 
     DialogsFileBrowserOptions browser_options;
     DialogsFileBrowserOptions browser_options;
     dialog_file_browser_set_basic_options(&browser_options, PICOPASS_APP_EXTENSION, &I_Nfc_10px);
     dialog_file_browser_set_basic_options(&browser_options, PICOPASS_APP_EXTENSION, &I_Nfc_10px);
-    browser_options.base_path = PICOPASS_APP_FOLDER;
+    browser_options.base_path = STORAGE_APP_DATA_PATH_PREFIX;
 
 
     bool res = dialog_file_browser_show(
     bool res = dialog_file_browser_show(
         dev->dialogs, dev->load_path, picopass_app_folder, &browser_options);
         dev->dialogs, dev->load_path, picopass_app_folder, &browser_options);
@@ -274,7 +270,7 @@ bool picopass_device_delete(PicopassDevice* dev, bool use_load_path) {
             furi_string_set(file_path, dev->load_path);
             furi_string_set(file_path, dev->load_path);
         } else {
         } else {
             furi_string_printf(
             furi_string_printf(
-                file_path, "%s/%s%s", PICOPASS_APP_FOLDER, dev->dev_name, PICOPASS_APP_EXTENSION);
+                file_path, APP_DATA_PATH("%s%s"), dev->dev_name, PICOPASS_APP_EXTENSION);
         }
         }
         if(!storage_simply_remove(dev->storage, furi_string_get_cstr(file_path))) break;
         if(!storage_simply_remove(dev->storage, furi_string_get_cstr(file_path))) break;
         deleted = true;
         deleted = true;
@@ -368,7 +364,7 @@ ReturnCode picopass_device_parse_wiegand(uint8_t* data, PicopassWiegandRecord* r
 
 
         record->CardNumber = (bot >> 1) & 0xFFFF;
         record->CardNumber = (bot >> 1) & 0xFFFF;
         record->FacilityCode = (bot >> 17) & 0xFF;
         record->FacilityCode = (bot >> 17) & 0xFF;
-        FURI_LOG_D(TAG, "FC:%u CN: %u\n", record->FacilityCode, record->CardNumber);
+        FURI_LOG_D(TAG, "FC: %u CN: %u", record->FacilityCode, record->CardNumber);
         record->valid = true;
         record->valid = true;
     } else {
     } else {
         record->CardNumber = 0;
         record->CardNumber = 0;

+ 1 - 2
applications/plugins/picopass/picopass_device.h

@@ -22,8 +22,8 @@
 #define PICOPASS_KD_BLOCK_INDEX 3
 #define PICOPASS_KD_BLOCK_INDEX 3
 #define PICOPASS_KC_BLOCK_INDEX 4
 #define PICOPASS_KC_BLOCK_INDEX 4
 #define PICOPASS_AIA_BLOCK_INDEX 5
 #define PICOPASS_AIA_BLOCK_INDEX 5
+#define PICOPASS_PACS_CFG_BLOCK_INDEX 6
 
 
-#define PICOPASS_APP_FOLDER ANY_PATH("picopass")
 #define PICOPASS_APP_EXTENSION ".picopass"
 #define PICOPASS_APP_EXTENSION ".picopass"
 #define PICOPASS_APP_SHADOW_EXTENSION ".pas"
 #define PICOPASS_APP_SHADOW_EXTENSION ".pas"
 
 
@@ -80,7 +80,6 @@ typedef struct {
     PicopassDeviceSaveFormat format;
     PicopassDeviceSaveFormat format;
     PicopassLoadingCallback loading_cb;
     PicopassLoadingCallback loading_cb;
     void* loading_cb_ctx;
     void* loading_cb_ctx;
-
 } PicopassDevice;
 } PicopassDevice;
 
 
 PicopassDevice* picopass_device_alloc();
 PicopassDevice* picopass_device_alloc();

+ 12 - 0
applications/plugins/picopass/picopass_i.h

@@ -81,3 +81,15 @@ void picopass_blink_start(Picopass* picopass);
 void picopass_blink_stop(Picopass* picopass);
 void picopass_blink_stop(Picopass* picopass);
 
 
 void picopass_show_loading_popup(void* context, bool show);
 void picopass_show_loading_popup(void* context, bool show);
+
+/** Check if memory is set to pattern
+ *
+ * @warning    zero size will return false
+ *
+ * @param[in]  data     Pointer to the byte array
+ * @param[in]  pattern  The pattern
+ * @param[in]  size     The byte array size
+ *
+ * @return     True if memory is set to pattern, false otherwise
+ */
+bool picopass_is_memset(const uint8_t* data, const uint8_t pattern, size_t size);

+ 160 - 5
applications/plugins/picopass/picopass_worker.c

@@ -5,7 +5,8 @@
 #define TAG "PicopassWorker"
 #define TAG "PicopassWorker"
 
 
 const uint8_t picopass_iclass_key[] = {0xaf, 0xa7, 0x85, 0xa7, 0xda, 0xb3, 0x33, 0x78};
 const uint8_t picopass_iclass_key[] = {0xaf, 0xa7, 0x85, 0xa7, 0xda, 0xb3, 0x33, 0x78};
-const uint8_t picopass_factory_key[] = {0x76, 0x65, 0x54, 0x43, 0x32, 0x21, 0x10, 0x00};
+const uint8_t picopass_factory_credit_key[] = {0x76, 0x65, 0x54, 0x43, 0x32, 0x21, 0x10, 0x00};
+const uint8_t picopass_factory_debit_key[] = {0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87};
 
 
 static void picopass_worker_enable_field() {
 static void picopass_worker_enable_field() {
     furi_hal_nfc_ll_txrx_on();
     furi_hal_nfc_ll_txrx_on();
@@ -197,6 +198,28 @@ static ReturnCode picopass_auth_standard(uint8_t* csn, uint8_t* div_key) {
     return rfalPicoPassPollerCheck(mac, &chkRes);
     return rfalPicoPassPollerCheck(mac, &chkRes);
 }
 }
 
 
+static ReturnCode picopass_auth_factory(uint8_t* csn, uint8_t* div_key) {
+    rfalPicoPassReadCheckRes rcRes;
+    rfalPicoPassCheckRes chkRes;
+
+    ReturnCode err;
+
+    uint8_t mac[4] = {0};
+    uint8_t ccnr[12] = {0};
+
+    err = rfalPicoPassPollerReadCheck(&rcRes);
+    if(err != ERR_NONE) {
+        FURI_LOG_E(TAG, "rfalPicoPassPollerReadCheck error %d", err);
+        return err;
+    }
+    memcpy(ccnr, rcRes.CCNR, sizeof(rcRes.CCNR)); // last 4 bytes left 0
+
+    loclass_diversifyKey(csn, picopass_factory_debit_key, div_key);
+    loclass_opt_doReaderMAC(ccnr, div_key, mac);
+
+    return rfalPicoPassPollerCheck(mac, &chkRes);
+}
+
 static ReturnCode picopass_auth_dict(
 static ReturnCode picopass_auth_dict(
     uint8_t* csn,
     uint8_t* csn,
     PicopassPacs* pacs,
     PicopassPacs* pacs,
@@ -264,14 +287,23 @@ static ReturnCode picopass_auth_dict(
 ReturnCode picopass_auth(PicopassBlock* AA1, PicopassPacs* pacs) {
 ReturnCode picopass_auth(PicopassBlock* AA1, PicopassPacs* pacs) {
     ReturnCode err;
     ReturnCode err;
 
 
-    FURI_LOG_E(TAG, "Trying standard legacy key");
+    FURI_LOG_I(TAG, "Trying standard legacy key");
     err = picopass_auth_standard(
     err = picopass_auth_standard(
         AA1[PICOPASS_CSN_BLOCK_INDEX].data, AA1[PICOPASS_KD_BLOCK_INDEX].data);
         AA1[PICOPASS_CSN_BLOCK_INDEX].data, AA1[PICOPASS_KD_BLOCK_INDEX].data);
     if(err == ERR_NONE) {
     if(err == ERR_NONE) {
+        memcpy(pacs->key, picopass_iclass_key, PICOPASS_BLOCK_LEN);
+        return ERR_NONE;
+    }
+
+    FURI_LOG_I(TAG, "Trying factory default key");
+    err = picopass_auth_factory(
+        AA1[PICOPASS_CSN_BLOCK_INDEX].data, AA1[PICOPASS_KD_BLOCK_INDEX].data);
+    if(err == ERR_NONE) {
+        memcpy(pacs->key, picopass_factory_debit_key, PICOPASS_BLOCK_LEN);
         return ERR_NONE;
         return ERR_NONE;
     }
     }
 
 
-    FURI_LOG_E(TAG, "Starting user dictionary attack");
+    FURI_LOG_I(TAG, "Starting user dictionary attack");
     err = picopass_auth_dict(
     err = picopass_auth_dict(
         AA1[PICOPASS_CSN_BLOCK_INDEX].data,
         AA1[PICOPASS_CSN_BLOCK_INDEX].data,
         pacs,
         pacs,
@@ -281,7 +313,7 @@ ReturnCode picopass_auth(PicopassBlock* AA1, PicopassPacs* pacs) {
         return ERR_NONE;
         return ERR_NONE;
     }
     }
 
 
-    FURI_LOG_E(TAG, "Starting in-built dictionary attack");
+    FURI_LOG_I(TAG, "Starting system dictionary attack");
     err = picopass_auth_dict(
     err = picopass_auth_dict(
         AA1[PICOPASS_CSN_BLOCK_INDEX].data,
         AA1[PICOPASS_CSN_BLOCK_INDEX].data,
         pacs,
         pacs,
@@ -406,6 +438,84 @@ ReturnCode picopass_write_card(PicopassBlock* AA1) {
     return ERR_NONE;
     return ERR_NONE;
 }
 }
 
 
+ReturnCode picopass_write_block(PicopassPacs* pacs, uint8_t blockNo, uint8_t* newBlock) {
+    rfalPicoPassIdentifyRes idRes;
+    rfalPicoPassSelectRes selRes;
+    rfalPicoPassReadCheckRes rcRes;
+    rfalPicoPassCheckRes chkRes;
+
+    ReturnCode err;
+
+    uint8_t div_key[8] = {0};
+    uint8_t mac[4] = {0};
+    uint8_t ccnr[12] = {0};
+
+    err = rfalPicoPassPollerIdentify(&idRes);
+    if(err != ERR_NONE) {
+        FURI_LOG_E(TAG, "rfalPicoPassPollerIdentify error %d", err);
+        return err;
+    }
+
+    err = rfalPicoPassPollerSelect(idRes.CSN, &selRes);
+    if(err != ERR_NONE) {
+        FURI_LOG_E(TAG, "rfalPicoPassPollerSelect error %d", err);
+        return err;
+    }
+
+    err = rfalPicoPassPollerReadCheck(&rcRes);
+    if(err != ERR_NONE) {
+        FURI_LOG_E(TAG, "rfalPicoPassPollerReadCheck error %d", err);
+        return err;
+    }
+    memcpy(ccnr, rcRes.CCNR, sizeof(rcRes.CCNR)); // last 4 bytes left 0
+
+    loclass_diversifyKey(selRes.CSN, pacs->key, div_key);
+    loclass_opt_doReaderMAC(ccnr, div_key, mac);
+
+    err = rfalPicoPassPollerCheck(mac, &chkRes);
+    if(err != ERR_NONE) {
+        FURI_LOG_E(TAG, "rfalPicoPassPollerCheck error %d", err);
+        return err;
+    }
+
+    FURI_LOG_D(TAG, "rfalPicoPassPollerWriteBlock %d", blockNo);
+    uint8_t data[9] = {
+        blockNo,
+        newBlock[0],
+        newBlock[1],
+        newBlock[2],
+        newBlock[3],
+        newBlock[4],
+        newBlock[5],
+        newBlock[6],
+        newBlock[7]};
+    loclass_doMAC_N(data, sizeof(data), div_key, mac);
+    FURI_LOG_D(
+        TAG,
+        "loclass_doMAC_N %d %02x%02x%02x%02x%02x%02x%02x%02x %02x%02x%02x%02x",
+        blockNo,
+        data[1],
+        data[2],
+        data[3],
+        data[4],
+        data[5],
+        data[6],
+        data[7],
+        data[8],
+        mac[0],
+        mac[1],
+        mac[2],
+        mac[3]);
+
+    err = rfalPicoPassPollerWriteBlock(data[0], data + 1, mac);
+    if(err != ERR_NONE) {
+        FURI_LOG_E(TAG, "rfalPicoPassPollerWriteBlock error %d", err);
+        return err;
+    }
+
+    return ERR_NONE;
+}
+
 int32_t picopass_worker_task(void* context) {
 int32_t picopass_worker_task(void* context) {
     PicopassWorker* picopass_worker = context;
     PicopassWorker* picopass_worker = context;
 
 
@@ -414,6 +524,8 @@ int32_t picopass_worker_task(void* context) {
         picopass_worker_detect(picopass_worker);
         picopass_worker_detect(picopass_worker);
     } else if(picopass_worker->state == PicopassWorkerStateWrite) {
     } else if(picopass_worker->state == PicopassWorkerStateWrite) {
         picopass_worker_write(picopass_worker);
         picopass_worker_write(picopass_worker);
+    } else if(picopass_worker->state == PicopassWorkerStateWriteStandardKey) {
+        picopass_worker_write_standard_key(picopass_worker);
     }
     }
     picopass_worker_disable_field(ERR_NONE);
     picopass_worker_disable_field(ERR_NONE);
 
 
@@ -448,7 +560,7 @@ void picopass_worker_detect(PicopassWorker* picopass_worker) {
             }
             }
 
 
             // Thank you proxmark!
             // Thank you proxmark!
-            pacs->legacy = (memcmp(AA1[5].data, "\xff\xff\xff\xff\xff\xff\xff\xff", 8) == 0);
+            pacs->legacy = picopass_is_memset(AA1[5].data, 0xFF, 8);
             pacs->se_enabled = (memcmp(AA1[5].data, "\xff\xff\xff\x00\x06\xff\xff\xff", 8) == 0);
             pacs->se_enabled = (memcmp(AA1[5].data, "\xff\xff\xff\x00\x06\xff\xff\xff", 8) == 0);
             if(pacs->se_enabled) {
             if(pacs->se_enabled) {
                 FURI_LOG_D(TAG, "SE enabled");
                 FURI_LOG_D(TAG, "SE enabled");
@@ -520,3 +632,46 @@ void picopass_worker_write(PicopassWorker* picopass_worker) {
         furi_delay_ms(100);
         furi_delay_ms(100);
     }
     }
 }
 }
+
+void picopass_worker_write_standard_key(PicopassWorker* picopass_worker) {
+    PicopassDeviceData* dev_data = picopass_worker->dev_data;
+    PicopassBlock* AA1 = dev_data->AA1;
+    PicopassPacs* pacs = &dev_data->pacs;
+    ReturnCode err;
+    PicopassWorkerEvent nextState = PicopassWorkerEventSuccess;
+
+    uint8_t* csn = AA1[PICOPASS_CSN_BLOCK_INDEX].data;
+    uint8_t* configBlock = AA1[PICOPASS_CONFIG_BLOCK_INDEX].data;
+    uint8_t fuses = configBlock[7];
+    uint8_t* oldKey = AA1[PICOPASS_KD_BLOCK_INDEX].data;
+
+    uint8_t newKey[PICOPASS_BLOCK_LEN] = {0};
+    loclass_diversifyKey(csn, picopass_iclass_key, newKey);
+
+    if((fuses & 0x80) == 0x80) {
+        FURI_LOG_D(TAG, "Plain write for personalized mode key change");
+    } else {
+        FURI_LOG_D(TAG, "XOR write for application mode key change");
+        // XOR when in application mode
+        for(size_t i = 0; i < PICOPASS_BLOCK_LEN; i++) {
+            newKey[i] ^= oldKey[i];
+        }
+    }
+
+    while(picopass_worker->state == PicopassWorkerStateWriteStandardKey) {
+        if(picopass_detect_card(1000) == ERR_NONE) {
+            err = picopass_write_block(pacs, PICOPASS_KD_BLOCK_INDEX, newKey);
+            if(err != ERR_NONE) {
+                FURI_LOG_E(TAG, "picopass_write_block error %d", err);
+                nextState = PicopassWorkerEventFail;
+            }
+
+            // Notify caller and exit
+            if(picopass_worker->callback) {
+                picopass_worker->callback(nextState, picopass_worker->context);
+            }
+            break;
+        }
+        furi_delay_ms(100);
+    }
+}

+ 1 - 0
applications/plugins/picopass/picopass_worker.h

@@ -12,6 +12,7 @@ typedef enum {
     // Main worker states
     // Main worker states
     PicopassWorkerStateDetect,
     PicopassWorkerStateDetect,
     PicopassWorkerStateWrite,
     PicopassWorkerStateWrite,
+    PicopassWorkerStateWriteStandardKey,
     // Transition
     // Transition
     PicopassWorkerStateStop,
     PicopassWorkerStateStop,
 } PicopassWorkerState;
 } PicopassWorkerState;

+ 1 - 0
applications/plugins/picopass/picopass_worker_i.h

@@ -31,3 +31,4 @@ int32_t picopass_worker_task(void* context);
 
 
 void picopass_worker_detect(PicopassWorker* picopass_worker);
 void picopass_worker_detect(PicopassWorker* picopass_worker);
 void picopass_worker_write(PicopassWorker* picopass_worker);
 void picopass_worker_write(PicopassWorker* picopass_worker);
+void picopass_worker_write_standard_key(PicopassWorker* picopass_worker);

+ 2 - 0
applications/plugins/picopass/scenes/picopass_scene_config.h

@@ -11,3 +11,5 @@ ADD_SCENE(picopass, delete, Delete)
 ADD_SCENE(picopass, delete_success, DeleteSuccess)
 ADD_SCENE(picopass, delete_success, DeleteSuccess)
 ADD_SCENE(picopass, write_card, WriteCard)
 ADD_SCENE(picopass, write_card, WriteCard)
 ADD_SCENE(picopass, write_card_success, WriteCardSuccess)
 ADD_SCENE(picopass, write_card_success, WriteCardSuccess)
+ADD_SCENE(picopass, read_factory_success, ReadFactorySuccess)
+ADD_SCENE(picopass, write_key, WriteKey)

+ 10 - 1
applications/plugins/picopass/scenes/picopass_scene_read_card.c

@@ -1,6 +1,8 @@
 #include "../picopass_i.h"
 #include "../picopass_i.h"
 #include <dolphin/dolphin.h>
 #include <dolphin/dolphin.h>
 
 
+const uint8_t picopass_factory_key_check[] = {0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87};
+
 void picopass_read_card_worker_callback(PicopassWorkerEvent event, void* context) {
 void picopass_read_card_worker_callback(PicopassWorkerEvent event, void* context) {
     UNUSED(event);
     UNUSED(event);
     Picopass* picopass = context;
     Picopass* picopass = context;
@@ -34,7 +36,14 @@ bool picopass_scene_read_card_on_event(void* context, SceneManagerEvent event) {
 
 
     if(event.type == SceneManagerEventTypeCustom) {
     if(event.type == SceneManagerEventTypeCustom) {
         if(event.event == PicopassCustomEventWorkerExit) {
         if(event.event == PicopassCustomEventWorkerExit) {
-            scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadCardSuccess);
+            if(memcmp(
+                   picopass->dev->dev_data.pacs.key,
+                   picopass_factory_key_check,
+                   PICOPASS_BLOCK_LEN) == 0) {
+                scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadFactorySuccess);
+            } else {
+                scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadCardSuccess);
+            }
             consumed = true;
             consumed = true;
         }
         }
     }
     }

+ 23 - 18
applications/plugins/picopass/scenes/picopass_scene_read_card_success.c

@@ -15,6 +15,7 @@ void picopass_scene_read_card_success_widget_callback(
 
 
 void picopass_scene_read_card_success_on_enter(void* context) {
 void picopass_scene_read_card_success_on_enter(void* context) {
     Picopass* picopass = context;
     Picopass* picopass = context;
+
     FuriString* csn_str = furi_string_alloc_set("CSN:");
     FuriString* csn_str = furi_string_alloc_set("CSN:");
     FuriString* credential_str = furi_string_alloc();
     FuriString* credential_str = furi_string_alloc();
     FuriString* wiegand_str = furi_string_alloc();
     FuriString* wiegand_str = furi_string_alloc();
@@ -30,27 +31,31 @@ void picopass_scene_read_card_success_on_enter(void* context) {
     PicopassPacs* pacs = &picopass->dev->dev_data.pacs;
     PicopassPacs* pacs = &picopass->dev->dev_data.pacs;
     Widget* widget = picopass->widget;
     Widget* widget = picopass->widget;
 
 
-    uint8_t csn[PICOPASS_BLOCK_LEN];
-    memcpy(csn, &AA1->data[PICOPASS_CSN_BLOCK_INDEX], PICOPASS_BLOCK_LEN);
+    uint8_t csn[PICOPASS_BLOCK_LEN] = {0};
+    memcpy(csn, AA1[PICOPASS_CSN_BLOCK_INDEX].data, PICOPASS_BLOCK_LEN);
     for(uint8_t i = 0; i < PICOPASS_BLOCK_LEN; i++) {
     for(uint8_t i = 0; i < PICOPASS_BLOCK_LEN; i++) {
         furi_string_cat_printf(csn_str, "%02X ", csn[i]);
         furi_string_cat_printf(csn_str, "%02X ", csn[i]);
     }
     }
 
 
-    // Neither of these are valid.  Indicates the block was all 0x00 or all 0xff
-    if(pacs->record.bitLength == 0 || pacs->record.bitLength == 255) {
+    bool no_key = picopass_is_memset(pacs->key, 0x00, PICOPASS_BLOCK_LEN);
+    bool empty =
+        picopass_is_memset(AA1[PICOPASS_PACS_CFG_BLOCK_INDEX].data, 0xFF, PICOPASS_BLOCK_LEN);
+
+    if(no_key) {
         furi_string_cat_printf(wiegand_str, "Read Failed");
         furi_string_cat_printf(wiegand_str, "Read Failed");
 
 
         if(pacs->se_enabled) {
         if(pacs->se_enabled) {
             furi_string_cat_printf(credential_str, "SE enabled");
             furi_string_cat_printf(credential_str, "SE enabled");
         }
         }
+    } else if(empty) {
+        furi_string_cat_printf(wiegand_str, "Empty");
+    } else if(pacs->record.bitLength == 0 || pacs->record.bitLength == 255) {
+        // Neither of these are valid.  Indicates the block was all 0x00 or all 0xff
+        furi_string_cat_printf(wiegand_str, "Invalid PACS");
 
 
-        widget_add_button_element(
-            widget,
-            GuiButtonTypeLeft,
-            "Retry",
-            picopass_scene_read_card_success_widget_callback,
-            picopass);
-
+        if(pacs->se_enabled) {
+            furi_string_cat_printf(credential_str, "SE enabled");
+        }
     } else {
     } else {
         size_t bytesLength = 1 + pacs->record.bitLength / 8;
         size_t bytesLength = 1 + pacs->record.bitLength / 8;
         furi_string_set(credential_str, "");
         furi_string_set(credential_str, "");
@@ -82,13 +87,6 @@ void picopass_scene_read_card_success_on_enter(void* context) {
             }
             }
         }
         }
 
 
-        widget_add_button_element(
-            widget,
-            GuiButtonTypeLeft,
-            "Retry",
-            picopass_scene_read_card_success_widget_callback,
-            picopass);
-
         widget_add_button_element(
         widget_add_button_element(
             widget,
             widget,
             GuiButtonTypeRight,
             GuiButtonTypeRight,
@@ -97,6 +95,13 @@ void picopass_scene_read_card_success_on_enter(void* context) {
             picopass);
             picopass);
     }
     }
 
 
+    widget_add_button_element(
+        widget,
+        GuiButtonTypeLeft,
+        "Retry",
+        picopass_scene_read_card_success_widget_callback,
+        picopass);
+
     widget_add_string_element(
     widget_add_string_element(
         widget, 64, 5, AlignCenter, AlignCenter, FontSecondary, furi_string_get_cstr(csn_str));
         widget, 64, 5, AlignCenter, AlignCenter, FontSecondary, furi_string_get_cstr(csn_str));
     widget_add_string_element(
     widget_add_string_element(

+ 78 - 0
applications/plugins/picopass/scenes/picopass_scene_read_factory_success.c

@@ -0,0 +1,78 @@
+#include "../picopass_i.h"
+#include <dolphin/dolphin.h>
+
+void picopass_scene_read_factory_success_widget_callback(
+    GuiButtonType result,
+    InputType type,
+    void* context) {
+    furi_assert(context);
+    Picopass* picopass = context;
+
+    if(type == InputTypeShort) {
+        view_dispatcher_send_custom_event(picopass->view_dispatcher, result);
+    }
+}
+
+void picopass_scene_read_factory_success_on_enter(void* context) {
+    Picopass* picopass = context;
+    FuriString* title = furi_string_alloc_set("Factory Default");
+    FuriString* subtitle = furi_string_alloc_set("");
+
+    DOLPHIN_DEED(DolphinDeedNfcReadSuccess);
+
+    // Send notification
+    notification_message(picopass->notifications, &sequence_success);
+
+    // Setup view
+    Widget* widget = picopass->widget;
+    //PicopassPacs* pacs = &picopass->dev->dev_data.pacs;
+    PicopassBlock* AA1 = picopass->dev->dev_data.AA1;
+
+    uint8_t* configBlock = AA1[PICOPASS_CONFIG_BLOCK_INDEX].data;
+    uint8_t fuses = configBlock[7];
+
+    if((fuses & 0x80) == 0x80) {
+        furi_string_cat_printf(subtitle, "Personalization mode");
+    } else {
+        furi_string_cat_printf(subtitle, "Application mode");
+    }
+
+    widget_add_button_element(
+        widget,
+        GuiButtonTypeCenter,
+        "Write Standard iClass Key",
+        picopass_scene_read_factory_success_widget_callback,
+        picopass);
+
+    widget_add_string_element(
+        widget, 64, 5, AlignCenter, AlignCenter, FontSecondary, furi_string_get_cstr(title));
+    widget_add_string_element(
+        widget, 64, 20, AlignCenter, AlignCenter, FontPrimary, furi_string_get_cstr(subtitle));
+
+    furi_string_free(title);
+    furi_string_free(subtitle);
+
+    view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewWidget);
+}
+
+bool picopass_scene_read_factory_success_on_event(void* context, SceneManagerEvent event) {
+    Picopass* picopass = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == GuiButtonTypeLeft) {
+            consumed = scene_manager_previous_scene(picopass->scene_manager);
+        } else if(event.event == GuiButtonTypeCenter) {
+            scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteKey);
+            consumed = true;
+        }
+    }
+    return consumed;
+}
+
+void picopass_scene_read_factory_success_on_exit(void* context) {
+    Picopass* picopass = context;
+
+    // Clear view
+    widget_reset(picopass->widget);
+}

+ 1 - 3
applications/plugins/picopass/scenes/picopass_scene_save_name.c

@@ -31,12 +31,10 @@ void picopass_scene_save_name_on_enter(void* context) {
         dev_name_empty);
         dev_name_empty);
 
 
     FuriString* folder_path;
     FuriString* folder_path;
-    folder_path = furi_string_alloc();
+    folder_path = furi_string_alloc_set(STORAGE_APP_DATA_PATH_PREFIX);
 
 
     if(furi_string_end_with(picopass->dev->load_path, PICOPASS_APP_EXTENSION)) {
     if(furi_string_end_with(picopass->dev->load_path, PICOPASS_APP_EXTENSION)) {
         path_extract_dirname(furi_string_get_cstr(picopass->dev->load_path), folder_path);
         path_extract_dirname(furi_string_get_cstr(picopass->dev->load_path), folder_path);
-    } else {
-        furi_string_set(folder_path, PICOPASS_APP_FOLDER);
     }
     }
 
 
     ValidatorIsFile* validator_is_file = validator_is_file_alloc_init(
     ValidatorIsFile* validator_is_file = validator_is_file_alloc_init(

+ 13 - 0
applications/plugins/picopass/scenes/picopass_scene_write_card_success.c

@@ -16,6 +16,7 @@ void picopass_scene_write_card_success_widget_callback(
 void picopass_scene_write_card_success_on_enter(void* context) {
 void picopass_scene_write_card_success_on_enter(void* context) {
     Picopass* picopass = context;
     Picopass* picopass = context;
     Widget* widget = picopass->widget;
     Widget* widget = picopass->widget;
+    FuriString* str = furi_string_alloc_set("Write Success!");
 
 
     DOLPHIN_DEED(DolphinDeedNfcReadSuccess);
     DOLPHIN_DEED(DolphinDeedNfcReadSuccess);
 
 
@@ -29,6 +30,18 @@ void picopass_scene_write_card_success_on_enter(void* context) {
         picopass_scene_write_card_success_widget_callback,
         picopass_scene_write_card_success_widget_callback,
         picopass);
         picopass);
 
 
+    widget_add_button_element(
+        widget,
+        GuiButtonTypeRight,
+        "Menu",
+        picopass_scene_write_card_success_widget_callback,
+        picopass);
+
+    widget_add_string_element(
+        widget, 64, 5, AlignCenter, AlignCenter, FontSecondary, furi_string_get_cstr(str));
+
+    furi_string_free(str);
+
     view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewWidget);
     view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewWidget);
 }
 }
 
 

+ 53 - 0
applications/plugins/picopass/scenes/picopass_scene_write_key.c

@@ -0,0 +1,53 @@
+#include "../picopass_i.h"
+#include <dolphin/dolphin.h>
+
+void picopass_write_key_worker_callback(PicopassWorkerEvent event, void* context) {
+    UNUSED(event);
+    Picopass* picopass = context;
+    view_dispatcher_send_custom_event(picopass->view_dispatcher, PicopassCustomEventWorkerExit);
+}
+
+void picopass_scene_write_key_on_enter(void* context) {
+    Picopass* picopass = context;
+    DOLPHIN_DEED(DolphinDeedNfcSave);
+
+    // Setup view
+    Popup* popup = picopass->popup;
+    popup_set_header(popup, "Writing\niClass\nkey", 68, 30, AlignLeft, AlignTop);
+    popup_set_icon(popup, 0, 3, &I_RFIDDolphinSend_97x61);
+
+    // Start worker
+    view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewPopup);
+    picopass_worker_start(
+        picopass->worker,
+        PicopassWorkerStateWriteStandardKey,
+        &picopass->dev->dev_data,
+        picopass_write_key_worker_callback,
+        picopass);
+
+    picopass_blink_start(picopass);
+}
+
+bool picopass_scene_write_key_on_event(void* context, SceneManagerEvent event) {
+    Picopass* picopass = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == PicopassCustomEventWorkerExit) {
+            scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteCardSuccess);
+            consumed = true;
+        }
+    }
+    return consumed;
+}
+
+void picopass_scene_write_key_on_exit(void* context) {
+    Picopass* picopass = context;
+
+    // Stop worker
+    picopass_worker_stop(picopass->worker);
+    // Clear view
+    popup_reset(picopass->popup);
+
+    picopass_blink_stop(picopass);
+}

+ 1 - 1
applications/plugins/signal_generator/application.fam

@@ -8,6 +8,6 @@ App(
     stack_size=1 * 1024,
     stack_size=1 * 1024,
     order=50,
     order=50,
     fap_icon="signal_gen_10px.png",
     fap_icon="signal_gen_10px.png",
-    fap_category="Tools",
+    fap_category="GPIO",
     fap_icon_assets="icons",
     fap_icon_assets="icons",
 )
 )

+ 1 - 1
applications/plugins/spi_mem_manager/application.fam

@@ -7,7 +7,7 @@ App(
     stack_size=1 * 2048,
     stack_size=1 * 2048,
     order=30,
     order=30,
     fap_icon="images/Dip8_10px.png",
     fap_icon="images/Dip8_10px.png",
-    fap_category="Tools",
+    fap_category="GPIO",
     fap_icon_assets="images",
     fap_icon_assets="images",
     fap_private_libs=[
     fap_private_libs=[
         Lib(
         Lib(

+ 1 - 1
applications/plugins/spi_mem_manager/scenes/spi_mem_scene_start.c

@@ -60,7 +60,7 @@ bool spi_mem_scene_start_on_event(void* context, SceneManagerEvent event) {
             scene_manager_next_scene(app->scene_manager, SPIMemSceneChipDetect);
             scene_manager_next_scene(app->scene_manager, SPIMemSceneChipDetect);
             success = true;
             success = true;
         } else if(event.event == SPIMemSceneStartSubmenuIndexSaved) {
         } else if(event.event == SPIMemSceneStartSubmenuIndexSaved) {
-            furi_string_set(app->file_path, SPI_MEM_FILE_FOLDER);
+            furi_string_set(app->file_path, STORAGE_APP_DATA_PATH_PREFIX);
             scene_manager_next_scene(app->scene_manager, SPIMemSceneSelectFile);
             scene_manager_next_scene(app->scene_manager, SPIMemSceneSelectFile);
             success = true;
             success = true;
         } else if(event.event == SPIMemSceneStartSubmenuIndexErase) {
         } else if(event.event == SPIMemSceneStartSubmenuIndexErase) {

+ 5 - 5
applications/plugins/spi_mem_manager/spi_mem_app.c

@@ -16,9 +16,9 @@ static bool spi_mem_back_event_callback(void* context) {
 }
 }
 
 
 SPIMemApp* spi_mem_alloc(void) {
 SPIMemApp* spi_mem_alloc(void) {
-    SPIMemApp* instance = malloc(sizeof(SPIMemApp));
+    SPIMemApp* instance = malloc(sizeof(SPIMemApp)); //-V799
 
 
-    instance->file_path = furi_string_alloc();
+    instance->file_path = furi_string_alloc_set(STORAGE_APP_DATA_PATH_PREFIX);
     instance->gui = furi_record_open(RECORD_GUI);
     instance->gui = furi_record_open(RECORD_GUI);
     instance->notifications = furi_record_open(RECORD_NOTIFICATION);
     instance->notifications = furi_record_open(RECORD_NOTIFICATION);
     instance->view_dispatcher = view_dispatcher_alloc();
     instance->view_dispatcher = view_dispatcher_alloc();
@@ -37,7 +37,8 @@ SPIMemApp* spi_mem_alloc(void) {
     instance->text_input = text_input_alloc();
     instance->text_input = text_input_alloc();
     instance->mode = SPIMemModeUnknown;
     instance->mode = SPIMemModeUnknown;
 
 
-    furi_string_set(instance->file_path, SPI_MEM_FILE_FOLDER);
+    // Migrate data from old sd-card folder
+    storage_common_migrate(instance->storage, EXT_PATH("spimem"), STORAGE_APP_DATA_PATH_PREFIX);
 
 
     view_dispatcher_enable_queue(instance->view_dispatcher);
     view_dispatcher_enable_queue(instance->view_dispatcher);
     view_dispatcher_set_event_callback_context(instance->view_dispatcher, instance);
     view_dispatcher_set_event_callback_context(instance->view_dispatcher, instance);
@@ -70,7 +71,7 @@ SPIMemApp* spi_mem_alloc(void) {
     furi_hal_spi_bus_handle_init(&furi_hal_spi_bus_handle_external);
     furi_hal_spi_bus_handle_init(&furi_hal_spi_bus_handle_external);
     scene_manager_next_scene(instance->scene_manager, SPIMemSceneStart);
     scene_manager_next_scene(instance->scene_manager, SPIMemSceneStart);
     return instance;
     return instance;
-}
+} //-V773
 
 
 void spi_mem_free(SPIMemApp* instance) {
 void spi_mem_free(SPIMemApp* instance) {
     view_dispatcher_remove_view(instance->view_dispatcher, SPIMemViewSubmenu);
     view_dispatcher_remove_view(instance->view_dispatcher, SPIMemViewSubmenu);
@@ -105,7 +106,6 @@ void spi_mem_free(SPIMemApp* instance) {
 int32_t spi_mem_app(void* p) {
 int32_t spi_mem_app(void* p) {
     UNUSED(p);
     UNUSED(p);
     SPIMemApp* instance = spi_mem_alloc();
     SPIMemApp* instance = spi_mem_alloc();
-    spi_mem_file_create_folder(instance);
     view_dispatcher_run(instance->view_dispatcher);
     view_dispatcher_run(instance->view_dispatcher);
     spi_mem_free(instance);
     spi_mem_free(instance);
     return 0;
     return 0;

+ 0 - 1
applications/plugins/spi_mem_manager/spi_mem_app_i.h

@@ -24,7 +24,6 @@
 
 
 #define TAG "SPIMem"
 #define TAG "SPIMem"
 #define SPI_MEM_FILE_EXTENSION ".bin"
 #define SPI_MEM_FILE_EXTENSION ".bin"
-#define SPI_MEM_FILE_FOLDER EXT_PATH("spimem")
 #define SPI_MEM_FILE_NAME_SIZE 100
 #define SPI_MEM_FILE_NAME_SIZE 100
 #define SPI_MEM_TEXT_BUFFER_SIZE 128
 #define SPI_MEM_TEXT_BUFFER_SIZE 128
 
 

+ 1 - 7
applications/plugins/spi_mem_manager/spi_mem_files.c

@@ -1,11 +1,5 @@
 #include "spi_mem_app_i.h"
 #include "spi_mem_app_i.h"
 
 
-void spi_mem_file_create_folder(SPIMemApp* app) {
-    if(!storage_simply_mkdir(app->storage, SPI_MEM_FILE_FOLDER)) {
-        dialog_message_show_storage_error(app->dialogs, "Cannot create\napp folder");
-    }
-}
-
 bool spi_mem_file_delete(SPIMemApp* app) {
 bool spi_mem_file_delete(SPIMemApp* app) {
     return (storage_simply_remove(app->storage, furi_string_get_cstr(app->file_path)));
     return (storage_simply_remove(app->storage, furi_string_get_cstr(app->file_path)));
 }
 }
@@ -13,7 +7,7 @@ bool spi_mem_file_delete(SPIMemApp* app) {
 bool spi_mem_file_select(SPIMemApp* app) {
 bool spi_mem_file_select(SPIMemApp* app) {
     DialogsFileBrowserOptions browser_options;
     DialogsFileBrowserOptions browser_options;
     dialog_file_browser_set_basic_options(&browser_options, SPI_MEM_FILE_EXTENSION, &I_Dip8_10px);
     dialog_file_browser_set_basic_options(&browser_options, SPI_MEM_FILE_EXTENSION, &I_Dip8_10px);
-    browser_options.base_path = SPI_MEM_FILE_FOLDER;
+    browser_options.base_path = STORAGE_APP_DATA_PATH_PREFIX;
     bool success =
     bool success =
         dialog_file_browser_show(app->dialogs, app->file_path, app->file_path, &browser_options);
         dialog_file_browser_show(app->dialogs, app->file_path, app->file_path, &browser_options);
     return success;
     return success;

+ 0 - 1
applications/plugins/spi_mem_manager/spi_mem_files.h

@@ -1,7 +1,6 @@
 #pragma once
 #pragma once
 #include "spi_mem_app.h"
 #include "spi_mem_app.h"
 
 
-void spi_mem_file_create_folder(SPIMemApp* app);
 bool spi_mem_file_select(SPIMemApp* app);
 bool spi_mem_file_select(SPIMemApp* app);
 bool spi_mem_file_create(SPIMemApp* app, const char* file_name);
 bool spi_mem_file_create(SPIMemApp* app, const char* file_name);
 bool spi_mem_file_delete(SPIMemApp* app);
 bool spi_mem_file_delete(SPIMemApp* app);

+ 1 - 1
applications/plugins/weather_station/application.fam

@@ -9,6 +9,6 @@ App(
     stack_size=4 * 1024,
     stack_size=4 * 1024,
     order=50,
     order=50,
     fap_icon="weather_station_10px.png",
     fap_icon="weather_station_10px.png",
-    fap_category="Tools",
+    fap_category="Sub-GHz",
     fap_icon_assets="images",
     fap_icon_assets="images",
 )
 )

+ 1 - 1
applications/plugins/weather_station/helpers/weather_station_types.h

@@ -3,7 +3,7 @@
 #include <furi.h>
 #include <furi.h>
 #include <furi_hal.h>
 #include <furi_hal.h>
 
 
-#define WS_VERSION_APP "0.7"
+#define WS_VERSION_APP "0.8"
 #define WS_DEVELOPED "SkorP"
 #define WS_DEVELOPED "SkorP"
 #define WS_GITHUB "https://github.com/flipperdevices/flipperzero-firmware"
 #define WS_GITHUB "https://github.com/flipperdevices/flipperzero-firmware"
 
 

+ 7 - 15
applications/plugins/weather_station/protocols/acurite_592txr.c

@@ -258,7 +258,7 @@ uint8_t ws_protocol_decoder_acurite_592txr_get_hash_data(void* context) {
         &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
         &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
 }
 }
 
 
-bool ws_protocol_decoder_acurite_592txr_serialize(
+SubGhzProtocolStatus ws_protocol_decoder_acurite_592txr_serialize(
     void* context,
     void* context,
     FlipperFormat* flipper_format,
     FlipperFormat* flipper_format,
     SubGhzRadioPreset* preset) {
     SubGhzRadioPreset* preset) {
@@ -267,22 +267,14 @@ bool ws_protocol_decoder_acurite_592txr_serialize(
     return ws_block_generic_serialize(&instance->generic, flipper_format, preset);
     return ws_block_generic_serialize(&instance->generic, flipper_format, preset);
 }
 }
 
 
-bool ws_protocol_decoder_acurite_592txr_deserialize(void* context, FlipperFormat* flipper_format) {
+SubGhzProtocolStatus
+    ws_protocol_decoder_acurite_592txr_deserialize(void* context, FlipperFormat* flipper_format) {
     furi_assert(context);
     furi_assert(context);
     WSProtocolDecoderAcurite_592TXR* instance = context;
     WSProtocolDecoderAcurite_592TXR* instance = context;
-    bool ret = false;
-    do {
-        if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) {
-            break;
-        }
-        if(instance->generic.data_count_bit !=
-           ws_protocol_acurite_592txr_const.min_count_bit_for_found) {
-            FURI_LOG_E(TAG, "Wrong number of bits in key");
-            break;
-        }
-        ret = true;
-    } while(false);
-    return ret;
+    return ws_block_generic_deserialize_check_count_bit(
+        &instance->generic,
+        flipper_format,
+        ws_protocol_acurite_592txr_const.min_count_bit_for_found);
 }
 }
 
 
 void ws_protocol_decoder_acurite_592txr_get_string(void* context, FuriString* output) {
 void ws_protocol_decoder_acurite_592txr_get_string(void* context, FuriString* output) {

+ 5 - 4
applications/plugins/weather_station/protocols/acurite_592txr.h

@@ -56,9 +56,9 @@ uint8_t ws_protocol_decoder_acurite_592txr_get_hash_data(void* context);
  * @param context Pointer to a WSProtocolDecoderAcurite_592TXR instance
  * @param context Pointer to a WSProtocolDecoderAcurite_592TXR instance
  * @param flipper_format Pointer to a FlipperFormat instance
  * @param flipper_format Pointer to a FlipperFormat instance
  * @param preset The modulation on which the signal was received, SubGhzRadioPreset
  * @param preset The modulation on which the signal was received, SubGhzRadioPreset
- * @return true On success
+ * @return status
  */
  */
-bool ws_protocol_decoder_acurite_592txr_serialize(
+SubGhzProtocolStatus ws_protocol_decoder_acurite_592txr_serialize(
     void* context,
     void* context,
     FlipperFormat* flipper_format,
     FlipperFormat* flipper_format,
     SubGhzRadioPreset* preset);
     SubGhzRadioPreset* preset);
@@ -67,9 +67,10 @@ bool ws_protocol_decoder_acurite_592txr_serialize(
  * Deserialize data WSProtocolDecoderAcurite_592TXR.
  * Deserialize data WSProtocolDecoderAcurite_592TXR.
  * @param context Pointer to a WSProtocolDecoderAcurite_592TXR instance
  * @param context Pointer to a WSProtocolDecoderAcurite_592TXR instance
  * @param flipper_format Pointer to a FlipperFormat instance
  * @param flipper_format Pointer to a FlipperFormat instance
- * @return true On success
+ * @return status
  */
  */
-bool ws_protocol_decoder_acurite_592txr_deserialize(void* context, FlipperFormat* flipper_format);
+SubGhzProtocolStatus
+    ws_protocol_decoder_acurite_592txr_deserialize(void* context, FlipperFormat* flipper_format);
 
 
 /**
 /**
  * Getting a textual representation of the received data.
  * Getting a textual representation of the received data.

+ 7 - 15
applications/plugins/weather_station/protocols/acurite_606tx.c

@@ -199,7 +199,7 @@ uint8_t ws_protocol_decoder_acurite_606tx_get_hash_data(void* context) {
         &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
         &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
 }
 }
 
 
-bool ws_protocol_decoder_acurite_606tx_serialize(
+SubGhzProtocolStatus ws_protocol_decoder_acurite_606tx_serialize(
     void* context,
     void* context,
     FlipperFormat* flipper_format,
     FlipperFormat* flipper_format,
     SubGhzRadioPreset* preset) {
     SubGhzRadioPreset* preset) {
@@ -208,22 +208,14 @@ bool ws_protocol_decoder_acurite_606tx_serialize(
     return ws_block_generic_serialize(&instance->generic, flipper_format, preset);
     return ws_block_generic_serialize(&instance->generic, flipper_format, preset);
 }
 }
 
 
-bool ws_protocol_decoder_acurite_606tx_deserialize(void* context, FlipperFormat* flipper_format) {
+SubGhzProtocolStatus
+    ws_protocol_decoder_acurite_606tx_deserialize(void* context, FlipperFormat* flipper_format) {
     furi_assert(context);
     furi_assert(context);
     WSProtocolDecoderAcurite_606TX* instance = context;
     WSProtocolDecoderAcurite_606TX* instance = context;
-    bool ret = false;
-    do {
-        if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) {
-            break;
-        }
-        if(instance->generic.data_count_bit !=
-           ws_protocol_acurite_606tx_const.min_count_bit_for_found) {
-            FURI_LOG_E(TAG, "Wrong number of bits in key");
-            break;
-        }
-        ret = true;
-    } while(false);
-    return ret;
+    return ws_block_generic_deserialize_check_count_bit(
+        &instance->generic,
+        flipper_format,
+        ws_protocol_acurite_606tx_const.min_count_bit_for_found);
 }
 }
 
 
 void ws_protocol_decoder_acurite_606tx_get_string(void* context, FuriString* output) {
 void ws_protocol_decoder_acurite_606tx_get_string(void* context, FuriString* output) {

+ 5 - 4
applications/plugins/weather_station/protocols/acurite_606tx.h

@@ -56,9 +56,9 @@ uint8_t ws_protocol_decoder_acurite_606tx_get_hash_data(void* context);
  * @param context Pointer to a WSProtocolDecoderAcurite_606TX instance
  * @param context Pointer to a WSProtocolDecoderAcurite_606TX instance
  * @param flipper_format Pointer to a FlipperFormat instance
  * @param flipper_format Pointer to a FlipperFormat instance
  * @param preset The modulation on which the signal was received, SubGhzRadioPreset
  * @param preset The modulation on which the signal was received, SubGhzRadioPreset
- * @return true On success
+ * @return status
  */
  */
-bool ws_protocol_decoder_acurite_606tx_serialize(
+SubGhzProtocolStatus ws_protocol_decoder_acurite_606tx_serialize(
     void* context,
     void* context,
     FlipperFormat* flipper_format,
     FlipperFormat* flipper_format,
     SubGhzRadioPreset* preset);
     SubGhzRadioPreset* preset);
@@ -67,9 +67,10 @@ bool ws_protocol_decoder_acurite_606tx_serialize(
  * Deserialize data WSProtocolDecoderAcurite_606TX.
  * Deserialize data WSProtocolDecoderAcurite_606TX.
  * @param context Pointer to a WSProtocolDecoderAcurite_606TX instance
  * @param context Pointer to a WSProtocolDecoderAcurite_606TX instance
  * @param flipper_format Pointer to a FlipperFormat instance
  * @param flipper_format Pointer to a FlipperFormat instance
- * @return true On success
+ * @return status
  */
  */
-bool ws_protocol_decoder_acurite_606tx_deserialize(void* context, FlipperFormat* flipper_format);
+SubGhzProtocolStatus
+    ws_protocol_decoder_acurite_606tx_deserialize(void* context, FlipperFormat* flipper_format);
 
 
 /**
 /**
  * Getting a textual representation of the received data.
  * Getting a textual representation of the received data.

+ 7 - 15
applications/plugins/weather_station/protocols/acurite_609txc.c

@@ -199,7 +199,7 @@ uint8_t ws_protocol_decoder_acurite_609txc_get_hash_data(void* context) {
         &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
         &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
 }
 }
 
 
-bool ws_protocol_decoder_acurite_609txc_serialize(
+SubGhzProtocolStatus ws_protocol_decoder_acurite_609txc_serialize(
     void* context,
     void* context,
     FlipperFormat* flipper_format,
     FlipperFormat* flipper_format,
     SubGhzRadioPreset* preset) {
     SubGhzRadioPreset* preset) {
@@ -208,22 +208,14 @@ bool ws_protocol_decoder_acurite_609txc_serialize(
     return ws_block_generic_serialize(&instance->generic, flipper_format, preset);
     return ws_block_generic_serialize(&instance->generic, flipper_format, preset);
 }
 }
 
 
-bool ws_protocol_decoder_acurite_609txc_deserialize(void* context, FlipperFormat* flipper_format) {
+SubGhzProtocolStatus
+    ws_protocol_decoder_acurite_609txc_deserialize(void* context, FlipperFormat* flipper_format) {
     furi_assert(context);
     furi_assert(context);
     WSProtocolDecoderAcurite_609TXC* instance = context;
     WSProtocolDecoderAcurite_609TXC* instance = context;
-    bool ret = false;
-    do {
-        if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) {
-            break;
-        }
-        if(instance->generic.data_count_bit !=
-           ws_protocol_acurite_609txc_const.min_count_bit_for_found) {
-            FURI_LOG_E(TAG, "Wrong number of bits in key");
-            break;
-        }
-        ret = true;
-    } while(false);
-    return ret;
+    return ws_block_generic_deserialize_check_count_bit(
+        &instance->generic,
+        flipper_format,
+        ws_protocol_acurite_609txc_const.min_count_bit_for_found);
 }
 }
 
 
 void ws_protocol_decoder_acurite_609txc_get_string(void* context, FuriString* output) {
 void ws_protocol_decoder_acurite_609txc_get_string(void* context, FuriString* output) {

+ 5 - 4
applications/plugins/weather_station/protocols/acurite_609txc.h

@@ -56,9 +56,9 @@ uint8_t ws_protocol_decoder_acurite_609txc_get_hash_data(void* context);
  * @param context Pointer to a WSProtocolDecoderAcurite_609TXC instance
  * @param context Pointer to a WSProtocolDecoderAcurite_609TXC instance
  * @param flipper_format Pointer to a FlipperFormat instance
  * @param flipper_format Pointer to a FlipperFormat instance
  * @param preset The modulation on which the signal was received, SubGhzRadioPreset
  * @param preset The modulation on which the signal was received, SubGhzRadioPreset
- * @return true On success
+ * @return status
  */
  */
-bool ws_protocol_decoder_acurite_609txc_serialize(
+SubGhzProtocolStatus ws_protocol_decoder_acurite_609txc_serialize(
     void* context,
     void* context,
     FlipperFormat* flipper_format,
     FlipperFormat* flipper_format,
     SubGhzRadioPreset* preset);
     SubGhzRadioPreset* preset);
@@ -67,9 +67,10 @@ bool ws_protocol_decoder_acurite_609txc_serialize(
  * Deserialize data WSProtocolDecoderAcurite_609TXC.
  * Deserialize data WSProtocolDecoderAcurite_609TXC.
  * @param context Pointer to a WSProtocolDecoderAcurite_609TXC instance
  * @param context Pointer to a WSProtocolDecoderAcurite_609TXC instance
  * @param flipper_format Pointer to a FlipperFormat instance
  * @param flipper_format Pointer to a FlipperFormat instance
- * @return true On success
+ * @return status
  */
  */
-bool ws_protocol_decoder_acurite_609txc_deserialize(void* context, FlipperFormat* flipper_format);
+SubGhzProtocolStatus
+    ws_protocol_decoder_acurite_609txc_deserialize(void* context, FlipperFormat* flipper_format);
 
 
 /**
 /**
  * Getting a textual representation of the received data.
  * Getting a textual representation of the received data.

+ 7 - 15
applications/plugins/weather_station/protocols/ambient_weather.c

@@ -228,7 +228,7 @@ uint8_t ws_protocol_decoder_ambient_weather_get_hash_data(void* context) {
         &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
         &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
 }
 }
 
 
-bool ws_protocol_decoder_ambient_weather_serialize(
+SubGhzProtocolStatus ws_protocol_decoder_ambient_weather_serialize(
     void* context,
     void* context,
     FlipperFormat* flipper_format,
     FlipperFormat* flipper_format,
     SubGhzRadioPreset* preset) {
     SubGhzRadioPreset* preset) {
@@ -237,22 +237,14 @@ bool ws_protocol_decoder_ambient_weather_serialize(
     return ws_block_generic_serialize(&instance->generic, flipper_format, preset);
     return ws_block_generic_serialize(&instance->generic, flipper_format, preset);
 }
 }
 
 
-bool ws_protocol_decoder_ambient_weather_deserialize(void* context, FlipperFormat* flipper_format) {
+SubGhzProtocolStatus
+    ws_protocol_decoder_ambient_weather_deserialize(void* context, FlipperFormat* flipper_format) {
     furi_assert(context);
     furi_assert(context);
     WSProtocolDecoderAmbient_Weather* instance = context;
     WSProtocolDecoderAmbient_Weather* instance = context;
-    bool ret = false;
-    do {
-        if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) {
-            break;
-        }
-        if(instance->generic.data_count_bit !=
-           ws_protocol_ambient_weather_const.min_count_bit_for_found) {
-            FURI_LOG_E(TAG, "Wrong number of bits in key");
-            break;
-        }
-        ret = true;
-    } while(false);
-    return ret;
+    return ws_block_generic_deserialize_check_count_bit(
+        &instance->generic,
+        flipper_format,
+        ws_protocol_ambient_weather_const.min_count_bit_for_found);
 }
 }
 
 
 void ws_protocol_decoder_ambient_weather_get_string(void* context, FuriString* output) {
 void ws_protocol_decoder_ambient_weather_get_string(void* context, FuriString* output) {

+ 5 - 4
applications/plugins/weather_station/protocols/ambient_weather.h

@@ -56,9 +56,9 @@ uint8_t ws_protocol_decoder_ambient_weather_get_hash_data(void* context);
  * @param context Pointer to a WSProtocolDecoderAmbient_Weather instance
  * @param context Pointer to a WSProtocolDecoderAmbient_Weather instance
  * @param flipper_format Pointer to a FlipperFormat instance
  * @param flipper_format Pointer to a FlipperFormat instance
  * @param preset The modulation on which the signal was received, SubGhzRadioPreset
  * @param preset The modulation on which the signal was received, SubGhzRadioPreset
- * @return true On success
+ * @return status
  */
  */
-bool ws_protocol_decoder_ambient_weather_serialize(
+SubGhzProtocolStatus ws_protocol_decoder_ambient_weather_serialize(
     void* context,
     void* context,
     FlipperFormat* flipper_format,
     FlipperFormat* flipper_format,
     SubGhzRadioPreset* preset);
     SubGhzRadioPreset* preset);
@@ -67,9 +67,10 @@ bool ws_protocol_decoder_ambient_weather_serialize(
  * Deserialize data WSProtocolDecoderAmbient_Weather.
  * Deserialize data WSProtocolDecoderAmbient_Weather.
  * @param context Pointer to a WSProtocolDecoderAmbient_Weather instance
  * @param context Pointer to a WSProtocolDecoderAmbient_Weather instance
  * @param flipper_format Pointer to a FlipperFormat instance
  * @param flipper_format Pointer to a FlipperFormat instance
- * @return true On success
+ * @return status
  */
  */
-bool ws_protocol_decoder_ambient_weather_deserialize(void* context, FlipperFormat* flipper_format);
+SubGhzProtocolStatus
+    ws_protocol_decoder_ambient_weather_deserialize(void* context, FlipperFormat* flipper_format);
 
 
 /**
 /**
  * Getting a textual representation of the received data.
  * Getting a textual representation of the received data.

+ 5 - 15
applications/plugins/weather_station/protocols/auriol_hg0601a.c

@@ -210,7 +210,7 @@ uint8_t ws_protocol_decoder_auriol_th_get_hash_data(void* context) {
         &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
         &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
 }
 }
 
 
-bool ws_protocol_decoder_auriol_th_serialize(
+SubGhzProtocolStatus ws_protocol_decoder_auriol_th_serialize(
     void* context,
     void* context,
     FlipperFormat* flipper_format,
     FlipperFormat* flipper_format,
     SubGhzRadioPreset* preset) {
     SubGhzRadioPreset* preset) {
@@ -219,22 +219,12 @@ bool ws_protocol_decoder_auriol_th_serialize(
     return ws_block_generic_serialize(&instance->generic, flipper_format, preset);
     return ws_block_generic_serialize(&instance->generic, flipper_format, preset);
 }
 }
 
 
-bool ws_protocol_decoder_auriol_th_deserialize(void* context, FlipperFormat* flipper_format) {
+SubGhzProtocolStatus
+    ws_protocol_decoder_auriol_th_deserialize(void* context, FlipperFormat* flipper_format) {
     furi_assert(context);
     furi_assert(context);
     WSProtocolDecoderAuriol_TH* instance = context;
     WSProtocolDecoderAuriol_TH* instance = context;
-    bool ret = false;
-    do {
-        if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) {
-            break;
-        }
-        if(instance->generic.data_count_bit !=
-           ws_protocol_auriol_th_const.min_count_bit_for_found) {
-            FURI_LOG_E(TAG, "Wrong number of bits in key");
-            break;
-        }
-        ret = true;
-    } while(false);
-    return ret;
+    return ws_block_generic_deserialize_check_count_bit(
+        &instance->generic, flipper_format, ws_protocol_auriol_th_const.min_count_bit_for_found);
 }
 }
 
 
 void ws_protocol_decoder_auriol_th_get_string(void* context, FuriString* output) {
 void ws_protocol_decoder_auriol_th_get_string(void* context, FuriString* output) {

Некоторые файлы не были показаны из-за большого количества измененных файлов