Преглед изворни кода

Merge pull request #75 from derskythe/subbrute-deep-refactor

SubBrute deep refactor
10697207+xMasterX@users.noreply.github.com пре 3 година
родитељ
комит
5489da72fc
35 измењених фајлова са 3010 додато и 1541 уклоњено
  1. 1 1
      applications/plugins/subbrute/application.fam
  2. 370 0
      applications/plugins/subbrute/helpers/subbrute_worker.c
  3. 35 0
      applications/plugins/subbrute/helpers/subbrute_worker.h
  4. 0 197
      applications/plugins/subbrute/scene/subbrute_scene_entrypoint.c
  5. 0 8
      applications/plugins/subbrute/scene/subbrute_scene_entrypoint.h
  6. 0 222
      applications/plugins/subbrute/scene/subbrute_scene_load_file.c
  7. 0 8
      applications/plugins/subbrute/scene/subbrute_scene_load_file.h
  8. 0 384
      applications/plugins/subbrute/scene/subbrute_scene_run_attack.c
  9. 0 8
      applications/plugins/subbrute/scene/subbrute_scene_run_attack.h
  10. 0 222
      applications/plugins/subbrute/scene/subbrute_scene_save_name.c
  11. 0 6
      applications/plugins/subbrute/scene/subbrute_scene_save_name.h
  12. 0 121
      applications/plugins/subbrute/scene/subbrute_scene_select_field.c
  13. 0 8
      applications/plugins/subbrute/scene/subbrute_scene_select_field.h
  14. 29 0
      applications/plugins/subbrute/scenes/subbrute_scene.h
  15. 7 0
      applications/plugins/subbrute/scenes/subbrute_scene_config.h
  16. 77 0
      applications/plugins/subbrute/scenes/subbrute_scene_load_file.c
  17. 61 0
      applications/plugins/subbrute/scenes/subbrute_scene_load_select.c
  18. 83 0
      applications/plugins/subbrute/scenes/subbrute_scene_run_attack.c
  19. 87 0
      applications/plugins/subbrute/scenes/subbrute_scene_save_name.c
  20. 51 0
      applications/plugins/subbrute/scenes/subbrute_scene_save_success.c
  21. 177 0
      applications/plugins/subbrute/scenes/subbrute_scene_setup_attack.c
  22. 66 0
      applications/plugins/subbrute/scenes/subbrute_scene_start.c
  23. 30 0
      applications/plugins/subbrute/scenes/subbute_scene.c
  24. 269 231
      applications/plugins/subbrute/subbrute.c
  25. 1 108
      applications/plugins/subbrute/subbrute.h
  26. 28 0
      applications/plugins/subbrute/subbrute_custom_event.h
  27. 643 0
      applications/plugins/subbrute/subbrute_device.c
  28. 99 0
      applications/plugins/subbrute/subbrute_device.h
  29. 83 0
      applications/plugins/subbrute/subbrute_i.h
  30. 0 13
      applications/plugins/subbrute/subbrute_utils.c
  31. 0 4
      applications/plugins/subbrute/subbrute_utils.h
  32. 374 0
      applications/plugins/subbrute/views/subbrute_attack_view.c
  33. 28 0
      applications/plugins/subbrute/views/subbrute_attack_view.h
  34. 381 0
      applications/plugins/subbrute/views/subbrute_main_view.c
  35. 30 0
      applications/plugins/subbrute/views/subbrute_main_view.h

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

@@ -2,7 +2,7 @@ App(
     appid="subbrute",
     name="Sub-GHz Bruteforcer",
     apptype=FlipperAppType.EXTERNAL,
-    entry_point="subbrute_start",
+    entry_point="subbrute_app",
     cdefines=["APP_SUB_BRUTE"],
     requires=["gui","dialogs"],
     stack_size=2 * 1024,

+ 370 - 0
applications/plugins/subbrute/helpers/subbrute_worker.c

@@ -0,0 +1,370 @@
+#include "subbrute_worker.h"
+
+#include <subghz/environment.h>
+#include <subghz/transmitter.h>
+#include <flipper_format_i.h>
+
+#define TAG "SubBruteWorker"
+
+struct SubBruteWorker {
+    FuriThread* thread;
+    volatile bool worker_running;
+    volatile bool worker_manual_mode;
+    bool is_manual_init;
+
+    SubGhzEnvironment* environment;
+    SubGhzTransmitter* transmitter;
+    FlipperFormat* flipper_format;
+
+    uint32_t last_time_tx_data;
+
+    // Preset and frequency needed
+    FuriHalSubGhzPreset preset;
+    uint32_t frequency;
+    string_t protocol_name;
+
+    //SubBruteWorkerCallback callback;
+    //void* context;
+};
+
+/** Taken from subghz_tx_rx_worker.c */
+#define SUBBRUTE_TXRX_WORKER_BUF_SIZE 2048
+#define SUBBRUTE_TXRX_WORKER_MAX_TXRX_SIZE 60
+#define SUBBRUTE_TXRX_WORKER_TIMEOUT_READ_WRITE_BUF 40
+#define SUBBRUTE_TX_TIMEOUT 50
+#define SUBBRUTE_SEND_DELAY 260
+
+/**
+ * Entrypoint for worker
+ *
+ * @param context SubBruteWorker*
+ * @return 0 if ok
+ */
+int32_t subbrute_worker_thread(void* context) {
+    furi_assert(context);
+    SubBruteWorker* instance = (SubBruteWorker*)context;
+
+    if(!instance->worker_running) {
+        FURI_LOG_W(TAG, "Worker is not set to running state!");
+        return -1;
+    }
+#ifdef FURI_DEBUG
+    FURI_LOG_I(TAG, "Worker start");
+#endif
+
+    instance->environment = subghz_environment_alloc();
+    instance->transmitter = subghz_transmitter_alloc_init(
+        instance->environment, string_get_cstr(instance->protocol_name));
+
+    furi_hal_subghz_reset();
+    furi_hal_subghz_load_preset(instance->preset);
+    instance->frequency = furi_hal_subghz_set_frequency_and_path(instance->frequency);
+
+    furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow);
+    furi_hal_gpio_write(&gpio_cc1101_g0, true);
+
+    // Set ready to transmit value
+    instance->last_time_tx_data = furi_get_tick() - SUBBRUTE_SEND_DELAY;
+
+    while(instance->worker_running) {
+        // Transmit
+        if(!furi_hal_subghz_tx()) {
+            FURI_LOG_E(TAG, "Cannot transmit!");
+            break;
+        }
+        furi_delay_ms(SUBBRUTE_TX_TIMEOUT);
+    }
+
+    furi_hal_subghz_set_path(FuriHalSubGhzPathIsolate);
+    furi_hal_subghz_sleep();
+
+    subghz_transmitter_free(instance->transmitter);
+    instance->transmitter = NULL;
+    subghz_environment_free(instance->environment);
+    instance->environment = NULL;
+
+#ifdef FURI_DEBUG
+    FURI_LOG_I(TAG, "Worker stop");
+#endif
+    return 0;
+}
+
+SubBruteWorker* subbrute_worker_alloc() {
+    SubBruteWorker* instance = malloc(sizeof(SubBruteWorker));
+
+    instance->thread = furi_thread_alloc();
+    furi_thread_set_name(instance->thread, "SubBruteAttackWorker");
+    furi_thread_set_stack_size(instance->thread, 2048);
+    furi_thread_set_context(instance->thread, instance);
+    furi_thread_set_callback(instance->thread, subbrute_worker_thread);
+
+    //instance->status = SubBruteWorkerStatusIDLE;
+    instance->worker_running = false;
+    instance->worker_manual_mode = false;
+
+    instance->flipper_format = flipper_format_string_alloc();
+    string_init(instance->protocol_name);
+
+    return instance;
+}
+
+void subbrute_worker_free(SubBruteWorker* instance) {
+    furi_assert(instance);
+    furi_assert(!instance->worker_running);
+
+    if(instance->transmitter != NULL) {
+        subghz_transmitter_free(instance->transmitter);
+        instance->transmitter = NULL;
+    }
+
+    if(instance->environment != NULL) {
+        subghz_environment_free(instance->environment);
+        instance->environment = NULL;
+    }
+
+    furi_thread_free(instance->thread);
+    flipper_format_free(instance->flipper_format);
+
+    string_clear(instance->protocol_name);
+
+    free(instance);
+}
+
+bool subbrute_worker_start(
+    SubBruteWorker* instance,
+    uint32_t frequency,
+    FuriHalSubGhzPreset preset,
+    const char* protocol_name) {
+    furi_assert(instance);
+
+    if(instance->worker_manual_mode) {
+        return false;
+    }
+
+    instance->frequency = frequency;
+    instance->preset = preset;
+
+    string_clear(instance->protocol_name);
+    string_init_printf(instance->protocol_name, "%s", protocol_name);
+
+    bool res = false;
+
+    furi_hal_subghz_reset();
+    furi_hal_subghz_idle();
+    furi_hal_subghz_load_preset(instance->preset);
+
+    furi_hal_subghz_set_frequency_and_path(instance->frequency);
+    furi_hal_subghz_flush_rx();
+
+    if(furi_hal_subghz_is_tx_allowed(frequency)) {
+        instance->frequency = frequency;
+        res = true;
+    }
+    instance->worker_running = res;
+
+#ifdef FURI_DEBUG
+    FURI_LOG_I(TAG, "Frequency: %d", frequency);
+#endif
+    instance->preset = preset;
+
+    furi_thread_start(instance->thread);
+
+    return res;
+}
+
+void subbrute_worker_stop(SubBruteWorker* instance) {
+    furi_assert(instance);
+
+    instance->worker_running = false;
+
+    furi_thread_join(instance->thread);
+
+    furi_hal_subghz_set_path(FuriHalSubGhzPathIsolate);
+    furi_hal_subghz_sleep();
+}
+
+bool subbrute_worker_is_running(SubBruteWorker* instance) {
+    furi_assert(instance);
+
+    return instance->worker_running;
+}
+
+bool subbrute_worker_can_transmit(SubBruteWorker* instance) {
+    furi_assert(instance);
+
+    return (furi_get_tick() - instance->last_time_tx_data) > SUBBRUTE_SEND_DELAY;
+}
+
+bool subbrute_worker_transmit(SubBruteWorker* instance, const char* payload) {
+    furi_assert(instance);
+    furi_assert(instance->worker_running);
+
+    if(!subbrute_worker_can_transmit(instance)) {
+        FURI_LOG_E(TAG, "Too early to transmit");
+
+        return false;
+    }
+    instance->last_time_tx_data = furi_get_tick();
+
+#ifdef FURI_DEBUG
+    //FURI_LOG_D(TAG, "payload: %s", payload);
+#endif
+
+    Stream* stream = flipper_format_get_raw_stream(instance->flipper_format);
+    stream_clean(stream);
+    stream_write_cstring(stream, payload);
+    subghz_transmitter_deserialize(instance->transmitter, instance->flipper_format);
+
+    return true;
+}
+
+bool subbrute_worker_init_manual_transmit(
+    SubBruteWorker* instance,
+    uint32_t frequency,
+    FuriHalSubGhzPreset preset,
+    const char* protocol_name) {
+#ifdef FURI_DEBUG
+    FURI_LOG_D(
+        TAG,
+        "subbrute_worker_init_manual_transmit. frequency: %d, protocol: %s",
+        frequency,
+        protocol_name);
+#endif
+    if(instance->worker_manual_mode || !subbrute_worker_can_transmit(instance)) {
+#ifdef FURI_DEBUG
+        FURI_LOG_D(TAG, "cannot transmit");
+#endif
+        return false;
+    }
+    if(instance->worker_running) {
+#ifdef FURI_DEBUG
+        FURI_LOG_D(TAG, "subbrute_worker_stop");
+#endif
+        subbrute_worker_stop(instance);
+    }
+
+    // Not transmit at this period
+    instance->worker_manual_mode = true;
+
+    if(instance->is_manual_init) {
+        FURI_LOG_E(TAG, "Trying to setup without normally shutdown prev transmit session!");
+        subbrute_worker_manual_transmit_stop(instance);
+    }
+
+    instance->preset = preset;
+    instance->frequency = frequency;
+
+    string_clear(instance->protocol_name);
+    string_init_printf(instance->protocol_name, "%s", protocol_name);
+
+    furi_hal_subghz_reset();
+    furi_hal_subghz_idle();
+    furi_hal_subghz_load_preset(instance->preset);
+
+    furi_hal_subghz_set_frequency_and_path(instance->frequency);
+    furi_hal_subghz_flush_rx();
+
+    if(!furi_hal_subghz_is_tx_allowed(frequency)) {
+        FURI_LOG_E(TAG, "Frequency: %d invalid!", frequency);
+
+        instance->frequency = frequency;
+        instance->worker_manual_mode = false;
+        return false;
+    }
+
+#ifdef FURI_DEBUG
+    FURI_LOG_I(TAG, "Frequency: %d", frequency);
+#endif
+
+    instance->environment = subghz_environment_alloc();
+    instance->transmitter = subghz_transmitter_alloc_init(
+        instance->environment, string_get_cstr(instance->protocol_name));
+
+    furi_hal_subghz_reset();
+    furi_hal_subghz_load_preset(instance->preset);
+    instance->frequency = furi_hal_subghz_set_frequency_and_path(frequency);
+
+    furi_hal_subghz_set_path(FuriHalSubGhzPathIsolate);
+    furi_hal_subghz_sleep();
+    subghz_transmitter_free(instance->transmitter);
+    instance->transmitter = NULL;
+
+    instance->worker_manual_mode = false;
+    instance->is_manual_init = true;
+
+    return true;
+}
+
+void subbrute_worker_manual_transmit_stop(SubBruteWorker* instance) {
+#ifdef FURI_DEBUG
+    FURI_LOG_D(TAG, "subbrute_worker_manual_transmit_stop");
+#endif
+    if(!instance->is_manual_init) {
+        return;
+    }
+
+    furi_hal_subghz_idle();
+    furi_hal_subghz_sleep();
+
+    if(instance->transmitter != NULL) {
+        subghz_transmitter_free(instance->transmitter);
+        instance->transmitter = NULL;
+    }
+    subghz_environment_free(instance->environment);
+    instance->environment = NULL;
+
+    instance->is_manual_init = false;
+}
+
+bool subbrute_worker_manual_transmit(SubBruteWorker* instance, const char* payload) {
+    furi_assert(instance);
+
+    if(instance->worker_manual_mode || !subbrute_worker_can_transmit(instance)) {
+#ifdef FURI_DEBUG
+        FURI_LOG_D(TAG, "cannot transmit");
+#endif
+        return false;
+    }
+    if(instance->worker_running) {
+#ifdef FURI_DEBUG
+        FURI_LOG_D(TAG, "subbrute_worker_stop");
+#endif
+        subbrute_worker_stop(instance);
+    }
+    if(!instance->is_manual_init) {
+        FURI_LOG_E(TAG, "Manually transmit doesn't set!");
+        return false;
+    }
+
+    instance->last_time_tx_data = furi_get_tick();
+    instance->worker_manual_mode = true;
+
+    Stream* stream = flipper_format_get_raw_stream(instance->flipper_format);
+    stream_clean(stream);
+    stream_write_cstring(stream, payload);
+
+    instance->transmitter = subghz_transmitter_alloc_init(
+        instance->environment, string_get_cstr(instance->protocol_name));
+    subghz_transmitter_deserialize(instance->transmitter, instance->flipper_format);
+    furi_hal_subghz_reset();
+    furi_hal_subghz_load_preset(instance->preset);
+    instance->frequency = furi_hal_subghz_set_frequency_and_path(instance->frequency);
+
+    furi_hal_subghz_start_async_tx(subghz_transmitter_yield, instance->transmitter);
+
+    while(!furi_hal_subghz_is_async_tx_complete()) {
+        furi_delay_ms(SUBBRUTE_TX_TIMEOUT);
+    }
+    furi_hal_subghz_stop_async_tx();
+
+    furi_hal_subghz_set_path(FuriHalSubGhzPathIsolate);
+    furi_hal_subghz_sleep();
+    subghz_transmitter_free(instance->transmitter);
+    instance->transmitter = NULL;
+
+    stream_clean(stream);
+
+    instance->worker_manual_mode = false;
+
+    return true;
+}

+ 35 - 0
applications/plugins/subbrute/helpers/subbrute_worker.h

@@ -0,0 +1,35 @@
+#pragma once
+
+#include <furi_hal_subghz.h>
+
+typedef struct SubBruteWorker SubBruteWorker;
+/**
+ * Same like SubGhzTxRxWorkerStatus in subghz_tx_rx_worker.h
+ * using just to not include that file
+
+typedef enum {
+    SubBruteWorkerStatusIDLE,
+    SubBruteWorkerStatusTx,
+    // SubBruteWorkerStatusRx,
+} SubBruteWorkerStatus;
+
+//typedef void (*SubBruteWorkerCallback)(SubBruteWorkerStatus event, void* context);
+*/
+SubBruteWorker* subbrute_worker_alloc();
+void subbrute_worker_free(SubBruteWorker* instance);
+bool subbrute_worker_start(
+    SubBruteWorker* instance,
+    uint32_t frequency,
+    FuriHalSubGhzPreset preset,
+    const char* protocol_name);
+void subbrute_worker_stop(SubBruteWorker* instance);
+//bool subbrute_worker_write(SubBruteWorker* instance, uint8_t* data, size_t size);
+bool subbrute_worker_is_running(SubBruteWorker* instance);
+bool subbrute_worker_can_transmit(SubBruteWorker* instance);
+bool subbrute_worker_transmit(SubBruteWorker* instance, const char* payload);
+bool subbrute_worker_init_manual_transmit(SubBruteWorker* instance,
+                                          uint32_t frequency,
+                                          FuriHalSubGhzPreset preset,
+                                          const char* protocol_name);
+bool subbrute_worker_manual_transmit(SubBruteWorker* instance, const char* payload);
+void subbrute_worker_manual_transmit_stop(SubBruteWorker* instance);

+ 0 - 197
applications/plugins/subbrute/scene/subbrute_scene_entrypoint.c

@@ -1,197 +0,0 @@
-#include "subbrute_scene_entrypoint.h"
-#include "../subbrute_utils.h"
-
-string_t subbrute_menu_items[10];
-
-void subbrute_scene_entrypoint_menu_callback(SubBruteState* context, uint32_t index) {
-    string_set_str(context->preset, "FuriHalSubGhzPresetOok650Async");
-    string_set_str(context->protocol, "RAW");
-    context->repeat = 5;
-    context->te = 0;
-    context->attack = index;
-    switch(index) {
-    case SubBruteAttackLoadFile:
-        context->current_scene = SceneSelectFile;
-        break;
-    case SubBruteAttackCAME12bit307:
-    case SubBruteAttackCAME12bit433:
-    case SubBruteAttackCAME12bit868:
-        if(index == SubBruteAttackCAME12bit307) {
-            context->frequency = 307800000;
-        } else if(index == SubBruteAttackCAME12bit433) {
-            context->frequency = 433920000;
-        } else if(index == SubBruteAttackCAME12bit868) {
-            context->frequency = 868350000;
-        }
-        context->bit = 12;
-        string_set_str(context->protocol, "CAME");
-        string_set_str(context->preset, "FuriHalSubGhzPresetOok650Async");
-        if(!subbrute_is_frequency_allowed(context)) {
-            return;
-        }
-        context->current_scene = SceneAttack;
-        break;
-    case SubBruteAttackChamberlain9bit315:
-        context->frequency = 315000000;
-        context->bit = 9;
-        string_set_str(context->protocol, "Cham_Code");
-        string_set_str(context->preset, "FuriHalSubGhzPresetOok650Async");
-
-        if(!subbrute_is_frequency_allowed(context)) {
-            return;
-        }
-        context->current_scene = SceneAttack;
-        break;
-    case SubBruteAttackChamberlain9bit390:
-        context->frequency = 390000000;
-        context->bit = 9;
-        string_set_str(context->protocol, "Cham_Code");
-        string_set_str(context->preset, "FuriHalSubGhzPresetOok650Async");
-
-        if(!subbrute_is_frequency_allowed(context)) {
-            return;
-        }
-        context->current_scene = SceneAttack;
-        break;
-    case SubBruteAttackLinear10bit300:
-        context->frequency = 300000000;
-        context->bit = 10;
-        string_set_str(context->protocol, "Linear");
-        string_set_str(context->preset, "FuriHalSubGhzPresetOok650Async");
-        if(!subbrute_is_frequency_allowed(context)) {
-            return;
-        }
-        context->current_scene = SceneAttack;
-        break;
-    case SubBruteAttackLinear10bit310:
-        context->frequency = 310000000;
-        context->bit = 10;
-        string_set_str(context->protocol, "Linear");
-        string_set_str(context->preset, "FuriHalSubGhzPresetOok650Async");
-        if(!subbrute_is_frequency_allowed(context)) {
-            return;
-        }
-        context->current_scene = SceneAttack;
-        break;
-    case SubBruteAttackNICE12bit433:
-        context->frequency = 433920000;
-        context->bit = 12;
-        string_set_str(context->protocol, "Nice FLO");
-        string_set_str(context->preset, "FuriHalSubGhzPresetOok650Async");
-        if(!subbrute_is_frequency_allowed(context)) {
-            return;
-        }
-        context->current_scene = SceneAttack;
-        break;
-    case SubBruteAttackNICE12bit868:
-        context->frequency = 868350000;
-        context->bit = 12;
-        string_set_str(context->protocol, "Nice FLO");
-        string_set_str(context->preset, "FuriHalSubGhzPresetOok650Async");
-        if(!subbrute_is_frequency_allowed(context)) {
-            return;
-        }
-        context->current_scene = SceneAttack;
-        break;
-    default:
-        break;
-    }
-}
-
-void subbrute_scene_entrypoint_on_enter(SubBruteState* context) {
-    // Clear the previous payload
-    context->menu_index = 0;
-    for(uint32_t i = 0; i < 10; i++) {
-        string_init(subbrute_menu_items[i]);
-    }
-
-    string_set(subbrute_menu_items[0], "BF existing dump");
-    string_set(subbrute_menu_items[1], "CAME 12bit 307mhz");
-    string_set(subbrute_menu_items[2], "CAME 12bit 433mhz");
-    string_set(subbrute_menu_items[3], "CAME 12bit 868mhz");
-    string_set(subbrute_menu_items[4], "Chamberlain 9bit 315mhz");
-    string_set(subbrute_menu_items[5], "Chamberlain 9bit 390mhz");
-    string_set(subbrute_menu_items[6], "Linear 10bit 300mhz");
-    string_set(subbrute_menu_items[7], "Linear 10bit 310mhz");
-    string_set(subbrute_menu_items[8], "NICE 12bit 433mhz");
-    string_set(subbrute_menu_items[9], "NICE 12bit 868mhz");
-}
-
-void subbrute_scene_entrypoint_on_exit(SubBruteState* context) {
-    UNUSED(context);
-    for(uint32_t i = 0; i < 10; i++) {
-        string_clear(subbrute_menu_items[i]);
-    }
-}
-
-void subbrute_scene_entrypoint_on_tick(SubBruteState* context) {
-    UNUSED(context);
-}
-
-void subbrute_scene_entrypoint_on_event(SubBruteEvent event, SubBruteState* context) {
-    if(event.evt_type == EventTypeKey) {
-        if(event.input_type == InputTypeShort) {
-            switch(event.key) {
-            case InputKeyDown:
-                if(context->menu_index < SubBruteAttackNICE12bit868) {
-                    context->menu_index++;
-                }
-                break;
-            case InputKeyUp:
-                if(context->menu_index > SubBruteAttackLoadFile) {
-                    context->menu_index--;
-                }
-                break;
-            case InputKeyLeft:
-            case InputKeyRight:
-                break;
-            case InputKeyOk:
-                subbrute_scene_entrypoint_menu_callback(context, context->menu_index);
-                break;
-            case InputKeyBack:
-                context->is_running = false;
-                break;
-            }
-        }
-    }
-}
-
-void subbrute_scene_entrypoint_on_draw(Canvas* canvas, SubBruteState* context) {
-    canvas_clear(canvas);
-    canvas_set_color(canvas, ColorBlack);
-
-    // Title
-    canvas_set_font(canvas, FontPrimary);
-    canvas_draw_str_aligned(canvas, 64, 6, AlignCenter, AlignTop, "Sub-GHz Bruteforcer");
-
-    if(context->menu_index > SubBruteAttackLoadFile) {
-        canvas_set_font(canvas, FontSecondary);
-        canvas_draw_str_aligned(
-            canvas,
-            64,
-            24,
-            AlignCenter,
-            AlignTop,
-            string_get_cstr(subbrute_menu_items[context->menu_index - 1]));
-    }
-
-    canvas_set_font(canvas, FontPrimary);
-    canvas_draw_str_aligned(
-        canvas,
-        64,
-        36,
-        AlignCenter,
-        AlignTop,
-        string_get_cstr(subbrute_menu_items[context->menu_index]));
-
-    if(context->menu_index < SubBruteAttackNICE12bit868) {
-        canvas_set_font(canvas, FontSecondary);
-        canvas_draw_str_aligned(
-            canvas,
-            64,
-            48,
-            AlignCenter,
-            AlignTop,
-            string_get_cstr(subbrute_menu_items[context->menu_index + 1]));
-    }
-}

