Explorar el Código

Update v3.6

- Ability to set time delay between group of repeated signals (by @gid9798)
- Ability to set repeats up to x14 (was x9 max)
MX hace 2 años
padre
commit
d024183624

+ 2 - 0
.gitignore

@@ -1 +1,3 @@
 /.idea/.idea.subbrute.dir/.idea/workspace.xml
 /.idea/.idea.subbrute.dir/.idea/workspace.xml
+
+.DS_Store

+ 21 - 21
LICENSE

@@ -1,21 +1,21 @@
-MIT License
-
-Copyright (c) 2022 Der Skythe
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+MIT License
+
+Copyright (c) 2022 Der Skythe
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 76 - 76
README.md

@@ -1,76 +1,76 @@
-# SubGHz Bruteforcer Plugin for Flipper Zero
-
-SubGhz Bruteforcer from [Unleashed Firmware](https://github.com/DarkFlippers/unleashed-firmware)
-
-### Disclaimer
-
-This software is for experimental purposes only and is not meant for any illegal activity/purposes.
-We do not condone illegal activity and strongly encourage keeping transmissions to legal/valid uses allowed by law. 
-
-### Supported Protocols:
-
-#### CAME
-
-- CAME 12bit 303MHz
-- CAME 12bit 307MHz
-- CAME 12bit 315MHz
-- CAME 12bit 433MHz
-- CAME 12bit 868MHz
-
-#### NICE
-
-- NICE 12bit 433MHz
-- NICE 12bit 868MHz
-
-#### Ansonic
-
-- Ansonic 12bit 433.075MHz
-- Ansonic 12bit 433.920MHz
-- Ansonic 12bit 434.075MHz
-
-#### Holtek
-
-- Holtek HT12X 12bit FM 433.920MHz (TE: 204us)
-- Holtek HT12X 12bit AM 433.920MHz (TE: 433us)
-- Holtek HT12X 12bit AM 315MHz (TE: 433us)
-- Holtek HT12X 12bit AM 868MHz (TE: 433us)
-- Holtek HT12X 12bit AM 915MHz (TE: 433us)
-#### Chamberlain
-
-- Chamberlain 9bit 300MHz
-- Chamberlain 9bit 315MHz
-- Chamberlain 9bit 390MHz
-- Chamberlain 9bit 433MHz
-- Chamberlain 8bit 300MHz
-- Chamberlain 8bit 315MHz
-- Chamberlain 8bit 390MHz
-- Chamberlain 7bit 300MHz
-- Chamberlain 7bit 315MHz
-- Chamberlain 7bit 390MHz
-
-#### Linear
-
-- Linear 10bit 300MHz
-- Linear 10bit 310MHz
-- Linear Delta 3 8bit 310MHz
-
-#### UNILARM
-
-- UNILARM 25bit 330MHz (TE: 209us) (only dip switch combinations, not full 25bit bruteforce)
-- UNILARM 25bit 433MHz (TE: 209us) (only dip switch combinations, not full 25bit bruteforce)
-
-#### SMC5326
-
-- SMC5326 25bit 330MHz (TE: 320us) (only dip switch combinations, not full 25bit bruteforce)
-- SMC5326 25bit 433MHz (TE: 320us) (only dip switch combinations, not full 25bit bruteforce)
-
-#### PT2260
-
-- PT2260 24bit 315MHz (TE: 286us) (only for 8 dip switch remote, not full 24bit bruteforce)
-- PT2260 24bit 330MHz (TE: 286us) (only for 8 dip switch remote, not full 24bit bruteforce)
-- PT2260 24bit 390MHz (TE: 286us) (only for 8 dip switch remote, not full 24bit bruteforce)
-- PT2260 24bit 433MHz (TE: 286us) (only for 8 dip switch remote, not full 24bit bruteforce)
-
-#### Additional
-
-- BF Existing dump works for most other static protocols supported by Flipper Zero
+# SubGHz Bruteforcer Plugin for Flipper Zero
+
+SubGhz Bruteforcer from [Unleashed Firmware](https://github.com/DarkFlippers/unleashed-firmware)
+
+### Disclaimer
+
+This software is for experimental purposes only and is not meant for any illegal activity/purposes.
+We do not condone illegal activity and strongly encourage keeping transmissions to legal/valid uses allowed by law. 
+
+### Supported Protocols:
+
+#### CAME
+
+- CAME 12bit 303MHz
+- CAME 12bit 307MHz
+- CAME 12bit 315MHz
+- CAME 12bit 433MHz
+- CAME 12bit 868MHz
+
+#### NICE
+
+- NICE 12bit 433MHz
+- NICE 12bit 868MHz
+
+#### Ansonic
+
+- Ansonic 12bit 433.075MHz
+- Ansonic 12bit 433.920MHz
+- Ansonic 12bit 434.075MHz
+
+#### Holtek
+
+- Holtek HT12X 12bit FM 433.920MHz (TE: 204us)
+- Holtek HT12X 12bit AM 433.920MHz (TE: 433us)
+- Holtek HT12X 12bit AM 315MHz (TE: 433us)
+- Holtek HT12X 12bit AM 868MHz (TE: 433us)
+- Holtek HT12X 12bit AM 915MHz (TE: 433us)
+#### Chamberlain
+
+- Chamberlain 9bit 300MHz
+- Chamberlain 9bit 315MHz
+- Chamberlain 9bit 390MHz
+- Chamberlain 9bit 433MHz
+- Chamberlain 8bit 300MHz
+- Chamberlain 8bit 315MHz
+- Chamberlain 8bit 390MHz
+- Chamberlain 7bit 300MHz
+- Chamberlain 7bit 315MHz
+- Chamberlain 7bit 390MHz
+
+#### Linear
+
+- Linear 10bit 300MHz
+- Linear 10bit 310MHz
+- Linear Delta 3 8bit 310MHz
+
+#### UNILARM
+
+- UNILARM 25bit 330MHz (TE: 209us) (only dip switch combinations, not full 25bit bruteforce)
+- UNILARM 25bit 433MHz (TE: 209us) (only dip switch combinations, not full 25bit bruteforce)
+
+#### SMC5326
+
+- SMC5326 25bit 330MHz (TE: 320us) (only dip switch combinations, not full 25bit bruteforce)
+- SMC5326 25bit 433MHz (TE: 320us) (only dip switch combinations, not full 25bit bruteforce)
+
+#### PT2260
+
+- PT2260 24bit 315MHz (TE: 286us) (only for 8 dip switch remote, not full 24bit bruteforce)
+- PT2260 24bit 330MHz (TE: 286us) (only for 8 dip switch remote, not full 24bit bruteforce)
+- PT2260 24bit 390MHz (TE: 286us) (only for 8 dip switch remote, not full 24bit bruteforce)
+- PT2260 24bit 433MHz (TE: 286us) (only for 8 dip switch remote, not full 24bit bruteforce)
+
+#### Additional
+
+- BF Existing dump works for most other static protocols supported by Flipper Zero

+ 12 - 12
application.fam

@@ -1,12 +1,12 @@
-App(
-    appid="subghz_bruteforcer",
-    name="Sub-GHz Bruteforcer",
-    apptype=FlipperAppType.EXTERNAL,
-    entry_point="subbrute_app",
-    requires=["gui","dialogs"],
-    stack_size=2 * 1024,
-    order=11,
-    fap_icon="images/subbrute_10px.png",
-    fap_category="Sub-GHz",
-    fap_icon_assets="images",
-)
+App(
+    appid="subghz_bruteforcer",
+    name="Sub-GHz Bruteforcer",
+    apptype=FlipperAppType.EXTERNAL,
+    entry_point="subbrute_app",
+    requires=["gui","dialogs"],
+    stack_size=2 * 1024,
+    order=11,
+    fap_icon="images/subbrute_10px.png",
+    fap_category="Sub-GHz",
+    fap_icon_assets="images",
+)

+ 58 - 58
helpers/gui_top_buttons.c

@@ -1,59 +1,59 @@
-#include "gui_top_buttons.h"
-
-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 = 3;
-    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_get_width(icon) + icon_h_offset;
-    const uint8_t icon_v_offset = icon_get_height(icon) + vertical_offset;
-    const uint8_t button_width = string_width + horizontal_offset * 2 + icon_width_with_offset;
-
-    const uint8_t x = 0;
-    const uint8_t y = 0 + button_height;
-
-    uint8_t line_x = x + button_width;
-    uint8_t line_y = y - button_height;
-    canvas_draw_box(canvas, x, line_y, button_width, button_height);
-    canvas_draw_line(canvas, line_x + 0, line_y, line_x + 0, y - 1);
-    canvas_draw_line(canvas, line_x + 1, line_y, line_x + 1, y - 2);
-    canvas_draw_line(canvas, line_x + 2, line_y, line_x + 2, y - 3);
-
-    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 = 3;
-    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_get_width(icon) + icon_h_offset;
-    const uint8_t icon_v_offset = icon_get_height(icon) + vertical_offset + 1;
-    const uint8_t button_width = string_width + horizontal_offset * 2 + icon_width_with_offset;
-
-    const uint8_t x = canvas_width(canvas);
-    const uint8_t y = 0 + button_height;
-
-    uint8_t line_x = x - button_width;
-    uint8_t line_y = y - button_height;
-    canvas_draw_box(canvas, line_x, line_y, button_width, button_height);
-    canvas_draw_line(canvas, line_x - 1, line_y, line_x - 1, y - 1);
-    canvas_draw_line(canvas, line_x - 2, line_y, line_x - 2, y - 2);
-    canvas_draw_line(canvas, line_x - 3, line_y, line_x - 3, y - 3);
-
-    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_get_width(icon), y - icon_v_offset, icon);
-    canvas_invert_color(canvas);
+#include "gui_top_buttons.h"
+
+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 = 3;
+    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_get_width(icon) + icon_h_offset;
+    const uint8_t icon_v_offset = icon_get_height(icon) + vertical_offset;
+    const uint8_t button_width = string_width + horizontal_offset * 2 + icon_width_with_offset;
+
+    const uint8_t x = 0;
+    const uint8_t y = 0 + button_height;
+
+    uint8_t line_x = x + button_width;
+    uint8_t line_y = y - button_height;
+    canvas_draw_box(canvas, x, line_y, button_width, button_height);
+    canvas_draw_line(canvas, line_x + 0, line_y, line_x + 0, y - 1);
+    canvas_draw_line(canvas, line_x + 1, line_y, line_x + 1, y - 2);
+    canvas_draw_line(canvas, line_x + 2, line_y, line_x + 2, y - 3);
+
+    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 = 3;
+    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_get_width(icon) + icon_h_offset;
+    const uint8_t icon_v_offset = icon_get_height(icon) + vertical_offset + 1;
+    const uint8_t button_width = string_width + horizontal_offset * 2 + icon_width_with_offset;
+
+    const uint8_t x = canvas_width(canvas);
+    const uint8_t y = 0 + button_height;
+
+    uint8_t line_x = x - button_width;
+    uint8_t line_y = y - button_height;
+    canvas_draw_box(canvas, line_x, line_y, button_width, button_height);
+    canvas_draw_line(canvas, line_x - 1, line_y, line_x - 1, y - 1);
+    canvas_draw_line(canvas, line_x - 2, line_y, line_x - 2, y - 2);
+    canvas_draw_line(canvas, line_x - 3, line_y, line_x - 3, y - 3);
+
+    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_get_width(icon), y - icon_v_offset, icon);
+    canvas_invert_color(canvas);
 }
 }

+ 20 - 20
helpers/gui_top_buttons.h

@@ -1,21 +1,21 @@
-#pragma once
-
-#include <input/input.h>
-#include <gui/elements.h>
-#include <gui/icon.h>
-#include <gui/icon_animation.h>
-#include <assets_icons.h>
-
-/**
- * Thanks to the author of metronome
- * @param canvas
- * @param str
- */
-void elements_button_top_left(Canvas* canvas, const char* str);
-
-/**
- * Thanks to the author of metronome
- * @param canvas
- * @param str
- */
+#pragma once
+
+#include <input/input.h>
+#include <gui/elements.h>
+#include <gui/icon.h>
+#include <gui/icon_animation.h>
+#include <assets_icons.h>
+
+/**
+ * Thanks to the author of metronome
+ * @param canvas
+ * @param str
+ */
+void elements_button_top_left(Canvas* canvas, const char* str);
+
+/**
+ * Thanks to the author of metronome
+ * @param canvas
+ * @param str
+ */
 void elements_button_top_right(Canvas* canvas, const char* str);
 void elements_button_top_right(Canvas* canvas, const char* str);

+ 459 - 437
helpers/subbrute_worker.c

