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

Merge remote-tracking branch 'mntm/dev' into mntm-pr-43

Willy-JL 10 месяцев назад
Родитель
Сommit
73340ca1be
100 измененных файлов с 2253 добавлено и 660 удалено
  1. 0 32
      .github/workflows/lint.yml
  2. 1 1
      air_arkanoid/.gitsubtree
  3. 5 5
      air_arkanoid/engine/sensors/ICM42688P/ICM42688P.c
  4. 1 1
      air_arkanoid/engine/sensors/ICM42688P/ICM42688P.h
  5. 1 1
      air_labyrinth/.gitsubtree
  6. 5 5
      air_labyrinth/engine/sensors/ICM42688P/ICM42688P.c
  7. 1 1
      air_labyrinth/engine/sensors/ICM42688P/ICM42688P.h
  8. 1 1
      airmouse/.gitsubtree
  9. 1 1
      airmouse/tracking/imu/lsm6ds3trc.c
  10. 1 1
      airmouse/tracking/imu/lsm6dso.c
  11. 1 1
      cli_bridge/.gitsubtree
  12. 1 0
      cli_bridge/application.fam
  13. 11 5
      cli_bridge/cli_control.c
  14. 0 1
      cli_bridge/cligui_main.c
  15. 9 1
      cross_remote/README.md
  16. 1 1
      cross_remote/application.fam
  17. 2 0
      cross_remote/docs/README.md
  18. 10 0
      cross_remote/docs/changelog.md
  19. 0 7
      cross_remote/helpers/subghz/subghz.c
  20. 5 1
      cross_remote/helpers/subghz/subghz_txrx.c
  21. 3 0
      cross_remote/helpers/xremote_custom_event.h
  22. 8 0
      cross_remote/helpers/xremote_storage.c
  23. 2 0
      cross_remote/helpers/xremote_storage.h
  24. 68 45
      cross_remote/scenes/xremote_scene_settings.c
  25. 6 1
      cross_remote/scenes/xremote_scene_transmit.c
  26. 0 1
      cross_remote/scenes/xremote_scene_xr_list.c
  27. 44 39
      cross_remote/xremote.c
  28. 6 1
      cross_remote/xremote.h
  29. 1 1
      cross_remote/xremote_i.h
  30. 1 1
      dtmf_dolphin/.gitsubtree
  31. 7 0
      dtmf_dolphin/LICENSE
  32. 19 1
      dtmf_dolphin/dtmf_dolphin_audio.h
  33. 2 0
      dtmf_dolphin/dtmf_dolphin_hal.h
  34. 1 0
      dtmf_dolphin/dtmf_dolphin_i.h
  35. 2 0
      dtmf_dolphin/views/dtmf_dolphin_dialer.h
  36. 14 0
      eth_troubleshooter/eth_view_process.h
  37. 2 0
      eth_troubleshooter/eth_worker.c
  38. 6 3
      eth_troubleshooter/eth_worker.h
  39. 1 0
      eth_troubleshooter/eth_worker_i.h
  40. 2 2
      eth_troubleshooter/eth_worker_ping.c
  41. 8 0
      eth_troubleshooter/eth_worker_traceroute.c
  42. 114 0
      eth_troubleshooter/lib/ioLibrary_Driver/Internet/ICMP/traceroute.c
  43. 21 0
      eth_troubleshooter/lib/ioLibrary_Driver/Internet/ICMP/traceroute.h
  44. 1 1
      ir_intervalometer/.gitsubtree
  45. 1 1
      key_copier/.gitsubtree
  46. 2 2
      key_copier/application.fam
  47. 2 3
      key_copier/key_copier.c
  48. BIN
      key_copier/screenshots/config.png
  49. BIN
      key_copier/screenshots/main_menu.png
  50. 5 0
      metroflip/CHANGELOG.md
  51. 27 27
      metroflip/README.md
  52. 4 0
      metroflip/api/calypso/transit/ravkav.c
  53. 1 0
      metroflip/api/calypso/transit/ravkav_i.h
  54. 16 0
      metroflip/api/metroflip/metroflip_api.h
  55. 11 1
      metroflip/api/metroflip/metroflip_api_table_i.h
  56. 6 2
      metroflip/api/mosgortrans/mosgortrans_util.c
  57. 0 22
      metroflip/api/mosgortrans/mosgortrans_util.h
  58. 3 3
      metroflip/app/README.md
  59. 10 1
      metroflip/application.fam
  60. BIN
      metroflip/images/DolphinDone_80x58.png
  61. BIN
      metroflip/images/DolphinMafia_119x62.png
  62. BIN
      metroflip/images/WarningDolphinFlip_45x42.png
  63. BIN
      metroflip/images/icon.png
  64. 2 2
      metroflip/manifest.yml
  65. 27 2
      metroflip/metroflip.c
  66. 17 7
      metroflip/metroflip_i.h
  67. 303 0
      metroflip/scenes/desfire.c
  68. 5 4
      metroflip/scenes/desfire.h
  69. 191 94
      metroflip/scenes/keys.c
  70. 3 1
      metroflip/scenes/keys.h
  71. 9 11
      metroflip/scenes/metroflip_scene_auto.c
  72. 4 0
      metroflip/scenes/metroflip_scene_config.h
  73. 1 1
      metroflip/scenes/metroflip_scene_credits.c
  74. 62 0
      metroflip/scenes/metroflip_scene_delete.c
  75. 151 0
      metroflip/scenes/metroflip_scene_load.c
  76. 2 1
      metroflip/scenes/metroflip_scene_parse.c
  77. 55 0
      metroflip/scenes/metroflip_scene_save.c
  78. 69 0
      metroflip/scenes/metroflip_scene_save_result.c
  79. 3 0
      metroflip/scenes/metroflip_scene_start.c
  80. 9 9
      metroflip/scenes/metroflip_scene_supported.c
  81. 44 16
      metroflip/scenes/plugins/bip.c
  82. 52 7
      metroflip/scenes/plugins/calypso.c
  83. 42 10
      metroflip/scenes/plugins/charliecard.c
  84. 48 17
      metroflip/scenes/plugins/clipper.c
  85. 245 0
      metroflip/scenes/plugins/gocard.c
  86. 47 17
      metroflip/scenes/plugins/itso.c
  87. 44 13
      metroflip/scenes/plugins/metromoney.c
  88. 47 17
      metroflip/scenes/plugins/myki.c
  89. 41 18
      metroflip/scenes/plugins/opal.c
  90. 45 17
      metroflip/scenes/plugins/smartrider.c
  91. 43 12
      metroflip/scenes/plugins/troika.c
  92. 1 1
      nfc_playlist/.gitsubtree
  93. 3 2
      nfc_playlist/README.md
  94. 1 1
      nfc_playlist/scenes/nfc_playlist_scene_view_playlist_content.c
  95. 1 1
      nrf24batch/.gitsubtree
  96. 34 34
      nrf24batch/lib/nrf24/nrf24.c
  97. 73 76
      nrf24batch/lib/nrf24/nrf24.h
  98. 1 1
      nrf24channelscanner/.gitsubtree
  99. 9 9
      nrf24channelscanner/lib/nrf24/nrf24.c
  100. 35 35
      nrf24channelscanner/lib/nrf24/nrf24.h

+ 0 - 32
.github/workflows/lint.yml

@@ -1,32 +0,0 @@
-name: "Lint"
-
-on:
-  push:
-  pull_request:
-
-env:
-  SET_GH_OUTPUT: 1
-
-jobs:
-  lint:
-    runs-on: ubuntu-latest
-    steps:
-      - name: "Checkout firmware"
-        uses: actions/checkout@v4
-        with:
-          repository: Next-Flip/Momentum-Firmware
-          fetch-depth: 1
-          ref: dev
-          submodules: recursive
-
-      - name: "Checkout apps"
-        uses: actions/checkout@v4
-        with:
-          path: applications/external
-          fetch-depth: 1
-          ref: ${{ github.event.pull_request.head.sha }}
-
-      - name: "Check formatting"
-        run: |
-          git add applications/external
-          ./fbt lint_all

+ 1 - 1
air_arkanoid/.gitsubtree

@@ -1,2 +1,2 @@
-https://github.com/xMasterX/all-the-plugins dev base_pack/air_arkanoid 4558d74c9da36abc851edd96a95d18f7d5511a75
+https://github.com/xMasterX/all-the-plugins dev base_pack/air_arkanoid 8bc18d5d99262484548b6bbc92690a1472c329a0
 https://github.com/flipperdevices/flipperzero-good-faps dev air_arkanoid b791dea234f855155027bb46215dc60f3ddeb243
 https://github.com/flipperdevices/flipperzero-good-faps dev air_arkanoid b791dea234f855155027bb46215dc60f3ddeb243

+ 5 - 5
air_arkanoid/engine/sensors/ICM42688P/ICM42688P.c