+ 0 - 8
applications/plugins/subbrute/scene/subbrute_scene_entrypoint.h

@@ -1,8 +0,0 @@
-#pragma once
-#include "../subbrute.h"
-
-void subbrute_scene_entrypoint_on_enter(SubBruteState* context);
-void subbrute_scene_entrypoint_on_exit(SubBruteState* context);
-void subbrute_scene_entrypoint_on_tick(SubBruteState* context);
-void subbrute_scene_entrypoint_on_event(SubBruteEvent event, SubBruteState* context);
-void subbrute_scene_entrypoint_on_draw(Canvas* canvas, SubBruteState* context);

+ 0 - 222
applications/plugins/subbrute/scene/subbrute_scene_load_file.c

@@ -1,222 +0,0 @@
-#include "subbrute_scene_load_file.h"
-#include "subbrute_scene_entrypoint.h"
-#include "../subbrute_utils.h"
-#include <lib/subghz/protocols/registry.h>
-
-#define SUBGHZ_APP_PATH_FOLDER "/ext/subghz"
-
-bool subbrute_load(SubBruteState* context, const char* file_path) {
-    bool result = false;
-
-    Storage* storage = furi_record_open(RECORD_STORAGE);
-    FlipperFormat* fff_data_file = flipper_format_file_alloc(storage);
-
-    string_t temp_str;
-    string_init(temp_str);
-    uint32_t temp_data32;
-
-    do {
-        if(!flipper_format_file_open_existing(fff_data_file, file_path)) {
-            FURI_LOG_E(TAG, "Error open file %s", file_path);
-            string_reset(context->notification_msg);
-            string_set_str(context->notification_msg, "Error open file");
-            break;
-        }
-        if(!flipper_format_read_header(fff_data_file, temp_str, &temp_data32)) {
-            FURI_LOG_E(TAG, "Missing or incorrect header");
-            string_reset(context->notification_msg);
-            string_set_str(context->notification_msg, "Missing or incorrect header");
-            break;
-        }
-        // Frequency
-        if(flipper_format_read_uint32(fff_data_file, "Frequency", &temp_data32, 1)) {
-            FURI_LOG_I(TAG, "Frequency: %d", temp_data32);
-            context->frequency = temp_data32;
-            if(!subbrute_is_frequency_allowed(context)) {
-                break;
-            }
-        } else {
-            FURI_LOG_E(TAG, "Missing or incorrect Frequency");
-            string_reset(context->notification_msg);
-            string_set_str(context->notification_msg, "Missing or incorrect Frequency");
-            break;
-        }
-        // Preset
-        if(!flipper_format_read_string(fff_data_file, "Preset", context->preset)) {
-            FURI_LOG_E(TAG, "Preset FAIL");
-            string_reset(context->notification_msg);
-            string_set_str(context->notification_msg, "Preset FAIL");
-        }
-        // Protocol
-        if(!flipper_format_read_string(fff_data_file, "Protocol", context->protocol)) {
-            FURI_LOG_E(TAG, "Missing Protocol");
-            string_reset(context->notification_msg);
-            string_set_str(context->notification_msg, "Missing Protocol");
-            break;
-        } else {
-            FURI_LOG_I(TAG, "Protocol: %s", string_get_cstr(context->protocol));
-        }
-
-        if(strcmp(string_get_cstr(context->protocol), "RAW") == 0) {
-            FURI_LOG_E(TAG, "RAW unsupported");
-            string_reset(context->notification_msg);
-            string_set_str(context->notification_msg, "RAW unsupported");
-            break;
-        }
-
-        const SubGhzProtocol* registry =
-            subghz_protocol_registry_get_by_name(string_get_cstr(context->protocol));
-
-        if(registry && registry->type == SubGhzProtocolTypeDynamic) {
-            FURI_LOG_D(TAG, "Protocol is dynamic - not supported");
-            string_reset(context->notification_msg);
-            string_set_str(context->notification_msg, "Dynamic protocol unsupported");
-            break;
-        }
-
-        context->decoder_result = subghz_receiver_search_decoder_base_by_name(
-            context->receiver, string_get_cstr(context->protocol));
-
-        if(context->decoder_result) {
-            FURI_LOG_I(TAG, "Found decoder");
-        } else {
-            FURI_LOG_E(TAG, "Protocol not found");
-            string_reset(context->notification_msg);
-            string_set_str(context->notification_msg, "Protocol not found");
-            break;
-        }
-
-        // Bit
-        if(!flipper_format_read_uint32(fff_data_file, "Bit", &temp_data32, 1)) {
-            FURI_LOG_E(TAG, "Missing or incorrect Bit");
-            string_reset(context->notification_msg);
-            string_set_str(context->notification_msg, "Missing or incorrect Bit");
-            break;
-        } else {
-            FURI_LOG_I(TAG, "Bit: %d", temp_data32);
-            context->bit = temp_data32;
-        }
-
-        // Key
-        if(!flipper_format_read_string(fff_data_file, "Key", temp_str)) {
-            FURI_LOG_E(TAG, "Missing or incorrect Key");
-            string_reset(context->notification_msg);
-            string_set_str(context->notification_msg, "Missing or incorrect Key");
-            break;
-        } else {
-            FURI_LOG_I(TAG, "Key: %s", string_get_cstr(temp_str));
-            string_set(context->key, string_get_cstr(temp_str));
-        }
-
-        // TE
-        if(!flipper_format_read_uint32(fff_data_file, "TE", &temp_data32, 1)) {
-            FURI_LOG_E(TAG, "Missing or incorrect TE");
-            //string_reset(context->notification_msg);
-            //string_set_str(context->notification_msg, "Missing or incorrect TE");
-            //break;
-        } else {
-            FURI_LOG_I(TAG, "TE: %d", temp_data32);
-            context->te = temp_data32;
-        }
-
-        // Repeat
-        if(flipper_format_read_uint32(fff_data_file, "Repeat", &temp_data32, 1)) {
-            FURI_LOG_I(TAG, "Repeat: %d", temp_data32);
-            context->repeat = temp_data32;
-        } else {
-            FURI_LOG_I(TAG, "Repeat: 3 (default)");
-            context->repeat = 3;
-        }
-
-        result = true;
-    } while(0);
-
-    string_clear(temp_str);
-    flipper_format_file_close(fff_data_file);
-    flipper_format_free(fff_data_file);
-    furi_record_close(RECORD_STORAGE);
-    if(result) {
-        FURI_LOG_I(TAG, "Loaded successfully");
-        string_reset(context->notification_msg);
-        string_set_str(context->notification_msg, "File looks ok.");
-    }
-
-    return result;
-}
-
-void subbrute_scene_load_file_on_enter(SubBruteState* context) {
-    if(subbrute_load_protocol_from_file(context)) {
-        context->current_scene = SceneSelectField;
-    } else {
-        subbrute_scene_entrypoint_on_enter(context);
-        context->current_scene = SceneEntryPoint;
-    }
-}
-
-void subbrute_scene_load_file_on_exit(SubBruteState* context) {
-    UNUSED(context);
-}
-
-void subbrute_scene_load_file_on_tick(SubBruteState* context) {
-    UNUSED(context);
-}
-
-void subbrute_scene_load_file_on_event(SubBruteEvent event, SubBruteState* context) {
-    UNUSED(context);
-    if(event.evt_type == EventTypeKey) {
-        if(event.input_type == InputTypeShort) {
-            switch(event.key) {
-            case InputKeyDown:
-            case InputKeyUp:
-            case InputKeyLeft:
-            case InputKeyRight:
-            case InputKeyOk:
-                break;
-            case InputKeyBack:
-                context->current_scene = SceneEntryPoint;
-                break;
-            }
-        }
-    }
-}
-
-void subbrute_scene_load_file_on_draw(Canvas* canvas, SubBruteState* context) {
-    UNUSED(context);
-    canvas_clear(canvas);
-    canvas_set_color(canvas, ColorBlack);
-
-    // Frame
-    //canvas_draw_frame(canvas, 0, 0, 128, 64);
-
-    // Title
-    canvas_set_font(canvas, FontPrimary);
-    canvas_draw_str_aligned(canvas, 64, 16, AlignCenter, AlignTop, "SubGHz Fuzzer");
-    canvas_draw_str_aligned(canvas, 64, 32, AlignCenter, AlignTop, "Error: Press back");
-}
-
-bool subbrute_load_protocol_from_file(SubBruteState* context) {
-    string_t file_path;
-    string_init(file_path);
-    string_set_str(file_path, SUBGHZ_APP_PATH_FOLDER);
-    context->environment = subghz_environment_alloc();
-    context->receiver = subghz_receiver_alloc_init(context->environment);
-    subghz_receiver_set_filter(context->receiver, SubGhzProtocolFlag_Decodable);
-
-    // Input events and views are managed by file_select
-
-    DialogsFileBrowserOptions browser_options;
-    dialog_file_browser_set_basic_options(&browser_options, SUBGHZ_APP_EXTENSION, &I_sub1_10px);
-
-    bool res = dialog_file_browser_show(context->dialogs, file_path, file_path, &browser_options);
-
-    if(res) {
-        res = subbrute_load(context, string_get_cstr(file_path));
-    }
-
-    subghz_environment_free(context->environment);
-    subghz_receiver_free(context->receiver);
-
-    string_clear(file_path);
-
-    return res;
-}

+ 0 - 8
applications/plugins/subbrute/scene/subbrute_scene_load_file.h

@@ -1,8 +0,0 @@
-#include "../subbrute.h"
-
-void subbrute_scene_load_file_on_enter(SubBruteState* context);
-void subbrute_scene_load_file_on_exit(SubBruteState* context);
-void subbrute_scene_load_file_on_tick(SubBruteState* context);
-void subbrute_scene_load_file_on_event(SubBruteEvent event, SubBruteState* context);
-void subbrute_scene_load_file_on_draw(Canvas* canvas, SubBruteState* context);
-bool subbrute_load_protocol_from_file(SubBruteState* context);

+ 0 - 384
applications/plugins/subbrute/scene/subbrute_scene_run_attack.c

@@ -1,384 +0,0 @@
-#include "subbrute_scene_run_attack.h"
-#include <lib/subghz/transmitter.h>
-#include <gui/elements.h>
-
-//uint64_t subbrute_counter = 0;
-uint64_t max_value;
-bool locked = false;
-bool toSave = false;
-char subbrute_payload_byte[4];
-#define SUBBRUTE_DELAY 1
-
-FuriHalSubGhzPreset str_to_preset(string_t preset) {
-    if(string_cmp_str(preset, "FuriHalSubGhzPresetOok270Async") == 0) {
-        return FuriHalSubGhzPresetOok270Async;
-    }
-    if(string_cmp_str(preset, "FuriHalSubGhzPresetOok650Async") == 0) {
-        return FuriHalSubGhzPresetOok650Async;
-    }
-    if(string_cmp_str(preset, "FuriHalSubGhzPreset2FSKDev238Async") == 0) {
-        return FuriHalSubGhzPreset2FSKDev238Async;
-    }
-    if(string_cmp_str(preset, "FuriHalSubGhzPreset2FSKDev476Async") == 0) {
-        return FuriHalSubGhzPreset2FSKDev476Async;
-    }
-    if(string_cmp_str(preset, "FuriHalSubGhzPresetMSK99_97KbAsync") == 0) {
-        return FuriHalSubGhzPresetMSK99_97KbAsync;
-    }
-    if(string_cmp_str(preset, "FuriHalSubGhzPresetMSK99_97KbAsync") == 0) {
-        return FuriHalSubGhzPresetMSK99_97KbAsync;
-    }
-    return FuriHalSubGhzPresetCustom;
-}
-
-void subbrute_emit(SubBruteState* context) {
-    //FURI_LOG_D(TAG, string_get_cstr(context->flipper_format_string));
-
-    context->transmitter =
-        subghz_transmitter_alloc_init(context->environment, string_get_cstr(context->protocol));
-
-    subghz_transmitter_deserialize(context->transmitter, context->flipper_format);
-    furi_hal_subghz_reset();
-    furi_hal_subghz_load_preset(str_to_preset(context->preset));
-
-    context->frequency_cal = furi_hal_subghz_set_frequency_and_path(context->frequency);
-
-    furi_hal_subghz_start_async_tx(subghz_transmitter_yield, context->transmitter);
-    while(!(furi_hal_subghz_is_async_tx_complete())) {
-        furi_delay_ms(1);
-    }
-
-    furi_hal_subghz_stop_async_tx();
-    subghz_transmitter_stop(context->transmitter);
-    furi_hal_subghz_idle();
-    subghz_transmitter_free(context->transmitter);
-}
-
-void prepare_emit(SubBruteState* context) {
-    UNUSED(context);
-
-    furi_hal_subghz_init();
-}
-
-void clear_emit(SubBruteState* context) {
-    UNUSED(context);
-
-    //furi_hal_subghz_stop_async_tx();
-    //furi_hal_subghz_idle();
-    furi_hal_subghz_sleep();
-}
-/*
-void subbrute_send_raw_packet(SubBruteState* context) {
-    string_reset(context->candidate);
-
-    // Payload to padded binary string
-    int* binaryNum = (int*)malloc(sizeof(int) * context->bit);
-    uint32_t i = 0;
-    for(i = 0; i < context->bit; i++) {
-        binaryNum[i] = 0;
-    }
-    i = 0;
-    uint64_t counter = context->payload;
-    while(counter > 0) {
-        binaryNum[i] = counter % 2;
-        counter = counter / 2;
-        i++;
-    }
-
-    // printing binary array in reverse order and build raw payload
-    for(uint32_t loop = 0; loop < context->repeat; loop++) {
-        for(int j = (int)context->bit - 1; j >= 0; j--) {
-            if(binaryNum[j] == 1) {
-                string_cat(context->candidate, context->subbrute_raw_one);
-            } else {
-                string_cat(context->candidate, context->subbrute_raw_zero);
-            }
-        }
-        string_cat(context->candidate, context->subbrute_raw_stop);
-    }
-
-    free(binaryNum);
-
-    string_init_printf(
-        context->flipper_format_string,
-        "Filetype: Flipper SubGhz RAW File\n"
-        "Version: 1\n"
-        "Frequency: %d\n"
-        "Preset: %s\n"
-        "Protocol: RAW\n"
-        "RAW_Data: %s",
-        context->frequency,
-        string_get_cstr(context->preset),
-        string_get_cstr(context->candidate));
-
-    subbrute_emit(context);
-}
-*/
-void subbrute_send_packet_parsed(SubBruteState* context) {
-    if(context->attack == SubBruteAttackLoadFile) {
-        snprintf(subbrute_payload_byte, 4, "%02X ", (uint8_t)context->payload);
-        string_replace_at(context->candidate, context->str_index, 3, subbrute_payload_byte);
-    } else {
-        string_t buffer;
-        string_init(buffer);
-        string_init_printf(buffer, "%16X", context->payload);
-        int j = 0;
-        string_set_str(context->candidate, "                       ");
-        for(uint8_t i = 0; i < 16; i++) {
-            if(string_get_char(buffer, i) != ' ') {
-                string_set_char(context->candidate, i + j, string_get_char(buffer, i));
-            } else {
-                string_set_char(context->candidate, i + j, '0');
-            }
-            if(i % 2 != 0) {
-                j++;
-            }
-        }
-        string_clear(buffer);
-    }
-    if(strcmp(string_get_cstr(context->protocol), "Princeton") == 0) {
-        string_init_printf(
-            context->flipper_format_string,
-            "Filetype: Flipper SubGhz Key File\n"
-            "Version: 1\n"
-            "Frequency: %u\n"
-            "Preset: %s\n"
-            "Protocol: %s\n"
-            "Bit: %d\n"
-            "Key: %s\n"
-            "TE: %d\n",
-            context->frequency,
-            string_get_cstr(context->preset),
-            string_get_cstr(context->protocol),
-            context->bit,
-            string_get_cstr(context->candidate),
-            context->te);
-    } else {
-        string_init_printf(
-            context->flipper_format_string,
-            "Filetype: Flipper SubGhz Key File\n"
-            "Version: 1\n"
-            "Frequency: %u\n"
-            "Preset: %s\n"
-            "Protocol: %s\n"
-            "Bit: %d\n"
-            "Key: %s\n",
-            context->frequency,
-            string_get_cstr(context->preset),
-            string_get_cstr(context->protocol),
-            context->bit,
-            string_get_cstr(context->candidate));
-    }
-
-    stream_clean(context->stream);
-    stream_write_string(context->stream, context->flipper_format_string);
-}
-
-void subbrute_send_packet(SubBruteState* context) {
-    ///if(string_cmp_str(context->protocol, "RAW") == 0) {
-    //   subbrute_send_raw_packet(context);
-    //} else {
-    subbrute_send_packet_parsed(context);
-    subbrute_emit(context);
-    //}
-    string_clear(context->flipper_format_string);
-}
-
-void subbrute_scene_run_attack_on_exit(SubBruteState* context) {
-    if(!toSave) {
-        clear_emit(context);
-        furi_thread_free(context->bruthread);
-        flipper_format_free(context->flipper_format);
-        subghz_receiver_free(context->receiver);
-        subghz_environment_free(context->environment);
-    }
-}
-
-void subbrute_scene_run_attack_on_tick(SubBruteState* context) {
-    if(!context->is_attacking || locked) {
-        return;
-    }
-    //if(0 != subbrute_counter) {
-    locked = true;
-    subbrute_send_packet(context);
-
-    if(context->payload == max_value) {
-        //context->payload = 0x00;
-        //subbrute_counter = 0;
-        context->is_attacking = false;
-        notification_message(context->notify, &sequence_blink_stop);
-        notification_message(context->notify, &sequence_single_vibro);
-    } else {
-        context->payload++;
-    }
-    locked = false;
-    //}
-    /*if(subbrute_counter > SUBBRUTE_DELAY) {
-        subbrute_counter = 0;
-    } else {
-        subbrute_counter++;
-    }*/
-}
-void subbrute_run_timer(SubBruteState* context) {
-    while(true) {
-        if(!context->is_attacking) {
-            context->is_thread_running = false;
-            break;
-        }
-        //furi_delay_ms(10);
-        subbrute_scene_run_attack_on_tick(context);
-    }
-}
-
-// entrypoint for worker
-static int32_t subbrute_worker_thread(void* ctx) {
-    SubBruteState* app = ctx;
-    subbrute_run_timer(app);
-    return 0;
-}
-
-void start_bruthread(SubBruteState* app) {
-    if(!app->is_thread_running) {
-        furi_thread_start(app->bruthread);
-        app->is_thread_running = true;
-    }
-}
-
-void subbrute_scene_run_attack_on_enter(SubBruteState* context) {
-    if(!toSave) {
-        if(context->attack == SubBruteAttackLoadFile) {
-            max_value = 0xFF;
-        } else {
-            string_t max_value_s;
-            string_init(max_value_s);
-            for(uint8_t i = 0; i < context->bit; i++) {
-                string_cat_printf(max_value_s, "1");
-            }
-            max_value = (uint64_t)strtol(string_get_cstr(max_value_s), NULL, 2);
-            string_clear(max_value_s);
-        }
-        context->str_index = (context->key_index * 3);
-        string_init_set(context->candidate, context->key);
-        context->flipper_format = flipper_format_string_alloc();
-        context->stream = flipper_format_get_raw_stream(context->flipper_format);
-        context->environment = subghz_environment_alloc();
-        context->receiver = subghz_receiver_alloc_init(context->environment);
-        subghz_receiver_set_filter(context->receiver, SubGhzProtocolFlag_Decodable);
-
-        prepare_emit(context);
-        context->bruthread = furi_thread_alloc();
-        furi_thread_set_name(context->bruthread, "SubBrute Worker");
-        furi_thread_set_stack_size(context->bruthread, 2048);
-        furi_thread_set_context(context->bruthread, context);
-        furi_thread_set_callback(context->bruthread, subbrute_worker_thread);
-    } else {
-        toSave = false;
-    }
-}
-
-void subbrute_scene_run_attack_on_event(SubBruteEvent event, SubBruteState* context) {
-    if(event.evt_type == EventTypeKey) {
-        if(event.input_type == InputTypeShort) {
-            switch(event.key) {
-            case InputKeyDown:
-                break;
-            case InputKeyUp:
-                if(!context->is_attacking) {
-                    subbrute_send_packet_parsed(context);
-                    string_clear(context->flipper_format_string);
-                    toSave = true;
-                    context->current_scene = SceneSaveName;
-                }
-                break;
-            case InputKeyLeft:
-                if(!context->is_attacking && context->payload > 0x00) {
-                    context->payload--;
-                    subbrute_send_packet(context);
-                    notification_message(context->notify, &sequence_blink_blue_10);
-                } else if(!context->is_attacking && context->payload == 0x00) {
-                    context->payload = max_value;
-                    subbrute_send_packet(context);
-                    notification_message(context->notify, &sequence_blink_blue_10);
-                }
-                break;
-            case InputKeyRight:
-                if(!context->is_attacking && context->payload < max_value) {
-                    context->payload++;
-                    subbrute_send_packet(context);
-                    notification_message(context->notify, &sequence_blink_blue_10);
-                } else if(!context->is_attacking && context->payload == max_value) {
-                    context->payload = 0x00;
-                    subbrute_send_packet(context);
-                    notification_message(context->notify, &sequence_blink_blue_10);
-                }
-                break;
-            case InputKeyOk:
-                if(!context->is_attacking) {
-                    if(context->payload == max_value) {
-                        context->payload = 0x00;
-                        //subbrute_counter = 0;
-                    }
-                    context->is_attacking = true;
-                    start_bruthread(context);
-                    notification_message(context->notify, &sequence_blink_start_blue);
-                } else {
-                    context->is_attacking = false;
-                    //context->close_thread_please = true;
-                    if(context->is_thread_running && context->bruthread) {
-                        furi_thread_join(context->bruthread); // wait until thread is finished
-                    }
-                    //context->close_thread_please = false;
-                    notification_message(context->notify, &sequence_blink_stop);
-                    notification_message(context->notify, &sequence_single_vibro);
-                }
-                break;
-            case InputKeyBack:
-                locked = false;
-                //context->close_thread_please = true;
-                context->is_attacking = false;
-                if(context->is_thread_running && context->bruthread) {
-                    furi_thread_join(context->bruthread); // wait until thread is finished
-                }
-                //context->close_thread_please = false;
-                string_reset(context->notification_msg);
-                context->payload = 0x00;
-                //subbrute_counter = 0;
-                notification_message(context->notify, &sequence_blink_stop);
-                if(context->attack == SubBruteAttackLoadFile) {
-                    context->current_scene = SceneSelectField;
-                } else {
-                    context->current_scene = SceneEntryPoint;
-                }
-                break;
-            }
-        }
-    }
-}
-
-void subbrute_scene_run_attack_on_draw(Canvas* canvas, SubBruteState* context) {
-    canvas_clear(canvas);
-    canvas_set_color(canvas, ColorBlack);
-
-    // Frame
-    //canvas_draw_frame(canvas, 0, 0, 128, 64);
-
-    // Title
-    canvas_set_font(canvas, FontPrimary);
-    canvas_draw_str_aligned(canvas, 64, 8, AlignCenter, AlignTop, "Fire in the hole!");
-
-    char msg_index[26];
-    snprintf(
-        msg_index, sizeof(msg_index), "< %04d / %04d >", (int)context->payload, (int)max_value);
-
-    canvas_draw_str_aligned(canvas, 64, 24, AlignCenter, AlignTop, msg_index);
-
-    canvas_set_font(canvas, FontSecondary);
-    char start_stop_msg[20];
-    snprintf(start_stop_msg, sizeof(start_stop_msg), " Press (^) to save ");
-    if(context->is_attacking) {
-        elements_button_center(canvas, "Stop");
-    } else {
-        elements_button_center(canvas, "Start");
-    }
-    canvas_draw_str_aligned(canvas, 64, 39, AlignCenter, AlignTop, start_stop_msg);
-}