@@ -1,437 +1,459 @@
-#include "subbrute_worker_private.h"
-#include <string.h>
-#include <toolbox/stream/stream.h>
-#include <flipper_format.h>
-#include <flipper_format_i.h>
-#include <lib/subghz/protocols/protocol_items.h>
-
-#define TAG "SubBruteWorker"
-#define SUBBRUTE_TX_TIMEOUT 6
-#define SUBBRUTE_MANUAL_TRANSMIT_INTERVAL 250
-
-SubBruteWorker* subbrute_worker_alloc() {
-    SubBruteWorker* instance = malloc(sizeof(SubBruteWorker));
-
-    instance->state = SubBruteWorkerStateIDLE;
-    instance->step = 0;
-    instance->worker_running = false;
-    instance->initiated = false;
-    instance->last_time_tx_data = 0;
-    instance->load_index = 0;
-
-    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->context = NULL;
-    instance->callback = NULL;
-
-    instance->decoder_result = NULL;
-    instance->transmitter = NULL;
-    instance->environment = subghz_environment_alloc();
-    subghz_environment_set_protocol_registry(
-        instance->environment, (void*)&subghz_protocol_registry);
-
-    instance->transmit_mode = false;
-
-    return instance;
-}
-
-void subbrute_worker_free(SubBruteWorker* instance) {
-    furi_assert(instance);
-
-    // I don't know how to free this
-    instance->decoder_result = NULL;
-
-    if(instance->transmitter != NULL) {
-        subghz_transmitter_free(instance->transmitter);
-        instance->transmitter = NULL;
-    }
-
-    subghz_environment_free(instance->environment);
-    instance->environment = NULL;
-
-    furi_thread_free(instance->thread);
-
-    free(instance);
-}
-
-uint64_t subbrute_worker_get_step(SubBruteWorker* instance) {
-    return instance->step;
-}
-
-bool subbrute_worker_set_step(SubBruteWorker* instance, uint64_t step) {
-    furi_assert(instance);
-    if(!subbrute_worker_can_manual_transmit(instance)) {
-        FURI_LOG_W(TAG, "Cannot set step during running mode");
-        return false;
-    }
-
-    instance->step = step;
-
-    return true;
-}
-
-bool subbrute_worker_init_default_attack(
-    SubBruteWorker* instance,
-    SubBruteAttacks attack_type,
-    uint64_t step,
-    const SubBruteProtocol* protocol,
-    uint8_t extra_repeats) {
-    furi_assert(instance);
-
-    if(instance->worker_running) {
-        FURI_LOG_W(TAG, "Init Worker when it's running");
-        subbrute_worker_stop(instance);
-    }
-
-    instance->attack = attack_type;
-    instance->frequency = protocol->frequency;
-    instance->preset = protocol->preset;
-    instance->file = protocol->file;
-    instance->step = step;
-    instance->bits = protocol->bits;
-    instance->te = protocol->te;
-    instance->repeat = protocol->repeat + extra_repeats;
-    instance->load_index = 0;
-    instance->file_key = 0;
-    instance->two_bytes = false;
-
-    instance->max_value =
-        subbrute_protocol_calc_max_value(instance->attack, instance->bits, instance->two_bytes);
-
-    instance->initiated = true;
-    instance->state = SubBruteWorkerStateReady;
-    subbrute_worker_send_callback(instance);
-#ifdef FURI_DEBUG
-    FURI_LOG_I(
-        TAG,
-        "subbrute_worker_init_default_attack: %s, bits: %d, preset: %s, file: %s, te: %ld, repeat: %d, max_value: %lld",
-        subbrute_protocol_name(instance->attack),
-        instance->bits,
-        subbrute_protocol_preset(instance->preset),
-        subbrute_protocol_file(instance->file),
-        instance->te,
-        instance->repeat,
-        instance->max_value);
-#endif
-
-    return true;
-}
-
-bool subbrute_worker_init_file_attack(
-    SubBruteWorker* instance,
-    uint64_t step,
-    uint8_t load_index,
-    uint64_t file_key,
-    SubBruteProtocol* protocol,
-    uint8_t extra_repeats,
-    bool two_bytes) {
-    furi_assert(instance);
-
-    if(instance->worker_running) {
-        FURI_LOG_W(TAG, "Init Worker when it's running");
-        subbrute_worker_stop(instance);
-    }
-
-    instance->attack = SubBruteAttackLoadFile;
-    instance->frequency = protocol->frequency;
-    instance->preset = protocol->preset;
-    instance->file = protocol->file;
-    instance->step = step;
-    instance->bits = protocol->bits;
-    instance->te = protocol->te;
-    instance->load_index = load_index;
-    instance->repeat = protocol->repeat + extra_repeats;
-    instance->file_key = file_key;
-    instance->two_bytes = two_bytes;
-
-    instance->max_value =
-        subbrute_protocol_calc_max_value(instance->attack, instance->bits, instance->two_bytes);
-
-    instance->initiated = true;
-    instance->state = SubBruteWorkerStateReady;
-    subbrute_worker_send_callback(instance);
-#ifdef FURI_DEBUG
-    FURI_LOG_I(
-        TAG,
-        "subbrute_worker_init_file_attack: %s, bits: %d, preset: %s, file: %s, te: %ld, repeat: %d, max_value: %lld, key: %llX",
-        subbrute_protocol_name(instance->attack),
-        instance->bits,
-        subbrute_protocol_preset(instance->preset),
-        subbrute_protocol_file(instance->file),
-        instance->te,
-        instance->repeat,
-        instance->max_value,
-        instance->file_key);
-#endif
-
-    return true;
-}
-
-bool subbrute_worker_start(SubBruteWorker* instance) {
-    furi_assert(instance);
-
-    if(!instance->initiated) {
-        FURI_LOG_W(TAG, "Worker not init!");
-        return false;
-    }
-
-    if(instance->worker_running) {
-        FURI_LOG_W(TAG, "Worker is already running!");
-        return false;
-    }
-    if(instance->state != SubBruteWorkerStateReady &&
-       instance->state != SubBruteWorkerStateFinished) {
-        FURI_LOG_W(TAG, "Worker cannot start, invalid device state: %d", instance->state);
-        return false;
-    }
-
-    instance->worker_running = true;
-    furi_thread_start(instance->thread);
-
-    return true;
-}
-
-void subbrute_worker_stop(SubBruteWorker* instance) {
-    furi_assert(instance);
-
-    if(!instance->worker_running) {
-        return;
-    }
-
-    instance->worker_running = false;
-    furi_thread_join(instance->thread);
-
-    furi_hal_subghz_set_path(FuriHalSubGhzPathIsolate);
-    furi_hal_subghz_sleep();
-}
-
-bool subbrute_worker_transmit_current_key(SubBruteWorker* instance, uint64_t step) {
-    furi_assert(instance);
-
-    if(!instance->initiated) {
-        FURI_LOG_W(TAG, "Worker not init!");
-        return false;
-    }
-    if(instance->worker_running) {
-        FURI_LOG_W(TAG, "Worker in running state!");
-        return false;
-    }
-    if(instance->state != SubBruteWorkerStateReady &&
-       instance->state != SubBruteWorkerStateFinished) {
-        FURI_LOG_W(TAG, "Invalid state for running worker! State: %d", instance->state);
-        return false;
-    }
-
-    uint32_t ticks = furi_get_tick();
-    if((ticks - instance->last_time_tx_data) < SUBBRUTE_MANUAL_TRANSMIT_INTERVAL) {
-#if FURI_DEBUG
-        FURI_LOG_D(TAG, "Need to wait, current: %ld", ticks - instance->last_time_tx_data);
-#endif
-        return false;
-    }
-
-    instance->last_time_tx_data = ticks;
-    instance->step = step;
-
-    bool result;
-    instance->protocol_name = subbrute_protocol_file(instance->file);
-    FlipperFormat* flipper_format = flipper_format_string_alloc();
-    Stream* stream = flipper_format_get_raw_stream(flipper_format);
-
-    stream_clean(stream);
-
-    if(instance->attack == SubBruteAttackLoadFile) {
-        subbrute_protocol_file_payload(
-            stream,
-            step,
-            instance->bits,
-            instance->te,
-            instance->repeat,
-            instance->load_index,
-            instance->file_key,
-            instance->two_bytes);
-    } else {
-        subbrute_protocol_default_payload(
-            stream, instance->file, step, instance->bits, instance->te, instance->repeat);
-    }
-
-    //    size_t written = stream_write_string(stream, payload);
-    //    if(written <= 0) {
-    //        FURI_LOG_W(TAG, "Error creating packet! EXIT");
-    //        result = false;
-    //    } else {
-    subbrute_worker_subghz_transmit(instance, flipper_format);
-
-    result = true;
-#if FURI_DEBUG
-    FURI_LOG_D(TAG, "Manual transmit done");
-#endif
-    //    }
-
-    flipper_format_free(flipper_format);
-    //    furi_string_free(payload);
-
-    return result;
-}
-
-bool subbrute_worker_is_running(SubBruteWorker* instance) {
-    return instance->worker_running;
-}
-
-bool subbrute_worker_can_manual_transmit(SubBruteWorker* instance) {
-    furi_assert(instance);
-
-    if(!instance->initiated) {
-        FURI_LOG_W(TAG, "Worker not init!");
-        return false;
-    }
-
-    return !instance->worker_running && instance->state != SubBruteWorkerStateIDLE &&
-           instance->state != SubBruteWorkerStateTx &&
-           ((furi_get_tick() - instance->last_time_tx_data) > SUBBRUTE_MANUAL_TRANSMIT_INTERVAL);
-}
-
-void subbrute_worker_set_callback(
-    SubBruteWorker* instance,
-    SubBruteWorkerCallback callback,
-    void* context) {
-    furi_assert(instance);
-
-    instance->callback = callback;
-    instance->context = context;
-}
-
-void subbrute_worker_subghz_transmit(SubBruteWorker* instance, FlipperFormat* flipper_format) {
-    while(instance->transmit_mode) {
-        furi_delay_ms(SUBBRUTE_TX_TIMEOUT);
-    }
-    instance->transmit_mode = true;
-    if(instance->transmitter != NULL) {
-        subghz_transmitter_free(instance->transmitter);
-        instance->transmitter = NULL;
-    }
-    instance->transmitter =
-        subghz_transmitter_alloc_init(instance->environment, instance->protocol_name);
-    subghz_transmitter_deserialize(instance->transmitter, flipper_format);
-    furi_hal_subghz_reset();
-    furi_hal_subghz_load_preset(instance->preset);
-    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;
-
-    instance->transmit_mode = false;
-}
-
-void subbrute_worker_send_callback(SubBruteWorker* instance) {
-    if(instance->callback != NULL) {
-        instance->callback(instance->context, instance->state);
-    }
-}
-
-/**
- * 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;
-    }
-    if(instance->state != SubBruteWorkerStateReady &&
-       instance->state != SubBruteWorkerStateFinished) {
-        FURI_LOG_W(TAG, "Invalid state for running worker! State: %d", instance->state);
-        return -2;
-    }
-#ifdef FURI_DEBUG
-    FURI_LOG_I(TAG, "Worker start");
-#endif
-
-    SubBruteWorkerState local_state = instance->state = SubBruteWorkerStateTx;
-    subbrute_worker_send_callback(instance);
-
-    instance->protocol_name = subbrute_protocol_file(instance->file);
-
-    FlipperFormat* flipper_format = flipper_format_string_alloc();
-    Stream* stream = flipper_format_get_raw_stream(flipper_format);
-
-    while(instance->worker_running) {
-        stream_clean(stream);
-        if(instance->attack == SubBruteAttackLoadFile) {
-            subbrute_protocol_file_payload(
-                stream,
-                instance->step,
-                instance->bits,
-                instance->te,
-                instance->repeat,
-                instance->load_index,
-                instance->file_key,
-                instance->two_bytes);
-        } else {
-            subbrute_protocol_default_payload(
-                stream,
-                instance->file,
-                instance->step,
-                instance->bits,
-                instance->te,
-                instance->repeat);
-        }
-#ifdef FURI_DEBUG
-        //FURI_LOG_I(TAG, "Payload: %s", furi_string_get_cstr(payload));
-        //furi_delay_ms(SUBBRUTE_MANUAL_TRANSMIT_INTERVAL / 4);
-#endif
-
-        //        size_t written = stream_write_stream_write_string(stream, payload);
-        //        if(written <= 0) {
-        //            FURI_LOG_W(TAG, "Error creating packet! BREAK");
-        //            instance->worker_running = false;
-        //            local_state = SubBruteWorkerStateIDLE;
-        //            furi_string_free(payload);
-        //            break;
-        //        }
-
-        subbrute_worker_subghz_transmit(instance, flipper_format);
-
-        if(instance->step + 1 > instance->max_value) {
-#ifdef FURI_DEBUG
-            FURI_LOG_I(TAG, "Worker finished to end");
-#endif
-            local_state = SubBruteWorkerStateFinished;
-            //            furi_string_free(payload);
-            break;
-        }
-        instance->step++;
-
-        //        furi_string_free(payload);
-        furi_delay_ms(SUBBRUTE_TX_TIMEOUT);
-    }
-
-    flipper_format_free(flipper_format);
-
-    instance->worker_running = false; // Because we have error states
-    instance->state = local_state == SubBruteWorkerStateTx ? SubBruteWorkerStateReady :
-                                                             local_state;
-    subbrute_worker_send_callback(instance);
-
-#ifdef FURI_DEBUG
-    FURI_LOG_I(TAG, "Worker stop");
-#endif
-    return 0;
-}
+#include "subbrute_worker_private.h"
+#include <string.h>
+#include <toolbox/stream/stream.h>
+#include <flipper_format.h>
+#include <flipper_format_i.h>
+#include <lib/subghz/protocols/protocol_items.h>
+
+#define TAG "SubBruteWorker"
+#define SUBBRUTE_TX_TIMEOUT 6
+#define SUBBRUTE_MANUAL_TRANSMIT_INTERVAL 250
+
+SubBruteWorker* subbrute_worker_alloc() {
+    SubBruteWorker* instance = malloc(sizeof(SubBruteWorker));
+
+    instance->state = SubBruteWorkerStateIDLE;
+    instance->step = 0;
+    instance->worker_running = false;
+    instance->initiated = false;
+    instance->last_time_tx_data = 0;
+    instance->load_index = 0;
+
+    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->context = NULL;
+    instance->callback = NULL;
+
+    instance->tx_timeout_ms = SUBBRUTE_TX_TIMEOUT;
+    instance->decoder_result = NULL;
+    instance->transmitter = NULL;
+    instance->environment = subghz_environment_alloc();
+    subghz_environment_set_protocol_registry(
+        instance->environment, (void*)&subghz_protocol_registry);
+
+    instance->transmit_mode = false;
+
+    return instance;
+}
+
+void subbrute_worker_free(SubBruteWorker* instance) {
+    furi_assert(instance);
+
+    // I don't know how to free this
+    instance->decoder_result = NULL;
+
+    if(instance->transmitter != NULL) {
+        subghz_transmitter_free(instance->transmitter);
+        instance->transmitter = NULL;
+    }
+
+    subghz_environment_free(instance->environment);
+    instance->environment = NULL;
+
+    furi_thread_free(instance->thread);
+
+    free(instance);
+}
+
+uint64_t subbrute_worker_get_step(SubBruteWorker* instance) {
+    return instance->step;
+}
+
+bool subbrute_worker_set_step(SubBruteWorker* instance, uint64_t step) {
+    furi_assert(instance);
+    if(!subbrute_worker_can_manual_transmit(instance)) {
+        FURI_LOG_W(TAG, "Cannot set step during running mode");
+        return false;
+    }
+
+    instance->step = step;
+
+    return true;
+}
+
+bool subbrute_worker_init_default_attack(
+    SubBruteWorker* instance,
+    SubBruteAttacks attack_type,
+    uint64_t step,
+    const SubBruteProtocol* protocol,
+    uint8_t extra_repeats) {
+    furi_assert(instance);
+
+    if(instance->worker_running) {
+        FURI_LOG_W(TAG, "Init Worker when it's running");
+        subbrute_worker_stop(instance);
+    }
+
+    instance->attack = attack_type;
+    instance->frequency = protocol->frequency;
+    instance->preset = protocol->preset;
+    instance->file = protocol->file;
+    instance->step = step;
+    instance->bits = protocol->bits;
+    instance->te = protocol->te;
+    instance->repeat = protocol->repeat + extra_repeats;
+    instance->load_index = 0;
+    instance->file_key = 0;
+    instance->two_bytes = false;
+
+    instance->max_value =
+        subbrute_protocol_calc_max_value(instance->attack, instance->bits, instance->two_bytes);
+
+    instance->initiated = true;
+    instance->state = SubBruteWorkerStateReady;
+    subbrute_worker_send_callback(instance);
+#ifdef FURI_DEBUG
+    FURI_LOG_I(
+        TAG,
+        "subbrute_worker_init_default_attack: %s, bits: %d, preset: %s, file: %s, te: %ld, repeat: %d, max_value: %lld",
+        subbrute_protocol_name(instance->attack),
+        instance->bits,
+        subbrute_protocol_preset(instance->preset),
+        subbrute_protocol_file(instance->file),
+        instance->te,
+        instance->repeat,
+        instance->max_value);
+#endif
+
+    return true;
+}
+
+bool subbrute_worker_init_file_attack(
+    SubBruteWorker* instance,
+    uint64_t step,
+    uint8_t load_index,
+    uint64_t file_key,
+    SubBruteProtocol* protocol,
+    uint8_t extra_repeats,
+    bool two_bytes) {
+    furi_assert(instance);
+
+    if(instance->worker_running) {
+        FURI_LOG_W(TAG, "Init Worker when it's running");
+        subbrute_worker_stop(instance);
+    }
+
+    instance->attack = SubBruteAttackLoadFile;
+    instance->frequency = protocol->frequency;
+    instance->preset = protocol->preset;
+    instance->file = protocol->file;
+    instance->step = step;
+    instance->bits = protocol->bits;
+    instance->te = protocol->te;
+    instance->load_index = load_index;
+    instance->repeat = protocol->repeat + extra_repeats;
+    instance->file_key = file_key;
+    instance->two_bytes = two_bytes;
+
+    instance->max_value =
+        subbrute_protocol_calc_max_value(instance->attack, instance->bits, instance->two_bytes);
+
+    instance->initiated = true;
+    instance->state = SubBruteWorkerStateReady;
+    subbrute_worker_send_callback(instance);
+#ifdef FURI_DEBUG
+    FURI_LOG_I(
+        TAG,
+        "subbrute_worker_init_file_attack: %s, bits: %d, preset: %s, file: %s, te: %ld, repeat: %d, max_value: %lld, key: %llX",
+        subbrute_protocol_name(instance->attack),
+        instance->bits,
+        subbrute_protocol_preset(instance->preset),
+        subbrute_protocol_file(instance->file),
+        instance->te,
+        instance->repeat,
+        instance->max_value,
+        instance->file_key);
+#endif
+
+    return true;
+}
+
+bool subbrute_worker_start(SubBruteWorker* instance) {
+    furi_assert(instance);
+
+    if(!instance->initiated) {
+        FURI_LOG_W(TAG, "Worker not init!");
+        return false;
+    }
+
+    if(instance->worker_running) {
+        FURI_LOG_W(TAG, "Worker is already running!");
+        return false;
+    }
+    if(instance->state != SubBruteWorkerStateReady &&
+       instance->state != SubBruteWorkerStateFinished) {
+        FURI_LOG_W(TAG, "Worker cannot start, invalid device state: %d", instance->state);
+        return false;
+    }
+
+    instance->worker_running = true;
+    furi_thread_start(instance->thread);
+
+    return true;
+}
+
+void subbrute_worker_stop(SubBruteWorker* instance) {
+    furi_assert(instance);
+
+    if(!instance->worker_running) {
+        return;
+    }
+
+    instance->worker_running = false;
+    furi_thread_join(instance->thread);
+
+    furi_hal_subghz_set_path(FuriHalSubGhzPathIsolate);
+    furi_hal_subghz_idle();
+    furi_hal_subghz_sleep();
+}
+
+bool subbrute_worker_transmit_current_key(SubBruteWorker* instance, uint64_t step) {
+    furi_assert(instance);
+
+    if(!instance->initiated) {
+        FURI_LOG_W(TAG, "Worker not init!");
+        return false;
+    }
+    if(instance->worker_running) {
+        FURI_LOG_W(TAG, "Worker in running state!");
+        return false;
+    }
+    if(instance->state != SubBruteWorkerStateReady &&
+       instance->state != SubBruteWorkerStateFinished) {
+        FURI_LOG_W(TAG, "Invalid state for running worker! State: %d", instance->state);
+        return false;
+    }
+
+    uint32_t ticks = furi_get_tick();
+    if((ticks - instance->last_time_tx_data) < SUBBRUTE_MANUAL_TRANSMIT_INTERVAL) {
+#if FURI_DEBUG
+        FURI_LOG_D(TAG, "Need to wait, current: %ld", ticks - instance->last_time_tx_data);
+#endif
+        return false;
+    }
+
+    instance->last_time_tx_data = ticks;
+    instance->step = step;
+
+    bool result;
+    instance->protocol_name = subbrute_protocol_file(instance->file);
+    FlipperFormat* flipper_format = flipper_format_string_alloc();
+    Stream* stream = flipper_format_get_raw_stream(flipper_format);
+
+    stream_clean(stream);
+
+    if(instance->attack == SubBruteAttackLoadFile) {
+        subbrute_protocol_file_payload(
+            stream,
+            step,
+            instance->bits,
+            instance->te,
+            instance->repeat,
+            instance->load_index,
+            instance->file_key,
+            instance->two_bytes);
+    } else {
+        subbrute_protocol_default_payload(
+            stream, instance->file, step, instance->bits, instance->te, instance->repeat);
+    }
+
+    //    size_t written = stream_write_string(stream, payload);
+    //    if(written <= 0) {
+    //        FURI_LOG_W(TAG, "Error creating packet! EXIT");
+    //        result = false;
+    //    } else {
+    subbrute_worker_subghz_transmit(instance, flipper_format);
+
+    result = true;
+#if FURI_DEBUG
+    FURI_LOG_D(TAG, "Manual transmit done");
+#endif
+    //    }
+
+    flipper_format_free(flipper_format);
+    //    furi_string_free(payload);
+
+    return result;
+}
+
+bool subbrute_worker_is_running(SubBruteWorker* instance) {
+    return instance->worker_running;
+}
+
+bool subbrute_worker_can_manual_transmit(SubBruteWorker* instance) {
+    furi_assert(instance);
+
+    if(!instance->initiated) {
+        FURI_LOG_W(TAG, "Worker not init!");
+        return false;
+    }
+
+    return !instance->worker_running && instance->state != SubBruteWorkerStateIDLE &&
+           instance->state != SubBruteWorkerStateTx &&
+           ((furi_get_tick() - instance->last_time_tx_data) > SUBBRUTE_MANUAL_TRANSMIT_INTERVAL);
+}
+
+void subbrute_worker_set_callback(
+    SubBruteWorker* instance,
+    SubBruteWorkerCallback callback,
+    void* context) {
+    furi_assert(instance);
+
+    instance->callback = callback;
+    instance->context = context;
+}
+
+void subbrute_worker_subghz_transmit(SubBruteWorker* instance, FlipperFormat* flipper_format) {
+    const uint8_t timeout = instance->tx_timeout_ms;
+    while(instance->transmit_mode) {
+        furi_delay_ms(timeout);
+    }
+    instance->transmit_mode = true;
+    if(instance->transmitter != NULL) {
+        subghz_transmitter_free(instance->transmitter);
+        instance->transmitter = NULL;
+    }
+    instance->transmitter =
+        subghz_transmitter_alloc_init(instance->environment, instance->protocol_name);
+    subghz_transmitter_deserialize(instance->transmitter, flipper_format);
+    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_start_async_tx(subghz_transmitter_yield, instance->transmitter);
+
+    while(!furi_hal_subghz_is_async_tx_complete()) {
+        furi_delay_ms(timeout);
+    }
+    furi_hal_subghz_stop_async_tx();
+
+    //furi_hal_subghz_set_path(FuriHalSubGhzPathIsolate);
+    furi_hal_subghz_idle();
+    //furi_hal_subghz_sleep();
+    subghz_transmitter_stop(instance->transmitter);
+    subghz_transmitter_free(instance->transmitter);
+    instance->transmitter = NULL;
+
+    instance->transmit_mode = false;
+}
+
+void subbrute_worker_send_callback(SubBruteWorker* instance) {
+    if(instance->callback != NULL) {
+        instance->callback(instance->context, instance->state);
+    }
+}
+
+/**
+ * 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;
+    }
+    if(instance->state != SubBruteWorkerStateReady &&
+       instance->state != SubBruteWorkerStateFinished) {
+        FURI_LOG_W(TAG, "Invalid state for running worker! State: %d", instance->state);
+        return -2;
+    }
+#ifdef FURI_DEBUG
+    FURI_LOG_I(TAG, "Worker start");
+#endif
+
+    SubBruteWorkerState local_state = instance->state = SubBruteWorkerStateTx;
+    subbrute_worker_send_callback(instance);
+
+    instance->protocol_name = subbrute_protocol_file(instance->file);
+
+    FlipperFormat* flipper_format = flipper_format_string_alloc();
+    Stream* stream = flipper_format_get_raw_stream(flipper_format);
+
+    while(instance->worker_running) {
+        stream_clean(stream);
+        if(instance->attack == SubBruteAttackLoadFile) {
+            subbrute_protocol_file_payload(
+                stream,
+                instance->step,
+                instance->bits,
+                instance->te,
+                instance->repeat,
+                instance->load_index,
+                instance->file_key,
+                instance->two_bytes);
+        } else {
+            subbrute_protocol_default_payload(
+                stream,
+                instance->file,
+                instance->step,
+                instance->bits,
+                instance->te,
+                instance->repeat);
+        }
+#ifdef FURI_DEBUG
+        //FURI_LOG_I(TAG, "Payload: %s", furi_string_get_cstr(payload));
+        //furi_delay_ms(SUBBRUTE_MANUAL_TRANSMIT_INTERVAL / 4);
+#endif
+
+        //        size_t written = stream_write_stream_write_string(stream, payload);
+        //        if(written <= 0) {
+        //            FURI_LOG_W(TAG, "Error creating packet! BREAK");
+        //            instance->worker_running = false;
+        //            local_state = SubBruteWorkerStateIDLE;
+        //            furi_string_free(payload);
+        //            break;
+        //        }
+
+        subbrute_worker_subghz_transmit(instance, flipper_format);
+
+        if(instance->step + 1 > instance->max_value) {
+#ifdef FURI_DEBUG
+            FURI_LOG_I(TAG, "Worker finished to end");
+#endif
+            local_state = SubBruteWorkerStateFinished;
+            //            furi_string_free(payload);
+            break;
+        }
+        instance->step++;
+
+        //        furi_string_free(payload);
+        furi_delay_ms(instance->tx_timeout_ms);
+    }
+
+    flipper_format_free(flipper_format);
+
+    instance->worker_running = false; // Because we have error states
+    instance->state = local_state == SubBruteWorkerStateTx ? SubBruteWorkerStateReady :
+                                                             local_state;
+    subbrute_worker_send_callback(instance);
+
+#ifdef FURI_DEBUG
+    FURI_LOG_I(TAG, "Worker stop");
+#endif
+    return 0;
+}
+
+uint8_t subbrute_worker_get_timeout(SubBruteWorker* instance) {
+    return instance->tx_timeout_ms;
+}
+
+void subbrute_worker_timeout_inc(SubBruteWorker* instance) {
+    if(instance->tx_timeout_ms < 255) {
+        instance->tx_timeout_ms++;
+    }
+}
+
+void subbrute_worker_timeout_dec(SubBruteWorker* instance) {
+    if(instance->tx_timeout_ms > 0) {
+        instance->tx_timeout_ms--;
+    }
+}

+ 48 - 42
helpers/subbrute_worker.h

@@ -1,42 +1,48 @@
-#pragma once
-
-#include "../subbrute_protocols.h"
-
-typedef enum {
-    SubBruteWorkerStateIDLE,
-    SubBruteWorkerStateReady,
-    SubBruteWorkerStateTx,
-    SubBruteWorkerStateFinished
-} SubBruteWorkerState;
-
-typedef void (*SubBruteWorkerCallback)(void* context, SubBruteWorkerState state);
-
-typedef struct SubBruteWorker SubBruteWorker;
-
-SubBruteWorker* subbrute_worker_alloc();
-void subbrute_worker_free(SubBruteWorker* instance);
-uint64_t subbrute_worker_get_step(SubBruteWorker* instance);
-bool subbrute_worker_set_step(SubBruteWorker* instance, uint64_t step);
-bool subbrute_worker_is_running(SubBruteWorker* instance);
-bool subbrute_worker_init_default_attack(
-    SubBruteWorker* instance,
-    SubBruteAttacks attack_type,
-    uint64_t step,
-    const SubBruteProtocol* protocol,
-    uint8_t extra_repeats);
-bool subbrute_worker_init_file_attack(
-    SubBruteWorker* instance,
-    uint64_t step,
-    uint8_t load_index,
-    uint64_t file_key,
-    SubBruteProtocol* protocol,
-    uint8_t extra_repeats,
-    bool two_bytes);
-bool subbrute_worker_start(SubBruteWorker* instance);
-void subbrute_worker_stop(SubBruteWorker* instance);
-bool subbrute_worker_transmit_current_key(SubBruteWorker* instance, uint64_t step);
-bool subbrute_worker_can_manual_transmit(SubBruteWorker* instance);
-void subbrute_worker_set_callback(
-    SubBruteWorker* instance,
-    SubBruteWorkerCallback callback,
-    void* context);
+#pragma once
+
+#include "../subbrute_protocols.h"
+
+typedef enum {
+    SubBruteWorkerStateIDLE,
+    SubBruteWorkerStateReady,
+    SubBruteWorkerStateTx,
+    SubBruteWorkerStateFinished
+} SubBruteWorkerState;
+
+typedef void (*SubBruteWorkerCallback)(void* context, SubBruteWorkerState state);
+
+typedef struct SubBruteWorker SubBruteWorker;
+
+SubBruteWorker* subbrute_worker_alloc();
+void subbrute_worker_free(SubBruteWorker* instance);
+uint64_t subbrute_worker_get_step(SubBruteWorker* instance);
+bool subbrute_worker_set_step(SubBruteWorker* instance, uint64_t step);
+bool subbrute_worker_is_running(SubBruteWorker* instance);
+bool subbrute_worker_init_default_attack(
+    SubBruteWorker* instance,
+    SubBruteAttacks attack_type,
+    uint64_t step,
+    const SubBruteProtocol* protocol,
+    uint8_t extra_repeats);
+bool subbrute_worker_init_file_attack(
+    SubBruteWorker* instance,
+    uint64_t step,
+    uint8_t load_index,
+    uint64_t file_key,
+    SubBruteProtocol* protocol,
+    uint8_t extra_repeats,
+    bool two_bytes);
+bool subbrute_worker_start(SubBruteWorker* instance);
+void subbrute_worker_stop(SubBruteWorker* instance);
+bool subbrute_worker_transmit_current_key(SubBruteWorker* instance, uint64_t step);
+bool subbrute_worker_can_manual_transmit(SubBruteWorker* instance);
+void subbrute_worker_set_callback(
+    SubBruteWorker* instance,
+    SubBruteWorkerCallback callback,
+    void* context);
+
+uint8_t subbrute_worker_get_timeout(SubBruteWorker* instance);
+
+void subbrute_worker_timeout_inc(SubBruteWorker* instance);
+
+void subbrute_worker_timeout_dec(SubBruteWorker* instance);

+ 48 - 47
helpers/subbrute_worker_private.h

@@ -1,48 +1,49 @@
-#pragma once
-
-#include "subbrute_worker.h"
-#include <lib/subghz/protocols/base.h>
-#include <lib/subghz/transmitter.h>
-#include <lib/subghz/receiver.h>
-#include <lib/subghz/environment.h>
-
-struct SubBruteWorker {
-    SubBruteWorkerState state;
-    volatile bool worker_running;
-    volatile bool initiated;
-    volatile bool transmit_mode;
-
-    // Current step
-    uint64_t step;
-
-    // SubGhz
-    FuriThread* thread;
-    SubGhzProtocolDecoderBase* decoder_result;
-    SubGhzEnvironment* environment;
-    SubGhzTransmitter* transmitter;
-    const char* protocol_name;
-
-    // Initiated values
-    SubBruteAttacks attack; // Attack state
-    uint32_t frequency;
-    FuriHalSubGhzPreset preset;
-    SubBruteFileProtocol file;
-    uint8_t bits;
-    uint32_t te;
-    uint8_t repeat;
-    uint8_t load_index; // Index of group to bruteforce in loaded file
-    uint64_t file_key;
-    uint64_t max_value; // Max step
-    bool two_bytes;
-
-    // Manual transmit
-    uint32_t last_time_tx_data;
-
-    // Callback for changed states
-    SubBruteWorkerCallback callback;
-    void* context;
-};
-
-int32_t subbrute_worker_thread(void* context);
-void subbrute_worker_subghz_transmit(SubBruteWorker* instance, FlipperFormat* flipper_format);
+#pragma once
+
+#include "subbrute_worker.h"
+#include <lib/subghz/protocols/base.h>
+#include <lib/subghz/transmitter.h>
+#include <lib/subghz/receiver.h>
+#include <lib/subghz/environment.h>
+
+struct SubBruteWorker {
+    SubBruteWorkerState state;
+    volatile bool worker_running;
+    volatile bool initiated;
+    volatile bool transmit_mode;
+
+    // Current step
+    uint64_t step;
+
+    // SubGhz
+    FuriThread* thread;
+    SubGhzProtocolDecoderBase* decoder_result;
+    SubGhzEnvironment* environment;
+    SubGhzTransmitter* transmitter;
+    const char* protocol_name;
+    uint8_t tx_timeout_ms;
+
+    // Initiated values
+    SubBruteAttacks attack; // Attack state
+    uint32_t frequency;
+    FuriHalSubGhzPreset preset;
+    SubBruteFileProtocol file;
+    uint8_t bits;
+    uint32_t te;
+    uint8_t repeat;
+    uint8_t load_index; // Index of group to bruteforce in loaded file
+    uint64_t file_key;
+    uint64_t max_value; // Max step
+    bool two_bytes;
+
+    // Manual transmit
+    uint32_t last_time_tx_data;
+
+    // Callback for changed states
+    SubBruteWorkerCallback callback;
+    void* context;
+};
+
+int32_t subbrute_worker_thread(void* context);
+void subbrute_worker_subghz_transmit(SubBruteWorker* instance, FlipperFormat* flipper_format);
 void subbrute_worker_send_callback(SubBruteWorker* instance);
 void subbrute_worker_send_callback(SubBruteWorker* instance);

+ 29 - 29
scenes/subbrute_scene.h

@@ -1,29 +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
+#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 - 6
scenes/subbrute_scene_config.h

@@ -1,7 +1,8 @@
-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, 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, setup_extra, SetupExtra)
 ADD_SCENE(subbrute, start, Start)
 ADD_SCENE(subbrute, start, Start)

+ 90 - 90
scenes/subbrute_scene_load_file.c

@@ -1,91 +1,91 @@
-#include "../subbrute_i.h"
-#include "subbrute_scene.h"
-
-#define TAG "SubBruteSceneLoadFile"
-
-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
-    FuriString* app_folder;
-    FuriString* load_path;
-    load_path = furi_string_alloc();
-    app_folder = furi_string_alloc_set(SUBBRUTE_PATH);
-
-    DialogsFileBrowserOptions browser_options;
-    dialog_file_browser_set_basic_options(&browser_options, SUBBRUTE_FILE_EXT, &I_sub1_10px);
-
-    SubBruteFileResult load_result = SubBruteFileResultUnknown;
-    // TODO: DELETE IT
-#ifdef SUBBRUTE_FAST_TRACK
-    bool res = true;
-    furi_string_printf(load_path, "%s", "/ext/subghz/princeton.sub");
-#else
-    bool res =
-        dialog_file_browser_show(instance->dialogs, load_path, app_folder, &browser_options);
-#endif
-#ifdef FURI_DEBUG
-    FURI_LOG_D(
-        TAG,
-        "load_path: %s, app_folder: %s",
-        furi_string_get_cstr(load_path),
-        furi_string_get_cstr(app_folder));
-#endif
-    if(res) {
-        load_result =
-            subbrute_device_load_from_file(instance->device, furi_string_get_cstr(load_path));
-        if(load_result == SubBruteFileResultOk) {
-            uint8_t extra_repeats = subbrute_main_view_get_extra_repeats(instance->view_main);
-
-            load_result = subbrute_device_attack_set(
-                instance->device, SubBruteAttackLoadFile, extra_repeats);
-            if(load_result == SubBruteFileResultOk) {
-                if(!subbrute_worker_init_file_attack(
-                       instance->worker,
-                       instance->device->current_step,
-                       instance->device->bit_index,
-                       instance->device->key_from_file,
-                       instance->device->file_protocol_info,
-                       extra_repeats,
-                       instance->device->two_bytes)) {
-                    furi_crash("Invalid attack set!");
-                }
-                // Ready to run!
-                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);
-
-            FuriString* dialog_msg;
-            dialog_msg = furi_string_alloc();
-            furi_string_cat_printf(
-                dialog_msg, "Cannot parse\nfile: %s", subbrute_device_error_get_desc(load_result));
-            dialog_message_show_storage_error(instance->dialogs, furi_string_get_cstr(dialog_msg));
-            furi_string_free(dialog_msg);
-            scene_manager_search_and_switch_to_previous_scene(
-                instance->scene_manager, SubBruteSceneStart);
-        }
-    } else {
-        scene_manager_search_and_switch_to_previous_scene(
-            instance->scene_manager, SubBruteSceneStart);
-    }
-
-    furi_string_free(app_folder);
-    furi_string_free(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;
+#include "../subbrute_i.h"
+#include "subbrute_scene.h"
+
+#define TAG "SubBruteSceneLoadFile"
+
+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
+    FuriString* app_folder;
+    FuriString* load_path;
+    load_path = furi_string_alloc();
+    app_folder = furi_string_alloc_set(SUBBRUTE_PATH);
+
+    DialogsFileBrowserOptions browser_options;
+    dialog_file_browser_set_basic_options(&browser_options, SUBBRUTE_FILE_EXT, &I_sub1_10px);
+
+    SubBruteFileResult load_result = SubBruteFileResultUnknown;
+    // TODO: DELETE IT
+#ifdef SUBBRUTE_FAST_TRACK
+    bool res = true;
+    furi_string_printf(load_path, "%s", "/ext/subghz/princeton.sub");
+#else
+    bool res =
+        dialog_file_browser_show(instance->dialogs, load_path, app_folder, &browser_options);
+#endif
+#ifdef FURI_DEBUG
+    FURI_LOG_D(
+        TAG,
+        "load_path: %s, app_folder: %s",
+        furi_string_get_cstr(load_path),
+        furi_string_get_cstr(app_folder));
+#endif
+    if(res) {
+        load_result =
+            subbrute_device_load_from_file(instance->device, furi_string_get_cstr(load_path));
+        if(load_result == SubBruteFileResultOk) {
+            uint8_t extra_repeats = subbrute_main_view_get_extra_repeats(instance->view_main);
+
+            load_result = subbrute_device_attack_set(
+                instance->device, SubBruteAttackLoadFile, extra_repeats);
+            if(load_result == SubBruteFileResultOk) {
+                if(!subbrute_worker_init_file_attack(
+                       instance->worker,
+                       instance->device->current_step,
+                       instance->device->bit_index,
+                       instance->device->key_from_file,
+                       instance->device->file_protocol_info,
+                       extra_repeats,
+                       instance->device->two_bytes)) {
+                    furi_crash("Invalid attack set!");
+                }
+                // Ready to run!
+                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);
+
+            FuriString* dialog_msg;
+            dialog_msg = furi_string_alloc();
+            furi_string_cat_printf(
+                dialog_msg, "Cannot parse\nfile: %s", subbrute_device_error_get_desc(load_result));
+            dialog_message_show_storage_error(instance->dialogs, furi_string_get_cstr(dialog_msg));
+            furi_string_free(dialog_msg);
+            scene_manager_search_and_switch_to_previous_scene(
+                instance->scene_manager, SubBruteSceneStart);
+        }
+    } else {
+        scene_manager_search_and_switch_to_previous_scene(
+            instance->scene_manager, SubBruteSceneStart);
+    }
+
+    furi_string_free(app_folder);
+    furi_string_free(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;
 }
 }

+ 81 - 81
scenes/subbrute_scene_load_select.c

@@ -1,82 +1,82 @@
-#include "../subbrute_i.h"
-#include "subbrute_scene.h"
-
-#define TAG "SubBruteSceneStart"
-
-void subbrute_scene_load_select_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_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->two_bytes, instance->device->key_from_file);
-
-    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) {
-            /*#ifdef FURI_DEBUG && !SUBBRUTE_FAST_TRACK
-            view_dispatcher_stop(instance->view_dispatcher);
-            consumed = true;
-#else*/
-            instance->device->current_step = 0;
-            instance->device->bit_index = subbrute_main_view_get_index(instance->view_main);
-            instance->device->two_bytes = subbrute_main_view_get_two_bytes(instance->view_main);
-            uint8_t extra_repeats = subbrute_main_view_get_extra_repeats(instance->view_main);
-            instance->device->max_value = subbrute_protocol_calc_max_value(
-                instance->device->attack,
-                instance->device->bit_index,
-                instance->device->two_bytes);
-
-            if(!subbrute_worker_init_file_attack(
-                   instance->worker,
-                   instance->device->current_step,
-                   instance->device->bit_index,
-                   instance->device->key_from_file,
-                   instance->device->file_protocol_info,
-                   extra_repeats,
-                   instance->device->two_bytes)) {
-                furi_crash("Invalid attack set!");
-            }
-            scene_manager_next_scene(instance->scene_manager, SubBruteSceneSetupAttack);
-            /*#endif*/
-            consumed = true;
-        } /* else if(event.event == SubBruteCustomEventTypeChangeStepUp) {
-            instance->device->two_bytes = true;
-        } else if(event.event == SubBruteCustomEventTypeChangeStepDown) {
-            instance->device->two_bytes = false;
-        }*/
-    } 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;
+#include "../subbrute_i.h"
+#include "subbrute_scene.h"
+
+#define TAG "SubBruteSceneStart"
+
+void subbrute_scene_load_select_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_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->two_bytes, instance->device->key_from_file);
+
+    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) {
+            /*#ifdef FURI_DEBUG && !SUBBRUTE_FAST_TRACK
+            view_dispatcher_stop(instance->view_dispatcher);
+            consumed = true;
+#else*/
+            instance->device->current_step = 0;
+            instance->device->bit_index = subbrute_main_view_get_index(instance->view_main);
+            instance->device->two_bytes = subbrute_main_view_get_two_bytes(instance->view_main);
+            uint8_t extra_repeats = subbrute_main_view_get_extra_repeats(instance->view_main);
+            instance->device->max_value = subbrute_protocol_calc_max_value(
+                instance->device->attack,
+                instance->device->bit_index,
+                instance->device->two_bytes);
+
+            if(!subbrute_worker_init_file_attack(
+                   instance->worker,
+                   instance->device->current_step,
+                   instance->device->bit_index,
+                   instance->device->key_from_file,
+                   instance->device->file_protocol_info,
+                   extra_repeats,
+                   instance->device->two_bytes)) {
+                furi_crash("Invalid attack set!");
+            }
+            scene_manager_next_scene(instance->scene_manager, SubBruteSceneSetupAttack);
+            /*#endif*/
+            consumed = true;
+        } /* else if(event.event == SubBruteCustomEventTypeChangeStepUp) {
+            instance->device->two_bytes = true;
+        } else if(event.event == SubBruteCustomEventTypeChangeStepDown) {
+            instance->device->two_bytes = false;
+        }*/
+    } 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;
 }
 }

+ 104 - 104
scenes/subbrute_scene_run_attack.c

@@ -1,104 +1,104 @@
-#include "../subbrute_i.h"
-#include "subbrute_scene.h"
-
-#define TAG "SubBruteSceneRunAttack"
-
-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);
-}
-
-static void
-    subbrute_scene_run_attack_device_state_changed(void* context, SubBruteWorkerState state) {
-    furi_assert(context);
-
-    SubBruteState* instance = (SubBruteState*)context;
-
-    if(state == SubBruteWorkerStateIDLE) {
-        // Can't be IDLE on this step!
-        view_dispatcher_send_custom_event(instance->view_dispatcher, SubBruteCustomEventTypeError);
-    } else if(state == SubBruteWorkerStateFinished) {
-        view_dispatcher_send_custom_event(
-            instance->view_dispatcher, SubBruteCustomEventTypeTransmitFinished);
-    }
-}
-void subbrute_scene_run_attack_on_exit(void* context) {
-    furi_assert(context);
-    SubBruteState* instance = (SubBruteState*)context;
-
-    notification_message(instance->notifications, &sequence_blink_stop);
-    subbrute_worker_stop(instance->worker);
-}
-
-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_worker_set_callback(
-        instance->worker, subbrute_scene_run_attack_device_state_changed, instance);
-
-    if(!subbrute_worker_is_running(instance->worker)) {
-        subbrute_worker_set_step(instance->worker, instance->device->current_step);
-        if(!subbrute_worker_start(instance->worker)) {
-            view_dispatcher_send_custom_event(
-                instance->view_dispatcher, SubBruteCustomEventTypeError);
-        } else {
-            notification_message(instance->notifications, &sequence_single_vibro);
-            notification_message(instance->notifications, &sequence_blink_start_yellow);
-        }
-    }
-}
-
-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) {
-        uint64_t step = subbrute_worker_get_step(instance->worker);
-        instance->device->current_step = step;
-        subbrute_attack_view_set_current_step(view, step);
-
-        if(event.event == SubBruteCustomEventTypeTransmitFinished) {
-            notification_message(instance->notifications, &sequence_display_backlight_on);
-            notification_message(instance->notifications, &sequence_double_vibro);
-
-            scene_manager_next_scene(instance->scene_manager, SubBruteSceneSetupAttack);
-        } else if(
-            event.event == SubBruteCustomEventTypeTransmitNotStarted ||
-            event.event == SubBruteCustomEventTypeBackPressed) {
-            if(subbrute_worker_is_running(instance->worker)) {
-                // Notify
-                notification_message(instance->notifications, &sequence_single_vibro);
-            }
-            // Stop transmit
-            scene_manager_search_and_switch_to_previous_scene(
-                instance->scene_manager, SubBruteSceneSetupAttack);
-        } else if(event.event == SubBruteCustomEventTypeError) {
-            notification_message(instance->notifications, &sequence_error);
-
-            // Stop transmit
-            scene_manager_search_and_switch_to_previous_scene(
-                instance->scene_manager, SubBruteSceneSetupAttack);
-        } else if(event.event == SubBruteCustomEventTypeUpdateView) {
-            //subbrute_attack_view_set_current_step(view, instance->device->current_step);
-        }
-        consumed = true;
-    } else if(event.type == SceneManagerEventTypeTick) {
-        uint64_t step = subbrute_worker_get_step(instance->worker);
-        instance->device->current_step = step;
-        subbrute_attack_view_set_current_step(view, step);
-
-        consumed = true;
-    }
-
-    return consumed;
-}
+#include "../subbrute_i.h"
+#include "subbrute_scene.h"
+
+#define TAG "SubBruteSceneRunAttack"
+
+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);
+}
+
+static void
+    subbrute_scene_run_attack_device_state_changed(void* context, SubBruteWorkerState state) {
+    furi_assert(context);
+
+    SubBruteState* instance = (SubBruteState*)context;
+
+    if(state == SubBruteWorkerStateIDLE) {
+        // Can't be IDLE on this step!
+        view_dispatcher_send_custom_event(instance->view_dispatcher, SubBruteCustomEventTypeError);
+    } else if(state == SubBruteWorkerStateFinished) {
+        view_dispatcher_send_custom_event(
+            instance->view_dispatcher, SubBruteCustomEventTypeTransmitFinished);
+    }
+}
+void subbrute_scene_run_attack_on_exit(void* context) {
+    furi_assert(context);
+    SubBruteState* instance = (SubBruteState*)context;
+
+    notification_message(instance->notifications, &sequence_blink_stop);
+    subbrute_worker_stop(instance->worker);
+}
+
+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_worker_set_callback(
+        instance->worker, subbrute_scene_run_attack_device_state_changed, instance);
+
+    if(!subbrute_worker_is_running(instance->worker)) {
+        subbrute_worker_set_step(instance->worker, instance->device->current_step);
+        if(!subbrute_worker_start(instance->worker)) {
+            view_dispatcher_send_custom_event(
+                instance->view_dispatcher, SubBruteCustomEventTypeError);
+        } else {
+            notification_message(instance->notifications, &sequence_single_vibro);
+            notification_message(instance->notifications, &sequence_blink_start_yellow);
+        }
+    }
+}
+
+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) {
+        uint64_t step = subbrute_worker_get_step(instance->worker);
+        instance->device->current_step = step;
+        subbrute_attack_view_set_current_step(view, step);
+
+        if(event.event == SubBruteCustomEventTypeTransmitFinished) {
+            notification_message(instance->notifications, &sequence_display_backlight_on);
+            notification_message(instance->notifications, &sequence_double_vibro);
+
+            scene_manager_next_scene(instance->scene_manager, SubBruteSceneSetupAttack);
+        } else if(
+            event.event == SubBruteCustomEventTypeTransmitNotStarted ||
+            event.event == SubBruteCustomEventTypeBackPressed) {
+            if(subbrute_worker_is_running(instance->worker)) {
+                // Notify
+                notification_message(instance->notifications, &sequence_single_vibro);
+            }
+            // Stop transmit
+            scene_manager_search_and_switch_to_previous_scene(
+                instance->scene_manager, SubBruteSceneSetupAttack);
+        } else if(event.event == SubBruteCustomEventTypeError) {
+            notification_message(instance->notifications, &sequence_error);
+
+            // Stop transmit
+            scene_manager_search_and_switch_to_previous_scene(
+                instance->scene_manager, SubBruteSceneSetupAttack);
+        } else if(event.event == SubBruteCustomEventTypeUpdateView) {
+            //subbrute_attack_view_set_current_step(view, instance->device->current_step);
+        }
+        consumed = true;
+    } else if(event.type == SceneManagerEventTypeTick) {
+        uint64_t step = subbrute_worker_get_step(instance->worker);
+        instance->device->current_step = step;
+        subbrute_attack_view_set_current_step(view, step);
+
+        consumed = true;
+    }
+
+    return consumed;
+}

+ 84 - 84
scenes/subbrute_scene_save_name.c