@@ -6,7 +6,7 @@
 #define ICM42688P_TIMEOUT 100
 #define ICM42688P_TIMEOUT 100
 
 
 struct ICM42688P {
 struct ICM42688P {
-    FuriHalSpiBusHandle* spi_bus;
+    const FuriHalSpiBusHandle* spi_bus;
     const GpioPin* irq_pin;
     const GpioPin* irq_pin;
     float accel_scale;
     float accel_scale;
     float gyro_scale;
     float gyro_scale;
@@ -36,7 +36,7 @@ static const struct GyroFullScale {
     [GyroFullScale15_625DPS] = {15.625f, ICM42688_GFS_15_625DPS},
     [GyroFullScale15_625DPS] = {15.625f, ICM42688_GFS_15_625DPS},
 };
 };
 
 
-static bool icm42688p_write_reg(FuriHalSpiBusHandle* spi_bus, uint8_t addr, uint8_t value) {
+static bool icm42688p_write_reg(const FuriHalSpiBusHandle* spi_bus, uint8_t addr, uint8_t value) {
     bool res = false;
     bool res = false;
     furi_hal_spi_acquire(spi_bus);
     furi_hal_spi_acquire(spi_bus);
     do {
     do {
@@ -48,7 +48,7 @@ static bool icm42688p_write_reg(FuriHalSpiBusHandle* spi_bus, uint8_t addr, uint
     return res;
     return res;
 }
 }
 
 
-static bool icm42688p_read_reg(FuriHalSpiBusHandle* spi_bus, uint8_t addr, uint8_t* value) {
+static bool icm42688p_read_reg(const FuriHalSpiBusHandle* spi_bus, uint8_t addr, uint8_t* value) {
     bool res = false;
     bool res = false;
     furi_hal_spi_acquire(spi_bus);
     furi_hal_spi_acquire(spi_bus);
     do {
     do {
@@ -62,7 +62,7 @@ static bool icm42688p_read_reg(FuriHalSpiBusHandle* spi_bus, uint8_t addr, uint8
 }
 }
 
 
 static bool
 static bool
-    icm42688p_read_mem(FuriHalSpiBusHandle* spi_bus, uint8_t addr, uint8_t* data, uint8_t len) {
+    icm42688p_read_mem(const FuriHalSpiBusHandle* spi_bus, uint8_t addr, uint8_t* data, uint8_t len) {
     bool res = false;
     bool res = false;
     furi_hal_spi_acquire(spi_bus);
     furi_hal_spi_acquire(spi_bus);
     do {
     do {
@@ -231,7 +231,7 @@ bool icm42688_fifo_read(ICM42688P* icm42688p, ICM42688PFifoPacket* data) {
     return (data->header) & (1 << 7);
     return (data->header) & (1 << 7);
 }
 }
 
 
-ICM42688P* icm42688p_alloc(FuriHalSpiBusHandle* spi_bus, const GpioPin* irq_pin) {
+ICM42688P* icm42688p_alloc(const FuriHalSpiBusHandle* spi_bus, const GpioPin* irq_pin) {
     ICM42688P* icm42688p = malloc(sizeof(ICM42688P));
     ICM42688P* icm42688p = malloc(sizeof(ICM42688P));
     icm42688p->spi_bus = spi_bus;
     icm42688p->spi_bus = spi_bus;
     icm42688p->irq_pin = irq_pin;
     icm42688p->irq_pin = irq_pin;

+ 1 - 1
air_arkanoid/engine/sensors/ICM42688P/ICM42688P.h

@@ -73,7 +73,7 @@ typedef struct ICM42688P ICM42688P;
 
 
 typedef void (*ICM42688PIrqCallback)(void* ctx);
 typedef void (*ICM42688PIrqCallback)(void* ctx);
 
 
-ICM42688P* icm42688p_alloc(FuriHalSpiBusHandle* spi_bus, const GpioPin* irq_pin);
+ICM42688P* icm42688p_alloc(const FuriHalSpiBusHandle* spi_bus, const GpioPin* irq_pin);
 
 
 bool icm42688p_init(ICM42688P* icm42688p);
 bool icm42688p_init(ICM42688P* icm42688p);
 
 

+ 1 - 1
air_labyrinth/.gitsubtree

@@ -1,2 +1,2 @@
-https://github.com/xMasterX/all-the-plugins dev non_catalog_apps/air_labyrinth 4558d74c9da36abc851edd96a95d18f7d5511a75
+https://github.com/xMasterX/all-the-plugins dev non_catalog_apps/air_labyrinth 8bc18d5d99262484548b6bbc92690a1472c329a0
 https://github.com/jamisonderek/flipper-zero-tutorials main vgm/apps/air_labyrinth ae42dc8f84f211002f13d37f49526194e6b599d7
 https://github.com/jamisonderek/flipper-zero-tutorials main vgm/apps/air_labyrinth ae42dc8f84f211002f13d37f49526194e6b599d7

+ 5 - 5
air_labyrinth/engine/sensors/ICM42688P/ICM42688P.c

@@ -6,7 +6,7 @@
 #define ICM42688P_TIMEOUT 100
 #define ICM42688P_TIMEOUT 100
 
 
 struct ICM42688P {
 struct ICM42688P {
-    FuriHalSpiBusHandle* spi_bus;
+    const FuriHalSpiBusHandle* spi_bus;
     const GpioPin* irq_pin;
     const GpioPin* irq_pin;
     float accel_scale;
     float accel_scale;
     float gyro_scale;
     float gyro_scale;
@@ -36,7 +36,7 @@ static const struct GyroFullScale {
     [GyroFullScale15_625DPS] = {15.625f, ICM42688_GFS_15_625DPS},
     [GyroFullScale15_625DPS] = {15.625f, ICM42688_GFS_15_625DPS},
 };
 };
 
 
-static bool icm42688p_write_reg(FuriHalSpiBusHandle* spi_bus, uint8_t addr, uint8_t value) {
+static bool icm42688p_write_reg(const FuriHalSpiBusHandle* spi_bus, uint8_t addr, uint8_t value) {
     bool res = false;
     bool res = false;
     furi_hal_spi_acquire(spi_bus);
     furi_hal_spi_acquire(spi_bus);
     do {
     do {
@@ -48,7 +48,7 @@ static bool icm42688p_write_reg(FuriHalSpiBusHandle* spi_bus, uint8_t addr, uint
     return res;
     return res;
 }
 }
 
 
-static bool icm42688p_read_reg(FuriHalSpiBusHandle* spi_bus, uint8_t addr, uint8_t* value) {
+static bool icm42688p_read_reg(const FuriHalSpiBusHandle* spi_bus, uint8_t addr, uint8_t* value) {
     bool res = false;
     bool res = false;
     furi_hal_spi_acquire(spi_bus);
     furi_hal_spi_acquire(spi_bus);
     do {
     do {
@@ -62,7 +62,7 @@ static bool icm42688p_read_reg(FuriHalSpiBusHandle* spi_bus, uint8_t addr, uint8
 }
 }
 
 
 static bool
 static bool
-    icm42688p_read_mem(FuriHalSpiBusHandle* spi_bus, uint8_t addr, uint8_t* data, uint8_t len) {
+    icm42688p_read_mem(const FuriHalSpiBusHandle* spi_bus, uint8_t addr, uint8_t* data, uint8_t len) {
     bool res = false;
     bool res = false;
     furi_hal_spi_acquire(spi_bus);
     furi_hal_spi_acquire(spi_bus);
     do {
     do {
@@ -231,7 +231,7 @@ bool icm42688_fifo_read(ICM42688P* icm42688p, ICM42688PFifoPacket* data) {
     return (data->header) & (1 << 7);
     return (data->header) & (1 << 7);
 }
 }
 
 
-ICM42688P* icm42688p_alloc(FuriHalSpiBusHandle* spi_bus, const GpioPin* irq_pin) {
+ICM42688P* icm42688p_alloc(const FuriHalSpiBusHandle* spi_bus, const GpioPin* irq_pin) {
     ICM42688P* icm42688p = malloc(sizeof(ICM42688P));
     ICM42688P* icm42688p = malloc(sizeof(ICM42688P));
     icm42688p->spi_bus = spi_bus;
     icm42688p->spi_bus = spi_bus;
     icm42688p->irq_pin = irq_pin;
     icm42688p->irq_pin = irq_pin;

+ 1 - 1
air_labyrinth/engine/sensors/ICM42688P/ICM42688P.h

@@ -73,7 +73,7 @@ typedef struct ICM42688P ICM42688P;
 
 
 typedef void (*ICM42688PIrqCallback)(void* ctx);
 typedef void (*ICM42688PIrqCallback)(void* ctx);
 
 
-ICM42688P* icm42688p_alloc(FuriHalSpiBusHandle* spi_bus, const GpioPin* irq_pin);
+ICM42688P* icm42688p_alloc(const FuriHalSpiBusHandle* spi_bus, const GpioPin* irq_pin);
 
 
 bool icm42688p_init(ICM42688P* icm42688p);
 bool icm42688p_init(ICM42688P* icm42688p);
 
 

+ 1 - 1
airmouse/.gitsubtree

@@ -1,2 +1,2 @@
-https://github.com/xMasterX/all-the-plugins dev non_catalog_apps/airmouse 4558d74c9da36abc851edd96a95d18f7d5511a75
+https://github.com/xMasterX/all-the-plugins dev non_catalog_apps/airmouse 8bc18d5d99262484548b6bbc92690a1472c329a0
 https://github.com/ginkage/FlippAirMouse/ main /
 https://github.com/ginkage/FlippAirMouse/ main /

+ 1 - 1
airmouse/tracking/imu/lsm6ds3trc.c

@@ -28,7 +28,7 @@ bool lsm6ds3trc_begin() {
     lsm6ds3trc_ctx.write_reg = lsm6ds3trc_write_i2c;
     lsm6ds3trc_ctx.write_reg = lsm6ds3trc_write_i2c;
     lsm6ds3trc_ctx.read_reg = lsm6ds3trc_read_i2c;
     lsm6ds3trc_ctx.read_reg = lsm6ds3trc_read_i2c;
     lsm6ds3trc_ctx.mdelay = furi_delay_ms;
     lsm6ds3trc_ctx.mdelay = furi_delay_ms;
-    lsm6ds3trc_ctx.handle = &furi_hal_i2c_handle_external;
+    lsm6ds3trc_ctx.handle = (FuriHalI2cBusHandle*)&furi_hal_i2c_handle_external;
 
 
     uint8_t whoami;
     uint8_t whoami;
     lsm6ds3tr_c_device_id_get(&lsm6ds3trc_ctx, &whoami);
     lsm6ds3tr_c_device_id_get(&lsm6ds3trc_ctx, &whoami);

+ 1 - 1
airmouse/tracking/imu/lsm6dso.c

@@ -27,7 +27,7 @@ bool lsm6dso_begin() {
     lsm6dso_ctx.write_reg = lsm6dso_write_i2c;
     lsm6dso_ctx.write_reg = lsm6dso_write_i2c;
     lsm6dso_ctx.read_reg = lsm6dso_read_i2c;
     lsm6dso_ctx.read_reg = lsm6dso_read_i2c;
     lsm6dso_ctx.mdelay = furi_delay_ms;
     lsm6dso_ctx.mdelay = furi_delay_ms;
-    lsm6dso_ctx.handle = &furi_hal_i2c_handle_external;
+    lsm6dso_ctx.handle = (FuriHalI2cBusHandle*)&furi_hal_i2c_handle_external;
 
 
     uint8_t whoami;
     uint8_t whoami;
     lsm6dso_device_id_get(&lsm6dso_ctx, &whoami);
     lsm6dso_device_id_get(&lsm6dso_ctx, &whoami);

+ 1 - 1
cli_bridge/.gitsubtree

@@ -1,2 +1,2 @@
 https://github.com/ranchordo/flipperzero-cli-bridge main /
 https://github.com/ranchordo/flipperzero-cli-bridge main /
-#https://github.com/xMasterX/all-the-plugins dev non_catalog_apps/cli_bridge 4558d74c9da36abc851edd96a95d18f7d5511a75
+https://github.com/xMasterX/all-the-plugins dev non_catalog_apps/cli_bridge 8bc18d5d99262484548b6bbc92690a1472c329a0

+ 1 - 0
cli_bridge/application.fam

@@ -7,4 +7,5 @@ App(
     stack_size=8 * 1024,
     stack_size=8 * 1024,
     fap_icon="cligui.png",
     fap_icon="cligui.png",
     fap_category="Tools",
     fap_category="Tools",
+    fap_icon_assets="assets",
 )
 )

+ 11 - 5
cli_bridge/cli_control.c

@@ -2,6 +2,7 @@
 
 
 #include <FreeRTOS.h>
 #include <FreeRTOS.h>
 #include <cli/cli.h>
 #include <cli/cli.h>
+
 #include <cli/cli_i.h>
 #include <cli/cli_i.h>
 #include <cli/cli_vcp.h>
 #include <cli/cli_vcp.h>
 #include <loader/loader.h>
 #include <loader/loader.h>
@@ -84,11 +85,13 @@ void clicontrol_hijack(size_t tx_size, size_t rx_size) {
     }
     }
 
 
     // Session switcharooney
     // Session switcharooney
-    FuriThreadStdoutWriteCallback prev_stdout = furi_thread_get_stdout_callback();
+    FuriThreadStdoutWriteCallback original_out_cb;
+    void* original_out_ctx;
+    furi_thread_get_stdout_callback(&original_out_cb, &original_out_ctx);
     cli_session_close(global_cli);
     cli_session_close(global_cli);
     restore_tx_stdout = false;
     restore_tx_stdout = false;
     cli_session_open(global_cli, session);
     cli_session_open(global_cli, session);
-    furi_thread_set_stdout_callback(prev_stdout, NULL);
+    furi_thread_set_stdout_callback(original_out_cb, original_out_ctx);
 
 
     furi_record_close(RECORD_CLI);
     furi_record_close(RECORD_CLI);
 }
 }
@@ -110,7 +113,7 @@ void clicontrol_unhijack(bool persist) {
 
 
     if(persist) {
     if(persist) {
         // Don't trigger a terminal reset as the session switches
         // Don't trigger a terminal reset as the session switches
-        cli_vcp.is_connected = &furi_hal_version_do_i_belong_here;
+        //cli_vcp.is_connected = &furi_hal_version_do_i_belong_here;
     } else {
     } else {
         // Send CTRL-C a few times
         // Send CTRL-C a few times
         char eot = 0x03;
         char eot = 0x03;
@@ -132,10 +135,13 @@ void clicontrol_unhijack(bool persist) {
     restore_tx_stdout = true; // Ready for next rx call
     restore_tx_stdout = true; // Ready for next rx call
 
 
     // Session switcharooney again
     // Session switcharooney again
-    FuriThreadStdoutWriteCallback prev_stdout = furi_thread_get_stdout_callback();
+    //FuriThreadStdoutWriteCallback prev_stdout = furi_thread_get_stdout_callback();
+    FuriThreadStdoutWriteCallback original_out_cb;
+    void* original_out_ctx;
+    furi_thread_get_stdout_callback(&original_out_cb, &original_out_ctx);
     cli_session_close(global_cli);
     cli_session_close(global_cli);
     cli_session_open(global_cli, &cli_vcp);
     cli_session_open(global_cli, &cli_vcp);
-    furi_thread_set_stdout_callback(prev_stdout, NULL);
+    furi_thread_set_stdout_callback(original_out_cb, original_out_ctx);
     furi_record_close(RECORD_CLI);
     furi_record_close(RECORD_CLI);
 
 
     // Unblock waiting rx handler, restore old cli_vcp.tx_stdout
     // Unblock waiting rx handler, restore old cli_vcp.tx_stdout

+ 0 - 1
cli_bridge/cligui_main.c

@@ -76,7 +76,6 @@ int32_t cligui_main(void* p) {
     FuriPubSub* input_events = furi_record_open(RECORD_INPUT_EVENTS);
     FuriPubSub* input_events = furi_record_open(RECORD_INPUT_EVENTS);
     FuriPubSubSubscription* input_events_sub =
     FuriPubSubSubscription* input_events_sub =
         furi_pubsub_subscribe(input_events, input_callback, (void*)cligui);
         furi_pubsub_subscribe(input_events, input_callback, (void*)cligui);
-    view_dispatcher_enable_queue(cligui->view_dispatcher);
     view_dispatcher_set_event_callback_context(cligui->view_dispatcher, cligui);
     view_dispatcher_set_event_callback_context(cligui->view_dispatcher, cligui);
     view_dispatcher_set_custom_event_callback(cligui->view_dispatcher, cligui_custom_event_cb);
     view_dispatcher_set_custom_event_callback(cligui->view_dispatcher, cligui_custom_event_cb);
     view_dispatcher_set_navigation_event_callback(cligui->view_dispatcher, cligui_back_event_cb);
     view_dispatcher_set_navigation_event_callback(cligui->view_dispatcher, cligui_back_event_cb);

+ 9 - 1
cross_remote/README.md

@@ -30,12 +30,15 @@ Wouldn't it be nicer to simply click one button and let everything happen? This
 - Add pauses, becaue target systems are not always fast enough for multiple commands<br>
 - Add pauses, becaue target systems are not always fast enough for multiple commands<br>
 - Run file containing chained IR & SubGhz commands<br>
 - Run file containing chained IR & SubGhz commands<br>
 - Loop Transmissions until quit
 - Loop Transmissions until quit
+- Pin to Favorites menu (if supported by firmware)
 
 
 ### Settings
 ### Settings
 - LED FX, allow the LED to blink
 - LED FX, allow the LED to blink
 - Save settings, stores a file with your settings in it on exit
 - Save settings, stores a file with your settings in it on exit
 - IR time ms, the default duration of an IR signal transmission. Individual times can be set
 - IR time ms, the default duration of an IR signal transmission. Individual times can be set
 - SubG. time ms, the default duration of a SubGhz signal. Only needed for Encoded signals, RAW files play until finished
 - SubG. time ms, the default duration of a SubGhz signal. Only needed for Encoded signals, RAW files play until finished
+- Loop transmip, repeats the command chain until cancelled
+- External IR & 5V on GPIO, settings for external IR boards
 
 
 ### Limitations
 ### Limitations
 SubGhz commands will stop working if you move/rename/delete the original files on your Flipper. This is because of how the Flippers SubGhz worker expects data. 
 SubGhz commands will stop working if you move/rename/delete the original files on your Flipper. This is because of how the Flippers SubGhz worker expects data. 
@@ -53,8 +56,13 @@ Then run the command:
  ```
  ```
 The application will be compiled and copied onto your device. 
 The application will be compiled and copied onto your device. 
 
 
+## Pin to Favorites feature
+This feature is only available in custom firmwares that support it or if you modify the owf to support the .xr file types.
+
+From the start screen on flipper press down to enter the favorites menu. Navigate left and right until you hit the tab "browser". From there navigate into the folder apps_data/xremote and select the command chain you want to add to your favorites. Select "Pin" from the context menu and your command chain will be available in the favorites menu. 
+
 ## Thank you notes
 ## Thank you notes
-- [Willy-JL](https://github.com/Willy-JL) for distributing in Momentum Firmware
+- [Willy-JL](https://github.com/Willy-JL) for code contributions and distributing in Momentum Firmware
 - [Roguemaster](https://github.com/RogueMaster/flipperzero-firmware-wPlugins) for distributing in Roguemaster Firmware
 - [Roguemaster](https://github.com/RogueMaster/flipperzero-firmware-wPlugins) for distributing in Roguemaster Firmware
 - [Miccayo](https://github.com/miccayo) for contributing the loop transmit feature
 - [Miccayo](https://github.com/miccayo) for contributing the loop transmit feature
 
 

+ 1 - 1
cross_remote/application.fam

@@ -6,7 +6,7 @@ App(
     stack_size=3 * 1024,
     stack_size=3 * 1024,
     fap_icon="icons/xremote_10px.png",
     fap_icon="icons/xremote_10px.png",
     fap_icon_assets="icons",
     fap_icon_assets="icons",
-    fap_version="3.0",
+    fap_version="3.3",
     fap_category="Infrared",
     fap_category="Infrared",
     fap_author="Leedave",
     fap_author="Leedave",
     fap_description="One-Click, sends multiple commands",
     fap_description="One-Click, sends multiple commands",

+ 2 - 0
cross_remote/docs/README.md

@@ -4,6 +4,7 @@ This app combines your IR and SubGhz commands into a playlist that can be run wi
 
 
 ## Features
 ## Features
 - Read out commands you recorded in the IR app
 - Read out commands you recorded in the IR app
+- Supports external GPIO Boards like IR Blaster or Masta-Blasta
 - Read out commands you saved as .sub files
 - Read out commands you saved as .sub files
 - Combine commands to a chain/playlist 
 - Combine commands to a chain/playlist 
 - Add pauses inbetween commands 
 - Add pauses inbetween commands 
@@ -12,6 +13,7 @@ This app combines your IR and SubGhz commands into a playlist that can be run wi
 - Configure duration of IR Signals
 - Configure duration of IR Signals
 - Configure default duration of Encoded SubGhz Signals
 - Configure default duration of Encoded SubGhz Signals
 - Loop Transmissions until quit
 - Loop Transmissions until quit
+- Pin to Favorites menu (if supported by Firmware)
 
 
 ## What good is this?
 ## What good is this?
 
 

+ 10 - 0
cross_remote/docs/changelog.md

@@ -1,3 +1,13 @@
+## 3.3
+- Added support for Favorites menu. 
+
+## 3.2
+- Added support for external IR GPIO boards, tested on IR Blaster & Masta-Blasta. 
+
+## 3.1
+- Bugfix to enable save on first use (thanks to WillyJL)
+- Bugfix for loop transmit when using RAW SubGHz transmissions
+
 ## 3.0
 ## 3.0
 - Added loop transmit feature (thanks to miccayo)
 - Added loop transmit feature (thanks to miccayo)
 - Replaced transmission counter with animations
 - Replaced transmission counter with animations

+ 0 - 7
cross_remote/helpers/subghz/subghz.c

@@ -28,13 +28,6 @@ void subghz_scene_transmit_callback_end_tx(void* context) {
     furi_assert(context);
     furi_assert(context);
     FURI_LOG_D(TAG, "callback end");
     FURI_LOG_D(TAG, "callback end");
     XRemote* app = context;
     XRemote* app = context;
-    view_dispatcher_send_custom_event(
-        app->view_dispatcher, XRemoteCustomEventViewTransmitterSendStop);
-
-    //app->state_notifications = SubGhzNotificationStateIDLE;
-    //subghz_txrx_stop(app->subghz->txrx);
-    //app->transmitting = false;
-    //xremote_scene_ir_notification_message(app, SubGhzNotificationMessageBlinkStop);
     xremote_cross_remote_set_transmitting(app->cross_remote, XRemoteTransmittingStopSubghz);
     xremote_cross_remote_set_transmitting(app->cross_remote, XRemoteTransmittingStopSubghz);
 }
 }
 
 

+ 5 - 1
cross_remote/helpers/subghz/subghz_txrx.c

@@ -3,6 +3,7 @@
 #include <lib/subghz/subghz_protocol_registry.h>
 #include <lib/subghz/subghz_protocol_registry.h>
 #include <applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h>
 #include <applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h>
 #include <lib/subghz/devices/cc1101_int/cc1101_int_interconnect.h>
 #include <lib/subghz/devices/cc1101_int/cc1101_int_interconnect.h>
+#include <infrared_transmit.h>
 
 
 #define TAG "SubGhz"
 #define TAG "SubGhz"
 
 
@@ -24,7 +25,10 @@ static void subghz_txrx_radio_device_power_on(SubGhzTxRx* instance) {
 
 
 static void subghz_txrx_radio_device_power_off(SubGhzTxRx* instance) {
 static void subghz_txrx_radio_device_power_off(SubGhzTxRx* instance) {
     UNUSED(instance);
     UNUSED(instance);
-    if(furi_hal_power_is_otg_enabled()) furi_hal_power_disable_otg();
+    FuriHalInfraredTxPin tx_pin_detected = furi_hal_infrared_detect_tx_output();
+    if(furi_hal_power_is_otg_enabled() && tx_pin_detected == FuriHalInfraredTxPinInternal) {
+        furi_hal_power_disable_otg();
+    }
 }
 }
 
 
 SubGhzTxRx* subghz_txrx_alloc(void) {
 SubGhzTxRx* subghz_txrx_alloc(void) {

+ 3 - 0
cross_remote/helpers/xremote_custom_event.h

@@ -52,6 +52,9 @@ typedef enum {
     XRemoteCustomEventPauseSetOk,
     XRemoteCustomEventPauseSetOk,
 
 
     XRemoteCustomEventViewTransmitterSendStop,
     XRemoteCustomEventViewTransmitterSendStop,
+
+    XRemoteCustomEventTypeIrGpioPinChanged,
+    XRemoteCustomEventTypeIrGpioOtgChanged,
 } XRemoteCustomEvent;
 } XRemoteCustomEvent;
 
 
 static inline uint32_t xremote_custom_menu_event_pack(uint16_t type, int16_t value) {
 static inline uint32_t xremote_custom_menu_event_pack(uint16_t type, int16_t value) {

+ 8 - 0
cross_remote/helpers/xremote_storage.c

@@ -62,6 +62,10 @@ void xremote_save_settings(void* context) {
     flipper_format_write_uint32(
     flipper_format_write_uint32(
         fff_file, XREMOTE_SETTINGS_KEY_LOOP_TRANSMIT, &app->loop_transmit, 1);
         fff_file, XREMOTE_SETTINGS_KEY_LOOP_TRANSMIT, &app->loop_transmit, 1);
 
 
+    //IR GPIO Settings
+    flipper_format_write_uint32(fff_file, XREMOTE_SETTINGS_KEY_IR_TX_PIN, &app->ir_tx_pin, 1);
+    flipper_format_write_bool(fff_file, XREMOTE_SETTINGS_KEY_IR_USE_OTP, &app->ir_is_otg_enabled, 1);
+
     if(!flipper_format_rewind(fff_file)) {
     if(!flipper_format_rewind(fff_file)) {
         xremote_close_config_file(fff_file);
         xremote_close_config_file(fff_file);
         FURI_LOG_E(TAG, "Rewind error");
         FURI_LOG_E(TAG, "Rewind error");
@@ -117,6 +121,10 @@ void xremote_read_settings(void* context) {
     flipper_format_read_uint32(
     flipper_format_read_uint32(
         fff_file, XREMOTE_SETTINGS_KEY_LOOP_TRANSMIT, &app->loop_transmit, 1);
         fff_file, XREMOTE_SETTINGS_KEY_LOOP_TRANSMIT, &app->loop_transmit, 1);
 
 
+    // IR GPIO Settings
+    flipper_format_read_uint32(fff_file, XREMOTE_SETTINGS_KEY_IR_TX_PIN, &app->ir_tx_pin, 1);
+    flipper_format_read_bool(fff_file, XREMOTE_SETTINGS_KEY_IR_USE_OTP, &app->ir_is_otg_enabled, 1);
+
     flipper_format_rewind(fff_file);
     flipper_format_rewind(fff_file);
 
 
     furi_string_free(temp_str);
     furi_string_free(temp_str);

+ 2 - 0
cross_remote/helpers/xremote_storage.h

@@ -12,6 +12,8 @@
 #define XREMOTE_SETTINGS_KEY_SPEAKER "Speaker"
 #define XREMOTE_SETTINGS_KEY_SPEAKER "Speaker"
 #define XREMOTE_SETTINGS_KEY_SAVE_SETTINGS "SaveSettings"
 #define XREMOTE_SETTINGS_KEY_SAVE_SETTINGS "SaveSettings"
 #define XREMOTE_SETTINGS_KEY_IR_TIMING "IRTiming"
 #define XREMOTE_SETTINGS_KEY_IR_TIMING "IRTiming"
+#define XREMOTE_SETTINGS_KEY_IR_TX_PIN "IRTXPin"
+#define XREMOTE_SETTINGS_KEY_IR_USE_OTP "IRUSEOTP"
 #define XREMOTE_SETTINGS_KEY_SG_TIMING "SGTiming"
 #define XREMOTE_SETTINGS_KEY_SG_TIMING "SGTiming"
 #define XREMOTE_SETTINGS_KEY_LOOP_TRANSMIT "LoopTransmit"
 #define XREMOTE_SETTINGS_KEY_LOOP_TRANSMIT "LoopTransmit"
 
 

+ 68 - 45
cross_remote/scenes/xremote_scene_settings.c

@@ -1,29 +1,16 @@
 #include "../xremote.h"
 #include "../xremote.h"
 #include <lib/toolbox/value_index.h>
 #include <lib/toolbox/value_index.h>
-/*
-enum SettingsIndex {
-    SettingsIndexHaptic = 10,
-    SettingsIndexValue1,
-    SettingsIndexValue2,
-};*/
-
-/*const char* const haptic_text[2] = {
-    "OFF",
-    "ON",
+
+static const char* infrared_pin_text[] = {
+    "Flipper",
+    "2 (A7)",
+    "Detect",
 };
 };
-const uint32_t haptic_value[2] = {
-    XRemoteHapticOff,
-    XRemoteHapticOn,
-};*/
 
 
-/*const char* const speaker_text[2] = {
+static const char* infrared_otg_text[] = {
     "OFF",
     "OFF",
     "ON",
     "ON",
 };
 };
-const uint32_t speaker_value[2] = {
-    XRemoteSpeakerOff,
-    XRemoteSpeakerOn,
-};*/
 
 
 const char* const led_text[2] = {
 const char* const led_text[2] = {
     "OFF",
     "OFF",
@@ -33,6 +20,7 @@ const uint32_t led_value[2] = {
     XRemoteLedOff,
     XRemoteLedOff,
     XRemoteLedOn,
     XRemoteLedOn,
 };
 };
+
 const char* const loop_text[2] = {
 const char* const loop_text[2] = {
     "OFF",
     "OFF",
     "ON",
     "ON",
@@ -41,6 +29,7 @@ const uint32_t loop_value[2] = {
     XRemoteLoopOff,
     XRemoteLoopOff,
     XRemoteLoopOn,
     XRemoteLoopOn,
 };
 };
+
 const char* const settings_text[2] = {
 const char* const settings_text[2] = {
     "OFF",
     "OFF",
     "ON",
     "ON",
@@ -50,20 +39,27 @@ const uint32_t settings_value[2] = {
     XRemoteSettingsOn,
     XRemoteSettingsOn,
 };
 };
 
 
-/*static void xremote_scene_settings_set_haptic(VariableItem* item) {
+static void xremote_scene_settings_set_ir_pin(VariableItem* item) {
     XRemote* app = variable_item_get_context(item);
     XRemote* app = variable_item_get_context(item);
     uint8_t index = variable_item_get_current_value_index(item);
     uint8_t index = variable_item_get_current_value_index(item);
 
 
-    variable_item_set_current_value_text(item, haptic_text[index]);
-    app->haptic = haptic_value[index];
-}*/
+    variable_item_set_current_value_text(item, infrared_pin_text[index]);
+    app->ir_tx_pin = index;
+    view_dispatcher_send_custom_event(
+        app->view_dispatcher,
+        xremote_custom_menu_event_pack(XRemoteCustomEventTypeIrGpioPinChanged, index));
+}
 
 
-/*static void xremote_scene_settings_set_speaker(VariableItem* item) {
+static void xremote_scene_settings_set_ir_is_otg_enabled(VariableItem* item) {
     XRemote* app = variable_item_get_context(item);
     XRemote* app = variable_item_get_context(item);
     uint8_t index = variable_item_get_current_value_index(item);
     uint8_t index = variable_item_get_current_value_index(item);
-    variable_item_set_current_value_text(item, speaker_text[index]);
-    app->speaker = speaker_value[index];
-}*/
+
+    variable_item_set_current_value_text(item, infrared_otg_text[index]);
+    app->ir_is_otg_enabled = index;
+    view_dispatcher_send_custom_event(
+        app->view_dispatcher,
+        xremote_custom_menu_event_pack(XRemoteCustomEventTypeIrGpioOtgChanged, index));
+}
 
 
 static void xremote_scene_settings_set_led(VariableItem* item) {
 static void xremote_scene_settings_set_led(VariableItem* item) {
     XRemote* app = variable_item_get_context(item);
     XRemote* app = variable_item_get_context(item);
@@ -109,25 +105,11 @@ void xremote_scene_settings_submenu_callback(void* context, uint32_t index) {
     view_dispatcher_send_custom_event(app->view_dispatcher, index);
     view_dispatcher_send_custom_event(app->view_dispatcher, index);
 }
 }
 
 
-void xremote_scene_settings_on_enter(void* context) {
-    XRemote* app = context;
+void xremote_scene_settings_init(void* context) {
+    XRemote* app = context;    
     VariableItem* item;
     VariableItem* item;
     uint8_t value_index;
     uint8_t value_index;
 
 
-    // Vibro on/off
-    /*    item = variable_item_list_add(
-        app->variable_item_list, "Vibro/Haptic:", 2, xremote_scene_settings_set_haptic, app);
-    value_index = value_index_uint32(app->haptic, haptic_value, 2);
-    variable_item_set_current_value_index(item, value_index);
-    variable_item_set_current_value_text(item, haptic_text[value_index]);*/
-
-    // Sound on/off
-    /*   item = variable_item_list_add(
-        app->variable_item_list, "Sound:", 2, xremote_scene_settings_set_speaker, app);
-    value_index = value_index_uint32(app->speaker, speaker_value, 2);
-    variable_item_set_current_value_index(item, value_index);
-    variable_item_set_current_value_text(item, speaker_text[value_index]);*/
-
     // LED Effects on/off
     // LED Effects on/off
     item = variable_item_list_add(
     item = variable_item_list_add(
         app->variable_item_list, "LED FX", 2, xremote_scene_settings_set_led, app);
         app->variable_item_list, "LED FX", 2, xremote_scene_settings_set_led, app);
@@ -149,6 +131,35 @@ void xremote_scene_settings_on_enter(void* context) {
     variable_item_set_current_value_index(item, value_index);
     variable_item_set_current_value_index(item, value_index);
     variable_item_set_current_value_text(item, settings_text[value_index]);
     variable_item_set_current_value_text(item, settings_text[value_index]);
 
 
+    // Infrared GPIO Board
+    item = variable_item_list_add(
+        app->variable_item_list, 
+        "External IR", 
+        COUNT_OF(infrared_pin_text), 
+        xremote_scene_settings_set_ir_pin, 
+        app);
+    value_index = app->ir_tx_pin;
+    variable_item_set_current_value_index(item, value_index);
+    variable_item_set_current_value_text(item, infrared_pin_text[value_index]);
+    
+    // Infrared GPIO 5V
+    item = variable_item_list_add(
+        app->variable_item_list,
+        "5V on IR GPIO",
+        COUNT_OF(infrared_otg_text),
+        xremote_scene_settings_set_ir_is_otg_enabled,
+        app);
+    
+    if(app->ir_tx_pin < FuriHalInfraredTxPinMax) {
+        value_index = app->ir_is_otg_enabled;
+        variable_item_set_current_value_index(item, value_index);
+        variable_item_set_current_value_text(item, infrared_otg_text[value_index]);
+    } else {
+        variable_item_set_values_count(item, 1);
+        variable_item_set_current_value_index(item, 0);
+        variable_item_set_current_value_text(item, "Auto");
+    }
+
     // Set Infrared Timer
     // Set Infrared Timer
     item = variable_item_list_add(
     item = variable_item_list_add(
         app->variable_item_list, "IR Time ms", 30, xremote_scene_settings_set_ir_timing, app);
         app->variable_item_list, "IR Time ms", 30, xremote_scene_settings_set_ir_timing, app);
@@ -163,16 +174,28 @@ void xremote_scene_settings_on_enter(void* context) {
 
 
     variable_item_set_current_value_index(item, (uint8_t)(app->sg_timing / 100));
     variable_item_set_current_value_index(item, (uint8_t)(app->sg_timing / 100));
     snprintf(app->sg_timing_char, 20, "%lu", app->sg_timing);
     snprintf(app->sg_timing_char, 20, "%lu", app->sg_timing);
-    variable_item_set_current_value_text(item, app->sg_timing_char);
+    variable_item_set_current_value_text(item, app->sg_timing_char);    
+}
 
 
+void xremote_scene_settings_on_enter(void* context) {
+    XRemote* app = context;
+    xremote_scene_settings_init(app);
     view_dispatcher_switch_to_view(app->view_dispatcher, XRemoteViewIdSettings);
     view_dispatcher_switch_to_view(app->view_dispatcher, XRemoteViewIdSettings);
 }
 }
 
 
 bool xremote_scene_settings_on_event(void* context, SceneManagerEvent event) {
 bool xremote_scene_settings_on_event(void* context, SceneManagerEvent event) {
     XRemote* app = context;
     XRemote* app = context;
-    UNUSED(app);
     bool consumed = false;
     bool consumed = false;
     if(event.type == SceneManagerEventTypeCustom) {
     if(event.type == SceneManagerEventTypeCustom) {
+        const uint16_t custom_event_type = xremote_custom_menu_event_get_type(event.event);
+        
+        if (custom_event_type == XRemoteCustomEventTypeIrGpioPinChanged) {
+            variable_item_list_reset(app->variable_item_list);
+            xremote_scene_settings_init(app);
+            xremote_ir_set_tx_pin(app);
+        } else if(custom_event_type == XRemoteCustomEventTypeIrGpioOtgChanged) {
+            xremote_ir_enable_otg(app, app->ir_is_otg_enabled);
+        }
     }
     }
     return consumed;
     return consumed;
 }
 }

+ 6 - 1
cross_remote/scenes/xremote_scene_transmit.c

@@ -117,7 +117,12 @@ void xremote_scene_transmit_send_signal(void* context, CrossRemoteItem* item) {
 
 
 static void xremote_scene_transmit_end_scene(XRemote* app) {
 static void xremote_scene_transmit_end_scene(XRemote* app) {
     xremote_scene_ir_notification_message(app, InfraredNotificationMessageBlinkStop);
     xremote_scene_ir_notification_message(app, InfraredNotificationMessageBlinkStop);
-    scene_manager_previous_scene(app->scene_manager);
+    if (app->loadFavorite) {
+        scene_manager_stop(app->scene_manager);
+        view_dispatcher_stop(app->view_dispatcher);
+    } else {
+        scene_manager_previous_scene(app->scene_manager);
+    }
 }
 }
 
 
 static void xremote_scene_transmit_run_single_transmit(XRemote* app) {
 static void xremote_scene_transmit_run_single_transmit(XRemote* app) {

+ 0 - 1
cross_remote/scenes/xremote_scene_xr_list.c

@@ -12,7 +12,6 @@ void xremote_scene_xr_list_on_enter(void* context) {
     furi_string_set(path, XREMOTE_APP_FOLDER);
     furi_string_set(path, XREMOTE_APP_FOLDER);
 
 
     bool success = dialog_file_browser_show(
     bool success = dialog_file_browser_show(
-        //app->dialogs, app->file_path, app->file_path, &browser_options);
         app->dialogs,
         app->dialogs,
         app->file_path,
         app->file_path,
         path,
         path,

+ 44 - 39
cross_remote/xremote.c

@@ -1,7 +1,5 @@
 #include "xremote.h"
 #include "xremote.h"
 
 
-#include <infrared/infrared_settings.h>
-
 bool xremote_custom_event_callback(void* context, uint32_t event) {
 bool xremote_custom_event_callback(void* context, uint32_t event) {
     furi_assert(context);
     furi_assert(context);
     XRemote* app = context;
     XRemote* app = context;
@@ -55,6 +53,7 @@ XRemote* xremote_app_alloc() {
     app->stop_transmit = false;
     app->stop_transmit = false;
     app->loop_transmit = 0;
     app->loop_transmit = 0;
     app->transmit_item = 0;
     app->transmit_item = 0;
+    app->loadFavorite = false;
 
 
     // Load configs
     // Load configs
     xremote_read_settings(app);
     xremote_read_settings(app);
@@ -193,59 +192,65 @@ void xremote_text_input_callback(void* context) {
     view_dispatcher_send_custom_event(app->view_dispatcher, XRemoteCustomEventTextInput);
     view_dispatcher_send_custom_event(app->view_dispatcher, XRemoteCustomEventTextInput);
 }
 }
 
 
-int32_t xremote_app(void* p) {
-    UNUSED(p);
-    bool otg_was_enabled = furi_hal_power_is_otg_enabled();
+void xremote_ir_enable_otg(XRemote* app, bool enable) {
+    if(enable) {
+        furi_hal_power_enable_otg();
+    } else {
+        furi_hal_power_disable_otg();
+    }
+    app->ir_is_otg_enabled = enable;
+}
 
 
-    XRemote* app = xremote_app_alloc();
+void xremote_ir_set_tx_pin(XRemote* app) {
+    if(app->ir_tx_pin < FuriHalInfraredTxPinMax) {
+        furi_hal_infrared_set_tx_output(app->ir_tx_pin);
+    } else {
+        FuriHalInfraredTxPin tx_pin_detected = furi_hal_infrared_detect_tx_output();
+        furi_hal_infrared_set_tx_output(tx_pin_detected);
+        if(tx_pin_detected != FuriHalInfraredTxPinInternal) {
+            xremote_ir_enable_otg(app, true);
+        }
+    }
+}
 
 
-    view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
+static void xremote_ir_load_settings(XRemote* app) {
+    xremote_ir_set_tx_pin(app);
+    if(app->ir_tx_pin < FuriHalInfraredTxPinMax) {
+        xremote_ir_enable_otg(app, app->ir_is_otg_enabled);
+    }
+}
 
 
-    //scene_manager_next_scene(app->scene_manager, XRemoteSceneInfoscreen); //Start with start screen
-    scene_manager_next_scene(
-        app->scene_manager, XRemoteSceneMenu); //if you want to directly start with Menu
+int32_t xremote_app(void* p) {
+    XRemote* app = xremote_app_alloc();
+    
+    view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
 
 
     furi_hal_power_suppress_charge_enter();
     furi_hal_power_suppress_charge_enter();
+    xremote_ir_load_settings(app);
+
 
 
     Storage* storage = furi_record_open(RECORD_STORAGE);
     Storage* storage = furi_record_open(RECORD_STORAGE);
     storage_common_mkdir(storage, XREMOTE_APP_FOLDER);
     storage_common_mkdir(storage, XREMOTE_APP_FOLDER);
     furi_record_close(RECORD_STORAGE);
     furi_record_close(RECORD_STORAGE);
 
 
-    if(strcmp(subghz_txrx_radio_device_get_name(app->subghz->txrx), "cc1101_ext") != 0) {
-        InfraredSettings settings = {0};
-        infrared_settings_load(&settings);
-        if(settings.tx_pin < FuriHalInfraredTxPinMax) {
-            furi_hal_infrared_set_tx_output(settings.tx_pin);
-            if(settings.otg_enabled != otg_was_enabled) {
-                if(settings.otg_enabled) {
-                    furi_hal_power_enable_otg();
-                } else {
-                    furi_hal_power_disable_otg();
-                }
-            }
-        } else {
-            FuriHalInfraredTxPin tx_pin_detected = furi_hal_infrared_detect_tx_output();
-            furi_hal_infrared_set_tx_output(tx_pin_detected);
-            if(tx_pin_detected != FuriHalInfraredTxPinInternal) {
-                furi_hal_power_enable_otg();
-            }
-        }
+    //bool loadFavorite = false;
+    if(p && strlen(p)) {
+        furi_string_set_str(app->file_path, p);
+        app->loadFavorite = xremote_cross_remote_load(app->cross_remote, app->file_path);
+    }
+    if (app->loadFavorite) {
+        scene_manager_next_scene(
+            app->scene_manager, XRemoteSceneTransmit); //if you loaded from Favorites
+    } else {
+        scene_manager_next_scene(
+            app->scene_manager, XRemoteSceneMenu); //if you want to directly start with Menu
     }
     }
 
 
     view_dispatcher_run(app->view_dispatcher);
     view_dispatcher_run(app->view_dispatcher);
 
 
-    furi_hal_infrared_set_tx_output(FuriHalInfraredTxPinInternal);
-    if(furi_hal_power_is_otg_enabled() != otg_was_enabled) {
-        if(otg_was_enabled) {
-            furi_hal_power_enable_otg();
-        } else {
-            furi_hal_power_disable_otg();
-        }
-    }
-
     xremote_save_settings(app);
     xremote_save_settings(app);
-
     furi_hal_power_suppress_charge_exit();
     furi_hal_power_suppress_charge_exit();
+    xremote_ir_enable_otg(app, false);
     xremote_app_free(app);
     xremote_app_free(app);
 
 
     return 0;
     return 0;

+ 6 - 1
cross_remote/xremote.h

@@ -36,6 +36,8 @@ typedef struct {
     XRemotePauseSet* xremote_pause_set;
     XRemotePauseSet* xremote_pause_set;
     InfraredRemote* ir_remote_buffer;
     InfraredRemote* ir_remote_buffer;
     InfraredWorker* ir_worker;
     InfraredWorker* ir_worker;
+    bool ir_is_otg_enabled; /**< Whether OTG power (external 5V) is enabled for IR. */
+    uint32_t ir_tx_pin; 
     SubGhzRemote* sg_remote_buffer;
     SubGhzRemote* sg_remote_buffer;
     CrossRemote* cross_remote;
     CrossRemote* cross_remote;
     uint32_t haptic;
     uint32_t haptic;
@@ -54,6 +56,7 @@ typedef struct {
     char text_store[XREMOTE_TEXT_STORE_NUM][XREMOTE_TEXT_STORE_SIZE + 1];
     char text_store[XREMOTE_TEXT_STORE_NUM][XREMOTE_TEXT_STORE_SIZE + 1];
     SubGhz* subghz;
     SubGhz* subghz;
     NumberInput* number_input;
     NumberInput* number_input;
+    bool loadFavorite;
 } XRemote;
 } XRemote;
 
 
 typedef enum {
 typedef enum {
@@ -98,4 +101,6 @@ typedef enum {
 } XRemoteSettingsStoreState;
 } XRemoteSettingsStoreState;
 
 
 void xremote_popup_closed_callback(void* context);
 void xremote_popup_closed_callback(void* context);
-void xremote_text_input_callback(void* context);
+void xremote_text_input_callback(void* context);
+void xremote_ir_enable_otg(XRemote* app, bool enable);
+void xremote_ir_set_tx_pin(XRemote* app);

+ 1 - 1
cross_remote/xremote_i.h

@@ -51,7 +51,7 @@
 #define XREMOTE_TEXT_STORE_SIZE 128
 #define XREMOTE_TEXT_STORE_SIZE 128
 #define XREMOTE_MAX_ITEM_NAME_LENGTH 22
 #define XREMOTE_MAX_ITEM_NAME_LENGTH 22
 #define XREMOTE_MAX_REMOTE_NAME_LENGTH 22
 #define XREMOTE_MAX_REMOTE_NAME_LENGTH 22
-#define XREMOTE_VERSION "3.0"
+#define XREMOTE_VERSION FAP_VERSION
 
 
 #define INFRARED_APP_EXTENSION ".ir"
 #define INFRARED_APP_EXTENSION ".ir"
 #define INFRARED_APP_FOLDER EXT_PATH("infrared")
 #define INFRARED_APP_FOLDER EXT_PATH("infrared")

+ 1 - 1
dtmf_dolphin/.gitsubtree

@@ -1,2 +1,2 @@
-https://github.com/xMasterX/all-the-plugins dev base_pack/dtmf_dolphin 4558d74c9da36abc851edd96a95d18f7d5511a75
+https://github.com/xMasterX/all-the-plugins dev base_pack/dtmf_dolphin 8bc18d5d99262484548b6bbc92690a1472c329a0
 https://github.com/litui/dtmf_dolphin main /
 https://github.com/litui/dtmf_dolphin main /

+ 7 - 0
dtmf_dolphin/LICENSE

@@ -2,6 +2,7 @@
 
 
 DTMFDolphinAudio* current_player;
 DTMFDolphinAudio* current_player;
 
 
+// Interrupt handler. Read DMA flag and put correspondint event to queue. 
 static void dtmf_dolphin_audio_dma_isr(void* ctx) {
 static void dtmf_dolphin_audio_dma_isr(void* ctx) {
     FuriMessageQueue* event_queue = ctx;
     FuriMessageQueue* event_queue = ctx;
 
 
@@ -216,8 +217,10 @@ bool dtmf_dolphin_audio_play_tones(
 
 
     dtmf_dolphin_dma_init((uint32_t)current_player->sample_buffer, current_player->buffer_length);
     dtmf_dolphin_dma_init((uint32_t)current_player->sample_buffer, current_player->buffer_length);
 
 
+    // Set interrupt handler.
     furi_hal_interrupt_set_isr(
     furi_hal_interrupt_set_isr(
         FuriHalInterruptIdDma1Ch1, dtmf_dolphin_audio_dma_isr, current_player->queue);
         FuriHalInterruptIdDma1Ch1, dtmf_dolphin_audio_dma_isr, current_player->queue);
+
     if(furi_hal_speaker_acquire(1000)) {
     if(furi_hal_speaker_acquire(1000)) {
         dtmf_dolphin_speaker_init();
         dtmf_dolphin_speaker_init();
         dtmf_dolphin_dma_start();
         dtmf_dolphin_dma_start();
@@ -244,6 +247,10 @@ bool dtmf_dolphin_audio_stop_tones() {
     dtmf_dolphin_dma_stop();
     dtmf_dolphin_dma_stop();
     furi_hal_speaker_release();
     furi_hal_speaker_release();
 
 
+    // Reset GPIO pin and bus states
+    dtmf_dolphin_gpio_deinit();
+
+    //Release interrrupt
     furi_hal_interrupt_set_isr(FuriHalInterruptIdDma1Ch1, NULL, NULL);
     furi_hal_interrupt_set_isr(FuriHalInterruptIdDma1Ch1, NULL, NULL);
 
 
     dtmf_dolphin_audio_free(current_player);
     dtmf_dolphin_audio_free(current_player);

+ 19 - 1
dtmf_dolphin/dtmf_dolphin_audio.h

@@ -11,6 +11,24 @@ void dtmf_dolphin_speaker_init() {
     TIM_OC_InitStruct.OCState = LL_TIM_OCSTATE_ENABLE;
     TIM_OC_InitStruct.OCState = LL_TIM_OCSTATE_ENABLE;
     TIM_OC_InitStruct.CompareValue = 127;
     TIM_OC_InitStruct.CompareValue = 127;
     LL_TIM_OC_Init(FURI_HAL_SPEAKER_TIMER, FURI_HAL_SPEAKER_CHANNEL, &TIM_OC_InitStruct);
     LL_TIM_OC_Init(FURI_HAL_SPEAKER_TIMER, FURI_HAL_SPEAKER_CHANNEL, &TIM_OC_InitStruct);
+
+    // Enable GPIO output
+    // Enable bus
+    furi_hal_bus_enable(FuriHalBusTIM2);
+
+    //configuring PA6 pin as TIM16 output
+    furi_hal_gpio_init_ex(
+        &gpio_ext_pa6,
+        GpioModeAltFunctionPushPull,
+        GpioPullNo,
+        GpioSpeedVeryHigh,
+        GpioAltFn14TIM16);
+}
+
+void dtmf_dolphin_gpio_deinit() {
+    // Disable GPIO output 
+    furi_hal_gpio_init(&gpio_ext_pa6, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
+    furi_hal_bus_disable(FuriHalBusTIM2);
 }
 }
 
 
 void dtmf_dolphin_speaker_start() {
 void dtmf_dolphin_speaker_start() {
@@ -24,7 +42,7 @@ void dtmf_dolphin_speaker_stop() {
 }
 }
 
 
 void dtmf_dolphin_dma_init(uint32_t address, size_t size) {
 void dtmf_dolphin_dma_init(uint32_t address, size_t size) {
-    uint32_t dma_dst = (uint32_t) & (FURI_HAL_SPEAKER_TIMER->CCR1);
+    uint32_t dma_dst = (uint32_t)&(FURI_HAL_SPEAKER_TIMER->CCR1);
 
 
     LL_DMA_ConfigAddresses(DMA_INSTANCE, address, dma_dst, LL_DMA_DIRECTION_MEMORY_TO_PERIPH);
     LL_DMA_ConfigAddresses(DMA_INSTANCE, address, dma_dst, LL_DMA_DIRECTION_MEMORY_TO_PERIPH);
     LL_DMA_SetDataLength(DMA_INSTANCE, size);
     LL_DMA_SetDataLength(DMA_INSTANCE, size);

+ 2 - 0
dtmf_dolphin/dtmf_dolphin_hal.h

@@ -28,6 +28,8 @@ void dtmf_dolphin_dma_start();
 
 
 void dtmf_dolphin_dma_stop();
 void dtmf_dolphin_dma_stop();
 
 
+void dtmf_dolphin_gpio_deinit();
+
 #ifdef __cplusplus
 #ifdef __cplusplus
 }
 }
 #endif
 #endif

+ 1 - 0
dtmf_dolphin/dtmf_dolphin_i.h

@@ -158,6 +158,7 @@ static void dtmf_dolphin_dialer_draw_callback(Canvas* canvas, void* _model) {
     if(model->gap_ms) {
     if(model->gap_ms) {
         furi_string_cat_printf(output, "Gaps: %u ms\n", model->gap_ms);
         furi_string_cat_printf(output, "Gaps: %u ms\n", model->gap_ms);
     }
     }
+    furi_string_cat_printf(output, "GPIO: PA6");
     elements_multiline_text(
     elements_multiline_text(
         canvas, (max_span * DTMF_DOLPHIN_BUTTON_WIDTH) + 4, 21, furi_string_get_cstr(output));
         canvas, (max_span * DTMF_DOLPHIN_BUTTON_WIDTH) + 4, 21, furi_string_get_cstr(output));
 
 

+ 2 - 0
dtmf_dolphin/views/dtmf_dolphin_dialer.h

@@ -13,6 +13,8 @@
 
 
 #define TAG "EthView"
 #define TAG "EthView"
 
 
+
+
 EthViewProcess* ethernet_view_process_malloc(EthWorkerProcess type, EthernetSaveConfig* config) {
 EthViewProcess* ethernet_view_process_malloc(EthWorkerProcess type, EthernetSaveConfig* config) {
     EthViewProcess* evp = malloc(sizeof(EthViewProcess));
     EthViewProcess* evp = malloc(sizeof(EthViewProcess));
     evp->type = type;
     evp->type = type;

+ 14 - 0
eth_troubleshooter/eth_view_process.h

@@ -57,3 +57,17 @@ typedef struct EthViewDrawPing {
     uint8_t current_digit;
     uint8_t current_digit;
     uint8_t* ip;
     uint8_t* ip;
 } EthViewDrawPing;
 } EthViewDrawPing;
+
+typedef struct
+{
+    uint8_t hop_number;
+    uint8_t ip[4];
+    uint16_t rtt_ms; // Round-trip time in milliseconds
+} TracerouteHop;
+
+typedef struct
+{
+    TracerouteHop hops[30]; // Maximum hops, adjust as needed
+    uint8_t hop_count;      // Number of hops recorded
+} TracerouteResult;
+

+ 2 - 0
eth_troubleshooter/eth_worker.c

@@ -24,6 +24,7 @@ EthWorker* eth_worker_alloc() {
     worker->dhcp_process = ethernet_view_process_malloc(EthWorkerProcessDHCP, worker->config);
     worker->dhcp_process = ethernet_view_process_malloc(EthWorkerProcessDHCP, worker->config);
     worker->stat_process = ethernet_view_process_malloc(EthWorkerProcessStatic, worker->config);
     worker->stat_process = ethernet_view_process_malloc(EthWorkerProcessStatic, worker->config);
     worker->ping_process = ethernet_view_process_malloc(EthWorkerProcessPing, worker->config);
     worker->ping_process = ethernet_view_process_malloc(EthWorkerProcessPing, worker->config);
+    worker->traceroute_process = ethernet_view_process_malloc(EthWorkerProcessTraceroute, worker->config);
     worker->reset_process = ethernet_view_process_malloc(EthWorkerProcessReset, worker->config);
     worker->reset_process = ethernet_view_process_malloc(EthWorkerProcessReset, worker->config);
     worker->active_process = worker->init_process;
     worker->active_process = worker->init_process;
 
 
@@ -51,6 +52,7 @@ void eth_worker_free(EthWorker* worker) {
     ethernet_view_process_free(worker->dhcp_process);
     ethernet_view_process_free(worker->dhcp_process);
     ethernet_view_process_free(worker->stat_process);
     ethernet_view_process_free(worker->stat_process);
     ethernet_view_process_free(worker->ping_process);
     ethernet_view_process_free(worker->ping_process);
+    ethernet_view_process_free(worker->traceroute_process);
     ethernet_view_process_free(worker->reset_process);
     ethernet_view_process_free(worker->reset_process);
     ethernet_save_process_free(worker->config);
     ethernet_save_process_free(worker->config);
 
 

+ 6 - 3
eth_troubleshooter/eth_worker.h

@@ -22,11 +22,13 @@ typedef enum {
     EthWorkerStateReset,
     EthWorkerStateReset,
 } EthWorkerState;
 } EthWorkerState;
 
 
-typedef enum {
+typedef enum
+{
     EthWorkerProcessInit,
     EthWorkerProcessInit,
     EthWorkerProcessDHCP,
     EthWorkerProcessDHCP,
     EthWorkerProcessStatic,
     EthWorkerProcessStatic,
     EthWorkerProcessPing,
     EthWorkerProcessPing,
+    EthWorkerProcessTraceroute,
     EthWorkerProcessReset,
     EthWorkerProcessReset,
     EthWorkerProcessActive,
     EthWorkerProcessActive,
     EthWorkerProcessExit,
     EthWorkerProcessExit,
@@ -74,5 +76,6 @@ void eth_worker_w5500(EthWorker* eth_worker);
 void eth_worker_init_process(EthWorker* eth_worker);
 void eth_worker_init_process(EthWorker* eth_worker);
 
 
 #define PING_SOCKET 1
 #define PING_SOCKET 1
-uint8_t ping_auto_interface(uint8_t* address);
-void dhcp_timer_callback(void* context);
+uint8_t ping_auto_interface(uint8_t* adress);
+uint8_t traceroute_auto(uint8_t *adress); 
+void dhcp_timer_callback(void *context);

+ 1 - 0
eth_troubleshooter/eth_worker_i.h

@@ -13,6 +13,7 @@ struct EthWorker {
     EthViewProcess* dhcp_process;
     EthViewProcess* dhcp_process;
     EthViewProcess* stat_process;
     EthViewProcess* stat_process;
     EthViewProcess* ping_process;
     EthViewProcess* ping_process;
+    EthViewProcess* traceroute_process;
     EthViewProcess* reset_process;
     EthViewProcess* reset_process;
     EthViewProcess* active_process;
     EthViewProcess* active_process;
 
 

+ 2 - 2
eth_troubleshooter/eth_worker_ping.c

@@ -6,6 +6,6 @@ void ping_wait_ms(int ms) {
     furi_delay_ms(ms);
     furi_delay_ms(ms);
 }
 }
 
 
-uint8_t ping_auto_interface(uint8_t* address) {
-    return ping_auto(PING_SOCKET, address);
+uint8_t ping_auto_interface(uint8_t* adress) {
+    return ping_auto(PING_SOCKET, adress);
 }
 }

+ 8 - 0
eth_troubleshooter/eth_worker_traceroute.c

@@ -0,0 +1,8 @@
+#include "eth_worker.h"
+#include <furi_hal.h>
+#include <traceroute.h>
+
+uint8_t traceroute_auto(uint8_t *adress)
+{
+    return traceroute(PING_SOCKET, adress);
+}

+ 114 - 0
eth_troubleshooter/lib/ioLibrary_Driver/Internet/ICMP/traceroute.c

@@ -0,0 +1,114 @@
+#include "traceroute.h"
+#include "ping.h"
+#include <string.h>
+
+static uint16_t RandomID = 0x1234;
+static uint16_t RandomSeqNum = 0x4321;
+
+uint16_t port = PORT; // Store the port number in a variable
+
+uint8_t traceroute(uint8_t s, uint8_t *dest_addr)
+{
+    uint8_t ttl = 1;
+    int ret;
+
+    while (ttl <= MAX_HOPS)
+    {
+        uint8_t sr = getSn_SR(s);
+        eth_printf("SR: %02X", sr);
+
+        switch (sr)
+        {
+        case SOCK_CLOSED:
+            close(s);
+            IINCHIP_WRITE(Sn_PROTO(s), IPPROTO_ICMP); // Set ICMP Protocol
+            if ((ret = socket(s, Sn_MR_IPRAW, 3000, 0)) != s)
+            {
+                eth_printf("Socket %d failed: %d", s, ret);
+                return SOCKET_ERROR;
+            }
+            while (getSn_SR(s) != SOCK_IPRAW)
+                ;
+            ping_wait_ms(500);
+            break;
+
+        case SOCK_IPRAW:
+            send_traceroute_request(s, dest_addr, ttl);
+
+            for (int i = 0; i < 40; i++)
+            { // Wait up to 2 seconds
+                uint16_t len = getSn_RX_RSR(s);
+                if (len > 0)
+                {
+                    uint8_t recv_addr[4];
+                    receive_traceroute_reply(s, recv_addr, len);
+
+                    eth_printf("Hop %d: %d.%d.%d.%d", ttl, recv_addr[0], recv_addr[1], recv_addr[2], recv_addr[3]);
+                    if (memcmp(recv_addr, dest_addr, 4) == 0)
+                    {
+                        eth_printf("Destination reached: %d.%d.%d.%d", recv_addr[0], recv_addr[1], recv_addr[2], recv_addr[3]);
+                        return 0; // Destination reached
+                    }
+                    break;
+                }
+                ping_wait_ms(50);
+            }
+            break;
+
+        default:
+            break;
+        }
+
+        ttl++;
+        if (ttl > MAX_HOPS)
+        {
+            eth_printf("Max hops reached.");
+            break;
+        }
+    }
+    return FUNCTION_ERROR;
+}
+
+void send_traceroute_request(uint8_t s, uint8_t *dest_addr, uint8_t ttl)
+{
+    PINGMSGR PingRequest;
+
+    PingRequest.Type = PING_REQUEST;
+    PingRequest.Code = CODE_ZERO;
+    PingRequest.ID = htons(RandomID++);
+    PingRequest.SeqNum = htons(RandomSeqNum++);
+
+    for (int i = 0; i < BUF_LEN; i++)
+    {
+        PingRequest.Data[i] = (i) % 8;
+    }
+
+    PingRequest.CheckSum = 0;
+    PingRequest.CheckSum = htons(checksum((uint8_t *)&PingRequest, sizeof(PingRequest)));
+
+    set_ttl(s, ttl);                                                          // Set TTL
+    sendto(s, (uint8_t *)&PingRequest, sizeof(PingRequest), dest_addr, port); // Use the port variable
+
+    eth_printf("Sent traceroute request (TTL: %d) to %d.%d.%d.%d", ttl, dest_addr[0], dest_addr[1], dest_addr[2], dest_addr[3]);
+}
+
+void receive_traceroute_reply(uint8_t s, uint8_t *addr, uint16_t len)
+{
+    uint8_t data_buf[128];
+
+    uint16_t rlen = recvfrom(s, data_buf, len, addr, &port); // Pass the pointer to the port variable
+    (void)rlen;
+    if (data_buf[0] == PING_REPLY)
+    {
+        eth_printf("Reply from %d.%d.%d.%d", addr[0], addr[1], addr[2], addr[3]);
+    }
+    else if (data_buf[0] == 11)
+    { // ICMP Time Exceeded
+        eth_printf("Time Exceeded from %d.%d.%d.%d", addr[0], addr[1], addr[2], addr[3]);
+    }
+}
+
+void set_ttl(uint8_t s, uint8_t ttl)
+{
+    IINCHIP_WRITE(Sn_TTL(s), ttl);
+}

+ 21 - 0
eth_troubleshooter/lib/ioLibrary_Driver/Internet/ICMP/traceroute.h

@@ -0,0 +1,21 @@
+#ifndef TRACEROUTE_H
+#define TRACEROUTE_H
+
+#include "wizchip_conf.h"
+#include "ping.h"
+#include "socket.h"
+#include "w5500.h"
+
+#define MAX_HOPS 30
+#define PORT 33434 // Keep the macro definition;
+#define BUF_LEN 32
+
+extern void ping_wait_ms(int ms);
+
+uint8_t traceroute(uint8_t s, uint8_t *dest_addr);
+void send_traceroute_request(uint8_t s, uint8_t *dest_addr, uint8_t ttl);
+void receive_traceroute_reply(uint8_t s, uint8_t *addr, uint16_t len);
+void set_ttl(uint8_t s, uint8_t ttl);
+// uint16_t checksum(uint8_t *data_buf, uint16_t len);
+
+#endif // TRACEROUTE_H

+ 1 - 1
ir_intervalometer/.gitsubtree

@@ -1,2 +1,2 @@
-https://github.com/xMasterX/all-the-plugins dev apps_source_code/ir_intervalometer 4558d74c9da36abc851edd96a95d18f7d5511a75
+https://github.com/xMasterX/all-the-plugins dev apps_source_code/ir_intervalometer 8bc18d5d99262484548b6bbc92690a1472c329a0
 https://github.com/Nitepone/flipper-intervalometer main /
 https://github.com/Nitepone/flipper-intervalometer main /

+ 1 - 1
key_copier/.gitsubtree

@@ -1,2 +1,2 @@
-https://github.com/xMasterX/all-the-plugins dev non_catalog_apps/key_copier ffa18100afc128abea7269fcc31d9c237523e7c6
+https://github.com/xMasterX/all-the-plugins dev non_catalog_apps/key_copier 8bc18d5d99262484548b6bbc92690a1472c329a0
 https://github.com/zinongli/KeyCopier main /
 https://github.com/zinongli/KeyCopier main /

+ 2 - 2
key_copier/application.fam

@@ -12,6 +12,6 @@ App(
     fap_category="Tools",
     fap_category="Tools",
     fap_icon_assets="assets",
     fap_icon_assets="assets",
     fap_description="@README.md",
     fap_description="@README.md",
-    fap_version="1.1",
-    fap_author="Torron",
+    fap_version="1.2",
+    fap_author="Torron"
 )
 )

+ 2 - 3
key_copier/key_copier.c

@@ -675,7 +675,7 @@ static KeyCopierApp* key_copier_app_alloc() {
     app->dialogs = furi_record_open(RECORD_DIALOGS);
     app->dialogs = furi_record_open(RECORD_DIALOGS);
     app->file_path = furi_string_alloc();
     app->file_path = furi_string_alloc();
     app->submenu = submenu_alloc();
     app->submenu = submenu_alloc();
-    submenu_set_header(app->submenu, "Key Copier v1.1");
+    submenu_set_header(app->submenu, "Key Copier v1.2");
     submenu_add_item(
     submenu_add_item(
         app->submenu,
         app->submenu,
         "Select Template",
         "Select Template",
@@ -701,7 +701,6 @@ static KeyCopierApp* key_copier_app_alloc() {
         app->view_dispatcher, KeyCopierViewTextInput, text_input_get_view(app->text_input));
         app->view_dispatcher, KeyCopierViewTextInput, text_input_get_view(app->text_input));
     app->temp_buffer_size = 32;
     app->temp_buffer_size = 32;
     app->temp_buffer = (char*)malloc(app->temp_buffer_size);
     app->temp_buffer = (char*)malloc(app->temp_buffer_size);
-    app->temp_buffer = "";
 
 
     app->view_measure = view_alloc();
     app->view_measure = view_alloc();
     view_set_draw_callback(app->view_measure, key_copier_view_measure_draw_callback);
     view_set_draw_callback(app->view_measure, key_copier_view_measure_draw_callback);
@@ -743,7 +742,7 @@ static KeyCopierApp* key_copier_app_alloc() {
         0,
         0,
         128,
         128,
         64,
         64,
-        "Key Maker App 1.1\nAuthor: @Torron\n\nTo measure your key:\n\n1. Place "
+        "Key Maker App 1.2\nAuthor: @Torron\n\nTo measure your key:\n\n1. Place "
         "it on top of the screen.\n\n2. Use the contour to align your key.\n\n3. "
         "it on top of the screen.\n\n2. Use the contour to align your key.\n\n3. "
         "Adjust each pin's depth until they match. It's easier if you look with "
         "Adjust each pin's depth until they match. It's easier if you look with "
         "one eye closed.\n\nGithub: github.com/zinongli/KeyCopier \n\nSpecial "
         "one eye closed.\n\nGithub: github.com/zinongli/KeyCopier \n\nSpecial "

BIN
key_copier/screenshots/config.png


BIN
key_copier/screenshots/main_menu.png


+ 5 - 0
metroflip/CHANGELOG.md

@@ -37,3 +37,8 @@ Big update!
 - Unified Calypso Parser: A new unified Calypso parser has been introduced (thanks to DocSystem), streamlining Calypso card support.
 - Unified Calypso Parser: A new unified Calypso parser has been introduced (thanks to DocSystem), streamlining Calypso card support.
 - RavKav Moved to Calypso Parser: RavKav has been moved to the new unified Calypso parser (credit to luu176).
 - RavKav Moved to Calypso Parser: RavKav has been moved to the new unified Calypso parser (credit to luu176).
 
 
+## v0.6
+
+- Added a load mode and a save mode to store card info
+- Fixed a major bug due to API symbol not existing
+

+ 27 - 27
metroflip/README.md

@@ -67,38 +67,38 @@ This is a list of metro cards and transit systems that need support or have part
 
 
 ## ✅ Supported Cards
 ## ✅ Supported Cards
 
 
-| **Card / Agency**  | **Country / City**                          | **Card Type**     |
-|--------------------|---------------------------------------------|-------------------|
-| **Bip!**           | 🇨🇱 Santiago de Chile, Chile                | Mifare Classic    |
-| **Charliecard**    | 🇺🇸 Boston, MA, USA                        | Mifare Classic    |
-| **Clipper**        | 🇺🇸 San Francisco, CA, USA                  | Mifare DESFire    |
-| **ITSO**           | 🇬🇧 United Kingdom                          | Mifare DESFire    |
-| **Metromoney**     | 🇬🇪 Tbilisi, Georgia                        | Mifare Classic    |
-| **myki**           | 🇦🇺 Melbourne (and surrounds), VIC, Australia | Mifare DESFire    |
-| **Navigo**         | 🇫🇷 Paris, France                           | Calypso           |
-| **Opal**           | 🇦🇺 Sydney (and surrounds), NSW, Australia  | Mifare DESFire    |
-| **Opus**           | 🇨🇦 Montreal, QC, Canada                    | Calypso           |
-| **Rav-Kav**        | 🇮🇱 Israel                                  | Calypso           |
-| **SmartRider**     | :australia: Western Australia, Australia   | Mifare Classic    |
-| **Troika**         | 🇷🇺 Moscow, Russia                          | Mifare Classic    |
+| **Card / Agency**  | **City / Country**                           | **Card Type**     |
+|--------------------|----------------------------------------------|-------------------|
+| **Bip!**           | 🇨🇱 Santiago de Chile, Chile                  | MIFARE Classic    |
+| **Charliecard**    | 🇺🇸 Boston, MA, USA                           | MIFARE Classic    |
+| **Clipper**        | 🇺🇸 San Francisco, CA, USA                    | MIFARE DESFire    |
+| **ITSO**           | 🇬🇧 United Kingdom                            | MIFARE DESFire    |
+| **Metromoney**     | 🇬🇪 Tbilisi, Georgia                          | MIFARE Classic    |
+| **myki**           | 🇦🇺 Melbourne (and surrounds), VIC, Australia | MIFARE DESFire    |
+| **Navigo**         | 🇫🇷 Paris, France                             | Calypso           |
+| **Opal**           | 🇦🇺 Sydney (and surrounds), NSW, Australia    | MIFARE DESFire    |
+| **Opus**           | 🇨🇦 Montreal, QC, Canada                      | Calypso           |
+| **Rav-Kav**        | 🇮🇱 Israel                                    | Calypso           |
+| **SmartRider**     | 🇦🇺 Perth, WA, Australia                      | MIFARE Classic    |
+| **Troika**         | 🇷🇺 Moscow, Russia                            | MIFARE Classic    |
 
 
 
 
 ---
 ---
 
 
 # Credits
 # Credits
-- **App Author**: [@luu176](https://github.com/luu176)
-- **Charliecard Parser**: [@zacharyweiss](https://github.com/zacharyweiss)
-- **Rav-Kav Parser**: [@luu176](https://github.com/luu176)
-- **Navigo Parser**: [@luu176](https://github.com/luu176), [@DocSystem](https://github.com/DocSystem)
-- **Opus Parser**: [@DocSystem](https://github.com/DocSystem)
-- **Metromoney Parser**: [@Leptopt1los](https://github.com/Leptopt1los)
-- **Bip! Parser**: [@rbasoalto](https://github.com/rbasoalto), [@gornekich](https://github.com/gornekich)
-- **Clipper Parser**: [@ke6jjj](https://github.com/ke6jjj)
-- **Troika Parser**: [@gornekich](https://github.com/gornekich)
-- **Myki Parser**: [@gornekich](https://github.com/gornekich)
-- **Opal Parser**: [@gornekich](https://github.com/gornekich)
-- **ITSO Parser**: [@gsp8181](https://github.com/gsp8181), [@hedger](https://github.com/hedger), [@gornekich](https://github.com/gornekich)
-- **Info Slaves**: [@equipter](https://github.com/equipter), [TheDingo8MyBaby](https://github.com/TheDingo8MyBaby)
+- **App Author:** [@luu176](https://github.com/luu176)
+- **Info Slaves:** [@equipter](https://github.com/equipter), [TheDingo8MyBaby](https://github.com/TheDingo8MyBaby)
+- **Bip! Parser:** [@rbasoalto](https://github.com/rbasoalto), [@gornekich](https://github.com/gornekich)
+- **Charliecard Parser:** [@zacharyweiss](https://github.com/zacharyweiss)
+- **Clipper Parser:** [@ke6jjj](https://github.com/ke6jjj)
+- **ITSO Parser:** [@gsp8181](https://github.com/gsp8181), [@hedger](https://github.com/hedger), [@gornekich](https://github.com/gornekich)
+- **Metromoney Parser:** [@Leptopt1los](https://github.com/Leptopt1los)
+- **myki Parser:** [@gornekich](https://github.com/gornekich)
+- **Navigo Parser:** [@luu176](https://github.com/luu176), [@DocSystem](https://github.com/DocSystem)
+- **Opal Parser:** [@gornekich](https://github.com/gornekich)
+- **Opus Parser:** [@DocSystem](https://github.com/DocSystem)
+- **Rav-Kav Parser:** [@luu176](https://github.com/luu176)
+- **Troika Parser:** [@gornekich](https://github.com/gornekich)
 
 
 ---
 ---
 
 

+ 4 - 0
metroflip/api/calypso/transit/ravkav.c

@@ -20,6 +20,10 @@ const char* get_ravkav_issuer(int issuer) {
 
 
 void show_ravkav_contract_info(RavKavCardContract* contract, FuriString* parsed_data) {
 void show_ravkav_contract_info(RavKavCardContract* contract, FuriString* parsed_data) {
     // Core contract validity period
     // Core contract validity period
+    if(contract->balance != 0.0f) {
+        furi_string_cat_printf(parsed_data, "Balance: %.2f ILS\n", (double)contract->balance);
+    }
+
     furi_string_cat_printf(parsed_data, "Valid from: ");
     furi_string_cat_printf(parsed_data, "Valid from: ");
     locale_format_datetime_cat(parsed_data, &contract->start_date, false);
     locale_format_datetime_cat(parsed_data, &contract->start_date, false);
     if(contract->end_date_available) {
     if(contract->end_date_available) {

+ 1 - 0
metroflip/api/calypso/transit/ravkav_i.h

@@ -23,6 +23,7 @@ typedef struct {
     bool end_date_available;
     bool end_date_available;
 
 
     bool present;
     bool present;
+    float balance;
 } RavKavCardContract;
 } RavKavCardContract;
 
 
 typedef struct {
 typedef struct {

+ 16 - 0
metroflip/api/metroflip/metroflip_api.h

@@ -23,6 +23,8 @@ extern "C" {
 // metroflip
 // metroflip
 
 
 void metroflip_exit_widget_callback(GuiButtonType result, InputType type, void* context);
 void metroflip_exit_widget_callback(GuiButtonType result, InputType type, void* context);
+void metroflip_save_widget_callback(GuiButtonType result, InputType type, void* context);
+void metroflip_delete_widget_callback(GuiButtonType result, InputType type, void* context);
 
 
 void metroflip_app_blink_start(Metroflip* metroflip);
 void metroflip_app_blink_start(Metroflip* metroflip);
 
 
@@ -132,6 +134,20 @@ void show_ravkav_event_info(RavKavCardEvent* event, FuriString* parsed_data);
 void show_ravkav_contract_info(RavKavCardContract* contract, FuriString* parsed_data);
 void show_ravkav_contract_info(RavKavCardContract* contract, FuriString* parsed_data);
 
 
 void show_ravkav_environment_info(RavKavCardEnv* environment, FuriString* parsed_data);
 void show_ravkav_environment_info(RavKavCardEnv* environment, FuriString* parsed_data);
+
+extern const Icon I_RFIDDolphinReceive_97x61;
+extern const Icon I_icon;
+extern const Icon I_DolphinDone_80x58;
+extern const Icon I_WarningDolphinFlip_45x42;
+extern const Icon I_DolphinMafia_119x62;
+
+void render_section_header(
+    FuriString* str,
+    const char* name,
+    uint8_t prefix_separator_cnt,
+    uint8_t suffix_separator_cnt);
+bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString* result);
+
 /*******************/
 /*******************/
 #ifdef __cplusplus
 #ifdef __cplusplus
 }
 }

+ 11 - 1
metroflip/api/metroflip/metroflip_api_table_i.h

@@ -9,6 +9,8 @@
 static constexpr auto metroflip_api_table = sort(create_array_t<sym_entry>(
 static constexpr auto metroflip_api_table = sort(create_array_t<sym_entry>(
     // metroflip stuff
     // metroflip stuff
     API_METHOD(metroflip_exit_widget_callback, void, (GuiButtonType, InputType, void*)),
     API_METHOD(metroflip_exit_widget_callback, void, (GuiButtonType, InputType, void*)),
+    API_METHOD(metroflip_save_widget_callback, void, (GuiButtonType, InputType, void*)),
+    API_METHOD(metroflip_delete_widget_callback, void, (GuiButtonType, InputType, void*)),
     API_METHOD(metroflip_app_blink_start, void, (Metroflip*)),
     API_METHOD(metroflip_app_blink_start, void, (Metroflip*)),
     API_METHOD(metroflip_app_blink_stop, void, (Metroflip*)),
     API_METHOD(metroflip_app_blink_stop, void, (Metroflip*)),
     API_METHOD(bit_slice_to_dec, int, (const char*, int, int)),
     API_METHOD(bit_slice_to_dec, int, (const char*, int, int)),
@@ -68,4 +70,12 @@ static constexpr auto metroflip_api_table = sort(create_array_t<sym_entry>(
     API_METHOD(get_ravkav_env_holder_structure, CalypsoApp*, ()),
     API_METHOD(get_ravkav_env_holder_structure, CalypsoApp*, ()),
     API_METHOD(show_ravkav_event_info, void, (RavKavCardEvent*, FuriString*)),
     API_METHOD(show_ravkav_event_info, void, (RavKavCardEvent*, FuriString*)),
     API_METHOD(show_ravkav_contract_info, void, (RavKavCardContract*, FuriString*)),
     API_METHOD(show_ravkav_contract_info, void, (RavKavCardContract*, FuriString*)),
-    API_METHOD(show_ravkav_environment_info, void, (RavKavCardEnv*, FuriString*))));
+    API_METHOD(show_ravkav_environment_info, void, (RavKavCardEnv*, FuriString*)),
+
+    API_VARIABLE(I_RFIDDolphinReceive_97x61, Icon),
+    API_VARIABLE(I_icon, Icon),
+    API_METHOD(render_section_header, void, (FuriString*, const char*, uint8_t, uint8_t)),
+    API_METHOD(mosgortrans_parse_transport_block, bool, (const MfClassicBlock*, FuriString*)),
+    API_VARIABLE(I_WarningDolphinFlip_45x42, Icon),
+    API_VARIABLE(I_DolphinDone_80x58, Icon),
+    API_VARIABLE(I_DolphinMafia_119x62, Icon)));

+ 6 - 2
metroflip/api/mosgortrans/mosgortrans_util.c

@@ -1,5 +1,9 @@
-#include "mosgortrans_util.h"
-
+#include "../metroflip/metroflip_api.h"
+#include <bit_lib.h>
+#include <datetime.h>
+#include <furi/core/string.h>
+#include <nfc/protocols/mf_classic/mf_classic.h>
+#include <furi_hal_rtc.h>
 #define TAG "Metroflip:Scene:Mosgortrans"
 #define TAG "Metroflip:Scene:Mosgortrans"
 
 
 void render_section_header(
 void render_section_header(

+ 0 - 22
metroflip/api/mosgortrans/mosgortrans_util.h

@@ -1,22 +0,0 @@
-#pragma once
-
-#include <bit_lib.h>
-#include <datetime.h>
-#include <furi/core/string.h>
-#include <nfc/protocols/mf_classic/mf_classic.h>
-#include <furi_hal_rtc.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-void render_section_header(
-    FuriString* str,
-    const char* name,
-    uint8_t prefix_separator_cnt,
-    uint8_t suffix_separator_cnt);
-bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString* result);
-
-#ifdef __cplusplus
-}
-#endif

+ 3 - 3
metroflip/app/README.md

@@ -14,7 +14,7 @@ This is a list of metro cards and transit systems that are supported.
 - **Clipper**  
 - **Clipper**  
 - **ITSO**  
 - **ITSO**  
 - **Metromoney**  
 - **Metromoney**  
-- **Myki**  
+- **myki**  
 - **Navigo**  
 - **Navigo**  
 - **Opal**  
 - **Opal**  
 - **Opus**  
 - **Opus**  
@@ -32,8 +32,8 @@ More coming soon!
 - **Info Slaves**: equipter, TheDingo8MyBaby  
 - **Info Slaves**: equipter, TheDingo8MyBaby  
 - **ITSO Parser**: gsp8181, hedger, gornekich  
 - **ITSO Parser**: gsp8181, hedger, gornekich  
 - **Metromoney Parser**: Leptopt1los  
 - **Metromoney Parser**: Leptopt1los  
-- **Myki Parser**: gornekich  
+- **myki Parser**: gornekich  
 - **Navigo Parser**: luu176, DocSystem  
 - **Navigo Parser**: luu176, DocSystem  
 - **Opal Parser**: gornekich  
 - **Opal Parser**: gornekich  
 - **Rav-Kav Parser**: luu176  
 - **Rav-Kav Parser**: luu176  
-- **Troika Parser**: gornekich  
+- **Troika Parser**: gornekich  

+ 10 - 1
metroflip/application.fam

@@ -5,7 +5,7 @@ App(
     entry_point="metroflip",
     entry_point="metroflip",
     stack_size=2 * 1024,
     stack_size=2 * 1024,
     fap_category="NFC",
     fap_category="NFC",
-    fap_version="0.5",
+    fap_version="0.6",
     fap_icon="icon.png",
     fap_icon="icon.png",
     fap_description="An implementation of metrodroid on the flipper",
     fap_description="An implementation of metrodroid on the flipper",
     fap_author="luu176",
     fap_author="luu176",
@@ -100,3 +100,12 @@ App(
     sources=["scenes/plugins/troika.c"],
     sources=["scenes/plugins/troika.c"],
     fal_embedded=True,
     fal_embedded=True,
 )
 )
+
+App(
+    appid="gocard_plugin",
+    apptype=FlipperAppType.PLUGIN,
+    entry_point="gocard_plugin_ep",
+    requires=["metroflip"],
+    sources=["scenes/plugins/gocard.c"],
+    fal_embedded=True,
+)

BIN
metroflip/images/DolphinDone_80x58.png


BIN
metroflip/images/DolphinMafia_119x62.png


BIN
metroflip/images/WarningDolphinFlip_45x42.png


BIN
metroflip/images/icon.png


+ 2 - 2
metroflip/manifest.yml

@@ -15,8 +15,8 @@ screenshots:
 short_description: 'An implementation of Metrodroid on the Flipper Zero'
 short_description: 'An implementation of Metrodroid on the Flipper Zero'
 sourcecode:
 sourcecode:
   location:
   location:
-    commit_sha: 9bb6a0ac17640a08df176e340c1cc57663d6fe42
+    commit_sha: c4e1ed2304ad96a3565f83a58b3c7f8529f9d651
     origin: https://github.com/luu176/Metroflip
     origin: https://github.com/luu176/Metroflip
     subdir:
     subdir:
   type: git
   type: git
-version: 0.5
+version: 0.6

+ 27 - 2
metroflip/metroflip.c

@@ -78,6 +78,10 @@ Metroflip* metroflip_alloc() {
         app->view_dispatcher, MetroflipViewTextBox, text_box_get_view(app->text_box));
         app->view_dispatcher, MetroflipViewTextBox, text_box_get_view(app->text_box));
     app->text_box_store = furi_string_alloc();
     app->text_box_store = furi_string_alloc();
 
 
+    // Dialog for loading
+    app->dialogs = furi_record_open(RECORD_DIALOGS);
+
+    app->data_loaded = false;
     return app;
     return app;
 }
 }
 
 
@@ -121,6 +125,10 @@ void metroflip_free(Metroflip* app) {
 
 
     // Records
     // Records
     furi_record_close(RECORD_GUI);
     furi_record_close(RECORD_GUI);
+
+    // Dialogs
+    furi_record_close(RECORD_DIALOGS);
+
     free(app);
     free(app);
 }
 }
 
 
@@ -150,6 +158,25 @@ void metroflip_exit_widget_callback(GuiButtonType result, InputType type, void*
 
 
     if(type == InputTypeShort) {
     if(type == InputTypeShort) {
         scene_manager_search_and_switch_to_previous_scene(app->scene_manager, MetroflipSceneStart);
         scene_manager_search_and_switch_to_previous_scene(app->scene_manager, MetroflipSceneStart);
+        scene_manager_set_scene_state(app->scene_manager, MetroflipSceneStart, MetroflipSceneAuto);
+    }
+}
+
+void metroflip_save_widget_callback(GuiButtonType result, InputType type, void* context) {
+    Metroflip* app = context;
+    UNUSED(result);
+
+    if(type == InputTypeShort) {
+        scene_manager_next_scene(app->scene_manager, MetroflipSceneSave);
+    }
+}
+
+void metroflip_delete_widget_callback(GuiButtonType result, InputType type, void* context) {
+    Metroflip* app = context;
+    UNUSED(result);
+
+    if(type == InputTypeShort) {
+        scene_manager_next_scene(app->scene_manager, MetroflipSceneDelete);
     }
     }
 }
 }
 
 
@@ -296,9 +323,7 @@ KeyfileManager manage_keyfiles(
             return SUCCESSFUL;
             return SUCCESSFUL;
         }
         }
     } else {
     } else {
-        FURI_LOG_I("TAG", "testing 1");
         size_t source_file_length = storage_file_size(source);
         size_t source_file_length = storage_file_size(source);
-        FURI_LOG_I("TAG", "testing 2");
 
 
         storage_file_close(source);
         storage_file_close(source);
         mf_classic_key_cache_load(instance, uid, uid_len);
         mf_classic_key_cache_load(instance, uid, uid_len);

+ 17 - 7
metroflip/metroflip_i.h

@@ -9,11 +9,6 @@
 #include <gui/view_dispatcher.h>
 #include <gui/view_dispatcher.h>
 #include <gui/scene_manager.h>
 #include <gui/scene_manager.h>
 #include "api/nfc/mf_classic_key_cache.h"
 #include "api/nfc/mf_classic_key_cache.h"
-#if __has_include(<assets_icons.h>)
-#include <assets_icons.h>
-#else
-extern const Icon I_RFIDDolphinReceive_97x61;
-#endif
 #include <flipper_application/plugins/composite_resolver.h>
 #include <flipper_application/plugins/composite_resolver.h>
 #include <loader/firmware_api/firmware_api.h>
 #include <loader/firmware_api/firmware_api.h>
 #include <flipper_application/plugins/plugin_manager.h>
 #include <flipper_application/plugins/plugin_manager.h>
@@ -48,13 +43,15 @@ extern const Icon I_RFIDDolphinReceive_97x61;
 #include <strings.h>
 #include <strings.h>
 #include <flipper_application/flipper_application.h>
 #include <flipper_application/flipper_application.h>
 #include <loader/firmware_api/firmware_api.h>
 #include <loader/firmware_api/firmware_api.h>
+#include <applications/services/storage/storage.h>
+#include <applications/services/dialogs/dialogs.h>
 
 
 #include "scenes/metroflip_scene.h"
 #include "scenes/metroflip_scene.h"
 
 
 #include "api/calypso/calypso_i.h"
 #include "api/calypso/calypso_i.h"
 
 
 #define KEY_MASK_BIT_CHECK(key_mask_1, key_mask_2) (((key_mask_1) & (key_mask_2)) == (key_mask_1))
 #define KEY_MASK_BIT_CHECK(key_mask_1, key_mask_2) (((key_mask_1) & (key_mask_2)) == (key_mask_1))
-
+#define METROFLIP_FILE_EXTENSION                   ".nfc"
 typedef struct {
 typedef struct {
     Gui* gui;
     Gui* gui;
     SceneManager* scene_manager;
     SceneManager* scene_manager;
@@ -76,6 +73,11 @@ typedef struct {
     MfClassicKeyCache* mfc_key_cache;
     MfClassicKeyCache* mfc_key_cache;
     NfcDetectedProtocols* detected_protocols;
     NfcDetectedProtocols* detected_protocols;
     DesfireCardType desfire_card_type;
     DesfireCardType desfire_card_type;
+    MfDesfireData* mfdes_data;
+    MfClassicData* mfc_data;
+
+    // save stuff
+    char save_buf[248];
 
 
     //plugin manager
     //plugin manager
     PluginManager* plugin_manager;
     PluginManager* plugin_manager;
@@ -94,9 +96,16 @@ typedef struct {
     bool auto_mode;
     bool auto_mode;
     CardType mfc_card_type;
     CardType mfc_card_type;
     NfcProtocol protocol;
     NfcProtocol protocol;
+    const char* file_path;
+    char delete_file_path[256];
 
 
     // Calypso specific context
     // Calypso specific context
     CalypsoContext* calypso_context;
     CalypsoContext* calypso_context;
+
+    DialogsApp* dialogs;
+
+    bool data_loaded;
+
 } Metroflip;
 } Metroflip;
 
 
 enum MetroflipCustomEvent {
 enum MetroflipCustomEvent {
@@ -137,6 +146,7 @@ typedef enum {
     MetroflipViewTextBox,
     MetroflipViewTextBox,
     MetroflipViewWidget,
     MetroflipViewWidget,
     MetroflipViewUart,
     MetroflipViewUart,
+    MetroflipViewCanvas,
 } MetroflipView;
 } MetroflipView;
 
 
 typedef enum {
 typedef enum {
@@ -145,7 +155,7 @@ typedef enum {
     MISSING_KEYFILE
     MISSING_KEYFILE
 } KeyfileManager;
 } KeyfileManager;
 
 
-CardType determine_card_type(Nfc* nfc);
+CardType determine_card_type(Nfc* nfc, MfClassicData* mfc_data, bool data_loaded);
 
 
 #ifdef FW_ORIGIN_Official
 #ifdef FW_ORIGIN_Official
 #define submenu_add_lockable_item(                                             \
 #define submenu_add_lockable_item(                                             \

+ 303 - 0
metroflip/scenes/desfire.c

@@ -0,0 +1,303 @@
+#include <lib/nfc/protocols/mf_desfire/mf_desfire.h>
+#include "../metroflip_i.h"
+#include "desfire.h"
+#include <lib/toolbox/strint.h>
+#include <stdio.h>
+
+static const MfDesfireApplicationId opal_verify_app_id = {.data = {0x31, 0x45, 0x53}};
+
+static const MfDesfireFileId opal_verify_file_id = 0x07;
+
+static const MfDesfireApplicationId myki_verify_app_id = {.data = {0x00, 0x11, 0xf2}};
+
+static const MfDesfireFileId myki_verify_file_id = 0x0f;
+
+static const MfDesfireApplicationId itso_verify_app_id = {.data = {0x16, 0x02, 0xa0}};
+
+static const MfDesfireFileId itso_verify_file_id = 0x0f;
+
+uint64_t itso_swap_uint64(uint64_t val) {
+    val = ((val << 8) & 0xFF00FF00FF00FF00ULL) | ((val >> 8) & 0x00FF00FF00FF00FFULL);
+    val = ((val << 16) & 0xFFFF0000FFFF0000ULL) | ((val >> 16) & 0x0000FFFF0000FFFFULL);
+    return (val << 32) | (val >> 32);
+}
+
+static const struct {
+    const MfDesfireApplicationId app;
+    const char* type;
+} clipper_verify_types[] = {
+    // Application advertised on classic, plastic cards.
+    {.app = {.data = {0x90, 0x11, 0xf2}}, .type = "Card"},
+    // Application advertised on a mobile device.
+    {.app = {.data = {0x91, 0x11, 0xf2}}, .type = "Mobile Device"},
+};
+
+static const size_t kNumCardVerifyTypes =
+    sizeof(clipper_verify_types) / sizeof(clipper_verify_types[0]);
+
+// File ids of important files on the card.
+static const MfDesfireFileId clipper_ecash_file_id = 2;
+static const MfDesfireFileId clipper_histidx_file_id = 6;
+static const MfDesfireFileId clipper_identity_file_id = 8;
+static const MfDesfireFileId clipper_history_file_id = 14;
+
+static bool get_file_contents(
+    const MfDesfireApplication* app,
+    const MfDesfireFileId* id,
+    MfDesfireFileType type,
+    size_t min_size,
+    const uint8_t** out) {
+    const MfDesfireFileSettings* settings = mf_desfire_get_file_settings(app, id);
+    if(settings == NULL) return false;
+    if(settings->type != type) return false;
+
+    const MfDesfireFileData* file_data = mf_desfire_get_file_data(app, id);
+
+    if(file_data == NULL) return false;
+
+    if(simple_array_get_count(file_data->data) < min_size) return false;
+
+    *out = simple_array_cget_data(file_data->data);
+
+    return true;
+}
+
+struct ClipperVerifyCardInfo_struct {
+    uint32_t serial_number;
+    uint16_t counter;
+    uint16_t last_txn_id;
+    uint32_t last_updated_tm_1900;
+    uint16_t last_terminal_id;
+    int16_t balance_cents;
+};
+typedef struct ClipperVerifyCardInfo_struct ClipperVerifyCardInfo;
+
+// Opal file 0x7 structure. Assumes a little-endian CPU.
+typedef struct FURI_PACKED {
+    uint32_t serial         : 32;
+    uint8_t check_digit     : 4;
+    bool blocked            : 1;
+    uint16_t txn_number     : 16;
+    int32_t balance         : 21;
+    uint16_t days           : 15;
+    uint16_t minutes        : 11;
+    uint8_t mode            : 3;
+    uint16_t usage          : 4;
+    bool auto_topup         : 1;
+    uint8_t weekly_journeys : 4;
+    uint16_t checksum       : 16;
+} OpalVerifyFile;
+
+static_assert(sizeof(OpalVerifyFile) == 16, "OpalFile");
+
+bool opal_verify(const MfDesfireData* data) {
+    // Check if the card has the expected application
+    const MfDesfireApplication* app = mf_desfire_get_application(data, &opal_verify_app_id);
+    if(app == NULL) {
+        return false;
+    }
+
+    // Verify the file settings: must be of type standard and have the expected size
+    const MfDesfireFileSettings* file_settings =
+        mf_desfire_get_file_settings(app, &opal_verify_file_id);
+    if(file_settings == NULL || file_settings->type != MfDesfireFileTypeStandard ||
+       file_settings->data.size != sizeof(OpalVerifyFile)) {
+        return false;
+    }
+
+    // Check that the file data exists
+    const MfDesfireFileData* file_data = mf_desfire_get_file_data(app, &opal_verify_file_id);
+    if(file_data == NULL) {
+        return false;
+    }
+
+    // Retrieve the opal file from the file data
+    const OpalVerifyFile* opal_file = simple_array_cget_data(file_data->data);
+    if(opal_file == NULL) {
+        return false;
+    }
+
+    // Ensure the check digit is valid (i.e. 0..9)
+    if(opal_file->check_digit > 9) {
+        return false;
+    }
+
+    // All checks passed, return true
+    return true;
+}
+
+bool myki_verify(const MfDesfireData* data) {
+    // Check if the card contains the expected Myki application.
+    const MfDesfireApplication* app = mf_desfire_get_application(data, &myki_verify_app_id);
+    if(app == NULL) {
+        return false;
+    }
+
+    // Define the structure for Myki file data.
+    typedef struct {
+        uint32_t top;
+        uint32_t bottom;
+    } mykiFile;
+
+    // Verify file settings: must be present, of the correct type, and large enough to contain a mykiFile.
+    const MfDesfireFileSettings* file_settings =
+        mf_desfire_get_file_settings(app, &myki_verify_file_id);
+    if(file_settings == NULL || file_settings->type != MfDesfireFileTypeStandard ||
+       file_settings->data.size < sizeof(mykiFile)) {
+        return false;
+    }
+
+    // Verify that the file data is available.
+    const MfDesfireFileData* file_data = mf_desfire_get_file_data(app, &myki_verify_file_id);
+    if(file_data == NULL) {
+        return false;
+    }
+
+    // Retrieve the Myki file data from the file data array.
+    const mykiFile* myki_file = simple_array_cget_data(file_data->data);
+    if(myki_file == NULL) {
+        return false;
+    }
+
+    // Check that Myki card numbers are prefixed with "308425".
+    if(myki_file->top != 308425UL) {
+        return false;
+    }
+
+    // Card numbers are always 15 digits in length.
+    // The bottom field must be within [10000000, 100000000) to meet this requirement.
+    if(myki_file->bottom < 10000000UL || myki_file->bottom >= 100000000UL) {
+        return false;
+    }
+
+    // All checks passed.
+    return true;
+}
+
+bool itso_verify(const MfDesfireData* data) {
+    // Check if the card contains the expected ITSO application.
+    const MfDesfireApplication* app = mf_desfire_get_application(data, &itso_verify_app_id);
+    if(app == NULL) {
+        return false;
+    }
+
+    // Define the structure for ITSO file data.
+    typedef struct {
+        uint64_t part1;
+        uint64_t part2;
+        uint64_t part3;
+        uint64_t part4;
+    } ItsoFile;
+
+    // Verify file settings: must exist, be of standard type,
+    // and have a data size at least as large as an ItsoFile.
+    const MfDesfireFileSettings* file_settings =
+        mf_desfire_get_file_settings(app, &itso_verify_file_id);
+    if(file_settings == NULL || file_settings->type != MfDesfireFileTypeStandard ||
+       file_settings->data.size < sizeof(ItsoFile)) {
+        return false;
+    }
+
+    // Verify that the file data is available.
+    const MfDesfireFileData* file_data = mf_desfire_get_file_data(app, &itso_verify_file_id);
+    if(file_data == NULL) {
+        return false;
+    }
+
+    // Retrieve the ITSO file from the file data.
+    const ItsoFile* itso_file = simple_array_cget_data(file_data->data);
+    if(itso_file == NULL) {
+        return false;
+    }
+
+    // Swap bytes for the first two parts.
+    uint64_t x1 = itso_swap_uint64(itso_file->part1);
+    uint64_t x2 = itso_swap_uint64(itso_file->part2);
+
+    // Prepare buffers for card and date strings.
+    char cardBuff[32];
+    char dateBuff[18];
+
+    // Format the hex strings.
+    snprintf(cardBuff, sizeof(cardBuff), "%llx%llx", x1, x2);
+    snprintf(dateBuff, sizeof(dateBuff), "%llx", x2);
+
+    // Get pointer to the card number substring (skipping the first 4 characters).
+    char* cardp = cardBuff + 4;
+    cardp[18] = '\0'; // Ensure the substring is null-terminated.
+
+    // Verify that all ITSO card numbers are prefixed with "633597".
+    if(strncmp(cardp, "633597", 6) != 0) {
+        return false;
+    }
+
+    // Prepare the date string by advancing 12 characters.
+    char* datep = dateBuff + 12;
+    dateBuff[17] = '\0'; // Ensure termination of the date string.
+
+    // Convert the date portion (in hexadecimal) to a date stamp.
+    uint32_t dateStamp;
+    if(strint_to_uint32(datep, NULL, &dateStamp, 16) != StrintParseNoError) {
+        return false;
+    }
+
+    // (Optional) Calculate the Unix timestamp if needed:
+    // uint32_t unixTimestamp = dateStamp * 24 * 60 * 60 + 852076800U;
+
+    // All checks passed.
+    return true;
+}
+
+bool clipper_verify(const MfDesfireData* data) {
+    bool verified = false;
+
+    do {
+        FURI_LOG_I("clipper verify", "verifying..");
+        const MfDesfireApplication* app = NULL;
+
+        // Try each card type until a matching application is found.
+        for(size_t i = 0; i < kNumCardVerifyTypes; i++) {
+            app = mf_desfire_get_application(data, &clipper_verify_types[i].app);
+            if(app != NULL) {
+                break;
+            }
+        }
+        // If no matching application was found, verification fails.
+        if(app == NULL) {
+            break;
+        }
+
+        const uint8_t* id_data;
+        if(!get_file_contents(
+               app, &clipper_identity_file_id, MfDesfireFileTypeStandard, 5, &id_data)) {
+            break;
+        }
+
+        // Get the ecash file contents.
+        const uint8_t* cash_data;
+        if(!get_file_contents(
+               app, &clipper_ecash_file_id, MfDesfireFileTypeBackup, 32, &cash_data)) {
+            break;
+        }
+
+        // Retrieve ride history file contents.
+        const uint8_t* history_index;
+        const uint8_t* history;
+        if(!get_file_contents(
+               app, &clipper_histidx_file_id, MfDesfireFileTypeBackup, 16, &history_index)) {
+            break;
+        }
+        if(!get_file_contents(
+               app, &clipper_history_file_id, MfDesfireFileTypeStandard, 512, &history)) {
+            break;
+        }
+
+        // Use a dummy string to verify that the ride history can be decoded.
+        FuriString* dummy_str = furi_string_alloc();
+        furi_string_free(dummy_str);
+
+        verified = true;
+    } while(false);
+
+    return verified;
+}

+ 5 - 4
metroflip/scenes/desfire.h

@@ -2,6 +2,7 @@
 #define DESFIRE_H
 #define DESFIRE_H
 
 
 #include "../metroflip_i.h"
 #include "../metroflip_i.h"
+#include <lib/nfc/protocols/mf_desfire/mf_desfire.h>
 
 
 typedef enum {
 typedef enum {
     CARD_TYPE_ITSO,
     CARD_TYPE_ITSO,
@@ -11,9 +12,9 @@ typedef enum {
     CARD_TYPE_DESFIRE_UNKNOWN
     CARD_TYPE_DESFIRE_UNKNOWN
 } DesfireCardType;
 } DesfireCardType;
 
 
-bool itso_parse(const NfcDevice* device, FuriString* parsed_data);
-bool opal_parse(const NfcDevice* device, FuriString* parsed_data);
-bool clipper_parse(const NfcDevice* device, FuriString* parsed_data);
-bool myki_parse(const NfcDevice* device, FuriString* parsed_data);
+bool itso_verify(const MfDesfireData* data);
+bool opal_verify(const MfDesfireData* data);
+bool clipper_verify(const MfDesfireData* data);
+bool myki_verify(const MfDesfireData* data);
 
 
 #endif // DESFIRE_H
 #endif // DESFIRE_H

+ 191 - 94
metroflip/scenes/keys.c

@@ -9,11 +9,11 @@
 
 
 #define TAG "keys_check"
 #define TAG "keys_check"
 
 
-const MfClassicKeyPair troika_1k_key[1] = {
+const MfClassicKeyPair troika_1k_key[] = {
     {.a = 0x08b386463229},
     {.a = 0x08b386463229},
 };
 };
 
 
-const MfClassicKeyPair troika_4k_key[1] = {
+const MfClassicKeyPair troika_4k_key[] = {
     {.a = 0xA73F5DC1D333},
     {.a = 0xA73F5DC1D333},
 };
 };
 
 
@@ -25,111 +25,163 @@ const MfClassicKeyPair charliecard_1k_verify_key[] = {
     {.a = 0x5EC39B022F2B},
     {.a = 0x5EC39B022F2B},
 };
 };
 
 
-const MfClassicKeyPair bip_1k_verify_key[1] = {
+const MfClassicKeyPair bip_1k_verify_key[] = {
     {.a = 0x3a42f33af429},
     {.a = 0x3a42f33af429},
 };
 };
 
 
-const MfClassicKeyPair metromoney_1k_verify_key[1] = {
+const MfClassicKeyPair metromoney_1k_verify_key[] = {
     {.a = 0x9C616585E26D},
     {.a = 0x9C616585E26D},
 };
 };
 
 
-static bool charliecard_verify(Nfc* nfc) {
+const uint8_t gocard_verify_data[1][14] = {
+    {0x16, 0x18, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x5A, 0x5B, 0x20, 0x21, 0x22, 0x23}};
+
+const uint8_t gocard_verify_data2[1][14] = {
+    {0x16, 0x18, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x01, 0x01}};
+
+static bool charliecard_verify(Nfc* nfc, MfClassicData* mfc_data, bool data_loaded) {
     bool verified = false;
     bool verified = false;
     FURI_LOG_I(TAG, "verifying charliecard..");
     FURI_LOG_I(TAG, "verifying charliecard..");
-
+    const uint8_t verify_sector = 1;
     do {
     do {
-        const uint8_t verify_sector = 1;
-        const uint8_t verify_block = mf_classic_get_first_block_num_of_sector(verify_sector) + 1;
-        FURI_LOG_I(TAG, "Verifying sector %u", verify_sector);
-
-        MfClassicKey key = {0};
-        bit_lib_num_to_bytes_be(charliecard_1k_verify_key[0].a, COUNT_OF(key.data), key.data);
-
-        MfClassicAuthContext auth_context;
-        MfClassicError error =
-            mf_classic_poller_sync_auth(nfc, verify_block, &key, MfClassicKeyTypeA, &auth_context);
-        if(error != MfClassicErrorNone) {
-            FURI_LOG_I(TAG, "Failed to read block %u: %d", verify_block, error);
-            break;
+        if(!data_loaded) {
+            const uint8_t verify_block =
+                mf_classic_get_first_block_num_of_sector(verify_sector) + 1;
+            FURI_LOG_I(TAG, "Verifying sector %u", verify_sector);
+
+            MfClassicKey key = {0};
+            bit_lib_num_to_bytes_be(charliecard_1k_verify_key[0].a, COUNT_OF(key.data), key.data);
+
+            MfClassicAuthContext auth_context;
+            MfClassicError error = mf_classic_poller_sync_auth(
+                nfc, verify_block, &key, MfClassicKeyTypeA, &auth_context);
+            if(error != MfClassicErrorNone) {
+                FURI_LOG_I(TAG, "Failed to read block %u: %d", verify_block, error);
+                break;
+            }
+
+            verified = true;
+        } else {
+            MfClassicSectorTrailer* sec_tr =
+                mf_classic_get_sector_trailer_by_sector(mfc_data, verify_sector);
+            FURI_LOG_I(TAG, "%2x", sec_tr->key_a.data[1]);
+            uint64_t key = bit_lib_bytes_to_num_be(sec_tr->key_a.data, 6);
+            if(key != charliecard_1k_verify_key[0].a) {
+                FURI_LOG_I(TAG, "not equall");
+                break;
+            }
+
+            verified = true;
         }
         }
-
-        verified = true;
     } while(false);
     } while(false);
 
 
     return verified;
     return verified;
 }
 }
 
 
-bool bip_verify(Nfc* nfc) {
+bool bip_verify(Nfc* nfc, MfClassicData* mfc_data, bool data_loaded) {
     bool verified = false;
     bool verified = false;
 
 
     do {
     do {
-        const uint8_t verify_sector = 0;
-        uint8_t block_num = mf_classic_get_first_block_num_of_sector(verify_sector);
-        FURI_LOG_I(TAG, "Verifying sector %u", verify_sector);
+        if(!data_loaded) {
+            const uint8_t verify_sector = 0;
+            uint8_t block_num = mf_classic_get_first_block_num_of_sector(verify_sector);
+            FURI_LOG_I(TAG, "Verifying sector %u", verify_sector);
 
 
-        MfClassicKey key = {};
-        bit_lib_num_to_bytes_be(bip_1k_verify_key[0].a, COUNT_OF(key.data), key.data);
+            MfClassicKey key = {};
+            bit_lib_num_to_bytes_be(bip_1k_verify_key[0].a, COUNT_OF(key.data), key.data);
 
 
-        MfClassicAuthContext auth_ctx = {};
-        MfClassicError error =
-            mf_classic_poller_sync_auth(nfc, block_num, &key, MfClassicKeyTypeA, &auth_ctx);
+            MfClassicAuthContext auth_ctx = {};
+            MfClassicError error =
+                mf_classic_poller_sync_auth(nfc, block_num, &key, MfClassicKeyTypeA, &auth_ctx);
 
 
-        if(error != MfClassicErrorNone) {
-            FURI_LOG_I(TAG, "Failed to read block %u: %d", block_num, error);
-            break;
-        }
+            if(error != MfClassicErrorNone) {
+                FURI_LOG_I(TAG, "Failed to read block %u: %d", block_num, error);
+                break;
+            }
+
+            verified = true;
+        } else {
+            MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(mfc_data, 0);
 
 
-        verified = true;
+            uint64_t key = bit_lib_bytes_to_num_be(sec_tr->key_a.data, 6);
+            if(key != bip_1k_verify_key[0].a) {
+                break;
+            }
+
+            verified = true;
+        }
     } while(false);
     } while(false);
 
 
     return verified;
     return verified;
 }
 }
 
 
-static bool metromoney_verify(Nfc* nfc) {
+static bool metromoney_verify(Nfc* nfc, MfClassicData* mfc_data, bool data_loaded) {
     bool verified = false;
     bool verified = false;
-
+    const uint8_t ticket_sector_number = 1;
     do {
     do {
-        const uint8_t ticket_sector_number = 1;
-        const uint8_t ticket_block_number =
-            mf_classic_get_first_block_num_of_sector(ticket_sector_number) + 1;
-        FURI_LOG_D(TAG, "Verifying sector %u", ticket_sector_number);
-
-        MfClassicKey key = {0};
-        bit_lib_num_to_bytes_be(metromoney_1k_verify_key[0].a, COUNT_OF(key.data), key.data);
-
-        MfClassicAuthContext auth_context;
-        MfClassicError error = mf_classic_poller_sync_auth(
-            nfc, ticket_block_number, &key, MfClassicKeyTypeA, &auth_context);
-        if(error != MfClassicErrorNone) {
-            FURI_LOG_D(TAG, "Failed to read block %u: %d", ticket_block_number, error);
-            break;
+        if(!data_loaded) {
+            const uint8_t ticket_block_number =
+                mf_classic_get_first_block_num_of_sector(ticket_sector_number) + 1;
+            FURI_LOG_D(TAG, "Verifying sector %u", ticket_sector_number);
+
+            MfClassicKey key = {0};
+            bit_lib_num_to_bytes_be(metromoney_1k_verify_key[0].a, COUNT_OF(key.data), key.data);
+
+            MfClassicAuthContext auth_context;
+            MfClassicError error = mf_classic_poller_sync_auth(
+                nfc, ticket_block_number, &key, MfClassicKeyTypeA, &auth_context);
+            if(error != MfClassicErrorNone) {
+                FURI_LOG_D(TAG, "Failed to read block %u: %d", ticket_block_number, error);
+                break;
+            }
+
+            verified = true;
+        } else {
+            MfClassicSectorTrailer* sec_tr =
+                mf_classic_get_sector_trailer_by_sector(mfc_data, ticket_sector_number);
+
+            uint64_t key = bit_lib_bytes_to_num_be(sec_tr->key_a.data, 6);
+            if(key != metromoney_1k_verify_key[0].a) {
+                break;
+            }
+
+            verified = true;
         }
         }
-
-        verified = true;
     } while(false);
     } while(false);
 
 
     return verified;
     return verified;
 }
 }
 
 
-static bool smartrider_verify(Nfc* nfc) {
+static bool smartrider_verify(Nfc* nfc, MfClassicData* mfc_data, bool data_loaded) {
     bool verified = false;
     bool verified = false;
 
 
     do {
     do {
-        const uint8_t block_number = mf_classic_get_first_block_num_of_sector(0) + 1;
-        FURI_LOG_D(TAG, "Verifying sector 0");
-
-        MfClassicKey key = {0};
-        bit_lib_num_to_bytes_be(smartrider_verify_key[0].a, COUNT_OF(key.data), key.data);
-
-        MfClassicAuthContext auth_context;
-        MfClassicError error =
-            mf_classic_poller_sync_auth(nfc, block_number, &key, MfClassicKeyTypeA, &auth_context);
-        if(error != MfClassicErrorNone) {
-            FURI_LOG_D(TAG, "Failed to read block %u: %d", block_number, error);
-            break;
+        if(!data_loaded) {
+            const uint8_t block_number = mf_classic_get_first_block_num_of_sector(0) + 1;
+            FURI_LOG_D(TAG, "Verifying sector 0");
+
+            MfClassicKey key = {0};
+            bit_lib_num_to_bytes_be(smartrider_verify_key[0].a, COUNT_OF(key.data), key.data);
+
+            MfClassicAuthContext auth_context;
+            MfClassicError error = mf_classic_poller_sync_auth(
+                nfc, block_number, &key, MfClassicKeyTypeA, &auth_context);
+            if(error != MfClassicErrorNone) {
+                FURI_LOG_D(TAG, "Failed to read block %u: %d", block_number, error);
+                break;
+            }
+
+            verified = true;
+        } else {
+            MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(mfc_data, 0);
+
+            uint64_t key = bit_lib_bytes_to_num_be(sec_tr->key_a.data, 6);
+            if(key != smartrider_verify_key[0].a) {
+                break;
+            }
+
+            verified = true;
         }
         }
-
-        verified = true;
     } while(false);
     } while(false);
 
 
     return verified;
     return verified;
@@ -151,51 +203,96 @@ static bool troika_get_card_config(TroikaCardConfig* config, MfClassicType type)
     return success;
     return success;
 }
 }
 
 
-static bool troika_verify_type(Nfc* nfc, MfClassicType type) {
+static bool
+    troika_verify_type(Nfc* nfc, MfClassicData* mfc_data, bool data_loaded, MfClassicType type) {
     bool verified = false;
     bool verified = false;
 
 
     do {
     do {
-        TroikaCardConfig cfg = {};
-        if(!troika_get_card_config(&cfg, type)) break;
+        if(!data_loaded) {
+            TroikaCardConfig cfg = {};
+            if(!troika_get_card_config(&cfg, type)) break;
+
+            const uint8_t block_num = mf_classic_get_first_block_num_of_sector(cfg.data_sector);
+            FURI_LOG_D(TAG, "Verifying sector %lu", cfg.data_sector);
+
+            MfClassicKey key = {0};
+            bit_lib_num_to_bytes_be(cfg.keys[0].a, COUNT_OF(key.data), key.data);
+
+            MfClassicAuthContext auth_context;
+            MfClassicError error = mf_classic_poller_sync_auth(
+                nfc, block_num, &key, MfClassicKeyTypeA, &auth_context);
+            if(error != MfClassicErrorNone) {
+                FURI_LOG_D(TAG, "Failed to read block %u: %d", block_num, error);
+                break;
+            }
+            FURI_LOG_D(TAG, "Verify success!");
+            verified = true;
+        } else {
+            TroikaCardConfig cfg = {};
+            if(!troika_get_card_config(&cfg, type)) break;
+            MfClassicSectorTrailer* sec_tr =
+                mf_classic_get_sector_trailer_by_sector(mfc_data, cfg.data_sector);
+
+            uint64_t key = bit_lib_bytes_to_num_be(sec_tr->key_a.data, 6);
+            if(key != cfg.keys[0].a) {
+                break;
+            }
+
+            verified = true;
+        }
+    } while(false);
 
 
-        const uint8_t block_num = mf_classic_get_first_block_num_of_sector(cfg.data_sector);
-        FURI_LOG_D(TAG, "Verifying sector %lu", cfg.data_sector);
+    return verified;
+}
 
 
-        MfClassicKey key = {0};
-        bit_lib_num_to_bytes_be(cfg.keys[0].a, COUNT_OF(key.data), key.data);
+static bool troika_verify(Nfc* nfc, MfClassicData* mfc_data, bool data_loaded) {
+    return troika_verify_type(nfc, mfc_data, data_loaded, MfClassicType1k) ||
+           troika_verify_type(nfc, mfc_data, data_loaded, MfClassicType4k);
+}
 
 
-        MfClassicAuthContext auth_context;
-        MfClassicError error =
-            mf_classic_poller_sync_auth(nfc, block_num, &key, MfClassicKeyTypeA, &auth_context);
-        if(error != MfClassicErrorNone) {
-            FURI_LOG_D(TAG, "Failed to read block %u: %d", block_num, error);
-            break;
+static bool gocard_verify(MfClassicData* mfc_data, bool data_loaded) {
+    bool verified = false;
+    FURI_LOG_I(TAG, "verifying charliecard..");
+    do {
+        if(data_loaded) {
+            uint8_t* buffer = &mfc_data->block[1].data[1];
+            size_t buffer_size = 14;
+
+            if(memcmp(buffer, gocard_verify_data[0], buffer_size) == 0) {
+                FURI_LOG_I(TAG, "Match!");
+            } else {
+                FURI_LOG_I(TAG, "No match.");
+                if(memcmp(buffer, gocard_verify_data2[0], buffer_size) == 0) {
+                    FURI_LOG_I(TAG, "Match!");
+                } else {
+                    FURI_LOG_I(TAG, "No match.");
+                    break;
+                }
+            }
+
+            verified = true;
         }
         }
-        FURI_LOG_D(TAG, "Verify success!");
-        verified = true;
     } while(false);
     } while(false);
 
 
     return verified;
     return verified;
 }
 }
 
 
-static bool troika_verify(Nfc* nfc) {
-    return troika_verify_type(nfc, MfClassicType1k) || troika_verify_type(nfc, MfClassicType4k);
-}
-
-CardType determine_card_type(Nfc* nfc) {
+CardType determine_card_type(Nfc* nfc, MfClassicData* mfc_data, bool data_loaded) {
     FURI_LOG_I(TAG, "checking keys..");
     FURI_LOG_I(TAG, "checking keys..");
     UNUSED(bip_verify);
     UNUSED(bip_verify);
 
 
-    if(bip_verify(nfc)) {
-        return CARD_TYPE_METROMONEY;
-    } else if(metromoney_verify(nfc)) {
+    if(bip_verify(nfc, mfc_data, data_loaded)) {
+        return CARD_TYPE_BIP;
+    } else if(metromoney_verify(nfc, mfc_data, data_loaded)) {
         return CARD_TYPE_METROMONEY;
         return CARD_TYPE_METROMONEY;
-    } else if(smartrider_verify(nfc)) {
+    } else if(smartrider_verify(nfc, mfc_data, data_loaded)) {
         return CARD_TYPE_SMARTRIDER;
         return CARD_TYPE_SMARTRIDER;
-    } else if(troika_verify(nfc)) {
+    } else if(troika_verify(nfc, mfc_data, data_loaded)) {
         return CARD_TYPE_TROIKA;
         return CARD_TYPE_TROIKA;
-    } else if(charliecard_verify(nfc)) {
+    } else if(charliecard_verify(nfc, mfc_data, data_loaded)) {
         return CARD_TYPE_CHARLIECARD;
         return CARD_TYPE_CHARLIECARD;
+    } else if(gocard_verify(mfc_data, data_loaded)) {
+        return CARD_TYPE_GOCARD;
     } else {
     } else {
         FURI_LOG_I(TAG, "its unknown");
         FURI_LOG_I(TAG, "its unknown");
         return CARD_TYPE_UNKNOWN;
         return CARD_TYPE_UNKNOWN;

+ 3 - 1
metroflip/scenes/keys.h

@@ -9,6 +9,7 @@ typedef enum {
     CARD_TYPE_CHARLIECARD,
     CARD_TYPE_CHARLIECARD,
     CARD_TYPE_SMARTRIDER,
     CARD_TYPE_SMARTRIDER,
     CARD_TYPE_TROIKA,
     CARD_TYPE_TROIKA,
+    CARD_TYPE_GOCARD,
     CARD_TYPE_UNKNOWN
     CARD_TYPE_UNKNOWN
 } CardType;
 } CardType;
 
 
@@ -28,9 +29,10 @@ typedef struct {
 
 
 extern const MfClassicKeyPair troika_1k_keys[16];
 extern const MfClassicKeyPair troika_1k_keys[16];
 extern const MfClassicKeyPair troika_4k_keys[40];
 extern const MfClassicKeyPair troika_4k_keys[40];
-extern const uint8_t SMARTRIDER_STANDARD_KEYS[3][6];
 extern const MfClassicKeyPair charliecard_1k_keys[16];
 extern const MfClassicKeyPair charliecard_1k_keys[16];
 extern const MfClassicKeyPair bip_1k_keys[16];
 extern const MfClassicKeyPair bip_1k_keys[16];
 extern const MfClassicKeyPair metromoney_1k_keys[16];
 extern const MfClassicKeyPair metromoney_1k_keys[16];
+extern const uint8_t gocard_verify_data[1][14];
+extern const uint8_t gocard_verify_data2[1][14];
 
 
 #endif // KEYS_H
 #endif // KEYS_H

+ 9 - 11
metroflip/scenes/metroflip_scene_auto.c

@@ -20,28 +20,24 @@ static NfcCommand
     Metroflip* app = context;
     Metroflip* app = context;
     NfcCommand command = NfcCommandContinue;
     NfcCommand command = NfcCommandContinue;
 
 
-    FuriString* parsed_data = furi_string_alloc();
-    furi_string_reset(app->text_box_store);
     const MfDesfirePollerEvent* mf_desfire_event = event.event_data;
     const MfDesfirePollerEvent* mf_desfire_event = event.event_data;
     if(mf_desfire_event->type == MfDesfirePollerEventTypeReadSuccess) {
     if(mf_desfire_event->type == MfDesfirePollerEventTypeReadSuccess) {
         nfc_device_set_data(
         nfc_device_set_data(
             app->nfc_device, NfcProtocolMfDesfire, nfc_poller_get_data(app->poller));
             app->nfc_device, NfcProtocolMfDesfire, nfc_poller_get_data(app->poller));
-        if(clipper_parse(app->nfc_device, parsed_data)) {
-            furi_string_reset(app->text_box_store);
+        const MfDesfireData* data = nfc_device_get_data(app->nfc_device, NfcProtocolMfDesfire);
+        if(clipper_verify(data)) {
             view_dispatcher_send_custom_event(
             view_dispatcher_send_custom_event(
                 app->view_dispatcher, MetroflipCustomEventPollerSuccess);
                 app->view_dispatcher, MetroflipCustomEventPollerSuccess);
             app->desfire_card_type = CARD_TYPE_CLIPPER;
             app->desfire_card_type = CARD_TYPE_CLIPPER;
-        } else if(itso_parse(app->nfc_device, parsed_data)) {
-            furi_string_reset(app->text_box_store);
+        } else if(itso_verify(data)) {
             view_dispatcher_send_custom_event(
             view_dispatcher_send_custom_event(
                 app->view_dispatcher, MetroflipCustomEventPollerSuccess);
                 app->view_dispatcher, MetroflipCustomEventPollerSuccess);
             app->desfire_card_type = CARD_TYPE_ITSO;
             app->desfire_card_type = CARD_TYPE_ITSO;
-        } else if(myki_parse(app->nfc_device, parsed_data)) {
-            furi_string_reset(app->text_box_store);
+        } else if(myki_verify(data)) {
             view_dispatcher_send_custom_event(
             view_dispatcher_send_custom_event(
                 app->view_dispatcher, MetroflipCustomEventPollerSuccess);
                 app->view_dispatcher, MetroflipCustomEventPollerSuccess);
             app->desfire_card_type = CARD_TYPE_MYKI;
             app->desfire_card_type = CARD_TYPE_MYKI;
-        } else if(opal_parse(app->nfc_device, parsed_data)) {
+        } else if(opal_verify(data)) {
             furi_string_reset(app->text_box_store);
             furi_string_reset(app->text_box_store);
             view_dispatcher_send_custom_event(
             view_dispatcher_send_custom_event(
                 app->view_dispatcher, MetroflipCustomEventPollerSuccess);
                 app->view_dispatcher, MetroflipCustomEventPollerSuccess);
@@ -52,7 +48,6 @@ static NfcCommand
                 app->view_dispatcher, MetroflipCustomEventPollerSuccess);
                 app->view_dispatcher, MetroflipCustomEventPollerSuccess);
             app->desfire_card_type = CARD_TYPE_DESFIRE_UNKNOWN;
             app->desfire_card_type = CARD_TYPE_DESFIRE_UNKNOWN;
         }
         }
-        furi_string_free(parsed_data);
         command = NfcCommandStop;
         command = NfcCommandStop;
     } else if(mf_desfire_event->type == MfDesfirePollerEventTypeReadFailed) {
     } else if(mf_desfire_event->type == MfDesfirePollerEventTypeReadFailed) {
         view_dispatcher_send_custom_event(app->view_dispatcher, MetroflipCustomEventPollerSuccess);
         view_dispatcher_send_custom_event(app->view_dispatcher, MetroflipCustomEventPollerSuccess);
@@ -160,7 +155,10 @@ bool metroflip_scene_auto_on_event(void* context, SceneManagerEvent event) {
             app->auto_mode = true;
             app->auto_mode = true;
             if(nfc_detected_protocols_get_protocol(app->detected_protocols, 0) ==
             if(nfc_detected_protocols_get_protocol(app->detected_protocols, 0) ==
                NfcProtocolMfClassic) {
                NfcProtocolMfClassic) {
-                CardType card_type = determine_card_type(app->nfc);
+                MfClassicData* mfc_data = mf_classic_alloc();
+                app->data_loaded = false;
+                CardType card_type = determine_card_type(app->nfc, mfc_data, app->data_loaded);
+                mf_classic_free(mfc_data);
                 app->mfc_card_type = card_type;
                 app->mfc_card_type = card_type;
                 Popup* popup = app->popup;
                 Popup* popup = app->popup;
                 UNUSED(popup);
                 UNUSED(popup);

+ 4 - 0
metroflip/scenes/metroflip_scene_config.h

@@ -2,6 +2,10 @@ ADD_SCENE(metroflip, start, Start)
 ADD_SCENE(metroflip, auto, Auto)
 ADD_SCENE(metroflip, auto, Auto)
 ADD_SCENE(metroflip, parse, Parse)
 ADD_SCENE(metroflip, parse, Parse)
 ADD_SCENE(metroflip, ovc, OVC)
 ADD_SCENE(metroflip, ovc, OVC)
+ADD_SCENE(metroflip, save, Save)
+ADD_SCENE(metroflip, save_result, SaveResult)
+ADD_SCENE(metroflip, delete, Delete)
 ADD_SCENE(metroflip, supported, Supported)
 ADD_SCENE(metroflip, supported, Supported)
 ADD_SCENE(metroflip, about, About)
 ADD_SCENE(metroflip, about, About)
 ADD_SCENE(metroflip, credits, Credits)
 ADD_SCENE(metroflip, credits, Credits)
+ADD_SCENE(metroflip, load, Load)

+ 1 - 1
metroflip/scenes/metroflip_scene_credits.c

@@ -17,6 +17,7 @@ void metroflip_scene_credits_on_enter(void* context) {
     furi_string_cat_printf(str, "Created by luu176\n");
     furi_string_cat_printf(str, "Created by luu176\n");
     furi_string_cat_printf(str, "Inspired by Metrodroid\n\n");
     furi_string_cat_printf(str, "Inspired by Metrodroid\n\n");
     furi_string_cat_printf(str, "Special Thanks:\n willyjl\n");
     furi_string_cat_printf(str, "Special Thanks:\n willyjl\n");
+    furi_string_cat_printf(str, "Info Slaves:\n Equip, TheDingo8MyBaby\n\n");
     furi_string_cat_printf(str, "\e#Parser Credits:\n\n");
     furi_string_cat_printf(str, "\e#Parser Credits:\n\n");
     furi_string_cat_printf(str, "Bip! Parser:\n rbasoalto, gornekich\n\n");
     furi_string_cat_printf(str, "Bip! Parser:\n rbasoalto, gornekich\n\n");
     furi_string_cat_printf(str, "CharlieCard Parser:\n zacharyweiss\n\n");
     furi_string_cat_printf(str, "CharlieCard Parser:\n zacharyweiss\n\n");
@@ -29,7 +30,6 @@ void metroflip_scene_credits_on_enter(void* context) {
     furi_string_cat_printf(str, "Opus Parser: DocSystem\n\n");
     furi_string_cat_printf(str, "Opus Parser: DocSystem\n\n");
     furi_string_cat_printf(str, "Rav-Kav Parser: luu176\n\n");
     furi_string_cat_printf(str, "Rav-Kav Parser: luu176\n\n");
     furi_string_cat_printf(str, "Troika Parser:\n gornekich\n\n");
     furi_string_cat_printf(str, "Troika Parser:\n gornekich\n\n");
-    furi_string_cat_printf(str, "Info Slaves:\n Equip, TheDingo8MyBaby\n\n");
 
 
     widget_add_text_scroll_element(widget, 0, 0, 128, 64, furi_string_get_cstr(str));
     widget_add_text_scroll_element(widget, 0, 0, 128, 64, furi_string_get_cstr(str));
 
 

+ 62 - 0
metroflip/scenes/metroflip_scene_delete.c

@@ -0,0 +1,62 @@
+#include "../metroflip_i.h"
+#include "../api/metroflip/metroflip_api.h"
+#include <stdio.h>
+enum PopupEvent {
+    PopupEventExit,
+};
+
+static void metroflip_scene_delete_popup_callback(void* context) {
+    Metroflip* app = context;
+
+    view_dispatcher_send_custom_event(app->view_dispatcher, PopupEventExit);
+}
+
+void metroflip_scene_delete_on_enter(void* context) {
+    Metroflip* app = context;
+    Popup* popup = app->popup;
+
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    FURI_LOG_I("PATH", "PATH: %s", app->delete_file_path);
+    bool success = storage_simply_remove(storage, app->delete_file_path);
+    furi_record_close(RECORD_STORAGE);
+    if(success) {
+        popup_set_icon(popup, 0, 2, &I_DolphinMafia_119x62);
+        popup_set_header(popup, "Deleted", 80, 19, AlignLeft, AlignBottom);
+        popup_enable_timeout(popup);
+    } else {
+        popup_set_icon(popup, 69, 15, &I_WarningDolphinFlip_45x42);
+        popup_set_header(popup, "Error!", 13, 22, AlignLeft, AlignBottom);
+        popup_disable_timeout(popup);
+    }
+    popup_set_timeout(popup, 1500);
+    popup_set_context(popup, app);
+    popup_set_callback(popup, metroflip_scene_delete_popup_callback);
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewPopup);
+}
+
+bool metroflip_scene_delete_on_event(void* context, SceneManagerEvent event) {
+    Metroflip* app = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        consumed = true;
+        switch(event.event) {
+        case PopupEventExit:
+            scene_manager_search_and_switch_to_previous_scene(
+                app->scene_manager, MetroflipSceneStart);
+            break;
+        default:
+            break;
+        }
+    }
+
+    return consumed;
+}
+
+void metroflip_scene_delete_on_exit(void* context) {
+    Metroflip* app = context;
+    app->delete_file_path[0] = '\0';
+
+    popup_reset(app->popup);
+}

+ 151 - 0
metroflip/scenes/metroflip_scene_load.c

@@ -0,0 +1,151 @@
+#include "../metroflip_i.h"
+#include <dolphin/dolphin.h>
+#include <furi.h>
+#include <bit_lib.h>
+#include <lib/nfc/protocols/nfc_protocol.h>
+#include "../api/metroflip/metroflip_api.h"
+#define TAG "Metroflip:Scene:Load"
+#include "keys.h"
+#include <nfc/protocols/mf_classic/mf_classic.h>
+
+void metroflip_scene_load_on_enter(void* context) {
+    Metroflip* app = (Metroflip*)context;
+    // We initialized this to be false every time we enter
+    app->data_loaded = false;
+    bool has_card_type = false;
+    // The same string we will use to direct parse scene which plugin to call
+    // Extracted from the file
+    FuriString* card_type = furi_string_alloc();
+    FuriString* device_type = furi_string_alloc();
+
+    // All the app_data browser stuff. Don't worry about this
+    DialogsFileBrowserOptions browser_options;
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    storage_simply_mkdir(storage, STORAGE_APP_DATA_PATH_PREFIX);
+    dialog_file_browser_set_basic_options(&browser_options, METROFLIP_FILE_EXTENSION, &I_icon);
+    browser_options.base_path = STORAGE_APP_DATA_PATH_PREFIX;
+    FuriString* file_path = furi_string_alloc_set(browser_options.base_path);
+
+    if(dialog_file_browser_show(app->dialogs, file_path, file_path, &browser_options)) {
+        FlipperFormat* format = flipper_format_file_alloc(storage);
+        do {
+            if(!flipper_format_file_open_existing(format, furi_string_get_cstr(file_path))) break;
+            if(!flipper_format_read_string(format, "Device type", device_type)) break;
+            const char* protocol_name = furi_string_get_cstr(device_type);
+            if(!flipper_format_read_string(format, "Card Type", card_type)) {
+                flipper_format_file_close(format);
+                flipper_format_file_open_existing(format, furi_string_get_cstr(file_path));
+
+                if(strcmp(protocol_name, "Mifare Classic") == 0) {
+                    MfClassicData* mfc_data = mf_classic_alloc();
+                    if(!mf_classic_load(mfc_data, format, 2)) break;
+                    app->data_loaded = true;
+                    CardType card_type = determine_card_type(app->nfc, mfc_data, app->data_loaded);
+                    app->mfc_card_type = card_type;
+                    has_card_type = true;
+                    switch(card_type) {
+                    case CARD_TYPE_METROMONEY:
+                        app->card_type = "metromoney";
+                        FURI_LOG_I(TAG, "Detected: Metromoney\n");
+                        break;
+                    case CARD_TYPE_CHARLIECARD:
+                        app->card_type = "charliecard";
+                        FURI_LOG_I(TAG, "Detected: CharlieCard\n");
+                        break;
+                    case CARD_TYPE_SMARTRIDER:
+                        app->card_type = "smartrider";
+                        FURI_LOG_I(TAG, "Detected: SmartRider\n");
+                        break;
+                    case CARD_TYPE_TROIKA:
+                        app->card_type = "troika";
+                        FURI_LOG_I(TAG, "Detected: Troika\n");
+                        break;
+                    case CARD_TYPE_GOCARD:
+                        app->card_type = "gocard";
+                        FURI_LOG_I(TAG, "Detected: go card\n");
+                        break;
+                    case CARD_TYPE_UNKNOWN:
+                        app->card_type = "unknown";
+                        //popup_set_header(popup, "Unsupported\n card", 58, 31, AlignLeft, AlignTop);
+                        break;
+                    default:
+                        app->card_type = "unknown";
+                        FURI_LOG_I(TAG, "Detected: Unknown card type\n");
+                        //popup_set_header(popup, "Unsupported\n card", 58, 31, AlignLeft, AlignTop);
+                        break;
+                    }
+                    mf_classic_free(mfc_data);
+                } else if(strcmp(protocol_name, "Mifare DESFire") == 0) {
+                    MfDesfireData* data = mf_desfire_alloc();
+                    if(!mf_desfire_load(data, format, 2)) break;
+                    app->data_loaded = true;
+                    if(clipper_verify(data)) {
+                        app->card_type = "clipper";
+                        FURI_LOG_I(TAG, "Detected: Clipper");
+                    } else if(itso_verify(data)) {
+                        app->card_type = "itso";
+                        FURI_LOG_I(TAG, "Detected: ITSO");
+                    } else if(myki_verify(data)) {
+                        app->card_type = "myki";
+                        FURI_LOG_I(TAG, "Detected: Myki");
+                    } else if(opal_verify(data)) {
+                        app->card_type = "opal";
+                        FURI_LOG_I(TAG, "Detected: Opal");
+                    } else {
+                        app->card_type = "unknown";
+                        FURI_LOG_I(TAG, "Detected: none");
+                    }
+                    mf_desfire_free(data);
+                    has_card_type = true;
+                } else {
+                    has_card_type = true;
+                }
+                flipper_format_file_close(format);
+            } else {
+                has_card_type = false;
+            }
+            app->file_path = furi_string_get_cstr(file_path);
+            strncpy(
+                app->delete_file_path,
+                furi_string_get_cstr(file_path),
+                sizeof(app->delete_file_path) - 1);
+            app->delete_file_path[sizeof(app->delete_file_path) - 1] = '\0';
+
+            app->data_loaded = true;
+        } while(0);
+        flipper_format_free(format);
+    }
+
+    if(app->data_loaded) {
+        // Direct to the parsing screen just like the auto scene does
+        if(!has_card_type) {
+            app->card_type = furi_string_get_cstr(card_type);
+            has_card_type = false;
+        }
+        scene_manager_search_and_switch_to_previous_scene(app->scene_manager, MetroflipSceneStart);
+        scene_manager_next_scene(app->scene_manager, MetroflipSceneParse);
+    } else {
+        scene_manager_search_and_switch_to_previous_scene(app->scene_manager, MetroflipSceneStart);
+    }
+    furi_string_free(file_path);
+    furi_record_close(RECORD_STORAGE);
+}
+
+bool metroflip_scene_load_on_event(void* context, SceneManagerEvent event) {
+    Metroflip* app = context;
+    UNUSED(event);
+    bool consumed = false;
+    // If they don't select any file in the brwoser and press back button,
+    // the data is not loaded
+    if(!app->data_loaded) {
+        scene_manager_search_and_switch_to_previous_scene(app->scene_manager, MetroflipSceneStart);
+    }
+    consumed = true;
+
+    return consumed;
+}
+
+void metroflip_scene_load_on_exit(void* context) {
+    Metroflip* app = context;
+    UNUSED(app);
+}

+ 2 - 1
metroflip/scenes/metroflip_scene_parse.c

@@ -1,6 +1,7 @@
 #include "../metroflip_i.h"
 #include "../metroflip_i.h"
 #include <furi.h>
 #include <furi.h>
 #include "../metroflip_plugins.h"
 #include "../metroflip_plugins.h"
+#include "../api/metroflip/metroflip_api.h"
 #define TAG "Metroflip:Scene:Parse"
 #define TAG "Metroflip:Scene:Parse"
 #include <stdio.h>
 #include <stdio.h>
 
 
@@ -62,5 +63,5 @@ void metroflip_scene_parse_on_exit(void* context) {
         plugin_manager_free(app->plugin_manager);
         plugin_manager_free(app->plugin_manager);
         composite_api_resolver_free(app->resolver);
         composite_api_resolver_free(app->resolver);
     }
     }
-    app->card_type = "unknown";
+    app->data_loaded = false;
 }
 }

+ 55 - 0
metroflip/scenes/metroflip_scene_save.c

@@ -0,0 +1,55 @@
+#include "../metroflip_i.h"
+
+enum TextInputResult {
+    TextInputResultOk,
+};
+
+static void metroflip_scene_save_text_input_callback(void* context) {
+    Metroflip* app = context;
+
+    view_dispatcher_send_custom_event(app->view_dispatcher, TextInputResultOk);
+}
+
+void metroflip_scene_save_on_enter(void* context) {
+    Metroflip* app = context;
+    TextInput* text_input = app->text_input;
+
+    text_input_set_header_text(text_input, "Save the NFC tag:");
+
+    text_input_set_result_callback(
+        text_input,
+        metroflip_scene_save_text_input_callback,
+        app,
+        app->save_buf,
+        sizeof(app->save_buf),
+        true);
+
+    ValidatorIsFile* validator_is_file =
+        validator_is_file_alloc_init(APP_DATA_PATH(), METROFLIP_FILE_EXTENSION, NULL);
+    text_input_set_validator(text_input, validator_is_file_callback, validator_is_file);
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewTextInput);
+}
+
+bool metroflip_scene_save_on_event(void* context, SceneManagerEvent event) {
+    Metroflip* app = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        consumed = true;
+        switch(event.event) {
+        case TextInputResultOk:
+            scene_manager_next_scene(app->scene_manager, MetroflipSceneSaveResult);
+            break;
+        default:
+            break;
+        }
+    }
+
+    return consumed;
+}
+
+void metroflip_scene_save_on_exit(void* context) {
+    Metroflip* app = context;
+    text_input_reset(app->text_input);
+}

+ 69 - 0
metroflip/scenes/metroflip_scene_save_result.c

@@ -0,0 +1,69 @@
+#include "../metroflip_i.h"
+#include "../api/metroflip/metroflip_api.h"
+#include <stdio.h>
+enum PopupEvent {
+    PopupEventExit,
+};
+
+static void metroflip_scene_save_result_popup_callback(void* context) {
+    Metroflip* app = context;
+
+    view_dispatcher_send_custom_event(app->view_dispatcher, PopupEventExit);
+}
+
+void metroflip_scene_save_result_on_enter(void* context) {
+    Metroflip* app = context;
+    Popup* popup = app->popup;
+
+    char path[280];
+    snprintf(path, sizeof(path), "/ext/apps_data/metroflip/%s.nfc", app->save_buf);
+    FURI_LOG_I("path", "path: %s", path);
+    bool success = nfc_device_save(app->nfc_device, path);
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    FlipperFormat* ff = flipper_format_file_alloc(storage);
+    flipper_format_write_empty_line(ff);
+    flipper_format_file_open_existing(ff, path);
+    flipper_format_insert_or_update_string_cstr(ff, "Card Type", app->card_type);
+    flipper_format_file_close(ff);
+    flipper_format_free(ff);
+    furi_record_close(RECORD_STORAGE);
+
+    if(success) {
+        popup_set_icon(popup, 36, 5, &I_DolphinDone_80x58);
+        popup_set_header(popup, "Saved!", 13, 22, AlignLeft, AlignBottom);
+        popup_enable_timeout(popup);
+    } else {
+        popup_set_icon(popup, 69, 15, &I_WarningDolphinFlip_45x42);
+        popup_set_header(popup, "Error!", 13, 22, AlignLeft, AlignBottom);
+        popup_disable_timeout(popup);
+    }
+    popup_set_timeout(popup, 1500);
+    popup_set_context(popup, app);
+    popup_set_callback(popup, metroflip_scene_save_result_popup_callback);
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewPopup);
+}
+
+bool metroflip_scene_save_result_on_event(void* context, SceneManagerEvent event) {
+    Metroflip* app = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        consumed = true;
+        switch(event.event) {
+        case PopupEventExit:
+            scene_manager_search_and_switch_to_previous_scene(
+                app->scene_manager, MetroflipSceneStart);
+            break;
+        default:
+            break;
+        }
+    }
+
+    return consumed;
+}
+
+void metroflip_scene_save_result_on_exit(void* context) {
+    Metroflip* app = context;
+    popup_reset(app->popup);
+}

+ 3 - 0
metroflip/scenes/metroflip_scene_start.c

@@ -21,6 +21,9 @@ void metroflip_scene_start_on_enter(void* context) {
         metroflip_scene_start_submenu_callback,
         metroflip_scene_start_submenu_callback,
         app);
         app);
 
 
+    submenu_add_item(
+        submenu, "Saved", MetroflipSceneLoad, metroflip_scene_start_submenu_callback, app);
+
     submenu_add_item(
     submenu_add_item(
         submenu,
         submenu,
         "Supported Cards",
         "Supported Cards",

+ 9 - 9
metroflip/scenes/metroflip_scene_supported.c

@@ -15,23 +15,23 @@ void metroflip_scene_supported_on_enter(void* context) {
 
 
     furi_string_printf(str, "\e#Supported Cards:\n\n");
     furi_string_printf(str, "\e#Supported Cards:\n\n");
     furi_string_cat_printf(
     furi_string_cat_printf(
-        str, " - Bip!:\nSantiago de Chile, Chile\nProtocol: Mifare Classic\n\n");
-    furi_string_cat_printf(str, " - Charliecard:\nBoston, MA, USA\nProtocol: Mifare Classic\n\n");
+        str, " - Bip!:\nSantiago de Chile, Chile\nProtocol: MIFARE Classic\n\n");
+    furi_string_cat_printf(str, " - Charliecard:\nBoston, MA, USA\nProtocol: MIFARE Classic\n\n");
     furi_string_cat_printf(
     furi_string_cat_printf(
-        str, " - Clipper:\nSan Francisco, CA, USA\nProtocol: Mifare DESFire\n\n");
-    furi_string_cat_printf(str, " - ITSO:\nUnited Kingdom\nProtocol: Mifare DESFire\n\n");
-    furi_string_cat_printf(str, " - Metromoney:\nTbilisi, Georgia\nProtocol: Mifare Classic\n\n");
+        str, " - Clipper:\nSan Francisco, CA, USA\nProtocol: MIFARE DESFire\n\n");
+    furi_string_cat_printf(str, " - ITSO:\nUnited Kingdom\nProtocol: MIFARE DESFire\n\n");
+    furi_string_cat_printf(str, " - Metromoney:\nTbilisi, Georgia\nProtocol: MIFARE Classic\n\n");
     furi_string_cat_printf(
     furi_string_cat_printf(
         str,
         str,
-        " - myki:\n🇦🇺 Melbourne (and surrounds), VIC, Australia\nProtocol: Mifare DESFire\n\n");
+        " - myki:\nMelbourne (and surrounds), VIC, Australia\nProtocol: MIFARE DESFire\n\n");
     furi_string_cat_printf(str, " - Navigo:\nParis, France\nProtocol: Calypso\n\n");
     furi_string_cat_printf(str, " - Navigo:\nParis, France\nProtocol: Calypso\n\n");
     furi_string_cat_printf(
     furi_string_cat_printf(
-        str, " - Opal:\nSydney (and surrounds), NSW, Australia\nProtocol: Mifare DESFire\n\n");
+        str, " - Opal:\nSydney (and surrounds), NSW, Australia\nProtocol: MIFARE DESFire\n\n");
     furi_string_cat_printf(str, " - Opus:\nMontreal, QC, Canada\nProtocol: Calypso\n\n");
     furi_string_cat_printf(str, " - Opus:\nMontreal, QC, Canada\nProtocol: Calypso\n\n");
     furi_string_cat_printf(str, " - Rav-Kav:\nIsrael\nProtocol: Calypso\n\n");
     furi_string_cat_printf(str, " - Rav-Kav:\nIsrael\nProtocol: Calypso\n\n");
     furi_string_cat_printf(
     furi_string_cat_printf(
-        str, " - SmartRider:\nWestern Australia, Australia\nProtocol: Mifare Classic\n\n");
-    furi_string_cat_printf(str, " - Troika:\nMoscow, Russia\nProtocol: Mifare Classic\n\n");
+        str, " - SmartRider:\nPerth, WA, Australia\nProtocol: MIFARE Classic\n\n");
+    furi_string_cat_printf(str, " - Troika:\nMoscow, Russia\nProtocol: MIFARE Classic\n\n");
 
 
     widget_add_text_scroll_element(widget, 0, 0, 128, 64, furi_string_get_cstr(str));
     widget_add_text_scroll_element(widget, 0, 0, 128, 64, furi_string_get_cstr(str));
 
 

+ 44 - 16
metroflip/scenes/plugins/bip.c

@@ -133,9 +133,7 @@ static bool is_bip_block_empty(const MfClassicBlock* block) {
     return true;
     return true;
 }
 }
 
 
-static bool
-    bip_parse(const NfcDevice* device, FuriString* parsed_data, const MfClassicData* data) {
-    furi_assert(device);
+static bool bip_parse(FuriString* parsed_data, const MfClassicData* data) {
     furi_assert(parsed_data);
     furi_assert(parsed_data);
 
 
     struct {
     struct {
@@ -311,7 +309,7 @@ static NfcCommand bip_poller_callback(NfcGenericEvent event, void* context) {
 
 
         dolphin_deed(DolphinDeedNfcReadSuccess);
         dolphin_deed(DolphinDeedNfcReadSuccess);
         furi_string_reset(app->text_box_store);
         furi_string_reset(app->text_box_store);
-        if(!bip_parse(app->nfc_device, parsed_data, mfc_data)) {
+        if(!bip_parse(parsed_data, mfc_data)) {
             furi_string_reset(app->text_box_store);
             furi_string_reset(app->text_box_store);
             FURI_LOG_I(TAG, "Unknown card type");
             FURI_LOG_I(TAG, "Unknown card type");
             furi_string_printf(parsed_data, "\e#Unknown card\n");
             furi_string_printf(parsed_data, "\e#Unknown card\n");
@@ -321,6 +319,8 @@ static NfcCommand bip_poller_callback(NfcGenericEvent event, void* context) {
 
 
         widget_add_button_element(
         widget_add_button_element(
             widget, GuiButtonTypeRight, "Exit", metroflip_exit_widget_callback, app);
             widget, GuiButtonTypeRight, "Exit", metroflip_exit_widget_callback, app);
+        widget_add_button_element(
+            widget, GuiButtonTypeCenter, "Save", metroflip_save_widget_callback, app);
 
 
         furi_string_free(parsed_data);
         furi_string_free(parsed_data);
         view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewWidget);
         view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewWidget);
@@ -339,18 +339,46 @@ static void bip_on_enter(Metroflip* app) {
 
 
     app->sec_num = 0;
     app->sec_num = 0;
 
 
-    // Setup view
-    Popup* popup = app->popup;
-    popup_set_header(popup, "Apply\n card to\nthe back", 68, 30, AlignLeft, AlignTop);
-    popup_set_icon(popup, 0, 3, &I_RFIDDolphinReceive_97x61);
-
-    // Start worker
-    view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewPopup);
-    nfc_scanner_alloc(app->nfc);
-    app->poller = nfc_poller_alloc(app->nfc, NfcProtocolMfClassic);
-    nfc_poller_start(app->poller, bip_poller_callback, app);
+    if(app->data_loaded) {
+        Storage* storage = furi_record_open(RECORD_STORAGE);
+        FlipperFormat* ff = flipper_format_file_alloc(storage);
+        if(flipper_format_file_open_existing(ff, app->file_path)) {
+            MfClassicData* mfc_data = mf_classic_alloc();
+            mf_classic_load(mfc_data, ff, 2);
+            FuriString* parsed_data = furi_string_alloc();
+            Widget* widget = app->widget;
 
 
-    metroflip_app_blink_start(app);
+            furi_string_reset(app->text_box_store);
+            if(!bip_parse(parsed_data, mfc_data)) {
+                furi_string_reset(app->text_box_store);
+                FURI_LOG_I(TAG, "Unknown card type");
+                furi_string_printf(parsed_data, "\e#Unknown card\n");
+            }
+            widget_add_text_scroll_element(
+                widget, 0, 0, 128, 64, furi_string_get_cstr(parsed_data));
+
+            widget_add_button_element(
+                widget, GuiButtonTypeRight, "Exit", metroflip_exit_widget_callback, app);
+            widget_add_button_element(
+                widget, GuiButtonTypeCenter, "Delete", metroflip_delete_widget_callback, app);
+            mf_classic_free(mfc_data);
+            furi_string_free(parsed_data);
+            view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewWidget);
+        }
+        flipper_format_free(ff);
+    } else {
+        // Setup view
+        Popup* popup = app->popup;
+        popup_set_header(popup, "Apply\n card to\nthe back", 68, 30, AlignLeft, AlignTop);
+        popup_set_icon(popup, 0, 3, &I_RFIDDolphinReceive_97x61);
+
+        // Start worker
+        view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewPopup);
+        app->poller = nfc_poller_alloc(app->nfc, NfcProtocolMfClassic);
+        nfc_poller_start(app->poller, bip_poller_callback, app);
+
+        metroflip_app_blink_start(app);
+    }
 }
 }
 
 
 static bool bip_on_event(Metroflip* app, SceneManagerEvent event) {
 static bool bip_on_event(Metroflip* app, SceneManagerEvent event) {
@@ -385,7 +413,7 @@ static bool bip_on_event(Metroflip* app, SceneManagerEvent event) {
 static void bip_on_exit(Metroflip* app) {
 static void bip_on_exit(Metroflip* app) {
     widget_reset(app->widget);
     widget_reset(app->widget);
 
 
-    if(app->poller) {
+    if(app->poller && !app->data_loaded) {
         nfc_poller_stop(app->poller);
         nfc_poller_stop(app->poller);
         nfc_poller_free(app->poller);
         nfc_poller_free(app->poller);
     }
     }

+ 52 - 7
metroflip/scenes/plugins/calypso.c

@@ -321,6 +321,8 @@ void metroflip_next_button_widget_callback(GuiButtonType result, InputType type,
             ctx->page_id = 0;
             ctx->page_id = 0;
             scene_manager_search_and_switch_to_previous_scene(
             scene_manager_search_and_switch_to_previous_scene(
                 app->scene_manager, MetroflipSceneStart);
                 app->scene_manager, MetroflipSceneStart);
+            scene_manager_set_scene_state(
+                app->scene_manager, MetroflipSceneStart, MetroflipSceneAuto);
             return;
             return;
         }
         }
         if(ctx->page_id < 10) {
         if(ctx->page_id < 10) {
@@ -352,6 +354,8 @@ void metroflip_next_button_widget_callback(GuiButtonType result, InputType type,
                 ctx->page_id = 0;
                 ctx->page_id = 0;
                 scene_manager_search_and_switch_to_previous_scene(
                 scene_manager_search_and_switch_to_previous_scene(
                     app->scene_manager, MetroflipSceneStart);
                     app->scene_manager, MetroflipSceneStart);
+                scene_manager_set_scene_state(
+                    app->scene_manager, MetroflipSceneStart, MetroflipSceneAuto);
                 return;
                 return;
             }
             }
             ctx->page_id += 1;
             ctx->page_id += 1;
@@ -359,6 +363,8 @@ void metroflip_next_button_widget_callback(GuiButtonType result, InputType type,
             ctx->page_id = 0;
             ctx->page_id = 0;
             scene_manager_search_and_switch_to_previous_scene(
             scene_manager_search_and_switch_to_previous_scene(
                 app->scene_manager, MetroflipSceneStart);
                 app->scene_manager, MetroflipSceneStart);
+            scene_manager_set_scene_state(
+                app->scene_manager, MetroflipSceneStart, MetroflipSceneAuto);
             return;
             return;
         }
         }
 
 
@@ -1783,8 +1789,17 @@ static NfcCommand calypso_poller_callback(NfcGenericEvent event, void* context)
                     if(card->card_type == CALYPSO_CARD_RAVKAV) {
                     if(card->card_type == CALYPSO_CARD_RAVKAV) {
                         card->ravkav = malloc(sizeof(RavKavCardData));
                         card->ravkav = malloc(sizeof(RavKavCardData));
 
 
+                        // Prepare calypso structure
+
+                        CalypsoApp* RavKavContractStructure = get_ravkav_contract_structure();
+                        if(!RavKavContractStructure) {
+                            FURI_LOG_E(TAG, "Failed to load RavKav Contract structure");
+                            break;
+                        }
+
+                        //get balance
                         error = select_new_app(
                         error = select_new_app(
-                            0x20, 0x20, tx_buffer, rx_buffer, iso14443_4b_poller, app, &stage);
+                            0x20, 0x2A, tx_buffer, rx_buffer, iso14443_4b_poller, app, &stage);
                         if(error != 0) {
                         if(error != 0) {
                             FURI_LOG_E(TAG, "Failed to select app for contracts");
                             FURI_LOG_E(TAG, "Failed to select app for contracts");
                             break;
                             break;
@@ -1793,15 +1808,44 @@ static NfcCommand calypso_poller_callback(NfcGenericEvent event, void* context)
                         // Check the response after selecting app
                         // Check the response after selecting app
                         if(check_response(rx_buffer, app, &stage, &response_length) != 0) {
                         if(check_response(rx_buffer, app, &stage, &response_length) != 0) {
                             FURI_LOG_E(
                             FURI_LOG_E(
-                                TAG, "Failed to check response after selecting app for contracts");
+                                TAG, "Failed to check response after selecting app for counter");
                             break;
                             break;
                         }
                         }
 
 
-                        // Prepare calypso structure
+                        error = read_new_file(
+                            1, tx_buffer, rx_buffer, iso14443_4b_poller, app, &stage);
+                        if(error != 0) {
+                            FURI_LOG_E(TAG, "Failed to read counter %d", 1);
+                            break;
+                        }
 
 
-                        CalypsoApp* RavKavContractStructure = get_ravkav_contract_structure();
-                        if(!RavKavContractStructure) {
-                            FURI_LOG_E(TAG, "Failed to load RavKav Contract structure");
+                        // Check the response after reading the file
+                        if(check_response(rx_buffer, app, &stage, &response_length) != 0) {
+                            FURI_LOG_E(
+                                TAG, "Failed to check response after reading counter %d", 1);
+                            break;
+                        }
+
+                        uint32_t value = 0;
+                        for(uint8_t i = 0; i < 3; i++) {
+                            value = (value << 8) | bit_buffer_get_byte(rx_buffer, i);
+                        }
+                        float result = value / 100.0f;
+                        FURI_LOG_I(TAG, "Value: %.2f ILS", (double)result);
+
+                        card->ravkav->contracts[0].balance = result;
+
+                        error = select_new_app(
+                            0x20, 0x20, tx_buffer, rx_buffer, iso14443_4b_poller, app, &stage);
+                        if(error != 0) {
+                            FURI_LOG_E(TAG, "Failed to select app for contracts");
+                            break;
+                        }
+
+                        // Check the response after selecting app
+                        if(check_response(rx_buffer, app, &stage, &response_length) != 0) {
+                            FURI_LOG_E(
+                                TAG, "Failed to check response after selecting app for contracts");
                             break;
                             break;
                         }
                         }
 
 
@@ -2477,6 +2521,7 @@ static bool calypso_on_event(Metroflip* app, SceneManagerEvent event) {
         }
         }
     } else if(event.type == SceneManagerEventTypeBack) {
     } else if(event.type == SceneManagerEventTypeBack) {
         scene_manager_search_and_switch_to_previous_scene(app->scene_manager, MetroflipSceneStart);
         scene_manager_search_and_switch_to_previous_scene(app->scene_manager, MetroflipSceneStart);
+        scene_manager_set_scene_state(app->scene_manager, MetroflipSceneStart, MetroflipSceneAuto);
         consumed = true;
         consumed = true;
     }
     }
 
 
@@ -2484,7 +2529,7 @@ static bool calypso_on_event(Metroflip* app, SceneManagerEvent event) {
 }
 }
 
 
 static void calypso_on_exit(Metroflip* app) {
 static void calypso_on_exit(Metroflip* app) {
-    if(app->poller) {
+    if(app->poller && !app->data_loaded) {
         nfc_poller_stop(app->poller);
         nfc_poller_stop(app->poller);
         nfc_poller_free(app->poller);
         nfc_poller_free(app->poller);
     }
     }

+ 42 - 10
metroflip/scenes/plugins/charliecard.c

@@ -1235,6 +1235,8 @@ static NfcCommand
 
 
         widget_add_button_element(
         widget_add_button_element(
             widget, GuiButtonTypeRight, "Exit", metroflip_exit_widget_callback, app);
             widget, GuiButtonTypeRight, "Exit", metroflip_exit_widget_callback, app);
+        widget_add_button_element(
+            widget, GuiButtonTypeCenter, "Save", metroflip_save_widget_callback, app);
 
 
         furi_string_free(parsed_data);
         furi_string_free(parsed_data);
         view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewWidget);
         view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewWidget);
@@ -1253,17 +1255,46 @@ static void charliecard_on_enter(Metroflip* app) {
 
 
     app->sec_num = 0;
     app->sec_num = 0;
 
 
-    // Setup view
-    Popup* popup = app->popup;
-    popup_set_header(popup, "Apply\n card to\nthe back", 68, 30, AlignLeft, AlignTop);
-    popup_set_icon(popup, 0, 3, &I_RFIDDolphinReceive_97x61);
+    if(app->data_loaded) {
+        Storage* storage = furi_record_open(RECORD_STORAGE);
+        FlipperFormat* ff = flipper_format_file_alloc(storage);
+        if(flipper_format_file_open_existing(ff, app->file_path)) {
+            MfClassicData* mfc_data = mf_classic_alloc();
+            mf_classic_load(mfc_data, ff, 2);
+            FuriString* parsed_data = furi_string_alloc();
+            Widget* widget = app->widget;
+
+            furi_string_reset(app->text_box_store);
+            if(!charliecard_parse(parsed_data, mfc_data)) {
+                furi_string_reset(app->text_box_store);
+                FURI_LOG_I(TAG, "Unknown card type");
+                furi_string_printf(parsed_data, "\e#Unknown card\n");
+            }
+            widget_add_text_scroll_element(
+                widget, 0, 0, 128, 64, furi_string_get_cstr(parsed_data));
+            widget_add_button_element(
+                widget, GuiButtonTypeRight, "Exit", metroflip_exit_widget_callback, app);
+            widget_add_button_element(
+                widget, GuiButtonTypeCenter, "Delete", metroflip_delete_widget_callback, app);
+
+            mf_classic_free(mfc_data);
+            furi_string_free(parsed_data);
+            view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewWidget);
+        }
+        flipper_format_free(ff);
+    } else {
+        // Setup view
+        Popup* popup = app->popup;
+        popup_set_header(popup, "Apply\n card to\nthe back", 68, 30, AlignLeft, AlignTop);
+        popup_set_icon(popup, 0, 3, &I_RFIDDolphinReceive_97x61);
 
 
-    // Start worker
-    view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewPopup);
-    app->poller = nfc_poller_alloc(app->nfc, NfcProtocolMfClassic);
-    nfc_poller_start(app->poller, metroflip_scene_charlicard_poller_callback, app);
+        // Start worker
+        view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewPopup);
+        app->poller = nfc_poller_alloc(app->nfc, NfcProtocolMfClassic);
+        nfc_poller_start(app->poller, metroflip_scene_charlicard_poller_callback, app);
 
 
-    metroflip_app_blink_start(app);
+        metroflip_app_blink_start(app);
+    }
 }
 }
 
 
 static bool charliecard_on_event(Metroflip* app, SceneManagerEvent event) {
 static bool charliecard_on_event(Metroflip* app, SceneManagerEvent event) {
@@ -1289,6 +1320,7 @@ static bool charliecard_on_event(Metroflip* app, SceneManagerEvent event) {
         }
         }
     } else if(event.type == SceneManagerEventTypeBack) {
     } else if(event.type == SceneManagerEventTypeBack) {
         scene_manager_search_and_switch_to_previous_scene(app->scene_manager, MetroflipSceneStart);
         scene_manager_search_and_switch_to_previous_scene(app->scene_manager, MetroflipSceneStart);
+        scene_manager_set_scene_state(app->scene_manager, MetroflipSceneStart, MetroflipSceneAuto);
         consumed = true;
         consumed = true;
     }
     }
 
 
@@ -1298,7 +1330,7 @@ static bool charliecard_on_event(Metroflip* app, SceneManagerEvent event) {
 static void charliecard_on_exit(Metroflip* app) {
 static void charliecard_on_exit(Metroflip* app) {
     widget_reset(app->widget);
     widget_reset(app->widget);
 
 
-    if(app->poller) {
+    if(app->poller && !app->data_loaded) {
         nfc_poller_stop(app->poller);
         nfc_poller_stop(app->poller);
         nfc_poller_free(app->poller);
         nfc_poller_free(app->poller);
     }
     }

+ 48 - 17
metroflip/scenes/plugins/clipper.c

@@ -232,15 +232,12 @@ static int16_t get_i16be(const uint8_t* field) {
         return raw;
         return raw;
 }
 }
 
 
-bool clipper_parse(const NfcDevice* device, FuriString* parsed_data) {
-    furi_assert(device);
+bool clipper_parse(const MfDesfireData* data, FuriString* parsed_data) {
     furi_assert(parsed_data);
     furi_assert(parsed_data);
 
 
     bool parsed = false;
     bool parsed = false;
 
 
     do {
     do {
-        const MfDesfireData* data = nfc_device_get_data(device, NfcProtocolMfDesfire);
-
         const MfDesfireApplication* app = NULL;
         const MfDesfireApplication* app = NULL;
         const char* device_description = NULL;
         const char* device_description = NULL;
 
 
@@ -579,7 +576,9 @@ static NfcCommand clipper_poller_callback(NfcGenericEvent event, void* context)
     if(mf_desfire_event->type == MfDesfirePollerEventTypeReadSuccess) {
     if(mf_desfire_event->type == MfDesfirePollerEventTypeReadSuccess) {
         nfc_device_set_data(
         nfc_device_set_data(
             app->nfc_device, NfcProtocolMfDesfire, nfc_poller_get_data(app->poller));
             app->nfc_device, NfcProtocolMfDesfire, nfc_poller_get_data(app->poller));
-        if(!clipper_parse(app->nfc_device, parsed_data)) {
+        const MfDesfireData* data = nfc_device_get_data(app->nfc_device, NfcProtocolMfDesfire);
+
+        if(!clipper_parse(data, parsed_data)) {
             furi_string_reset(app->text_box_store);
             furi_string_reset(app->text_box_store);
             FURI_LOG_I(TAG, "Unknown card type");
             FURI_LOG_I(TAG, "Unknown card type");
             furi_string_printf(parsed_data, "\e#Unknown card\n");
             furi_string_printf(parsed_data, "\e#Unknown card\n");
@@ -588,6 +587,8 @@ static NfcCommand clipper_poller_callback(NfcGenericEvent event, void* context)
 
 
         widget_add_button_element(
         widget_add_button_element(
             widget, GuiButtonTypeRight, "Exit", metroflip_exit_widget_callback, app);
             widget, GuiButtonTypeRight, "Exit", metroflip_exit_widget_callback, app);
+        widget_add_button_element(
+            widget, GuiButtonTypeCenter, "Save", metroflip_save_widget_callback, app);
 
 
         furi_string_free(parsed_data);
         furi_string_free(parsed_data);
         view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewWidget);
         view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewWidget);
@@ -604,18 +605,47 @@ static NfcCommand clipper_poller_callback(NfcGenericEvent event, void* context)
 static void clipper_on_enter(Metroflip* app) {
 static void clipper_on_enter(Metroflip* app) {
     dolphin_deed(DolphinDeedNfcRead);
     dolphin_deed(DolphinDeedNfcRead);
 
 
-    // Setup view
-    Popup* popup = app->popup;
-    popup_set_header(popup, "Apply\n card to\nthe back", 68, 30, AlignLeft, AlignTop);
-    popup_set_icon(popup, 0, 3, &I_RFIDDolphinReceive_97x61);
-
-    // Start worker
-    view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewPopup);
-    nfc_scanner_alloc(app->nfc);
-    app->poller = nfc_poller_alloc(app->nfc, NfcProtocolMfDesfire);
-    nfc_poller_start(app->poller, clipper_poller_callback, app);
+    if(app->data_loaded) {
+        Storage* storage = furi_record_open(RECORD_STORAGE);
+        FlipperFormat* ff = flipper_format_file_alloc(storage);
+        if(flipper_format_file_open_existing(ff, app->file_path)) {
+            MfDesfireData* data = mf_desfire_alloc();
+            mf_desfire_load(data, ff, 2);
+            FuriString* parsed_data = furi_string_alloc();
+            Widget* widget = app->widget;
 
 
-    metroflip_app_blink_start(app);
+            furi_string_reset(app->text_box_store);
+            if(!clipper_parse(data, parsed_data)) {
+                furi_string_reset(app->text_box_store);
+                FURI_LOG_I(TAG, "Unknown card type");
+                furi_string_printf(parsed_data, "\e#Unknown card\n");
+            }
+            widget_add_text_scroll_element(
+                widget, 0, 0, 128, 64, furi_string_get_cstr(parsed_data));
+
+            widget_add_button_element(
+                widget, GuiButtonTypeRight, "Exit", metroflip_exit_widget_callback, app);
+            widget_add_button_element(
+                widget, GuiButtonTypeCenter, "Delete", metroflip_delete_widget_callback, app);
+            mf_desfire_free(data);
+            furi_string_free(parsed_data);
+            view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewWidget);
+        }
+        flipper_format_free(ff);
+    } else {
+        // Setup view
+        Popup* popup = app->popup;
+        popup_set_header(popup, "Apply\n card to\nthe back", 68, 30, AlignLeft, AlignTop);
+        popup_set_icon(popup, 0, 3, &I_RFIDDolphinReceive_97x61);
+
+        // Start worker
+        view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewPopup);
+        nfc_scanner_alloc(app->nfc);
+        app->poller = nfc_poller_alloc(app->nfc, NfcProtocolMfDesfire);
+        nfc_poller_start(app->poller, clipper_poller_callback, app);
+
+        metroflip_app_blink_start(app);
+    }
 }
 }
 
 
 static bool clipper_on_event(Metroflip* app, SceneManagerEvent event) {
 static bool clipper_on_event(Metroflip* app, SceneManagerEvent event) {
@@ -641,6 +671,7 @@ static bool clipper_on_event(Metroflip* app, SceneManagerEvent event) {
         }
         }
     } else if(event.type == SceneManagerEventTypeBack) {
     } else if(event.type == SceneManagerEventTypeBack) {
         scene_manager_search_and_switch_to_previous_scene(app->scene_manager, MetroflipSceneStart);
         scene_manager_search_and_switch_to_previous_scene(app->scene_manager, MetroflipSceneStart);
+        scene_manager_set_scene_state(app->scene_manager, MetroflipSceneStart, MetroflipSceneAuto);
         consumed = true;
         consumed = true;
     }
     }
 
 
@@ -651,7 +682,7 @@ static void clipper_on_exit(Metroflip* app) {
     widget_reset(app->widget);
     widget_reset(app->widget);
     metroflip_app_blink_stop(app);
     metroflip_app_blink_stop(app);
 
 
-    if(app->poller) {
+    if(app->poller && !app->data_loaded) {
         nfc_poller_stop(app->poller);
         nfc_poller_stop(app->poller);
         nfc_poller_free(app->poller);
         nfc_poller_free(app->poller);
     }
     }

+ 245 - 0
metroflip/scenes/plugins/gocard.c

@@ -0,0 +1,245 @@
+
+#include <flipper_application.h>
+#include "../../metroflip_i.h"
+
+#include <nfc/protocols/mf_classic/mf_classic_poller_sync.h>
+#include <nfc/protocols/mf_classic/mf_classic.h>
+#include <nfc/protocols/mf_classic/mf_classic_poller.h>
+
+#include <dolphin/dolphin.h>
+#include <bit_lib.h>
+#include <furi_hal.h>
+#include <nfc/nfc.h>
+#include <nfc/nfc_device.h>
+#include <nfc/nfc_listener.h>
+#include "../../api/metroflip/metroflip_api.h"
+#include "../../metroflip_plugins.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+#define TAG "Metroflip:Scene:gocard"
+
+typedef enum {
+    CHILD = 2051, // 0x803
+    ADULT = 3073 // 0xc01
+} ConcessionType;
+
+// Function to print concession type
+void printConcessionType(unsigned short concession_type, FuriString* parsed_data) {
+    switch(concession_type) {
+    case CHILD:
+        furi_string_cat_printf(parsed_data, "Concession Type: Child\n");
+        break;
+    case ADULT:
+        furi_string_cat_printf(parsed_data, "Concession Type: Adult\n");
+        break;
+    default:
+        furi_string_cat_printf(parsed_data, "Concession Type: 0x%X\n", concession_type);
+        break;
+    }
+}
+
+unsigned short byteArrayToIntReversed(unsigned int dec1, unsigned int dec2) {
+    unsigned char byte1 = (unsigned char)dec1;
+    unsigned char byte2 = (unsigned char)dec2;
+    return ((unsigned short)byte2 << 8) | byte1;
+}
+
+// Function to extract a substring and convert binary to decimal
+uint32_t extract_and_convert(const char* str, int start, int length) {
+    uint32_t value = 0;
+    for(int i = 0; i < length; i++) {
+        if(str[start + i] == '1') {
+            value |= (1U << (length - 1 - i));
+        }
+    }
+    return value;
+}
+
+void parse_gocard_time(const char* bin_str, FuriString* parsed_data) {
+    int len = strlen(bin_str);
+    if(len != 32 && len != 33) {
+        FURI_LOG_I(TAG, "Invalid input length");
+        return;
+    }
+
+    // Extract values from right to left using bit_slice_to_dec
+    uint32_t day = bit_slice_to_dec(bin_str, len - 5, len);
+    uint32_t month = bit_slice_to_dec(bin_str, len - 9, len - 6);
+    uint32_t year = bit_slice_to_dec(bin_str, len - 15, len - 10);
+    uint32_t minutes = bit_slice_to_dec(bin_str, len - 26, len - 16);
+
+    // Convert year from offset 2000
+    year += 2000;
+
+    // Convert minutes since midnight to HH:MM
+    uint32_t hours = minutes / 60;
+    uint32_t mins = minutes % 60;
+
+    // Format output string: "YYYY-MM-DD HH:MM"
+    furi_string_cat_printf(
+        parsed_data, "%04lu-%02lu-%02lu %02lu:%02lu\n", year, month, day, hours, mins);
+}
+
+static bool gocard_parse(FuriString* parsed_data, const MfClassicData* data) {
+    bool parsed = false;
+
+    do {
+        int balance_slot = 4;
+
+        if(data->block[balance_slot].data[13] <= data->block[balance_slot + 1].data[13])
+            balance_slot++;
+
+        unsigned short balancecents = byteArrayToIntReversed(
+            data->block[balance_slot].data[2], data->block[balance_slot].data[3]);
+
+        // Check if the sign flag is set in 'balance'
+        if((balancecents & 0x8000) == 0x8000) {
+            balancecents = balancecents & 0x7fff; // Clear the sign flag.
+            balancecents *= -1; // Negate the balance.
+        }
+        // Otherwise, check the sign flag in data->block[4].data[1]
+        else if((data->block[balance_slot].data[1] & 0x80) == 0x80) {
+            // seq_go uses a sign flag in an adjacent byte.
+            balancecents *= -1;
+        }
+
+        double balance = balancecents / 100.0;
+        furi_string_printf(parsed_data, "\e#go card\nValue: A$%.2f\n", balance); //show balance
+
+        int start_index = 4; //byte to start at
+        int end_index = 7; // byte to end at
+        int config_block = 6; //block number containing card configuration
+        int num_bytes = end_index - start_index + 1;
+        char config_bit_representation[num_bytes * 8 + 1];
+
+        for(int i = end_index, j = 0; i >= start_index;
+            i--, j++) { // Reverse the order of bytes and converty to binary
+            char bits[9];
+            byte_to_binary(data->block[config_block].data[i], bits);
+            memcpy(&config_bit_representation[j * 8], bits, 8);
+        }
+
+        config_bit_representation[num_bytes * 8] = '\0'; //add a null terminator as always
+        furi_string_cat_printf(parsed_data, "Expiry:\n");
+        parse_gocard_time(config_bit_representation, parsed_data);
+        FURI_LOG_I(TAG, "bitrepr: %s", config_bit_representation);
+
+        //concession type:
+
+        unsigned short concession_type = byteArrayToIntReversed(
+            data->block[config_block].data[8], data->block[config_block].data[9]);
+
+        printConcessionType(concession_type, parsed_data);
+
+        parsed = true;
+    } while(false);
+
+    return parsed;
+}
+
+static void gocard_on_enter(Metroflip* app) {
+    dolphin_deed(DolphinDeedNfcRead);
+
+    app->sec_num = 0;
+
+    if(app->data_loaded) {
+        Storage* storage = furi_record_open(RECORD_STORAGE);
+        FlipperFormat* ff = flipper_format_file_alloc(storage);
+        if(flipper_format_file_open_existing(ff, app->file_path)) {
+            MfClassicData* mfc_data = mf_classic_alloc();
+            mf_classic_load(mfc_data, ff, 2);
+            FuriString* parsed_data = furi_string_alloc();
+            Widget* widget = app->widget;
+
+            furi_string_reset(app->text_box_store);
+            if(!gocard_parse(parsed_data, mfc_data)) {
+                furi_string_reset(app->text_box_store);
+                FURI_LOG_I(TAG, "Unknown card type");
+                furi_string_printf(parsed_data, "\e#Unknown card\n");
+            }
+            widget_add_text_scroll_element(
+                widget, 0, 0, 128, 64, furi_string_get_cstr(parsed_data));
+
+            widget_add_button_element(
+                widget, GuiButtonTypeRight, "Exit", metroflip_exit_widget_callback, app);
+            widget_add_button_element(
+                widget, GuiButtonTypeCenter, "Delete", metroflip_delete_widget_callback, app);
+            mf_classic_free(mfc_data);
+            furi_string_free(parsed_data);
+            view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewWidget);
+        }
+        flipper_format_free(ff);
+    } else {
+        // Setup view
+        Popup* popup = app->popup;
+        popup_set_header(popup, "unsupported", 68, 30, AlignLeft, AlignTop);
+        popup_set_icon(popup, 0, 3, &I_RFIDDolphinReceive_97x61);
+    }
+}
+
+static bool gocard_on_event(Metroflip* app, SceneManagerEvent event) {
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == MetroflipCustomEventCardDetected) {
+            Popup* popup = app->popup;
+            popup_set_header(popup, "DON'T\nMOVE", 68, 30, AlignLeft, AlignTop);
+            consumed = true;
+        } else if(event.event == MetroflipCustomEventCardLost) {
+            Popup* popup = app->popup;
+            popup_set_header(popup, "Card \n lost", 68, 30, AlignLeft, AlignTop);
+            consumed = true;
+        } else if(event.event == MetroflipCustomEventWrongCard) {
+            Popup* popup = app->popup;
+            popup_set_header(popup, "WRONG \n CARD", 68, 30, AlignLeft, AlignTop);
+            consumed = true;
+        } else if(event.event == MetroflipCustomEventPollerFail) {
+            Popup* popup = app->popup;
+            popup_set_header(popup, "Failed", 68, 30, AlignLeft, AlignTop);
+            consumed = true;
+        }
+    } else if(event.type == SceneManagerEventTypeBack) {
+        scene_manager_search_and_switch_to_previous_scene(app->scene_manager, MetroflipSceneStart);
+        scene_manager_set_scene_state(app->scene_manager, MetroflipSceneStart, MetroflipSceneAuto);
+        consumed = true;
+    }
+
+    return consumed;
+}
+
+static void gocard_on_exit(Metroflip* app) {
+    widget_reset(app->widget);
+
+    if(app->poller && !app->data_loaded) {
+        nfc_poller_stop(app->poller);
+        nfc_poller_free(app->poller);
+    }
+
+    // Clear view
+    popup_reset(app->popup);
+
+    metroflip_app_blink_stop(app);
+}
+
+/* Actual implementation of app<>plugin interface */
+static const MetroflipPlugin gocard_plugin = {
+    .card_name = "gocard",
+    .plugin_on_enter = gocard_on_enter,
+    .plugin_on_event = gocard_on_event,
+    .plugin_on_exit = gocard_on_exit,
+
+};
+
+/* Plugin descriptor to comply with basic plugin specification */
+static const FlipperAppPluginDescriptor gocard_plugin_descriptor = {
+    .appid = METROFLIP_SUPPORTED_CARD_PLUGIN_APP_ID,
+    .ep_api_version = METROFLIP_SUPPORTED_CARD_PLUGIN_API_VERSION,
+    .entry_point = &gocard_plugin,
+};
+
+/* Plugin entry point - must return a pointer to const descriptor  */
+const FlipperAppPluginDescriptor* gocard_plugin_ep(void) {
+    return &gocard_plugin_descriptor;
+}

+ 47 - 17
metroflip/scenes/plugins/itso.c

@@ -28,15 +28,12 @@ uint64_t swap_uint64(uint64_t val) {
     return (val << 32) | (val >> 32);
     return (val << 32) | (val >> 32);
 }
 }
 
 
-bool itso_parse(const NfcDevice* device, FuriString* parsed_data) {
-    furi_assert(device);
+bool itso_parse(const MfDesfireData* data, FuriString* parsed_data) {
     furi_assert(parsed_data);
     furi_assert(parsed_data);
 
 
     bool parsed = false;
     bool parsed = false;
 
 
     do {
     do {
-        const MfDesfireData* data = nfc_device_get_data(device, NfcProtocolMfDesfire);
-
         const MfDesfireApplication* app = mf_desfire_get_application(data, &itso_app_id);
         const MfDesfireApplication* app = mf_desfire_get_application(data, &itso_app_id);
         if(app == NULL) break;
         if(app == NULL) break;
 
 
@@ -126,7 +123,8 @@ static NfcCommand itso_poller_callback(NfcGenericEvent event, void* context) {
     if(mf_desfire_event->type == MfDesfirePollerEventTypeReadSuccess) {
     if(mf_desfire_event->type == MfDesfirePollerEventTypeReadSuccess) {
         nfc_device_set_data(
         nfc_device_set_data(
             app->nfc_device, NfcProtocolMfDesfire, nfc_poller_get_data(app->poller));
             app->nfc_device, NfcProtocolMfDesfire, nfc_poller_get_data(app->poller));
-        if(!itso_parse(app->nfc_device, parsed_data)) {
+        const MfDesfireData* data = nfc_device_get_data(app->nfc_device, NfcProtocolMfDesfire);
+        if(!itso_parse(data, parsed_data)) {
             furi_string_reset(app->text_box_store);
             furi_string_reset(app->text_box_store);
             FURI_LOG_I(TAG, "Unknown card type");
             FURI_LOG_I(TAG, "Unknown card type");
             furi_string_printf(parsed_data, "\e#Unknown card\n");
             furi_string_printf(parsed_data, "\e#Unknown card\n");
@@ -135,6 +133,8 @@ static NfcCommand itso_poller_callback(NfcGenericEvent event, void* context) {
 
 
         widget_add_button_element(
         widget_add_button_element(
             widget, GuiButtonTypeRight, "Exit", metroflip_exit_widget_callback, app);
             widget, GuiButtonTypeRight, "Exit", metroflip_exit_widget_callback, app);
+        widget_add_button_element(
+            widget, GuiButtonTypeCenter, "Save", metroflip_save_widget_callback, app);
 
 
         furi_string_free(parsed_data);
         furi_string_free(parsed_data);
         view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewWidget);
         view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewWidget);
@@ -151,18 +151,47 @@ static NfcCommand itso_poller_callback(NfcGenericEvent event, void* context) {
 static void itso_on_enter(Metroflip* app) {
 static void itso_on_enter(Metroflip* app) {
     dolphin_deed(DolphinDeedNfcRead);
     dolphin_deed(DolphinDeedNfcRead);
 
 
-    // Setup view
-    Popup* popup = app->popup;
-    popup_set_header(popup, "Apply\n card to\nthe back", 68, 30, AlignLeft, AlignTop);
-    popup_set_icon(popup, 0, 3, &I_RFIDDolphinReceive_97x61);
+    if(app->data_loaded) {
+        Storage* storage = furi_record_open(RECORD_STORAGE);
+        FlipperFormat* ff = flipper_format_file_alloc(storage);
+        if(flipper_format_file_open_existing(ff, app->file_path)) {
+            MfDesfireData* data = mf_desfire_alloc();
+            mf_desfire_load(data, ff, 2);
+            FuriString* parsed_data = furi_string_alloc();
+            Widget* widget = app->widget;
 
 
-    // Start worker
-    view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewPopup);
-    nfc_scanner_alloc(app->nfc);
-    app->poller = nfc_poller_alloc(app->nfc, NfcProtocolMfDesfire);
-    nfc_poller_start(app->poller, itso_poller_callback, app);
-
-    metroflip_app_blink_start(app);
+            furi_string_reset(app->text_box_store);
+            if(!itso_parse(data, parsed_data)) {
+                furi_string_reset(app->text_box_store);
+                FURI_LOG_I(TAG, "Unknown card type");
+                furi_string_printf(parsed_data, "\e#Unknown card\n");
+            }
+            widget_add_text_scroll_element(
+                widget, 0, 0, 128, 64, furi_string_get_cstr(parsed_data));
+
+            widget_add_button_element(
+                widget, GuiButtonTypeRight, "Exit", metroflip_exit_widget_callback, app);
+            widget_add_button_element(
+                widget, GuiButtonTypeCenter, "Delete", metroflip_delete_widget_callback, app);
+            mf_desfire_free(data);
+            furi_string_free(parsed_data);
+            view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewWidget);
+        }
+        flipper_format_free(ff);
+    } else {
+        // Setup view
+        Popup* popup = app->popup;
+        popup_set_header(popup, "Apply\n card to\nthe back", 68, 30, AlignLeft, AlignTop);
+        popup_set_icon(popup, 0, 3, &I_RFIDDolphinReceive_97x61);
+
+        // Start worker
+        view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewPopup);
+        nfc_scanner_alloc(app->nfc);
+        app->poller = nfc_poller_alloc(app->nfc, NfcProtocolMfDesfire);
+        nfc_poller_start(app->poller, itso_poller_callback, app);
+
+        metroflip_app_blink_start(app);
+    }
 }
 }
 
 
 static bool itso_on_event(Metroflip* app, SceneManagerEvent event) {
 static bool itso_on_event(Metroflip* app, SceneManagerEvent event) {
@@ -188,6 +217,7 @@ static bool itso_on_event(Metroflip* app, SceneManagerEvent event) {
         }
         }
     } else if(event.type == SceneManagerEventTypeBack) {
     } else if(event.type == SceneManagerEventTypeBack) {
         scene_manager_search_and_switch_to_previous_scene(app->scene_manager, MetroflipSceneStart);
         scene_manager_search_and_switch_to_previous_scene(app->scene_manager, MetroflipSceneStart);
+        scene_manager_set_scene_state(app->scene_manager, MetroflipSceneStart, MetroflipSceneAuto);
         consumed = true;
         consumed = true;
     }
     }
 
 
@@ -197,7 +227,7 @@ static bool itso_on_event(Metroflip* app, SceneManagerEvent event) {
 static void itso_on_exit(Metroflip* app) {
 static void itso_on_exit(Metroflip* app) {
     widget_reset(app->widget);
     widget_reset(app->widget);
     metroflip_app_blink_stop(app);
     metroflip_app_blink_stop(app);
-    if(app->poller) {
+    if(app->poller && !app->data_loaded) {
         nfc_poller_stop(app->poller);
         nfc_poller_stop(app->poller);
         nfc_poller_free(app->poller);
         nfc_poller_free(app->poller);
     }
     }

+ 44 - 13
metroflip/scenes/plugins/metromoney.c

@@ -124,6 +124,8 @@ static NfcCommand metromoney_poller_callback(NfcGenericEvent event, void* contex
 
 
         widget_add_button_element(
         widget_add_button_element(
             widget, GuiButtonTypeRight, "Exit", metroflip_exit_widget_callback, app);
             widget, GuiButtonTypeRight, "Exit", metroflip_exit_widget_callback, app);
+        widget_add_button_element(
+            widget, GuiButtonTypeCenter, "Save", metroflip_save_widget_callback, app);
 
 
         furi_string_free(parsed_data);
         furi_string_free(parsed_data);
         view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewWidget);
         view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewWidget);
@@ -142,18 +144,46 @@ static void metromoney_on_enter(Metroflip* app) {
 
 
     app->sec_num = 0;
     app->sec_num = 0;
 
 
-    // Setup view
-    Popup* popup = app->popup;
-    popup_set_header(popup, "Apply\n card to\nthe back", 68, 30, AlignLeft, AlignTop);
-    popup_set_icon(popup, 0, 3, &I_RFIDDolphinReceive_97x61);
-
-    // Start worker
-    view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewPopup);
-    nfc_scanner_alloc(app->nfc);
-    app->poller = nfc_poller_alloc(app->nfc, NfcProtocolMfClassic);
-    nfc_poller_start(app->poller, metromoney_poller_callback, app);
-
-    metroflip_app_blink_start(app);
+    if(app->data_loaded) {
+        Storage* storage = furi_record_open(RECORD_STORAGE);
+        FlipperFormat* ff = flipper_format_file_alloc(storage);
+        if(flipper_format_file_open_existing(ff, app->file_path)) {
+            MfClassicData* mfc_data = mf_classic_alloc();
+            mf_classic_load(mfc_data, ff, 2);
+            FuriString* parsed_data = furi_string_alloc();
+            Widget* widget = app->widget;
+
+            furi_string_reset(app->text_box_store);
+            if(!metromoney_parse(parsed_data, mfc_data)) {
+                furi_string_reset(app->text_box_store);
+                FURI_LOG_I(TAG, "Unknown card type");
+                furi_string_printf(parsed_data, "\e#Unknown card\n");
+            }
+            widget_add_text_scroll_element(
+                widget, 0, 0, 128, 64, furi_string_get_cstr(parsed_data));
+
+            widget_add_button_element(
+                widget, GuiButtonTypeRight, "Exit", metroflip_exit_widget_callback, app);
+            widget_add_button_element(
+                widget, GuiButtonTypeCenter, "Delete", metroflip_delete_widget_callback, app);
+            mf_classic_free(mfc_data);
+            furi_string_free(parsed_data);
+            view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewWidget);
+        }
+        flipper_format_free(ff);
+    } else {
+        // Setup view
+        Popup* popup = app->popup;
+        popup_set_header(popup, "Apply\n card to\nthe back", 68, 30, AlignLeft, AlignTop);
+        popup_set_icon(popup, 0, 3, &I_RFIDDolphinReceive_97x61);
+
+        // Start worker
+        view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewPopup);
+        app->poller = nfc_poller_alloc(app->nfc, NfcProtocolMfClassic);
+        nfc_poller_start(app->poller, metromoney_poller_callback, app);
+
+        metroflip_app_blink_start(app);
+    }
 }
 }
 
 
 static bool metromoney_on_event(Metroflip* app, SceneManagerEvent event) {
 static bool metromoney_on_event(Metroflip* app, SceneManagerEvent event) {
@@ -179,6 +209,7 @@ static bool metromoney_on_event(Metroflip* app, SceneManagerEvent event) {
         }
         }
     } else if(event.type == SceneManagerEventTypeBack) {
     } else if(event.type == SceneManagerEventTypeBack) {
         scene_manager_search_and_switch_to_previous_scene(app->scene_manager, MetroflipSceneStart);
         scene_manager_search_and_switch_to_previous_scene(app->scene_manager, MetroflipSceneStart);
+        scene_manager_set_scene_state(app->scene_manager, MetroflipSceneStart, MetroflipSceneAuto);
         consumed = true;
         consumed = true;
     }
     }
 
 
@@ -188,7 +219,7 @@ static bool metromoney_on_event(Metroflip* app, SceneManagerEvent event) {
 static void metromoney_on_exit(Metroflip* app) {
 static void metromoney_on_exit(Metroflip* app) {
     widget_reset(app->widget);
     widget_reset(app->widget);
 
 
-    if(app->poller) {
+    if(app->poller && !app->data_loaded) {
         nfc_poller_stop(app->poller);
         nfc_poller_stop(app->poller);
         nfc_poller_free(app->poller);
         nfc_poller_free(app->poller);
     }
     }

+ 47 - 17
metroflip/scenes/plugins/myki.c

@@ -36,15 +36,12 @@ static uint8_t myki_calculate_luhn(uint64_t number) {
     return (10 - (sum % 10)) % 10;
     return (10 - (sum % 10)) % 10;
 }
 }
 
 
-bool myki_parse(const NfcDevice* device, FuriString* parsed_data) {
-    furi_assert(device);
+bool myki_parse(const MfDesfireData* data, FuriString* parsed_data) {
     furi_assert(parsed_data);
     furi_assert(parsed_data);
 
 
     bool parsed = false;
     bool parsed = false;
 
 
     do {
     do {
-        const MfDesfireData* data = nfc_device_get_data(device, NfcProtocolMfDesfire);
-
         const MfDesfireApplication* app = mf_desfire_get_application(data, &myki_app_id);
         const MfDesfireApplication* app = mf_desfire_get_application(data, &myki_app_id);
         if(app == NULL) break;
         if(app == NULL) break;
 
 
@@ -109,7 +106,8 @@ static NfcCommand myki_poller_callback(NfcGenericEvent event, void* context) {
     if(mf_desfire_event->type == MfDesfirePollerEventTypeReadSuccess) {
     if(mf_desfire_event->type == MfDesfirePollerEventTypeReadSuccess) {
         nfc_device_set_data(
         nfc_device_set_data(
             app->nfc_device, NfcProtocolMfDesfire, nfc_poller_get_data(app->poller));
             app->nfc_device, NfcProtocolMfDesfire, nfc_poller_get_data(app->poller));
-        if(!myki_parse(app->nfc_device, parsed_data)) {
+        const MfDesfireData* data = nfc_device_get_data(app->nfc_device, NfcProtocolMfDesfire);
+        if(!myki_parse(data, parsed_data)) {
             furi_string_reset(app->text_box_store);
             furi_string_reset(app->text_box_store);
             FURI_LOG_I(TAG, "Unknown card type");
             FURI_LOG_I(TAG, "Unknown card type");
             furi_string_printf(parsed_data, "\e#Unknown card\n");
             furi_string_printf(parsed_data, "\e#Unknown card\n");
@@ -118,6 +116,8 @@ static NfcCommand myki_poller_callback(NfcGenericEvent event, void* context) {
 
 
         widget_add_button_element(
         widget_add_button_element(
             widget, GuiButtonTypeRight, "Exit", metroflip_exit_widget_callback, app);
             widget, GuiButtonTypeRight, "Exit", metroflip_exit_widget_callback, app);
+        widget_add_button_element(
+            widget, GuiButtonTypeCenter, "Save", metroflip_save_widget_callback, app);
 
 
         furi_string_free(parsed_data);
         furi_string_free(parsed_data);
         view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewWidget);
         view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewWidget);
@@ -134,18 +134,47 @@ static NfcCommand myki_poller_callback(NfcGenericEvent event, void* context) {
 static void myki_on_enter(Metroflip* app) {
 static void myki_on_enter(Metroflip* app) {
     dolphin_deed(DolphinDeedNfcRead);
     dolphin_deed(DolphinDeedNfcRead);
 
 
-    // Setup view
-    Popup* popup = app->popup;
-    popup_set_header(popup, "Apply\n card to\nthe back", 68, 30, AlignLeft, AlignTop);
-    popup_set_icon(popup, 0, 3, &I_RFIDDolphinReceive_97x61);
+    if(app->data_loaded) {
+        Storage* storage = furi_record_open(RECORD_STORAGE);
+        FlipperFormat* ff = flipper_format_file_alloc(storage);
+        if(flipper_format_file_open_existing(ff, app->file_path)) {
+            MfDesfireData* data = mf_desfire_alloc();
+            mf_desfire_load(data, ff, 2);
+            FuriString* parsed_data = furi_string_alloc();
+            Widget* widget = app->widget;
 
 
-    // Start worker
-    view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewPopup);
-    nfc_scanner_alloc(app->nfc);
-    app->poller = nfc_poller_alloc(app->nfc, NfcProtocolMfDesfire);
-    nfc_poller_start(app->poller, myki_poller_callback, app);
-
-    metroflip_app_blink_start(app);
+            furi_string_reset(app->text_box_store);
+            if(!myki_parse(data, parsed_data)) {
+                furi_string_reset(app->text_box_store);
+                FURI_LOG_I(TAG, "Unknown card type");
+                furi_string_printf(parsed_data, "\e#Unknown card\n");
+            }
+            widget_add_text_scroll_element(
+                widget, 0, 0, 128, 64, furi_string_get_cstr(parsed_data));
+
+            widget_add_button_element(
+                widget, GuiButtonTypeRight, "Exit", metroflip_exit_widget_callback, app);
+            widget_add_button_element(
+                widget, GuiButtonTypeCenter, "Delete", metroflip_delete_widget_callback, app);
+            mf_desfire_free(data);
+            furi_string_free(parsed_data);
+            view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewWidget);
+        }
+        flipper_format_free(ff);
+    } else {
+        // Setup view
+        Popup* popup = app->popup;
+        popup_set_header(popup, "Apply\n card to\nthe back", 68, 30, AlignLeft, AlignTop);
+        popup_set_icon(popup, 0, 3, &I_RFIDDolphinReceive_97x61);
+
+        // Start worker
+        view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewPopup);
+        nfc_scanner_alloc(app->nfc);
+        app->poller = nfc_poller_alloc(app->nfc, NfcProtocolMfDesfire);
+        nfc_poller_start(app->poller, myki_poller_callback, app);
+
+        metroflip_app_blink_start(app);
+    }
 }
 }
 
 
 static bool myki_on_event(Metroflip* app, SceneManagerEvent event) {
 static bool myki_on_event(Metroflip* app, SceneManagerEvent event) {
@@ -171,6 +200,7 @@ static bool myki_on_event(Metroflip* app, SceneManagerEvent event) {
         }
         }
     } else if(event.type == SceneManagerEventTypeBack) {
     } else if(event.type == SceneManagerEventTypeBack) {
         scene_manager_search_and_switch_to_previous_scene(app->scene_manager, MetroflipSceneStart);
         scene_manager_search_and_switch_to_previous_scene(app->scene_manager, MetroflipSceneStart);
+        scene_manager_set_scene_state(app->scene_manager, MetroflipSceneStart, MetroflipSceneAuto);
         consumed = true;
         consumed = true;
     }
     }
 
 
@@ -180,7 +210,7 @@ static bool myki_on_event(Metroflip* app, SceneManagerEvent event) {
 static void myki_on_exit(Metroflip* app) {
 static void myki_on_exit(Metroflip* app) {
     widget_reset(app->widget);
     widget_reset(app->widget);
     metroflip_app_blink_stop(app);
     metroflip_app_blink_stop(app);
-    if(app->poller) {
+    if(app->poller && !app->data_loaded) {
         nfc_poller_stop(app->poller);
         nfc_poller_stop(app->poller);
         nfc_poller_free(app->poller);
         nfc_poller_free(app->poller);
     }
     }

+ 41 - 18
metroflip/scenes/plugins/opal.c

@@ -118,13 +118,9 @@ static void opal_days_minutes_to_datetime(uint16_t days, uint16_t minutes, DateT
 
 
     out->day = days;
     out->day = days;
 }
 }
-
-bool opal_parse(const NfcDevice* device, FuriString* parsed_data) {
-    furi_assert(device);
+bool opal_parse(const MfDesfireData* data, FuriString* parsed_data) {
     furi_assert(parsed_data);
     furi_assert(parsed_data);
 
 
-    const MfDesfireData* data = nfc_device_get_data(device, NfcProtocolMfDesfire);
-
     bool parsed = false;
     bool parsed = false;
 
 
     do {
     do {
@@ -230,7 +226,8 @@ static NfcCommand opal_poller_callback(NfcGenericEvent event, void* context) {
     if(mf_desfire_event->type == MfDesfirePollerEventTypeReadSuccess) {
     if(mf_desfire_event->type == MfDesfirePollerEventTypeReadSuccess) {
         nfc_device_set_data(
         nfc_device_set_data(
             app->nfc_device, NfcProtocolMfDesfire, nfc_poller_get_data(app->poller));
             app->nfc_device, NfcProtocolMfDesfire, nfc_poller_get_data(app->poller));
-        if(!opal_parse(app->nfc_device, parsed_data)) {
+        const MfDesfireData* data = nfc_device_get_data(app->nfc_device, NfcProtocolMfDesfire);
+        if(!opal_parse(data, parsed_data)) {
             furi_string_reset(app->text_box_store);
             furi_string_reset(app->text_box_store);
             FURI_LOG_I(TAG, "Unknown card type");
             FURI_LOG_I(TAG, "Unknown card type");
             furi_string_printf(parsed_data, "\e#Unknown card\n");
             furi_string_printf(parsed_data, "\e#Unknown card\n");
@@ -239,6 +236,8 @@ static NfcCommand opal_poller_callback(NfcGenericEvent event, void* context) {
 
 
         widget_add_button_element(
         widget_add_button_element(
             widget, GuiButtonTypeRight, "Exit", metroflip_exit_widget_callback, app);
             widget, GuiButtonTypeRight, "Exit", metroflip_exit_widget_callback, app);
+        widget_add_button_element(
+            widget, GuiButtonTypeCenter, "Save", metroflip_save_widget_callback, app);
 
 
         furi_string_free(parsed_data);
         furi_string_free(parsed_data);
         view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewWidget);
         view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewWidget);
@@ -255,18 +254,41 @@ static NfcCommand opal_poller_callback(NfcGenericEvent event, void* context) {
 static void opal_on_enter(Metroflip* app) {
 static void opal_on_enter(Metroflip* app) {
     dolphin_deed(DolphinDeedNfcRead);
     dolphin_deed(DolphinDeedNfcRead);
 
 
-    // Setup view
-    Popup* popup = app->popup;
-    popup_set_header(popup, "Apply\n card to\nthe back", 68, 30, AlignLeft, AlignTop);
-    popup_set_icon(popup, 0, 3, &I_RFIDDolphinReceive_97x61);
-
-    // Start worker
-    view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewPopup);
-    nfc_scanner_alloc(app->nfc);
-    app->poller = nfc_poller_alloc(app->nfc, NfcProtocolMfDesfire);
-    nfc_poller_start(app->poller, opal_poller_callback, app);
+    if(app->data_loaded) {
+        Storage* storage = furi_record_open(RECORD_STORAGE);
+        FlipperFormat* ff = flipper_format_file_alloc(storage);
+        if(flipper_format_file_open_existing(ff, app->file_path)) {
+            mf_desfire_load(app->mfdes_data, ff, 2);
+            FuriString* parsed_data = furi_string_alloc();
+            Widget* widget = app->widget;
 
 
-    metroflip_app_blink_start(app);
+            furi_string_reset(app->text_box_store);
+            opal_parse(app->mfdes_data, parsed_data);
+            widget_add_text_scroll_element(
+                widget, 0, 0, 128, 64, furi_string_get_cstr(parsed_data));
+
+            widget_add_button_element(
+                widget, GuiButtonTypeRight, "Exit", metroflip_exit_widget_callback, app);
+            widget_add_button_element(
+                widget, GuiButtonTypeCenter, "Delete", metroflip_delete_widget_callback, app);
+            furi_string_free(parsed_data);
+            view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewWidget);
+        }
+        flipper_format_free(ff);
+    } else {
+        // Setup view
+        Popup* popup = app->popup;
+        popup_set_header(popup, "Apply\n card to\nthe back", 68, 30, AlignLeft, AlignTop);
+        popup_set_icon(popup, 0, 3, &I_RFIDDolphinReceive_97x61);
+
+        // Start worker
+        view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewPopup);
+        nfc_scanner_alloc(app->nfc);
+        app->poller = nfc_poller_alloc(app->nfc, NfcProtocolMfDesfire);
+        nfc_poller_start(app->poller, opal_poller_callback, app);
+
+        metroflip_app_blink_start(app);
+    }
 }
 }
 
 
 static bool opal_on_event(Metroflip* app, SceneManagerEvent event) {
 static bool opal_on_event(Metroflip* app, SceneManagerEvent event) {
@@ -292,6 +314,7 @@ static bool opal_on_event(Metroflip* app, SceneManagerEvent event) {
         }
         }
     } else if(event.type == SceneManagerEventTypeBack) {
     } else if(event.type == SceneManagerEventTypeBack) {
         scene_manager_search_and_switch_to_previous_scene(app->scene_manager, MetroflipSceneStart);
         scene_manager_search_and_switch_to_previous_scene(app->scene_manager, MetroflipSceneStart);
+        scene_manager_set_scene_state(app->scene_manager, MetroflipSceneStart, MetroflipSceneAuto);
         consumed = true;
         consumed = true;
     }
     }
 
 
@@ -301,7 +324,7 @@ static bool opal_on_event(Metroflip* app, SceneManagerEvent event) {
 static void opal_on_exit(Metroflip* app) {
 static void opal_on_exit(Metroflip* app) {
     widget_reset(app->widget);
     widget_reset(app->widget);
     metroflip_app_blink_stop(app);
     metroflip_app_blink_stop(app);
-    if(app->poller) {
+    if(app->poller && !app->data_loaded) {
         nfc_poller_stop(app->poller);
         nfc_poller_stop(app->poller);
         nfc_poller_free(app->poller);
         nfc_poller_free(app->poller);
     }
     }

+ 45 - 17
metroflip/scenes/plugins/smartrider.c

@@ -134,10 +134,8 @@ static void calculate_date(uint32_t timestamp, char* date_str, size_t date_str_s
     }
     }
 }
 }
 
 
-static bool smartrider_parse(const NfcDevice* device, FuriString* parsed_data) {
-    furi_assert(device);
+static bool smartrider_parse(FuriString* parsed_data, const MfClassicData* data) {
     furi_assert(parsed_data);
     furi_assert(parsed_data);
-    const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic);
     SmartRiderData sr_data = {0};
     SmartRiderData sr_data = {0};
 
 
     if(data->type != MfClassicType1k) {
     if(data->type != MfClassicType1k) {
@@ -307,10 +305,10 @@ static NfcCommand smartrider_poller_callback(NfcGenericEvent event, void* contex
     } else if(mfc_event->type == MfClassicPollerEventTypeSuccess) {
     } else if(mfc_event->type == MfClassicPollerEventTypeSuccess) {
         nfc_device_set_data(
         nfc_device_set_data(
             app->nfc_device, NfcProtocolMfClassic, nfc_poller_get_data(app->poller));
             app->nfc_device, NfcProtocolMfClassic, nfc_poller_get_data(app->poller));
-
+        const MfClassicData* mfc_data = nfc_device_get_data(app->nfc_device, NfcProtocolMfClassic);
         dolphin_deed(DolphinDeedNfcReadSuccess);
         dolphin_deed(DolphinDeedNfcReadSuccess);
         furi_string_reset(app->text_box_store);
         furi_string_reset(app->text_box_store);
-        if(!smartrider_parse(app->nfc_device, parsed_data)) {
+        if(!smartrider_parse(parsed_data, mfc_data)) {
             FURI_LOG_I(TAG, "Unknown card type");
             FURI_LOG_I(TAG, "Unknown card type");
             furi_string_printf(parsed_data, "\e#Unknown card\n");
             furi_string_printf(parsed_data, "\e#Unknown card\n");
         }
         }
@@ -318,6 +316,8 @@ static NfcCommand smartrider_poller_callback(NfcGenericEvent event, void* contex
 
 
         widget_add_button_element(
         widget_add_button_element(
             widget, GuiButtonTypeRight, "Exit", metroflip_exit_widget_callback, app);
             widget, GuiButtonTypeRight, "Exit", metroflip_exit_widget_callback, app);
+        widget_add_button_element(
+            widget, GuiButtonTypeCenter, "Save", metroflip_save_widget_callback, app);
 
 
         furi_string_free(parsed_data);
         furi_string_free(parsed_data);
         view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewWidget);
         view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewWidget);
@@ -333,23 +333,50 @@ static NfcCommand smartrider_poller_callback(NfcGenericEvent event, void* contex
 }
 }
 
 
 static void smartrider_on_enter(Metroflip* app) {
 static void smartrider_on_enter(Metroflip* app) {
-    FURI_LOG_I(TAG, "entered smartrider");
     dolphin_deed(DolphinDeedNfcRead);
     dolphin_deed(DolphinDeedNfcRead);
 
 
     mf_classic_key_cache_reset(app->mfc_key_cache);
     mf_classic_key_cache_reset(app->mfc_key_cache);
 
 
-    // Setup view
-    Popup* popup = app->popup;
-    popup_set_header(popup, "Apply\n card to\nthe back", 68, 30, AlignLeft, AlignTop);
-    popup_set_icon(popup, 0, 3, &I_RFIDDolphinReceive_97x61);
+    if(app->data_loaded) {
+        Storage* storage = furi_record_open(RECORD_STORAGE);
+        FlipperFormat* ff = flipper_format_file_alloc(storage);
+        if(flipper_format_file_open_existing(ff, app->file_path)) {
+            MfClassicData* mfc_data = mf_classic_alloc();
+            mf_classic_load(mfc_data, ff, 2);
+            FuriString* parsed_data = furi_string_alloc();
+            Widget* widget = app->widget;
+
+            furi_string_reset(app->text_box_store);
+            if(!smartrider_parse(parsed_data, mfc_data)) {
+                furi_string_reset(app->text_box_store);
+                FURI_LOG_I(TAG, "Unknown card type");
+                furi_string_printf(parsed_data, "\e#Unknown card\n");
+            }
+            widget_add_text_scroll_element(
+                widget, 0, 0, 128, 64, furi_string_get_cstr(parsed_data));
+
+            widget_add_button_element(
+                widget, GuiButtonTypeRight, "Exit", metroflip_exit_widget_callback, app);
+            widget_add_button_element(
+                widget, GuiButtonTypeCenter, "Delete", metroflip_delete_widget_callback, app);
+            mf_classic_free(mfc_data);
+            furi_string_free(parsed_data);
+            view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewWidget);
+        }
+        flipper_format_free(ff);
+    } else {
+        // Setup view
+        Popup* popup = app->popup;
+        popup_set_header(popup, "Apply\n card to\nthe back", 68, 30, AlignLeft, AlignTop);
+        popup_set_icon(popup, 0, 3, &I_RFIDDolphinReceive_97x61);
 
 
-    // Start worker
-    view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewPopup);
-    nfc_scanner_alloc(app->nfc);
-    app->poller = nfc_poller_alloc(app->nfc, NfcProtocolMfClassic);
-    nfc_poller_start(app->poller, smartrider_poller_callback, app);
+        // Start worker
+        view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewPopup);
+        app->poller = nfc_poller_alloc(app->nfc, NfcProtocolMfClassic);
+        nfc_poller_start(app->poller, smartrider_poller_callback, app);
 
 
-    metroflip_app_blink_start(app);
+        metroflip_app_blink_start(app);
+    }
 }
 }
 
 
 static bool smartrider_on_event(Metroflip* app, SceneManagerEvent event) {
 static bool smartrider_on_event(Metroflip* app, SceneManagerEvent event) {
@@ -375,6 +402,7 @@ static bool smartrider_on_event(Metroflip* app, SceneManagerEvent event) {
         }
         }
     } else if(event.type == SceneManagerEventTypeBack) {
     } else if(event.type == SceneManagerEventTypeBack) {
         scene_manager_search_and_switch_to_previous_scene(app->scene_manager, MetroflipSceneStart);
         scene_manager_search_and_switch_to_previous_scene(app->scene_manager, MetroflipSceneStart);
+        scene_manager_set_scene_state(app->scene_manager, MetroflipSceneStart, MetroflipSceneAuto);
         consumed = true;
         consumed = true;
     }
     }
 
 
@@ -384,7 +412,7 @@ static bool smartrider_on_event(Metroflip* app, SceneManagerEvent event) {
 static void smartrider_on_exit(Metroflip* app) {
 static void smartrider_on_exit(Metroflip* app) {
     widget_reset(app->widget);
     widget_reset(app->widget);
 
 
-    if(app->poller) {
+    if(app->poller && !app->data_loaded) {
         nfc_poller_stop(app->poller);
         nfc_poller_stop(app->poller);
         nfc_poller_free(app->poller);
         nfc_poller_free(app->poller);
     }
     }

+ 43 - 12
metroflip/scenes/plugins/troika.c

@@ -4,7 +4,7 @@
 #include <nfc/protocols/mf_classic/mf_classic_poller_sync.h>
 #include <nfc/protocols/mf_classic/mf_classic_poller_sync.h>
 #include <nfc/protocols/mf_classic/mf_classic.h>
 #include <nfc/protocols/mf_classic/mf_classic.h>
 #include <nfc/protocols/mf_classic/mf_classic_poller.h>
 #include <nfc/protocols/mf_classic/mf_classic_poller.h>
-#include "../../api/mosgortrans/mosgortrans_util.h"
+#include "../../api/metroflip/metroflip_api.h"
 
 
 #include <dolphin/dolphin.h>
 #include <dolphin/dolphin.h>
 #include <bit_lib.h>
 #include <bit_lib.h>
@@ -221,6 +221,8 @@ static NfcCommand troika_poller_callback(NfcGenericEvent event, void* context) {
 
 
         widget_add_button_element(
         widget_add_button_element(
             widget, GuiButtonTypeRight, "Exit", metroflip_exit_widget_callback, app);
             widget, GuiButtonTypeRight, "Exit", metroflip_exit_widget_callback, app);
+        widget_add_button_element(
+            widget, GuiButtonTypeCenter, "Save", metroflip_save_widget_callback, app);
 
 
         furi_string_free(parsed_data);
         furi_string_free(parsed_data);
         view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewWidget);
         view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewWidget);
@@ -239,18 +241,46 @@ static void troika_on_enter(Metroflip* app) {
 
 
     app->sec_num = 0;
     app->sec_num = 0;
 
 
-    // Setup view
-    Popup* popup = app->popup;
-    popup_set_header(popup, "Apply\n card to\nthe back", 68, 30, AlignLeft, AlignTop);
-    popup_set_icon(popup, 0, 3, &I_RFIDDolphinReceive_97x61);
+    if(app->data_loaded) {
+        Storage* storage = furi_record_open(RECORD_STORAGE);
+        FlipperFormat* ff = flipper_format_file_alloc(storage);
+        if(flipper_format_file_open_existing(ff, app->file_path)) {
+            MfClassicData* mfc_data = mf_classic_alloc();
+            mf_classic_load(mfc_data, ff, 2);
+            FuriString* parsed_data = furi_string_alloc();
+            Widget* widget = app->widget;
+
+            furi_string_reset(app->text_box_store);
+            if(!troika_parse(parsed_data, mfc_data)) {
+                furi_string_reset(app->text_box_store);
+                FURI_LOG_I(TAG, "Unknown card type");
+                furi_string_printf(parsed_data, "\e#Unknown card\n");
+            }
+            widget_add_text_scroll_element(
+                widget, 0, 0, 128, 64, furi_string_get_cstr(parsed_data));
+
+            widget_add_button_element(
+                widget, GuiButtonTypeRight, "Exit", metroflip_exit_widget_callback, app);
+            widget_add_button_element(
+                widget, GuiButtonTypeCenter, "Delete", metroflip_delete_widget_callback, app);
+            mf_classic_free(mfc_data);
+            furi_string_free(parsed_data);
+            view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewWidget);
+        }
+        flipper_format_free(ff);
+    } else {
+        // Setup view
+        Popup* popup = app->popup;
+        popup_set_header(popup, "Apply\n card to\nthe back", 68, 30, AlignLeft, AlignTop);
+        popup_set_icon(popup, 0, 3, &I_RFIDDolphinReceive_97x61);
 
 
-    // Start worker
-    view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewPopup);
-    nfc_scanner_alloc(app->nfc);
-    app->poller = nfc_poller_alloc(app->nfc, NfcProtocolMfClassic);
-    nfc_poller_start(app->poller, troika_poller_callback, app);
+        // Start worker
+        view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewPopup);
+        app->poller = nfc_poller_alloc(app->nfc, NfcProtocolMfClassic);
+        nfc_poller_start(app->poller, troika_poller_callback, app);
 
 
-    metroflip_app_blink_start(app);
+        metroflip_app_blink_start(app);
+    }
 }
 }
 
 
 static bool troika_on_event(Metroflip* app, SceneManagerEvent event) {
 static bool troika_on_event(Metroflip* app, SceneManagerEvent event) {
@@ -276,6 +306,7 @@ static bool troika_on_event(Metroflip* app, SceneManagerEvent event) {
         }
         }
     } else if(event.type == SceneManagerEventTypeBack) {
     } else if(event.type == SceneManagerEventTypeBack) {
         scene_manager_search_and_switch_to_previous_scene(app->scene_manager, MetroflipSceneStart);
         scene_manager_search_and_switch_to_previous_scene(app->scene_manager, MetroflipSceneStart);
+        scene_manager_set_scene_state(app->scene_manager, MetroflipSceneStart, MetroflipSceneAuto);
         consumed = true;
         consumed = true;
     }
     }
 
 
@@ -285,7 +316,7 @@ static bool troika_on_event(Metroflip* app, SceneManagerEvent event) {
 static void troika_on_exit(Metroflip* app) {
 static void troika_on_exit(Metroflip* app) {
     widget_reset(app->widget);
     widget_reset(app->widget);
 
 
-    if(app->poller) {
+    if(app->poller && !app->data_loaded) {
         nfc_poller_stop(app->poller);
         nfc_poller_stop(app->poller);
         nfc_poller_free(app->poller);
         nfc_poller_free(app->poller);
     }
     }

+ 1 - 1
nfc_playlist/.gitsubtree

@@ -1,2 +1,2 @@
-https://github.com/xMasterX/all-the-plugins dev non_catalog_apps/nfc_playlist 35b8ffdadb1a2caf757875edf815ea3b3a4c4dcb
+https://github.com/xMasterX/all-the-plugins dev non_catalog_apps/nfc_playlist 8bc18d5d99262484548b6bbc92690a1472c329a0
 https://github.com/acegoal07/FlipperZero_NFC_Playlist main /
 https://github.com/acegoal07/FlipperZero_NFC_Playlist main /

+ 3 - 2
nfc_playlist/README.md

@@ -15,8 +15,9 @@ Any feedback is welcome and would be very much appreciated as it helps me to imp
 - <a href="https://github.com/acegoal07/FlipperZero_NFC_Playlist/issues/new?assignees=acegoal07&labels=bug&projects=&template=bug_report.md&title=%5BBUG%5D">Bug report</a>
 - <a href="https://github.com/acegoal07/FlipperZero_NFC_Playlist/issues/new?assignees=acegoal07&labels=bug&projects=&template=bug_report.md&title=%5BBUG%5D">Bug report</a>
 ## Supported Firmwares
 ## Supported Firmwares
 As i know these firmwares are supported and working if you know any more please let me know
 As i know these firmwares are supported and working if you know any more please let me know
-- <a href="https://github.com/Next-Flip/Momentum-Firmware">Momentum</a>
-- <a href="https://github.com/RogueMaster/flipperzero-firmware-wPlugins">RogueMaster</a>
+- <a href="https://github.com/Next-Flip/Momentum-Firmware" target="_blank">Momentum</a>
+- <a href="https://github.com/RogueMaster/flipperzero-firmware-wPlugins" target="_blank">RogueMaster</a>
+- <a href="https://github.com/DarkFlippers/unleashed-firmware" target="_blank">Unleashed</a>
 ## Settings:
 ## Settings:
 - Emulate time (How long the NFC card will be emulated for)
 - Emulate time (How long the NFC card will be emulated for)
 - Delay time (How long the gap between the cards will be)
 - Delay time (How long the gap between the cards will be)

+ 1 - 1
nfc_playlist/scenes/nfc_playlist_scene_view_playlist_content.c

@@ -34,7 +34,7 @@ void nfc_playlist_view_playlist_content_scene_on_enter(void* context) {
 
 
         widget_add_text_scroll_element(
         widget_add_text_scroll_element(
             nfc_playlist->widget, 4, 4, 124, 60, furi_string_get_cstr(tmp_str));
             nfc_playlist->widget, 4, 4, 124, 60, furi_string_get_cstr(tmp_str));
-        widget_add_frame_element(nfc_playlist->widget, 0, 0, 128, 64, 0);
+        widget_add_rect_element(nfc_playlist->widget, 0, 0, 128, 64, 0, false);
 
 
         furi_string_free(tmp_str);
         furi_string_free(tmp_str);
     } else {
     } else {

+ 1 - 1
nrf24batch/.gitsubtree

@@ -1,2 +1,2 @@
-https://github.com/xMasterX/all-the-plugins dev non_catalog_apps/nrf24-batch 4558d74c9da36abc851edd96a95d18f7d5511a75
+https://github.com/xMasterX/all-the-plugins dev non_catalog_apps/nrf24-batch 8bc18d5d99262484548b6bbc92690a1472c329a0
 https://github.com/vad7/nRF24-Batch main /
 https://github.com/vad7/nRF24-Batch main /

+ 34 - 34
nrf24batch/lib/nrf24/nrf24.c

@@ -37,21 +37,24 @@ void nrf24_deinit() {
     }
     }
 }
 }
 
 
-void nrf24_spi_trx(FuriHalSpiBusHandle* handle, uint8_t* tx, uint8_t* rx, uint8_t size) {
+void nrf24_spi_trx(
+    const FuriHalSpiBusHandle* handle,
+    uint8_t* tx,
+    uint8_t* rx,
+    uint8_t size) {
     furi_hal_gpio_write(handle->cs, false);
     furi_hal_gpio_write(handle->cs, false);
     furi_hal_spi_bus_trx(handle, tx, rx, size, nrf24_TIMEOUT);
     furi_hal_spi_bus_trx(handle, tx, rx, size, nrf24_TIMEOUT);
     furi_hal_gpio_write(handle->cs, true);
     furi_hal_gpio_write(handle->cs, true);
 }
 }
 
 
-uint8_t nrf24_write_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t data) {
+uint8_t nrf24_write_reg(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t data) {
     uint8_t buf[] = {W_REGISTER | (REGISTER_MASK & reg), data};
     uint8_t buf[] = {W_REGISTER | (REGISTER_MASK & reg), data};
     nrf24_spi_trx(handle, buf, buf, 2);
     nrf24_spi_trx(handle, buf, buf, 2);
     //FURI_LOG_D("NRF_WR", " #%02X=%02X", reg, data);
     //FURI_LOG_D("NRF_WR", " #%02X=%02X", reg, data);
     return buf[0];
     return buf[0];
 }
 }
 
 
-uint8_t
-    nrf24_write_buf_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* data, uint8_t size) {
+uint8_t nrf24_write_buf_reg(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* data, uint8_t size) {
     uint8_t buf[size + 1];
     uint8_t buf[size + 1];
     buf[0] = W_REGISTER | (REGISTER_MASK & reg);
     buf[0] = W_REGISTER | (REGISTER_MASK & reg);
     memcpy(&buf[1], data, size);
     memcpy(&buf[1], data, size);
@@ -60,7 +63,7 @@ uint8_t
     return buf[0];
     return buf[0];
 }
 }
 
 
-uint8_t nrf24_read_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* data, uint8_t size) {
+uint8_t nrf24_read_reg(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* data, uint8_t size) {
     uint8_t buf[size + 1];
     uint8_t buf[size + 1];
     memset(buf, 0, size + 1);
     memset(buf, 0, size + 1);
     buf[0] = R_REGISTER | (REGISTER_MASK & reg);
     buf[0] = R_REGISTER | (REGISTER_MASK & reg);
@@ -69,47 +72,47 @@ uint8_t nrf24_read_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* data,
     return buf[0];
     return buf[0];
 }
 }
 
 
-uint8_t nrf24_read_register(FuriHalSpiBusHandle* handle, uint8_t reg) {
-    uint8_t buf[] = {R_REGISTER | (REGISTER_MASK & reg), 0};
+uint8_t nrf24_read_register(const FuriHalSpiBusHandle* handle, uint8_t reg) {
+    uint8_t buf[] = { R_REGISTER | (REGISTER_MASK & reg), 0 };
     nrf24_spi_trx(handle, buf, buf, 2);
     nrf24_spi_trx(handle, buf, buf, 2);
     return buf[1];
     return buf[1];
 }
 }
 
 
-uint8_t nrf24_flush_rx(FuriHalSpiBusHandle* handle) {
+uint8_t nrf24_flush_rx(const FuriHalSpiBusHandle* handle) {
     uint8_t tx[] = {FLUSH_RX};
     uint8_t tx[] = {FLUSH_RX};
     uint8_t rx[] = {0};
     uint8_t rx[] = {0};
     nrf24_spi_trx(handle, tx, rx, 1);
     nrf24_spi_trx(handle, tx, rx, 1);
     return rx[0];
     return rx[0];
 }
 }
 
 
-uint8_t nrf24_flush_tx(FuriHalSpiBusHandle* handle) {
+uint8_t nrf24_flush_tx(const FuriHalSpiBusHandle* handle) {
     uint8_t tx[] = {FLUSH_TX};
     uint8_t tx[] = {FLUSH_TX};
     uint8_t rx[] = {0};
     uint8_t rx[] = {0};
     nrf24_spi_trx(handle, tx, rx, 1);
     nrf24_spi_trx(handle, tx, rx, 1);
     return rx[0];
     return rx[0];
 }
 }
 
 
-uint8_t nrf24_get_maclen(FuriHalSpiBusHandle* handle) {
+uint8_t nrf24_get_maclen(const FuriHalSpiBusHandle* handle) {
     uint8_t maclen;
     uint8_t maclen;
     nrf24_read_reg(handle, REG_SETUP_AW, &maclen, 1);
     nrf24_read_reg(handle, REG_SETUP_AW, &maclen, 1);
     maclen &= 3;
     maclen &= 3;
     return maclen + 2;
     return maclen + 2;
 }
 }
 
 
-uint8_t nrf24_set_maclen(FuriHalSpiBusHandle* handle, uint8_t maclen) {
+uint8_t nrf24_set_maclen(const FuriHalSpiBusHandle* handle, uint8_t maclen) {
     assert(maclen > 1 && maclen < 6);
     assert(maclen > 1 && maclen < 6);
     uint8_t status = 0;
     uint8_t status = 0;
     status = nrf24_write_reg(handle, REG_SETUP_AW, maclen - 2);
     status = nrf24_write_reg(handle, REG_SETUP_AW, maclen - 2);
     return status;
     return status;
 }
 }
 
 
-uint8_t nrf24_status(FuriHalSpiBusHandle* handle) {
+uint8_t nrf24_status(const FuriHalSpiBusHandle* handle) {
     uint8_t tx = RF24_NOP;
     uint8_t tx = RF24_NOP;
     nrf24_spi_trx(handle, &tx, &tx, 1);
     nrf24_spi_trx(handle, &tx, &tx, 1);
     return tx;
     return tx;
 }
 }
 
 
-uint32_t nrf24_get_rate(FuriHalSpiBusHandle* handle) {
+uint32_t nrf24_get_rate(const FuriHalSpiBusHandle* handle) {
     uint8_t setup = 0;
     uint8_t setup = 0;
     uint32_t rate = 0;
     uint32_t rate = 0;
     nrf24_read_reg(handle, REG_RF_SETUP, &setup, 1);
     nrf24_read_reg(handle, REG_RF_SETUP, &setup, 1);
@@ -124,7 +127,7 @@ uint32_t nrf24_get_rate(FuriHalSpiBusHandle* handle) {
     return rate;
     return rate;
 }
 }
 
 
-uint8_t nrf24_set_rate(FuriHalSpiBusHandle* handle, uint32_t rate) {
+uint8_t nrf24_set_rate(const FuriHalSpiBusHandle* handle, uint32_t rate) {
     uint8_t r6 = 0;
     uint8_t r6 = 0;
     uint8_t status = 0;
     uint8_t status = 0;
     if(!rate) rate = 2000000;
     if(!rate) rate = 2000000;
@@ -142,19 +145,19 @@ uint8_t nrf24_set_rate(FuriHalSpiBusHandle* handle, uint32_t rate) {
     return status;
     return status;
 }
 }
 
 
-uint8_t nrf24_get_chan(FuriHalSpiBusHandle* handle) {
+uint8_t nrf24_get_chan(const FuriHalSpiBusHandle* handle) {
     uint8_t channel = 0;
     uint8_t channel = 0;
     nrf24_read_reg(handle, REG_RF_CH, &channel, 1);
     nrf24_read_reg(handle, REG_RF_CH, &channel, 1);
     return channel;
     return channel;
 }
 }
 
 
-uint8_t nrf24_set_chan(FuriHalSpiBusHandle* handle, uint8_t chan) {
+uint8_t nrf24_set_chan(const FuriHalSpiBusHandle* handle, uint8_t chan) {
     uint8_t status;
     uint8_t status;
     status = nrf24_write_reg(handle, REG_RF_CH, chan);
     status = nrf24_write_reg(handle, REG_RF_CH, chan);
     return status;
     return status;
 }
 }
 
 
-uint8_t nrf24_get_src_mac(FuriHalSpiBusHandle* handle, uint8_t* mac) {
+uint8_t nrf24_get_src_mac(const FuriHalSpiBusHandle* handle, uint8_t* mac) {
     uint8_t size = 0;
     uint8_t size = 0;
     uint8_t status = 0;
     uint8_t status = 0;
     size = nrf24_get_maclen(handle);
     size = nrf24_get_maclen(handle);
@@ -162,7 +165,7 @@ uint8_t nrf24_get_src_mac(FuriHalSpiBusHandle* handle, uint8_t* mac) {
     return status;
     return status;
 }
 }
 
 
-uint8_t nrf24_set_src_mac(FuriHalSpiBusHandle* handle, uint8_t* mac, uint8_t size) {
+uint8_t nrf24_set_src_mac(const FuriHalSpiBusHandle* handle, uint8_t* mac, uint8_t size) {
     uint8_t status = 0;
     uint8_t status = 0;
     uint8_t clearmac[] = {0, 0, 0, 0, 0};
     uint8_t clearmac[] = {0, 0, 0, 0, 0};
     nrf24_set_maclen(handle, size);
     nrf24_set_maclen(handle, size);
@@ -171,7 +174,7 @@ uint8_t nrf24_set_src_mac(FuriHalSpiBusHandle* handle, uint8_t* mac, uint8_t siz
     return status;
     return status;
 }
 }
 
 
-uint8_t nrf24_get_dst_mac(FuriHalSpiBusHandle* handle, uint8_t* mac) {
+uint8_t nrf24_get_dst_mac(const FuriHalSpiBusHandle* handle, uint8_t* mac) {
     uint8_t size = 0;
     uint8_t size = 0;
     uint8_t status = 0;
     uint8_t status = 0;
     size = nrf24_get_maclen(handle);
     size = nrf24_get_maclen(handle);
@@ -179,7 +182,7 @@ uint8_t nrf24_get_dst_mac(FuriHalSpiBusHandle* handle, uint8_t* mac) {
     return status;
     return status;
 }
 }
 
 
-uint8_t nrf24_set_dst_mac(FuriHalSpiBusHandle* handle, uint8_t* mac, uint8_t size) {
+uint8_t nrf24_set_dst_mac(const FuriHalSpiBusHandle* handle, uint8_t* mac, uint8_t size) {
     uint8_t status = 0;
     uint8_t status = 0;
     uint8_t clearmac[] = {0, 0, 0, 0, 0};
     uint8_t clearmac[] = {0, 0, 0, 0, 0};
     nrf24_set_maclen(handle, size);
     nrf24_set_maclen(handle, size);
@@ -188,14 +191,14 @@ uint8_t nrf24_set_dst_mac(FuriHalSpiBusHandle* handle, uint8_t* mac, uint8_t siz
     return status;
     return status;
 }
 }
 
 
-uint8_t nrf24_get_packetlen(FuriHalSpiBusHandle* handle, uint8_t pipe) {
+uint8_t nrf24_get_packetlen(const FuriHalSpiBusHandle* handle, uint8_t pipe) {
     uint8_t len = 0;
     uint8_t len = 0;
     if(pipe > 5) pipe = 0;
     if(pipe > 5) pipe = 0;
     nrf24_read_reg(handle, RX_PW_P0 + pipe, &len, 1);
     nrf24_read_reg(handle, RX_PW_P0 + pipe, &len, 1);
     return len;
     return len;
 }
 }
 
 
-uint8_t nrf24_set_packetlen(FuriHalSpiBusHandle* handle, uint8_t len) {
+uint8_t nrf24_set_packetlen(const FuriHalSpiBusHandle* handle, uint8_t len) {
     uint8_t status = 0;
     uint8_t status = 0;
     status = nrf24_write_reg(handle, RX_PW_P0, len);
     status = nrf24_write_reg(handle, RX_PW_P0, len);
     return status;
     return status;
@@ -203,11 +206,7 @@ uint8_t nrf24_set_packetlen(FuriHalSpiBusHandle* handle, uint8_t len) {
 
 
 // packet_size: 0 - dyn payload (read from PL_WID), 1 - read from pipe size, >1 - override
 // packet_size: 0 - dyn payload (read from PL_WID), 1 - read from pipe size, >1 - override
 // Return STATUS reg + additional: RX_DR - new data available, 0x80 - NRF24 hardware error
 // Return STATUS reg + additional: RX_DR - new data available, 0x80 - NRF24 hardware error
-uint8_t nrf24_rxpacket(
-    FuriHalSpiBusHandle* handle,
-    uint8_t* packet,
-    uint8_t* ret_packetsize,
-    uint8_t packet_size) {
+uint8_t nrf24_rxpacket(const FuriHalSpiBusHandle* handle, uint8_t* packet, uint8_t* ret_packetsize, uint8_t packet_size) {
     uint8_t status = 0;
     uint8_t status = 0;
     uint8_t buf[33]; // 32 max payload size + 1 for command
     uint8_t buf[33]; // 32 max payload size + 1 for command
 
 
@@ -246,7 +245,7 @@ uint8_t nrf24_rxpacket(
 }
 }
 
 
 // Return 0 when error
 // Return 0 when error
-uint8_t nrf24_txpacket(FuriHalSpiBusHandle* handle, uint8_t* payload, uint8_t size, bool ack) {
+uint8_t nrf24_txpacket(const FuriHalSpiBusHandle* handle, uint8_t* payload, uint8_t size, bool ack) {
     uint8_t status = 0;
     uint8_t status = 0;
     uint8_t buf[size + 1];
     uint8_t buf[size + 1];
     buf[0] = ack ? W_TX_PAYLOAD : W_TX_PAYLOAD_NOACK;
     buf[0] = ack ? W_TX_PAYLOAD : W_TX_PAYLOAD_NOACK;
@@ -270,7 +269,7 @@ uint8_t nrf24_txpacket(FuriHalSpiBusHandle* handle, uint8_t* payload, uint8_t si
     return status & TX_DS;
     return status & TX_DS;
 }
 }
 
 
-uint8_t nrf24_power_up(FuriHalSpiBusHandle* handle) {
+uint8_t nrf24_power_up(const FuriHalSpiBusHandle* handle) {
     uint8_t status = 0;
     uint8_t status = 0;
     uint8_t cfg = 0;
     uint8_t cfg = 0;
     nrf24_read_reg(handle, REG_CONFIG, &cfg, 1);
     nrf24_read_reg(handle, REG_CONFIG, &cfg, 1);
@@ -280,7 +279,7 @@ uint8_t nrf24_power_up(FuriHalSpiBusHandle* handle) {
     return status;
     return status;
 }
 }
 
 
-uint8_t nrf24_set_idle(FuriHalSpiBusHandle* handle) {
+uint8_t nrf24_set_idle(const FuriHalSpiBusHandle* handle) {
     uint8_t status = 0;
     uint8_t status = 0;
     uint8_t cfg = 0;
     uint8_t cfg = 0;
     nrf24_read_reg(handle, REG_CONFIG, &cfg, 1);
     nrf24_read_reg(handle, REG_CONFIG, &cfg, 1);
@@ -290,7 +289,7 @@ uint8_t nrf24_set_idle(FuriHalSpiBusHandle* handle) {
     return status;
     return status;
 }
 }
 
 
-uint8_t nrf24_set_rx_mode(FuriHalSpiBusHandle* handle) {
+uint8_t nrf24_set_rx_mode(const FuriHalSpiBusHandle* handle) {
     uint8_t cfg = 0;
     uint8_t cfg = 0;
     cfg = nrf24_read_register(handle, REG_CONFIG);
     cfg = nrf24_read_register(handle, REG_CONFIG);
     cfg |= 0x03; // PWR_UP, and PRIM_RX
     cfg |= 0x03; // PWR_UP, and PRIM_RX
@@ -299,7 +298,7 @@ uint8_t nrf24_set_rx_mode(FuriHalSpiBusHandle* handle) {
     return cfg;
     return cfg;
 }
 }
 
 
-uint8_t nrf24_set_tx_mode(FuriHalSpiBusHandle* handle) {
+uint8_t nrf24_set_tx_mode(const FuriHalSpiBusHandle* handle) {
     uint8_t reg;
     uint8_t reg;
     furi_hal_gpio_write(nrf24_CE_PIN, false);
     furi_hal_gpio_write(nrf24_CE_PIN, false);
     //nrf24_write_reg(handle, REG_STATUS, TX_DS | MAX_RT);
     //nrf24_write_reg(handle, REG_STATUS, TX_DS | MAX_RT);
@@ -379,6 +378,7 @@ void int16_to_bytes(uint16_t val, uint8_t* out, bool bigendian) {
 
 
 uint8_t nrf24_set_mac(uint8_t mac_addr, uint8_t* mac, uint8_t mlen) {
 uint8_t nrf24_set_mac(uint8_t mac_addr, uint8_t* mac, uint8_t mlen) {
     uint8_t addr[5];
     uint8_t addr[5];
-    for(int i = 0; i < mlen; i++) addr[i] = mac[mlen - i - 1];
+    for(int i = 0; i < mlen; i++)
+        addr[i] = mac[mlen - i - 1];
     return nrf24_write_buf_reg(nrf24_HANDLE, mac_addr, addr, mlen);
     return nrf24_write_buf_reg(nrf24_HANDLE, mac_addr, addr, mlen);
-}
+}

+ 73 - 76
nrf24batch/lib/nrf24/nrf24.h

@@ -8,53 +8,53 @@
 extern "C" {
 extern "C" {
 #endif
 #endif
 
 
-#define R_REGISTER 0x00
-#define W_REGISTER 0x20
-#define REGISTER_MASK 0x1F
-#define ACTIVATE 0x50
-#define R_RX_PL_WID 0x60
-#define R_RX_PAYLOAD 0x61
-#define W_TX_PAYLOAD 0xA0
+#define R_REGISTER         0x00
+#define W_REGISTER         0x20
+#define REGISTER_MASK      0x1F
+#define ACTIVATE           0x50
+#define R_RX_PL_WID        0x60
+#define R_RX_PAYLOAD       0x61
+#define W_TX_PAYLOAD       0xA0
 #define W_TX_PAYLOAD_NOACK 0xB0
 #define W_TX_PAYLOAD_NOACK 0xB0
-#define W_ACK_PAYLOAD 0xA8
-#define FLUSH_TX 0xE1
-#define FLUSH_RX 0xE2
-#define REUSE_TX_PL 0xE3
-#define RF24_NOP 0xFF
-
-#define REG_CONFIG 0x00
-#define REG_EN_AA 0x01
-#define REG_EN_RXADDR 0x02
-#define REG_SETUP_AW 0x03
-#define REG_SETUP_RETR 0x04
-#define REG_DYNPD 0x1C
-#define REG_FEATURE 0x1D
-#define REG_RF_SETUP 0x06
-#define REG_STATUS 0x07
-#define REG_RX_ADDR_P0 0x0A
-#define REG_RX_ADDR_P1 0x0B
-#define REG_RX_ADDR_P2 0x0C
-#define REG_RX_ADDR_P3 0x0D
-#define REG_RX_ADDR_P4 0x0E
-#define REG_RX_ADDR_P5 0x0F
-#define REG_RF_CH 0x05
-#define REG_TX_ADDR 0x10
+#define W_ACK_PAYLOAD      0xA8
+#define FLUSH_TX           0xE1
+#define FLUSH_RX           0xE2
+#define REUSE_TX_PL        0xE3
+#define RF24_NOP           0xFF
+
+#define REG_CONFIG      0x00
+#define REG_EN_AA       0x01
+#define REG_EN_RXADDR   0x02
+#define REG_SETUP_AW    0x03
+#define REG_SETUP_RETR  0x04
+#define REG_DYNPD       0x1C
+#define REG_FEATURE     0x1D
+#define REG_RF_SETUP    0x06
+#define REG_STATUS      0x07
+#define REG_RX_ADDR_P0  0x0A
+#define REG_RX_ADDR_P1  0x0B
+#define REG_RX_ADDR_P2  0x0C
+#define REG_RX_ADDR_P3  0x0D
+#define REG_RX_ADDR_P4  0x0E
+#define REG_RX_ADDR_P5  0x0F
+#define REG_RF_CH       0x05
+#define REG_TX_ADDR     0x10
 #define REG_FIFO_STATUS 0x17
 #define REG_FIFO_STATUS 0x17
-#define REG_OBSERVE_TX 0x08
-
-#define RX_PW_P0 0x11
-#define RX_PW_P1 0x12
-#define RX_PW_P2 0x13
-#define RX_PW_P3 0x14
-#define RX_PW_P4 0x15
-#define RX_PW_P5 0x16
-#define RX_DR 0x40
-#define TX_DS 0x20
-#define MAX_RT 0x10
+#define REG_OBSERVE_TX  0x08
+
+#define RX_PW_P0         0x11
+#define RX_PW_P1         0x12
+#define RX_PW_P2         0x13
+#define RX_PW_P3         0x14
+#define RX_PW_P4         0x15
+#define RX_PW_P5         0x16
+#define RX_DR            0x40
+#define TX_DS            0x20
+#define MAX_RT           0x10
 #define NRF24_EN_DYN_ACK 0x01
 #define NRF24_EN_DYN_ACK 0x01
 
 
 #define nrf24_TIMEOUT 500
 #define nrf24_TIMEOUT 500
-#define nrf24_CE_PIN &gpio_ext_pb2
+#define nrf24_CE_PIN  &gpio_ext_pb2
 #define nrf24_HANDLE                                                                        \
 #define nrf24_HANDLE                                                                        \
     (momentum_settings.spi_nrf24_handle == SpiDefault ? &furi_hal_spi_bus_handle_external : \
     (momentum_settings.spi_nrf24_handle == SpiDefault ? &furi_hal_spi_bus_handle_external : \
                                                         &furi_hal_spi_bus_handle_external_extra)
                                                         &furi_hal_spi_bus_handle_external_extra)
@@ -69,7 +69,7 @@ extern "C" {
  *
  *
  * @return     device status
  * @return     device status
  */
  */
-uint8_t nrf24_write_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t data);
+uint8_t nrf24_write_reg(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t data);
 
 
 /** Write buffer to device register
 /** Write buffer to device register
  *
  *
@@ -80,7 +80,7 @@ uint8_t nrf24_write_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t data);
  *
  *
  * @return     device status
  * @return     device status
  */
  */
-uint8_t nrf24_write_buf_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* data, uint8_t size);
+uint8_t nrf24_write_buf_reg(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* data, uint8_t size);
 
 
 /** Read device register
 /** Read device register
  *
  *
@@ -90,10 +90,10 @@ uint8_t nrf24_write_buf_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* d
  *
  *
  * @return     device status
  * @return     device status
  */
  */
-uint8_t nrf24_read_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* data, uint8_t size);
+uint8_t nrf24_read_reg(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* data, uint8_t size);
 
 
 // Read single register (1 byte)
 // Read single register (1 byte)
-uint8_t nrf24_read_register(FuriHalSpiBusHandle* handle, uint8_t reg);
+uint8_t nrf24_read_register(const FuriHalSpiBusHandle* handle, uint8_t reg);
 
 
 /** Power up the radio for operation
 /** Power up the radio for operation
  * 
  * 
@@ -101,7 +101,7 @@ uint8_t nrf24_read_register(FuriHalSpiBusHandle* handle, uint8_t reg);
  * 
  * 
  * @return     device status
  * @return     device status
  */
  */
-uint8_t nrf24_power_up(FuriHalSpiBusHandle* handle);
+uint8_t nrf24_power_up(const FuriHalSpiBusHandle* handle);
 
 
 /** Power down the radio
 /** Power down the radio
  * 
  * 
@@ -109,7 +109,7 @@ uint8_t nrf24_power_up(FuriHalSpiBusHandle* handle);
  * 
  * 
  * @return     device status
  * @return     device status
  */
  */
-uint8_t nrf24_set_idle(FuriHalSpiBusHandle* handle);
+uint8_t nrf24_set_idle(const FuriHalSpiBusHandle* handle);
 
 
 /** Sets the radio to RX mode
 /** Sets the radio to RX mode
  *
  *
@@ -117,7 +117,7 @@ uint8_t nrf24_set_idle(FuriHalSpiBusHandle* handle);
  * 
  * 
  * @return     device status
  * @return     device status
  */
  */
-uint8_t nrf24_set_rx_mode(FuriHalSpiBusHandle* handle);
+uint8_t nrf24_set_rx_mode(const FuriHalSpiBusHandle* handle);
 
 
 /** Sets the radio to TX mode
 /** Sets the radio to TX mode
  *
  *
@@ -125,7 +125,7 @@ uint8_t nrf24_set_rx_mode(FuriHalSpiBusHandle* handle);
  * 
  * 
  * @return     device status
  * @return     device status
  */
  */
-uint8_t nrf24_set_tx_mode(FuriHalSpiBusHandle* handle);
+uint8_t nrf24_set_tx_mode(const FuriHalSpiBusHandle* handle);
 
 
 /*=============================================================================================================*/
 /*=============================================================================================================*/
 
 
@@ -147,7 +147,7 @@ void nrf24_deinit();
  *
  *
  * @return     device status
  * @return     device status
  */
  */
-uint8_t nrf24_flush_rx(FuriHalSpiBusHandle* handle);
+uint8_t nrf24_flush_rx(const FuriHalSpiBusHandle* handle);
 
 
 /** Send flush tx command
 /** Send flush tx command
  *
  *
@@ -155,7 +155,7 @@ uint8_t nrf24_flush_rx(FuriHalSpiBusHandle* handle);
  *
  *
  * @return     device status
  * @return     device status
  */
  */
-uint8_t nrf24_flush_tx(FuriHalSpiBusHandle* handle);
+uint8_t nrf24_flush_tx(const FuriHalSpiBusHandle* handle);
 
 
 /** Gets the RX packet length in data pipe 0
 /** Gets the RX packet length in data pipe 0
  * 
  * 
@@ -163,7 +163,7 @@ uint8_t nrf24_flush_tx(FuriHalSpiBusHandle* handle);
  *             pipe - pipe index (0..5)
  *             pipe - pipe index (0..5)
  * @return     packet length in data pipe 0
  * @return     packet length in data pipe 0
  */
  */
-uint8_t nrf24_get_packetlen(FuriHalSpiBusHandle* handle, uint8_t pipe);
+uint8_t nrf24_get_packetlen(const FuriHalSpiBusHandle* handle, uint8_t pipe);
 
 
 /** Sets the RX packet length in data pipe 0
 /** Sets the RX packet length in data pipe 0
  * 
  * 
@@ -172,7 +172,7 @@ uint8_t nrf24_get_packetlen(FuriHalSpiBusHandle* handle, uint8_t pipe);
  * 
  * 
  * @return     device status
  * @return     device status
  */
  */
-uint8_t nrf24_set_packetlen(FuriHalSpiBusHandle* handle, uint8_t len);
+uint8_t nrf24_set_packetlen(const FuriHalSpiBusHandle* handle, uint8_t len);
 
 
 /** Gets configured length of MAC address
 /** Gets configured length of MAC address
  *
  *
@@ -180,7 +180,7 @@ uint8_t nrf24_set_packetlen(FuriHalSpiBusHandle* handle, uint8_t len);
  * 
  * 
  * @return     MAC address length
  * @return     MAC address length
  */
  */
-uint8_t nrf24_get_maclen(FuriHalSpiBusHandle* handle);
+uint8_t nrf24_get_maclen(const FuriHalSpiBusHandle* handle);
 
 
 /** Sets configured length of MAC address
 /** Sets configured length of MAC address
  *
  *
@@ -189,7 +189,7 @@ uint8_t nrf24_get_maclen(FuriHalSpiBusHandle* handle);
  * 
  * 
  * @return     MAC address length
  * @return     MAC address length
  */
  */
-uint8_t nrf24_set_maclen(FuriHalSpiBusHandle* handle, uint8_t maclen);
+uint8_t nrf24_set_maclen(const FuriHalSpiBusHandle* handle, uint8_t maclen);
 
 
 /** Gets the current status flags from the STATUS register
 /** Gets the current status flags from the STATUS register
  * 
  * 
@@ -197,7 +197,7 @@ uint8_t nrf24_set_maclen(FuriHalSpiBusHandle* handle, uint8_t maclen);
  * 
  * 
  * @return     status flags
  * @return     status flags
  */
  */
-uint8_t nrf24_status(FuriHalSpiBusHandle* handle);
+uint8_t nrf24_status(const FuriHalSpiBusHandle* handle);
 
 
 /** Gets the current transfer rate
 /** Gets the current transfer rate
  * 
  * 
@@ -205,7 +205,7 @@ uint8_t nrf24_status(FuriHalSpiBusHandle* handle);
  * 
  * 
  * @return     transfer rate in bps
  * @return     transfer rate in bps
  */
  */
-uint32_t nrf24_get_rate(FuriHalSpiBusHandle* handle);
+uint32_t nrf24_get_rate(const FuriHalSpiBusHandle* handle);
 
 
 /** Sets the transfer rate
 /** Sets the transfer rate
  *
  *
@@ -214,7 +214,7 @@ uint32_t nrf24_get_rate(FuriHalSpiBusHandle* handle);
  * 
  * 
  * @return     device status
  * @return     device status
  */
  */
-uint8_t nrf24_set_rate(FuriHalSpiBusHandle* handle, uint32_t rate);
+uint8_t nrf24_set_rate(const FuriHalSpiBusHandle* handle, uint32_t rate);
 
 
 /** Gets the current channel
 /** Gets the current channel
  * In nrf24, the channel number is multiplied times 1MHz and added to 2400MHz to get the frequency
  * In nrf24, the channel number is multiplied times 1MHz and added to 2400MHz to get the frequency
@@ -223,7 +223,7 @@ uint8_t nrf24_set_rate(FuriHalSpiBusHandle* handle, uint32_t rate);
  * 
  * 
  * @return     channel
  * @return     channel
  */
  */
-uint8_t nrf24_get_chan(FuriHalSpiBusHandle* handle);
+uint8_t nrf24_get_chan(const FuriHalSpiBusHandle* handle);
 
 
 /** Sets the channel
 /** Sets the channel
  *
  *
@@ -232,7 +232,7 @@ uint8_t nrf24_get_chan(FuriHalSpiBusHandle* handle);
  * 
  * 
  * @return     device status
  * @return     device status
  */
  */
-uint8_t nrf24_set_chan(FuriHalSpiBusHandle* handle, uint8_t chan);
+uint8_t nrf24_set_chan(const FuriHalSpiBusHandle* handle, uint8_t chan);
 
 
 /** Gets the source mac address
 /** Gets the source mac address
  *
  *
@@ -241,7 +241,7 @@ uint8_t nrf24_set_chan(FuriHalSpiBusHandle* handle, uint8_t chan);
  * 
  * 
  * @return     device status
  * @return     device status
  */
  */
-uint8_t nrf24_get_src_mac(FuriHalSpiBusHandle* handle, uint8_t* mac);
+uint8_t nrf24_get_src_mac(const FuriHalSpiBusHandle* handle, uint8_t* mac);
 
 
 /** Sets the source mac address
 /** Sets the source mac address
  *
  *
@@ -251,7 +251,7 @@ uint8_t nrf24_get_src_mac(FuriHalSpiBusHandle* handle, uint8_t* mac);
  * 
  * 
  * @return     device status
  * @return     device status
  */
  */
-uint8_t nrf24_set_src_mac(FuriHalSpiBusHandle* handle, uint8_t* mac, uint8_t size);
+uint8_t nrf24_set_src_mac(const FuriHalSpiBusHandle* handle, uint8_t* mac, uint8_t size);
 
 
 /** Gets the dest mac address
 /** Gets the dest mac address
  *
  *
@@ -260,7 +260,7 @@ uint8_t nrf24_set_src_mac(FuriHalSpiBusHandle* handle, uint8_t* mac, uint8_t siz
  * 
  * 
  * @return     device status
  * @return     device status
  */
  */
-uint8_t nrf24_get_dst_mac(FuriHalSpiBusHandle* handle, uint8_t* mac);
+uint8_t nrf24_get_dst_mac(const FuriHalSpiBusHandle* handle, uint8_t* mac);
 
 
 /** Sets the dest mac address
 /** Sets the dest mac address
  *
  *
@@ -270,7 +270,7 @@ uint8_t nrf24_get_dst_mac(FuriHalSpiBusHandle* handle, uint8_t* mac);
  * 
  * 
  * @return     device status
  * @return     device status
  */
  */
-uint8_t nrf24_set_dst_mac(FuriHalSpiBusHandle* handle, uint8_t* mac, uint8_t size);
+uint8_t nrf24_set_dst_mac(const FuriHalSpiBusHandle* handle, uint8_t* mac, uint8_t size);
 
 
 /** Reads RX packet
 /** Reads RX packet
  *
  *
@@ -281,11 +281,8 @@ uint8_t nrf24_set_dst_mac(FuriHalSpiBusHandle* handle, uint8_t* mac, uint8_t siz
  * 
  * 
  * @return     device status
  * @return     device status
  */
  */
-uint8_t nrf24_rxpacket(
-    FuriHalSpiBusHandle* handle,
-    uint8_t* packet,
-    uint8_t* ret_packetsize,
-    uint8_t packet_size_flag);
+uint8_t
+    nrf24_rxpacket(const FuriHalSpiBusHandle* handle, uint8_t* packet, uint8_t* ret_packetsize, uint8_t packet_size_flag);
 
 
 /** Sends TX packet
 /** Sends TX packet
  *
  *
@@ -296,7 +293,7 @@ uint8_t nrf24_rxpacket(
  * 
  * 
  * @return     device status
  * @return     device status
  */
  */
-uint8_t nrf24_txpacket(FuriHalSpiBusHandle* handle, uint8_t* payload, uint8_t size, bool ack);
+uint8_t nrf24_txpacket(const FuriHalSpiBusHandle* handle, uint8_t* payload, uint8_t size, bool ack);
 
 
 /** Configure the radio
 /** Configure the radio
  * This is not comprehensive, but covers a lot of the common configuration options that may be changed
  * This is not comprehensive, but covers a lot of the common configuration options that may be changed
@@ -311,7 +308,7 @@ uint8_t nrf24_txpacket(FuriHalSpiBusHandle* handle, uint8_t* payload, uint8_t si
  * 
  * 
  */
  */
 void nrf24_configure(
 void nrf24_configure(
-    FuriHalSpiBusHandle* handle,
+    const FuriHalSpiBusHandle* handle,
     uint8_t rate,
     uint8_t rate,
     uint8_t* srcmac,
     uint8_t* srcmac,
     uint8_t* dstmac,
     uint8_t* dstmac,
@@ -330,7 +327,7 @@ uint8_t nrf24_set_mac(uint8_t mac_addr, uint8_t* mac, uint8_t mlen);
  * @param      channel - channel to tune to
  * @param      channel - channel to tune to
  * @param      rate - transfer rate in Mbps (1 or 2) 
  * @param      rate - transfer rate in Mbps (1 or 2) 
  */
  */
-void nrf24_init_promisc_mode(FuriHalSpiBusHandle* handle, uint8_t channel, uint8_t rate);
+void nrf24_init_promisc_mode(const FuriHalSpiBusHandle* handle, uint8_t channel, uint8_t rate);
 
 
 /** Listens for a packet and returns first possible address sniffed
 /** Listens for a packet and returns first possible address sniffed
  * Call this only after calling nrf24_init_promisc_mode
  * Call this only after calling nrf24_init_promisc_mode
@@ -340,7 +337,7 @@ void nrf24_init_promisc_mode(FuriHalSpiBusHandle* handle, uint8_t channel, uint8
  * 
  * 
  * @return     success
  * @return     success
  */
  */
-bool nrf24_sniff_address(FuriHalSpiBusHandle* handle, uint8_t maclen, uint8_t* address);
+bool nrf24_sniff_address(const FuriHalSpiBusHandle* handle, uint8_t maclen, uint8_t* address);
 
 
 /** Sends ping packet on each channel for designated tx mac looking for ack
 /** Sends ping packet on each channel for designated tx mac looking for ack
  * 
  * 
@@ -356,7 +353,7 @@ bool nrf24_sniff_address(FuriHalSpiBusHandle* handle, uint8_t maclen, uint8_t* a
  * @return     channel that the address is listening on, if this value is above the max_channel param, it failed
  * @return     channel that the address is listening on, if this value is above the max_channel param, it failed
  */
  */
 uint8_t nrf24_find_channel(
 uint8_t nrf24_find_channel(
-    FuriHalSpiBusHandle* handle,
+    const FuriHalSpiBusHandle* handle,
     uint8_t* srcmac,
     uint8_t* srcmac,
     uint8_t* dstmac,
     uint8_t* dstmac,
     uint8_t maclen,
     uint8_t maclen,
@@ -389,4 +386,4 @@ uint32_t bytes_to_int32(uint8_t* bytes, bool bigendian);
 
 
 #ifdef __cplusplus
 #ifdef __cplusplus
 }
 }
-#endif
+#endif

+ 1 - 1
nrf24channelscanner/.gitsubtree

@@ -1,2 +1,2 @@
-https://github.com/xMasterX/all-the-plugins dev non_catalog_apps/nrf24channelscanner 4558d74c9da36abc851edd96a95d18f7d5511a75
+https://github.com/xMasterX/all-the-plugins dev non_catalog_apps/nrf24channelscanner 8bc18d5d99262484548b6bbc92690a1472c329a0
 https://github.com/htotoo/NRF24ChannelScanner main NRF24ChannelScanner e92da5db51ebbf0402d5e07b7af3a8d696817be2
 https://github.com/htotoo/NRF24ChannelScanner main NRF24ChannelScanner e92da5db51ebbf0402d5e07b7af3a8d696817be2

+ 9 - 9
nrf24channelscanner/lib/nrf24/nrf24.c

@@ -45,7 +45,7 @@ void nrf24_deinit() {
 }
 }
 
 
 void nrf24_spi_trx(
 void nrf24_spi_trx(
-    FuriHalSpiBusHandle* handle,
+    const FuriHalSpiBusHandle* handle,
     uint8_t* tx,
     uint8_t* tx,
     uint8_t* rx,
     uint8_t* rx,
     uint8_t size,
     uint8_t size,
@@ -56,14 +56,14 @@ void nrf24_spi_trx(
     furi_hal_gpio_write(handle->cs, true);
     furi_hal_gpio_write(handle->cs, true);
 }
 }
 
 
-uint8_t nrf24_write_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t data) {
+uint8_t nrf24_write_reg(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t data) {
     uint8_t tx[2] = {W_REGISTER | (REGISTER_MASK & reg), data};
     uint8_t tx[2] = {W_REGISTER | (REGISTER_MASK & reg), data};
     uint8_t rx[2] = {0};
     uint8_t rx[2] = {0};
     nrf24_spi_trx(handle, tx, rx, 2, nrf24_TIMEOUT);
     nrf24_spi_trx(handle, tx, rx, 2, nrf24_TIMEOUT);
     return rx[0];
     return rx[0];
 }
 }
 
 
-uint8_t nrf24_read_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* data, uint8_t size) {
+uint8_t nrf24_read_reg(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* data, uint8_t size) {
     uint8_t tx[size + 1];
     uint8_t tx[size + 1];
     uint8_t rx[size + 1];
     uint8_t rx[size + 1];
     memset(rx, 0, size + 1);
     memset(rx, 0, size + 1);
@@ -74,27 +74,27 @@ uint8_t nrf24_read_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* data,
     return rx[0];
     return rx[0];
 }
 }
 
 
-uint8_t nrf24_flush_rx(FuriHalSpiBusHandle* handle) {
+uint8_t nrf24_flush_rx(const FuriHalSpiBusHandle* handle) {
     uint8_t tx[] = {FLUSH_RX};
     uint8_t tx[] = {FLUSH_RX};
     uint8_t rx[] = {0};
     uint8_t rx[] = {0};
     nrf24_spi_trx(handle, tx, rx, 1, nrf24_TIMEOUT);
     nrf24_spi_trx(handle, tx, rx, 1, nrf24_TIMEOUT);
     return rx[0];
     return rx[0];
 }
 }
 
 
-uint8_t nrf24_get_rdp(FuriHalSpiBusHandle* handle) {
+uint8_t nrf24_get_rdp(const FuriHalSpiBusHandle* handle) {
     uint8_t rdp;
     uint8_t rdp;
     nrf24_read_reg(handle, REG_RDP, &rdp, 1);
     nrf24_read_reg(handle, REG_RDP, &rdp, 1);
     return rdp;
     return rdp;
 }
 }
 
 
-uint8_t nrf24_status(FuriHalSpiBusHandle* handle) {
+uint8_t nrf24_status(const FuriHalSpiBusHandle* handle) {
     uint8_t status;
     uint8_t status;
     uint8_t tx[] = {R_REGISTER | (REGISTER_MASK & REG_STATUS)};
     uint8_t tx[] = {R_REGISTER | (REGISTER_MASK & REG_STATUS)};
     nrf24_spi_trx(handle, tx, &status, 1, nrf24_TIMEOUT);
     nrf24_spi_trx(handle, tx, &status, 1, nrf24_TIMEOUT);
     return status;
     return status;
 }
 }
 
 
-uint8_t nrf24_set_idle(FuriHalSpiBusHandle* handle) {
+uint8_t nrf24_set_idle(const FuriHalSpiBusHandle* handle) {
     uint8_t status = 0;
     uint8_t status = 0;
     uint8_t cfg = 0;
     uint8_t cfg = 0;
     nrf24_read_reg(handle, REG_CONFIG, &cfg, 1);
     nrf24_read_reg(handle, REG_CONFIG, &cfg, 1);
@@ -104,7 +104,7 @@ uint8_t nrf24_set_idle(FuriHalSpiBusHandle* handle) {
     return status;
     return status;
 }
 }
 
 
-uint8_t nrf24_set_rx_mode(FuriHalSpiBusHandle* handle, bool nodelay) {
+uint8_t nrf24_set_rx_mode(const FuriHalSpiBusHandle* handle, bool nodelay) {
     uint8_t status = 0;
     uint8_t status = 0;
     uint8_t cfg = 0;
     uint8_t cfg = 0;
     nrf24_read_reg(handle, REG_CONFIG, &cfg, 1);
     nrf24_read_reg(handle, REG_CONFIG, &cfg, 1);
@@ -115,7 +115,7 @@ uint8_t nrf24_set_rx_mode(FuriHalSpiBusHandle* handle, bool nodelay) {
     return status;
     return status;
 }
 }
 
 
-bool nrf24_check_connected(FuriHalSpiBusHandle* handle) {
+bool nrf24_check_connected(const FuriHalSpiBusHandle* handle) {
     uint8_t status = nrf24_status(handle);
     uint8_t status = nrf24_status(handle);
 
 
     if(status != 0x00) {
     if(status != 0x00) {

+ 35 - 35
nrf24channelscanner/lib/nrf24/nrf24.h

@@ -10,40 +10,40 @@
 extern "C" {
 extern "C" {
 #endif
 #endif
 
 
-#define R_REGISTER 0x00
-#define W_REGISTER 0x20
-#define REGISTER_MASK 0x1F
-#define ACTIVATE 0x50
-#define R_RX_PL_WID 0x60
-#define R_RX_PAYLOAD 0x61
-#define W_TX_PAYLOAD 0xA0
+#define R_REGISTER         0x00
+#define W_REGISTER         0x20
+#define REGISTER_MASK      0x1F
+#define ACTIVATE           0x50
+#define R_RX_PL_WID        0x60
+#define R_RX_PAYLOAD       0x61
+#define W_TX_PAYLOAD       0xA0
 #define W_TX_PAYLOAD_NOACK 0xB0
 #define W_TX_PAYLOAD_NOACK 0xB0
-#define W_ACK_PAYLOAD 0xA8
-#define FLUSH_TX 0xE1
-#define FLUSH_RX 0xE2
-#define REUSE_TX_PL 0xE3
-#define RF24_NOP 0xFF
-
-#define REG_CONFIG 0x00
-#define REG_EN_AA 0x01
-#define REG_EN_RXADDR 0x02
-#define REG_SETUP_AW 0x03
+#define W_ACK_PAYLOAD      0xA8
+#define FLUSH_TX           0xE1
+#define FLUSH_RX           0xE2
+#define REUSE_TX_PL        0xE3
+#define RF24_NOP           0xFF
+
+#define REG_CONFIG     0x00
+#define REG_EN_AA      0x01
+#define REG_EN_RXADDR  0x02
+#define REG_SETUP_AW   0x03
 #define REG_SETUP_RETR 0x04
 #define REG_SETUP_RETR 0x04
-#define REG_RDP 0x09
-#define REG_DYNPD 0x1C
-#define REG_FEATURE 0x1D
-#define REG_RF_SETUP 0x06
-#define REG_STATUS 0x07
+#define REG_RDP        0x09
+#define REG_DYNPD      0x1C
+#define REG_FEATURE    0x1D
+#define REG_RF_SETUP   0x06
+#define REG_STATUS     0x07
 #define REG_RX_ADDR_P0 0x0A
 #define REG_RX_ADDR_P0 0x0A
-#define REG_RF_CH 0x05
-#define REG_TX_ADDR 0x10
+#define REG_RF_CH      0x05
+#define REG_TX_ADDR    0x10
 
 
 #define RX_PW_P0 0x11
 #define RX_PW_P0 0x11
-#define TX_DS 0x20
-#define MAX_RT 0x10
+#define TX_DS    0x20
+#define MAX_RT   0x10
 
 
 #define nrf24_TIMEOUT 500
 #define nrf24_TIMEOUT 500
-#define nrf24_CE_PIN &gpio_ext_pb2
+#define nrf24_CE_PIN  &gpio_ext_pb2
 #ifdef MOMENTUM_SETTINGS_PATH
 #ifdef MOMENTUM_SETTINGS_PATH
 #define nrf24_HANDLE                                                                        \
 #define nrf24_HANDLE                                                                        \
     (momentum_settings.spi_nrf24_handle == SpiDefault ? &furi_hal_spi_bus_handle_external : \
     (momentum_settings.spi_nrf24_handle == SpiDefault ? &furi_hal_spi_bus_handle_external : \
@@ -61,7 +61,7 @@ extern "C" {
  *
  *
  * @return     device status
  * @return     device status
  */
  */
-uint8_t nrf24_write_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t data);
+uint8_t nrf24_write_reg(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t data);
 
 
 /** Read device register
 /** Read device register
  *
  *
@@ -71,7 +71,7 @@ uint8_t nrf24_write_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t data);
  *
  *
  * @return     device status
  * @return     device status
  */
  */
-uint8_t nrf24_read_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* data, uint8_t size);
+uint8_t nrf24_read_reg(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* data, uint8_t size);
 
 
 /** Power down the radio
 /** Power down the radio
  * 
  * 
@@ -79,7 +79,7 @@ uint8_t nrf24_read_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* data,
  * 
  * 
  * @return     device status
  * @return     device status
  */
  */
-uint8_t nrf24_set_idle(FuriHalSpiBusHandle* handle);
+uint8_t nrf24_set_idle(const FuriHalSpiBusHandle* handle);
 
 
 /** Sets the radio to RX mode
 /** Sets the radio to RX mode
  *
  *
@@ -87,7 +87,7 @@ uint8_t nrf24_set_idle(FuriHalSpiBusHandle* handle);
  * 
  * 
  * @return     device status
  * @return     device status
  */
  */
-uint8_t nrf24_set_rx_mode(FuriHalSpiBusHandle* handle, bool nodelay);
+uint8_t nrf24_set_rx_mode(const FuriHalSpiBusHandle* handle, bool nodelay);
 
 
 /*=============================================================================================================*/
 /*=============================================================================================================*/
 
 
@@ -109,7 +109,7 @@ void nrf24_deinit();
  *
  *
  * @return     device status
  * @return     device status
  */
  */
-uint8_t nrf24_flush_rx(FuriHalSpiBusHandle* handle);
+uint8_t nrf24_flush_rx(const FuriHalSpiBusHandle* handle);
 
 
 /** Gets RDP from register 0x09
 /** Gets RDP from register 0x09
  *
  *
@@ -117,7 +117,7 @@ uint8_t nrf24_flush_rx(FuriHalSpiBusHandle* handle);
  * 
  * 
  * @return     RDP from register 0x09
  * @return     RDP from register 0x09
  */
  */
-uint8_t nrf24_get_rdp(FuriHalSpiBusHandle* handle);
+uint8_t nrf24_get_rdp(const FuriHalSpiBusHandle* handle);
 
 
 /** Gets the current status flags from the STATUS register
 /** Gets the current status flags from the STATUS register
  * 
  * 
@@ -125,9 +125,9 @@ uint8_t nrf24_get_rdp(FuriHalSpiBusHandle* handle);
  * 
  * 
  * @return     status flags
  * @return     status flags
  */
  */
-uint8_t nrf24_status(FuriHalSpiBusHandle* handle);
+uint8_t nrf24_status(const FuriHalSpiBusHandle* handle);
 
 
-bool nrf24_check_connected(FuriHalSpiBusHandle* handle);
+bool nrf24_check_connected(const FuriHalSpiBusHandle* handle);
 
 
 #ifdef __cplusplus
 #ifdef __cplusplus
 }
 }

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