+ 0 - 8
applications/plugins/subbrute/scene/subbrute_scene_run_attack.h

@@ -1,8 +0,0 @@
-#include "../subbrute.h"
-
-void subbrute_scene_run_attack_on_enter(SubBruteState* context);
-void subbrute_scene_run_attack_on_exit(SubBruteState* context);
-void subbrute_scene_run_attack_on_tick(SubBruteState* context);
-void subbrute_scene_run_attack_on_event(SubBruteEvent event, SubBruteState* context);
-void subbrute_scene_run_attack_on_draw(Canvas* canvas, SubBruteState* context);
-void send_packet();

+ 0 - 222
applications/plugins/subbrute/scene/subbrute_scene_save_name.c

@@ -1,222 +0,0 @@
-#include "../subbrute.h"
-#include "m-string.h"
-#include "subghz/types.h"
-#include <lib/toolbox/random_name.h>
-#include <gui/modules/validators.h>
-#include <lib/toolbox/path.h>
-
-#define MAX_TEXT_INPUT_LEN 22
-
-bool backpressed = false;
-
-bool subbrute_path_is_file(string_t path) {
-    return string_end_with_str_p(path, ".sub");
-}
-// method modified from subghz_i.c
-// https://github.com/flipperdevices/flipperzero-firmware/blob/b0daa601ad5b87427a45f9089c8b403a01f72c2a/applications/subghz/subghz_i.c#L417-L456
-bool subbrute_save_protocol_to_file(Stream* flipper_format_stream, const char* dev_file_name) {
-    furi_assert(dev_file_name);
-
-    Storage* storage = furi_record_open(RECORD_STORAGE);
-
-    bool saved = false;
-    string_t file_dir;
-    string_init(file_dir);
-
-    path_extract_dirname(dev_file_name, file_dir);
-    do {
-        if(!storage_simply_mkdir(storage, string_get_cstr(file_dir))) {
-            FURI_LOG_E(TAG, "(save) Cannot mkdir");
-            break;
-        }
-
-        if(!storage_simply_remove(storage, dev_file_name)) {
-            FURI_LOG_E(TAG, "(save) Cannot remove");
-            break;
-        }
-
-        stream_seek(flipper_format_stream, 0, StreamOffsetFromStart);
-        stream_save_to_file(flipper_format_stream, storage, dev_file_name, FSOM_CREATE_ALWAYS);
-
-        saved = true;
-        FURI_LOG_D(TAG, "(save) OK Save");
-    } while(0);
-    string_clear(file_dir);
-    furi_record_close(RECORD_STORAGE);
-    return saved;
-}
-
-void custom_callback(SubBruteState* context) {
-    if(strcmp(context->file_name_tmp, "")) {
-        string_cat_printf(context->file_path, "/%s%s", context->file_name_tmp, ".sub");
-        if(subbrute_path_is_file(context->file_path_tmp)) {
-            context->current_scene = SceneAttack;
-            return; //false;
-
-        } else {
-            subbrute_save_protocol_to_file(context->stream, string_get_cstr(context->file_path));
-        }
-
-        string_set_str(context->file_path, EXT_PATH("subghz"));
-        string_reset(context->file_path_tmp);
-
-        //scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveSuccess);
-        context->current_scene = SceneAttack;
-        return; //true;
-    } else {
-        //error no file name
-        context->current_scene = SceneAttack;
-        return; //true;
-    }
-}
-
-void subbrute_scene_save_name_text_input_callback(void* context) {
-    furi_assert(context);
-    SubBruteState* statee = context;
-    custom_callback(statee);
-}
-
-void subbrute_scene_save_name_on_tick(SubBruteState* context) {
-    if(backpressed) {
-        void* validator_context = text_input_get_validator_callback_context(context->text_input);
-        text_input_set_validator(context->text_input, NULL, NULL);
-        validator_is_file_free(validator_context);
-
-        // Clear view
-        text_input_reset(context->text_input);
-
-        // TextInput
-        view_dispatcher_remove_view(context->view_dispatcher, 0);
-        text_input_free(context->text_input);
-
-        // Popup
-        view_dispatcher_remove_view(context->view_dispatcher, 1);
-        popup_free(context->popup);
-
-        context->current_scene = SceneAttack;
-    }
-}
-
-bool subbrute_back_event_callback(void* context) {
-    UNUSED(context);
-    backpressed = true;
-    return true;
-}
-
-void subbrute_scene_save_name_on_enter(SubBruteState* context) {
-    // Text Input
-    context->text_input = text_input_alloc();
-    view_dispatcher_add_view(
-        context->view_dispatcher, 0, text_input_get_view(context->text_input));
-
-    // Popup
-    context->popup = popup_alloc();
-    view_dispatcher_add_view(context->view_dispatcher, 1, popup_get_view(context->popup));
-
-    // Setup view
-    TextInput* text_input = context->text_input;
-    bool dev_name_empty = false;
-
-    string_t file_name;
-    string_t dir_name;
-    string_init(file_name);
-    string_init(dir_name);
-
-    if(!subbrute_path_is_file(context->file_path)) {
-        char file_name_buf[64] = {0};
-        set_random_name(file_name_buf, 64);
-        string_set_str(file_name, file_name_buf);
-        string_set_str(context->file_path, EXT_PATH("subghz"));
-        //highlighting the entire filename by default
-        dev_name_empty = true;
-    } else {
-        string_set(context->file_path_tmp, context->file_path);
-        path_extract_dirname(string_get_cstr(context->file_path), dir_name);
-        path_extract_filename(context->file_path, file_name, true);
-        string_set(context->file_path, dir_name);
-    }
-
-    strncpy(context->file_name_tmp, string_get_cstr(file_name), 64);
-    text_input_set_header_text(text_input, "Name signal");
-    text_input_set_result_callback(
-        text_input,
-        subbrute_scene_save_name_text_input_callback,
-        context,
-        context->file_name_tmp,
-        MAX_TEXT_INPUT_LEN, // buffer size
-        dev_name_empty);
-
-    ValidatorIsFile* validator_is_file =
-        validator_is_file_alloc_init(string_get_cstr(context->file_path), ".sub", "");
-    text_input_set_validator(text_input, validator_is_file_callback, validator_is_file);
-
-    string_clear(file_name);
-    string_clear(dir_name);
-
-    view_dispatcher_set_navigation_event_callback(
-        context->view_dispatcher, subbrute_back_event_callback);
-
-    view_dispatcher_switch_to_view(context->view_dispatcher, 0);
-}
-
-void subbrute_scene_save_name_on_event(SubBruteEvent event, SubBruteState* context) {
-    UNUSED(context);
-    if(event.evt_type == EventTypeKey) {
-        if(event.input_type == InputTypeShort) {
-            switch(event.key) {
-            case InputKeyDown:
-            case InputKeyUp:
-            case InputKeyLeft:
-            case InputKeyRight:
-            case InputKeyOk:
-                break;
-            case InputKeyBack:
-                //context->current_scene = SceneAttack;
-                break;
-            }
-        }
-    }
-}
-
-void subbrute_scene_save_name_on_exit(SubBruteState* context) {
-    if(!backpressed) {
-        // Clear validator
-        void* validator_context = text_input_get_validator_callback_context(context->text_input);
-        text_input_set_validator(context->text_input, NULL, NULL);
-        validator_is_file_free(validator_context);
-
-        // Clear view
-        text_input_reset(context->text_input);
-
-        // Setup view
-        Popup* popup = context->popup;
-        popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59);
-        popup_set_header(popup, "Saved!", 13, 22, AlignLeft, AlignBottom);
-        popup_set_timeout(popup, 1500);
-        popup_set_context(popup, context);
-        popup_set_callback(popup, NULL);
-        popup_enable_timeout(popup);
-        view_dispatcher_switch_to_view(context->view_dispatcher, 1);
-
-        furi_delay_ms(1050);
-        // Clear view
-        //Popup* popup = subghz->popup;
-        popup_set_header(popup, NULL, 0, 0, AlignCenter, AlignBottom);
-        popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignTop);
-        popup_set_icon(popup, 0, 0, NULL);
-        popup_set_callback(popup, NULL);
-        popup_set_context(popup, NULL);
-        popup_set_timeout(popup, 0);
-        popup_disable_timeout(popup);
-
-        // TextInput
-        view_dispatcher_remove_view(context->view_dispatcher, 0);
-        text_input_free(context->text_input);
-
-        // Popup
-        view_dispatcher_remove_view(context->view_dispatcher, 1);
-        popup_free(context->popup);
-    } else {
-        backpressed = false;
-    }
-}

+ 0 - 6
applications/plugins/subbrute/scene/subbrute_scene_save_name.h

@@ -1,6 +0,0 @@
-#include "../subbrute.h"
-
-void subbrute_scene_save_name_on_enter(SubBruteState* context);
-void subbrute_scene_save_name_on_exit(SubBruteState* context);
-void subbrute_scene_save_name_on_event(SubBruteEvent event, SubBruteState* context);
-void subbrute_scene_save_name_on_tick(SubBruteState* context);

+ 0 - 121
applications/plugins/subbrute/scene/subbrute_scene_select_field.c

@@ -1,121 +0,0 @@
-#include "subbrute_scene_select_field.h"
-
-void center_displayed_key(SubBruteState* context, uint8_t index) {
-    const char* key_cstr = string_get_cstr(context->key);
-    uint8_t str_index = (index * 3);
-
-    char display_menu[17] = {
-        'X', 'X', ' ', 'X', 'X', ' ', '<', 'X', 'X', '>', ' ', 'X', 'X', ' ', 'X', 'X', '\0'};
-
-    if(index > 1) {
-        display_menu[0] = key_cstr[str_index - 6];
-        display_menu[1] = key_cstr[str_index - 5];
-    } else {
-        display_menu[0] = ' ';
-        display_menu[1] = ' ';
-    }
-
-    if(index > 0) {
-        display_menu[3] = key_cstr[str_index - 3];
-        display_menu[4] = key_cstr[str_index - 2];
-    } else {
-        display_menu[3] = ' ';
-        display_menu[4] = ' ';
-    }
-
-    display_menu[7] = key_cstr[str_index];
-    display_menu[8] = key_cstr[str_index + 1];
-
-    if((str_index + 4) <= (uint8_t)strlen(key_cstr)) {
-        display_menu[11] = key_cstr[str_index + 3];
-        display_menu[12] = key_cstr[str_index + 4];
-    } else {
-        display_menu[11] = ' ';
-        display_menu[12] = ' ';
-    }
-
-    if((str_index + 8) <= (uint8_t)strlen(key_cstr)) {
-        display_menu[14] = key_cstr[str_index + 6];
-        display_menu[15] = key_cstr[str_index + 7];
-    } else {
-        display_menu[14] = ' ';
-        display_menu[15] = ' ';
-    }
-
-    string_reset(context->notification_msg);
-    string_set_str(context->notification_msg, display_menu);
-}
-
-void subbrute_scene_select_field_on_enter(SubBruteState* context) {
-    string_clear(context->notification_msg);
-}
-
-void subbrute_scene_select_field_on_exit(SubBruteState* context) {
-    UNUSED(context);
-}
-
-void subbrute_scene_select_field_on_tick(SubBruteState* context) {
-    UNUSED(context);
-}
-
-void subbrute_scene_select_field_on_event(SubBruteEvent event, SubBruteState* context) {
-    if(event.evt_type == EventTypeKey) {
-        if(event.input_type == InputTypeShort) {
-            //const char* key_cstr = string_get_cstr(context->key);
-
-            // don't look, it's ugly but I'm a python dev so...
-            /*uint8_t nb_bytes = 0;
-            for(uint8_t i = 0; i < strlen(key_cstr); i++) {
-                if(' ' == key_cstr[i]) {
-                    nb_bytes++;
-                }
-            }*/
-
-            switch(event.key) {
-            case InputKeyDown:
-            case InputKeyUp:
-                break;
-            case InputKeyLeft:
-                if(context->key_index > 0) {
-                    context->key_index--;
-                }
-                break;
-            case InputKeyRight:
-                if(context->key_index < 7) {
-                    context->key_index++;
-                }
-                break;
-            case InputKeyOk:
-                string_reset(context->notification_msg);
-                context->current_scene = SceneAttack;
-                break;
-            case InputKeyBack:
-                string_reset(context->notification_msg);
-                context->current_scene = SceneSelectFile;
-                break;
-            }
-            //FURI_LOG_D(TAG, "Position: %d/%d", context->key_index, nb_bytes);
-        }
-    }
-}
-
-void subbrute_scene_select_field_on_draw(Canvas* canvas, SubBruteState* context) {
-    canvas_clear(canvas);
-    canvas_set_color(canvas, ColorBlack);
-
-    // Frame
-    //canvas_draw_frame(canvas, 0, 0, 128, 64);
-
-    // Title
-    canvas_set_font(canvas, FontPrimary);
-    canvas_draw_str_aligned(canvas, 64, 10, AlignCenter, AlignTop, "use < > to select field");
-
-    char msg_index[18];
-    snprintf(msg_index, sizeof(msg_index), "Field index : %d", context->key_index);
-    canvas_draw_str_aligned(canvas, 64, 26, AlignCenter, AlignTop, msg_index);
-
-    center_displayed_key(context, context->key_index);
-    canvas_set_font(canvas, FontSecondary);
-    canvas_draw_str_aligned(
-        canvas, 64, 40, AlignCenter, AlignTop, string_get_cstr(context->notification_msg));
-}

+ 0 - 8
applications/plugins/subbrute/scene/subbrute_scene_select_field.h

@@ -1,8 +0,0 @@
-#include "../subbrute.h"
-
-void subbrute_scene_select_field_on_enter(SubBruteState* context);
-void subbrute_scene_select_field_on_exit(SubBruteState* context);
-void subbrute_scene_select_field_on_tick(SubBruteState* context);
-void subbrute_scene_select_field_on_event(SubBruteEvent event, SubBruteState* context);
-void subbrute_scene_select_field_on_draw(Canvas* canvas, SubBruteState* context);
-void center_displayed_key(SubBruteState* context, uint8_t index);

+ 29 - 0
applications/plugins/subbrute/scenes/subbrute_scene.h

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

+ 7 - 0
applications/plugins/subbrute/scenes/subbrute_scene_config.h

@@ -0,0 +1,7 @@
+ADD_SCENE(subbrute, load_file, LoadFile)
+ADD_SCENE(subbrute, load_select, LoadSelect)
+ADD_SCENE(subbrute, run_attack, RunAttack)
+ADD_SCENE(subbrute, save_name, SaveName)
+ADD_SCENE(subbrute, save_success, SaveSuccess)
+ADD_SCENE(subbrute, setup_attack, SetupAttack)
+ADD_SCENE(subbrute, start, Start)

+ 77 - 0
applications/plugins/subbrute/scenes/subbrute_scene_load_file.c

@@ -0,0 +1,77 @@
+#include "../subbrute_i.h"
+#include "../subbrute_custom_event.h"
+#include <lib/subghz/protocols/registry.h>
+
+#define TAG "SubBruteSceneLoadFile"
+
+//void subbrute_scene_load_file_callback(SubBruteCustomEvent event, void* context) {
+////    furi_assert(context);
+////
+////    SubBruteState* instance = (SubBruteState*)context;
+////    view_dispatcher_send_custom_event(instance->view_dispatcher, event);
+//}
+
+void subbrute_scene_load_file_on_enter(void* context) {
+    furi_assert(context);
+    SubBruteState* instance = (SubBruteState*)context;
+
+    // Input events and views are managed by file_browser
+    string_t app_folder;
+    string_t load_path;
+    string_init(load_path);
+    string_init_set_str(app_folder, SUBBRUTE_PATH);
+
+    DialogsFileBrowserOptions browser_options;
+    dialog_file_browser_set_basic_options(&browser_options, SUBBRUTE_FILE_EXT, &I_sub1_10px);
+
+    SubBruteFileResult load_result = SubBruteFileResultUnknown;
+    bool res =
+        dialog_file_browser_show(instance->dialogs, load_path, app_folder, &browser_options);
+#ifdef FURI_DEBUG
+    FURI_LOG_D(
+        TAG,
+        "load_path: %s, app_folder: %s",
+        string_get_cstr(load_path),
+        string_get_cstr(app_folder));
+#endif
+    if(res) {
+        load_result = subbrute_device_load_from_file(instance->device, load_path);
+        if(load_result == SubBruteFileResultOk) {
+            load_result = subbrute_device_attack_set(instance->device, SubBruteAttackLoadFile);
+            if(load_result == SubBruteFileResultOk) {
+                // Ready to run!
+                instance->device->state = SubBruteDeviceStateReady;
+                FURI_LOG_I(TAG, "Ready to run");
+                res = true;
+            }
+        }
+    }
+
+    if(load_result == SubBruteFileResultOk) {
+        scene_manager_next_scene(instance->scene_manager, SubBruteSceneLoadSelect);
+    } else {
+        FURI_LOG_E(TAG, "Returned error: %d", load_result);
+
+        string_t dialog_msg;
+        string_init(dialog_msg);
+        string_cat_printf(
+            dialog_msg, "Cannot parse\nfile: %s", subbrute_device_error_get_desc(load_result));
+        dialog_message_show_storage_error(instance->dialogs, string_get_cstr(dialog_msg));
+        string_clear(dialog_msg);
+        scene_manager_search_and_switch_to_previous_scene(
+            instance->scene_manager, SubBruteSceneStart);
+    }
+
+    string_clear(app_folder);
+    string_clear(load_path);
+}
+
+void subbrute_scene_load_file_on_exit(void* context) {
+    UNUSED(context);
+}
+
+bool subbrute_scene_load_file_on_event(void* context, SceneManagerEvent event) {
+    UNUSED(context);
+    UNUSED(event);
+    return false;
+}