@@ -1,84 +1,84 @@
-#include "../subbrute_i.h"
-#include "subbrute_scene.h"
-#include <lib/toolbox/random_name.h>
-
-#define TAG "SubBruteSceneSaveFile"
-
-void subbrute_scene_save_name_on_enter(void* context) {
-    SubBruteState* instance = (SubBruteState*)context;
-
-    // Setup view
-    TextInput* text_input = instance->text_input;
-    set_random_name(instance->text_store, sizeof(instance->text_store));
-
-    text_input_set_header_text(text_input, "Name of file");
-    text_input_set_result_callback(
-        text_input,
-        subbrute_text_input_callback,
-        instance,
-        instance->text_store,
-        SUBBRUTE_MAX_LEN_NAME,
-        true);
-
-    furi_string_reset(instance->file_path);
-    furi_string_set_str(instance->file_path, SUBBRUTE_PATH);
-
-    ValidatorIsFile* validator_is_file = validator_is_file_alloc_init(
-        furi_string_get_cstr(instance->file_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->text_store);
-#endif
-        bool success = false;
-        if(strcmp(instance->text_store, "")) {
-            furi_string_reset(instance->file_path);
-            furi_string_cat_printf(
-                instance->file_path,
-                "%s/%s%s",
-                SUBBRUTE_PATH,
-                instance->text_store,
-                SUBBRUTE_FILE_EXT);
-
-            if(subbrute_device_save_file(
-                   instance->device, furi_string_get_cstr(instance->file_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);
-
-    furi_string_reset(instance->file_path);
-}
+#include "../subbrute_i.h"
+#include "subbrute_scene.h"
+#include <lib/toolbox/random_name.h>
+
+#define TAG "SubBruteSceneSaveFile"
+
+void subbrute_scene_save_name_on_enter(void* context) {
+    SubBruteState* instance = (SubBruteState*)context;
+
+    // Setup view
+    TextInput* text_input = instance->text_input;
+    set_random_name(instance->text_store, sizeof(instance->text_store));
+
+    text_input_set_header_text(text_input, "Name of file");
+    text_input_set_result_callback(
+        text_input,
+        subbrute_text_input_callback,
+        instance,
+        instance->text_store,
+        SUBBRUTE_MAX_LEN_NAME,
+        true);
+
+    furi_string_reset(instance->file_path);
+    furi_string_set_str(instance->file_path, SUBBRUTE_PATH);
+
+    ValidatorIsFile* validator_is_file = validator_is_file_alloc_init(
+        furi_string_get_cstr(instance->file_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->text_store);
+#endif
+        bool success = false;
+        if(strcmp(instance->text_store, "")) {
+            furi_string_reset(instance->file_path);
+            furi_string_cat_printf(
+                instance->file_path,
+                "%s/%s%s",
+                SUBBRUTE_PATH,
+                instance->text_store,
+                SUBBRUTE_FILE_EXT);
+
+            if(subbrute_device_save_file(
+                   instance->device, furi_string_get_cstr(instance->file_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);
+
+    furi_string_reset(instance->file_path);
+}

+ 51 - 51
scenes/subbrute_scene_save_success.c

@@ -1,51 +1,51 @@
-#include "../subbrute_i.h"
-#include "subbrute_scene.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);
-}
+#include "../subbrute_i.h"
+#include "subbrute_scene.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);
+}

+ 140 - 138
scenes/subbrute_scene_setup_attack.c

@@ -1,138 +1,140 @@
-#include "../subbrute_i.h"
-#include "subbrute_scene.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);
-}
-
-static void
-    subbrute_scene_setup_attack_device_state_changed(void* context, SubBruteWorkerState state) {
-    furi_assert(context);
-
-    SubBruteState* instance = (SubBruteState*)context;
-
-    if(state == SubBruteWorkerStateIDLE) {
-        // Can't be IDLE on this step!
-        view_dispatcher_send_custom_event(instance->view_dispatcher, SubBruteCustomEventTypeError);
-    }
-}
-
-void subbrute_scene_setup_attack_on_enter(void* context) {
-    furi_assert(context);
-    SubBruteState* instance = (SubBruteState*)context;
-    SubBruteAttackView* view = instance->view_attack;
-
-    notification_message(instance->notifications, &sequence_reset_vibro);
-
-#ifdef FURI_DEBUG
-    FURI_LOG_D(TAG, "Enter Attack: %s", subbrute_protocol_name(instance->device->attack));
-#endif
-
-    subbrute_worker_set_callback(
-        instance->worker, subbrute_scene_setup_attack_device_state_changed, context);
-    if(subbrute_worker_is_running(instance->worker)) {
-        subbrute_worker_stop(instance->worker);
-        instance->device->current_step = subbrute_worker_get_step(instance->worker);
-    }
-
-    subbrute_attack_view_init_values(
-        view,
-        instance->device->attack,
-        instance->device->max_value,
-        instance->device->current_step,
-        false,
-        instance->device->extra_repeats);
-
-    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_stop(instance->worker);
-    notification_message(instance->notifications, &sequence_blink_stop);
-    notification_message(instance->notifications, &sequence_reset_vibro);
-}
-
-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) {
-            scene_manager_next_scene(instance->scene_manager, SubBruteSceneRunAttack);
-        } else if(event.event == SubBruteCustomEventTypeSaveFile) {
-            subbrute_attack_view_init_values(
-                view,
-                instance->device->attack,
-                instance->device->max_value,
-                instance->device->current_step,
-                false,
-                instance->device->extra_repeats);
-            scene_manager_next_scene(instance->scene_manager, SubBruteSceneSaveName);
-        } else if(event.event == SubBruteCustomEventTypeBackPressed) {
-            subbrute_attack_view_init_values(
-                view,
-                instance->device->attack,
-                instance->device->max_value,
-                instance->device->current_step,
-                false,
-                instance->device->extra_repeats);
-            scene_manager_next_scene(instance->scene_manager, SubBruteSceneStart);
-        } else if(event.event == SubBruteCustomEventTypeError) {
-            notification_message(instance->notifications, &sequence_error);
-        } else if(event.event == SubBruteCustomEventTypeTransmitCustom) {
-            // We can transmit only in not working states
-            if(subbrute_worker_can_manual_transmit(instance->worker)) {
-                // MANUAL Transmit!
-                // Blink
-                notification_message(instance->notifications, &sequence_blink_green_100);
-                subbrute_worker_transmit_current_key(
-                    instance->worker, instance->device->current_step);
-                // Stop
-                notification_message(instance->notifications, &sequence_blink_stop);
-            }
-        } else if(event.event == SubBruteCustomEventTypeChangeStepUp) {
-            // +1
-            uint64_t step = subbrute_device_add_step(instance->device, 1);
-            subbrute_worker_set_step(instance->worker, step);
-            subbrute_attack_view_set_current_step(view, step);
-        } else if(event.event == SubBruteCustomEventTypeChangeStepUpMore) {
-            // +50
-            uint64_t step = subbrute_device_add_step(instance->device, 50);
-            subbrute_worker_set_step(instance->worker, step);
-            subbrute_attack_view_set_current_step(view, step);
-        } else if(event.event == SubBruteCustomEventTypeChangeStepDown) {
-            // -1
-            uint64_t step = subbrute_device_add_step(instance->device, -1);
-            subbrute_worker_set_step(instance->worker, step);
-            subbrute_attack_view_set_current_step(view, step);
-        } else if(event.event == SubBruteCustomEventTypeChangeStepDownMore) {
-            // -50
-            uint64_t step = subbrute_device_add_step(instance->device, -50);
-            subbrute_worker_set_step(instance->worker, step);
-            subbrute_attack_view_set_current_step(view, step);
-        }
-
-        consumed = true;
-    } else if(event.type == SceneManagerEventTypeTick) {
-        if(subbrute_worker_is_running(instance->worker)) {
-            instance->device->current_step = subbrute_worker_get_step(instance->worker);
-        }
-        subbrute_attack_view_set_current_step(view, instance->device->current_step);
-        consumed = true;
-    }
-
-    return consumed;
-}
+#include "../subbrute_i.h"
+#include "subbrute_scene.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);
+}
+
+static void
+    subbrute_scene_setup_attack_device_state_changed(void* context, SubBruteWorkerState state) {
+    furi_assert(context);
+
+    SubBruteState* instance = (SubBruteState*)context;
+
+    if(state == SubBruteWorkerStateIDLE) {
+        // Can't be IDLE on this step!
+        view_dispatcher_send_custom_event(instance->view_dispatcher, SubBruteCustomEventTypeError);
+    }
+}
+
+void subbrute_scene_setup_attack_on_enter(void* context) {
+    furi_assert(context);
+    SubBruteState* instance = (SubBruteState*)context;
+    SubBruteAttackView* view = instance->view_attack;
+
+    notification_message(instance->notifications, &sequence_reset_vibro);
+
+#ifdef FURI_DEBUG
+    FURI_LOG_D(TAG, "Enter Attack: %s", subbrute_protocol_name(instance->device->attack));
+#endif
+
+    subbrute_worker_set_callback(
+        instance->worker, subbrute_scene_setup_attack_device_state_changed, context);
+    if(subbrute_worker_is_running(instance->worker)) {
+        subbrute_worker_stop(instance->worker);
+        instance->device->current_step = subbrute_worker_get_step(instance->worker);
+    }
+
+    subbrute_attack_view_init_values(
+        view,
+        instance->device->attack,
+        instance->device->max_value,
+        instance->device->current_step,
+        false,
+        instance->device->extra_repeats);
+
+    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_stop(instance->worker);
+    notification_message(instance->notifications, &sequence_blink_stop);
+    notification_message(instance->notifications, &sequence_reset_vibro);
+}
+
+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) {
+            scene_manager_next_scene(instance->scene_manager, SubBruteSceneRunAttack);
+        } else if(event.event == SubBruteCustomEventTypeSaveFile) {
+            subbrute_attack_view_init_values(
+                view,
+                instance->device->attack,
+                instance->device->max_value,
+                instance->device->current_step,
+                false,
+                instance->device->extra_repeats);
+            scene_manager_next_scene(instance->scene_manager, SubBruteSceneSaveName);
+        } else if(event.event == SubBruteCustomEventTypeExtraSettings) {
+            scene_manager_next_scene(instance->scene_manager, SubBruteSceneSetupExtra);
+        } else if(event.event == SubBruteCustomEventTypeBackPressed) {
+            subbrute_attack_view_init_values(
+                view,
+                instance->device->attack,
+                instance->device->max_value,
+                instance->device->current_step,
+                false,
+                instance->device->extra_repeats);
+            scene_manager_next_scene(instance->scene_manager, SubBruteSceneStart);
+        } else if(event.event == SubBruteCustomEventTypeError) {
+            notification_message(instance->notifications, &sequence_error);
+        } else if(event.event == SubBruteCustomEventTypeTransmitCustom) {
+            // We can transmit only in not working states
+            if(subbrute_worker_can_manual_transmit(instance->worker)) {
+                // MANUAL Transmit!
+                // Blink
+                notification_message(instance->notifications, &sequence_blink_green_100);
+                subbrute_worker_transmit_current_key(
+                    instance->worker, instance->device->current_step);
+                // Stop
+                notification_message(instance->notifications, &sequence_blink_stop);
+            }
+        } else if(event.event == SubBruteCustomEventTypeChangeStepUp) {
+            // +1
+            uint64_t step = subbrute_device_add_step(instance->device, 1);
+            subbrute_worker_set_step(instance->worker, step);
+            subbrute_attack_view_set_current_step(view, step);
+        } else if(event.event == SubBruteCustomEventTypeChangeStepUpMore) {
+            // +50
+            uint64_t step = subbrute_device_add_step(instance->device, 50);
+            subbrute_worker_set_step(instance->worker, step);
+            subbrute_attack_view_set_current_step(view, step);
+        } else if(event.event == SubBruteCustomEventTypeChangeStepDown) {
+            // -1
+            uint64_t step = subbrute_device_add_step(instance->device, -1);
+            subbrute_worker_set_step(instance->worker, step);
+            subbrute_attack_view_set_current_step(view, step);
+        } else if(event.event == SubBruteCustomEventTypeChangeStepDownMore) {
+            // -50
+            uint64_t step = subbrute_device_add_step(instance->device, -50);
+            subbrute_worker_set_step(instance->worker, step);
+            subbrute_attack_view_set_current_step(view, step);
+        }
+
+        consumed = true;
+    } else if(event.type == SceneManagerEventTypeTick) {
+        if(subbrute_worker_is_running(instance->worker)) {
+            instance->device->current_step = subbrute_worker_get_step(instance->worker);
+        }
+        subbrute_attack_view_set_current_step(view, instance->device->current_step);
+        consumed = true;
+    }
+
+    return consumed;
+}

+ 66 - 0
scenes/subbrute_scene_setup_extra.c

@@ -0,0 +1,66 @@
+#include "../subbrute_i.h"
+#include "subbrute_scene.h"
+
+#define TAG "SubBruteSceneLoadFile"
+
+void setup_extra_widget_callback(GuiButtonType result, InputType type, void* context);
+
+static void setup_extra_widget_draw(void* context) {
+    furi_assert(context);
+    SubBruteState* instance = context;
+
+    Widget* widget = instance->widget;
+
+    widget_add_button_element(
+        widget, GuiButtonTypeLeft, "-TD", setup_extra_widget_callback, instance);
+    widget_add_button_element(
+        widget, GuiButtonTypeRight, "TD+", setup_extra_widget_callback, instance);
+
+    char str[20];
+    snprintf(&str[0], 20, "%d", subbrute_worker_get_timeout(instance->worker));
+
+    widget_add_string_element(
+        instance->widget, 64, 15, AlignCenter, AlignCenter, FontPrimary, "Time Delay");
+
+    widget_add_string_element(
+        instance->widget, 64, 32, AlignCenter, AlignCenter, FontBigNumbers, &str[0]);
+}
+
+void setup_extra_widget_callback(GuiButtonType result, InputType type, void* context) {
+    furi_assert(context);
+    SubBruteState* instance = context;
+
+    if((result == GuiButtonTypeLeft) && ((type == InputTypeShort) || (type == InputTypeRepeat))) {
+        widget_reset(instance->widget);
+        subbrute_worker_timeout_dec(instance->worker);
+        setup_extra_widget_draw(instance);
+    } else if(
+        (result == GuiButtonTypeRight) &&
+        ((type == InputTypeShort) || (type == InputTypeRepeat))) {
+        widget_reset(instance->widget);
+        subbrute_worker_timeout_inc(instance->worker);
+        setup_extra_widget_draw(instance);
+    }
+}
+
+void subbrute_scene_setup_extra_on_enter(void* context) {
+    furi_assert(context);
+    SubBruteState* instance = context;
+
+    setup_extra_widget_draw(instance);
+
+    view_dispatcher_switch_to_view(instance->view_dispatcher, SubBruteViewWidget);
+}
+
+void subbrute_scene_setup_extra_on_exit(void* context) {
+    furi_assert(context);
+    SubBruteState* instance = context;
+
+    widget_reset(instance->widget);
+}
+
+bool subbrute_scene_setup_extra_on_event(void* context, SceneManagerEvent event) {
+    UNUSED(context);
+    UNUSED(event);
+    return false;
+}

+ 88 - 88
scenes/subbrute_scene_start.c

@@ -1,89 +1,89 @@
-#include "../subbrute_i.h"
-#include "subbrute_scene.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, instance->device->two_bytes, 0);
-
-    view_dispatcher_switch_to_view(instance->view_dispatcher, instance->current_view);
-
-    // TODO: DELETE IT
-#ifdef SUBBRUTE_FAST_TRACK
-    scene_manager_next_scene(instance->scene_manager, SubBruteSceneLoadFile);
-#endif
-}
-
-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: %ld, SubBruteCustomEventTypeMenuSelected: %s, SubBruteCustomEventTypeLoadFile: %s",
-            event.event,
-            event.event == SubBruteCustomEventTypeMenuSelected ? "true" : "false",
-            event.event == SubBruteCustomEventTypeLoadFile ? "true" : "false");
-#endif
-        if(event.event == SubBruteCustomEventTypeMenuSelected) {
-            SubBruteAttacks attack = subbrute_main_view_get_index(instance->view_main);
-            uint8_t extra_repeats = subbrute_main_view_get_extra_repeats(instance->view_main);
-
-            if((subbrute_device_attack_set(instance->device, attack, extra_repeats) !=
-                SubBruteFileResultOk) ||
-               (!subbrute_worker_init_default_attack(
-                   instance->worker,
-                   attack,
-                   instance->device->current_step,
-                   instance->device->protocol_info,
-                   instance->device->extra_repeats))) {
-                furi_crash("Invalid attack set!");
-            }
-            scene_manager_next_scene(instance->scene_manager, SubBruteSceneSetupAttack);
-
-            consumed = true;
-        } else if(event.event == SubBruteCustomEventTypeLoadFile) {
-            //uint8_t extra_repeats = subbrute_main_view_get_extra_repeats(instance->view_main);
-
-            //instance->device->extra_repeats = extra_repeats;
-            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;
+#include "../subbrute_i.h"
+#include "subbrute_scene.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, instance->device->two_bytes, 0);
+
+    view_dispatcher_switch_to_view(instance->view_dispatcher, instance->current_view);
+
+    // TODO: DELETE IT
+#ifdef SUBBRUTE_FAST_TRACK
+    scene_manager_next_scene(instance->scene_manager, SubBruteSceneLoadFile);
+#endif
+}
+
+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: %ld, SubBruteCustomEventTypeMenuSelected: %s, SubBruteCustomEventTypeLoadFile: %s",
+            event.event,
+            event.event == SubBruteCustomEventTypeMenuSelected ? "true" : "false",
+            event.event == SubBruteCustomEventTypeLoadFile ? "true" : "false");
+#endif
+        if(event.event == SubBruteCustomEventTypeMenuSelected) {
+            SubBruteAttacks attack = subbrute_main_view_get_index(instance->view_main);
+            uint8_t extra_repeats = subbrute_main_view_get_extra_repeats(instance->view_main);
+
+            if((subbrute_device_attack_set(instance->device, attack, extra_repeats) !=
+                SubBruteFileResultOk) ||
+               (!subbrute_worker_init_default_attack(
+                   instance->worker,
+                   attack,
+                   instance->device->current_step,
+                   instance->device->protocol_info,
+                   instance->device->extra_repeats))) {
+                furi_crash("Invalid attack set!");
+            }
+            scene_manager_next_scene(instance->scene_manager, SubBruteSceneSetupAttack);
+
+            consumed = true;
+        } else if(event.event == SubBruteCustomEventTypeLoadFile) {
+            //uint8_t extra_repeats = subbrute_main_view_get_extra_repeats(instance->view_main);
+
+            //instance->device->extra_repeats = extra_repeats;
+            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 - 30
scenes/subbute_scene.c

@@ -1,30 +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,
-};
+#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,
+};

+ 208 - 202
subbrute.c

@@ -1,203 +1,209 @@
-#include "subbrute_i.h"
-#include "subbrute_custom_event.h"
-#include "scenes/subbrute_scene.h"
-
-#define TAG "SubBruteApp"
-
-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);
-}
-
-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 subbrute_tick_event_callback(void* context) {
-    furi_assert(context);
-    SubBruteState* instance = context;
-    scene_manager_handle_tick_event(instance->scene_manager);
-}
-
-SubBruteState* subbrute_alloc() {
-    SubBruteState* instance = malloc(sizeof(SubBruteState));
-
-    memset(instance->text_store, 0, sizeof(instance->text_store));
-    instance->file_path = furi_string_alloc();
-
-    instance->scene_manager = scene_manager_alloc(&subbrute_scene_handlers, instance);
-    instance->view_dispatcher = view_dispatcher_alloc();
-
-    instance->gui = furi_record_open(RECORD_GUI);
-
-    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
-    instance->dialogs = furi_record_open(RECORD_DIALOGS);
-
-    // Notifications
-    instance->notifications = furi_record_open(RECORD_NOTIFICATION);
-
-    // Devices
-    instance->device = subbrute_device_alloc();
-
-    // SubBruteWorker
-    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));
-
-    //instance->flipper_format = flipper_format_string_alloc();
-    //instance->environment = subghz_environment_alloc();
-
-    return instance;
-}
-
-void subbrute_free(SubBruteState* instance) {
-    furi_assert(instance);
-
-    // SubBruteWorker
-    subbrute_worker_stop(instance->worker);
-    subbrute_worker_free(instance->worker);
-
-    // SubBruteDevice
-    subbrute_device_free(instance->device);
-
-    // Notifications
-    notification_message(instance->notifications, &sequence_blink_stop);
-    furi_record_close(RECORD_NOTIFICATION);
-    instance->notifications = NULL;
-
-    // View Main
-    view_dispatcher_remove_view(instance->view_dispatcher, SubBruteViewMain);
-    subbrute_main_view_free(instance->view_main);
-
-    // View Attack
-    view_dispatcher_remove_view(instance->view_dispatcher, SubBruteViewAttack);
-    subbrute_attack_view_free(instance->view_attack);
-
-    // TextInput
-    view_dispatcher_remove_view(instance->view_dispatcher, SubBruteViewTextInput);
-    text_input_free(instance->text_input);
-
-    // Custom Widget
-    view_dispatcher_remove_view(instance->view_dispatcher, SubBruteViewWidget);
-    widget_free(instance->widget);
-
-    // Popup
-    view_dispatcher_remove_view(instance->view_dispatcher, SubBruteViewPopup);
-    popup_free(instance->popup);
-
-    // ViewStack
-    view_dispatcher_remove_view(instance->view_dispatcher, SubBruteViewStack);
-    view_stack_free(instance->view_stack);
-
-    //Dialog
-    furi_record_close(RECORD_DIALOGS);
-    instance->dialogs = NULL;
-
-    // Scene manager
-    scene_manager_free(instance->scene_manager);
-
-    // View Dispatcher
-    view_dispatcher_free(instance->view_dispatcher);
-
-    // GUI
-    furi_record_close(RECORD_GUI);
-    instance->gui = NULL;
-
-    furi_string_free(instance->file_path);
-
-    // The rest
-    free(instance);
-}
-
-void subbrute_text_input_callback(void* context) {
-    furi_assert(context);
-    SubBruteState* instance = context;
-    view_dispatcher_send_custom_event(
-        instance->view_dispatcher, SubBruteCustomEventTypeTextEditDone);
-}
-
-void subbrute_popup_closed_callback(void* context) {
-    furi_assert(context);
-    SubBruteState* instance = context;
-    view_dispatcher_send_custom_event(
-        instance->view_dispatcher, SubBruteCustomEventTypePopupClosed);
-}
-
-// ENTRYPOINT
-int32_t subbrute_app(void* p) {
-    UNUSED(p);
-
-    SubBruteState* instance = subbrute_alloc();
-    view_dispatcher_attach_to_gui(
-        instance->view_dispatcher, instance->gui, ViewDispatcherTypeFullscreen);
-    scene_manager_next_scene(instance->scene_manager, SubBruteSceneStart);
-
-    // Enable power for External CC1101 if it is connected
-    furi_hal_subghz_enable_ext_power();
-    // Auto switch to internal radio if external radio is not available
-    furi_delay_ms(15);
-    if(!furi_hal_subghz_check_radio()) {
-        furi_hal_subghz_select_radio_type(SubGhzRadioInternal);
-        furi_hal_subghz_init_radio_type(SubGhzRadioInternal);
-    }
-
-    furi_hal_power_suppress_charge_enter();
-
-    notification_message(instance->notifications, &sequence_display_backlight_on);
-    view_dispatcher_run(instance->view_dispatcher);
-    furi_hal_power_suppress_charge_exit();
-    // Disable power for External CC1101 if it was enabled and module is connected
-    furi_hal_subghz_disable_ext_power();
-    // Reinit SPI handles for internal radio / nfc
-    furi_hal_subghz_init_radio_type(SubGhzRadioInternal);
-
-    subbrute_free(instance);
-
-    return 0;
+#include "subbrute_i.h"
+#include "subbrute_custom_event.h"
+#include "scenes/subbrute_scene.h"
+
+#define TAG "SubBruteApp"
+
+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);
+}
+
+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 subbrute_tick_event_callback(void* context) {
+    furi_assert(context);
+    SubBruteState* instance = context;
+    scene_manager_handle_tick_event(instance->scene_manager);
+}
+
+SubBruteState* subbrute_alloc() {
+    SubBruteState* instance = malloc(sizeof(SubBruteState));
+
+    memset(instance->text_store, 0, sizeof(instance->text_store));
+    instance->file_path = furi_string_alloc();
+
+    instance->scene_manager = scene_manager_alloc(&subbrute_scene_handlers, instance);
+    instance->view_dispatcher = view_dispatcher_alloc();
+
+    instance->gui = furi_record_open(RECORD_GUI);
+
+    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
+    instance->dialogs = furi_record_open(RECORD_DIALOGS);
+
+    // Notifications
+    instance->notifications = furi_record_open(RECORD_NOTIFICATION);
+
+    // Devices
+    instance->device = subbrute_device_alloc();
+
+    // SubBruteWorker
+    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));
+
+    //instance->flipper_format = flipper_format_string_alloc();
+    //instance->environment = subghz_environment_alloc();
+
+    // Uncomment to enable Debug pin output on PIN 17(1W)
+    //furi_hal_subghz_set_async_mirror_pin(&gpio_ibutton);
+
+    return instance;
+}
+
+void subbrute_free(SubBruteState* instance) {
+    furi_assert(instance);
+
+    // Uncomment to enable Debug pin output on PIN 17(1W)
+    //furi_hal_subghz_set_async_mirror_pin(NULL);
+
+    // SubBruteWorker
+    subbrute_worker_stop(instance->worker);
+    subbrute_worker_free(instance->worker);
+
+    // SubBruteDevice
+    subbrute_device_free(instance->device);
+
+    // Notifications
+    notification_message(instance->notifications, &sequence_blink_stop);
+    furi_record_close(RECORD_NOTIFICATION);
+    instance->notifications = NULL;
+
+    // View Main
+    view_dispatcher_remove_view(instance->view_dispatcher, SubBruteViewMain);
+    subbrute_main_view_free(instance->view_main);
+
+    // View Attack
+    view_dispatcher_remove_view(instance->view_dispatcher, SubBruteViewAttack);
+    subbrute_attack_view_free(instance->view_attack);
+
+    // TextInput
+    view_dispatcher_remove_view(instance->view_dispatcher, SubBruteViewTextInput);
+    text_input_free(instance->text_input);
+
+    // Custom Widget
+    view_dispatcher_remove_view(instance->view_dispatcher, SubBruteViewWidget);
+    widget_free(instance->widget);
+
+    // Popup
+    view_dispatcher_remove_view(instance->view_dispatcher, SubBruteViewPopup);
+    popup_free(instance->popup);
+
+    // ViewStack
+    view_dispatcher_remove_view(instance->view_dispatcher, SubBruteViewStack);
+    view_stack_free(instance->view_stack);
+
+    //Dialog
+    furi_record_close(RECORD_DIALOGS);
+    instance->dialogs = NULL;
+
+    // Scene manager
+    scene_manager_free(instance->scene_manager);
+
+    // View Dispatcher
+    view_dispatcher_free(instance->view_dispatcher);
+
+    // GUI
+    furi_record_close(RECORD_GUI);
+    instance->gui = NULL;
+
+    furi_string_free(instance->file_path);
+
+    // The rest
+    free(instance);
+}
+
+void subbrute_text_input_callback(void* context) {
+    furi_assert(context);
+    SubBruteState* instance = context;
+    view_dispatcher_send_custom_event(
+        instance->view_dispatcher, SubBruteCustomEventTypeTextEditDone);
+}
+
+void subbrute_popup_closed_callback(void* context) {
+    furi_assert(context);
+    SubBruteState* instance = context;
+    view_dispatcher_send_custom_event(
+        instance->view_dispatcher, SubBruteCustomEventTypePopupClosed);
+}
+
+// ENTRYPOINT
+int32_t subbrute_app(void* p) {
+    UNUSED(p);
+
+    SubBruteState* instance = subbrute_alloc();
+    view_dispatcher_attach_to_gui(
+        instance->view_dispatcher, instance->gui, ViewDispatcherTypeFullscreen);
+    scene_manager_next_scene(instance->scene_manager, SubBruteSceneStart);
+
+    // Enable power for External CC1101 if it is connected
+    furi_hal_subghz_enable_ext_power();
+    // Auto switch to internal radio if external radio is not available
+    furi_delay_ms(15);
+    if(!furi_hal_subghz_check_radio()) {
+        furi_hal_subghz_select_radio_type(SubGhzRadioInternal);
+        furi_hal_subghz_init_radio_type(SubGhzRadioInternal);
+    }
+
+    furi_hal_power_suppress_charge_enter();
+
+    notification_message(instance->notifications, &sequence_display_backlight_on);
+    view_dispatcher_run(instance->view_dispatcher);
+    furi_hal_power_suppress_charge_exit();
+    // Disable power for External CC1101 if it was enabled and module is connected
+    furi_hal_subghz_disable_ext_power();
+    // Reinit SPI handles for internal radio / nfc
+    furi_hal_subghz_init_radio_type(SubGhzRadioInternal);
+
+    subbrute_free(instance);
+
+    return 0;
 }
 }

+ 2 - 2
subbrute.h

@@ -1,3 +1,3 @@
-#pragma once
-
+#pragma once
+
 typedef struct SubBruteState SubBruteState;
 typedef struct SubBruteState SubBruteState;

+ 26 - 25
subbrute_custom_event.h

@@ -1,26 +1,27 @@
-#pragma once
-
-typedef enum {
-    // Reserve first 100 events for button types and indexes, starting from 0
-    SubBruteCustomEventTypeReserved = 100,
-
-    SubBruteCustomEventTypeBackPressed,
-    SubBruteCustomEventTypeIndexSelected,
-    SubBruteCustomEventTypeTransmitStarted,
-    SubBruteCustomEventTypeError,
-    SubBruteCustomEventTypeTransmitFinished,
-    SubBruteCustomEventTypeTransmitNotStarted,
-    SubBruteCustomEventTypeTransmitCustom,
-    SubBruteCustomEventTypeSaveFile,
-    SubBruteCustomEventTypeUpdateView,
-    SubBruteCustomEventTypeChangeStepUp,
-    SubBruteCustomEventTypeChangeStepDown,
-    SubBruteCustomEventTypeChangeStepUpMore,
-    SubBruteCustomEventTypeChangeStepDownMore,
-
-    SubBruteCustomEventTypeMenuSelected,
-    SubBruteCustomEventTypeTextEditDone,
-    SubBruteCustomEventTypePopupClosed,
-
-    SubBruteCustomEventTypeLoadFile,
+#pragma once
+
+typedef enum {
+    // Reserve first 100 events for button types and indexes, starting from 0
+    SubBruteCustomEventTypeReserved = 100,
+
+    SubBruteCustomEventTypeBackPressed,
+    SubBruteCustomEventTypeIndexSelected,
+    SubBruteCustomEventTypeTransmitStarted,
+    SubBruteCustomEventTypeError,
+    SubBruteCustomEventTypeTransmitFinished,
+    SubBruteCustomEventTypeTransmitNotStarted,
+    SubBruteCustomEventTypeTransmitCustom,
+    SubBruteCustomEventTypeSaveFile,
+    SubBruteCustomEventTypeExtraSettings,
+    SubBruteCustomEventTypeUpdateView,
+    SubBruteCustomEventTypeChangeStepUp,
+    SubBruteCustomEventTypeChangeStepDown,
+    SubBruteCustomEventTypeChangeStepUpMore,
+    SubBruteCustomEventTypeChangeStepDownMore,
+
+    SubBruteCustomEventTypeMenuSelected,
+    SubBruteCustomEventTypeTextEditDone,
+    SubBruteCustomEventTypePopupClosed,
+
+    SubBruteCustomEventTypeLoadFile,
 } SubBruteCustomEvent;
 } SubBruteCustomEvent;

+ 457 - 457
subbrute_device.c

