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

Merge branch 'fz-dev' into dev

10697207+xMasterX@users.noreply.github.com 3 лет назад
Родитель
Сommit
aa8001e623

+ 8 - 0
applications/plugins/subbrute/LICENSE.md

@@ -0,0 +1,8 @@
+/*
+ * ----------------------------------------------------------------------------
+ * "THE BEER-WARE LICENSE" (Revision 42):
+ * @G4N4P4T1 wrote this file. As long as you retain this notice you
+ * can do whatever you want with this stuff. If we meet some day, and you think
+ * this stuff is worth it, you can buy me a beer in return.
+ * ----------------------------------------------------------------------------
+ */

+ 4 - 0
applications/plugins/subbrute/README.md

@@ -0,0 +1,4 @@
+# FlipFrid
+
+SubGhz Fuzzer
+select your base message, the field to fuzz and let's get fuzzy !

+ 10 - 0
applications/plugins/subbrute/application.fam

@@ -0,0 +1,10 @@
+App(
+    appid="subbrute",
+    name="Sub-GHz Bruteforcer",
+    apptype=FlipperAppType.PLUGIN,
+    entry_point="subbrute_start",
+    cdefines=["APP_SUB_BRUTE"],
+    requires=["gui","dialogs"],
+    stack_size=2 * 1024,
+    order=11,
+)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

+ 267 - 0
applications/plugins/subbrute/subbrute.c