+ 61 - 0
applications/plugins/subbrute/scenes/subbrute_scene_load_select.c

@@ -0,0 +1,61 @@
+#include "../subbrute_i.h"
+#include "../subbrute_custom_event.h"
+#include "../views/subbrute_main_view.h"
+
+#define TAG "SubBruteSceneStart"
+
+void subbrute_scene_load_select_callback(SubBruteCustomEvent event, void* context) {
+    furi_assert(context);
+
+    SubBruteState* instance = (SubBruteState*)context;
+#ifdef FURI_DEBUG
+    FURI_LOG_D(TAG, "subbrute_scene_load_select_callback");
+#endif
+    view_dispatcher_send_custom_event(instance->view_dispatcher, event);
+}
+
+void subbrute_scene_load_select_on_enter(void* context) {
+    furi_assert(context);
+#ifdef FURI_DEBUG
+    FURI_LOG_I(TAG, "subbrute_scene_load_select_on_enter");
+#endif
+    SubBruteState* instance = (SubBruteState*)context;
+    SubBruteMainView* view = instance->view_main;
+
+    instance->current_view = SubBruteViewMain;
+    subbrute_main_view_set_callback(view, subbrute_scene_load_select_callback, instance);
+    subbrute_main_view_set_index(view, 7, true, instance->device->file_key);
+
+    view_dispatcher_switch_to_view(instance->view_dispatcher, instance->current_view);
+}
+
+void subbrute_scene_load_select_on_exit(void* context) {
+    UNUSED(context);
+#ifdef FURI_DEBUG
+    FURI_LOG_I(TAG, "subbrute_scene_load_select_on_exit");
+#endif
+}
+
+bool subbrute_scene_load_select_on_event(void* context, SceneManagerEvent event) {
+    SubBruteState* instance = (SubBruteState*)context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == SubBruteCustomEventTypeIndexSelected) {
+            instance->device->load_index = subbrute_main_view_get_index(instance->view_main);
+#ifdef FURI_DEBUG
+            FURI_LOG_D(TAG, "load_index: %d", instance->device->load_index);
+#endif
+            scene_manager_next_scene(instance->scene_manager, SubBruteSceneSetupAttack);
+            consumed = true;
+        }
+    } else if(event.type == SceneManagerEventTypeBack) {
+        if(!scene_manager_search_and_switch_to_previous_scene(
+               instance->scene_manager, SubBruteSceneStart)) {
+            scene_manager_next_scene(instance->scene_manager, SubBruteSceneStart);
+        }
+        consumed = true;
+    }
+
+    return consumed;
+}

+ 83 - 0
applications/plugins/subbrute/scenes/subbrute_scene_run_attack.c

@@ -0,0 +1,83 @@
+#include "../subbrute_i.h"
+#include "../subbrute_custom_event.h"
+#include "../views/subbrute_attack_view.h"
+#include "../helpers/subbrute_worker.h"
+
+static void subbrute_scene_run_attack_callback(SubBruteCustomEvent event, void* context) {
+    furi_assert(context);
+
+    SubBruteState* instance = (SubBruteState*)context;
+    view_dispatcher_send_custom_event(instance->view_dispatcher, event);
+}
+
+void subbrute_scene_run_attack_on_exit(void* context) {
+    furi_assert(context);
+
+    SubBruteState* instance = (SubBruteState*)context;
+    notification_message(instance->notifications, &sequence_blink_stop);
+}
+
+void subbrute_scene_run_attack_on_enter(void* context) {
+    furi_assert(context);
+    SubBruteState* instance = (SubBruteState*)context;
+    SubBruteAttackView* view = instance->view_attack;
+
+    instance->current_view = SubBruteViewAttack;
+    subbrute_attack_view_set_callback(view, subbrute_scene_run_attack_callback, instance);
+    view_dispatcher_switch_to_view(instance->view_dispatcher, instance->current_view);
+
+    subbrute_attack_view_init_values(
+        view,
+        (uint8_t)instance->device->attack,
+        instance->device->max_value,
+        instance->device->key_index,
+        true);
+
+    // Start worker if not started
+    subbrute_worker_init_manual_transmit(
+        instance->worker,
+        instance->device->frequency,
+        instance->device->preset,
+        string_get_cstr(instance->device->protocol_name));
+}
+
+bool subbrute_scene_run_attack_on_event(void* context, SceneManagerEvent event) {
+    SubBruteState* instance = (SubBruteState*)context;
+    SubBruteAttackView* view = instance->view_attack;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == SubBruteCustomEventTypeTransmitNotStarted ||
+           event.event == SubBruteCustomEventTypeTransmitFinished ||
+           event.event == SubBruteCustomEventTypeBackPressed) {
+            // Stop transmit
+            scene_manager_next_scene(instance->scene_manager, SubBruteSceneSetupAttack);
+            consumed = true;
+        }
+    } else if(event.type == SceneManagerEventTypeTick) {
+        if(subbrute_worker_can_transmit(instance->worker)) {
+            // Blink
+            notification_message(instance->notifications, &sequence_blink_yellow_100);
+
+            if(subbrute_worker_manual_transmit(instance->worker, instance->device->payload)) {
+                // Make payload for new iteration or exit
+                if(instance->device->key_index + 1 > instance->device->max_value) {
+                    // End of list
+                    scene_manager_next_scene(instance->scene_manager, SubBruteSceneSetupAttack);
+                } else {
+                    instance->device->key_index++;
+                    subbrute_attack_view_set_current_step(view, instance->device->key_index);
+                    subbrute_device_create_packet_parsed(
+                        instance->device, instance->device->key_index);
+                }
+            }
+
+            // Stop
+            notification_message(instance->notifications, &sequence_blink_stop);
+        }
+
+        consumed = true;
+    }
+
+    return consumed;
+}

+ 87 - 0
applications/plugins/subbrute/scenes/subbrute_scene_save_name.c

@@ -0,0 +1,87 @@
+#include <m-string.h>
+#include <subghz/types.h>
+#include <lib/toolbox/random_name.h>
+#include <gui/modules/validators.h>
+#include <lib/toolbox/path.h>
+
+#include "../subbrute_i.h"
+#include "../subbrute_custom_event.h"
+
+#define TAG "SubBruteSceneSaveFile"
+
+void subbrute_scene_save_name_on_enter(void* context) {
+    SubBruteState* instance = (SubBruteState*)context;
+    SubBruteDevice* device = instance->device;
+
+    // Setup view
+    TextInput* text_input = instance->text_input;
+    set_random_name(device->text_store, sizeof(device->text_store));
+
+    text_input_set_header_text(text_input, "Name of file");
+    text_input_set_result_callback(
+        text_input,
+        subbrute_text_input_callback,
+        instance,
+        device->text_store,
+        SUBBRUTE_MAX_LEN_NAME,
+        true);
+
+    string_set_str(device->load_path, SUBBRUTE_PATH);
+
+    ValidatorIsFile* validator_is_file =
+        validator_is_file_alloc_init(string_get_cstr(device->load_path), SUBBRUTE_FILE_EXT, "");
+    text_input_set_validator(text_input, validator_is_file_callback, validator_is_file);
+
+    view_dispatcher_switch_to_view(instance->view_dispatcher, SubBruteViewTextInput);
+}
+
+bool subbrute_scene_save_name_on_event(void* context, SceneManagerEvent event) {
+    SubBruteState* instance = (SubBruteState*)context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeBack) {
+        scene_manager_previous_scene(instance->scene_manager);
+        return true;
+    } else if(
+        event.type == SceneManagerEventTypeCustom &&
+        event.event == SubBruteCustomEventTypeTextEditDone) {
+#ifdef FURI_DEBUG
+        FURI_LOG_D(TAG, "Saving: %s", instance->device->text_store);
+#endif
+        bool success = false;
+        if(strcmp(instance->device->text_store, "")) {
+            string_cat_printf(
+                instance->device->load_path,
+                "/%s%s",
+                instance->device->text_store,
+                SUBBRUTE_FILE_EXT);
+
+            if(subbrute_device_save_file(
+                   instance->device, string_get_cstr(instance->device->load_path))) {
+                scene_manager_next_scene(instance->scene_manager, SubBruteSceneSaveSuccess);
+                success = true;
+                consumed = true;
+            }
+        }
+
+        if(!success) {
+            dialog_message_show_storage_error(instance->dialogs, "Error during saving!");
+            consumed = scene_manager_search_and_switch_to_previous_scene(
+                instance->scene_manager, SubBruteSceneSetupAttack);
+        }
+    }
+    return consumed;
+}
+
+void subbrute_scene_save_name_on_exit(void* context) {
+    SubBruteState* instance = (SubBruteState*)context;
+
+    // Clear view
+    void* validator_context = text_input_get_validator_callback_context(instance->text_input);
+    text_input_set_validator(instance->text_input, NULL, NULL);
+    validator_is_file_free(validator_context);
+
+    text_input_reset(instance->text_input);
+
+    string_reset(instance->device->load_path);
+}

+ 51 - 0
applications/plugins/subbrute/scenes/subbrute_scene_save_success.c

@@ -0,0 +1,51 @@
+#include "../subbrute_i.h"
+#include "../subbrute_custom_event.h"
+
+void subbrute_scene_save_success_on_enter(void* context) {
+    furi_assert(context);
+    SubBruteState* instance = context;
+
+    // Setup view
+    Popup* popup = instance->popup;
+    popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59);
+    popup_set_header(popup, "Saved!", 13, 22, AlignLeft, AlignBottom);
+    popup_set_timeout(popup, 1500);
+    popup_set_context(popup, instance);
+    popup_set_callback(popup, subbrute_popup_closed_callback);
+    popup_enable_timeout(popup);
+    view_dispatcher_switch_to_view(instance->view_dispatcher, SubBruteViewPopup);
+}
+
+bool subbrute_scene_save_success_on_event(void* context, SceneManagerEvent event) {
+    furi_assert(context);
+
+    SubBruteState* instance = (SubBruteState*)context;
+    //SubBruteMainView* view = instance->view_main;
+    
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == SubBruteCustomEventTypePopupClosed) {
+            if(!scene_manager_search_and_switch_to_previous_scene(
+                   instance->scene_manager, SubBruteSceneSetupAttack)) {
+                scene_manager_next_scene(instance->scene_manager, SubBruteSceneStart);
+            }
+            return true;
+        }
+    }
+    return false;
+}
+
+void subbrute_scene_save_success_on_exit(void* context) {
+    furi_assert(context);
+
+    SubBruteState* instance = (SubBruteState*)context;
+
+    // Clear view
+    Popup* popup = instance->popup;
+    popup_set_header(popup, NULL, 0, 0, AlignCenter, AlignBottom);
+    popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignTop);
+    popup_set_icon(popup, 0, 0, NULL);
+    popup_set_callback(popup, NULL);
+    popup_set_context(popup, NULL);
+    popup_set_timeout(popup, 0);
+    popup_disable_timeout(popup);
+}

+ 177 - 0
applications/plugins/subbrute/scenes/subbrute_scene_setup_attack.c

@@ -0,0 +1,177 @@
+#include "../subbrute_i.h"
+#include "../subbrute_custom_event.h"
+#include "../views/subbrute_attack_view.h"
+
+#define TAG "SubBruteSceneSetupAttack"
+
+static void subbrute_scene_setup_attack_callback(SubBruteCustomEvent event, void* context) {
+    furi_assert(context);
+
+    SubBruteState* instance = (SubBruteState*)context;
+    view_dispatcher_send_custom_event(instance->view_dispatcher, event);
+}
+
+void subbrute_scene_setup_attack_on_enter(void* context) {
+    furi_assert(context);
+    SubBruteState* instance = (SubBruteState*)context;
+    SubBruteAttackView* view = instance->view_attack;
+
+#ifdef FURI_DEBUG
+    FURI_LOG_D(TAG, "Enter Attack: %d", instance->device->attack);
+#endif
+
+    subbrute_attack_view_init_values(
+        view,
+        instance->device->attack,
+        instance->device->max_value,
+        instance->device->key_index,
+        false);
+
+    subbrute_worker_init_manual_transmit(
+        instance->worker,
+        instance->device->frequency,
+        instance->device->preset,
+        string_get_cstr(instance->device->protocol_name));
+
+    instance->current_view = SubBruteViewAttack;
+    subbrute_attack_view_set_callback(view, subbrute_scene_setup_attack_callback, instance);
+    view_dispatcher_switch_to_view(instance->view_dispatcher, instance->current_view);
+}
+
+void subbrute_scene_setup_attack_on_exit(void* context) {
+    furi_assert(context);
+#ifdef FURI_DEBUG
+    FURI_LOG_D(TAG, "subbrute_scene_setup_attack_on_exit");
+#endif
+    SubBruteState* instance = (SubBruteState*)context;
+    subbrute_worker_manual_transmit_stop(instance->worker);
+    notification_message(instance->notifications, &sequence_blink_stop);
+}
+
+bool subbrute_scene_setup_attack_on_event(void* context, SceneManagerEvent event) {
+    SubBruteState* instance = (SubBruteState*)context;
+    SubBruteAttackView* view = instance->view_attack;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == SubBruteCustomEventTypeTransmitStarted) {
+            subbrute_device_create_packet_parsed(instance->device, instance->device->key_index);
+            scene_manager_next_scene(instance->scene_manager, SubBruteSceneRunAttack);
+        } else if(event.event == SubBruteCustomEventTypeSaveFile) {
+            subbrute_worker_manual_transmit_stop(instance->worker);
+
+            subbrute_attack_view_init_values(
+                view,
+                instance->device->attack,
+                instance->device->max_value,
+                instance->device->key_index,
+                false);
+            scene_manager_next_scene(instance->scene_manager, SubBruteSceneSaveName);
+        } else if(event.event == SubBruteCustomEventTypeBackPressed) {
+#ifdef FURI_DEBUG
+            FURI_LOG_D(TAG, "SubBruteCustomEventTypeBackPressed");
+#endif
+            instance->device->key_index = 0x00;
+            //subbrute_attack_view_stop_worker(view);
+            subbrute_attack_view_init_values(
+                view,
+                instance->device->attack,
+                instance->device->max_value,
+                instance->device->key_index,
+                false);
+            scene_manager_next_scene(instance->scene_manager, SubBruteSceneStart);
+        } else if(event.event == SubBruteCustomEventTypeChangeStepUp) {
+            // +1
+            if((instance->device->key_index + 1) - instance->device->max_value == 1) {
+                instance->device->key_index = 0x00;
+            } else {
+                uint64_t value = instance->device->key_index + 1;
+                if(value == instance->device->max_value) {
+                    instance->device->key_index = value;
+                } else {
+                    instance->device->key_index = value % instance->device->max_value;
+                }
+            }
+            subbrute_attack_view_set_current_step(view, instance->device->key_index);
+        } else if(event.event == SubBruteCustomEventTypeChangeStepUpMore) {
+            // +100
+            uint64_t value = instance->device->key_index + 100;
+            if(value == instance->device->max_value) {
+                instance->device->key_index += value;
+            } else {
+                instance->device->key_index = value % instance->device->max_value;
+            }
+            subbrute_attack_view_set_current_step(view, instance->device->key_index);
+        } else if(event.event == SubBruteCustomEventTypeChangeStepDown) {
+            // -1
+            if(instance->device->key_index - 1 == 0) {
+                instance->device->key_index = 0x00;
+            } else if(instance->device->key_index == 0) {
+                instance->device->key_index = instance->device->max_value;
+            } else {
+                uint64_t value = ((instance->device->key_index - 1) + instance->device->max_value);
+                if(value == instance->device->max_value) {
+                    instance->device->key_index = value;
+                } else {
+                    instance->device->key_index = value % instance->device->max_value;
+                }
+            }
+            subbrute_attack_view_set_current_step(view, instance->device->key_index);
+        } else if(event.event == SubBruteCustomEventTypeChangeStepDownMore) {
+            // -100
+            uint64_t value = ((instance->device->key_index - 100) + instance->device->max_value);
+            if(value == instance->device->max_value) {
+                instance->device->key_index = value;
+            } else {
+                instance->device->key_index = value % instance->device->max_value;
+            }
+            subbrute_attack_view_set_current_step(view, instance->device->key_index);
+        } else if(event.event == SubBruteCustomEventTypeTransmitCustom) {
+            if(subbrute_worker_can_transmit(instance->worker)) {
+                // Blink
+                notification_message(instance->notifications, &sequence_blink_green_100);
+
+                //                if(!subbrute_attack_view_is_worker_running(view)) {
+                //                    subbrute_attack_view_start_worker(
+                //                        view,
+                //                        instance->device->frequency,
+                //                        instance->device->preset,
+                //                        string_get_cstr(instance->device->protocol_name));
+                //                }
+                subbrute_device_create_packet_parsed(
+                    instance->device, instance->device->key_index);
+                subbrute_worker_manual_transmit(instance->worker, instance->device->payload);
+
+                // Stop
+                notification_message(instance->notifications, &sequence_blink_stop);
+            }
+        }
+
+        consumed = true;
+    }
+
+    //    if(event.type == SceneManagerEventTypeCustom) {
+    //        switch(event.event) {
+    //        case SubBruteCustomEventTypeMenuSelected:
+    //            with_view_model(
+    //                view, (SubBruteMainViewModel * model) {
+    //                    instance->menu_index = model->index;
+    //                    return false;
+    //                });
+    //            scene_manager_next_scene(instance->scene_manager, SubBruteSceneLoadFile);
+    //            consumed = true;
+    //            break;
+    //        case SubBruteCustomEventTypeLoadFile:
+    //            with_view_model(
+    //                view, (SubBruteMainViewModel * model) {
+    //                    instance->menu_index = model->index;
+    //                    return false;
+    //                });
+    //            scene_manager_next_scene(instance->scene_manager, SubBruteSceneSetupAttack);
+    //            consumed = true;
+    //            break;
+    //        }
+    //    }
+
+    return consumed;
+}

+ 66 - 0
applications/plugins/subbrute/scenes/subbrute_scene_start.c

@@ -0,0 +1,66 @@
+#include "../subbrute_i.h"
+#include "../subbrute_custom_event.h"
+#include "../views/subbrute_main_view.h"
+
+#define TAG "SubBruteSceneStart"
+
+void subbrute_scene_start_callback(SubBruteCustomEvent event, void* context) {
+    furi_assert(context);
+
+    SubBruteState* instance = (SubBruteState*)context;
+#ifdef FURI_DEBUG
+    FURI_LOG_D(TAG, "subbrute_scene_start_callback");
+#endif
+    view_dispatcher_send_custom_event(instance->view_dispatcher, event);
+}
+
+void subbrute_scene_start_on_enter(void* context) {
+    furi_assert(context);
+#ifdef FURI_DEBUG
+    FURI_LOG_I(TAG, "subbrute_scene_start_on_enter");
+#endif
+    SubBruteState* instance = (SubBruteState*)context;
+    SubBruteMainView* view = instance->view_main;
+
+    instance->current_view = SubBruteViewMain;
+    subbrute_main_view_set_callback(view, subbrute_scene_start_callback, instance);
+    subbrute_main_view_set_index(view, instance->device->attack, false, NULL);
+
+    view_dispatcher_switch_to_view(instance->view_dispatcher, instance->current_view);
+}
+
+void subbrute_scene_start_on_exit(void* context) {
+    UNUSED(context);
+#ifdef FURI_DEBUG
+    FURI_LOG_I(TAG, "subbrute_scene_start_on_exit");
+#endif
+}
+
+bool subbrute_scene_start_on_event(void* context, SceneManagerEvent event) {
+    SubBruteState* instance = (SubBruteState*)context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+#ifdef FURI_DEBUG
+        FURI_LOG_D(TAG, "Event: %d", event.event);
+#endif
+        if(event.event == SubBruteCustomEventTypeMenuSelected) {
+            SubBruteAttacks attack = subbrute_main_view_get_index(instance->view_main);
+
+            subbrute_device_attack_set(instance->device, attack);
+            scene_manager_next_scene(instance->scene_manager, SubBruteSceneSetupAttack);
+
+            consumed = true;
+        } else if(event.event == SubBruteCustomEventTypeLoadFile) {
+            scene_manager_next_scene(instance->scene_manager, SubBruteSceneLoadFile);
+            consumed = true;
+        }
+    } else if(event.type == SceneManagerEventTypeBack) {
+        //exit app
+        scene_manager_stop(instance->scene_manager);
+        view_dispatcher_stop(instance->view_dispatcher);
+        consumed = true;
+    }
+
+    return consumed;
+}

+ 30 - 0
applications/plugins/subbrute/scenes/subbute_scene.c