@@ -1,458 +1,458 @@
-#include "subbrute_device.h"
-
-#include <stdint.h>
-#include <storage/storage.h>
-#include <lib/toolbox/stream/stream.h>
-#include <lib/flipper_format/flipper_format.h>
-#include <lib/flipper_format/flipper_format_i.h>
-#include <lib/subghz/protocols/protocol_items.h>
-
-#define TAG "SubBruteDevice"
-
-SubBruteDevice* subbrute_device_alloc() {
-    SubBruteDevice* instance = malloc(sizeof(SubBruteDevice));
-
-    instance->current_step = 0;
-
-    instance->protocol_info = NULL;
-    instance->file_protocol_info = NULL;
-    instance->decoder_result = NULL;
-    instance->receiver = NULL;
-    instance->environment = subghz_environment_alloc();
-    subghz_environment_set_protocol_registry(
-        instance->environment, (void*)&subghz_protocol_registry);
-
-#ifdef FURI_DEBUG
-    subbrute_device_attack_set_default_values(instance, SubBruteAttackLoadFile);
-#else
-    subbrute_device_attack_set_default_values(instance, SubBruteAttackCAME12bit433);
-#endif
-    return instance;
-}
-
-void subbrute_device_free(SubBruteDevice* instance) {
-    furi_assert(instance);
-
-    // I don't know how to free this
-    instance->decoder_result = NULL;
-
-    if(instance->receiver != NULL) {
-        subghz_receiver_free(instance->receiver);
-        instance->receiver = NULL;
-    }
-
-    subghz_environment_free(instance->environment);
-    instance->environment = NULL;
-
-    subbrute_device_free_protocol_info(instance);
-
-    free(instance);
-}
-
-uint64_t subbrute_device_add_step(SubBruteDevice* instance, int8_t step) {
-    if(step > 0) {
-        if((instance->current_step + step) - instance->max_value == 1) {
-            instance->current_step = 0x00;
-        } else {
-            uint64_t value = instance->current_step + step;
-            if(value == instance->max_value) {
-                instance->current_step = value;
-            } else {
-                instance->current_step = value % instance->max_value;
-            }
-        }
-    } else {
-        if(instance->current_step + step == 0) {
-            instance->current_step = 0x00;
-        } else if(instance->current_step == 0) {
-            instance->current_step = instance->max_value;
-        } else {
-            uint64_t value = ((instance->current_step + step) + instance->max_value);
-            if(value == instance->max_value) {
-                instance->current_step = value;
-            } else {
-                instance->current_step = value % instance->max_value;
-            }
-        }
-    }
-
-    return instance->current_step;
-}
-
-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
-
-    Storage* storage = furi_record_open(RECORD_STORAGE);
-    FlipperFormat* file = flipper_format_file_alloc(storage);
-    bool result = false;
-    do {
-        if(!flipper_format_file_open_always(file, dev_file_name)) {
-            FURI_LOG_E(TAG, "Failed to open file: %s", dev_file_name);
-            break;
-        }
-        Stream* stream = flipper_format_get_raw_stream(file);
-        if(instance->attack == SubBruteAttackLoadFile) {
-            subbrute_protocol_file_generate_file(
-                stream,
-                instance->file_protocol_info->frequency,
-                instance->file_protocol_info->preset,
-                instance->file_protocol_info->file,
-                instance->current_step,
-                instance->file_protocol_info->bits,
-                instance->file_protocol_info->te,
-                instance->bit_index,
-                instance->key_from_file,
-                instance->two_bytes);
-        } else {
-            subbrute_protocol_default_generate_file(
-                stream,
-                instance->protocol_info->frequency,
-                instance->protocol_info->preset,
-                instance->protocol_info->file,
-                instance->current_step,
-                instance->protocol_info->bits,
-                instance->protocol_info->te);
-        }
-
-        result = true;
-    } while(false);
-
-    if(!result) {
-        FURI_LOG_E(TAG, "subbrute_device_save_file failed!");
-    }
-
-    flipper_format_file_close(file);
-    flipper_format_free(file);
-    furi_record_close(RECORD_STORAGE);
-
-    return result;
-}
-
-SubBruteFileResult subbrute_device_attack_set(
-    SubBruteDevice* instance,
-    SubBruteAttacks type,
-    uint8_t extra_repeats) {
-    furi_assert(instance);
-#ifdef FURI_DEBUG
-    FURI_LOG_D(TAG, "subbrute_device_attack_set: %d, extra_repeats: %d", type, extra_repeats);
-#endif
-    subbrute_device_attack_set_default_values(instance, type);
-
-    if(type != SubBruteAttackLoadFile) {
-        subbrute_device_free_protocol_info(instance);
-        instance->protocol_info = subbrute_protocol(type);
-    }
-
-    instance->extra_repeats = extra_repeats;
-
-    // For non-file types we didn't set SubGhzProtocolDecoderBase
-    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;
-#ifdef FURI_DEBUG
-    uint8_t bits;
-    uint32_t te;
-    uint8_t repeat;
-    FuriHalSubGhzPreset preset;
-    SubBruteFileProtocol file;
-#endif
-    if(type != SubBruteAttackLoadFile) {
-        instance->decoder_result = subghz_receiver_search_decoder_base_by_name(
-            instance->receiver, subbrute_protocol_file(instance->protocol_info->file));
-
-        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;
-
-            // Calc max value
-            instance->max_value = subbrute_protocol_calc_max_value(
-                instance->attack, instance->protocol_info->bits, instance->two_bytes);
-        }
-#ifdef FURI_DEBUG
-        bits = instance->protocol_info->bits;
-        te = instance->protocol_info->te;
-        repeat = instance->protocol_info->repeat + instance->extra_repeats;
-        preset = instance->protocol_info->preset;
-        file = instance->protocol_info->file;
-#endif
-    } else {
-        // And here we need to set preset enum
-        protocol_check_result = SubBruteFileResultOk;
-
-        // Calc max value
-        instance->max_value = subbrute_protocol_calc_max_value(
-            instance->attack, instance->file_protocol_info->bits, instance->two_bytes);
-#ifdef FURI_DEBUG
-        bits = instance->file_protocol_info->bits;
-        te = instance->file_protocol_info->te;
-        repeat = instance->file_protocol_info->repeat + instance->extra_repeats;
-        preset = instance->file_protocol_info->preset;
-        file = instance->file_protocol_info->file;
-#endif
-    }
-
-    subghz_receiver_free(instance->receiver);
-    instance->receiver = NULL;
-
-    if(protocol_check_result != SubBruteFileResultOk) {
-        return SubBruteFileResultProtocolNotFound;
-    }
-
-#ifdef FURI_DEBUG
-    FURI_LOG_I(
-        TAG,
-        "subbrute_device_attack_set: %s, bits: %d, preset: %s, file: %s, te: %ld, repeat: %d, max_value: %lld",
-        subbrute_protocol_name(instance->attack),
-        bits,
-        subbrute_protocol_preset(preset),
-        subbrute_protocol_file(file),
-        te,
-        repeat,
-        instance->max_value);
-#endif
-
-    return SubBruteFileResultOk;
-}
-
-uint8_t subbrute_device_load_from_file(SubBruteDevice* instance, const char* file_path) {
-    furi_assert(instance);
-#ifdef FURI_DEBUG
-    FURI_LOG_D(TAG, "subbrute_device_load_from_file: %s", file_path);
-#endif
-    SubBruteFileResult result = SubBruteFileResultUnknown;
-
-    Storage* storage = furi_record_open(RECORD_STORAGE);
-    FlipperFormat* fff_data_file = flipper_format_file_alloc(storage);
-
-    subbrute_device_free_protocol_info(instance);
-    instance->file_protocol_info = malloc(sizeof(SubBruteProtocol));
-
-    FuriString* temp_str;
-    temp_str = furi_string_alloc();
-    uint32_t temp_data32;
-
-    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, file_path)) {
-            FURI_LOG_E(TAG, "Error open file %s", 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)) {
-            FURI_LOG_E(TAG, "Missing or incorrect Frequency");
-            result = SubBruteFileResultMissingOrIncorrectFrequency;
-            break;
-        }
-        instance->file_protocol_info->frequency = temp_data32;
-        if(!furi_hal_subghz_is_tx_allowed(instance->file_protocol_info->frequency)) {
-            result = SubBruteFileResultFrequencyNotAllowed;
-            break;
-        }
-
-        // Preset
-        if(!flipper_format_read_string(fff_data_file, "Preset", temp_str)) {
-            FURI_LOG_E(TAG, "Preset FAIL");
-            result = SubBruteFileResultPresetInvalid;
-            break;
-        }
-        instance->file_protocol_info->preset = subbrute_protocol_convert_preset(temp_str);
-
-        const char* protocol_file = NULL;
-        // Protocol
-        if(!flipper_format_read_string(fff_data_file, "Protocol", temp_str)) {
-            FURI_LOG_E(TAG, "Missing Protocol");
-            result = SubBruteFileResultMissingProtocol;
-            break;
-        }
-        instance->file_protocol_info->file = subbrute_protocol_file_protocol_name(temp_str);
-        protocol_file = subbrute_protocol_file(instance->file_protocol_info->file);
-#ifdef FURI_DEBUG
-        FURI_LOG_D(TAG, "Protocol: %s", protocol_file);
-#endif
-
-        instance->decoder_result = subghz_receiver_search_decoder_base_by_name(
-            instance->receiver, furi_string_get_cstr(temp_str));
-
-        if((!instance->decoder_result) || (strcmp(protocol_file, "RAW") == 0) ||
-           (strcmp(protocol_file, "Unknown") == 0)) {
-            FURI_LOG_E(TAG, "Protocol 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
-        FURI_LOG_D(TAG, "Decoder: %s", instance->decoder_result->protocol->name);
-#endif
-
-        // Bit
-        if(!flipper_format_read_uint32(fff_data_file, "Bit", &temp_data32, 1)) {
-            FURI_LOG_E(TAG, "Missing or incorrect Bit");
-            result = SubBruteFileResultMissingOrIncorrectBit;
-            break;
-        }
-        instance->file_protocol_info->bits = temp_data32;
-#ifdef FURI_DEBUG
-        FURI_LOG_D(TAG, "Bit: %d", instance->file_protocol_info->bits);
-#endif
-
-        uint8_t key_data[sizeof(uint64_t)] = {0};
-        if(!flipper_format_read_hex(fff_data_file, "Key", key_data, sizeof(uint64_t))) {
-            FURI_LOG_E(TAG, "Missing Key");
-            result = SubBruteFileResultMissingOrIncorrectKey;
-            break;
-        }
-        uint64_t data = 0;
-        for(uint8_t i = 0; i < sizeof(uint64_t); i++) {
-            data = (data << 8) | key_data[i];
-        }
-#if FURI_DEBUG
-        FURI_LOG_D(TAG, "Key: %.16llX", data);
-#endif
-        instance->key_from_file = data;
-
-        // 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->file_protocol_info->te = temp_data32 != 0 ? temp_data32 : 0;
-        }
-
-        // Repeat
-        if(flipper_format_read_uint32(fff_data_file, "Repeat", &temp_data32, 1)) {
-#ifdef FURI_DEBUG
-            FURI_LOG_D(TAG, "Repeat: %ld", temp_data32);
-#endif
-            instance->file_protocol_info->repeat = (uint8_t)temp_data32;
-        } else {
-#ifdef FURI_DEBUG
-            FURI_LOG_D(TAG, "Repeat: 3 (default)");
-#endif
-            instance->file_protocol_info->repeat = 3;
-        }
-
-        result = SubBruteFileResultOk;
-    } while(0);
-
-    furi_string_free(temp_str);
-    flipper_format_file_close(fff_data_file);
-    flipper_format_free(fff_data_file);
-    furi_record_close(RECORD_STORAGE);
-
-    subghz_receiver_free(instance->receiver);
-
-    instance->decoder_result = NULL;
-    instance->receiver = NULL;
-
-    if(result == SubBruteFileResultOk) {
-#ifdef FURI_DEBUG
-        FURI_LOG_D(TAG, "Loaded successfully");
-#endif
-    } else {
-        subbrute_device_free_protocol_info(instance);
-    }
-
-    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->current_step = 0x00;
-    instance->bit_index = 0x00;
-    instance->extra_repeats = 0;
-    instance->two_bytes = false;
-
-    if(default_attack != SubBruteAttackLoadFile) {
-        instance->max_value = subbrute_protocol_calc_max_value(
-            instance->attack, instance->bit_index, instance->two_bytes);
-    }
-}
-
-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 = "Protocol 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;
-}
-
-void subbrute_device_free_protocol_info(SubBruteDevice* instance) {
-    furi_assert(instance);
-    instance->protocol_info = NULL;
-    if(instance->file_protocol_info) {
-        free(instance->file_protocol_info);
-    }
-    instance->file_protocol_info = NULL;
+#include "subbrute_device.h"
+
+#include <stdint.h>
+#include <storage/storage.h>
+#include <lib/toolbox/stream/stream.h>
+#include <lib/flipper_format/flipper_format.h>
+#include <lib/flipper_format/flipper_format_i.h>
+#include <lib/subghz/protocols/protocol_items.h>
+
+#define TAG "SubBruteDevice"
+
+SubBruteDevice* subbrute_device_alloc() {
+    SubBruteDevice* instance = malloc(sizeof(SubBruteDevice));
+
+    instance->current_step = 0;
+
+    instance->protocol_info = NULL;
+    instance->file_protocol_info = NULL;
+    instance->decoder_result = NULL;
+    instance->receiver = NULL;
+    instance->environment = subghz_environment_alloc();
+    subghz_environment_set_protocol_registry(
+        instance->environment, (void*)&subghz_protocol_registry);
+
+#ifdef FURI_DEBUG
+    subbrute_device_attack_set_default_values(instance, SubBruteAttackLoadFile);
+#else
+    subbrute_device_attack_set_default_values(instance, SubBruteAttackCAME12bit433);
+#endif
+    return instance;
+}
+
+void subbrute_device_free(SubBruteDevice* instance) {
+    furi_assert(instance);
+
+    // I don't know how to free this
+    instance->decoder_result = NULL;
+
+    if(instance->receiver != NULL) {
+        subghz_receiver_free(instance->receiver);
+        instance->receiver = NULL;
+    }
+
+    subghz_environment_free(instance->environment);
+    instance->environment = NULL;
+
+    subbrute_device_free_protocol_info(instance);
+
+    free(instance);
+}
+
+uint64_t subbrute_device_add_step(SubBruteDevice* instance, int8_t step) {
+    if(step > 0) {
+        if((instance->current_step + step) - instance->max_value == 1) {
+            instance->current_step = 0x00;
+        } else {
+            uint64_t value = instance->current_step + step;
+            if(value == instance->max_value) {
+                instance->current_step = value;
+            } else {
+                instance->current_step = value % instance->max_value;
+            }
+        }
+    } else {
+        if(instance->current_step + step == 0) {
+            instance->current_step = 0x00;
+        } else if(instance->current_step == 0) {
+            instance->current_step = instance->max_value;
+        } else {
+            uint64_t value = ((instance->current_step + step) + instance->max_value);
+            if(value == instance->max_value) {
+                instance->current_step = value;
+            } else {
+                instance->current_step = value % instance->max_value;
+            }
+        }
+    }
+
+    return instance->current_step;
+}
+
+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
+
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    FlipperFormat* file = flipper_format_file_alloc(storage);
+    bool result = false;
+    do {
+        if(!flipper_format_file_open_always(file, dev_file_name)) {
+            FURI_LOG_E(TAG, "Failed to open file: %s", dev_file_name);
+            break;
+        }
+        Stream* stream = flipper_format_get_raw_stream(file);
+        if(instance->attack == SubBruteAttackLoadFile) {
+            subbrute_protocol_file_generate_file(
+                stream,
+                instance->file_protocol_info->frequency,
+                instance->file_protocol_info->preset,
+                instance->file_protocol_info->file,
+                instance->current_step,
+                instance->file_protocol_info->bits,
+                instance->file_protocol_info->te,
+                instance->bit_index,
+                instance->key_from_file,
+                instance->two_bytes);
+        } else {
+            subbrute_protocol_default_generate_file(
+                stream,
+                instance->protocol_info->frequency,
+                instance->protocol_info->preset,
+                instance->protocol_info->file,
+                instance->current_step,
+                instance->protocol_info->bits,
+                instance->protocol_info->te);
+        }
+
+        result = true;
+    } while(false);
+
+    if(!result) {
+        FURI_LOG_E(TAG, "subbrute_device_save_file failed!");
+    }
+
+    flipper_format_file_close(file);
+    flipper_format_free(file);
+    furi_record_close(RECORD_STORAGE);
+
+    return result;
+}
+
+SubBruteFileResult subbrute_device_attack_set(
+    SubBruteDevice* instance,
+    SubBruteAttacks type,
+    uint8_t extra_repeats) {
+    furi_assert(instance);
+#ifdef FURI_DEBUG
+    FURI_LOG_D(TAG, "subbrute_device_attack_set: %d, extra_repeats: %d", type, extra_repeats);
+#endif
+    subbrute_device_attack_set_default_values(instance, type);
+
+    if(type != SubBruteAttackLoadFile) {
+        subbrute_device_free_protocol_info(instance);
+        instance->protocol_info = subbrute_protocol(type);
+    }
+
+    instance->extra_repeats = extra_repeats;
+
+    // For non-file types we didn't set SubGhzProtocolDecoderBase
+    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;
+#ifdef FURI_DEBUG
+    uint8_t bits;
+    uint32_t te;
+    uint8_t repeat;
+    FuriHalSubGhzPreset preset;
+    SubBruteFileProtocol file;
+#endif
+    if(type != SubBruteAttackLoadFile) {
+        instance->decoder_result = subghz_receiver_search_decoder_base_by_name(
+            instance->receiver, subbrute_protocol_file(instance->protocol_info->file));
+
+        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;
+
+            // Calc max value
+            instance->max_value = subbrute_protocol_calc_max_value(
+                instance->attack, instance->protocol_info->bits, instance->two_bytes);
+        }
+#ifdef FURI_DEBUG
+        bits = instance->protocol_info->bits;
+        te = instance->protocol_info->te;
+        repeat = instance->protocol_info->repeat + instance->extra_repeats;
+        preset = instance->protocol_info->preset;
+        file = instance->protocol_info->file;
+#endif
+    } else {
+        // And here we need to set preset enum
+        protocol_check_result = SubBruteFileResultOk;
+
+        // Calc max value
+        instance->max_value = subbrute_protocol_calc_max_value(
+            instance->attack, instance->file_protocol_info->bits, instance->two_bytes);
+#ifdef FURI_DEBUG
+        bits = instance->file_protocol_info->bits;
+        te = instance->file_protocol_info->te;
+        repeat = instance->file_protocol_info->repeat + instance->extra_repeats;
+        preset = instance->file_protocol_info->preset;
+        file = instance->file_protocol_info->file;
+#endif
+    }
+
+    subghz_receiver_free(instance->receiver);
+    instance->receiver = NULL;
+
+    if(protocol_check_result != SubBruteFileResultOk) {
+        return SubBruteFileResultProtocolNotFound;
+    }
+
+#ifdef FURI_DEBUG
+    FURI_LOG_I(
+        TAG,
+        "subbrute_device_attack_set: %s, bits: %d, preset: %s, file: %s, te: %ld, repeat: %d, max_value: %lld",
+        subbrute_protocol_name(instance->attack),
+        bits,
+        subbrute_protocol_preset(preset),
+        subbrute_protocol_file(file),
+        te,
+        repeat,
+        instance->max_value);
+#endif
+
+    return SubBruteFileResultOk;
+}
+
+uint8_t subbrute_device_load_from_file(SubBruteDevice* instance, const char* file_path) {
+    furi_assert(instance);
+#ifdef FURI_DEBUG
+    FURI_LOG_D(TAG, "subbrute_device_load_from_file: %s", file_path);
+#endif
+    SubBruteFileResult result = SubBruteFileResultUnknown;
+
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    FlipperFormat* fff_data_file = flipper_format_file_alloc(storage);
+
+    subbrute_device_free_protocol_info(instance);
+    instance->file_protocol_info = malloc(sizeof(SubBruteProtocol));
+
+    FuriString* temp_str;
+    temp_str = furi_string_alloc();
+    uint32_t temp_data32;
+
+    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, file_path)) {
+            FURI_LOG_E(TAG, "Error open file %s", 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)) {
+            FURI_LOG_E(TAG, "Missing or incorrect Frequency");
+            result = SubBruteFileResultMissingOrIncorrectFrequency;
+            break;
+        }
+        instance->file_protocol_info->frequency = temp_data32;
+        if(!furi_hal_subghz_is_tx_allowed(instance->file_protocol_info->frequency)) {
+            result = SubBruteFileResultFrequencyNotAllowed;
+            break;
+        }
+
+        // Preset
+        if(!flipper_format_read_string(fff_data_file, "Preset", temp_str)) {
+            FURI_LOG_E(TAG, "Preset FAIL");
+            result = SubBruteFileResultPresetInvalid;
+            break;
+        }
+        instance->file_protocol_info->preset = subbrute_protocol_convert_preset(temp_str);
+
+        const char* protocol_file = NULL;
+        // Protocol
+        if(!flipper_format_read_string(fff_data_file, "Protocol", temp_str)) {
+            FURI_LOG_E(TAG, "Missing Protocol");
+            result = SubBruteFileResultMissingProtocol;
+            break;
+        }
+        instance->file_protocol_info->file = subbrute_protocol_file_protocol_name(temp_str);
+        protocol_file = subbrute_protocol_file(instance->file_protocol_info->file);
+#ifdef FURI_DEBUG
+        FURI_LOG_D(TAG, "Protocol: %s", protocol_file);
+#endif
+
+        instance->decoder_result = subghz_receiver_search_decoder_base_by_name(
+            instance->receiver, furi_string_get_cstr(temp_str));
+
+        if((!instance->decoder_result) || (strcmp(protocol_file, "RAW") == 0) ||
+           (strcmp(protocol_file, "Unknown") == 0)) {
+            FURI_LOG_E(TAG, "Protocol 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
+        FURI_LOG_D(TAG, "Decoder: %s", instance->decoder_result->protocol->name);
+#endif
+
+        // Bit
+        if(!flipper_format_read_uint32(fff_data_file, "Bit", &temp_data32, 1)) {
+            FURI_LOG_E(TAG, "Missing or incorrect Bit");
+            result = SubBruteFileResultMissingOrIncorrectBit;
+            break;
+        }
+        instance->file_protocol_info->bits = temp_data32;
+#ifdef FURI_DEBUG
+        FURI_LOG_D(TAG, "Bit: %d", instance->file_protocol_info->bits);
+#endif
+
+        uint8_t key_data[sizeof(uint64_t)] = {0};
+        if(!flipper_format_read_hex(fff_data_file, "Key", key_data, sizeof(uint64_t))) {
+            FURI_LOG_E(TAG, "Missing Key");
+            result = SubBruteFileResultMissingOrIncorrectKey;
+            break;
+        }
+        uint64_t data = 0;
+        for(uint8_t i = 0; i < sizeof(uint64_t); i++) {
+            data = (data << 8) | key_data[i];
+        }
+#if FURI_DEBUG
+        FURI_LOG_D(TAG, "Key: %.16llX", data);
+#endif
+        instance->key_from_file = data;
+
+        // 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->file_protocol_info->te = temp_data32 != 0 ? temp_data32 : 0;
+        }
+
+        // Repeat
+        if(flipper_format_read_uint32(fff_data_file, "Repeat", &temp_data32, 1)) {
+#ifdef FURI_DEBUG
+            FURI_LOG_D(TAG, "Repeat: %ld", temp_data32);
+#endif
+            instance->file_protocol_info->repeat = (uint8_t)temp_data32;
+        } else {
+#ifdef FURI_DEBUG
+            FURI_LOG_D(TAG, "Repeat: 3 (default)");
+#endif
+            instance->file_protocol_info->repeat = 3;
+        }
+
+        result = SubBruteFileResultOk;
+    } while(0);
+
+    furi_string_free(temp_str);
+    flipper_format_file_close(fff_data_file);
+    flipper_format_free(fff_data_file);
+    furi_record_close(RECORD_STORAGE);
+
+    subghz_receiver_free(instance->receiver);
+
+    instance->decoder_result = NULL;
+    instance->receiver = NULL;
+
+    if(result == SubBruteFileResultOk) {
+#ifdef FURI_DEBUG
+        FURI_LOG_D(TAG, "Loaded successfully");
+#endif
+    } else {
+        subbrute_device_free_protocol_info(instance);
+    }
+
+    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->current_step = 0x00;
+    instance->bit_index = 0x00;
+    instance->extra_repeats = 0;
+    instance->two_bytes = false;
+
+    if(default_attack != SubBruteAttackLoadFile) {
+        instance->max_value = subbrute_protocol_calc_max_value(
+            instance->attack, instance->bit_index, instance->two_bytes);
+    }
+}
+
+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 = "Protocol 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;
+}
+
+void subbrute_device_free_protocol_info(SubBruteDevice* instance) {
+    furi_assert(instance);
+    instance->protocol_info = NULL;
+    if(instance->file_protocol_info) {
+        free(instance->file_protocol_info);
+    }
+    instance->file_protocol_info = NULL;
 }
 }

+ 74 - 74
subbrute_device.h

@@ -1,75 +1,75 @@
-#pragma once
-
-#include "subbrute_protocols.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 {
-    SubBruteFileResultUnknown,
-    SubBruteFileResultOk,
-    SubBruteFileResultErrorOpenFile,
-    SubBruteFileResultMissingOrIncorrectHeader,
-    SubBruteFileResultFrequencyNotAllowed,
-    SubBruteFileResultMissingOrIncorrectFrequency,
-    SubBruteFileResultPresetInvalid,
-    SubBruteFileResultMissingProtocol,
-    SubBruteFileResultProtocolNotSupported,
-    SubBruteFileResultDynamicProtocolNotValid,
-    SubBruteFileResultProtocolNotFound,
-    SubBruteFileResultMissingOrIncorrectBit,
-    SubBruteFileResultMissingOrIncorrectKey,
-    SubBruteFileResultMissingOrIncorrectTe,
-} SubBruteFileResult;
-
-typedef struct {
-    const SubBruteProtocol* protocol_info;
-    SubBruteProtocol* file_protocol_info;
-
-    // Current step
-    uint64_t current_step;
-
-    // SubGhz
-    SubGhzReceiver* receiver;
-    SubGhzProtocolDecoderBase* decoder_result;
-    SubGhzEnvironment* environment;
-
-    // Attack state
-    SubBruteAttacks attack;
-    uint64_t max_value;
-    uint8_t extra_repeats;
-
-    // Loaded info for attack type
-    uint64_t key_from_file;
-    uint64_t current_key_from_file;
-    bool two_bytes;
-    // Index of group to bruteforce in loaded file
-    uint8_t bit_index;
-} 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);
-SubBruteFileResult subbrute_device_attack_set(
-    SubBruteDevice* context,
-    SubBruteAttacks type,
-    uint8_t extra_repeats);
-uint8_t subbrute_device_load_from_file(SubBruteDevice* context, const char* file_path);
-
-uint64_t subbrute_device_add_step(SubBruteDevice* instance, int8_t step);
-
-void subbrute_device_free_protocol_info(SubBruteDevice* instance);
-void subbrute_device_attack_set_default_values(
-    SubBruteDevice* context,
+#pragma once
+
+#include "subbrute_protocols.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 {
+    SubBruteFileResultUnknown,
+    SubBruteFileResultOk,
+    SubBruteFileResultErrorOpenFile,
+    SubBruteFileResultMissingOrIncorrectHeader,
+    SubBruteFileResultFrequencyNotAllowed,
+    SubBruteFileResultMissingOrIncorrectFrequency,
+    SubBruteFileResultPresetInvalid,
+    SubBruteFileResultMissingProtocol,
+    SubBruteFileResultProtocolNotSupported,
+    SubBruteFileResultDynamicProtocolNotValid,
+    SubBruteFileResultProtocolNotFound,
+    SubBruteFileResultMissingOrIncorrectBit,
+    SubBruteFileResultMissingOrIncorrectKey,
+    SubBruteFileResultMissingOrIncorrectTe,
+} SubBruteFileResult;
+
+typedef struct {
+    const SubBruteProtocol* protocol_info;
+    SubBruteProtocol* file_protocol_info;
+
+    // Current step
+    uint64_t current_step;
+
+    // SubGhz
+    SubGhzReceiver* receiver;
+    SubGhzProtocolDecoderBase* decoder_result;
+    SubGhzEnvironment* environment;
+
+    // Attack state
+    SubBruteAttacks attack;
+    uint64_t max_value;
+    uint8_t extra_repeats;
+
+    // Loaded info for attack type
+    uint64_t key_from_file;
+    uint64_t current_key_from_file;
+    bool two_bytes;
+    // Index of group to bruteforce in loaded file
+    uint8_t bit_index;
+} 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);
+SubBruteFileResult subbrute_device_attack_set(
+    SubBruteDevice* context,
+    SubBruteAttacks type,
+    uint8_t extra_repeats);
+uint8_t subbrute_device_load_from_file(SubBruteDevice* context, const char* file_path);
+
+uint64_t subbrute_device_add_step(SubBruteDevice* instance, int8_t step);
+
+void subbrute_device_free_protocol_info(SubBruteDevice* instance);
+void subbrute_device_attack_set_default_values(
+    SubBruteDevice* context,
     SubBruteAttacks default_attack);
     SubBruteAttacks default_attack);

+ 79 - 79
subbrute_i.h

@@ -1,80 +1,80 @@
-#pragma once
-
-#include <furi.h>
-#include <furi_hal.h>
-#include <input/input.h>
-
-#include <notification/notification.h>
-#include <notification/notification_messages.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 "subghz_bruteforcer_icons.h"
-
-#include <dialogs/dialogs.h>
-
-#include <notification/notification.h>
-#include <notification/notification_messages.h>
-
-#include "subbrute.h"
-#include "subbrute_device.h"
-#include "helpers/subbrute_worker.h"
-#include "views/subbrute_attack_view.h"
-#include "views/subbrute_main_view.h"
-
-#define SUBBRUTEFORCER_VER "Sub-GHz BruteForcer 3.5"
-
-#ifdef FURI_DEBUG
-//#define SUBBRUTE_FAST_TRACK false
-#endif
-
-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;
-
-    // Text store
-    char text_store[SUBBRUTE_MAX_LEN_NAME];
-    FuriString* file_path;
-
-    // Views
-    SubBruteMainView* view_main;
-    SubBruteAttackView* view_attack;
-    SubBruteView current_view;
-
-    // Scene
-    SceneManager* scene_manager;
-
-    // SubBruteDevice
-    SubBruteDevice* device;
-    // SubBruteWorker
-    SubBruteWorker* worker;
-};
-
-void subbrute_show_loading_popup(void* context, bool show);
-void subbrute_text_input_callback(void* context);
+#pragma once
+
+#include <furi.h>
+#include <furi_hal.h>
+#include <input/input.h>
+
+#include <notification/notification.h>
+#include <notification/notification_messages.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 "subghz_bruteforcer_icons.h"
+
+#include <dialogs/dialogs.h>
+
+#include <notification/notification.h>
+#include <notification/notification_messages.h>
+
+#include "subbrute.h"
+#include "subbrute_device.h"
+#include "helpers/subbrute_worker.h"
+#include "views/subbrute_attack_view.h"
+#include "views/subbrute_main_view.h"
+
+#define SUBBRUTEFORCER_VER "Sub-GHz BruteForcer 3.6"
+
+#ifdef FURI_DEBUG
+//#define SUBBRUTE_FAST_TRACK false
+#endif
+
+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;
+
+    // Text store
+    char text_store[SUBBRUTE_MAX_LEN_NAME];
+    FuriString* file_path;
+
+    // Views
+    SubBruteMainView* view_main;
+    SubBruteAttackView* view_attack;
+    SubBruteView current_view;
+
+    // Scene
+    SceneManager* scene_manager;
+
+    // SubBruteDevice
+    SubBruteDevice* device;
+    // SubBruteWorker
+    SubBruteWorker* worker;
+};
+
+void subbrute_show_loading_popup(void* context, bool show);
+void subbrute_text_input_callback(void* context);
 void subbrute_popup_closed_callback(void* context);
 void subbrute_popup_closed_callback(void* context);

+ 875 - 875
subbrute_protocols.c