@@ -0,0 +1,267 @@
+#include "subbrute.h"
+
+#include "scene/subbrute_scene_load_file.h"
+#include "scene/subbrute_scene_select_field.h"
+#include "scene/subbrute_scene_run_attack.h"
+#include "scene/subbrute_scene_entrypoint.h"
+#include "scene/subbrute_scene_save_name.h"
+
+static void draw_callback(Canvas* const canvas, void* ctx) {
+    SubBruteState* subbrute_state = (SubBruteState*)acquire_mutex((ValueMutex*)ctx, 100);
+
+    if(subbrute_state == NULL) {
+        return;
+    }
+
+    // Draw correct Canvas
+    switch(subbrute_state->current_scene) {
+    case NoneScene:
+    case SceneSelectFile:
+        subbrute_scene_load_file_on_draw(canvas, subbrute_state);
+        break;
+    case SceneSelectField:
+        subbrute_scene_select_field_on_draw(canvas, subbrute_state);
+        break;
+    case SceneAttack:
+        subbrute_scene_run_attack_on_draw(canvas, subbrute_state);
+        break;
+    case SceneEntryPoint:
+        subbrute_scene_entrypoint_on_draw(canvas, subbrute_state);
+        break;
+    case SceneSaveName:
+        break;
+    }
+
+    release_mutex((ValueMutex*)ctx, subbrute_state);
+}
+
+void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
+    furi_assert(event_queue);
+
+    SubBruteEvent event = {
+        .evt_type = EventTypeKey, .key = input_event->key, .input_type = input_event->type};
+    furi_message_queue_put(event_queue, &event, 100);
+}
+
+static void timer_callback(FuriMessageQueue* event_queue) {
+    furi_assert(event_queue);
+    SubBruteEvent event = {
+        .evt_type = EventTypeTick, .key = InputKeyUp, .input_type = InputTypeRelease};
+    furi_message_queue_put(event_queue, &event, 100);
+}
+
+SubBruteState* subbrute_alloc() {
+    SubBruteState* subbrute = malloc(sizeof(SubBruteState));
+
+    string_init(subbrute->protocol);
+    string_init(subbrute->preset);
+    string_init(subbrute->file_path);
+    string_init(subbrute->file_path_tmp);
+    string_init_set(subbrute->notification_msg, "");
+    string_init(subbrute->candidate);
+    string_init(subbrute->flipper_format_string);
+
+    subbrute->previous_scene = NoneScene;
+    subbrute->current_scene = SceneSelectFile;
+    subbrute->is_running = true;
+    subbrute->is_attacking = false;
+    subbrute->key_index = 7;
+    subbrute->notify = furi_record_open(RECORD_NOTIFICATION);
+
+    subbrute->view_dispatcher = view_dispatcher_alloc();
+
+    //Dialog
+    subbrute->dialogs = furi_record_open(RECORD_DIALOGS);
+
+    subbrute->preset_def = malloc(sizeof(SubGhzPresetDefinition));
+
+    //subbrute->flipper_format = flipper_format_string_alloc();
+    //subbrute->environment = subghz_environment_alloc();
+
+    return subbrute;
+}
+
+void subbrute_free(SubBruteState* subbrute) {
+    //Dialog
+    furi_record_close(RECORD_DIALOGS);
+
+    notification_message(subbrute->notify, &sequence_blink_stop);
+
+    furi_record_close(RECORD_NOTIFICATION);
+
+    view_dispatcher_free(subbrute->view_dispatcher);
+
+    string_clear(subbrute->preset);
+    string_clear(subbrute->candidate);
+
+    // Path strings
+    string_clear(subbrute->file_path);
+    string_clear(subbrute->file_path_tmp);
+    string_clear(subbrute->notification_msg);
+    string_clear(subbrute->candidate);
+    string_clear(subbrute->flipper_format_string);
+
+    //flipper_format_free(subbrute->flipper_format);
+    //subghz_environment_free(subbrute->environment);
+    //subghz_receiver_free(subbrute->receiver);
+
+    free(subbrute->preset_def);
+
+    // The rest
+    free(subbrute);
+}
+
+// ENTRYPOINT
+int32_t subbrute_start(void* p) {
+    UNUSED(p);
+    // Input
+    FURI_LOG_I(TAG, "Initializing input");
+    FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(SubBruteEvent));
+    SubBruteState* subbrute_state = subbrute_alloc();
+    ValueMutex subbrute_state_mutex;
+
+    // Mutex
+    FURI_LOG_I(TAG, "Initializing flipfrid mutex");
+    if(!init_mutex(&subbrute_state_mutex, subbrute_state, sizeof(SubBruteState))) {
+        FURI_LOG_E(TAG, "cannot create mutex\r\n");
+        furi_message_queue_free(event_queue);
+        subbrute_free(subbrute_state);
+        return 255;
+    }
+
+    furi_hal_power_suppress_charge_enter();
+
+    // Configure view port
+    FURI_LOG_I(TAG, "Initializing viewport");
+    ViewPort* view_port = view_port_alloc();
+    view_port_draw_callback_set(view_port, draw_callback, &subbrute_state_mutex);
+    view_port_input_callback_set(view_port, input_callback, event_queue);
+
+    // Configure timer
+    FURI_LOG_I(TAG, "Initializing timer");
+    FuriTimer* timer = furi_timer_alloc(timer_callback, FuriTimerTypePeriodic, event_queue);
+    furi_timer_start(timer, furi_kernel_get_tick_frequency() / 10); // 10 times per second
+
+    // Register view port in GUI
+    FURI_LOG_I(TAG, "Initializing gui");
+    subbrute_state->gui = furi_record_open(RECORD_GUI);
+    gui_add_view_port(subbrute_state->gui, view_port, GuiLayerFullscreen);
+
+    view_dispatcher_attach_to_gui(
+        subbrute_state->view_dispatcher, subbrute_state->gui, ViewDispatcherTypeFullscreen);
+
+    subbrute_state->current_scene = SceneEntryPoint;
+
+    // Init values
+    SubBruteEvent event;
+    while(subbrute_state->is_running) {
+        // Get next event
+        FuriStatus event_status = furi_message_queue_get(event_queue, &event, 25);
+        if(event_status == FuriStatusOk) {
+            if(event.evt_type == EventTypeKey) {
+                //Handle event key
+                FURI_LOG_D(TAG, "EVENT ###");
+                switch(subbrute_state->current_scene) {
+                case SceneSelectFile:
+                    subbrute_scene_load_file_on_event(event, subbrute_state);
+                    break;
+                case SceneSelectField:
+                    subbrute_scene_select_field_on_event(event, subbrute_state);
+                    break;
+                case SceneSaveName:
+                    subbrute_scene_save_name_on_event(event, subbrute_state);
+                    break;
+                case SceneAttack:
+                    subbrute_scene_run_attack_on_event(event, subbrute_state);
+                    break;
+                case NoneScene:
+                case SceneEntryPoint:
+                    subbrute_scene_entrypoint_on_event(event, subbrute_state);
+                    break;
+                }
+
+            } else if(event.evt_type == EventTypeTick) {
+                //Handle event tick
+                if(subbrute_state->current_scene != subbrute_state->previous_scene) {
+                    // Trigger Exit Scene
+                    switch(subbrute_state->previous_scene) {
+                    case SceneSelectFile:
+                        subbrute_scene_load_file_on_exit(subbrute_state);
+                        break;
+                    case SceneSelectField:
+                        subbrute_scene_select_field_on_exit(subbrute_state);
+                        break;
+                    case SceneAttack:
+                        subbrute_scene_run_attack_on_exit(subbrute_state);
+                        break;
+                    case SceneEntryPoint:
+                        subbrute_scene_entrypoint_on_exit(subbrute_state);
+                        break;
+                    case SceneSaveName:
+                        subbrute_scene_save_name_on_exit(subbrute_state);
+                        break;
+                    case NoneScene:
+                        break;
+                    }
+
+                    // Trigger Entry Scene
+                    switch(subbrute_state->current_scene) {
+                    case NoneScene:
+                    case SceneSelectFile:
+                        subbrute_scene_load_file_on_enter(subbrute_state);
+                        break;
+                    case SceneSelectField:
+                        subbrute_scene_select_field_on_enter(subbrute_state);
+                        break;
+                    case SceneAttack:
+                        subbrute_scene_run_attack_on_enter(subbrute_state);
+                        break;
+                    case SceneSaveName:
+                        subbrute_scene_save_name_on_enter(subbrute_state);
+                        break;
+                    case SceneEntryPoint:
+                        subbrute_scene_entrypoint_on_enter(subbrute_state);
+                        break;
+                    }
+                    subbrute_state->previous_scene = subbrute_state->current_scene;
+                }
+
+                // Trigger Tick Scene
+                switch(subbrute_state->current_scene) {
+                case NoneScene:
+                case SceneSelectFile:
+                    subbrute_scene_load_file_on_tick(subbrute_state);
+                    break;
+                case SceneSelectField:
+                    subbrute_scene_select_field_on_tick(subbrute_state);
+                    break;
+                case SceneAttack:
+                    //subbrute_scene_run_attack_on_tick(subbrute_state);
+                    break;
+                case SceneEntryPoint:
+                    subbrute_scene_entrypoint_on_tick(subbrute_state);
+                    break;
+                case SceneSaveName:
+                    subbrute_scene_save_name_on_tick(subbrute_state);
+                    break;
+                }
+                view_port_update(view_port);
+            }
+        }
+    }
+
+    // Cleanup
+    furi_timer_stop(timer);
+    furi_timer_free(timer);
+
+    furi_hal_power_suppress_charge_exit();
+
+    FURI_LOG_I(TAG, "Cleaning up");
+    gui_remove_view_port(subbrute_state->gui, view_port);
+    view_port_free(view_port);
+    furi_message_queue_free(event_queue);
+    furi_record_close(RECORD_GUI);
+    subbrute_free(subbrute_state);
+
+    return 0;
+}

+ 110 - 0
applications/plugins/subbrute/subbrute.h

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

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

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

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

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