@@ -0,0 +1,30 @@
+#include "subbrute_scene.h"
+
+// Generate scene on_enter handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
+void (*const subbrute_on_enter_handlers[])(void*) = {
+#include "subbrute_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Generate scene on_event handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event,
+bool (*const subbrute_on_event_handlers[])(void* context, SceneManagerEvent event) = {
+#include "subbrute_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Generate scene on_exit handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit,
+void (*const subbrute_on_exit_handlers[])(void* context) = {
+#include "subbrute_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Initialize scene handlers configuration structure
+const SceneManagerHandlers subbrute_scene_handlers = {
+    .on_enter_handlers = subbrute_on_enter_handlers,
+    .on_event_handlers = subbrute_on_event_handlers,
+    .on_exit_handlers = subbrute_on_exit_handlers,
+    .scene_num = SubBruteSceneNum,
+};

+ 269 - 231
applications/plugins/subbrute/subbrute.c

@@ -1,267 +1,305 @@
-#include "subbrute.h"
-
-#include "scene/subbrute_scene_load_file.h"
-#include "scene/subbrute_scene_select_field.h"
-#include "scene/subbrute_scene_run_attack.h"
-#include "scene/subbrute_scene_entrypoint.h"
-#include "scene/subbrute_scene_save_name.h"
-
-static void draw_callback(Canvas* const canvas, void* ctx) {
-    SubBruteState* subbrute_state = (SubBruteState*)acquire_mutex((ValueMutex*)ctx, 100);
-
-    if(subbrute_state == NULL) {
-        return;
-    }
+#include <furi.h>
+#include <furi_hal.h>
+#include <input/input.h>
+#include <m-string.h>
+
+#include <gui/gui.h>
+#include <gui/view_dispatcher.h>
+#include <gui/view_stack.h>
+#include <gui/scene_manager.h>
+#include <gui/modules/text_input.h>
+#include <gui/modules/popup.h>
+#include <gui/modules/widget.h>
+#include <gui/modules/loading.h>
+
+#include <dialogs/dialogs.h>
 
-    // Draw correct Canvas
-    switch(subbrute_state->current_scene) {
-    case NoneScene:
-    case SceneSelectFile:
-        subbrute_scene_load_file_on_draw(canvas, subbrute_state);
-        break;
-    case SceneSelectField:
-        subbrute_scene_select_field_on_draw(canvas, subbrute_state);
-        break;
-    case SceneAttack:
-        subbrute_scene_run_attack_on_draw(canvas, subbrute_state);
-        break;
-    case SceneEntryPoint:
-        subbrute_scene_entrypoint_on_draw(canvas, subbrute_state);
-        break;
-    case SceneSaveName:
-        break;
-    }
-
-    release_mutex((ValueMutex*)ctx, subbrute_state);
+#include "subbrute.h"
+#include "subbrute_i.h"
+#include "subbrute_custom_event.h"
+
+#define TAG "SubBruteApp"
+
+static const char* subbrute_menu_names[] = {
+    [SubBruteAttackCAME12bit307] = "CAME 12bit 307mhz",
+    [SubBruteAttackCAME12bit433] = "CAME 12bit 433mhz",
+    [SubBruteAttackCAME12bit868] = "CAME 12bit 868mhz",
+    [SubBruteAttackChamberlain9bit315] = "Chamberlain 9bit 315mhz",
+    [SubBruteAttackChamberlain9bit390] = "Chamberlain 9bit 390mhz",
+    [SubBruteAttackLinear10bit300] = "Linear 10bit 300mhz",
+    [SubBruteAttackLinear10bit310] = "Linear 10bit 310mhz",
+    [SubBruteAttackNICE12bit433] = "NICE 12bit 433mhz",
+    [SubBruteAttackNICE12bit868] = "NICE 12bit 868mhz",
+    [SubBruteAttackLoadFile] = "BF existing dump",
+    [SubBruteAttackTotalCount] = "Total Count",
+};
+
+static const char* subbrute_menu_names_small[] = {
+    [SubBruteAttackCAME12bit307] = "CAME 307mhz",
+    [SubBruteAttackCAME12bit433] = "CAME 433mhz",
+    [SubBruteAttackCAME12bit868] = "CAME 868mhz",
+    [SubBruteAttackChamberlain9bit315] = "Cham 315mhz",
+    [SubBruteAttackChamberlain9bit390] = "Cham 390mhz",
+    [SubBruteAttackLinear10bit300] = "Linear 300mhz",
+    [SubBruteAttackLinear10bit310] = "Linear 310mhz",
+    [SubBruteAttackNICE12bit433] = "NICE 433mhz",
+    [SubBruteAttackNICE12bit868] = "NICE 868mhz",
+    [SubBruteAttackLoadFile] = "Existing",
+    [SubBruteAttackTotalCount] = "Total Count",
+};
+
+static bool subbrute_custom_event_callback(void* context, uint32_t event) {
+    furi_assert(context);
+    SubBruteState* instance = context;
+    return scene_manager_handle_custom_event(instance->scene_manager, event);
 }
 
-void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
-    furi_assert(event_queue);
-
-    SubBruteEvent event = {
-        .evt_type = EventTypeKey, .key = input_event->key, .input_type = input_event->type};
-    furi_message_queue_put(event_queue, &event, 100);
+static bool subbrute_back_event_callback(void* context) {
+    furi_assert(context);
+    SubBruteState* instance = context;
+    return scene_manager_handle_back_event(instance->scene_manager);
 }
 
-static void timer_callback(FuriMessageQueue* event_queue) {
-    furi_assert(event_queue);
-    SubBruteEvent event = {
-        .evt_type = EventTypeTick, .key = InputKeyUp, .input_type = InputTypeRelease};
-    furi_message_queue_put(event_queue, &event, 100);
+static void subbrute_tick_event_callback(void* context) {
+    furi_assert(context);
+    SubBruteState* instance = context;
+    scene_manager_handle_tick_event(instance->scene_manager);
 }
 
 SubBruteState* subbrute_alloc() {
-    SubBruteState* subbrute = malloc(sizeof(SubBruteState));
+    SubBruteState* instance = malloc(sizeof(SubBruteState));
 
-    string_init(subbrute->protocol);
-    string_init(subbrute->preset);
-    string_init(subbrute->file_path);
-    string_init(subbrute->file_path_tmp);
-    string_init_set(subbrute->notification_msg, "");
-    string_init(subbrute->candidate);
-    string_init(subbrute->flipper_format_string);
+    instance->scene_manager = scene_manager_alloc(&subbrute_scene_handlers, instance);
+    instance->view_dispatcher = view_dispatcher_alloc();
 
-    subbrute->previous_scene = NoneScene;
-    subbrute->current_scene = SceneSelectFile;
-    subbrute->is_running = true;
-    subbrute->is_attacking = false;
-    subbrute->key_index = 7;
-    subbrute->notify = furi_record_open(RECORD_NOTIFICATION);
+    instance->gui = furi_record_open(RECORD_GUI);
 
-    subbrute->view_dispatcher = view_dispatcher_alloc();
+    view_dispatcher_enable_queue(instance->view_dispatcher);
+    view_dispatcher_set_event_callback_context(instance->view_dispatcher, instance);
+    view_dispatcher_set_custom_event_callback(
+        instance->view_dispatcher, subbrute_custom_event_callback);
+    view_dispatcher_set_navigation_event_callback(
+        instance->view_dispatcher, subbrute_back_event_callback);
+    view_dispatcher_set_tick_event_callback(
+        instance->view_dispatcher, subbrute_tick_event_callback, 100);
 
     //Dialog
-    subbrute->dialogs = furi_record_open(RECORD_DIALOGS);
-
-    subbrute->preset_def = malloc(sizeof(SubGhzPresetDefinition));
-
-    //subbrute->flipper_format = flipper_format_string_alloc();
-    //subbrute->environment = subghz_environment_alloc();
-
-    return subbrute;
+    instance->dialogs = furi_record_open(RECORD_DIALOGS);
+
+    // Notifications
+    instance->notifications = furi_record_open(RECORD_NOTIFICATION);
+
+    // Devices
+    instance->device = subbrute_device_alloc();
+
+    // Worker
+    instance->worker = subbrute_worker_alloc();
+
+    // TextInput
+    instance->text_input = text_input_alloc();
+    view_dispatcher_add_view(
+        instance->view_dispatcher,
+        SubBruteViewTextInput,
+        text_input_get_view(instance->text_input));
+
+    // Custom Widget
+    instance->widget = widget_alloc();
+    view_dispatcher_add_view(
+        instance->view_dispatcher, SubBruteViewWidget, widget_get_view(instance->widget));
+
+    // Popup
+    instance->popup = popup_alloc();
+    view_dispatcher_add_view(
+        instance->view_dispatcher, SubBruteViewPopup, popup_get_view(instance->popup));
+
+    // ViewStack
+    instance->view_stack = view_stack_alloc();
+    view_dispatcher_add_view(
+        instance->view_dispatcher, SubBruteViewStack, view_stack_get_view(instance->view_stack));
+
+    // SubBruteMainView
+    instance->view_main = subbrute_main_view_alloc();
+    view_dispatcher_add_view(
+        instance->view_dispatcher,
+        SubBruteViewMain,
+        subbrute_main_view_get_view(instance->view_main));
+
+    // SubBruteAttackView
+    instance->view_attack = subbrute_attack_view_alloc();
+    view_dispatcher_add_view(
+        instance->view_dispatcher,
+        SubBruteViewAttack,
+        subbrute_attack_view_get_view(instance->view_attack));
+
+    // Loading
+    instance->loading = loading_alloc();
+    //instance->flipper_format = flipper_format_string_alloc();
+    //instance->environment = subghz_environment_alloc();
+
+    return instance;
 }
 
-void subbrute_free(SubBruteState* subbrute) {
+void subbrute_free(SubBruteState* instance) {
+    furi_assert(instance);
+
+    // SubBruteDevice
+#ifdef FURI_DEBUG
+    FURI_LOG_D(TAG, "free SubBruteDevice");
+#endif
+    subbrute_device_free(instance->device);
+
+    // SubBruteWorker
+#ifdef FURI_DEBUG
+    FURI_LOG_D(TAG, "free SubBruteDevice");
+#endif
+    subbrute_worker_stop(instance->worker);
+    subbrute_worker_free(instance->worker);
+
+    // Notifications
+#ifdef FURI_DEBUG
+    FURI_LOG_D(TAG, "free Notifications");
+#endif
+    notification_message(instance->notifications, &sequence_blink_stop);
+    furi_record_close(RECORD_NOTIFICATION);
+    instance->notifications = NULL;
+
+    // Loading
+#ifdef FURI_DEBUG
+    FURI_LOG_D(TAG, "free loading");
+#endif
+    loading_free(instance->loading);
+
+    // View Main
+#ifdef FURI_DEBUG
+    FURI_LOG_D(TAG, "free SubBruteViewMain");
+#endif
+    view_dispatcher_remove_view(instance->view_dispatcher, SubBruteViewMain);
+    subbrute_main_view_free(instance->view_main);
+
+    // View Attack
+#ifdef FURI_DEBUG
+    FURI_LOG_D(TAG, "free SubBruteViewAttack");
+#endif
+    view_dispatcher_remove_view(instance->view_dispatcher, SubBruteViewAttack);
+    subbrute_attack_view_free(instance->view_attack);
+
+    // TextInput
+#ifdef FURI_DEBUG
+    FURI_LOG_D(TAG, "free SubBruteViewTextInput");
+#endif
+    view_dispatcher_remove_view(instance->view_dispatcher, SubBruteViewTextInput);
+    text_input_free(instance->text_input);
+
+    // Custom Widget
+#ifdef FURI_DEBUG
+    FURI_LOG_D(TAG, "free SubBruteViewWidget");
+#endif
+    view_dispatcher_remove_view(instance->view_dispatcher, SubBruteViewWidget);
+    widget_free(instance->widget);
+
+    // Popup
+#ifdef FURI_DEBUG
+    FURI_LOG_D(TAG, "free SubBruteViewPopup");
+#endif
+    view_dispatcher_remove_view(instance->view_dispatcher, SubBruteViewPopup);
+    popup_free(instance->popup);
+
+    // ViewStack
+#ifdef FURI_DEBUG
+    FURI_LOG_D(TAG, "free SubBruteViewStack");
+#endif
+    view_dispatcher_remove_view(instance->view_dispatcher, SubBruteViewStack);
+    view_stack_free(instance->view_stack);
+
     //Dialog
+#ifdef FURI_DEBUG
+    FURI_LOG_D(TAG, "free RECORD_DIALOGS");
+#endif
     furi_record_close(RECORD_DIALOGS);
+    instance->dialogs = NULL;
+
+    // Scene manager
+#ifdef FURI_DEBUG
+    FURI_LOG_D(TAG, "free scene_manager");
+#endif
+    scene_manager_free(instance->scene_manager);
+
+    // View Dispatcher
+#ifdef FURI_DEBUG
+    FURI_LOG_D(TAG, "free view_dispatcher");
+#endif
+    view_dispatcher_free(instance->view_dispatcher);
+
+    // GUI
+#ifdef FURI_DEBUG
+    FURI_LOG_D(TAG, "free RECORD_GUI");
+#endif
+    furi_record_close(RECORD_GUI);
+    instance->gui = NULL;
 
-    notification_message(subbrute->notify, &sequence_blink_stop);
+    // The rest
+#ifdef FURI_DEBUG
+    FURI_LOG_D(TAG, "free instance");
+#endif
+    free(instance);
+}
 
-    furi_record_close(RECORD_NOTIFICATION);
+void subbrute_show_loading_popup(void* context, bool show) {
+    TaskHandle_t timer_task = xTaskGetHandle(configTIMER_SERVICE_TASK_NAME);
+    SubBruteState* instance = context;
+    ViewStack* view_stack = instance->view_stack;
+    Loading* loading = instance->loading;
+
+    if(show) {
+        // Raise timer priority so that animations can play
+        vTaskPrioritySet(timer_task, configMAX_PRIORITIES - 1);
+        view_stack_add_view(view_stack, loading_get_view(loading));
+    } else {
+        view_stack_remove_view(view_stack, loading_get_view(loading));
+        // Restore default timer priority
+        vTaskPrioritySet(timer_task, configTIMER_TASK_PRIORITY);
+    }
+}
 
-    view_dispatcher_free(subbrute->view_dispatcher);
+void subbrute_text_input_callback(void* context) {
+    furi_assert(context);
+    SubBruteState* instance = context;
+    view_dispatcher_send_custom_event(
+        instance->view_dispatcher, SubBruteCustomEventTypeTextEditDone);
+}
 
-    string_clear(subbrute->preset);
-    string_clear(subbrute->candidate);
+void subbrute_popup_closed_callback(void* context) {
+    furi_assert(context);
+    SubBruteState* instance = context;
+    view_dispatcher_send_custom_event(
+        instance->view_dispatcher, SubBruteCustomEventTypePopupClosed);
+}
 
-    // Path strings
-    string_clear(subbrute->file_path);
-    string_clear(subbrute->file_path_tmp);
-    string_clear(subbrute->notification_msg);
-    string_clear(subbrute->candidate);
-    string_clear(subbrute->flipper_format_string);
+const char* subbrute_get_menu_name(SubBruteAttacks index) {
+    furi_assert(index < SubBruteAttackTotalCount);
 
-    //flipper_format_free(subbrute->flipper_format);
-    //subghz_environment_free(subbrute->environment);
-    //subghz_receiver_free(subbrute->receiver);
+    return subbrute_menu_names[index];
+}
 
-    free(subbrute->preset_def);
+const char* subbrute_get_small_menu_name(SubBruteAttacks index) {
+    furi_assert(index < SubBruteAttackTotalCount);
 
-    // The rest
-    free(subbrute);
+    return subbrute_menu_names_small[index];
 }
 
 // ENTRYPOINT
-int32_t subbrute_start(void* p) {
+int32_t subbrute_app(void* p) {
     UNUSED(p);
-    // Input
-    FURI_LOG_I(TAG, "Initializing input");
-    FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(SubBruteEvent));
-    SubBruteState* subbrute_state = subbrute_alloc();
-    ValueMutex subbrute_state_mutex;
-
-    // Mutex
-    FURI_LOG_I(TAG, "Initializing flipfrid mutex");
-    if(!init_mutex(&subbrute_state_mutex, subbrute_state, sizeof(SubBruteState))) {
-        FURI_LOG_E(TAG, "cannot create mutex\r\n");
-        furi_message_queue_free(event_queue);
-        subbrute_free(subbrute_state);
-        return 255;
-    }
-
-    furi_hal_power_suppress_charge_enter();
-
-    // Configure view port
-    FURI_LOG_I(TAG, "Initializing viewport");
-    ViewPort* view_port = view_port_alloc();
-    view_port_draw_callback_set(view_port, draw_callback, &subbrute_state_mutex);
-    view_port_input_callback_set(view_port, input_callback, event_queue);
-
-    // Configure timer
-    FURI_LOG_I(TAG, "Initializing timer");
-    FuriTimer* timer = furi_timer_alloc(timer_callback, FuriTimerTypePeriodic, event_queue);
-    furi_timer_start(timer, furi_kernel_get_tick_frequency() / 10); // 10 times per second
-
-    // Register view port in GUI
-    FURI_LOG_I(TAG, "Initializing gui");
-    subbrute_state->gui = furi_record_open(RECORD_GUI);
-    gui_add_view_port(subbrute_state->gui, view_port, GuiLayerFullscreen);
 
+    SubBruteState* instance = subbrute_alloc();
     view_dispatcher_attach_to_gui(
-        subbrute_state->view_dispatcher, subbrute_state->gui, ViewDispatcherTypeFullscreen);
-
-    subbrute_state->current_scene = SceneEntryPoint;
-
-    // Init values
-    SubBruteEvent event;
-    while(subbrute_state->is_running) {
-        // Get next event
-        FuriStatus event_status = furi_message_queue_get(event_queue, &event, 25);
-        if(event_status == FuriStatusOk) {
-            if(event.evt_type == EventTypeKey) {
-                //Handle event key
-                FURI_LOG_D(TAG, "EVENT ###");
-                switch(subbrute_state->current_scene) {
-                case SceneSelectFile:
-                    subbrute_scene_load_file_on_event(event, subbrute_state);
-                    break;
-                case SceneSelectField:
-                    subbrute_scene_select_field_on_event(event, subbrute_state);
-                    break;
-                case SceneSaveName:
-                    subbrute_scene_save_name_on_event(event, subbrute_state);
-                    break;
-                case SceneAttack:
-                    subbrute_scene_run_attack_on_event(event, subbrute_state);
-                    break;
-                case NoneScene:
-                case SceneEntryPoint:
-                    subbrute_scene_entrypoint_on_event(event, subbrute_state);
-                    break;
-                }
-
-            } else if(event.evt_type == EventTypeTick) {
-                //Handle event tick
-                if(subbrute_state->current_scene != subbrute_state->previous_scene) {
-                    // Trigger Exit Scene
-                    switch(subbrute_state->previous_scene) {
-                    case SceneSelectFile:
-                        subbrute_scene_load_file_on_exit(subbrute_state);
-                        break;
-                    case SceneSelectField:
-                        subbrute_scene_select_field_on_exit(subbrute_state);
-                        break;
-                    case SceneAttack:
-                        subbrute_scene_run_attack_on_exit(subbrute_state);
-                        break;
-                    case SceneEntryPoint:
-                        subbrute_scene_entrypoint_on_exit(subbrute_state);
-                        break;
-                    case SceneSaveName:
-                        subbrute_scene_save_name_on_exit(subbrute_state);
-                        break;
-                    case NoneScene:
-                        break;
-                    }
-
-                    // Trigger Entry Scene
-                    switch(subbrute_state->current_scene) {
-                    case NoneScene:
-                    case SceneSelectFile:
-                        subbrute_scene_load_file_on_enter(subbrute_state);
-                        break;
-                    case SceneSelectField:
-                        subbrute_scene_select_field_on_enter(subbrute_state);
-                        break;
-                    case SceneAttack:
-                        subbrute_scene_run_attack_on_enter(subbrute_state);
-                        break;
-                    case SceneSaveName:
-                        subbrute_scene_save_name_on_enter(subbrute_state);
-                        break;
-                    case SceneEntryPoint:
-                        subbrute_scene_entrypoint_on_enter(subbrute_state);
-                        break;
-                    }
-                    subbrute_state->previous_scene = subbrute_state->current_scene;
-                }
-
-                // Trigger Tick Scene
-                switch(subbrute_state->current_scene) {
-                case NoneScene:
-                case SceneSelectFile:
-                    subbrute_scene_load_file_on_tick(subbrute_state);
-                    break;
-                case SceneSelectField:
-                    subbrute_scene_select_field_on_tick(subbrute_state);
-                    break;
-                case SceneAttack:
-                    //subbrute_scene_run_attack_on_tick(subbrute_state);
-                    break;
-                case SceneEntryPoint:
-                    subbrute_scene_entrypoint_on_tick(subbrute_state);
-                    break;
-                case SceneSaveName:
-                    subbrute_scene_save_name_on_tick(subbrute_state);
-                    break;
-                }
-                view_port_update(view_port);
-            }
-        }
-    }
-
-    // Cleanup
-    furi_timer_stop(timer);
-    furi_timer_free(timer);
+        instance->view_dispatcher, instance->gui, ViewDispatcherTypeFullscreen);
+    scene_manager_next_scene(instance->scene_manager, SubBruteSceneStart);
 
+    furi_hal_power_suppress_charge_enter();
+    view_dispatcher_run(instance->view_dispatcher);
     furi_hal_power_suppress_charge_exit();
-
-    FURI_LOG_I(TAG, "Cleaning up");
-    gui_remove_view_port(subbrute_state->gui, view_port);
-    view_port_free(view_port);
-    furi_message_queue_free(event_queue);
-    furi_record_close(RECORD_GUI);
-    subbrute_free(subbrute_state);
+    subbrute_free(instance);
 
     return 0;
 }

+ 1 - 108
applications/plugins/subbrute/subbrute.h

@@ -1,110 +1,3 @@
 #pragma once
-#include <furi.h>
-#include <furi_hal.h>
-#include <input/input.h>
-#include <gui/gui.h>
-#include "m-string.h"
 
-#include <toolbox/stream/stream.h>
-#include <lib/subghz/transmitter.h>
-#include <lib/subghz/receiver.h>
-#include <flipper_format/flipper_format_i.h>
-#include <dialogs/dialogs.h>
-#include <notification/notification.h>
-#include <notification/notification_messages.h>
-#include <gui/view_dispatcher.h>
-#include <gui/modules/text_input.h>
-#include <gui/modules/popup.h>
-
-#define TAG "SUBBRUTE"
-
-typedef enum {
-    NoneScene,
-    SceneSelectFile,
-    SceneSelectField,
-    SceneAttack,
-    SceneEntryPoint,
-    SceneSaveName
-} SubBruteScene;
-
-typedef enum {
-    SubBruteAttackLoadFile,
-    SubBruteAttackCAME12bit307,
-    SubBruteAttackCAME12bit433,
-    SubBruteAttackCAME12bit868,
-    SubBruteAttackChamberlain9bit315,
-    SubBruteAttackChamberlain9bit390,
-    SubBruteAttackLinear10bit300,
-    SubBruteAttackLinear10bit310,
-    SubBruteAttackNICE12bit433,
-    SubBruteAttackNICE12bit868,
-} SubBruteAttacks;
-
-typedef enum {
-    EventTypeTick,
-    EventTypeKey,
-    EventTypeCustom,
-} EventType;
-
-typedef struct {
-    EventType evt_type;
-    InputKey key;
-    InputType input_type;
-} SubBruteEvent;
-
-// STRUCTS
-typedef struct {
-    // Application stuff
-    bool is_running;
-    bool is_attacking;
-    bool is_thread_running;
-    bool close_thread_please;
-    SubBruteScene current_scene;
-    SubBruteScene previous_scene;
-    NotificationApp* notify;
-    Gui* gui;
-    ViewDispatcher* view_dispatcher;
-    TextInput* text_input;
-    Popup* popup;
-
-    // SubGhz Stuff
-    FuriThread* bruthread;
-    FlipperFormat* flipper_format;
-    SubGhzEnvironment* environment;
-    SubGhzTransmitter* transmitter;
-    SubGhzReceiver* receiver;
-    SubGhzProtocolDecoderBase* decoder_result;
-    SubGhzPresetDefinition* preset_def;
-    string_t preset;
-    Stream* stream;
-    string_t protocol;
-    uint32_t frequency;
-    uint32_t frequency_cal;
-    uint32_t repeat;
-    uint32_t bit;
-    string_t key;
-    uint32_t te;
-
-    // Context Stuff
-    DialogsApp* dialogs;
-    char file_name_tmp[64];
-    string_t file_path;
-    string_t file_path_tmp;
-    string_t notification_msg;
-    uint8_t key_index;
-    uint64_t payload;
-    string_t candidate;
-    uint8_t str_index;
-    string_t flipper_format_string;
-
-    SubBruteAttacks attack;
-
-    //Menu stuff
-    uint8_t menu_index;
-
-    // RAW stuff
-    string_t subbrute_raw_one;
-    string_t subbrute_raw_zero;
-    string_t subbrute_raw_stop;
-
-} SubBruteState;
+typedef struct SubBruteState SubBruteState;

+ 28 - 0
applications/plugins/subbrute/subbrute_custom_event.h

@@ -0,0 +1,28 @@
+#pragma once
+
+#include <stdint.h>
+#include <stddef.h>
+
+typedef enum {
+    // Reserve first 100 events for button types and indexes, starting from 0
+    SubBruteCustomEventTypeReserved = 100,
+
+    SubBruteCustomEventTypeBackPressed,
+    SubBruteCustomEventTypeIndexSelected,
+    SubBruteCustomEventTypeTransmitStarted,
+    SubBruteCustomEventTypeTransmitFinished,
+    SubBruteCustomEventTypeTransmitNotStarted,
+    SubBruteCustomEventTypeTransmitCustom,
+    SubBruteCustomEventTypeSaveFile,
+    SubBruteCustomEventTypeSaveSuccess,
+    SubBruteCustomEventTypeChangeStepUp,
+    SubBruteCustomEventTypeChangeStepDown,
+    SubBruteCustomEventTypeChangeStepUpMore,
+    SubBruteCustomEventTypeChangeStepDownMore,
+
+    SubBruteCustomEventTypeMenuSelected,
+    SubBruteCustomEventTypeTextEditDone,
+    SubBruteCustomEventTypePopupClosed,
+
+    SubBruteCustomEventTypeLoadFile,
+} SubBruteCustomEvent;

+ 643 - 0
applications/plugins/subbrute/subbrute_device.c

@@ -0,0 +1,643 @@
+#include "subbrute_device.h"
+#include "subbrute_i.h"
+
+#include <furi.h>
+#include <furi_hal.h>
+#include <furi_hal_subghz.h>
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#include <lib/subghz/types.h>
+#include <lib/subghz/protocols/base.h>
+
+#include <storage/storage.h>
+#include <dialogs/dialogs.h>
+#include <stream/stream.h>
+#include <stream/buffered_file_stream.h>
+#include <lib/toolbox/path.h>
+#include <lib/flipper_format/flipper_format_i.h>
+
+#define TAG "SubBruteDevice"
+
+/**
+ * List of protocols
+ */
+static const char* protocol_came = "CAME";
+static const char* protocol_cham_code = "Cham_Code";
+static const char* protocol_linear = "Linear";
+static const char* protocol_nice_flo = "Nice FLO";
+static const char* protocol_princeton = "Princeton";
+static const char* protocol_raw = "RAW";
+
+/**
+ * Values to not use less memory for packet parse operations
+ */
+static const char* subbrute_key_file_start =
+    "Filetype: Flipper SubGhz Key File\nVersion: 1\nFrequency: %u\nPreset: %s\nProtocol: %s\nBit: %d";
+static const char* subbrute_key_file_key = "%s\nKey: %s\n";
+static const char* subbrute_key_file_princeton_end = "%s\nKey: %s\nTE: %d\n";
+
+// Why nobody set in as const in all codebase?
+static const char* preset_ook270_async = "FuriHalSubGhzPresetOok270Async";
+static const char* preset_ook650_async = "FuriHalSubGhzPresetOok650Async";
+static const char* preset_2fsk_dev238_async = "FuriHalSubGhzPreset2FSKDev238Async";
+static const char* preset_2fsk_dev476_async = "FuriHalSubGhzPreset2FSKDev476Async";
+static const char* preset_msk99_97_kb_async = "FuriHalSubGhzPresetMSK99_97KbAsync";
+static const char* preset_gfs99_97_kb_async = "FuriHalSubGhzPresetGFS99_97KbAsync";
+
+SubBruteDevice* subbrute_device_alloc() {
+    SubBruteDevice* instance = malloc(sizeof(SubBruteDevice));
+
+    instance->state = SubBruteDeviceStateIDLE;
+    instance->key_index = 0;
+
+    string_init(instance->load_path);
+    string_init(instance->preset_name);
+    string_init(instance->protocol_name);
+
+    instance->decoder_result = NULL;
+    instance->receiver = NULL;
+    instance->environment = NULL;
+
+    subbrute_device_attack_set_default_values(instance, SubBruteAttackCAME12bit307);
+
+    return instance;
+}
+
+void subbrute_device_free(SubBruteDevice* instance) {
+    furi_assert(instance);
+#ifdef FURI_DEBUG
+    FURI_LOG_D(TAG, "subbrute_device_free");
+#endif
+
+    // I don't know how to free this
+    instance->decoder_result = NULL;
+
+    if(instance->receiver != NULL) {
+#ifdef FURI_DEBUG
+        FURI_LOG_D(TAG, "subghz_receiver_free");
+#endif
+        subghz_receiver_free(instance->receiver);
+        instance->receiver = NULL;
+    }
+
+    if(instance->environment != NULL) {
+#ifdef FURI_DEBUG
+        FURI_LOG_D(TAG, "subghz_environment_free");
+#endif
+        subghz_environment_free(instance->environment);
+        instance->environment = NULL;
+    }
+
+#ifdef FURI_DEBUG
+    FURI_LOG_D(TAG, "before free");
+#endif
+
+    string_clear(instance->load_path);
+    string_clear(instance->preset_name);
+    string_clear(instance->protocol_name);
+
+    free(instance);
+}
+
+bool subbrute_device_save_file(SubBruteDevice* instance, const char* dev_file_name) {
+    furi_assert(instance);
+
+#ifdef FURI_DEBUG
+    FURI_LOG_D(TAG, "subbrute_device_save_file: %s", dev_file_name);
+#endif
+    bool result = subbrute_device_create_packet_parsed(instance, instance->key_index);
+
+    if(!result) {
+        FURI_LOG_E(TAG, "subbrute_device_create_packet_parsed failed!");
+        //subbrute_device_notification_message(instance, &sequence_error);
+        return false;
+    }
+
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    Stream* stream = buffered_file_stream_alloc(storage);
+
+    result = false;
+    do {
+        if(!buffered_file_stream_open(stream, dev_file_name, FSAM_READ_WRITE, FSOM_OPEN_ALWAYS)) {
+            buffered_file_stream_close(stream);
+            break;
+        }
+        stream_write_cstring(stream, instance->payload);
+
+        result = true;
+    } while(false);
+
+    buffered_file_stream_close(stream);
+    stream_free(stream);
+    if(!result) {
+        FURI_LOG_E(TAG, "stream_write_string failed!");
+        //subbrute_device_notification_message(instance, &sequence_error);
+    }
+
+    furi_record_close(RECORD_STORAGE);
+
+    return result;
+}
+
+const char* subbrute_device_error_get_desc(SubBruteFileResult error_id) {
+    const char* result;
+    switch(error_id) {
+    case(SubBruteFileResultOk):
+        result = "OK";
+        break;
+    case(SubBruteFileResultErrorOpenFile):
+        result = "invalid name/path";
+        break;
+    case(SubBruteFileResultMissingOrIncorrectHeader):
+        result = "Missing or incorrect header";
+        break;
+    case(SubBruteFileResultFrequencyNotAllowed):
+        result = "Invalid frequency!";
+        break;
+    case(SubBruteFileResultMissingOrIncorrectFrequency):
+        result = "Missing or incorrect Frequency";
+        break;
+    case(SubBruteFileResultPresetInvalid):
+        result = "Preset FAIL";
+        break;
+    case(SubBruteFileResultMissingProtocol):
+        result = "Missing Protocol";
+        break;
+    case(SubBruteFileResultProtocolNotSupported):
+        result = "RAW unsupported";
+        break;
+    case(SubBruteFileResultDynamicProtocolNotValid):
+        result = "Dynamic protocol unsupported";
+        break;
+    case(SubBruteFileResultProtocolNotFound):
+        result = "Protocol not found";
+        break;
+    case(SubBruteFileResultMissingOrIncorrectBit):
+        result = "Missing or incorrect Bit";
+        break;
+    case(SubBruteFileResultMissingOrIncorrectKey):
+        result = "Missing or incorrect Key";
+        break;
+    case(SubBruteFileResultMissingOrIncorrectTe):
+        result = "Missing or incorrect TE";
+        break;
+    case SubBruteFileResultUnknown:
+    default:
+        result = "Unknown error";
+        break;
+    }
+    return result;
+}
+
+bool subbrute_device_create_packet_parsed(SubBruteDevice* instance, uint64_t step) {
+    furi_assert(instance);
+
+    //char step_payload[32];
+    //memset(step_payload, '0', sizeof(step_payload));
+    memset(instance->payload, 0, sizeof(instance->payload));
+    string_t candidate;
+    string_init(candidate);
+
+    if(instance->attack == SubBruteAttackLoadFile) {
+        if(step >= sizeof(instance->file_key)) {
+            return false;
+        }
+        char subbrute_payload_byte[4];
+        string_set_str(candidate, instance->file_key);
+        snprintf(subbrute_payload_byte, 4, "%02X ", (uint8_t)step);
+        string_replace_at(candidate, instance->load_index * 3, 3, subbrute_payload_byte);
+        //snprintf(step_payload, sizeof(step_payload), "%02X", (uint8_t)instance->file_key[step]);
+    } else {
+        //snprintf(step_payload, sizeof(step_payload), "%16X", step);
+        //snprintf(step_payload, sizeof(step_payload), "%016llX", step);
+        string_t buffer;
+        string_init(buffer);
+        string_init_printf(buffer, "%16X", step);
+        int j = 0;
+        string_set_str(candidate, "                       ");
+        for(uint8_t i = 0; i < 16; i++) {
+            if(string_get_char(buffer, i) != ' ') {
+                string_set_char(candidate, i + j, string_get_char(buffer, i));
+            } else {
+                string_set_char(candidate, i + j, '0');
+            }
+            if(i % 2 != 0) {
+                j++;
+            }
+        }
+        string_clear(buffer);
+    }
+
+#ifdef FURI_DEBUG
+    FURI_LOG_D(TAG, "candidate: %s, step: %d", string_get_cstr(candidate), step);
+#endif
+
+    if(instance->has_tail) {
+        snprintf(
+            instance->payload,
+            sizeof(instance->payload),
+            subbrute_key_file_princeton_end,
+            instance->file_template,
+            string_get_cstr(candidate),
+            instance->te);
+    } else {
+        snprintf(
+            instance->payload,
+            sizeof(instance->payload),
+            subbrute_key_file_key,
+            instance->file_template,
+            string_get_cstr(candidate));
+    }
+
+#ifdef FURI_DEBUG
+    //FURI_LOG_D(TAG, "payload: %s", instance->payload);
+#endif
+
+    string_clear(candidate);
+
+    return true;
+}
+
+SubBruteFileResult subbrute_device_attack_set(SubBruteDevice* instance, SubBruteAttacks type) {
+    furi_assert(instance);
+#ifdef FURI_DEBUG
+    FURI_LOG_D(TAG, "subbrute_device_attack_set: %d", type);
+#endif
+    subbrute_device_attack_set_default_values(instance, type);
+    switch(type) {
+    case SubBruteAttackLoadFile:
+        // In this case values must be already set
+        //        file_result =
+        //            subbrute_device_load_from_file(instance, string_get_cstr(instance->load_path));
+        //        if(file_result != SubBruteFileResultOk) {
+        //            // Failed load file so failed to set attack type
+        //            return file_result; // RETURN
+        //        }
+        break;
+    case SubBruteAttackCAME12bit307:
+    case SubBruteAttackCAME12bit433:
+    case SubBruteAttackCAME12bit868:
+        if(type == SubBruteAttackCAME12bit307) {
+            instance->frequency = 307800000;
+        } else if(type == SubBruteAttackCAME12bit433) {
+            instance->frequency = 433920000;
+        } else /* ALWAYS TRUE if(type == SubBruteAttackCAME12bit868) */ {
+            instance->frequency = 868350000;
+        }
+        instance->bit = 12;
+        string_set_str(instance->protocol_name, protocol_came);
+        string_set_str(instance->preset_name, preset_ook650_async);
+        break;
+    case SubBruteAttackChamberlain9bit315:
+        instance->frequency = 315000000;
+        instance->bit = 9;
+        string_set_str(instance->protocol_name, protocol_cham_code);
+        string_set_str(instance->preset_name, preset_ook650_async);
+        break;
+    case SubBruteAttackChamberlain9bit390:
+        instance->frequency = 390000000;
+        instance->bit = 9;
+        string_set_str(instance->protocol_name, protocol_cham_code);
+        string_set_str(instance->preset_name, preset_ook650_async);
+        break;
+    case SubBruteAttackLinear10bit300:
+        instance->frequency = 300000000;
+        instance->bit = 10;
+        string_set_str(instance->protocol_name, protocol_linear);
+        string_set_str(instance->preset_name, preset_ook650_async);
+        break;
+    case SubBruteAttackLinear10bit310:
+        instance->frequency = 310000000;
+        instance->bit = 10;
+        string_set_str(instance->protocol_name, protocol_linear);
+        string_set_str(instance->preset_name, preset_ook650_async);
+        break;
+    case SubBruteAttackNICE12bit433:
+        instance->frequency = 433920000;
+        instance->bit = 12;
+        string_set_str(instance->protocol_name, protocol_nice_flo);
+        string_set_str(instance->preset_name, preset_ook650_async);
+        break;
+    case SubBruteAttackNICE12bit868:
+        instance->frequency = 868350000;
+        instance->bit = 12;
+        string_set_str(instance->protocol_name, protocol_nice_flo);
+        string_set_str(instance->preset_name, preset_ook650_async);
+        break;
+    default:
+        FURI_LOG_E(TAG, "Unknown attack type: %d", type);
+        return SubBruteFileResultProtocolNotFound; // RETURN
+    }
+
+    if(!furi_hal_subghz_is_tx_allowed(instance->frequency)) {
+        FURI_LOG_E(TAG, "Frequency invalid: %d", instance->frequency);
+        return SubBruteFileResultMissingOrIncorrectFrequency; // RETURN
+    }
+
+    // For non-file types we didn't set SubGhzProtocolDecoderBase
+    instance->environment = subghz_environment_alloc();
+    instance->receiver = subghz_receiver_alloc_init(instance->environment);
+    subghz_receiver_set_filter(instance->receiver, SubGhzProtocolFlag_Decodable);
+    furi_hal_subghz_reset();
+
+    uint8_t protocol_check_result = SubBruteFileResultProtocolNotFound;
+    if(type != SubBruteAttackLoadFile) {
+        instance->decoder_result = subghz_receiver_search_decoder_base_by_name(
+            instance->receiver, string_get_cstr(instance->protocol_name));
+
+        if(!instance->decoder_result ||
+           instance->decoder_result->protocol->type == SubGhzProtocolTypeDynamic) {
+            FURI_LOG_E(TAG, "Can't load SubGhzProtocolDecoderBase in phase non-file decoder set");
+        } else {
+            protocol_check_result = SubBruteFileResultOk;
+        }
+    } else {
+        // And here we need to set preset enum
+        instance->preset = subbrute_device_convert_preset(string_get_cstr(instance->preset_name));
+        protocol_check_result = SubBruteFileResultOk;
+    }
+
+    subghz_environment_free(instance->environment);
+    subghz_receiver_free(instance->receiver);
+    instance->receiver = NULL;
+    instance->environment = NULL;
+
+    if(protocol_check_result != SubBruteFileResultOk) {
+        return SubBruteFileResultProtocolNotFound;
+    }
+
+    instance->has_tail =
+        (strcmp(string_get_cstr(instance->protocol_name), protocol_princeton) == 0);
+
+    // Calc max value
+    if(instance->attack == SubBruteAttackLoadFile) {
+        instance->max_value = 0xFF;
+    } else {
+        string_t max_value_s;
+        string_init(max_value_s);
+        for(uint8_t i = 0; i < instance->bit; i++) {
+            string_cat_printf(max_value_s, "1");
+        }
+        instance->max_value = (uint64_t)strtol(string_get_cstr(max_value_s), NULL, 2);
+        string_clear(max_value_s);
+    }
+
+    // Now we are ready to set file template for using in the future with snprintf
+    // for sending attack payload
+    snprintf(
+        instance->file_template,
+        sizeof(instance->file_template),
+        subbrute_key_file_start,
+        instance->frequency,
+        string_get_cstr(instance->preset_name),
+        string_get_cstr(instance->protocol_name),
+        instance->bit);
+//    strncat(instance->file_template, "\n", sizeof(instance->file_template));
+//    strncat(instance->file_template, subbrute_key_file_key, sizeof(instance->file_template));
+//    if(instance->has_tail) {
+//        strncat(
+//            instance->file_template,
+//            subbrute_key_file_princeton_end,
+//            sizeof(instance->file_template));
+//    }
+#ifdef FURI_DEBUG
+    FURI_LOG_D(TAG, "tail: %d, file_template: %s", instance->has_tail, instance->file_template);
+#endif
+
+    // Init payload
+    subbrute_device_create_packet_parsed(instance, instance->key_index);
+
+    return SubBruteFileResultOk;
+}
+
+uint8_t subbrute_device_load_from_file(SubBruteDevice* instance, string_t file_path) {
+    furi_assert(instance);
+#ifdef FURI_DEBUG
+    FURI_LOG_D(TAG, "subbrute_device_load_from_file: %s", string_get_cstr(file_path));
+#endif
+    SubBruteFileResult result = SubBruteFileResultUnknown;
+
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    FlipperFormat* fff_data_file = flipper_format_file_alloc(storage);
+
+    string_t temp_str;
+    string_init(temp_str);
+    uint32_t temp_data32;
+
+    instance->environment = subghz_environment_alloc();
+    instance->receiver = subghz_receiver_alloc_init(instance->environment);
+    subghz_receiver_set_filter(instance->receiver, SubGhzProtocolFlag_Decodable);
+    furi_hal_subghz_reset();
+
+    do {
+        if(!flipper_format_file_open_existing(fff_data_file, string_get_cstr(file_path))) {
+            FURI_LOG_E(TAG, "Error open file %s", string_get_cstr(file_path));
+            result = SubBruteFileResultErrorOpenFile;
+            break;
+        }
+        if(!flipper_format_read_header(fff_data_file, temp_str, &temp_data32)) {
+            FURI_LOG_E(TAG, "Missing or incorrect header");
+            result = SubBruteFileResultMissingOrIncorrectHeader;
+            break;
+        }
+
+        // Frequency
+        if(flipper_format_read_uint32(fff_data_file, "Frequency", &temp_data32, 1)) {
+            instance->frequency = temp_data32;
+            if(!furi_hal_subghz_is_tx_allowed(instance->frequency)) {
+                result = SubBruteFileResultFrequencyNotAllowed;
+                break;
+            }
+        } else {
+            FURI_LOG_E(TAG, "Missing or incorrect Frequency");
+            result = SubBruteFileResultMissingOrIncorrectFrequency;
+            break;
+        }
+        // Preset
+        if(!flipper_format_read_string(fff_data_file, "Preset", temp_str)) {
+            FURI_LOG_E(TAG, "Preset FAIL");
+            result = SubBruteFileResultPresetInvalid;
+        } else {
+            string_init_set_str(instance->preset_name, string_get_cstr(temp_str));
+        }
+
+        // Protocol
+        if(!flipper_format_read_string(fff_data_file, "Protocol", temp_str)) {
+            FURI_LOG_E(TAG, "Missing Protocol");
+            result = SubBruteFileResultMissingProtocol;
+            break;
+        } else {
+            string_init_set_str(instance->protocol_name, string_get_cstr(temp_str));
+#ifdef FURI_DEBUG
+            FURI_LOG_D(TAG, "Protocol: %s", string_get_cstr(instance->protocol_name));
+#endif
+        }
+
+        instance->decoder_result = subghz_receiver_search_decoder_base_by_name(
+            instance->receiver, string_get_cstr(instance->protocol_name));
+
+        if(!instance->decoder_result ||
+           strcmp(string_get_cstr(instance->protocol_name), "RAW") == 0) {
+            FURI_LOG_E(TAG, "RAW unsupported");
+            result = SubBruteFileResultProtocolNotSupported;
+            break;
+        }
+
+        if(instance->decoder_result->protocol->type == SubGhzProtocolTypeDynamic) {
+            FURI_LOG_E(TAG, "Protocol is dynamic - not supported");
+            result = SubBruteFileResultDynamicProtocolNotValid;
+            break;
+        }
+#ifdef FURI_DEBUG
+        else {
+            FURI_LOG_D(TAG, "Decoder: %s", instance->decoder_result->protocol->name);
+        }
+#endif
+
+        //        instance->decoder_result = subghz_receiver_search_decoder_base_by_name(
+        //            instance->receiver, string_get_cstr(instance->protocol_name));
+        //
+        //        if(!instance->decoder_result) {
+        //            FURI_LOG_E(TAG, "Protocol not found");
+        //            result = SubBruteFileResultProtocolNotFound;
+        //            break;
+        //        }
+
+        // Bit
+        if(!flipper_format_read_uint32(fff_data_file, "Bit", &temp_data32, 1)) {
+            FURI_LOG_E(TAG, "Missing or incorrect Bit");
+            result = SubBruteFileResultMissingOrIncorrectBit;
+            break;
+        } else {
+            instance->bit = temp_data32;
+#ifdef FURI_DEBUG
+            FURI_LOG_D(TAG, "Bit: %d", instance->bit);
+#endif
+        }
+
+        // Key
+        if(!flipper_format_read_string(fff_data_file, "Key", temp_str)) {
+            FURI_LOG_E(TAG, "Missing or incorrect Key");
+            result = SubBruteFileResultMissingOrIncorrectKey;
+            break;
+        } else {
+            snprintf(
+                instance->file_key, sizeof(instance->file_key), "%s", string_get_cstr(temp_str));
+#ifdef FURI_DEBUG
+            FURI_LOG_D(TAG, "Key: %s", instance->file_key);
+#endif
+        }
+
+        // TE
+        if(!flipper_format_read_uint32(fff_data_file, "TE", &temp_data32, 1)) {
+            FURI_LOG_E(TAG, "Missing or incorrect TE");
+            //result = SubBruteFileResultMissingOrIncorrectTe;
+            //break;
+        } else {
+            instance->te = temp_data32;
+            instance->has_tail = true;
+        }
+
+        // Repeat
+        if(flipper_format_read_uint32(fff_data_file, "Repeat", &temp_data32, 1)) {
+#ifdef FURI_DEBUG
+            FURI_LOG_D(TAG, "Repeat: %d", temp_data32);
+#endif
+            instance->repeat = temp_data32;
+        } else {
+#ifdef FURI_DEBUG
+            FURI_LOG_D(TAG, "Repeat: 3 (default)");
+#endif
+            instance->repeat = 3;
+        }
+
+        result = SubBruteFileResultOk;
+    } while(0);
+
+    string_clear(temp_str);
+    flipper_format_file_close(fff_data_file);
+    flipper_format_free(fff_data_file);
+    furi_record_close(RECORD_STORAGE);
+
+    subghz_environment_free(instance->environment);
+    subghz_receiver_free(instance->receiver);
+
+    instance->decoder_result = NULL;
+    instance->receiver = NULL;
+    instance->environment = NULL;
+
+    if(result == SubBruteFileResultOk) {
+#ifdef FURI_DEBUG
+        FURI_LOG_D(TAG, "Loaded successfully");
+#endif
+    }
+
+    return result;
+}
+
+void subbrute_device_attack_set_default_values(
+    SubBruteDevice* instance,
+    SubBruteAttacks default_attack) {
+    furi_assert(instance);
+#ifdef FURI_DEBUG
+    FURI_LOG_D(TAG, "subbrute_device_attack_set_default_values");
+#endif
+    instance->attack = default_attack;
+    instance->key_index = 0x00;
+    instance->load_index = 0x00;
+    memset(instance->file_template, 0, sizeof(instance->file_template));
+    memset(instance->current_key, 0, sizeof(instance->current_key));
+    memset(instance->text_store, 0, sizeof(instance->text_store));
+    memset(instance->payload, 0, sizeof(instance->payload));
+
+    if(default_attack != SubBruteAttackLoadFile) {
+        memset(instance->file_key, 0, sizeof(instance->file_key));
+
+        instance->max_value = (uint64_t)0x00;
+
+        string_clear(instance->protocol_name);
+        string_clear(instance->preset_name);
+
+        string_clear(instance->load_path);
+        string_init(instance->load_path);
+
+        string_init_set_str(instance->protocol_name, protocol_raw);
+        string_init_set_str(instance->preset_name, preset_ook650_async);
+        instance->preset = FuriHalSubGhzPresetOok650Async;
+
+        instance->repeat = 5;
+        instance->te = 0;
+        instance->has_tail = false;
+    }
+#ifdef FURI_DEBUG
+    FURI_LOG_D(
+        TAG, "subbrute_device_attack_set_default_values done. has_tail: %d", instance->has_tail);
+    //furi_delay_ms(250);
+#endif
+}
+
+FuriHalSubGhzPreset subbrute_device_convert_preset(const char* preset_name) {
+    string_t preset;
+    string_init_set_str(preset, preset_name);
+    FuriHalSubGhzPreset preset_value;
+    if(string_cmp_str(preset, preset_ook270_async) == 0) {
+        preset_value = FuriHalSubGhzPresetOok270Async;
+    } else if(string_cmp_str(preset, preset_ook650_async) == 0) {
+        preset_value = FuriHalSubGhzPresetOok650Async;
+    } else if(string_cmp_str(preset, preset_2fsk_dev238_async) == 0) {
+        preset_value = FuriHalSubGhzPreset2FSKDev238Async;
+    } else if(string_cmp_str(preset, preset_2fsk_dev476_async) == 0) {
+        preset_value = FuriHalSubGhzPreset2FSKDev476Async;
+    } else if(string_cmp_str(preset, preset_msk99_97_kb_async) == 0) {
+        preset_value = FuriHalSubGhzPresetMSK99_97KbAsync;
+    } else if(string_cmp_str(preset, preset_gfs99_97_kb_async) == 0) {
+        preset_value = FuriHalSubGhzPresetMSK99_97KbAsync;
+    } else {
+        preset_value = FuriHalSubGhzPresetCustom;
+    }
+
+    string_clear(preset);
+    return preset_value;
+}

+ 99 - 0
applications/plugins/subbrute/subbrute_device.h

@@ -0,0 +1,99 @@
+#pragma once
+
+#include <lib/toolbox/stream/stream.h>
+#include <gui/gui.h>
+#include <dialogs/dialogs.h>
+#include <lib/subghz/protocols/base.h>
+#include <lib/subghz/transmitter.h>
+#include <lib/subghz/receiver.h>
+#include <lib/subghz/environment.h>
+
+#define SUBBRUTE_TEXT_STORE_SIZE 256
+
+#define SUBBRUTE_MAX_LEN_NAME 64
+#define SUBBRUTE_PATH EXT_PATH("subghz")
+#define SUBBRUTE_FILE_EXT ".sub"
+
+#define SUBBRUTE_PAYLOAD_SIZE 16
+
+typedef enum {
+    SubBruteAttackCAME12bit307,
+    SubBruteAttackCAME12bit433,
+    SubBruteAttackCAME12bit868,
+    SubBruteAttackChamberlain9bit315,
+    SubBruteAttackChamberlain9bit390,
+    SubBruteAttackLinear10bit300,
+    SubBruteAttackLinear10bit310,
+    SubBruteAttackNICE12bit433,
+    SubBruteAttackNICE12bit868,
+    SubBruteAttackLoadFile,
+    SubBruteAttackTotalCount,
+} SubBruteAttacks;
+
+typedef enum {
+    SubBruteFileResultUnknown,
+    SubBruteFileResultOk,
+    SubBruteFileResultErrorOpenFile,
+    SubBruteFileResultMissingOrIncorrectHeader,
+    SubBruteFileResultFrequencyNotAllowed,
+    SubBruteFileResultMissingOrIncorrectFrequency,
+    SubBruteFileResultPresetInvalid,
+    SubBruteFileResultMissingProtocol,
+    SubBruteFileResultProtocolNotSupported,
+    SubBruteFileResultDynamicProtocolNotValid,
+    SubBruteFileResultProtocolNotFound,
+    SubBruteFileResultMissingOrIncorrectBit,
+    SubBruteFileResultMissingOrIncorrectKey,
+    SubBruteFileResultMissingOrIncorrectTe,
+} SubBruteFileResult;
+
+typedef enum {
+    SubBruteDeviceStateIDLE,
+    SubBruteDeviceStateReady,
+    SubBruteDeviceStateTx,
+    SubBruteDeviceStateFinished,
+} SubBruteDeviceState;
+
+typedef struct {
+    SubBruteDeviceState state;
+
+    // Current step
+    uint64_t key_index;
+    string_t load_path;
+    // Index of group to bruteforce in loaded file
+    uint8_t load_index;
+
+    SubGhzReceiver* receiver;
+    SubGhzProtocolDecoderBase* decoder_result;
+    SubGhzEnvironment* environment;
+
+    // Attack state
+    SubBruteAttacks attack;
+    char file_template[SUBBRUTE_TEXT_STORE_SIZE];
+    bool has_tail;
+    char payload[SUBBRUTE_TEXT_STORE_SIZE * 2];
+    uint64_t max_value;
+
+    // Loaded info for attack type
+    FuriHalSubGhzPreset preset;
+    string_t preset_name;
+    string_t protocol_name;
+    uint32_t frequency;
+    uint32_t repeat;
+    uint32_t bit;
+    char current_key[SUBBRUTE_PAYLOAD_SIZE];
+    uint32_t te;
+
+    char file_key[SUBBRUTE_MAX_LEN_NAME];
+    char text_store[SUBBRUTE_PAYLOAD_SIZE];
+} SubBruteDevice;
+
+SubBruteDevice* subbrute_device_alloc();
+void subbrute_device_free(SubBruteDevice* instance);
+bool subbrute_device_save_file(SubBruteDevice* instance, const char* key_name);
+const char* subbrute_device_error_get_desc(SubBruteFileResult error_id);
+bool subbrute_device_create_packet_parsed(SubBruteDevice* context, uint64_t step);
+SubBruteFileResult subbrute_device_attack_set(SubBruteDevice* context, SubBruteAttacks type);
+uint8_t subbrute_device_load_from_file(SubBruteDevice* context, string_t file_path);
+FuriHalSubGhzPreset subbrute_device_convert_preset(const char* preset);
+void subbrute_device_attack_set_default_values(SubBruteDevice* context, SubBruteAttacks default_attack);

+ 83 - 0
applications/plugins/subbrute/subbrute_i.h

@@ -0,0 +1,83 @@
+#pragma once
+
+#include <furi.h>
+#include <furi_hal.h>
+#include <input/input.h>
+
+#include "lib/toolbox/path.h"
+#include <notification/notification.h>
+#include <notification/notification_messages.h>
+#include <m-string.h>
+
+#include <lib/toolbox/stream/stream.h>
+#include <stream_buffer.h>
+
+#include <gui/gui.h>
+#include <gui/view_dispatcher.h>
+#include <gui/view_stack.h>
+#include <gui/scene_manager.h>
+#include <gui/modules/text_input.h>
+#include <gui/modules/popup.h>
+#include <gui/modules/widget.h>
+#include <gui/modules/loading.h>
+
+#include <dialogs/dialogs.h>
+
+#include <lib/subghz/protocols/base.h>
+#include <lib/subghz/transmitter.h>
+#include <lib/subghz/receiver.h>
+#include <lib/subghz/environment.h>
+#include <notification/notification.h>
+#include <notification/notification_messages.h>
+
+#include "subbrute_device.h"
+#include "helpers/subbrute_worker.h"
+#include "subbrute.h"
+#include "scenes/subbrute_scene.h"
+#include "views/subbrute_attack_view.h"
+#include "views/subbrute_main_view.h"
+
+typedef enum {
+    SubBruteViewNone,
+    SubBruteViewMain,
+    SubBruteViewAttack,
+    SubBruteViewTextInput,
+    SubBruteViewDialogEx,
+    SubBruteViewPopup,
+    SubBruteViewWidget,
+    SubBruteViewStack,
+} SubBruteView;
+
+struct SubBruteState {
+    // GUI elements
+    NotificationApp* notifications;
+    Gui* gui;
+    ViewDispatcher* view_dispatcher;
+    ViewStack* view_stack;
+    TextInput* text_input;
+    Popup* popup;
+    Widget* widget;
+    DialogsApp* dialogs;
+    Loading* loading;
+
+    // Views
+    SubBruteMainView* view_main;
+    SubBruteAttackView* view_attack;
+    SubBruteView current_view;
+
+    // Scene
+    SceneManager* scene_manager;
+
+    SubBruteDevice* device;
+    SubBruteWorker* worker;
+
+    //Menu stuff
+    // TODO: Do we need it?
+    uint8_t menu_index;
+};
+
+void subbrute_show_loading_popup(void* context, bool show);
+void subbrute_text_input_callback(void* context);
+void subbrute_popup_closed_callback(void* context);
+const char* subbrute_get_menu_name(uint8_t index);
+const char* subbrute_get_small_menu_name(uint8_t index);

+ 0 - 13
applications/plugins/subbrute/subbrute_utils.c

@@ -1,13 +0,0 @@
-#include "subbrute_utils.h"
-
-bool subbrute_is_frequency_allowed(SubBruteState* context) {
-    // I know you don't like it but laws are laws
-    // It's opensource so do whatever you want, but remember the risks :)
-    // (Yes, this comment is the only purpose of this function)
-    bool r = furi_hal_subghz_is_tx_allowed(context->frequency);
-    if(!r) {
-        FURI_LOG_E(TAG, "Frequency %d is not allowed in your region", context->frequency);
-        notification_message(context->notify, &sequence_single_vibro);
-    }
-    return r;
-}

+ 0 - 4
applications/plugins/subbrute/subbrute_utils.h

@@ -1,4 +0,0 @@
-#pragma once
-#include "subbrute.h"
-
-bool subbrute_is_frequency_allowed(SubBruteState* context);

+ 374 - 0
applications/plugins/subbrute/views/subbrute_attack_view.c

@@ -0,0 +1,374 @@
+#include "subbrute_attack_view.h"
+#include "../subbrute_i.h"
+
+#include "assets_icons.h"
+#include <input/input.h>
+#include <gui/elements.h>
+#include <gui/icon_i.h>
+#include <gui/icon_animation_i.h>
+
+#define TAG "SubBruteAttackView"
+
+struct SubBruteAttackView {
+    View* view;
+    SubBruteAttackViewCallback callback;
+    void* context;
+};
+
+typedef struct {
+    SubBruteAttacks index;
+    uint64_t max_value;
+    uint64_t current_step;
+    bool is_attacking;
+    IconAnimation* icon;
+} SubBruteAttackViewModel;
+
+void subbrute_attack_view_set_callback(
+    SubBruteAttackView* instance,
+    SubBruteAttackViewCallback callback,
+    void* context) {
+    furi_assert(instance);
+    furi_assert(callback);
+
+    instance->callback = callback;
+    instance->context = context;
+}
+
+bool subbrute_attack_view_input(InputEvent* event, void* context) {
+    furi_assert(event);
+    furi_assert(context);
+#ifdef FURI_DEBUG
+    FURI_LOG_D(TAG, "InputKey: %d", event->key);
+#endif
+    SubBruteAttackView* instance = context;
+
+    if(event->key == InputKeyBack && event->type == InputTypeShort) {
+        instance->callback(SubBruteCustomEventTypeBackPressed, instance->context);
+        with_view_model(
+            instance->view, (SubBruteAttackViewModel * model) {
+                model->is_attacking = false;
+                return true;
+            });
+        return true;
+    }
+
+    bool is_attacking = false;
+
+    with_view_model(
+        instance->view, (SubBruteAttackViewModel * model) {
+            is_attacking = model->is_attacking;
+            return false;
+        });
+
+    //    if(!is_attacking) {
+    //        instance->callback(SubBruteCustomEventTypeTransmitNotStarted, instance->context);
+    //    } else {
+    //        instance->callback(SubBruteCustomEventTypeTransmitStarted, instance->context);
+    //    }
+
+    if(!is_attacking) {
+        if((event->type == InputTypeShort || event->type == InputTypeRepeat) &&
+           event->key == InputKeyOk) {
+#ifdef FURI_DEBUG
+            FURI_LOG_D(TAG, "InputKey: %d OK", event->key);
+#endif
+            with_view_model(
+                instance->view, (SubBruteAttackViewModel * model) {
+                    model->is_attacking = true;
+                    icon_animation_stop(model->icon);
+                    icon_animation_start(model->icon);
+                    return true;
+                });
+            instance->callback(SubBruteCustomEventTypeTransmitStarted, instance->context);
+            //        } else if(event->key == InputKeyBack) {
+            //            if(previous_scene == SubBruteSceneLoadFile) {
+            //                instance->callback(SubBruteCustomEventTypeLoadFile, instance->context);
+            //            } else {
+            //                instance->callback(SubBruteCustomEventTypeBackPressed, instance->context);
+            //            }
+        } else if(event->key == InputKeyUp) {
+            instance->callback(SubBruteCustomEventTypeSaveFile, instance->context);
+        } else if(event->key == InputKeyDown) {
+            instance->callback(SubBruteCustomEventTypeTransmitCustom, instance->context);
+        } else if(event->type == InputTypeShort) {
+            if(event->key == InputKeyLeft) {
+                instance->callback(SubBruteCustomEventTypeChangeStepDown, instance->context);
+            } else if(event->key == InputKeyRight) {
+                instance->callback(SubBruteCustomEventTypeChangeStepUp, instance->context);
+            }
+            //            with_view_model(
+            //                instance->view, (SubBruteAttackViewModel * model) {
+            //                    if(event->key == InputKeyLeft) {
+            //                        model->current_step =
+            //                            ((model->current_step - 1) + model->max_value) % model->max_value;
+            //                    } else if(event->key == InputKeyRight) {
+            //                        model->current_step = (model->current_step + 1) % model->max_value;
+            //                    }
+            //                    return true;
+            //                });
+            //            instance->callback(SubBruteCustomEventTypeChangeStep, instance->context);
+        } else if(event->type == InputTypeRepeat) {
+            if(event->key == InputKeyLeft) {
+                instance->callback(SubBruteCustomEventTypeChangeStepDownMore, instance->context);
+            } else if(event->key == InputKeyRight) {
+                instance->callback(SubBruteCustomEventTypeChangeStepUpMore, instance->context);
+            }
+            /*with_view_model(
+                instance->view, (SubBruteAttackViewModel * model) {
+                    if(event->key == InputKeyLeft) {
+                        model->current_step =
+                            ((model->current_step - 100) + model->max_value) % model->max_value;
+                    } else if(event->key == InputKeyRight) {
+                        model->current_step = (model->current_step + 100) % model->max_value;
+                    }
+                    return true;
+                });
+            instance->callback(SubBruteCustomEventTypeChangeStep, instance->context);*/
+        }
+    } else {
+        if((event->type == InputTypeShort || event->type == InputTypeRepeat) &&
+           (event->key == InputKeyOk || event->key == InputKeyBack)) {
+            with_view_model(
+                instance->view, (SubBruteAttackViewModel * model) {
+                    model->is_attacking = false;
+                    icon_animation_stop(model->icon);
+                    icon_animation_start(model->icon);
+                    return true;
+                });
+            instance->callback(SubBruteCustomEventTypeTransmitNotStarted, instance->context);
+        }
+    }
+
+    return true;
+}
+
+SubBruteAttackView* subbrute_attack_view_alloc() {
+    SubBruteAttackView* instance = malloc(sizeof(SubBruteAttackView));
+
+    instance->view = view_alloc();
+    view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(SubBruteAttackViewModel));
+    view_set_context(instance->view, instance);
+
+    with_view_model(
+        instance->view, (SubBruteAttackViewModel * model) {
+            model->icon = icon_animation_alloc(&A_Sub1ghz_14);
+            view_tie_icon_animation(instance->view, model->icon);
+            return false;
+        });
+
+    view_set_draw_callback(instance->view, (ViewDrawCallback)subbrute_attack_view_draw);
+    view_set_input_callback(instance->view, subbrute_attack_view_input);
+    view_set_enter_callback(instance->view, subbrute_attack_view_enter);
+    view_set_exit_callback(instance->view, subbrute_attack_view_exit);
+
+
+    return instance;
+}
+
+void subbrute_attack_view_enter(void* context) {
+    furi_assert(context);
+
+#ifdef FURI_DEBUG
+    FURI_LOG_D(TAG, "subbrute_attack_view_enter");
+#endif
+}
+
+void subbrute_attack_view_free(SubBruteAttackView* instance) {
+    furi_assert(instance);
+
+#ifdef FURI_DEBUG
+    FURI_LOG_D(TAG, "subbrute_attack_view_free");
+#endif
+
+    with_view_model(
+        instance->view, (SubBruteAttackViewModel * model) {
+            icon_animation_free(model->icon);
+            return false;
+        });
+
+    view_free(instance->view);
+    free(instance);
+}
+
+View* subbrute_attack_view_get_view(SubBruteAttackView* instance) {
+    furi_assert(instance);
+    return instance->view;
+}
+
+void subbrute_attack_view_set_current_step(SubBruteAttackView* instance, uint64_t current_step) {
+    furi_assert(instance);
+#ifdef FURI_DEBUG
+    FURI_LOG_D(TAG, "Set step: %d", current_step);
+#endif
+    with_view_model(
+        instance->view, (SubBruteAttackViewModel * model) {
+            model->current_step = current_step;
+            return true;
+        });
+}
+
+uint64_t subbrute_attack_view_get_current_step(SubBruteAttackView* instance) {
+    uint64_t current_step;
+    with_view_model(
+        instance->view, (SubBruteAttackViewModel * model) {
+            current_step = model->current_step;
+            return false;
+        });
+#ifdef FURI_DEBUG
+    FURI_LOG_D(TAG, "Get step: %d", current_step);
+#endif
+    return current_step;
+}
+
+// We need to call init every time, because not every time we calls enter
+// normally, call enter only once
+void subbrute_attack_view_init_values(
+    SubBruteAttackView* instance,
+    uint8_t index,
+    uint64_t max_value,
+    uint64_t current_step,
+    bool is_attacking) {
+#ifdef FURI_DEBUG
+    FURI_LOG_D(
+        TAG, "init, index: %d, max_value: %d, current_step: %d", index, max_value, current_step);
+#endif
+    with_view_model(
+        instance->view, (SubBruteAttackViewModel * model) {
+            model->max_value = max_value;
+            model->index = index;
+            model->current_step = current_step;
+            model->is_attacking = is_attacking;
+            if(is_attacking) {
+                icon_animation_start(model->icon);
+            } else {
+                icon_animation_stop(model->icon);
+            }
+            return true;
+        });
+}
+
+void subbrute_attack_view_exit(void* context) {
+    furi_assert(context);
+    SubBruteAttackView* instance = context;
+#ifdef FURI_DEBUG
+    FURI_LOG_D(TAG, "subbrute_attack_view_exit");
+#endif
+    with_view_model(
+        instance->view, (SubBruteAttackViewModel * model) {
+            icon_animation_stop(model->icon);
+            return false;
+        });
+}
+
+void elements_button_top_left(Canvas* canvas, const char* str) {
+    const Icon* icon = &I_ButtonUp_7x4;
+
+    const uint8_t button_height = 12;
+    const uint8_t vertical_offset = 9; //
+    const uint8_t horizontal_offset = 3;
+    const uint8_t string_width = canvas_string_width(canvas, str);
+    const uint8_t icon_h_offset = 3;
+    const uint8_t icon_width_with_offset = icon->width + icon_h_offset;
+    const uint8_t icon_v_offset = icon->height; //
+    const uint8_t button_width = string_width + horizontal_offset * 2 + icon_width_with_offset + 1;
+
+    const uint8_t x = 0;
+    const uint8_t y = 0;
+
+    canvas_draw_box(canvas, x, y, button_width, button_height);
+#ifdef FURI_DEBUG
+    FURI_LOG_D(
+        TAG, "lbox, x: %d, y: %d, width: %d, height: %d", x, y, button_width, button_height);
+#endif
+    //    canvas_draw_line(canvas, x + button_width + 0, y, x + button_width + 0, y + button_height - 0); //
+    //    canvas_draw_line(canvas, x + button_width + 1, y, x + button_width + 1, y + button_height - 1);
+    //    canvas_draw_line(canvas, x + button_width + 2, y, x + button_width + 2, y + button_height - 2);
+
+    canvas_invert_color(canvas);
+    canvas_draw_icon(canvas, x + horizontal_offset, y + icon_v_offset, icon);
+    canvas_draw_str(
+        canvas, x + horizontal_offset + icon_width_with_offset, y + vertical_offset, str);
+    canvas_invert_color(canvas);
+}
+
+void elements_button_top_right(Canvas* canvas, const char* str) {
+    const Icon* icon = &I_ButtonDown_7x4;
+
+    const uint8_t button_height = 12;
+    const uint8_t vertical_offset = 9;
+    const uint8_t horizontal_offset = 3;
+    const uint8_t string_width = canvas_string_width(canvas, str);
+    const uint8_t icon_h_offset = 3;
+    const uint8_t icon_width_with_offset = icon->width + icon_h_offset;
+    const uint8_t icon_v_offset = icon->height; // + vertical_offset;
+    const uint8_t button_width = string_width + horizontal_offset * 2 + icon_width_with_offset + 1;
+
+    const uint8_t x = canvas_width(canvas);
+    const uint8_t y = 0;
+
+    canvas_draw_box(canvas, x - button_width, y, button_width, button_height);
+#ifdef FURI_DEBUG
+    FURI_LOG_D(
+        TAG,
+        "rbox, x: %d, y: %d, width: %d, height: %d",
+        x - button_width,
+        y,
+        button_width,
+        button_height);
+#endif
+    //    canvas_draw_line(canvas, x - button_width - 1, y, x + button_width - 1, y + button_height - 0);
+    //    canvas_draw_line(canvas, x - button_width - 2, y, x + button_width - 2, y + button_height - 1);
+    //    canvas_draw_line(canvas, x - button_width - 3, y, x + button_width - 3, y + button_height - 2);
+
+    canvas_invert_color(canvas);
+    canvas_draw_str(canvas, x - button_width + horizontal_offset, y + vertical_offset, str);
+    canvas_draw_icon(canvas, x - horizontal_offset - icon->width, y + icon_v_offset, icon);
+    canvas_invert_color(canvas);
+}
+
+void subbrute_attack_view_draw(Canvas* canvas, void* context) {
+    furi_assert(context);
+    SubBruteAttackViewModel* model = (SubBruteAttackViewModel*)context;
+    char buffer[26];
+
+    const char* attack_name = NULL;
+    attack_name = subbrute_get_menu_name(model->index);
+    // Title
+    if(model->is_attacking) {
+        canvas_set_color(canvas, ColorBlack);
+        canvas_set_font(canvas, FontSecondary);
+        canvas_draw_str_aligned(canvas, 64, 2, AlignCenter, AlignTop, attack_name);
+    }
+    // Value
+    canvas_set_font(canvas, FontBigNumbers);
+    snprintf(buffer, sizeof(buffer), "%04d/%04d", (int)model->current_step, (int)model->max_value);
+    canvas_draw_str_aligned(canvas, 64, 17, AlignCenter, AlignTop, buffer);
+    canvas_set_font(canvas, FontSecondary);
+
+    if(!model->is_attacking) {
+        canvas_set_color(canvas, ColorBlack);
+        canvas_set_font(canvas, FontSecondary);
+        canvas_draw_str_aligned(canvas, 64, 44, AlignCenter, AlignBottom, attack_name);
+
+        elements_button_left(canvas, "-1");
+        elements_button_right(canvas, "+1");
+        elements_button_center(canvas, "Start");
+        elements_button_top_left(canvas, "Save");
+        elements_button_top_right(canvas, "Resend");
+    } else {
+        // canvas_draw_icon_animation
+        const uint8_t icon_h_offset = 0;
+        const uint8_t icon_width_with_offset = model->icon->icon->width + icon_h_offset;
+        const uint8_t icon_v_offset = model->icon->icon->height; // + vertical_offset;
+        const uint8_t x = canvas_width(canvas);
+        const uint8_t y = canvas_height(canvas);
+        canvas_draw_icon_animation(
+            canvas, x - icon_width_with_offset, y - icon_v_offset, model->icon);
+        // Progress bar
+        // Resolution: 128x64 px
+        float progress_value = (float)model->current_step / model->max_value;
+        elements_progress_bar(canvas, 8, 37, 110, progress_value > 1 ? 1 : progress_value);
+
+        elements_button_center(canvas, "Stop");
+    }
+}

+ 28 - 0
applications/plugins/subbrute/views/subbrute_attack_view.h

@@ -0,0 +1,28 @@
+#pragma once
+
+#include <gui/view.h>
+#include "assets_icons.h"
+#include <input/input.h>
+#include <gui/elements.h>
+#include <gui/icon.h>
+#include <subghz/types.h>
+#include "../subbrute_custom_event.h"
+
+typedef void (*SubBruteAttackViewCallback)(SubBruteCustomEvent event, void* context);
+typedef struct SubBruteAttackView SubBruteAttackView;
+
+void subbrute_attack_view_set_callback(
+    SubBruteAttackView* instance,
+    SubBruteAttackViewCallback callback,
+    void* context);
+SubBruteAttackView* subbrute_attack_view_alloc();
+void subbrute_attack_view_free(SubBruteAttackView* instance);
+View* subbrute_attack_view_get_view(SubBruteAttackView* instance);
+void subbrute_attack_view_set_current_step(SubBruteAttackView* instance, uint64_t current_step);
+uint64_t subbrute_attack_view_get_current_step(SubBruteAttackView* instance);
+void subbrute_attack_view_init_values(
+    SubBruteAttackView* instance,
+    uint8_t index,
+    uint64_t max_value,
+    uint64_t current_step,
+    bool is_attacking);

+ 381 - 0
applications/plugins/subbrute/views/subbrute_main_view.c

@@ -0,0 +1,381 @@
+#include "subbrute_main_view.h"
+#include "../subbrute_i.h"
+
+#include <input/input.h>
+#include <gui/elements.h>
+#include "assets_icons.h"
+#include <gui/icon.h>
+
+#define STATUS_BAR_Y_SHIFT 14
+#define TAG "SubBruteMainView"
+
+struct SubBruteMainView {
+    View* view;
+    SubBruteMainViewCallback callback;
+    void* context;
+};
+
+typedef struct {
+    uint8_t index;
+    uint8_t window_position;
+    bool is_select_byte;
+    const char* key_field;
+} SubBruteMainViewModel;
+
+void subbrute_main_view_set_callback(
+    SubBruteMainView* instance,
+    SubBruteMainViewCallback callback,
+    void* context) {
+    furi_assert(instance);
+    furi_assert(callback);
+
+    instance->callback = callback;
+    instance->context = context;
+}
+
+void center_displayed_key(string_t result, const char* key_cstr, uint8_t index) {
+    uint8_t str_index = (index * 3);
+
+    char display_menu[] = {
+        'X', 'X', ' ', 'X', 'X', ' ', '<', 'X', 'X', '>', ' ', 'X', 'X', ' ', 'X', 'X', '\0'};
+
+    if(key_cstr != NULL) {
+        if(index > 1) {
+            display_menu[0] = key_cstr[str_index - 6];
+            display_menu[1] = key_cstr[str_index - 5];
+        } else {
+            display_menu[0] = ' ';
+            display_menu[1] = ' ';
+        }
+
+        if(index > 0) {
+            display_menu[3] = key_cstr[str_index - 3];
+            display_menu[4] = key_cstr[str_index - 2];
+        } else {
+            display_menu[3] = ' ';
+            display_menu[4] = ' ';
+        }
+
+        display_menu[7] = key_cstr[str_index];
+        display_menu[8] = key_cstr[str_index + 1];
+
+        if((str_index + 4) <= (uint8_t)strlen(key_cstr)) {
+            display_menu[11] = key_cstr[str_index + 3];
+            display_menu[12] = key_cstr[str_index + 4];
+        } else {
+            display_menu[11] = ' ';
+            display_menu[12] = ' ';
+        }
+
+        if((str_index + 8) <= (uint8_t)strlen(key_cstr)) {
+            display_menu[14] = key_cstr[str_index + 6];
+            display_menu[15] = key_cstr[str_index + 7];
+        } else {
+            display_menu[14] = ' ';
+            display_menu[15] = ' ';
+        }
+    }
+    string_init_set_str(result, display_menu);
+}
+
+void subbrute_main_view_draw(Canvas* canvas, SubBruteMainViewModel* model) {
+    SubBruteMainViewModel* m = model;
+
+    // Title
+    canvas_set_font(canvas, FontPrimary);
+    canvas_draw_box(canvas, 0, 0, canvas_width(canvas), STATUS_BAR_Y_SHIFT);
+    canvas_invert_color(canvas);
+    canvas_draw_str_aligned(canvas, 64, 2, AlignCenter, AlignTop, "Sub-GHz Bruteforcer");
+    canvas_invert_color(canvas);
+
+    if(m->is_select_byte) {
+#ifdef FURI_DEBUG
+        FURI_LOG_D(TAG, "key_field: %s", m->key_field);
+#endif
+        char msg_index[18];
+        snprintf(msg_index, sizeof(msg_index), "Field index : %d", m->index);
+        canvas_draw_str_aligned(canvas, 64, 26, AlignCenter, AlignTop, msg_index);
+
+        string_t menu_items;
+        string_init(menu_items);
+
+        center_displayed_key(menu_items, m->key_field, m->index);
+        canvas_set_font(canvas, FontSecondary);
+        canvas_draw_str_aligned(
+            canvas, 64, 40, AlignCenter, AlignTop, string_get_cstr(menu_items));
+
+        elements_button_center(canvas, "Select");
+        elements_button_left(canvas, "<");
+        elements_button_right(canvas, ">");
+
+        string_reset(menu_items);
+    } else {
+        // Menu
+        canvas_set_color(canvas, ColorBlack);
+        canvas_set_font(canvas, FontSecondary);
+        uint8_t items_on_screen = 3;
+        const uint8_t item_height = 16;
+
+#ifdef FURI_DEBUG
+        FURI_LOG_D(TAG, "window_position: %d, index: %d", model->window_position, m->index);
+#endif
+        for(uint8_t position = 0; position < SubBruteAttackTotalCount; ++position) {
+            uint8_t item_position = position - model->window_position;
+
+            if(item_position < items_on_screen) {
+                const char* str = subbrute_get_menu_name(position);
+                if(m->index == position) {
+                    canvas_draw_str_aligned(
+                        canvas,
+                        64,
+                        9 + (item_position * item_height) + STATUS_BAR_Y_SHIFT,
+                        AlignCenter,
+                        AlignCenter,
+                        str);
+                    elements_frame(
+                        canvas, 1, 1 + (item_position * item_height) + STATUS_BAR_Y_SHIFT, 124, 15);
+                } else {
+                    canvas_draw_str_aligned(
+                        canvas,
+                        64,
+                        9 + (item_position * item_height) + STATUS_BAR_Y_SHIFT,
+                        AlignCenter,
+                        AlignCenter,
+                        str);
+                }
+            }
+        }
+
+        elements_scrollbar_pos(
+            canvas,
+            canvas_width(canvas),
+            STATUS_BAR_Y_SHIFT + 2,
+            canvas_height(canvas) - STATUS_BAR_Y_SHIFT,
+            m->index,
+            SubBruteAttackTotalCount);
+    }
+}
+
+bool subbrute_main_view_input(InputEvent* event, void* context) {
+    furi_assert(event);
+    furi_assert(context);
+#ifdef FURI_DEBUG
+    FURI_LOG_D(TAG, "InputKey: %d", event->key);
+#endif
+
+    if(event->key == InputKeyBack && event->type == InputTypeShort) {
+        return false;
+    }
+
+    SubBruteMainView* instance = context;
+    const uint8_t min_value = 0;
+    const uint8_t correct_total = SubBruteAttackTotalCount - 1;
+    uint8_t index = 0;
+    bool is_select_byte = false;
+    with_view_model(
+        instance->view, (SubBruteMainViewModel * model) {
+            is_select_byte = model->is_select_byte;
+            return false;
+        });
+
+    bool consumed = false;
+    if(!is_select_byte) {
+        if((event->type == InputTypeShort) || (event->type == InputTypeRepeat)) {
+            with_view_model(
+                instance->view, (SubBruteMainViewModel * model) {
+                    bool ret = false;
+                    uint8_t items_on_screen = 3;
+                    if(event->key == InputKeyUp) {
+                        if(model->index == min_value) {
+                            model->index = correct_total;
+                        } else {
+                            model->index = CLAMP(model->index - 1, correct_total, min_value);
+                        }
+                        ret = true;
+                        consumed = true;
+                    } else if(event->key == InputKeyDown) {
+                        if(model->index == correct_total) {
+                            model->index = min_value;
+                        } else {
+                            model->index = CLAMP(model->index + 1, correct_total, min_value);
+                        }
+                        ret = true;
+                        consumed = true;
+                    }
+                    if(ret) {
+                        model->window_position = model->index;
+                        if(model->window_position > 0) {
+                            model->window_position -= 1;
+                        }
+
+                        if(SubBruteAttackTotalCount <= items_on_screen) {
+                            model->window_position = 0;
+                        } else {
+                            if(model->window_position >=
+                               (SubBruteAttackTotalCount - items_on_screen)) {
+                                model->window_position =
+                                    (SubBruteAttackTotalCount - items_on_screen);
+                            }
+                        }
+                    }
+                    index = model->index;
+                    return ret;
+                });
+        }
+
+#ifdef FURI_DEBUG
+        with_view_model(
+            instance->view, (SubBruteMainViewModel * model) {
+                index = model->index;
+                return false;
+            });
+        FURI_LOG_I(TAG, "Index: %d", index);
+#endif
+
+        if(event->key == InputKeyOk && event->type == InputTypeShort) {
+            if(index == SubBruteAttackLoadFile) {
+                instance->callback(SubBruteCustomEventTypeLoadFile, instance->context);
+            } else {
+                instance->callback(SubBruteCustomEventTypeMenuSelected, instance->context);
+            }
+            consumed = true;
+        }
+    } else {
+        if((event->type == InputTypeShort) || (event->type == InputTypeRepeat)) {
+            with_view_model(
+                instance->view, (SubBruteMainViewModel * model) {
+                    if(event->key == InputKeyLeft) {
+                        if(model->index > 0) {
+                            model->index--;
+                        }
+                    } else if(event->key == InputKeyRight) {
+                        if(model->index < 7) {
+                            model->index++;
+                        }
+                    }
+
+                    index = model->index;
+                    return true;
+                });
+        }
+
+#ifdef FURI_DEBUG
+        with_view_model(
+            instance->view, (SubBruteMainViewModel * model) {
+                index = model->index;
+                return false;
+            });
+        FURI_LOG_I(TAG, "Index: %d", index);
+#endif
+
+        if(event->key == InputKeyOk && event->type == InputTypeShort) {
+            instance->callback(SubBruteCustomEventTypeIndexSelected, instance->context);
+            consumed = true;
+        }
+    }
+
+    return consumed;
+}
+
+void subbrute_main_view_enter(void* context) {
+    furi_assert(context);
+
+#ifdef FURI_DEBUG
+    FURI_LOG_D(TAG, "subbrute_main_view_enter");
+#endif
+}
+
+void subbrute_main_view_exit(void* context) {
+    furi_assert(context);
+
+#ifdef FURI_DEBUG
+    FURI_LOG_D(TAG, "subbrute_main_view_exit");
+#endif
+}
+
+SubBruteMainView* subbrute_main_view_alloc() {
+    SubBruteMainView* instance = malloc(sizeof(SubBruteMainView));
+    instance->view = view_alloc();
+    view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(SubBruteMainViewModel));
+    view_set_context(instance->view, instance);
+    view_set_draw_callback(instance->view, (ViewDrawCallback)subbrute_main_view_draw);
+    view_set_input_callback(instance->view, subbrute_main_view_input);
+    view_set_enter_callback(instance->view, subbrute_main_view_enter);
+    view_set_exit_callback(instance->view, subbrute_main_view_exit);
+
+    with_view_model(
+        instance->view, (SubBruteMainViewModel * model) {
+            model->index = 0;
+            model->window_position = 0;
+            model->key_field = NULL;
+            model->is_select_byte = false;
+            return true;
+        });
+
+    return instance;
+}
+
+void subbrute_main_view_free(SubBruteMainView* instance) {
+    furi_assert(instance);
+
+    view_free(instance->view);
+    free(instance);
+}
+
+View* subbrute_main_view_get_view(SubBruteMainView* instance) {
+    furi_assert(instance);
+    return instance->view;
+}
+
+void subbrute_main_view_set_index(
+    SubBruteMainView* instance,
+    uint8_t idx,
+    bool is_select_byte,
+    const char* key_field) {
+    furi_assert(instance);
+    furi_assert(idx < SubBruteAttackTotalCount);
+#ifdef FURI_DEBUG
+    FURI_LOG_I(TAG, "Set index: %d", idx);
+#endif
+    with_view_model(
+        instance->view, (SubBruteMainViewModel * model) {
+            model->is_select_byte = is_select_byte;
+            model->key_field = key_field;
+            model->index = idx;
+            model->window_position = idx;
+
+            if(!is_select_byte) {
+                uint8_t items_on_screen = 3;
+
+                if(model->window_position > 0) {
+                    model->window_position -= 1;
+                }
+
+                if(SubBruteAttackTotalCount <= items_on_screen) {
+                    model->window_position = 0;
+                } else {
+                    if(model->window_position >= (SubBruteAttackTotalCount - items_on_screen)) {
+                        model->window_position = (SubBruteAttackTotalCount - items_on_screen);
+                    }
+                }
+            }
+            return true;
+        });
+}
+
+SubBruteAttacks subbrute_main_view_get_index(SubBruteMainView* instance) {
+    furi_assert(instance);
+
+    uint8_t idx = 0;
+    with_view_model(
+        instance->view, (SubBruteMainViewModel * model) {
+            idx = model->index;
+            return false;
+        });
+
+#ifdef FURI_DEBUG
+    FURI_LOG_D(TAG, "Get index: %d", idx);
+#endif
+
+    return idx;
+}

+ 30 - 0
applications/plugins/subbrute/views/subbrute_main_view.h

@@ -0,0 +1,30 @@
+#pragma once
+
+#include "../subbrute_custom_event.h"
+#include <gui/view.h>
+#include "assets_icons.h"
+#include <input/input.h>
+#include <gui/elements.h>
+#include <gui/icon.h>
+
+typedef void (*SubBruteMainViewCallback)(SubBruteCustomEvent event, void* context);
+typedef struct SubBruteMainView SubBruteMainView;
+
+void subbrute_main_view_set_callback(
+    SubBruteMainView* instance,
+    SubBruteMainViewCallback callback,
+    void* context);
+
+SubBruteMainView* subbrute_main_view_alloc();
+void subbrute_main_view_free(SubBruteMainView* instance);
+View* subbrute_main_view_get_view(SubBruteMainView* instance);
+void subbrute_main_view_set_index(
+    SubBruteMainView* instance,
+    uint8_t idx,
+    bool is_select_byte,
+    const char* key_field);
+uint8_t subbrute_main_view_get_index(SubBruteMainView* instance);
+void subbrute_attack_view_enter(void* context);
+void subbrute_attack_view_exit(void* context);
+bool subbrute_attack_view_input(InputEvent* event, void* context);
+void subbrute_attack_view_draw(Canvas* canvas, void* context);