@@ -1,876 +1,876 @@
-#include "subbrute_protocols.h"
-#include "math.h"
-#include <string.h>
-
-#define TAG "SubBruteProtocols"
-
-/**
- * CAME 12bit 303MHz
- */
-const SubBruteProtocol subbrute_protocol_came_12bit_303 = {
-    .frequency = 303875000,
-    .bits = 12,
-    .te = 0,
-    .repeat = 3,
-    .preset = FuriHalSubGhzPresetOok650Async,
-    .file = CAMEFileProtocol};
-
-/**
- * CAME 12bit 307MHz
- */
-const SubBruteProtocol subbrute_protocol_came_12bit_307 = {
-    .frequency = 307800000,
-    .bits = 12,
-    .te = 0,
-    .repeat = 3,
-    .preset = FuriHalSubGhzPresetOok650Async,
-    .file = CAMEFileProtocol};
-
-/**
- * CAME 12bit 315MHz
- */
-const SubBruteProtocol subbrute_protocol_came_12bit_315 = {
-    .frequency = 315000000,
-    .bits = 12,
-    .te = 0,
-    .repeat = 3,
-    .preset = FuriHalSubGhzPresetOok650Async,
-    .file = CAMEFileProtocol};
-
-/**
- * CAME 12bit 433MHz
- */
-const SubBruteProtocol subbrute_protocol_came_12bit_433 = {
-    .frequency = 433920000,
-    .bits = 12,
-    .te = 0,
-    .repeat = 3,
-    .preset = FuriHalSubGhzPresetOok650Async,
-    .file = CAMEFileProtocol};
-
-/**
- * CAME 12bit 868MHz
- */
-const SubBruteProtocol subbrute_protocol_came_12bit_868 = {
-    .frequency = 868350000,
-    .bits = 12,
-    .te = 0,
-    .repeat = 3,
-    .preset = FuriHalSubGhzPresetOok650Async,
-    .file = CAMEFileProtocol};
-
-/**
- * NICE 12bit 433MHz
- */
-const SubBruteProtocol subbrute_protocol_nice_12bit_433 = {
-    .frequency = 433920000,
-    .bits = 12,
-    .te = 0,
-    .repeat = 3,
-    .preset = FuriHalSubGhzPresetOok650Async,
-    .file = NICEFileProtocol};
-
-/**
- * NICE 12bit 868MHz
- */
-const SubBruteProtocol subbrute_protocol_nice_12bit_868 = {
-    .frequency = 868350000,
-    .bits = 12,
-    .te = 0,
-    .repeat = 3,
-    .preset = FuriHalSubGhzPresetOok650Async,
-    .file = NICEFileProtocol};
-
-/**
- * Ansonic 12bit 433.075MHz
- */
-const SubBruteProtocol subbrute_protocol_ansonic_12bit_433075 = {
-    .frequency = 433075000,
-    .bits = 12,
-    .te = 0,
-    .repeat = 3,
-    .preset = FuriHalSubGhzPreset2FSKDev238Async,
-    .file = AnsonicFileProtocol};
-
-/**
- * Ansonic 12bit 433.92MHz
- */
-const SubBruteProtocol subbrute_protocol_ansonic_12bit_433 = {
-    .frequency = 433920000,
-    .bits = 12,
-    .te = 0,
-    .repeat = 3,
-    .preset = FuriHalSubGhzPreset2FSKDev238Async,
-    .file = AnsonicFileProtocol};
-
-/**
- * Ansonic 12bit 434.075MHz
- */
-const SubBruteProtocol subbrute_protocol_ansonic_12bit_434 = {
-    .frequency = 434075000,
-    .bits = 12,
-    .te = 0,
-    .repeat = 3,
-    .preset = FuriHalSubGhzPreset2FSKDev238Async,
-    .file = AnsonicFileProtocol};
-
-/**
- * Chamberlain 9bit 300MHz
- */
-const SubBruteProtocol subbrute_protocol_chamberlain_9bit_300 = {
-    .frequency = 300000000,
-    .bits = 9,
-    .te = 0,
-    .repeat = 3,
-    .preset = FuriHalSubGhzPresetOok650Async,
-    .file = ChamberlainFileProtocol};
-
-/**
- * Chamberlain 9bit 315MHz
- */
-const SubBruteProtocol subbrute_protocol_chamberlain_9bit_315 = {
-    .frequency = 315000000,
-    .bits = 9,
-    .te = 0,
-    .repeat = 3,
-    .preset = FuriHalSubGhzPresetOok650Async,
-    .file = ChamberlainFileProtocol};
-
-/**
- * Chamberlain 9bit 390MHz
- */
-const SubBruteProtocol subbrute_protocol_chamberlain_9bit_390 = {
-    .frequency = 390000000,
-    .bits = 9,
-    .te = 0,
-    .repeat = 3,
-    .preset = FuriHalSubGhzPresetOok650Async,
-    .file = ChamberlainFileProtocol};
-
-/**
- * Chamberlain 9bit 433MHz
- */
-const SubBruteProtocol subbrute_protocol_chamberlain_9bit_433 = {
-    .frequency = 433920000,
-    .bits = 9,
-    .te = 0,
-    .repeat = 3,
-    .preset = FuriHalSubGhzPresetOok650Async,
-    .file = ChamberlainFileProtocol};
-
-/**
- * Chamberlain 8bit 300MHz
- */
-const SubBruteProtocol subbrute_protocol_chamberlain_8bit_300 = {
-    .frequency = 300000000,
-    .bits = 8,
-    .te = 0,
-    .repeat = 3,
-    .preset = FuriHalSubGhzPresetOok650Async,
-    .file = ChamberlainFileProtocol};
-
-/**
- * Chamberlain 8bit 315MHz
- */
-const SubBruteProtocol subbrute_protocol_chamberlain_8bit_315 = {
-    .frequency = 315000000,
-    .bits = 8,
-    .te = 0,
-    .repeat = 3,
-    .preset = FuriHalSubGhzPresetOok650Async,
-    .file = ChamberlainFileProtocol};
-
-/**
- * Chamberlain 8bit 390MHz
- */
-const SubBruteProtocol subbrute_protocol_chamberlain_8bit_390 = {
-    .frequency = 390000000,
-    .bits = 8,
-    .te = 0,
-    .repeat = 3,
-    .preset = FuriHalSubGhzPresetOok650Async,
-    .file = ChamberlainFileProtocol};
-
-/**
- * Chamberlain 7bit 300MHz
- */
-const SubBruteProtocol subbrute_protocol_chamberlain_7bit_300 = {
-    .frequency = 300000000,
-    .bits = 7,
-    .te = 0,
-    .repeat = 3,
-    .preset = FuriHalSubGhzPresetOok650Async,
-    .file = ChamberlainFileProtocol};
-
-/**
- * Chamberlain 7bit 315MHz
- */
-const SubBruteProtocol subbrute_protocol_chamberlain_7bit_315 = {
-    .frequency = 315000000,
-    .bits = 7,
-    .te = 0,
-    .repeat = 3,
-    .preset = FuriHalSubGhzPresetOok650Async,
-    .file = ChamberlainFileProtocol};
-
-/**
- * Chamberlain 7bit 390MHz
- */
-const SubBruteProtocol subbrute_protocol_chamberlain_7bit_390 = {
-    .frequency = 390000000,
-    .bits = 7,
-    .te = 0,
-    .repeat = 3,
-    .preset = FuriHalSubGhzPresetOok650Async,
-    .file = ChamberlainFileProtocol};
-
-/**
- * Linear 10bit 300MHz
- */
-const SubBruteProtocol subbrute_protocol_linear_10bit_300 = {
-    .frequency = 300000000,
-    .bits = 10,
-    .te = 0,
-    .repeat = 5,
-    .preset = FuriHalSubGhzPresetOok650Async,
-    .file = LinearFileProtocol};
-
-/**
- * Linear 10bit 310MHz
- */
-const SubBruteProtocol subbrute_protocol_linear_10bit_310 = {
-    .frequency = 310000000,
-    .bits = 10,
-    .te = 0,
-    .repeat = 5,
-    .preset = FuriHalSubGhzPresetOok650Async,
-    .file = LinearFileProtocol};
-
-/**
- * Linear Delta 3 8bit 310MHz
- */
-const SubBruteProtocol subbrute_protocol_linear_delta_8bit_310 = {
-    .frequency = 310000000,
-    .bits = 8,
-    .te = 0,
-    .repeat = 5,
-    .preset = FuriHalSubGhzPresetOok650Async,
-    .file = LinearDeltaFileProtocol};
-
-/**
- * UNILARM 24bit 330MHz
- */
-const SubBruteProtocol subbrute_protocol_unilarm_24bit_330 = {
-    .frequency = 330000000,
-    .bits = 25,
-    .te = 209,
-    .repeat = 4,
-    .preset = FuriHalSubGhzPresetOok650Async,
-    .file = UNILARMFileProtocol};
-
-/**
- * UNILARM 24bit 433MHz
- */
-const SubBruteProtocol subbrute_protocol_unilarm_24bit_433 = {
-    .frequency = 433920000,
-    .bits = 25,
-    .te = 209,
-    .repeat = 4,
-    .preset = FuriHalSubGhzPresetOok650Async,
-    .file = UNILARMFileProtocol};
-
-/**
- * SMC5326 24bit 330MHz
- */
-const SubBruteProtocol subbrute_protocol_smc5326_24bit_330 = {
-    .frequency = 330000000,
-    .bits = 25,
-    .te = 320,
-    .repeat = 4,
-    .preset = FuriHalSubGhzPresetOok650Async,
-    .file = SMC5326FileProtocol};
-
-/**
- * SMC5326 24bit 433MHz
- */
-const SubBruteProtocol subbrute_protocol_smc5326_24bit_433 = {
-    .frequency = 433920000,
-    .bits = 25,
-    .te = 320,
-    .repeat = 4,
-    .preset = FuriHalSubGhzPresetOok650Async,
-    .file = SMC5326FileProtocol};
-
-/**
- * PT2260 (Princeton) 24bit 315MHz
- */
-const SubBruteProtocol subbrute_protocol_pt2260_24bit_315 = {
-    .frequency = 315000000,
-    .bits = 24,
-    .te = 286,
-    .repeat = 4,
-    .preset = FuriHalSubGhzPresetOok650Async,
-    .file = PT2260FileProtocol};
-
-/**
- * PT2260 (Princeton) 24bit 330MHz
- */
-const SubBruteProtocol subbrute_protocol_pt2260_24bit_330 = {
-    .frequency = 330000000,
-    .bits = 24,
-    .te = 286,
-    .repeat = 4,
-    .preset = FuriHalSubGhzPresetOok650Async,
-    .file = PT2260FileProtocol};
-
-/**
- * PT2260 (Princeton) 24bit 390MHz
- */
-const SubBruteProtocol subbrute_protocol_pt2260_24bit_390 = {
-    .frequency = 390000000,
-    .bits = 24,
-    .te = 286,
-    .repeat = 4,
-    .preset = FuriHalSubGhzPresetOok650Async,
-    .file = PT2260FileProtocol};
-
-/**
- * PT2260 (Princeton) 24bit 433MHz
- */
-const SubBruteProtocol subbrute_protocol_pt2260_24bit_433 = {
-    .frequency = 433920000,
-    .bits = 24,
-    .te = 286,
-    .repeat = 4,
-    .preset = FuriHalSubGhzPresetOok650Async,
-    .file = PT2260FileProtocol};
-
-/**
- * Holtek FM 12bit 433MHz
- */
-const SubBruteProtocol subbrute_protocol_holtek_12bit_433 = {
-    .frequency = 433920000,
-    .bits = 12,
-    .te = 204,
-    .repeat = 4,
-    .preset = FuriHalSubGhzPreset2FSKDev476Async,
-    .file = HoltekFileProtocol};
-
-/**
- * Holtek AM 12bit 433MHz
- */
-const SubBruteProtocol subbrute_protocol_holtek_12bit_am_433 = {
-    .frequency = 433920000,
-    .bits = 12,
-    .te = 433,
-    .repeat = 3,
-    .preset = FuriHalSubGhzPresetOok650Async,
-    .file = HoltekFileProtocol};
-
-/**
- * Holtek AM 12bit 315MHz
- */
-const SubBruteProtocol subbrute_protocol_holtek_12bit_am_315 = {
-    .frequency = 315000000,
-    .bits = 12,
-    .te = 433,
-    .repeat = 3,
-    .preset = FuriHalSubGhzPresetOok650Async,
-    .file = HoltekFileProtocol};
-
-/**
- * Holtek AM 12bit 868MHz
- */
-const SubBruteProtocol subbrute_protocol_holtek_12bit_am_868 = {
-    .frequency = 868350000,
-    .bits = 12,
-    .te = 433,
-    .repeat = 3,
-    .preset = FuriHalSubGhzPresetOok650Async,
-    .file = HoltekFileProtocol};
-
-/**
- * Holtek AM 12bit 915MHz
- */
-const SubBruteProtocol subbrute_protocol_holtek_12bit_am_915 = {
-    .frequency = 915000000,
-    .bits = 12,
-    .te = 433,
-    .repeat = 3,
-    .preset = FuriHalSubGhzPresetOok650Async,
-    .file = HoltekFileProtocol};
-
-/**
- * BF existing dump
- */
-const SubBruteProtocol subbrute_protocol_load_file =
-    {0, 0, 0, 3, FuriHalSubGhzPresetOok650Async, UnknownFileProtocol};
-
-static const char* subbrute_protocol_names[] = {
-    [SubBruteAttackCAME12bit303] = "CAME 12bit 303MHz",
-    [SubBruteAttackCAME12bit307] = "CAME 12bit 307MHz",
-    [SubBruteAttackCAME12bit315] = "CAME 12bit 315MHz",
-    [SubBruteAttackCAME12bit433] = "CAME 12bit 433MHz",
-    [SubBruteAttackCAME12bit868] = "CAME 12bit 868MHz",
-    [SubBruteAttackNICE12bit433] = "NICE 12bit 433MHz",
-    [SubBruteAttackNICE12bit868] = "NICE 12bit 868MHz",
-    [SubBruteAttackAnsonic12bit433075] = "Ansonic 12bit 433.07MHz",
-    [SubBruteAttackAnsonic12bit433] = "Ansonic 12bit 433.92MHz",
-    [SubBruteAttackAnsonic12bit434] = "Ansonic 12bit 434.07MHz",
-    [SubBruteAttackHoltek12bitFM433] = "Holtek FM 12bit 433MHz",
-    [SubBruteAttackHoltek12bitAM433] = "Holtek AM 12bit 433MHz",
-    [SubBruteAttackHoltek12bitAM315] = "Holtek AM 12bit 315MHz",
-    [SubBruteAttackHoltek12bitAM868] = "Holtek AM 12bit 868MHz",
-    [SubBruteAttackHoltek12bitAM915] = "Holtek AM 12bit 915MHz",
-    [SubBruteAttackChamberlain9bit300] = "Chamberlain 9bit 300MHz",
-    [SubBruteAttackChamberlain9bit315] = "Chamberlain 9bit 315MHz",
-    [SubBruteAttackChamberlain9bit390] = "Chamberlain 9bit 390MHz",
-    [SubBruteAttackChamberlain9bit433] = "Chamberlain 9bit 433MHz",
-    [SubBruteAttackChamberlain8bit300] = "Chamberlain 8bit 300MHz",
-    [SubBruteAttackChamberlain8bit315] = "Chamberlain 8bit 315MHz",
-    [SubBruteAttackChamberlain8bit390] = "Chamberlain 8bit 390MHz",
-    [SubBruteAttackChamberlain7bit300] = "Chamberlain 7bit 300MHz",
-    [SubBruteAttackChamberlain7bit315] = "Chamberlain 7bit 315MHz",
-    [SubBruteAttackChamberlain7bit390] = "Chamberlain 7bit 390MHz",
-    [SubBruteAttackLinear10bit300] = "Linear 10bit 300MHz",
-    [SubBruteAttackLinear10bit310] = "Linear 10bit 310MHz",
-    [SubBruteAttackLinearDelta8bit310] = "LinearDelta3 8bit 310MHz",
-    [SubBruteAttackUNILARM24bit330] = "UNILARM 25bit 330MHz",
-    [SubBruteAttackUNILARM24bit433] = "UNILARM 25bit 433MHz",
-    [SubBruteAttackSMC532624bit330] = "SMC5326 25bit 330MHz",
-    [SubBruteAttackSMC532624bit433] = "SMC5326 25bit 433MHz",
-    [SubBruteAttackPT226024bit315] = "PT2260 24bit 315MHz",
-    [SubBruteAttackPT226024bit330] = "PT2260 24bit 330MHz",
-    [SubBruteAttackPT226024bit390] = "PT2260 24bit 390MHz",
-    [SubBruteAttackPT226024bit433] = "PT2260 24bit 433MHz",
-    [SubBruteAttackLoadFile] = "BF existing dump",
-    [SubBruteAttackTotalCount] = "Total Count",
-};
-
-static const char* subbrute_protocol_presets[] = {
-    [FuriHalSubGhzPresetIDLE] = "FuriHalSubGhzPresetIDLE",
-    [FuriHalSubGhzPresetOok270Async] = "FuriHalSubGhzPresetOok270Async",
-    [FuriHalSubGhzPresetOok650Async] = "FuriHalSubGhzPresetOok650Async",
-    [FuriHalSubGhzPreset2FSKDev238Async] = "FuriHalSubGhzPreset2FSKDev238Async",
-    [FuriHalSubGhzPreset2FSKDev476Async] = "FuriHalSubGhzPreset2FSKDev476Async",
-    [FuriHalSubGhzPresetMSK99_97KbAsync] = "FuriHalSubGhzPresetMSK99_97KbAsync",
-    [FuriHalSubGhzPresetGFSK9_99KbAsync] = "FuriHalSubGhzPresetGFSK9_99KbAsync",
-};
-
-const SubBruteProtocol* subbrute_protocol_registry[] = {
-    [SubBruteAttackCAME12bit303] = &subbrute_protocol_came_12bit_303,
-    [SubBruteAttackCAME12bit307] = &subbrute_protocol_came_12bit_307,
-    [SubBruteAttackCAME12bit315] = &subbrute_protocol_came_12bit_315,
-    [SubBruteAttackCAME12bit433] = &subbrute_protocol_came_12bit_433,
-    [SubBruteAttackCAME12bit868] = &subbrute_protocol_came_12bit_868,
-    [SubBruteAttackNICE12bit433] = &subbrute_protocol_nice_12bit_433,
-    [SubBruteAttackNICE12bit868] = &subbrute_protocol_nice_12bit_868,
-    [SubBruteAttackAnsonic12bit433075] = &subbrute_protocol_ansonic_12bit_433075,
-    [SubBruteAttackAnsonic12bit433] = &subbrute_protocol_ansonic_12bit_433,
-    [SubBruteAttackAnsonic12bit434] = &subbrute_protocol_ansonic_12bit_434,
-    [SubBruteAttackHoltek12bitFM433] = &subbrute_protocol_holtek_12bit_433,
-    [SubBruteAttackHoltek12bitAM433] = &subbrute_protocol_holtek_12bit_am_433,
-    [SubBruteAttackHoltek12bitAM315] = &subbrute_protocol_holtek_12bit_am_315,
-    [SubBruteAttackHoltek12bitAM868] = &subbrute_protocol_holtek_12bit_am_868,
-    [SubBruteAttackHoltek12bitAM915] = &subbrute_protocol_holtek_12bit_am_915,
-    [SubBruteAttackChamberlain9bit300] = &subbrute_protocol_chamberlain_9bit_300,
-    [SubBruteAttackChamberlain9bit315] = &subbrute_protocol_chamberlain_9bit_315,
-    [SubBruteAttackChamberlain9bit390] = &subbrute_protocol_chamberlain_9bit_390,
-    [SubBruteAttackChamberlain9bit433] = &subbrute_protocol_chamberlain_9bit_433,
-    [SubBruteAttackChamberlain8bit300] = &subbrute_protocol_chamberlain_8bit_300,
-    [SubBruteAttackChamberlain8bit315] = &subbrute_protocol_chamberlain_8bit_315,
-    [SubBruteAttackChamberlain8bit390] = &subbrute_protocol_chamberlain_8bit_390,
-    [SubBruteAttackChamberlain7bit300] = &subbrute_protocol_chamberlain_7bit_300,
-    [SubBruteAttackChamberlain7bit315] = &subbrute_protocol_chamberlain_7bit_315,
-    [SubBruteAttackChamberlain7bit390] = &subbrute_protocol_chamberlain_7bit_390,
-    [SubBruteAttackLinear10bit300] = &subbrute_protocol_linear_10bit_300,
-    [SubBruteAttackLinear10bit310] = &subbrute_protocol_linear_10bit_310,
-    [SubBruteAttackLinearDelta8bit310] = &subbrute_protocol_linear_delta_8bit_310,
-    [SubBruteAttackUNILARM24bit330] = &subbrute_protocol_unilarm_24bit_330,
-    [SubBruteAttackUNILARM24bit433] = &subbrute_protocol_unilarm_24bit_433,
-    [SubBruteAttackSMC532624bit330] = &subbrute_protocol_smc5326_24bit_330,
-    [SubBruteAttackSMC532624bit433] = &subbrute_protocol_smc5326_24bit_433,
-    [SubBruteAttackPT226024bit315] = &subbrute_protocol_pt2260_24bit_315,
-    [SubBruteAttackPT226024bit330] = &subbrute_protocol_pt2260_24bit_330,
-    [SubBruteAttackPT226024bit390] = &subbrute_protocol_pt2260_24bit_390,
-    [SubBruteAttackPT226024bit433] = &subbrute_protocol_pt2260_24bit_433,
-    [SubBruteAttackLoadFile] = &subbrute_protocol_load_file};
-
-static const char* subbrute_protocol_file_types[] = {
-    [CAMEFileProtocol] = "CAME",
-    [NICEFileProtocol] = "Nice FLO",
-    [ChamberlainFileProtocol] = "Cham_Code",
-    [LinearFileProtocol] = "Linear",
-    [LinearDeltaFileProtocol] = "LinearDelta3",
-    [PrincetonFileProtocol] = "Princeton",
-    [RAWFileProtocol] = "RAW",
-    [BETTFileProtocol] = "BETT",
-    [ClemsaFileProtocol] = "Clemsa",
-    [DoitrandFileProtocol] = "Doitrand",
-    [GateTXFileProtocol] = "GateTX",
-    [MagellanFileProtocol] = "Magellan",
-    [IntertechnoV3FileProtocol] = "Intertechno_V3",
-    [AnsonicFileProtocol] = "Ansonic",
-    [SMC5326FileProtocol] = "SMC5326",
-    [UNILARMFileProtocol] = "SMC5326",
-    [PT2260FileProtocol] = "Princeton",
-    [HoneywellFileProtocol] = "Honeywell",
-    [HoltekFileProtocol] = "Holtek_HT12X",
-    [UnknownFileProtocol] = "Unknown"};
-
-/**
- * Values to not use less memory for packet parse operations
- */
-static const char* subbrute_key_file_start_no_tail =
-    "Filetype: Flipper SubGhz Key File\nVersion: 1\nFrequency: %u\nPreset: %s\nProtocol: %s\nBit: %d\nKey: %s\n";
-static const char* subbrute_key_file_start_with_tail =
-    "Filetype: Flipper SubGhz Key File\nVersion: 1\nFrequency: %u\nPreset: %s\nProtocol: %s\nBit: %d\nKey: %s\nTE: %d\n";
-static const char* subbrute_key_small_no_tail = "Bit: %d\nKey: %s\nRepeat: %d\n";
-//static const char* subbrute_key_small_raw =
-//    "Filetype: Flipper SubGhz Key File\nVersion: 1\nFrequency: %u\nPreset: %s\nProtocol: %s\nBit: %d\n";
-static const char* subbrute_key_small_with_tail = "Bit: %d\nKey: %s\nTE: %d\nRepeat: %d\n";
-
-const char* subbrute_protocol_name(SubBruteAttacks index) {
-    return subbrute_protocol_names[index];
-}
-
-const SubBruteProtocol* subbrute_protocol(SubBruteAttacks index) {
-    return subbrute_protocol_registry[index];
-}
-
-uint8_t subbrute_protocol_repeats_count(SubBruteAttacks index) {
-    return subbrute_protocol_registry[index]->repeat;
-}
-
-const char* subbrute_protocol_preset(FuriHalSubGhzPreset preset) {
-    return subbrute_protocol_presets[preset];
-}
-
-const char* subbrute_protocol_file(SubBruteFileProtocol protocol) {
-    return subbrute_protocol_file_types[protocol];
-}
-
-FuriHalSubGhzPreset subbrute_protocol_convert_preset(FuriString* preset_name) {
-    for(size_t i = FuriHalSubGhzPresetIDLE; i < FuriHalSubGhzPresetCustom; i++) {
-        if(furi_string_cmp_str(preset_name, subbrute_protocol_presets[i]) == 0) {
-            return i;
-        }
-    }
-
-    return FuriHalSubGhzPresetIDLE;
-}
-
-SubBruteFileProtocol subbrute_protocol_file_protocol_name(FuriString* name) {
-    for(size_t i = CAMEFileProtocol; i < TotalFileProtocol - 1; i++) {
-        if(furi_string_cmp_str(name, subbrute_protocol_file_types[i]) == 0) {
-            return i;
-        }
-    }
-
-    return UnknownFileProtocol;
-}
-
-void subbrute_protocol_create_candidate_for_existing_file(
-    FuriString* candidate,
-    uint64_t step,
-    uint8_t bit_index,
-    uint64_t file_key,
-    bool two_bytes) {
-    uint8_t p[8];
-    for(int i = 0; i < 8; i++) {
-        p[i] = (uint8_t)(file_key >> 8 * (7 - i)) & 0xFF;
-    }
-    uint8_t low_byte = step & (0xff);
-    uint8_t high_byte = (step >> 8) & 0xff;
-
-    size_t size = sizeof(uint64_t);
-    for(uint8_t i = 0; i < size; i++) {
-        if(i == bit_index - 1 && two_bytes) {
-            furi_string_cat_printf(candidate, "%02X %02X", high_byte, low_byte);
-            i++;
-        } else if(i == bit_index) {
-            furi_string_cat_printf(candidate, "%02X", low_byte);
-        } else if(p[i] != 0) {
-            furi_string_cat_printf(candidate, "%02X", p[i]);
-        } else {
-            furi_string_cat_printf(candidate, "%s", "00");
-        }
-
-        if(i < size - 1) {
-            furi_string_push_back(candidate, ' ');
-        }
-    }
-
-#ifdef FURI_DEBUG
-    FURI_LOG_D(TAG, "file candidate: %s, step: %lld", furi_string_get_cstr(candidate), step);
-#endif
-}
-
-void subbrute_protocol_create_candidate_for_default(
-    FuriString* candidate,
-    SubBruteFileProtocol file,
-    uint64_t step) {
-    uint8_t p[8];
-    if(file == SMC5326FileProtocol) {
-        const uint8_t lut[] = {0x00, 0x02, 0x03}; // 00, 10, 11
-        const uint64_t gate1 = 0x01D5; // 111010101
-        //const uint8_t gate2 = 0x0175; // 101110101
-
-        uint64_t total = 0;
-        for(size_t j = 0; j < 8; j++) {
-            total |= lut[step % 3] << (2 * j);
-            double sub_step = step / 3;
-            step = (uint64_t)floor(sub_step);
-        }
-        total <<= 9;
-        total |= gate1;
-
-        for(int i = 0; i < 8; i++) {
-            p[i] = (uint8_t)(total >> 8 * (7 - i)) & 0xFF;
-        }
-    } else if(file == UNILARMFileProtocol) {
-        const uint8_t lut[] = {0x00, 0x02, 0x03}; // 00, 10, 11
-        const uint64_t gate1 = 3 << 7;
-        //const uint8_t gate2 = 3 << 5;
-
-        uint64_t total = 0;
-        for(size_t j = 0; j < 8; j++) {
-            total |= lut[step % 3] << (2 * j);
-            double sub_step = step / 3;
-            step = (uint64_t)floor(sub_step);
-        }
-        total <<= 9;
-        total |= gate1;
-
-        for(int i = 0; i < 8; i++) {
-            p[i] = (uint8_t)(total >> 8 * (7 - i)) & 0xFF;
-        }
-    } else if(file == PT2260FileProtocol) {
-        const uint8_t lut[] = {0x00, 0x01, 0x03}; // 00, 01, 11
-        const uint64_t button_open = 0x03; // 11
-        //const uint8_t button_lock = 0x0C; // 1100
-        //const uint8_t button_stop = 0x30; // 110000
-        //const uint8_t button_close = 0xC0; // 11000000
-
-        uint64_t total = 0;
-        for(size_t j = 0; j < 8; j++) {
-            total |= lut[step % 3] << (2 * j);
-            double sub_step = step / 3;
-            step = (uint64_t)floor(sub_step);
-        }
-        total <<= 8;
-        total |= button_open;
-
-        for(int i = 0; i < 8; i++) {
-            p[i] = (uint8_t)(total >> 8 * (7 - i)) & 0xFF;
-        }
-    } else {
-        for(int i = 0; i < 8; i++) {
-            p[i] = (uint8_t)(step >> 8 * (7 - i)) & 0xFF;
-        }
-    }
-
-    size_t size = sizeof(uint64_t);
-    for(uint8_t i = 0; i < size; i++) {
-        if(p[i] != 0) {
-            furi_string_cat_printf(candidate, "%02X", p[i]);
-        } else {
-            furi_string_cat_printf(candidate, "%s", "00");
-        }
-
-        if(i < size - 1) {
-            furi_string_push_back(candidate, ' ');
-        }
-    }
-
-#ifdef FURI_DEBUG
-    FURI_LOG_D(TAG, "candidate: %s, step: %lld", furi_string_get_cstr(candidate), step);
-#endif
-}
-
-void subbrute_protocol_default_payload(
-    Stream* stream,
-    SubBruteFileProtocol file,
-    uint64_t step,
-    uint8_t bits,
-    uint32_t te,
-    uint8_t repeat) {
-    FuriString* candidate = furi_string_alloc();
-    subbrute_protocol_create_candidate_for_default(candidate, file, step);
-
-#ifdef FURI_DEBUG
-    FURI_LOG_D(
-        TAG,
-        "candidate: %s, step: %lld, repeat: %d, te: %s",
-        furi_string_get_cstr(candidate),
-        step,
-        repeat,
-        te ? "true" : "false");
-#endif
-    stream_clean(stream);
-    if(te) {
-        stream_write_format(
-            stream,
-            subbrute_key_small_with_tail,
-            bits,
-            furi_string_get_cstr(candidate),
-            te,
-            repeat);
-    } else {
-        stream_write_format(
-            stream, subbrute_key_small_no_tail, bits, furi_string_get_cstr(candidate), repeat);
-    }
-
-    furi_string_free(candidate);
-}
-
-void subbrute_protocol_file_payload(
-    Stream* stream,
-    uint64_t step,
-    uint8_t bits,
-    uint32_t te,
-    uint8_t repeat,
-    uint8_t bit_index,
-    uint64_t file_key,
-    bool two_bytes) {
-    FuriString* candidate = furi_string_alloc();
-    subbrute_protocol_create_candidate_for_existing_file(
-        candidate, step, bit_index, file_key, two_bytes);
-
-#ifdef FURI_DEBUG
-    FURI_LOG_D(
-        TAG,
-        "candidate: %s, step: %lld, repeat: %d, te: %s",
-        furi_string_get_cstr(candidate),
-        step,
-        repeat,
-        te ? "true" : "false");
-#endif
-    stream_clean(stream);
-
-    if(te) {
-        stream_write_format(
-            stream,
-            subbrute_key_small_with_tail,
-            bits,
-            furi_string_get_cstr(candidate),
-            te,
-            repeat);
-    } else {
-        stream_write_format(
-            stream, subbrute_key_small_no_tail, bits, furi_string_get_cstr(candidate), repeat);
-    }
-
-    furi_string_free(candidate);
-}
-
-void subbrute_protocol_default_generate_file(
-    Stream* stream,
-    uint32_t frequency,
-    FuriHalSubGhzPreset preset,
-    SubBruteFileProtocol file,
-    uint64_t step,
-    uint8_t bits,
-    uint32_t te) {
-    FuriString* candidate = furi_string_alloc();
-    subbrute_protocol_create_candidate_for_default(candidate, file, step);
-
-#ifdef FURI_DEBUG
-    FURI_LOG_D(TAG, "candidate: %s, step: %lld", furi_string_get_cstr(candidate), step);
-#endif
-    stream_clean(stream);
-
-    if(te) {
-        stream_write_format(
-            stream,
-            subbrute_key_file_start_with_tail,
-            frequency,
-            subbrute_protocol_preset(preset),
-            subbrute_protocol_file(file),
-            bits,
-            furi_string_get_cstr(candidate),
-            te);
-    } else {
-        stream_write_format(
-            stream,
-            subbrute_key_file_start_no_tail,
-            frequency,
-            subbrute_protocol_preset(preset),
-            subbrute_protocol_file(file),
-            bits,
-            furi_string_get_cstr(candidate));
-    }
-
-    furi_string_free(candidate);
-}
-
-void subbrute_protocol_file_generate_file(
-    Stream* stream,
-    uint32_t frequency,
-    FuriHalSubGhzPreset preset,
-    SubBruteFileProtocol file,
-    uint64_t step,
-    uint8_t bits,
-    uint32_t te,
-    uint8_t bit_index,
-    uint64_t file_key,
-    bool two_bytes) {
-    FuriString* candidate = furi_string_alloc();
-    // char subbrute_payload_byte[8];
-    //furi_string_set_str(candidate, file_key);
-    subbrute_protocol_create_candidate_for_existing_file(
-        candidate, step, bit_index, file_key, two_bytes);
-
-    stream_clean(stream);
-
-    if(te) {
-        stream_write_format(
-            stream,
-            subbrute_key_file_start_with_tail,
-            frequency,
-            subbrute_protocol_preset(preset),
-            subbrute_protocol_file(file),
-            bits,
-            furi_string_get_cstr(candidate),
-            te);
-    } else {
-        stream_write_format(
-            stream,
-            subbrute_key_file_start_no_tail,
-            frequency,
-            subbrute_protocol_preset(preset),
-            subbrute_protocol_file(file),
-            bits,
-            furi_string_get_cstr(candidate));
-    }
-
-    furi_string_free(candidate);
-}
-
-uint64_t
-    subbrute_protocol_calc_max_value(SubBruteAttacks attack_type, uint8_t bits, bool two_bytes) {
-    uint64_t max_value;
-    if(attack_type == SubBruteAttackLoadFile) {
-        max_value = two_bytes ? 0xFFFF : 0xFF;
-    } else if(
-        attack_type == SubBruteAttackSMC532624bit330 ||
-        attack_type == SubBruteAttackSMC532624bit433 ||
-        attack_type == SubBruteAttackUNILARM24bit330 ||
-        attack_type == SubBruteAttackUNILARM24bit433 ||
-        attack_type == SubBruteAttackPT226024bit315 ||
-        attack_type == SubBruteAttackPT226024bit330 ||
-        attack_type == SubBruteAttackPT226024bit390 ||
-        attack_type == SubBruteAttackPT226024bit433) {
-        max_value = 6561;
-    } else {
-        FuriString* max_value_s;
-        max_value_s = furi_string_alloc();
-        for(uint8_t i = 0; i < bits; i++) {
-            furi_string_cat_printf(max_value_s, "1");
-        }
-        max_value = (uint64_t)strtol(furi_string_get_cstr(max_value_s), NULL, 2);
-        furi_string_free(max_value_s);
-    }
-
-    return max_value;
+#include "subbrute_protocols.h"
+#include "math.h"
+#include <string.h>
+
+#define TAG "SubBruteProtocols"
+
+/**
+ * CAME 12bit 303MHz
+ */
+const SubBruteProtocol subbrute_protocol_came_12bit_303 = {
+    .frequency = 303875000,
+    .bits = 12,
+    .te = 0,
+    .repeat = 3,
+    .preset = FuriHalSubGhzPresetOok650Async,
+    .file = CAMEFileProtocol};
+
+/**
+ * CAME 12bit 307MHz
+ */
+const SubBruteProtocol subbrute_protocol_came_12bit_307 = {
+    .frequency = 307800000,
+    .bits = 12,
+    .te = 0,
+    .repeat = 3,
+    .preset = FuriHalSubGhzPresetOok650Async,
+    .file = CAMEFileProtocol};
+
+/**
+ * CAME 12bit 315MHz
+ */
+const SubBruteProtocol subbrute_protocol_came_12bit_315 = {
+    .frequency = 315000000,
+    .bits = 12,
+    .te = 0,
+    .repeat = 3,
+    .preset = FuriHalSubGhzPresetOok650Async,
+    .file = CAMEFileProtocol};
+
+/**
+ * CAME 12bit 433MHz
+ */
+const SubBruteProtocol subbrute_protocol_came_12bit_433 = {
+    .frequency = 433920000,
+    .bits = 12,
+    .te = 0,
+    .repeat = 3,
+    .preset = FuriHalSubGhzPresetOok650Async,
+    .file = CAMEFileProtocol};
+
+/**
+ * CAME 12bit 868MHz
+ */
+const SubBruteProtocol subbrute_protocol_came_12bit_868 = {
+    .frequency = 868350000,
+    .bits = 12,
+    .te = 0,
+    .repeat = 3,
+    .preset = FuriHalSubGhzPresetOok650Async,
+    .file = CAMEFileProtocol};
+
+/**
+ * NICE 12bit 433MHz
+ */
+const SubBruteProtocol subbrute_protocol_nice_12bit_433 = {
+    .frequency = 433920000,
+    .bits = 12,
+    .te = 0,
+    .repeat = 3,
+    .preset = FuriHalSubGhzPresetOok650Async,
+    .file = NICEFileProtocol};
+
+/**
+ * NICE 12bit 868MHz
+ */
+const SubBruteProtocol subbrute_protocol_nice_12bit_868 = {
+    .frequency = 868350000,
+    .bits = 12,
+    .te = 0,
+    .repeat = 3,
+    .preset = FuriHalSubGhzPresetOok650Async,
+    .file = NICEFileProtocol};
+
+/**
+ * Ansonic 12bit 433.075MHz
+ */
+const SubBruteProtocol subbrute_protocol_ansonic_12bit_433075 = {
+    .frequency = 433075000,
+    .bits = 12,
+    .te = 0,
+    .repeat = 3,
+    .preset = FuriHalSubGhzPreset2FSKDev238Async,
+    .file = AnsonicFileProtocol};
+
+/**
+ * Ansonic 12bit 433.92MHz
+ */
+const SubBruteProtocol subbrute_protocol_ansonic_12bit_433 = {
+    .frequency = 433920000,
+    .bits = 12,
+    .te = 0,
+    .repeat = 3,
+    .preset = FuriHalSubGhzPreset2FSKDev238Async,
+    .file = AnsonicFileProtocol};
+
+/**
+ * Ansonic 12bit 434.075MHz
+ */
+const SubBruteProtocol subbrute_protocol_ansonic_12bit_434 = {
+    .frequency = 434075000,
+    .bits = 12,
+    .te = 0,
+    .repeat = 3,
+    .preset = FuriHalSubGhzPreset2FSKDev238Async,
+    .file = AnsonicFileProtocol};
+
+/**
+ * Chamberlain 9bit 300MHz
+ */
+const SubBruteProtocol subbrute_protocol_chamberlain_9bit_300 = {
+    .frequency = 300000000,
+    .bits = 9,
+    .te = 0,
+    .repeat = 3,
+    .preset = FuriHalSubGhzPresetOok650Async,
+    .file = ChamberlainFileProtocol};
+
+/**
+ * Chamberlain 9bit 315MHz
+ */
+const SubBruteProtocol subbrute_protocol_chamberlain_9bit_315 = {
+    .frequency = 315000000,
+    .bits = 9,
+    .te = 0,
+    .repeat = 3,
+    .preset = FuriHalSubGhzPresetOok650Async,
+    .file = ChamberlainFileProtocol};
+
+/**
+ * Chamberlain 9bit 390MHz
+ */
+const SubBruteProtocol subbrute_protocol_chamberlain_9bit_390 = {
+    .frequency = 390000000,
+    .bits = 9,
+    .te = 0,
+    .repeat = 3,
+    .preset = FuriHalSubGhzPresetOok650Async,
+    .file = ChamberlainFileProtocol};
+
+/**
+ * Chamberlain 9bit 433MHz
+ */
+const SubBruteProtocol subbrute_protocol_chamberlain_9bit_433 = {
+    .frequency = 433920000,
+    .bits = 9,
+    .te = 0,
+    .repeat = 3,
+    .preset = FuriHalSubGhzPresetOok650Async,
+    .file = ChamberlainFileProtocol};
+
+/**
+ * Chamberlain 8bit 300MHz
+ */
+const SubBruteProtocol subbrute_protocol_chamberlain_8bit_300 = {
+    .frequency = 300000000,
+    .bits = 8,
+    .te = 0,
+    .repeat = 3,
+    .preset = FuriHalSubGhzPresetOok650Async,
+    .file = ChamberlainFileProtocol};
+
+/**
+ * Chamberlain 8bit 315MHz
+ */
+const SubBruteProtocol subbrute_protocol_chamberlain_8bit_315 = {
+    .frequency = 315000000,
+    .bits = 8,
+    .te = 0,
+    .repeat = 3,
+    .preset = FuriHalSubGhzPresetOok650Async,
+    .file = ChamberlainFileProtocol};
+
+/**
+ * Chamberlain 8bit 390MHz
+ */
+const SubBruteProtocol subbrute_protocol_chamberlain_8bit_390 = {
+    .frequency = 390000000,
+    .bits = 8,
+    .te = 0,
+    .repeat = 3,
+    .preset = FuriHalSubGhzPresetOok650Async,
+    .file = ChamberlainFileProtocol};
+
+/**
+ * Chamberlain 7bit 300MHz
+ */
+const SubBruteProtocol subbrute_protocol_chamberlain_7bit_300 = {
+    .frequency = 300000000,
+    .bits = 7,
+    .te = 0,
+    .repeat = 3,
+    .preset = FuriHalSubGhzPresetOok650Async,
+    .file = ChamberlainFileProtocol};
+
+/**
+ * Chamberlain 7bit 315MHz
+ */
+const SubBruteProtocol subbrute_protocol_chamberlain_7bit_315 = {
+    .frequency = 315000000,
+    .bits = 7,
+    .te = 0,
+    .repeat = 3,
+    .preset = FuriHalSubGhzPresetOok650Async,
+    .file = ChamberlainFileProtocol};
+
+/**
+ * Chamberlain 7bit 390MHz
+ */
+const SubBruteProtocol subbrute_protocol_chamberlain_7bit_390 = {
+    .frequency = 390000000,
+    .bits = 7,
+    .te = 0,
+    .repeat = 3,
+    .preset = FuriHalSubGhzPresetOok650Async,
+    .file = ChamberlainFileProtocol};
+
+/**
+ * Linear 10bit 300MHz
+ */
+const SubBruteProtocol subbrute_protocol_linear_10bit_300 = {
+    .frequency = 300000000,
+    .bits = 10,
+    .te = 0,
+    .repeat = 5,
+    .preset = FuriHalSubGhzPresetOok650Async,
+    .file = LinearFileProtocol};
+
+/**
+ * Linear 10bit 310MHz
+ */
+const SubBruteProtocol subbrute_protocol_linear_10bit_310 = {
+    .frequency = 310000000,
+    .bits = 10,
+    .te = 0,
+    .repeat = 5,
+    .preset = FuriHalSubGhzPresetOok650Async,
+    .file = LinearFileProtocol};
+
+/**
+ * Linear Delta 3 8bit 310MHz
+ */
+const SubBruteProtocol subbrute_protocol_linear_delta_8bit_310 = {
+    .frequency = 310000000,
+    .bits = 8,
+    .te = 0,
+    .repeat = 5,
+    .preset = FuriHalSubGhzPresetOok650Async,
+    .file = LinearDeltaFileProtocol};
+
+/**
+ * UNILARM 24bit 330MHz
+ */
+const SubBruteProtocol subbrute_protocol_unilarm_24bit_330 = {
+    .frequency = 330000000,
+    .bits = 25,
+    .te = 209,
+    .repeat = 4,
+    .preset = FuriHalSubGhzPresetOok650Async,
+    .file = UNILARMFileProtocol};
+
+/**
+ * UNILARM 24bit 433MHz
+ */
+const SubBruteProtocol subbrute_protocol_unilarm_24bit_433 = {
+    .frequency = 433920000,
+    .bits = 25,
+    .te = 209,
+    .repeat = 4,
+    .preset = FuriHalSubGhzPresetOok650Async,
+    .file = UNILARMFileProtocol};
+
+/**
+ * SMC5326 24bit 330MHz
+ */
+const SubBruteProtocol subbrute_protocol_smc5326_24bit_330 = {
+    .frequency = 330000000,
+    .bits = 25,
+    .te = 320,
+    .repeat = 4,
+    .preset = FuriHalSubGhzPresetOok650Async,
+    .file = SMC5326FileProtocol};
+
+/**
+ * SMC5326 24bit 433MHz
+ */
+const SubBruteProtocol subbrute_protocol_smc5326_24bit_433 = {
+    .frequency = 433920000,
+    .bits = 25,
+    .te = 320,
+    .repeat = 4,
+    .preset = FuriHalSubGhzPresetOok650Async,
+    .file = SMC5326FileProtocol};
+
+/**
+ * PT2260 (Princeton) 24bit 315MHz
+ */
+const SubBruteProtocol subbrute_protocol_pt2260_24bit_315 = {
+    .frequency = 315000000,
+    .bits = 24,
+    .te = 286,
+    .repeat = 4,
+    .preset = FuriHalSubGhzPresetOok650Async,
+    .file = PT2260FileProtocol};
+
+/**
+ * PT2260 (Princeton) 24bit 330MHz
+ */
+const SubBruteProtocol subbrute_protocol_pt2260_24bit_330 = {
+    .frequency = 330000000,
+    .bits = 24,
+    .te = 286,
+    .repeat = 4,
+    .preset = FuriHalSubGhzPresetOok650Async,
+    .file = PT2260FileProtocol};
+
+/**
+ * PT2260 (Princeton) 24bit 390MHz
+ */
+const SubBruteProtocol subbrute_protocol_pt2260_24bit_390 = {
+    .frequency = 390000000,
+    .bits = 24,
+    .te = 286,
+    .repeat = 4,
+    .preset = FuriHalSubGhzPresetOok650Async,
+    .file = PT2260FileProtocol};
+
+/**
+ * PT2260 (Princeton) 24bit 433MHz
+ */
+const SubBruteProtocol subbrute_protocol_pt2260_24bit_433 = {
+    .frequency = 433920000,
+    .bits = 24,
+    .te = 286,
+    .repeat = 4,
+    .preset = FuriHalSubGhzPresetOok650Async,
+    .file = PT2260FileProtocol};
+
+/**
+ * Holtek FM 12bit 433MHz
+ */
+const SubBruteProtocol subbrute_protocol_holtek_12bit_433 = {
+    .frequency = 433920000,
+    .bits = 12,
+    .te = 204,
+    .repeat = 4,
+    .preset = FuriHalSubGhzPreset2FSKDev476Async,
+    .file = HoltekFileProtocol};
+
+/**
+ * Holtek AM 12bit 433MHz
+ */
+const SubBruteProtocol subbrute_protocol_holtek_12bit_am_433 = {
+    .frequency = 433920000,
+    .bits = 12,
+    .te = 433,
+    .repeat = 3,
+    .preset = FuriHalSubGhzPresetOok650Async,
+    .file = HoltekFileProtocol};
+
+/**
+ * Holtek AM 12bit 315MHz
+ */
+const SubBruteProtocol subbrute_protocol_holtek_12bit_am_315 = {
+    .frequency = 315000000,
+    .bits = 12,
+    .te = 433,
+    .repeat = 3,
+    .preset = FuriHalSubGhzPresetOok650Async,
+    .file = HoltekFileProtocol};
+
+/**
+ * Holtek AM 12bit 868MHz
+ */
+const SubBruteProtocol subbrute_protocol_holtek_12bit_am_868 = {
+    .frequency = 868350000,
+    .bits = 12,
+    .te = 433,
+    .repeat = 3,
+    .preset = FuriHalSubGhzPresetOok650Async,
+    .file = HoltekFileProtocol};
+
+/**
+ * Holtek AM 12bit 915MHz
+ */
+const SubBruteProtocol subbrute_protocol_holtek_12bit_am_915 = {
+    .frequency = 915000000,
+    .bits = 12,
+    .te = 433,
+    .repeat = 3,
+    .preset = FuriHalSubGhzPresetOok650Async,
+    .file = HoltekFileProtocol};
+
+/**
+ * BF existing dump
+ */
+const SubBruteProtocol subbrute_protocol_load_file =
+    {0, 0, 0, 3, FuriHalSubGhzPresetOok650Async, UnknownFileProtocol};
+
+static const char* subbrute_protocol_names[] = {
+    [SubBruteAttackCAME12bit303] = "CAME 12bit 303MHz",
+    [SubBruteAttackCAME12bit307] = "CAME 12bit 307MHz",
+    [SubBruteAttackCAME12bit315] = "CAME 12bit 315MHz",
+    [SubBruteAttackCAME12bit433] = "CAME 12bit 433MHz",
+    [SubBruteAttackCAME12bit868] = "CAME 12bit 868MHz",
+    [SubBruteAttackNICE12bit433] = "NICE 12bit 433MHz",
+    [SubBruteAttackNICE12bit868] = "NICE 12bit 868MHz",
+    [SubBruteAttackAnsonic12bit433075] = "Ansonic 12bit 433.07MHz",
+    [SubBruteAttackAnsonic12bit433] = "Ansonic 12bit 433.92MHz",
+    [SubBruteAttackAnsonic12bit434] = "Ansonic 12bit 434.07MHz",
+    [SubBruteAttackHoltek12bitFM433] = "Holtek FM 12bit 433MHz",
+    [SubBruteAttackHoltek12bitAM433] = "Holtek AM 12bit 433MHz",
+    [SubBruteAttackHoltek12bitAM315] = "Holtek AM 12bit 315MHz",
+    [SubBruteAttackHoltek12bitAM868] = "Holtek AM 12bit 868MHz",
+    [SubBruteAttackHoltek12bitAM915] = "Holtek AM 12bit 915MHz",
+    [SubBruteAttackChamberlain9bit300] = "Chamberlain 9bit 300MHz",
+    [SubBruteAttackChamberlain9bit315] = "Chamberlain 9bit 315MHz",
+    [SubBruteAttackChamberlain9bit390] = "Chamberlain 9bit 390MHz",
+    [SubBruteAttackChamberlain9bit433] = "Chamberlain 9bit 433MHz",
+    [SubBruteAttackChamberlain8bit300] = "Chamberlain 8bit 300MHz",
+    [SubBruteAttackChamberlain8bit315] = "Chamberlain 8bit 315MHz",
+    [SubBruteAttackChamberlain8bit390] = "Chamberlain 8bit 390MHz",
+    [SubBruteAttackChamberlain7bit300] = "Chamberlain 7bit 300MHz",
+    [SubBruteAttackChamberlain7bit315] = "Chamberlain 7bit 315MHz",
+    [SubBruteAttackChamberlain7bit390] = "Chamberlain 7bit 390MHz",
+    [SubBruteAttackLinear10bit300] = "Linear 10bit 300MHz",
+    [SubBruteAttackLinear10bit310] = "Linear 10bit 310MHz",
+    [SubBruteAttackLinearDelta8bit310] = "LinearDelta3 8bit 310MHz",
+    [SubBruteAttackUNILARM24bit330] = "UNILARM 25bit 330MHz",
+    [SubBruteAttackUNILARM24bit433] = "UNILARM 25bit 433MHz",
+    [SubBruteAttackSMC532624bit330] = "SMC5326 25bit 330MHz",
+    [SubBruteAttackSMC532624bit433] = "SMC5326 25bit 433MHz",
+    [SubBruteAttackPT226024bit315] = "PT2260 24bit 315MHz",
+    [SubBruteAttackPT226024bit330] = "PT2260 24bit 330MHz",
+    [SubBruteAttackPT226024bit390] = "PT2260 24bit 390MHz",
+    [SubBruteAttackPT226024bit433] = "PT2260 24bit 433MHz",
+    [SubBruteAttackLoadFile] = "BF existing dump",
+    [SubBruteAttackTotalCount] = "Total Count",
+};
+
+static const char* subbrute_protocol_presets[] = {
+    [FuriHalSubGhzPresetIDLE] = "FuriHalSubGhzPresetIDLE",
+    [FuriHalSubGhzPresetOok270Async] = "FuriHalSubGhzPresetOok270Async",
+    [FuriHalSubGhzPresetOok650Async] = "FuriHalSubGhzPresetOok650Async",
+    [FuriHalSubGhzPreset2FSKDev238Async] = "FuriHalSubGhzPreset2FSKDev238Async",
+    [FuriHalSubGhzPreset2FSKDev476Async] = "FuriHalSubGhzPreset2FSKDev476Async",
+    [FuriHalSubGhzPresetMSK99_97KbAsync] = "FuriHalSubGhzPresetMSK99_97KbAsync",
+    [FuriHalSubGhzPresetGFSK9_99KbAsync] = "FuriHalSubGhzPresetGFSK9_99KbAsync",
+};
+
+const SubBruteProtocol* subbrute_protocol_registry[] = {
+    [SubBruteAttackCAME12bit303] = &subbrute_protocol_came_12bit_303,
+    [SubBruteAttackCAME12bit307] = &subbrute_protocol_came_12bit_307,
+    [SubBruteAttackCAME12bit315] = &subbrute_protocol_came_12bit_315,
+    [SubBruteAttackCAME12bit433] = &subbrute_protocol_came_12bit_433,
+    [SubBruteAttackCAME12bit868] = &subbrute_protocol_came_12bit_868,
+    [SubBruteAttackNICE12bit433] = &subbrute_protocol_nice_12bit_433,
+    [SubBruteAttackNICE12bit868] = &subbrute_protocol_nice_12bit_868,
+    [SubBruteAttackAnsonic12bit433075] = &subbrute_protocol_ansonic_12bit_433075,
+    [SubBruteAttackAnsonic12bit433] = &subbrute_protocol_ansonic_12bit_433,
+    [SubBruteAttackAnsonic12bit434] = &subbrute_protocol_ansonic_12bit_434,
+    [SubBruteAttackHoltek12bitFM433] = &subbrute_protocol_holtek_12bit_433,
+    [SubBruteAttackHoltek12bitAM433] = &subbrute_protocol_holtek_12bit_am_433,
+    [SubBruteAttackHoltek12bitAM315] = &subbrute_protocol_holtek_12bit_am_315,
+    [SubBruteAttackHoltek12bitAM868] = &subbrute_protocol_holtek_12bit_am_868,
+    [SubBruteAttackHoltek12bitAM915] = &subbrute_protocol_holtek_12bit_am_915,
+    [SubBruteAttackChamberlain9bit300] = &subbrute_protocol_chamberlain_9bit_300,
+    [SubBruteAttackChamberlain9bit315] = &subbrute_protocol_chamberlain_9bit_315,
+    [SubBruteAttackChamberlain9bit390] = &subbrute_protocol_chamberlain_9bit_390,
+    [SubBruteAttackChamberlain9bit433] = &subbrute_protocol_chamberlain_9bit_433,
+    [SubBruteAttackChamberlain8bit300] = &subbrute_protocol_chamberlain_8bit_300,
+    [SubBruteAttackChamberlain8bit315] = &subbrute_protocol_chamberlain_8bit_315,
+    [SubBruteAttackChamberlain8bit390] = &subbrute_protocol_chamberlain_8bit_390,
+    [SubBruteAttackChamberlain7bit300] = &subbrute_protocol_chamberlain_7bit_300,
+    [SubBruteAttackChamberlain7bit315] = &subbrute_protocol_chamberlain_7bit_315,
+    [SubBruteAttackChamberlain7bit390] = &subbrute_protocol_chamberlain_7bit_390,
+    [SubBruteAttackLinear10bit300] = &subbrute_protocol_linear_10bit_300,
+    [SubBruteAttackLinear10bit310] = &subbrute_protocol_linear_10bit_310,
+    [SubBruteAttackLinearDelta8bit310] = &subbrute_protocol_linear_delta_8bit_310,
+    [SubBruteAttackUNILARM24bit330] = &subbrute_protocol_unilarm_24bit_330,
+    [SubBruteAttackUNILARM24bit433] = &subbrute_protocol_unilarm_24bit_433,
+    [SubBruteAttackSMC532624bit330] = &subbrute_protocol_smc5326_24bit_330,
+    [SubBruteAttackSMC532624bit433] = &subbrute_protocol_smc5326_24bit_433,
+    [SubBruteAttackPT226024bit315] = &subbrute_protocol_pt2260_24bit_315,
+    [SubBruteAttackPT226024bit330] = &subbrute_protocol_pt2260_24bit_330,
+    [SubBruteAttackPT226024bit390] = &subbrute_protocol_pt2260_24bit_390,
+    [SubBruteAttackPT226024bit433] = &subbrute_protocol_pt2260_24bit_433,
+    [SubBruteAttackLoadFile] = &subbrute_protocol_load_file};
+
+static const char* subbrute_protocol_file_types[] = {
+    [CAMEFileProtocol] = "CAME",
+    [NICEFileProtocol] = "Nice FLO",
+    [ChamberlainFileProtocol] = "Cham_Code",
+    [LinearFileProtocol] = "Linear",
+    [LinearDeltaFileProtocol] = "LinearDelta3",
+    [PrincetonFileProtocol] = "Princeton",
+    [RAWFileProtocol] = "RAW",
+    [BETTFileProtocol] = "BETT",
+    [ClemsaFileProtocol] = "Clemsa",
+    [DoitrandFileProtocol] = "Doitrand",
+    [GateTXFileProtocol] = "GateTX",
+    [MagellanFileProtocol] = "Magellan",
+    [IntertechnoV3FileProtocol] = "Intertechno_V3",
+    [AnsonicFileProtocol] = "Ansonic",
+    [SMC5326FileProtocol] = "SMC5326",
+    [UNILARMFileProtocol] = "SMC5326",
+    [PT2260FileProtocol] = "Princeton",
+    [HoneywellFileProtocol] = "Honeywell",
+    [HoltekFileProtocol] = "Holtek_HT12X",
+    [UnknownFileProtocol] = "Unknown"};
+
+/**
+ * Values to not use less memory for packet parse operations
+ */
+static const char* subbrute_key_file_start_no_tail =
+    "Filetype: Flipper SubGhz Key File\nVersion: 1\nFrequency: %u\nPreset: %s\nProtocol: %s\nBit: %d\nKey: %s\n";
+static const char* subbrute_key_file_start_with_tail =
+    "Filetype: Flipper SubGhz Key File\nVersion: 1\nFrequency: %u\nPreset: %s\nProtocol: %s\nBit: %d\nKey: %s\nTE: %d\n";
+static const char* subbrute_key_small_no_tail = "Bit: %d\nKey: %s\nRepeat: %d\n";
+//static const char* subbrute_key_small_raw =
+//    "Filetype: Flipper SubGhz Key File\nVersion: 1\nFrequency: %u\nPreset: %s\nProtocol: %s\nBit: %d\n";
+static const char* subbrute_key_small_with_tail = "Bit: %d\nKey: %s\nTE: %d\nRepeat: %d\n";
+
+const char* subbrute_protocol_name(SubBruteAttacks index) {
+    return subbrute_protocol_names[index];
+}
+
+const SubBruteProtocol* subbrute_protocol(SubBruteAttacks index) {
+    return subbrute_protocol_registry[index];
+}
+
+uint8_t subbrute_protocol_repeats_count(SubBruteAttacks index) {
+    return subbrute_protocol_registry[index]->repeat;
+}
+
+const char* subbrute_protocol_preset(FuriHalSubGhzPreset preset) {
+    return subbrute_protocol_presets[preset];
+}
+
+const char* subbrute_protocol_file(SubBruteFileProtocol protocol) {
+    return subbrute_protocol_file_types[protocol];
+}
+
+FuriHalSubGhzPreset subbrute_protocol_convert_preset(FuriString* preset_name) {
+    for(size_t i = FuriHalSubGhzPresetIDLE; i < FuriHalSubGhzPresetCustom; i++) {
+        if(furi_string_cmp_str(preset_name, subbrute_protocol_presets[i]) == 0) {
+            return i;
+        }
+    }
+
+    return FuriHalSubGhzPresetIDLE;
+}
+
+SubBruteFileProtocol subbrute_protocol_file_protocol_name(FuriString* name) {
+    for(size_t i = CAMEFileProtocol; i < TotalFileProtocol - 1; i++) {
+        if(furi_string_cmp_str(name, subbrute_protocol_file_types[i]) == 0) {
+            return i;
+        }
+    }
+
+    return UnknownFileProtocol;
+}
+
+void subbrute_protocol_create_candidate_for_existing_file(
+    FuriString* candidate,
+    uint64_t step,
+    uint8_t bit_index,
+    uint64_t file_key,
+    bool two_bytes) {
+    uint8_t p[8];
+    for(int i = 0; i < 8; i++) {
+        p[i] = (uint8_t)(file_key >> 8 * (7 - i)) & 0xFF;
+    }
+    uint8_t low_byte = step & (0xff);
+    uint8_t high_byte = (step >> 8) & 0xff;
+
+    size_t size = sizeof(uint64_t);
+    for(uint8_t i = 0; i < size; i++) {
+        if(i == bit_index - 1 && two_bytes) {
+            furi_string_cat_printf(candidate, "%02X %02X", high_byte, low_byte);
+            i++;
+        } else if(i == bit_index) {
+            furi_string_cat_printf(candidate, "%02X", low_byte);
+        } else if(p[i] != 0) {
+            furi_string_cat_printf(candidate, "%02X", p[i]);
+        } else {
+            furi_string_cat_printf(candidate, "%s", "00");
+        }
+
+        if(i < size - 1) {
+            furi_string_push_back(candidate, ' ');
+        }
+    }
+
+#ifdef FURI_DEBUG
+    FURI_LOG_D(TAG, "file candidate: %s, step: %lld", furi_string_get_cstr(candidate), step);
+#endif
+}
+
+void subbrute_protocol_create_candidate_for_default(
+    FuriString* candidate,
+    SubBruteFileProtocol file,
+    uint64_t step) {
+    uint8_t p[8];
+    if(file == SMC5326FileProtocol) {
+        const uint8_t lut[] = {0x00, 0x02, 0x03}; // 00, 10, 11
+        const uint64_t gate1 = 0x01D5; // 111010101
+        //const uint8_t gate2 = 0x0175; // 101110101
+
+        uint64_t total = 0;
+        for(size_t j = 0; j < 8; j++) {
+            total |= lut[step % 3] << (2 * j);
+            double sub_step = step / 3;
+            step = (uint64_t)floor(sub_step);
+        }
+        total <<= 9;
+        total |= gate1;
+
+        for(int i = 0; i < 8; i++) {
+            p[i] = (uint8_t)(total >> 8 * (7 - i)) & 0xFF;
+        }
+    } else if(file == UNILARMFileProtocol) {
+        const uint8_t lut[] = {0x00, 0x02, 0x03}; // 00, 10, 11
+        const uint64_t gate1 = 3 << 7;
+        //const uint8_t gate2 = 3 << 5;
+
+        uint64_t total = 0;
+        for(size_t j = 0; j < 8; j++) {
+            total |= lut[step % 3] << (2 * j);
+            double sub_step = step / 3;
+            step = (uint64_t)floor(sub_step);
+        }
+        total <<= 9;
+        total |= gate1;
+
+        for(int i = 0; i < 8; i++) {
+            p[i] = (uint8_t)(total >> 8 * (7 - i)) & 0xFF;
+        }
+    } else if(file == PT2260FileProtocol) {
+        const uint8_t lut[] = {0x00, 0x01, 0x03}; // 00, 01, 11
+        const uint64_t button_open = 0x03; // 11
+        //const uint8_t button_lock = 0x0C; // 1100
+        //const uint8_t button_stop = 0x30; // 110000
+        //const uint8_t button_close = 0xC0; // 11000000
+
+        uint64_t total = 0;
+        for(size_t j = 0; j < 8; j++) {
+            total |= lut[step % 3] << (2 * j);
+            double sub_step = step / 3;
+            step = (uint64_t)floor(sub_step);
+        }
+        total <<= 8;
+        total |= button_open;
+
+        for(int i = 0; i < 8; i++) {
+            p[i] = (uint8_t)(total >> 8 * (7 - i)) & 0xFF;
+        }
+    } else {
+        for(int i = 0; i < 8; i++) {
+            p[i] = (uint8_t)(step >> 8 * (7 - i)) & 0xFF;
+        }
+    }
+
+    size_t size = sizeof(uint64_t);
+    for(uint8_t i = 0; i < size; i++) {
+        if(p[i] != 0) {
+            furi_string_cat_printf(candidate, "%02X", p[i]);
+        } else {
+            furi_string_cat_printf(candidate, "%s", "00");
+        }
+
+        if(i < size - 1) {
+            furi_string_push_back(candidate, ' ');
+        }
+    }
+
+#ifdef FURI_DEBUG
+    FURI_LOG_D(TAG, "candidate: %s, step: %lld", furi_string_get_cstr(candidate), step);
+#endif
+}
+
+void subbrute_protocol_default_payload(
+    Stream* stream,
+    SubBruteFileProtocol file,
+    uint64_t step,
+    uint8_t bits,
+    uint32_t te,
+    uint8_t repeat) {
+    FuriString* candidate = furi_string_alloc();
+    subbrute_protocol_create_candidate_for_default(candidate, file, step);
+
+#ifdef FURI_DEBUG
+    FURI_LOG_D(
+        TAG,
+        "candidate: %s, step: %lld, repeat: %d, te: %s",
+        furi_string_get_cstr(candidate),
+        step,
+        repeat,
+        te ? "true" : "false");
+#endif
+    stream_clean(stream);
+    if(te) {
+        stream_write_format(
+            stream,
+            subbrute_key_small_with_tail,
+            bits,
+            furi_string_get_cstr(candidate),
+            te,
+            repeat);
+    } else {
+        stream_write_format(
+            stream, subbrute_key_small_no_tail, bits, furi_string_get_cstr(candidate), repeat);
+    }
+
+    furi_string_free(candidate);
+}
+
+void subbrute_protocol_file_payload(
+    Stream* stream,
+    uint64_t step,
+    uint8_t bits,
+    uint32_t te,
+    uint8_t repeat,
+    uint8_t bit_index,
+    uint64_t file_key,
+    bool two_bytes) {
+    FuriString* candidate = furi_string_alloc();
+    subbrute_protocol_create_candidate_for_existing_file(
+        candidate, step, bit_index, file_key, two_bytes);
+
+#ifdef FURI_DEBUG
+    FURI_LOG_D(
+        TAG,
+        "candidate: %s, step: %lld, repeat: %d, te: %s",
+        furi_string_get_cstr(candidate),
+        step,
+        repeat,
+        te ? "true" : "false");
+#endif
+    stream_clean(stream);
+
+    if(te) {
+        stream_write_format(
+            stream,
+            subbrute_key_small_with_tail,
+            bits,
+            furi_string_get_cstr(candidate),
+            te,
+            repeat);
+    } else {
+        stream_write_format(
+            stream, subbrute_key_small_no_tail, bits, furi_string_get_cstr(candidate), repeat);
+    }
+
+    furi_string_free(candidate);
+}
+
+void subbrute_protocol_default_generate_file(
+    Stream* stream,
+    uint32_t frequency,
+    FuriHalSubGhzPreset preset,
+    SubBruteFileProtocol file,
+    uint64_t step,
+    uint8_t bits,
+    uint32_t te) {
+    FuriString* candidate = furi_string_alloc();
+    subbrute_protocol_create_candidate_for_default(candidate, file, step);
+
+#ifdef FURI_DEBUG
+    FURI_LOG_D(TAG, "candidate: %s, step: %lld", furi_string_get_cstr(candidate), step);
+#endif
+    stream_clean(stream);
+
+    if(te) {
+        stream_write_format(
+            stream,
+            subbrute_key_file_start_with_tail,
+            frequency,
+            subbrute_protocol_preset(preset),
+            subbrute_protocol_file(file),
+            bits,
+            furi_string_get_cstr(candidate),
+            te);
+    } else {
+        stream_write_format(
+            stream,
+            subbrute_key_file_start_no_tail,
+            frequency,
+            subbrute_protocol_preset(preset),
+            subbrute_protocol_file(file),
+            bits,
+            furi_string_get_cstr(candidate));
+    }
+
+    furi_string_free(candidate);
+}
+
+void subbrute_protocol_file_generate_file(
+    Stream* stream,
+    uint32_t frequency,
+    FuriHalSubGhzPreset preset,
+    SubBruteFileProtocol file,
+    uint64_t step,
+    uint8_t bits,
+    uint32_t te,
+    uint8_t bit_index,
+    uint64_t file_key,
+    bool two_bytes) {
+    FuriString* candidate = furi_string_alloc();
+    // char subbrute_payload_byte[8];
+    //furi_string_set_str(candidate, file_key);
+    subbrute_protocol_create_candidate_for_existing_file(
+        candidate, step, bit_index, file_key, two_bytes);
+
+    stream_clean(stream);
+
+    if(te) {
+        stream_write_format(
+            stream,
+            subbrute_key_file_start_with_tail,
+            frequency,
+            subbrute_protocol_preset(preset),
+            subbrute_protocol_file(file),
+            bits,
+            furi_string_get_cstr(candidate),
+            te);
+    } else {
+        stream_write_format(
+            stream,
+            subbrute_key_file_start_no_tail,
+            frequency,
+            subbrute_protocol_preset(preset),
+            subbrute_protocol_file(file),
+            bits,
+            furi_string_get_cstr(candidate));
+    }
+
+    furi_string_free(candidate);
+}
+
+uint64_t
+    subbrute_protocol_calc_max_value(SubBruteAttacks attack_type, uint8_t bits, bool two_bytes) {
+    uint64_t max_value;
+    if(attack_type == SubBruteAttackLoadFile) {
+        max_value = two_bytes ? 0xFFFF : 0xFF;
+    } else if(
+        attack_type == SubBruteAttackSMC532624bit330 ||
+        attack_type == SubBruteAttackSMC532624bit433 ||
+        attack_type == SubBruteAttackUNILARM24bit330 ||
+        attack_type == SubBruteAttackUNILARM24bit433 ||
+        attack_type == SubBruteAttackPT226024bit315 ||
+        attack_type == SubBruteAttackPT226024bit330 ||
+        attack_type == SubBruteAttackPT226024bit390 ||
+        attack_type == SubBruteAttackPT226024bit433) {
+        max_value = 6561;
+    } else {
+        FuriString* max_value_s;
+        max_value_s = furi_string_alloc();
+        for(uint8_t i = 0; i < bits; i++) {
+            furi_string_cat_printf(max_value_s, "1");
+        }
+        max_value = (uint64_t)strtol(furi_string_get_cstr(max_value_s), NULL, 2);
+        furi_string_free(max_value_s);
+    }
+
+    return max_value;
 }
 }

+ 125 - 125
subbrute_protocols.h

@@ -1,126 +1,126 @@
-#pragma once
-
-#include <furi.h>
-#include <furi_hal_subghz.h>
-#include <core/string.h>
-#include <toolbox/stream/stream.h>
-
-typedef enum {
-    CAMEFileProtocol,
-    NICEFileProtocol,
-    ChamberlainFileProtocol,
-    LinearFileProtocol,
-    LinearDeltaFileProtocol,
-    PrincetonFileProtocol,
-    RAWFileProtocol,
-    BETTFileProtocol,
-    ClemsaFileProtocol,
-    DoitrandFileProtocol,
-    GateTXFileProtocol,
-    MagellanFileProtocol,
-    IntertechnoV3FileProtocol,
-    AnsonicFileProtocol,
-    SMC5326FileProtocol,
-    UNILARMFileProtocol,
-    PT2260FileProtocol,
-    HoneywellFileProtocol,
-    HoltekFileProtocol,
-    UnknownFileProtocol,
-    TotalFileProtocol,
-} SubBruteFileProtocol;
-
-typedef enum {
-    SubBruteAttackCAME12bit303,
-    SubBruteAttackCAME12bit307,
-    SubBruteAttackCAME12bit315,
-    SubBruteAttackCAME12bit433,
-    SubBruteAttackCAME12bit868,
-    SubBruteAttackNICE12bit433,
-    SubBruteAttackNICE12bit868,
-    SubBruteAttackAnsonic12bit433075,
-    SubBruteAttackAnsonic12bit433,
-    SubBruteAttackAnsonic12bit434,
-    SubBruteAttackHoltek12bitFM433,
-    SubBruteAttackHoltek12bitAM433,
-    SubBruteAttackHoltek12bitAM315,
-    SubBruteAttackHoltek12bitAM868,
-    SubBruteAttackHoltek12bitAM915,
-    SubBruteAttackChamberlain9bit300,
-    SubBruteAttackChamberlain9bit315,
-    SubBruteAttackChamberlain9bit390,
-    SubBruteAttackChamberlain9bit433,
-    SubBruteAttackChamberlain8bit300,
-    SubBruteAttackChamberlain8bit315,
-    SubBruteAttackChamberlain8bit390,
-    SubBruteAttackChamberlain7bit300,
-    SubBruteAttackChamberlain7bit315,
-    SubBruteAttackChamberlain7bit390,
-    SubBruteAttackLinear10bit300,
-    SubBruteAttackLinear10bit310,
-    SubBruteAttackLinearDelta8bit310,
-    SubBruteAttackUNILARM24bit330,
-    SubBruteAttackUNILARM24bit433,
-    SubBruteAttackSMC532624bit330,
-    SubBruteAttackSMC532624bit433,
-    SubBruteAttackPT226024bit315,
-    SubBruteAttackPT226024bit330,
-    SubBruteAttackPT226024bit390,
-    SubBruteAttackPT226024bit433,
-    SubBruteAttackLoadFile,
-    SubBruteAttackTotalCount,
-} SubBruteAttacks;
-
-typedef struct {
-    uint32_t frequency;
-    uint8_t bits;
-    uint32_t te;
-    uint8_t repeat;
-    FuriHalSubGhzPreset preset;
-    SubBruteFileProtocol file;
-} SubBruteProtocol;
-
-const SubBruteProtocol* subbrute_protocol(SubBruteAttacks index);
-const char* subbrute_protocol_preset(FuriHalSubGhzPreset preset);
-const char* subbrute_protocol_file(SubBruteFileProtocol protocol);
-FuriHalSubGhzPreset subbrute_protocol_convert_preset(FuriString* preset_name);
-SubBruteFileProtocol subbrute_protocol_file_protocol_name(FuriString* name);
-uint8_t subbrute_protocol_repeats_count(SubBruteAttacks index);
-const char* subbrute_protocol_name(SubBruteAttacks index);
-
-void subbrute_protocol_default_payload(
-    Stream* stream,
-    SubBruteFileProtocol file,
-    uint64_t step,
-    uint8_t bits,
-    uint32_t te,
-    uint8_t repeat);
-void subbrute_protocol_file_payload(
-    Stream* stream,
-    uint64_t step,
-    uint8_t bits,
-    uint32_t te,
-    uint8_t repeat,
-    uint8_t bit_index,
-    uint64_t file_key,
-    bool two_bytes);
-void subbrute_protocol_default_generate_file(
-    Stream* stream,
-    uint32_t frequency,
-    FuriHalSubGhzPreset preset,
-    SubBruteFileProtocol file,
-    uint64_t step,
-    uint8_t bits,
-    uint32_t te);
-void subbrute_protocol_file_generate_file(
-    Stream* stream,
-    uint32_t frequency,
-    FuriHalSubGhzPreset preset,
-    SubBruteFileProtocol file,
-    uint64_t step,
-    uint8_t bits,
-    uint32_t te,
-    uint8_t bit_index,
-    uint64_t file_key,
-    bool two_bytes);
-uint64_t
+#pragma once
+
+#include <furi.h>
+#include <furi_hal_subghz.h>
+#include <core/string.h>
+#include <toolbox/stream/stream.h>
+
+typedef enum {
+    CAMEFileProtocol,
+    NICEFileProtocol,
+    ChamberlainFileProtocol,
+    LinearFileProtocol,
+    LinearDeltaFileProtocol,
+    PrincetonFileProtocol,
+    RAWFileProtocol,
+    BETTFileProtocol,
+    ClemsaFileProtocol,
+    DoitrandFileProtocol,
+    GateTXFileProtocol,
+    MagellanFileProtocol,
+    IntertechnoV3FileProtocol,
+    AnsonicFileProtocol,
+    SMC5326FileProtocol,
+    UNILARMFileProtocol,
+    PT2260FileProtocol,
+    HoneywellFileProtocol,
+    HoltekFileProtocol,
+    UnknownFileProtocol,
+    TotalFileProtocol,
+} SubBruteFileProtocol;
+
+typedef enum {
+    SubBruteAttackCAME12bit303,
+    SubBruteAttackCAME12bit307,
+    SubBruteAttackCAME12bit315,
+    SubBruteAttackCAME12bit433,
+    SubBruteAttackCAME12bit868,
+    SubBruteAttackNICE12bit433,
+    SubBruteAttackNICE12bit868,
+    SubBruteAttackAnsonic12bit433075,
+    SubBruteAttackAnsonic12bit433,
+    SubBruteAttackAnsonic12bit434,
+    SubBruteAttackHoltek12bitFM433,
+    SubBruteAttackHoltek12bitAM433,
+    SubBruteAttackHoltek12bitAM315,
+    SubBruteAttackHoltek12bitAM868,
+    SubBruteAttackHoltek12bitAM915,
+    SubBruteAttackChamberlain9bit300,
+    SubBruteAttackChamberlain9bit315,
+    SubBruteAttackChamberlain9bit390,
+    SubBruteAttackChamberlain9bit433,
+    SubBruteAttackChamberlain8bit300,
+    SubBruteAttackChamberlain8bit315,
+    SubBruteAttackChamberlain8bit390,
+    SubBruteAttackChamberlain7bit300,
+    SubBruteAttackChamberlain7bit315,
+    SubBruteAttackChamberlain7bit390,
+    SubBruteAttackLinear10bit300,
+    SubBruteAttackLinear10bit310,
+    SubBruteAttackLinearDelta8bit310,
+    SubBruteAttackUNILARM24bit330,
+    SubBruteAttackUNILARM24bit433,
+    SubBruteAttackSMC532624bit330,
+    SubBruteAttackSMC532624bit433,
+    SubBruteAttackPT226024bit315,
+    SubBruteAttackPT226024bit330,
+    SubBruteAttackPT226024bit390,
+    SubBruteAttackPT226024bit433,
+    SubBruteAttackLoadFile,
+    SubBruteAttackTotalCount,
+} SubBruteAttacks;
+
+typedef struct {
+    uint32_t frequency;
+    uint8_t bits;
+    uint32_t te;
+    uint8_t repeat;
+    FuriHalSubGhzPreset preset;
+    SubBruteFileProtocol file;
+} SubBruteProtocol;
+
+const SubBruteProtocol* subbrute_protocol(SubBruteAttacks index);
+const char* subbrute_protocol_preset(FuriHalSubGhzPreset preset);
+const char* subbrute_protocol_file(SubBruteFileProtocol protocol);
+FuriHalSubGhzPreset subbrute_protocol_convert_preset(FuriString* preset_name);
+SubBruteFileProtocol subbrute_protocol_file_protocol_name(FuriString* name);
+uint8_t subbrute_protocol_repeats_count(SubBruteAttacks index);
+const char* subbrute_protocol_name(SubBruteAttacks index);
+
+void subbrute_protocol_default_payload(
+    Stream* stream,
+    SubBruteFileProtocol file,
+    uint64_t step,
+    uint8_t bits,
+    uint32_t te,
+    uint8_t repeat);
+void subbrute_protocol_file_payload(
+    Stream* stream,
+    uint64_t step,
+    uint8_t bits,
+    uint32_t te,
+    uint8_t repeat,
+    uint8_t bit_index,
+    uint64_t file_key,
+    bool two_bytes);
+void subbrute_protocol_default_generate_file(
+    Stream* stream,
+    uint32_t frequency,
+    FuriHalSubGhzPreset preset,
+    SubBruteFileProtocol file,
+    uint64_t step,
+    uint8_t bits,
+    uint32_t te);
+void subbrute_protocol_file_generate_file(
+    Stream* stream,
+    uint32_t frequency,
+    FuriHalSubGhzPreset preset,
+    SubBruteFileProtocol file,
+    uint64_t step,
+    uint8_t bits,
+    uint32_t te,
+    uint8_t bit_index,
+    uint64_t file_key,
+    bool two_bytes);
+uint64_t
     subbrute_protocol_calc_max_value(SubBruteAttacks attack_type, uint8_t bits, bool two_bytes);
     subbrute_protocol_calc_max_value(SubBruteAttacks attack_type, uint8_t bits, bool two_bytes);

+ 344 - 341
views/subbrute_attack_view.c

@@ -1,341 +1,344 @@
-#include "subbrute_attack_view.h"
-#include "../subbrute_i.h"
-#include "../subbrute_protocols.h"
-#include "../helpers/gui_top_buttons.h"
-
-#include <input/input.h>
-#include <gui/elements.h>
-#include <gui/icon.h>
-#include <gui/icon_animation.h>
-#include <assets_icons.h>
-
-#define TAG "SubBruteAttackView"
-
-struct SubBruteAttackView {
-    View* view;
-    SubBruteAttackViewCallback callback;
-    void* context;
-    SubBruteAttacks attack_type;
-    uint64_t max_value;
-    uint64_t current_step;
-    bool is_attacking;
-    uint8_t extra_repeats;
-};
-
-typedef struct {
-    SubBruteAttacks attack_type;
-    uint64_t max_value;
-    uint64_t current_step;
-    uint8_t extra_repeats;
-    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);
-    SubBruteAttackView* instance = context;
-#ifdef FURI_DEBUG
-    FURI_LOG_D(TAG, "InputKey: %d", event->key);
-#endif
-
-    if(event->key == InputKeyBack && event->type == InputTypeShort) {
-        instance->is_attacking = false;
-        with_view_model(
-            instance->view,
-            SubBruteAttackViewModel * model,
-            { model->is_attacking = false; },
-            true);
-
-        instance->callback(SubBruteCustomEventTypeBackPressed, instance->context);
-        return true;
-    }
-
-    bool update = false;
-
-    if(!instance->is_attacking) {
-        if(event->type == InputTypeShort && event->key == InputKeyOk) {
-#ifdef FURI_DEBUG
-            FURI_LOG_D(TAG, "InputKey: %d OK", event->key);
-#endif
-            instance->is_attacking = true;
-            instance->callback(SubBruteCustomEventTypeTransmitStarted, instance->context);
-            update = true;
-        } else if(event->key == InputKeyUp) {
-            instance->callback(SubBruteCustomEventTypeSaveFile, instance->context);
-            update = true;
-        } else if(event->key == InputKeyDown) {
-            instance->callback(SubBruteCustomEventTypeTransmitCustom, instance->context);
-            update = true;
-        } else if(event->type == InputTypeShort) {
-            if(event->key == InputKeyLeft) {
-                instance->callback(SubBruteCustomEventTypeChangeStepDown, instance->context);
-            } else if(event->key == InputKeyRight) {
-                instance->callback(SubBruteCustomEventTypeChangeStepUp, instance->context);
-            }
-            update = true;
-        } else if(event->type == InputTypeRepeat) {
-            if(event->key == InputKeyLeft) {
-                instance->callback(SubBruteCustomEventTypeChangeStepDownMore, instance->context);
-            } else if(event->key == InputKeyRight) {
-                instance->callback(SubBruteCustomEventTypeChangeStepUpMore, instance->context);
-            }
-            update = true;
-        }
-    } else {
-        // ATTACK Mode!
-        if((event->type == InputTypeShort || event->type == InputTypeRepeat) &&
-           (event->key == InputKeyOk || event->key == InputKeyBack)) {
-            instance->is_attacking = false;
-            instance->callback(SubBruteCustomEventTypeTransmitNotStarted, instance->context);
-
-            update = true;
-        }
-    }
-
-    if(update) {
-        with_view_model(
-            instance->view,
-            SubBruteAttackViewModel * model,
-            {
-                if(model->is_attacking != instance->is_attacking) {
-                    if(instance->is_attacking) {
-                        icon_animation_stop(model->icon);
-                        icon_animation_start(model->icon);
-                    } else {
-                        icon_animation_stop(model->icon);
-                    }
-                }
-
-                model->attack_type = instance->attack_type;
-                model->max_value = instance->max_value;
-                model->current_step = instance->current_step;
-                model->is_attacking = instance->is_attacking;
-            },
-            true);
-    }
-
-    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);
-        },
-        true);
-
-    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);
-
-    instance->attack_type = SubBruteAttackTotalCount;
-    instance->max_value = 0x00;
-    instance->current_step = 0;
-    instance->is_attacking = false;
-
-    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); },
-        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
-    instance->current_step = current_step;
-    with_view_model(
-        instance->view,
-        SubBruteAttackViewModel * model,
-        { model->current_step = current_step; },
-        true);
-}
-
-// 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,
-    uint8_t extra_repeats) {
-#ifdef FURI_DEBUG
-    FURI_LOG_I(
-        TAG,
-        "INIT, attack_type: %d, max_value: %lld, current_step: %lld, extra_repeats: %d",
-        index,
-        max_value,
-        current_step,
-        extra_repeats);
-#endif
-    instance->attack_type = index;
-    instance->max_value = max_value;
-    instance->current_step = current_step;
-    instance->is_attacking = is_attacking;
-    instance->extra_repeats = extra_repeats;
-
-    with_view_model(
-        instance->view,
-        SubBruteAttackViewModel * model,
-        {
-            model->max_value = max_value;
-            model->attack_type = index;
-            model->current_step = current_step;
-            model->is_attacking = is_attacking;
-            model->extra_repeats = extra_repeats;
-            if(is_attacking) {
-                icon_animation_start(model->icon);
-            } else {
-                icon_animation_stop(model->icon);
-            }
-        },
-        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); },
-        false);
-}
-
-void subbrute_attack_view_draw(Canvas* canvas, void* context) {
-    furi_assert(context);
-    SubBruteAttackViewModel* model = (SubBruteAttackViewModel*)context;
-    char buffer[64];
-
-    const char* attack_name = NULL;
-    attack_name = subbrute_protocol_name(model->attack_type);
-
-    // 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);
-    }
-
-    // Current Step / Max value
-    const uint8_t y_frequency = 17;
-    if(model->max_value > 9999) {
-        canvas_set_font(canvas, FontBigNumbers);
-        snprintf(buffer, sizeof(buffer), "%05d/", (int)model->current_step);
-        canvas_draw_str_aligned(canvas, 5, y_frequency, AlignLeft, AlignTop, buffer);
-
-        // Second part with another font, because BigNumber is out of screen bounds
-        canvas_set_font(canvas, FontPrimary);
-        snprintf(buffer, sizeof(buffer), "%05d", (int)model->max_value);
-        canvas_draw_str_aligned(canvas, 107, y_frequency + 13, AlignRight, AlignBottom, buffer);
-    } else if(model->max_value <= 0xFF) {
-        canvas_set_font(canvas, FontBigNumbers);
-        snprintf(
-            buffer, sizeof(buffer), "%03d/%03d", (int)model->current_step, (int)model->max_value);
-        canvas_draw_str_aligned(canvas, 64, y_frequency, AlignCenter, AlignTop, buffer);
-    } else {
-        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, y_frequency, AlignCenter, AlignTop, buffer);
-    }
-    canvas_set_font(canvas, FontSecondary);
-
-    memset(buffer, 0, sizeof(buffer));
-    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);
-
-        snprintf(
-            buffer,
-            sizeof(buffer),
-            "x%d",
-            model->extra_repeats + subbrute_protocol_repeats_count(model->attack_type));
-        canvas_draw_str_aligned(canvas, 60, 6, AlignCenter, AlignCenter, buffer);
-
-        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 =
-            icon_animation_get_width(model->icon) + icon_h_offset;
-        const uint8_t icon_v_offset = icon_animation_get_height(model->icon); // + 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);
-
-        snprintf(
-            buffer,
-            sizeof(buffer),
-            "x%d",
-            model->extra_repeats + subbrute_protocol_repeats_count(model->attack_type));
-        canvas_draw_str(canvas, 4, y - 8, buffer);
-        canvas_draw_str(canvas, 4, y - 1, "repeats");
-
-        elements_button_center(canvas, "Stop");
-    }
-}
+#include "subbrute_attack_view.h"
+#include "../subbrute_i.h"
+#include "../subbrute_protocols.h"
+#include "../helpers/gui_top_buttons.h"
+
+#include <input/input.h>
+#include <gui/elements.h>
+#include <gui/icon.h>
+#include <gui/icon_animation.h>
+#include <assets_icons.h>
+
+#define TAG "SubBruteAttackView"
+
+struct SubBruteAttackView {
+    View* view;
+    SubBruteAttackViewCallback callback;
+    void* context;
+    SubBruteAttacks attack_type;
+    uint64_t max_value;
+    uint64_t current_step;
+    bool is_attacking;
+    uint8_t extra_repeats;
+};
+
+typedef struct {
+    SubBruteAttacks attack_type;
+    uint64_t max_value;
+    uint64_t current_step;
+    uint8_t extra_repeats;
+    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);
+    SubBruteAttackView* instance = context;
+#ifdef FURI_DEBUG
+    FURI_LOG_D(TAG, "InputKey: %d", event->key);
+#endif
+
+    if(event->key == InputKeyBack && event->type == InputTypeShort) {
+        instance->is_attacking = false;
+        with_view_model(
+            instance->view,
+            SubBruteAttackViewModel * model,
+            { model->is_attacking = false; },
+            true);
+
+        instance->callback(SubBruteCustomEventTypeBackPressed, instance->context);
+        return true;
+    }
+
+    bool update = false;
+
+    if(!instance->is_attacking) {
+        if(event->type == InputTypeShort && event->key == InputKeyOk) {
+#ifdef FURI_DEBUG
+            FURI_LOG_D(TAG, "InputKey: %d OK", event->key);
+#endif
+            instance->is_attacking = true;
+            instance->callback(SubBruteCustomEventTypeTransmitStarted, instance->context);
+            update = true;
+        } else if(event->key == InputKeyUp && event->type == InputTypeShort) {
+            instance->callback(SubBruteCustomEventTypeSaveFile, instance->context);
+            update = true;
+        } else if(event->key == InputKeyUp && event->type == InputTypeLong) {
+            instance->callback(SubBruteCustomEventTypeExtraSettings, instance->context);
+            update = true;
+        } else if(event->key == InputKeyDown) {
+            instance->callback(SubBruteCustomEventTypeTransmitCustom, instance->context);
+            update = true;
+        } else if(event->type == InputTypeShort) {
+            if(event->key == InputKeyLeft) {
+                instance->callback(SubBruteCustomEventTypeChangeStepDown, instance->context);
+            } else if(event->key == InputKeyRight) {
+                instance->callback(SubBruteCustomEventTypeChangeStepUp, instance->context);
+            }
+            update = true;
+        } else if(event->type == InputTypeRepeat) {
+            if(event->key == InputKeyLeft) {
+                instance->callback(SubBruteCustomEventTypeChangeStepDownMore, instance->context);
+            } else if(event->key == InputKeyRight) {
+                instance->callback(SubBruteCustomEventTypeChangeStepUpMore, instance->context);
+            }
+            update = true;
+        }
+    } else {
+        // ATTACK Mode!
+        if((event->type == InputTypeShort || event->type == InputTypeRepeat) &&
+           (event->key == InputKeyOk || event->key == InputKeyBack)) {
+            instance->is_attacking = false;
+            instance->callback(SubBruteCustomEventTypeTransmitNotStarted, instance->context);
+
+            update = true;
+        }
+    }
+
+    if(update) {
+        with_view_model(
+            instance->view,
+            SubBruteAttackViewModel * model,
+            {
+                if(model->is_attacking != instance->is_attacking) {
+                    if(instance->is_attacking) {
+                        icon_animation_stop(model->icon);
+                        icon_animation_start(model->icon);
+                    } else {
+                        icon_animation_stop(model->icon);
+                    }
+                }
+
+                model->attack_type = instance->attack_type;
+                model->max_value = instance->max_value;
+                model->current_step = instance->current_step;
+                model->is_attacking = instance->is_attacking;
+            },
+            true);
+    }
+
+    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);
+        },
+        true);
+
+    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);
+
+    instance->attack_type = SubBruteAttackTotalCount;
+    instance->max_value = 0x00;
+    instance->current_step = 0;
+    instance->is_attacking = false;
+
+    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); },
+        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
+    instance->current_step = current_step;
+    with_view_model(
+        instance->view,
+        SubBruteAttackViewModel * model,
+        { model->current_step = current_step; },
+        true);
+}
+
+// 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,
+    uint8_t extra_repeats) {
+#ifdef FURI_DEBUG
+    FURI_LOG_I(
+        TAG,
+        "INIT, attack_type: %d, max_value: %lld, current_step: %lld, extra_repeats: %d",
+        index,
+        max_value,
+        current_step,
+        extra_repeats);
+#endif
+    instance->attack_type = index;
+    instance->max_value = max_value;
+    instance->current_step = current_step;
+    instance->is_attacking = is_attacking;
+    instance->extra_repeats = extra_repeats;
+
+    with_view_model(
+        instance->view,
+        SubBruteAttackViewModel * model,
+        {
+            model->max_value = max_value;
+            model->attack_type = index;
+            model->current_step = current_step;
+            model->is_attacking = is_attacking;
+            model->extra_repeats = extra_repeats;
+            if(is_attacking) {
+                icon_animation_start(model->icon);
+            } else {
+                icon_animation_stop(model->icon);
+            }
+        },
+        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); },
+        false);
+}
+
+void subbrute_attack_view_draw(Canvas* canvas, void* context) {
+    furi_assert(context);
+    SubBruteAttackViewModel* model = (SubBruteAttackViewModel*)context;
+    char buffer[64];
+
+    const char* attack_name = NULL;
+    attack_name = subbrute_protocol_name(model->attack_type);
+
+    // 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);
+    }
+
+    // Current Step / Max value
+    const uint8_t y_frequency = 17;
+    if(model->max_value > 9999) {
+        canvas_set_font(canvas, FontBigNumbers);
+        snprintf(buffer, sizeof(buffer), "%05d/", (int)model->current_step);
+        canvas_draw_str_aligned(canvas, 5, y_frequency, AlignLeft, AlignTop, buffer);
+
+        // Second part with another font, because BigNumber is out of screen bounds
+        canvas_set_font(canvas, FontPrimary);
+        snprintf(buffer, sizeof(buffer), "%05d", (int)model->max_value);
+        canvas_draw_str_aligned(canvas, 107, y_frequency + 13, AlignRight, AlignBottom, buffer);
+    } else if(model->max_value <= 0xFF) {
+        canvas_set_font(canvas, FontBigNumbers);
+        snprintf(
+            buffer, sizeof(buffer), "%03d/%03d", (int)model->current_step, (int)model->max_value);
+        canvas_draw_str_aligned(canvas, 64, y_frequency, AlignCenter, AlignTop, buffer);
+    } else {
+        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, y_frequency, AlignCenter, AlignTop, buffer);
+    }
+    canvas_set_font(canvas, FontSecondary);
+
+    memset(buffer, 0, sizeof(buffer));
+    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);
+
+        snprintf(
+            buffer,
+            sizeof(buffer),
+            "x%d",
+            model->extra_repeats + subbrute_protocol_repeats_count(model->attack_type));
+        canvas_draw_str_aligned(canvas, 60, 6, AlignCenter, AlignCenter, buffer);
+
+        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 =
+            icon_animation_get_width(model->icon) + icon_h_offset;
+        const uint8_t icon_v_offset = icon_animation_get_height(model->icon); // + 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);
+
+        snprintf(
+            buffer,
+            sizeof(buffer),
+            "x%d",
+            model->extra_repeats + subbrute_protocol_repeats_count(model->attack_type));
+        canvas_draw_str(canvas, 4, y - 8, buffer);
+        canvas_draw_str(canvas, 4, y - 1, "repeats");
+
+        elements_button_center(canvas, "Stop");
+    }
+}

+ 24 - 24
views/subbrute_attack_view.h

@@ -1,25 +1,25 @@
-#pragma once
-
-#include "../subbrute_custom_event.h"
-#include <gui/view.h>
-#include <input/input.h>
-#include <gui/elements.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);
-void subbrute_attack_view_init_values(
-    SubBruteAttackView* instance,
-    uint8_t index,
-    uint64_t max_value,
-    uint64_t current_step,
-    bool is_attacking,
+#pragma once
+
+#include "../subbrute_custom_event.h"
+#include <gui/view.h>
+#include <input/input.h>
+#include <gui/elements.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);
+void subbrute_attack_view_init_values(
+    SubBruteAttackView* instance,
+    uint8_t index,
+    uint64_t max_value,
+    uint64_t current_step,
+    bool is_attacking,
     uint8_t extra_repeats);
     uint8_t extra_repeats);

+ 467 - 462
views/subbrute_main_view.c

@@ -1,463 +1,468 @@
-#include "subbrute_main_view.h"
-#include "../subbrute_i.h"
-#include "../subbrute_protocols.h"
-#include "../helpers/gui_top_buttons.h"
-
-#include <input/input.h>
-#include <gui/elements.h>
-#include <gui/icon.h>
-
-#define STATUS_BAR_Y_SHIFT 14
-#define TAG "SubBruteMainView"
-
-#define ITEMS_ON_SCREEN 3
-#define ITEMS_INTERVAL 1
-#define ITEM_WIDTH 14
-#define ITEM_Y 27
-#define ITEM_HEIGHT 13
-#define TEXT_X 6
-#define TEXT_Y 37
-#define TEXT_INTERVAL 3
-#define TEXT_WIDTH 12
-#define ITEM_FRAME_RADIUS 2
-
-struct SubBruteMainView {
-    View* view;
-    SubBruteMainViewCallback callback;
-    void* context;
-    uint8_t index;
-    bool is_select_byte;
-    bool two_bytes;
-    uint64_t key_from_file;
-    uint8_t extra_repeats;
-    uint8_t window_position;
-};
-
-typedef struct {
-    uint8_t index;
-    uint8_t extra_repeats;
-    uint8_t window_position;
-    bool is_select_byte;
-    bool two_bytes;
-    uint64_t key_from_file;
-} 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 subbrute_main_view_center_displayed_key(
-    Canvas* canvas,
-    uint64_t key,
-    uint8_t index,
-    bool two_bytes) {
-    uint8_t text_x = TEXT_X;
-    uint8_t item_x = TEXT_X - ITEMS_INTERVAL;
-    canvas_set_font(canvas, FontSecondary);
-
-    for(int i = 0; i < 8; i++) {
-        char current_value[3] = {0};
-        uint8_t byte_value = (uint8_t)(key >> 8 * (7 - i)) & 0xFF;
-        snprintf(current_value, sizeof(current_value), "%02X", byte_value);
-
-        // For two bytes we need to select prev location
-        if(!two_bytes && i == index) {
-            canvas_set_color(canvas, ColorBlack);
-            canvas_draw_rbox(
-                canvas, item_x - 1, ITEM_Y, ITEM_WIDTH + 1, ITEM_HEIGHT, ITEM_FRAME_RADIUS);
-            canvas_set_color(canvas, ColorWhite);
-            canvas_draw_str(canvas, text_x, TEXT_Y, current_value);
-        } else if(two_bytes && (i == index || i == index - 1)) {
-            if(i == index) {
-                canvas_set_color(canvas, ColorBlack);
-                canvas_draw_rbox(
-                    canvas,
-                    item_x - ITEMS_INTERVAL - ITEM_WIDTH - 1,
-                    ITEM_Y,
-                    ITEM_WIDTH * 2 + ITEMS_INTERVAL * 2 + 1,
-                    ITEM_HEIGHT,
-                    ITEM_FRAME_RADIUS);
-
-                canvas_set_color(canvas, ColorWhite);
-                canvas_draw_str(canvas, text_x, TEXT_Y, current_value);
-
-                // Redraw prev element with white
-                memset(current_value, 0, sizeof(current_value));
-                byte_value = (uint8_t)(key >> 8 * (7 - i + 1)) & 0xFF;
-                snprintf(current_value, sizeof(current_value), "%02X", byte_value);
-                canvas_draw_str(
-                    canvas, text_x - (TEXT_WIDTH + TEXT_INTERVAL), TEXT_Y, current_value);
-            } else {
-                canvas_set_color(canvas, ColorWhite);
-                canvas_draw_str(canvas, text_x, TEXT_Y, current_value);
-            }
-        } else {
-            canvas_set_color(canvas, ColorBlack);
-            canvas_draw_str(canvas, text_x, TEXT_Y, current_value);
-        }
-        text_x = text_x + TEXT_WIDTH + TEXT_INTERVAL;
-        item_x = item_x + ITEM_WIDTH + ITEMS_INTERVAL;
-    }
-
-    // Return normal color
-    canvas_set_color(canvas, ColorBlack);
-}
-
-void subbrute_main_view_draw(Canvas* canvas, SubBruteMainViewModel* model) {
-    uint16_t screen_width = canvas_width(canvas);
-    uint16_t screen_height = canvas_height(canvas);
-
-    if(model->is_select_byte) {
-#ifdef FURI_DEBUG
-        //FURI_LOG_D(TAG, "key_from_file: %s", model->key_from_file);
-#endif
-        //char msg_index[18];
-        //snprintf(msg_index, sizeof(msg_index), "Field index: %d", model->index);
-        canvas_set_font(canvas, FontSecondary);
-        canvas_draw_str_aligned(
-            canvas, 64, 17, AlignCenter, AlignTop, "Please select values to calc:");
-
-        subbrute_main_view_center_displayed_key(
-            canvas, model->key_from_file, model->index, model->two_bytes);
-        //const char* line = furi_string_get_cstr(menu_items);
-        //canvas_set_font(canvas, FontSecondary);
-        //canvas_draw_str_aligned(
-        //    canvas, 64, 37, AlignCenter, AlignTop, furi_string_get_cstr(menu_items));
-
-        elements_button_center(canvas, "Select");
-        if(model->index > 0) {
-            elements_button_left(canvas, " ");
-        }
-        if(model->index < 7) {
-            elements_button_right(canvas, " ");
-        }
-        // Switch to another mode
-        if(model->two_bytes) {
-            elements_button_top_left(canvas, "One byte");
-        } else {
-            elements_button_top_left(canvas, "Two bytes");
-        }
-    } else {
-        // 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, 3, AlignCenter, AlignTop, SUBBRUTEFORCER_VER);
-        canvas_invert_color(canvas);
-
-        // Menu
-        canvas_set_color(canvas, ColorBlack);
-        canvas_set_font(canvas, FontSecondary);
-        const uint8_t item_height = 16;
-
-#ifdef FURI_DEBUG
-        //FURI_LOG_D(TAG, "window_position: %d, index: %d", model->window_position, model->index);
-#endif
-        for(uint8_t position = 0; position < SubBruteAttackTotalCount; ++position) {
-            uint8_t item_position = position - model->window_position;
-
-            if(item_position < ITEMS_ON_SCREEN) {
-                if(model->index == position) {
-                    canvas_draw_str_aligned(
-                        canvas,
-                        4,
-                        9 + (item_position * item_height) + STATUS_BAR_Y_SHIFT,
-                        AlignLeft,
-                        AlignCenter,
-                        subbrute_protocol_name(position));
-
-                    if(model->extra_repeats > 0) {
-                        canvas_set_font(canvas, FontBatteryPercent);
-                        char buffer[10];
-                        snprintf(
-                            buffer,
-                            sizeof(buffer),
-                            "x%d",
-                            model->extra_repeats + subbrute_protocol_repeats_count(model->index));
-                        canvas_draw_str_aligned(
-                            canvas,
-                            screen_width - 15,
-                            9 + (item_position * item_height) + STATUS_BAR_Y_SHIFT,
-                            AlignLeft,
-                            AlignCenter,
-                            buffer);
-                        canvas_set_font(canvas, FontSecondary);
-                    }
-
-                    elements_frame(
-                        canvas, 1, 1 + (item_position * item_height) + STATUS_BAR_Y_SHIFT, 124, 15);
-                } else {
-                    canvas_draw_str_aligned(
-                        canvas,
-                        4,
-                        9 + (item_position * item_height) + STATUS_BAR_Y_SHIFT,
-                        AlignLeft,
-                        AlignCenter,
-                        subbrute_protocol_name(position));
-                }
-            }
-        }
-
-        elements_scrollbar_pos(
-            canvas,
-            screen_width,
-            STATUS_BAR_Y_SHIFT + 2,
-            screen_height - STATUS_BAR_Y_SHIFT,
-            model->index,
-            SubBruteAttackTotalCount);
-    }
-}
-
-bool subbrute_main_view_input(InputEvent* event, void* context) {
-    furi_assert(event);
-    furi_assert(context);
-
-    if(event->key == InputKeyBack && event->type == InputTypeShort) {
-#ifdef FURI_DEBUG
-        FURI_LOG_I(TAG, "InputKey: BACK");
-#endif
-        return false;
-    }
-
-    SubBruteMainView* instance = context;
-#ifdef FURI_DEBUG
-    FURI_LOG_D(TAG, "InputKey: %d, extra_repeats: %d", event->key, instance->extra_repeats);
-#endif
-    const uint8_t min_value = 0;
-    const uint8_t correct_total = SubBruteAttackTotalCount - 1;
-    uint8_t max_repeats = 9 - subbrute_protocol_repeats_count(instance->index);
-
-    bool updated = false;
-    bool consumed = false;
-    bool is_short = (event->type == InputTypeShort) || (event->type == InputTypeRepeat);
-
-    if(!instance->is_select_byte) {
-        if(event->key == InputKeyUp && is_short) {
-            if(instance->index == min_value) {
-                instance->index = correct_total;
-            } else {
-                instance->index = CLAMP(instance->index - 1, correct_total, min_value);
-            }
-            instance->extra_repeats = 0;
-            updated = true;
-            consumed = true;
-        } else if(event->key == InputKeyDown && is_short) {
-            if(instance->index == correct_total) {
-                instance->index = min_value;
-            } else {
-                instance->index = CLAMP(instance->index + 1, correct_total, min_value);
-            }
-            instance->extra_repeats = 0;
-            updated = true;
-            consumed = true;
-        } else if(event->key == InputKeyLeft && is_short) {
-            instance->extra_repeats = CLAMP(instance->extra_repeats - 1, max_repeats, 0);
-
-            updated = true;
-            consumed = true;
-        } else if(event->key == InputKeyRight && is_short) {
-            instance->extra_repeats = CLAMP(instance->extra_repeats + 1, max_repeats, 0);
-
-            updated = true;
-            consumed = true;
-        } else if(event->key == InputKeyOk && is_short) {
-            if(instance->index == SubBruteAttackLoadFile) {
-                instance->callback(SubBruteCustomEventTypeLoadFile, instance->context);
-            } else {
-                instance->callback(SubBruteCustomEventTypeMenuSelected, instance->context);
-            }
-            consumed = true;
-            updated = true;
-        }
-        if(updated) {
-            instance->window_position = instance->index;
-            if(instance->window_position > 0) {
-                instance->window_position -= 1;
-            }
-
-            if(SubBruteAttackTotalCount <= ITEMS_ON_SCREEN) {
-                instance->window_position = 0;
-            } else {
-                if(instance->window_position >= (SubBruteAttackTotalCount - ITEMS_ON_SCREEN)) {
-                    instance->window_position = (SubBruteAttackTotalCount - ITEMS_ON_SCREEN);
-                }
-            }
-        }
-    } else if(is_short) {
-        if(event->key == InputKeyLeft) {
-            if((instance->index > 0 && !instance->two_bytes) ||
-               (instance->two_bytes && instance->index > 1)) {
-                instance->index--;
-            }
-            updated = true;
-            consumed = true;
-        } else if(event->key == InputKeyRight) {
-            if(instance->index < 7) {
-                instance->index++;
-            }
-            updated = true;
-            consumed = true;
-        } else if(event->key == InputKeyUp) {
-            instance->two_bytes = !instance->two_bytes;
-            // Because index is changing
-            if(instance->two_bytes && instance->index < 7) {
-                instance->index++;
-            }
-            // instance->callback(
-            //     instance->two_bytes ? SubBruteCustomEventTypeChangeStepUp :
-            //                           SubBruteCustomEventTypeChangeStepDown,
-            //     instance->context);
-
-            updated = true;
-            consumed = true;
-        } else if(event->key == InputKeyOk) {
-            instance->callback(SubBruteCustomEventTypeIndexSelected, instance->context);
-            consumed = true;
-            updated = true;
-        }
-    }
-
-    if(updated) {
-        with_view_model(
-            instance->view,
-            SubBruteMainViewModel * model,
-            {
-                model->index = instance->index;
-                model->window_position = instance->window_position;
-                model->key_from_file = instance->key_from_file;
-                model->is_select_byte = instance->is_select_byte;
-                model->two_bytes = instance->two_bytes;
-                model->extra_repeats = instance->extra_repeats;
-            },
-            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);
-
-    instance->index = 0;
-    instance->window_position = 0;
-    instance->key_from_file = 0;
-    instance->is_select_byte = false;
-    instance->two_bytes = false;
-    instance->extra_repeats = 0;
-    with_view_model(
-        instance->view,
-        SubBruteMainViewModel * model,
-        {
-            model->index = instance->index;
-            model->window_position = instance->window_position;
-            model->key_from_file = instance->key_from_file;
-            model->is_select_byte = instance->is_select_byte;
-            model->two_bytes = instance->two_bytes;
-            model->extra_repeats = instance->extra_repeats;
-        },
-        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,
-    bool two_bytes,
-    uint64_t key_from_file) {
-    furi_assert(instance);
-    furi_assert(idx < SubBruteAttackTotalCount);
-#ifdef FURI_DEBUG
-    FURI_LOG_I(TAG, "Set index: %d, is_select_byte: %d", idx, is_select_byte);
-#endif
-    instance->is_select_byte = is_select_byte;
-    instance->two_bytes = two_bytes;
-    instance->key_from_file = key_from_file;
-    instance->index = idx;
-    instance->window_position = idx;
-
-    if(!is_select_byte) {
-        if(instance->window_position > 0) {
-            instance->window_position -= 1;
-        }
-
-        if(SubBruteAttackTotalCount <= ITEMS_ON_SCREEN) {
-            instance->window_position = 0;
-        } else {
-            if(instance->window_position >= (SubBruteAttackTotalCount - ITEMS_ON_SCREEN)) {
-                instance->window_position = (SubBruteAttackTotalCount - ITEMS_ON_SCREEN);
-            }
-        }
-    }
-
-    with_view_model(
-        instance->view,
-        SubBruteMainViewModel * model,
-        {
-            model->index = instance->index;
-            model->window_position = instance->window_position;
-            model->key_from_file = instance->key_from_file;
-            model->is_select_byte = instance->is_select_byte;
-            model->two_bytes = instance->two_bytes;
-            model->extra_repeats = instance->extra_repeats;
-        },
-        true);
-}
-
-SubBruteAttacks subbrute_main_view_get_index(SubBruteMainView* instance) {
-    furi_assert(instance);
-    return instance->index;
-}
-
-uint8_t subbrute_main_view_get_extra_repeats(SubBruteMainView* instance) {
-    furi_assert(instance);
-    return instance->extra_repeats;
-}
-
-bool subbrute_main_view_get_two_bytes(SubBruteMainView* instance) {
-    furi_assert(instance);
-    return instance->two_bytes;
+#include "subbrute_main_view.h"
+#include "../subbrute_i.h"
+#include "../subbrute_protocols.h"
+#include "../helpers/gui_top_buttons.h"
+
+#include <input/input.h>
+#include <gui/elements.h>
+#include <gui/icon.h>
+
+#define STATUS_BAR_Y_SHIFT 14
+#define TAG "SubBruteMainView"
+
+#define ITEMS_ON_SCREEN 3
+#define ITEMS_INTERVAL 1
+#define ITEM_WIDTH 14
+#define ITEM_Y 27
+#define ITEM_HEIGHT 13
+#define TEXT_X 6
+#define TEXT_Y 37
+#define TEXT_INTERVAL 3
+#define TEXT_WIDTH 12
+#define ITEM_FRAME_RADIUS 2
+
+struct SubBruteMainView {
+    View* view;
+    SubBruteMainViewCallback callback;
+    void* context;
+    uint8_t index;
+    bool is_select_byte;
+    bool two_bytes;
+    uint64_t key_from_file;
+    uint8_t extra_repeats;
+    uint8_t window_position;
+};
+
+typedef struct {
+    uint8_t index;
+    uint8_t extra_repeats;
+    uint8_t window_position;
+    bool is_select_byte;
+    bool two_bytes;
+    uint64_t key_from_file;
+} 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 subbrute_main_view_center_displayed_key(
+    Canvas* canvas,
+    uint64_t key,
+    uint8_t index,
+    bool two_bytes) {
+    uint8_t text_x = TEXT_X;
+    uint8_t item_x = TEXT_X - ITEMS_INTERVAL;
+    canvas_set_font(canvas, FontSecondary);
+
+    for(int i = 0; i < 8; i++) {
+        char current_value[3] = {0};
+        uint8_t byte_value = (uint8_t)(key >> 8 * (7 - i)) & 0xFF;
+        snprintf(current_value, sizeof(current_value), "%02X", byte_value);
+
+        // For two bytes we need to select prev location
+        if(!two_bytes && i == index) {
+            canvas_set_color(canvas, ColorBlack);
+            canvas_draw_rbox(
+                canvas, item_x - 1, ITEM_Y, ITEM_WIDTH + 1, ITEM_HEIGHT, ITEM_FRAME_RADIUS);
+            canvas_set_color(canvas, ColorWhite);
+            canvas_draw_str(canvas, text_x, TEXT_Y, current_value);
+        } else if(two_bytes && (i == index || i == index - 1)) {
+            if(i == index) {
+                canvas_set_color(canvas, ColorBlack);
+                canvas_draw_rbox(
+                    canvas,
+                    item_x - ITEMS_INTERVAL - ITEM_WIDTH - 1,
+                    ITEM_Y,
+                    ITEM_WIDTH * 2 + ITEMS_INTERVAL * 2 + 1,
+                    ITEM_HEIGHT,
+                    ITEM_FRAME_RADIUS);
+
+                canvas_set_color(canvas, ColorWhite);
+                canvas_draw_str(canvas, text_x, TEXT_Y, current_value);
+
+                // Redraw prev element with white
+                memset(current_value, 0, sizeof(current_value));
+                byte_value = (uint8_t)(key >> 8 * (7 - i + 1)) & 0xFF;
+                snprintf(current_value, sizeof(current_value), "%02X", byte_value);
+                canvas_draw_str(
+                    canvas, text_x - (TEXT_WIDTH + TEXT_INTERVAL), TEXT_Y, current_value);
+            } else {
+                canvas_set_color(canvas, ColorWhite);
+                canvas_draw_str(canvas, text_x, TEXT_Y, current_value);
+            }
+        } else {
+            canvas_set_color(canvas, ColorBlack);
+            canvas_draw_str(canvas, text_x, TEXT_Y, current_value);
+        }
+        text_x = text_x + TEXT_WIDTH + TEXT_INTERVAL;
+        item_x = item_x + ITEM_WIDTH + ITEMS_INTERVAL;
+    }
+
+    // Return normal color
+    canvas_set_color(canvas, ColorBlack);
+}
+
+void subbrute_main_view_draw(Canvas* canvas, SubBruteMainViewModel* model) {
+    uint16_t screen_width = canvas_width(canvas);
+    uint16_t screen_height = canvas_height(canvas);
+
+    if(model->is_select_byte) {
+#ifdef FURI_DEBUG
+        //FURI_LOG_D(TAG, "key_from_file: %s", model->key_from_file);
+#endif
+        //char msg_index[18];
+        //snprintf(msg_index, sizeof(msg_index), "Field index: %d", model->index);
+        canvas_set_font(canvas, FontSecondary);
+        canvas_draw_str_aligned(
+            canvas, 64, 17, AlignCenter, AlignTop, "Please select values to calc:");
+
+        subbrute_main_view_center_displayed_key(
+            canvas, model->key_from_file, model->index, model->two_bytes);
+        //const char* line = furi_string_get_cstr(menu_items);
+        //canvas_set_font(canvas, FontSecondary);
+        //canvas_draw_str_aligned(
+        //    canvas, 64, 37, AlignCenter, AlignTop, furi_string_get_cstr(menu_items));
+
+        elements_button_center(canvas, "Select");
+        if(model->index > 0) {
+            elements_button_left(canvas, " ");
+        }
+        if(model->index < 7) {
+            elements_button_right(canvas, " ");
+        }
+        // Switch to another mode
+        if(model->two_bytes) {
+            elements_button_top_left(canvas, "One byte");
+        } else {
+            elements_button_top_left(canvas, "Two bytes");
+        }
+    } else {
+        // 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, 3, AlignCenter, AlignTop, SUBBRUTEFORCER_VER);
+        canvas_invert_color(canvas);
+
+        // Menu
+        canvas_set_color(canvas, ColorBlack);
+        canvas_set_font(canvas, FontSecondary);
+        const uint8_t item_height = 16;
+
+#ifdef FURI_DEBUG
+        //FURI_LOG_D(TAG, "window_position: %d, index: %d", model->window_position, model->index);
+#endif
+        for(uint8_t position = 0; position < SubBruteAttackTotalCount; ++position) {
+            uint8_t item_position = position - model->window_position;
+
+            if(item_position < ITEMS_ON_SCREEN) {
+                if(model->index == position) {
+                    canvas_draw_str_aligned(
+                        canvas,
+                        3,
+                        9 + (item_position * item_height) + STATUS_BAR_Y_SHIFT,
+                        AlignLeft,
+                        AlignCenter,
+                        subbrute_protocol_name(position));
+
+                    if(model->extra_repeats > 0) {
+                        canvas_set_font(canvas, FontBatteryPercent);
+                        char buffer[10];
+                        snprintf(
+                            buffer,
+                            sizeof(buffer),
+                            "x%d",
+                            model->extra_repeats + subbrute_protocol_repeats_count(model->index));
+                        uint8_t temp_x_offset_repeats = 18;
+                        if(model->extra_repeats + subbrute_protocol_repeats_count(model->index) <
+                           10) {
+                            temp_x_offset_repeats = 15;
+                        }
+                        canvas_draw_str_aligned(
+                            canvas,
+                            screen_width - temp_x_offset_repeats,
+                            9 + (item_position * item_height) + STATUS_BAR_Y_SHIFT,
+                            AlignLeft,
+                            AlignCenter,
+                            buffer);
+                        canvas_set_font(canvas, FontSecondary);
+                    }
+
+                    elements_frame(
+                        canvas, 1, 1 + (item_position * item_height) + STATUS_BAR_Y_SHIFT, 124, 15);
+                } else {
+                    canvas_draw_str_aligned(
+                        canvas,
+                        4,
+                        9 + (item_position * item_height) + STATUS_BAR_Y_SHIFT,
+                        AlignLeft,
+                        AlignCenter,
+                        subbrute_protocol_name(position));
+                }
+            }
+        }
+
+        elements_scrollbar_pos(
+            canvas,
+            screen_width,
+            STATUS_BAR_Y_SHIFT + 2,
+            screen_height - STATUS_BAR_Y_SHIFT,
+            model->index,
+            SubBruteAttackTotalCount);
+    }
+}
+
+bool subbrute_main_view_input(InputEvent* event, void* context) {
+    furi_assert(event);
+    furi_assert(context);
+
+    if(event->key == InputKeyBack && event->type == InputTypeShort) {
+#ifdef FURI_DEBUG
+        FURI_LOG_I(TAG, "InputKey: BACK");
+#endif
+        return false;
+    }
+
+    SubBruteMainView* instance = context;
+#ifdef FURI_DEBUG
+    FURI_LOG_D(TAG, "InputKey: %d, extra_repeats: %d", event->key, instance->extra_repeats);
+#endif
+    const uint8_t min_value = 0;
+    const uint8_t correct_total = SubBruteAttackTotalCount - 1;
+    uint8_t max_repeats = 14 - subbrute_protocol_repeats_count(instance->index);
+
+    bool updated = false;
+    bool consumed = false;
+    bool is_short = (event->type == InputTypeShort) || (event->type == InputTypeRepeat);
+
+    if(!instance->is_select_byte) {
+        if(event->key == InputKeyUp && is_short) {
+            if(instance->index == min_value) {
+                instance->index = correct_total;
+            } else {
+                instance->index = CLAMP(instance->index - 1, correct_total, min_value);
+            }
+            instance->extra_repeats = 0;
+            updated = true;
+            consumed = true;
+        } else if(event->key == InputKeyDown && is_short) {
+            if(instance->index == correct_total) {
+                instance->index = min_value;
+            } else {
+                instance->index = CLAMP(instance->index + 1, correct_total, min_value);
+            }
+            instance->extra_repeats = 0;
+            updated = true;
+            consumed = true;
+        } else if(event->key == InputKeyLeft && is_short) {
+            instance->extra_repeats = CLAMP(instance->extra_repeats - 1, max_repeats, 0);
+
+            updated = true;
+            consumed = true;
+        } else if(event->key == InputKeyRight && is_short) {
+            instance->extra_repeats = CLAMP(instance->extra_repeats + 1, max_repeats, 0);
+
+            updated = true;
+            consumed = true;
+        } else if(event->key == InputKeyOk && is_short) {
+            if(instance->index == SubBruteAttackLoadFile) {
+                instance->callback(SubBruteCustomEventTypeLoadFile, instance->context);
+            } else {
+                instance->callback(SubBruteCustomEventTypeMenuSelected, instance->context);
+            }
+            consumed = true;
+            updated = true;
+        }
+        if(updated) {
+            instance->window_position = instance->index;
+            if(instance->window_position > 0) {
+                instance->window_position -= 1;
+            }
+
+            if(SubBruteAttackTotalCount <= ITEMS_ON_SCREEN) {
+                instance->window_position = 0;
+            } else {
+                if(instance->window_position >= (SubBruteAttackTotalCount - ITEMS_ON_SCREEN)) {
+                    instance->window_position = (SubBruteAttackTotalCount - ITEMS_ON_SCREEN);
+                }
+            }
+        }
+    } else if(is_short) {
+        if(event->key == InputKeyLeft) {
+            if((instance->index > 0 && !instance->two_bytes) ||
+               (instance->two_bytes && instance->index > 1)) {
+                instance->index--;
+            }
+            updated = true;
+            consumed = true;
+        } else if(event->key == InputKeyRight) {
+            if(instance->index < 7) {
+                instance->index++;
+            }
+            updated = true;
+            consumed = true;
+        } else if(event->key == InputKeyUp) {
+            instance->two_bytes = !instance->two_bytes;
+            // Because index is changing
+            if(instance->two_bytes && instance->index < 7) {
+                instance->index++;
+            }
+            // instance->callback(
+            //     instance->two_bytes ? SubBruteCustomEventTypeChangeStepUp :
+            //                           SubBruteCustomEventTypeChangeStepDown,
+            //     instance->context);
+
+            updated = true;
+            consumed = true;
+        } else if(event->key == InputKeyOk) {
+            instance->callback(SubBruteCustomEventTypeIndexSelected, instance->context);
+            consumed = true;
+            updated = true;
+        }
+    }
+
+    if(updated) {
+        with_view_model(
+            instance->view,
+            SubBruteMainViewModel * model,
+            {
+                model->index = instance->index;
+                model->window_position = instance->window_position;
+                model->key_from_file = instance->key_from_file;
+                model->is_select_byte = instance->is_select_byte;
+                model->two_bytes = instance->two_bytes;
+                model->extra_repeats = instance->extra_repeats;
+            },
+            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);
+
+    instance->index = 0;
+    instance->window_position = 0;
+    instance->key_from_file = 0;
+    instance->is_select_byte = false;
+    instance->two_bytes = false;
+    instance->extra_repeats = 0;
+    with_view_model(
+        instance->view,
+        SubBruteMainViewModel * model,
+        {
+            model->index = instance->index;
+            model->window_position = instance->window_position;
+            model->key_from_file = instance->key_from_file;
+            model->is_select_byte = instance->is_select_byte;
+            model->two_bytes = instance->two_bytes;
+            model->extra_repeats = instance->extra_repeats;
+        },
+        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,
+    bool two_bytes,
+    uint64_t key_from_file) {
+    furi_assert(instance);
+    furi_assert(idx < SubBruteAttackTotalCount);
+#ifdef FURI_DEBUG
+    FURI_LOG_I(TAG, "Set index: %d, is_select_byte: %d", idx, is_select_byte);
+#endif
+    instance->is_select_byte = is_select_byte;
+    instance->two_bytes = two_bytes;
+    instance->key_from_file = key_from_file;
+    instance->index = idx;
+    instance->window_position = idx;
+
+    if(!is_select_byte) {
+        if(instance->window_position > 0) {
+            instance->window_position -= 1;
+        }
+
+        if(SubBruteAttackTotalCount <= ITEMS_ON_SCREEN) {
+            instance->window_position = 0;
+        } else {
+            if(instance->window_position >= (SubBruteAttackTotalCount - ITEMS_ON_SCREEN)) {
+                instance->window_position = (SubBruteAttackTotalCount - ITEMS_ON_SCREEN);
+            }
+        }
+    }
+
+    with_view_model(
+        instance->view,
+        SubBruteMainViewModel * model,
+        {
+            model->index = instance->index;
+            model->window_position = instance->window_position;
+            model->key_from_file = instance->key_from_file;
+            model->is_select_byte = instance->is_select_byte;
+            model->two_bytes = instance->two_bytes;
+            model->extra_repeats = instance->extra_repeats;
+        },
+        true);
+}
+
+SubBruteAttacks subbrute_main_view_get_index(SubBruteMainView* instance) {
+    furi_assert(instance);
+    return instance->index;
+}
+
+uint8_t subbrute_main_view_get_extra_repeats(SubBruteMainView* instance) {
+    furi_assert(instance);
+    return instance->extra_repeats;
+}
+
+bool subbrute_main_view_get_two_bytes(SubBruteMainView* instance) {
+    furi_assert(instance);
+    return instance->two_bytes;
 }
 }

+ 31 - 31
views/subbrute_main_view.h

@@ -1,32 +1,32 @@
-#pragma once
-
-#include "../subbrute_custom_event.h"
-#include "../subbrute_protocols.h"
-#include <gui/view.h>
-#include <input/input.h>
-#include <gui/elements.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,
-    bool two_bytes,
-    uint64_t file_key);
-SubBruteAttacks subbrute_main_view_get_index(SubBruteMainView* instance);
-uint8_t subbrute_main_view_get_extra_repeats(SubBruteMainView* instance);
-bool subbrute_main_view_get_two_bytes(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);
+#pragma once
+
+#include "../subbrute_custom_event.h"
+#include "../subbrute_protocols.h"
+#include <gui/view.h>
+#include <input/input.h>
+#include <gui/elements.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,
+    bool two_bytes,
+    uint64_t file_key);
+SubBruteAttacks subbrute_main_view_get_index(SubBruteMainView* instance);
+uint8_t subbrute_main_view_get_extra_repeats(SubBruteMainView* instance);
+bool subbrute_main_view_get_two_bytes(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);
 void subbrute_attack_view_draw(Canvas* canvas, void* context);