Procházet zdrojové kódy

Merge pull request #1 from derskythe/feat/more-repeats

Feat/more repeats
Der Skythe před 3 roky
rodič
revize
eca764ad8f

+ 3 - 2
helpers/subbrute_worker.c

@@ -78,7 +78,8 @@ bool subbrute_worker_init_default_attack(
     SubBruteWorker* instance,
     SubBruteAttacks attack_type,
     uint64_t step,
-    const SubBruteProtocol* protocol) {
+    const SubBruteProtocol* protocol,
+    uint8_t extra_repeats) {
     furi_assert(instance);
 
     if(instance->worker_running) {
@@ -93,7 +94,7 @@ bool subbrute_worker_init_default_attack(
     instance->step = step;
     instance->bits = protocol->bits;
     instance->te = protocol->te;
-    instance->repeat = protocol->repeat;
+    instance->repeat = protocol->repeat + extra_repeats;
     instance->load_index = 0;
     instance->file_key = NULL;
     instance->max_value = subbrute_protocol_calc_max_value(instance->attack, instance->bits);

+ 2 - 1
helpers/subbrute_worker.h

@@ -22,7 +22,8 @@ bool subbrute_worker_init_default_attack(
     SubBruteWorker* instance,
     SubBruteAttacks attack_type,
     uint64_t step,
-    const SubBruteProtocol* protocol);
+    const SubBruteProtocol* protocol,
+    uint8_t extra_repeats);
 bool subbrute_worker_init_file_attack(
     SubBruteWorker* instance,
     uint64_t step,

+ 1 - 1
scenes/subbrute_scene_load_file.c

@@ -37,7 +37,7 @@ void subbrute_scene_load_file_on_enter(void* context) {
         load_result =
             subbrute_device_load_from_file(instance->device, furi_string_get_cstr(load_path));
         if(load_result == SubBruteFileResultOk) {
-            load_result = subbrute_device_attack_set(instance->device, SubBruteAttackLoadFile);
+            load_result = subbrute_device_attack_set(instance->device, SubBruteAttackLoadFile, 0);
             if(load_result == SubBruteFileResultOk) {
                 if(!subbrute_worker_init_file_attack(
                        instance->worker,

+ 6 - 4
scenes/subbrute_scene_setup_attack.c

@@ -45,7 +45,8 @@ void subbrute_scene_setup_attack_on_enter(void* context) {
         instance->device->attack,
         instance->device->max_value,
         instance->device->key_index,
-        false);
+        false,
+        instance->device->extra_repeats);
 
     instance->current_view = SubBruteViewAttack;
     subbrute_attack_view_set_callback(view, subbrute_scene_setup_attack_callback, instance);
@@ -70,7 +71,6 @@ bool subbrute_scene_setup_attack_on_event(void* context, SceneManagerEvent event
 
     if(event.type == SceneManagerEventTypeCustom) {
         if(event.event == SubBruteCustomEventTypeTransmitStarted) {
-            subbrute_attack_view_set_worker_type(view, false);
             scene_manager_next_scene(instance->scene_manager, SubBruteSceneRunAttack);
         } else if(event.event == SubBruteCustomEventTypeSaveFile) {
             subbrute_attack_view_init_values(
@@ -78,7 +78,8 @@ bool subbrute_scene_setup_attack_on_event(void* context, SceneManagerEvent event
                 instance->device->attack,
                 instance->device->max_value,
                 instance->device->key_index,
-                false);
+                false,
+                instance->device->extra_repeats);
             scene_manager_next_scene(instance->scene_manager, SubBruteSceneSaveName);
         } else if(event.event == SubBruteCustomEventTypeBackPressed) {
             subbrute_attack_view_init_values(
@@ -86,7 +87,8 @@ bool subbrute_scene_setup_attack_on_event(void* context, SceneManagerEvent event
                 instance->device->attack,
                 instance->device->max_value,
                 instance->device->key_index,
-                false);
+                false,
+                instance->device->extra_repeats);
             scene_manager_next_scene(instance->scene_manager, SubBruteSceneStart);
         } else if(event.event == SubBruteCustomEventTypeError) {
             notification_message(instance->notifications, &sequence_error);

+ 5 - 2
scenes/subbrute_scene_start.c

@@ -50,19 +50,22 @@ bool subbrute_scene_start_on_event(void* context, SceneManagerEvent event) {
 #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) != SubBruteFileResultOk ||
+            if(subbrute_device_attack_set(instance->device, attack, extra_repeats) != SubBruteFileResultOk ||
                !subbrute_worker_init_default_attack(
                    instance->worker,
                    attack,
                    instance->device->key_index,
-                   instance->device->protocol_info)) {
+                   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) {
+            instance->device->extra_repeats = 0;
             scene_manager_next_scene(instance->scene_manager, SubBruteSceneLoadFile);
             consumed = true;
         }

+ 9 - 4
subbrute_device.c

@@ -133,16 +133,20 @@ bool subbrute_device_save_file(SubBruteDevice* instance, const char* dev_file_na
     return result;
 }
 
-SubBruteFileResult subbrute_device_attack_set(SubBruteDevice* instance, SubBruteAttacks type) {
+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", type);
+    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
@@ -175,7 +179,7 @@ SubBruteFileResult subbrute_device_attack_set(SubBruteDevice* instance, SubBrute
 #ifdef FURI_DEBUG
         bits = instance->protocol_info->bits;
         te = instance->protocol_info->te;
-        repeat = instance->protocol_info->repeat;
+        repeat = instance->protocol_info->repeat + instance->extra_repeats;
         preset = instance->protocol_info->preset;
         file = instance->protocol_info->file;
 #endif
@@ -189,7 +193,7 @@ SubBruteFileResult subbrute_device_attack_set(SubBruteDevice* instance, SubBrute
 #ifdef FURI_DEBUG
         bits = instance->file_protocol_info->bits;
         te = instance->file_protocol_info->te;
-        repeat = instance->file_protocol_info->repeat;
+        repeat = instance->file_protocol_info->repeat + instance->extra_repeats;
         preset = instance->file_protocol_info->preset;
         file = instance->file_protocol_info->file;
 #endif
@@ -390,6 +394,7 @@ void subbrute_device_attack_set_default_values(
     instance->attack = default_attack;
     instance->key_index = 0x00;
     instance->load_index = 0x00;
+    instance->extra_repeats = 0;
     memset(instance->current_key, 0, sizeof(instance->current_key));
 
     if(default_attack != SubBruteAttackLoadFile) {

+ 5 - 1
subbrute_device.h

@@ -48,6 +48,7 @@ typedef struct {
     // Attack state
     SubBruteAttacks attack;
     uint64_t max_value;
+    uint8_t extra_repeats;
 
     // Loaded info for attack type
     char current_key[SUBBRUTE_PAYLOAD_SIZE];
@@ -59,7 +60,10 @@ 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);
+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);

+ 18 - 2
subbrute_protocols.c

@@ -287,6 +287,10 @@ 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];
 }
@@ -338,7 +342,13 @@ void subbrute_protocol_default_payload(
     furi_string_free(buffer);
 
 #ifdef FURI_DEBUG
-    //FURI_LOG_D(TAG, "candidate: %s, step: %lld", furi_string_get_cstr(candidate), step);
+    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) {
@@ -372,7 +382,13 @@ void subbrute_protocol_file_payload(
     furi_string_replace_at(candidate, load_index * 3, 3, subbrute_payload_byte);
 
 #ifdef FURI_DEBUG
-    FURI_LOG_D(TAG, "candidate: %s, step: %lld", furi_string_get_cstr(candidate), step);
+    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);
 

+ 1 - 9
subbrute_protocols.h

@@ -4,15 +4,6 @@
 #include <furi_hal_subghz.h>
 #include <core/string.h>
 #include <toolbox/stream/stream.h>
-//typedef enum {
-//    FrequencyProtocolField,
-//    BitsProtocolField,
-//    HasTeProtocolField,
-//    RepeatProtocolField,
-//    PresetProtocolField,
-//    FileProtocolField,
-//    TotalProtocolFields
-//} ProtocolFields;
 
 typedef enum {
     CAMEFileProtocol,
@@ -61,6 +52,7 @@ 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(

+ 100 - 122
views/subbrute_attack_view.c

@@ -14,14 +14,19 @@ 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 index;
+    SubBruteAttacks attack_type;
     uint64_t max_value;
     uint64_t current_step;
+    uint8_t extra_repeats;
     bool is_attacking;
-    bool is_continuous_worker;
     IconAnimation* icon;
 } SubBruteAttackViewModel;
 
@@ -39,136 +44,87 @@ void subbrute_attack_view_set_callback(
 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
-    SubBruteAttackView* instance = context;
 
     if(event->key == InputKeyBack && event->type == InputTypeShort) {
-        instance->callback(SubBruteCustomEventTypeBackPressed, instance->context);
+        instance->is_attacking = false;
         with_view_model(
             instance->view,
             SubBruteAttackViewModel * model,
-            {
-                model->is_attacking = false;
-                model->is_continuous_worker = false;
-            },
+            { model->is_attacking = false; },
             true);
+
+        instance->callback(SubBruteCustomEventTypeBackPressed, instance->context);
         return true;
     }
 
-    bool is_attacking = false;
+    bool update = false;
 
-    with_view_model(
-        instance->view,
-        SubBruteAttackViewModel * model,
-        { is_attacking = model->is_attacking; },
-        false);
-
-    //    if(!is_attacking) {
-    //        instance->callback(SubBruteCustomEventTypeTransmitNotStarted, instance->context);
-    //    } else {
-    //        instance->callback(SubBruteCustomEventTypeTransmitStarted, instance->context);
-    //    }
-
-    if(!is_attacking) {
+    if(!instance->is_attacking) {
         if(event->type == InputTypeShort && event->key == InputKeyOk) {
 #ifdef FURI_DEBUG
             FURI_LOG_D(TAG, "InputKey: %d OK", event->key);
 #endif
-            with_view_model(
-                instance->view,
-                SubBruteAttackViewModel * model,
-                {
-                    model->is_attacking = true;
-                    model->is_continuous_worker = false;
-                    icon_animation_stop(model->icon);
-                    icon_animation_start(model->icon);
-                },
-                true);
+            instance->is_attacking = true;
             instance->callback(SubBruteCustomEventTypeTransmitStarted, instance->context);
-            /*if(event->type == InputTypeRepeat && event->key == InputKeyOk) {
-#ifdef FURI_DEBUG
-            FURI_LOG_D(TAG, "InputKey: %d OK. SubBruteCustomEventTypeTransmitContinuousStarted", event->key);
-#endif
-            with_view_model(
-                instance->view, (SubBruteAttackViewModel * model) {
-                    model->is_attacking = true;
-                    model->is_continuous_worker = true;
-                    icon_animation_stop(model->icon);
-                    icon_animation_start(model->icon);
-                    return true;
-                });
-            instance->callback(SubBruteCustomEventTypeTransmitContinuousStarted, instance->context);
-        } else if(event->type == InputTypeShort && event->key == InputKeyOk) {
-#ifdef FURI_DEBUG
-            FURI_LOG_D(TAG, "InputKey: %d OK", event->key);
-#endif
-            with_view_model(
-                instance->view, (SubBruteAttackViewModel * model) {
-                    model->is_attacking = true;
-                    model->is_continuous_worker = false;
-                    icon_animation_stop(model->icon);
-                    icon_animation_start(model->icon);
-                    return 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);
             }
-            //            with_view_model(
-            //                instance->view, (SubBruteAttackViewModel * model) {
-            //                    if(event->key == InputKeyLeft) {
-            //                        model->current_step =
-            //                            ((model->current_step - 1) + model->max_value) % model->max_value;
-            //                    } else if(event->key == InputKeyRight) {
-            //                        model->current_step = (model->current_step + 1) % model->max_value;
-            //                    }
-            //                    return true;
-            //                });
-            //            instance->callback(SubBruteCustomEventTypeChangeStep, instance->context);
+            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);
             }
-            /*with_view_model(
-                instance->view, (SubBruteAttackViewModel * model) {
-                    if(event->key == InputKeyLeft) {
-                        model->current_step =
-                            ((model->current_step - 100) + model->max_value) % model->max_value;
-                    } else if(event->key == InputKeyRight) {
-                        model->current_step = (model->current_step + 100) % model->max_value;
-                    }
-                    return true;
-                });
-            instance->callback(SubBruteCustomEventTypeChangeStep, instance->context);*/
+            update = true;
         }
     } else {
+        // ATTACK Mode!
         if((event->type == InputTypeShort || event->type == InputTypeRepeat) &&
            (event->key == InputKeyOk || event->key == InputKeyBack)) {
-            with_view_model(
-                instance->view,
-                SubBruteAttackViewModel * model,
-                {
-                    model->is_attacking = false;
-                    model->is_continuous_worker = false;
-                    icon_animation_stop(model->icon);
-                    icon_animation_start(model->icon);
-                },
-                true);
+            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;
 }
 
@@ -186,13 +142,18 @@ SubBruteAttackView* subbrute_attack_view_alloc() {
             model->icon = icon_animation_alloc(&A_Sub1ghz_14);
             view_tie_icon_animation(instance->view, model->icon);
         },
-        false);
+        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;
 }
 
@@ -231,6 +192,7 @@ void subbrute_attack_view_set_current_step(SubBruteAttackView* instance, uint64_
 #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,
@@ -238,15 +200,6 @@ void subbrute_attack_view_set_current_step(SubBruteAttackView* instance, uint64_
         true);
 }
 
-void subbrute_attack_view_set_worker_type(SubBruteAttackView* instance, bool is_continuous_worker) {
-    furi_assert(instance);
-    with_view_model(
-        instance->view,
-        SubBruteAttackViewModel * model,
-        { model->is_continuous_worker = is_continuous_worker; },
-        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(
@@ -254,23 +207,32 @@ void subbrute_attack_view_init_values(
     uint8_t index,
     uint64_t max_value,
     uint64_t current_step,
-    bool is_attacking) {
+    bool is_attacking,
+    uint8_t extra_repeats) {
 #ifdef FURI_DEBUG
-    FURI_LOG_D(
+    FURI_LOG_I(
         TAG,
-        "init, index: %d, max_value: %lld, current_step: %lld",
+        "INIT, attack_type: %d, max_value: %lld, current_step: %lld, extra_repeats: %d",
         index,
         max_value,
-        current_step);
+        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->index = index;
+            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 {
@@ -313,10 +275,12 @@ void elements_button_top_left(Canvas* canvas, const char* str) {
     const uint8_t x = 0;
     const uint8_t y = 0 + button_height;
 
-    canvas_draw_box(canvas, x, y - button_height, button_width, button_height);
-    canvas_draw_line(canvas, x + button_width + 0, y - button_height, x + button_width + 0, y - 1);
-    canvas_draw_line(canvas, x + button_width + 1, y - button_height, x + button_width + 1, y - 2);
-    canvas_draw_line(canvas, x + button_width + 2, y - button_height, x + button_width + 2, y - 3);
+    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);
@@ -345,10 +309,12 @@ void elements_button_top_right(Canvas* canvas, const char* str) {
     const uint8_t x = canvas_width(canvas);
     const uint8_t y = 0 + button_height;
 
-    canvas_draw_box(canvas, x - button_width, y - button_height, button_width, button_height);
-    canvas_draw_line(canvas, x - button_width - 1, y - button_height, x - button_width - 1, y - 1);
-    canvas_draw_line(canvas, x - button_width - 2, y - button_height, x - button_width - 2, y - 2);
-    canvas_draw_line(canvas, x - button_width - 3, y - button_height, x - button_width - 3, y - 3);
+    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);
@@ -360,36 +326,43 @@ void elements_button_top_right(Canvas* canvas, const char* str) {
 void subbrute_attack_view_draw(Canvas* canvas, void* context) {
     furi_assert(context);
     SubBruteAttackViewModel* model = (SubBruteAttackViewModel*)context;
-    char buffer[26];
+    char buffer[64];
 
     const char* attack_name = NULL;
-    attack_name = subbrute_protocol_name(model->index);
+    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);
     }
-    // Value
+
+    // Current Step / Max value
     canvas_set_font(canvas, FontBigNumbers);
     snprintf(buffer, sizeof(buffer), "%04d/%04d", (int)model->current_step, (int)model->max_value);
     canvas_draw_str_aligned(canvas, 64, 17, AlignCenter, AlignTop, buffer);
     canvas_set_font(canvas, FontSecondary);
 
+    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 {
-        if(model->is_continuous_worker) {
-            canvas_invert_color(canvas);
-        }
         // canvas_draw_icon_animation
         const uint8_t icon_h_offset = 0;
         const uint8_t icon_width_with_offset =
@@ -404,9 +377,14 @@ void subbrute_attack_view_draw(Canvas* canvas, void* context) {
         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");
-        if(model->is_continuous_worker) {
-            canvas_invert_color(canvas);
-        }
     }
 }

+ 2 - 2
views/subbrute_attack_view.h

@@ -16,10 +16,10 @@ 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_set_worker_type(SubBruteAttackView* instance, bool is_continuous_worker);
 void subbrute_attack_view_init_values(
     SubBruteAttackView* instance,
     uint8_t index,
     uint64_t max_value,
     uint64_t current_step,
-    bool is_attacking);
+    bool is_attacking,
+    uint8_t extra_repeats);

+ 164 - 130
views/subbrute_main_view.c

@@ -9,14 +9,22 @@
 #define STATUS_BAR_Y_SHIFT 14
 #define TAG "SubBruteMainView"
 
+#define ITEMS_ON_SCREEN 3
+
 struct SubBruteMainView {
     View* view;
     SubBruteMainViewCallback callback;
     void* context;
+    uint8_t index;
+    bool is_select_byte;
+    const char* key_field;
+    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;
     const char* key_field;
@@ -79,26 +87,27 @@ FuriString* center_displayed_key(const char* key_cstr, uint8_t index) {
 }
 
 void subbrute_main_view_draw(Canvas* canvas, SubBruteMainViewModel* model) {
-    SubBruteMainViewModel* m = model;
-
     // Title
     canvas_set_font(canvas, FontPrimary);
     canvas_draw_box(canvas, 0, 0, canvas_width(canvas), STATUS_BAR_Y_SHIFT);
     canvas_invert_color(canvas);
-    canvas_draw_str_aligned(canvas, 64, 3, AlignCenter, AlignTop, "Sub-GHz BruteForcer 3.1");
+    canvas_draw_str_aligned(canvas, 64, 3, AlignCenter, AlignTop, "Sub-GHz BruteForcer 3.2");
     canvas_invert_color(canvas);
 
-    if(m->is_select_byte) {
+    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_field: %s", m->key_field);
+        //FURI_LOG_D(TAG, "key_field: %s", model->key_field);
 #endif
         char msg_index[18];
-        snprintf(msg_index, sizeof(msg_index), "Field index : %d", m->index);
+        snprintf(msg_index, sizeof(msg_index), "Field index : %d", model->index);
         canvas_draw_str_aligned(canvas, 64, 26, AlignCenter, AlignTop, msg_index);
 
         FuriString* menu_items;
 
-        menu_items = center_displayed_key(m->key_field, m->index);
+        menu_items = center_displayed_key(model->key_field, model->index);
         canvas_set_font(canvas, FontSecondary);
         canvas_draw_str_aligned(
             canvas, 64, 40, AlignCenter, AlignTop, furi_string_get_cstr(menu_items));
@@ -113,17 +122,16 @@ void subbrute_main_view_draw(Canvas* canvas, SubBruteMainViewModel* model) {
         // Menu
         canvas_set_color(canvas, ColorBlack);
         canvas_set_font(canvas, FontSecondary);
-        uint8_t items_on_screen = 3;
         const uint8_t item_height = 16;
 
 #ifdef FURI_DEBUG
-        //FURI_LOG_D(TAG, "window_position: %d, index: %d", model->window_position, m->index);
+        //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(m->index == position) {
+            if(item_position < ITEMS_ON_SCREEN) {
+                if(model->index == position) {
                     canvas_draw_str_aligned(
                         canvas,
                         4,
@@ -131,6 +139,25 @@ void subbrute_main_view_draw(Canvas* canvas, SubBruteMainViewModel* model) {
                         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 {
@@ -147,10 +174,10 @@ void subbrute_main_view_draw(Canvas* canvas, SubBruteMainViewModel* model) {
 
         elements_scrollbar_pos(
             canvas,
-            canvas_width(canvas),
+            screen_width,
             STATUS_BAR_Y_SHIFT + 2,
-            canvas_height(canvas) - STATUS_BAR_Y_SHIFT,
-            m->index,
+            screen_height - STATUS_BAR_Y_SHIFT,
+            model->index,
             SubBruteAttackTotalCount);
     }
 }
@@ -158,124 +185,124 @@ void subbrute_main_view_draw(Canvas* canvas, SubBruteMainViewModel* model) {
 bool subbrute_main_view_input(InputEvent* event, void* context) {
     furi_assert(event);
     furi_assert(context);
-#ifdef FURI_DEBUG
-    FURI_LOG_D(TAG, "InputKey: %d", event->key);
-#endif
 
     if(event->key == InputKeyBack && event->type == InputTypeShort) {
+#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);
     uint8_t index = 0;
-    bool is_select_byte = false;
-    with_view_model(
-        instance->view,
-        SubBruteMainViewModel * model,
-        { is_select_byte = model->is_select_byte; },
-        false);
 
+    bool updated = false;
     bool consumed = false;
-    if(!is_select_byte) {
-        if((event->type == InputTypeShort) || (event->type == InputTypeRepeat)) {
-            bool ret = false;
-            with_view_model(
-                instance->view,
-                SubBruteMainViewModel * model,
-                {
-                    uint8_t items_on_screen = 3;
-                    if(event->key == InputKeyUp) {
-                        if(model->index == min_value) {
-                            model->index = correct_total;
-                        } else {
-                            model->index = CLAMP(model->index - 1, correct_total, min_value);
-                        }
-                        ret = true;
-                        consumed = true;
-                    } else if(event->key == InputKeyDown) {
-                        if(model->index == correct_total) {
-                            model->index = min_value;
-                        } else {
-                            model->index = CLAMP(model->index + 1, correct_total, min_value);
-                        }
-                        ret = true;
-                        consumed = true;
-                    }
-                    if(ret) {
-                        model->window_position = model->index;
-                        if(model->window_position > 0) {
-                            model->window_position -= 1;
-                        }
-
-                        if(SubBruteAttackTotalCount <= items_on_screen) {
-                            model->window_position = 0;
-                        } else {
-                            if(model->window_position >=
-                               (SubBruteAttackTotalCount - items_on_screen)) {
-                                model->window_position =
-                                    (SubBruteAttackTotalCount - items_on_screen);
-                            }
-                        }
-                    }
-                    index = model->index;
-                },
-                ret);
-        }
-
-#ifdef FURI_DEBUG
-        with_view_model(
-            instance->view, SubBruteMainViewModel * model, { index = model->index; }, false);
-        FURI_LOG_I(TAG, "Index: %d", index);
-#endif
+    bool is_short = (event->type == InputTypeShort) || (event->type == InputTypeRepeat);
 
-        if(event->key == InputKeyOk && event->type == InputTypeShort) {
+    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(index == SubBruteAttackLoadFile) {
                 instance->callback(SubBruteCustomEventTypeLoadFile, instance->context);
             } else {
                 instance->callback(SubBruteCustomEventTypeMenuSelected, instance->context);
             }
             consumed = true;
+            updated = true;
         }
-    } else {
-        if((event->type == InputTypeShort) || (event->type == InputTypeRepeat)) {
-            with_view_model(
-                instance->view,
-                SubBruteMainViewModel * model,
-                {
-                    if(event->key == InputKeyLeft) {
-                        if(model->index > 0) {
-                            model->index--;
-                        }
-                    } else if(event->key == InputKeyRight) {
-                        if(model->index < 7) {
-                            model->index++;
-                        }
-                    }
+        if(updated) {
+            instance->window_position = instance->index;
+            if(instance->window_position > 0) {
+                instance->window_position -= 1;
+            }
 
-                    index = model->index;
-                },
-                true);
+            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);
+                }
+            }
         }
-
-#ifdef FURI_DEBUG
-        with_view_model(
-            instance->view, SubBruteMainViewModel * model, { index = model->index; }, false);
-        FURI_LOG_I(TAG, "Index: %d", index);
-#endif
-
-        if(event->key == InputKeyOk && event->type == InputTypeShort) {
+    } else {
+        if(event->key == InputKeyLeft && is_short) {
+            if(instance->index > 0) {
+                instance->index--;
+            }
+            updated = true;
+        } else if(event->key == InputKeyRight && is_short) {
+            if(instance->index < 7) {
+                instance->index++;
+            }
+            updated = true;
+        } else if(event->key == InputKeyOk && is_short) {
             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_field = instance->key_field;
+                model->is_select_byte = instance->is_select_byte;
+                model->extra_repeats = instance->extra_repeats;
+            },
+            true);
+    }
+
     return consumed;
 }
 
 void subbrute_main_view_enter(void* context) {
     furi_assert(context);
+    SubBruteMainView* instance = context;
+
+    with_view_model(
+        instance->view,
+        SubBruteMainViewModel * model,
+        {
+            model->key_field = NULL;
+            model->is_select_byte = false;
+        },
+        true);
 
 #ifdef FURI_DEBUG
     FURI_LOG_D(TAG, "subbrute_main_view_enter");
@@ -308,9 +335,16 @@ SubBruteMainView* subbrute_main_view_alloc() {
             model->window_position = 0;
             model->key_field = NULL;
             model->is_select_byte = false;
+            model->extra_repeats = 0;
         },
         true);
 
+    instance->index = 0;
+    instance->window_position = 0;
+    instance->key_field = NULL;
+    instance->is_select_byte = false;
+    instance->extra_repeats = 0;
+
     return instance;
 }
 
@@ -336,44 +370,44 @@ void subbrute_main_view_set_index(
 #ifdef FURI_DEBUG
     FURI_LOG_I(TAG, "Set index: %d", idx);
 #endif
+    instance->is_select_byte = is_select_byte;
+    instance->key_field = key_field;
+    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->is_select_byte = is_select_byte;
-            model->key_field = key_field;
-            model->index = idx;
-            model->window_position = idx;
-
-            if(!is_select_byte) {
-                uint8_t items_on_screen = 3;
-
-                if(model->window_position > 0) {
-                    model->window_position -= 1;
-                }
-
-                if(SubBruteAttackTotalCount <= items_on_screen) {
-                    model->window_position = 0;
-                } else {
-                    if(model->window_position >= (SubBruteAttackTotalCount - items_on_screen)) {
-                        model->window_position = (SubBruteAttackTotalCount - items_on_screen);
-                    }
-                }
-            }
+            model->index = instance->index;
+            model->window_position = instance->window_position;
+            model->key_field = instance->key_field;
+            model->is_select_byte = instance->is_select_byte;
+            model->extra_repeats = instance->extra_repeats;
         },
         true);
 }
 
 SubBruteAttacks subbrute_main_view_get_index(SubBruteMainView* instance) {
     furi_assert(instance);
+    return instance->index;
+}
 
-    uint8_t idx = 0;
-    with_view_model(
-        instance->view, SubBruteMainViewModel * model, { idx = model->index; }, false);
-
-#ifdef FURI_DEBUG
-    FURI_LOG_D(TAG, "Get index: %d", idx);
-#endif
-
-    return idx;
+uint8_t subbrute_main_view_get_extra_repeats(SubBruteMainView* instance) {
+    furi_assert(instance);
+    return instance->extra_repeats;
 }

+ 3 - 1
views/subbrute_main_view.h

@@ -1,6 +1,7 @@
 #pragma once
 
 #include "../subbrute_custom_event.h"
+#include "../subbrute_protocols.h"
 #include <gui/view.h>
 #include <input/input.h>
 #include <gui/elements.h>
@@ -21,7 +22,8 @@ void subbrute_main_view_set_index(
     uint8_t idx,
     bool is_select_byte,
     const char* key_field);
-uint8_t subbrute_main_view_get_index(SubBruteMainView* instance);
+SubBruteAttacks subbrute_main_view_get_index(SubBruteMainView* instance);
+uint8_t subbrute_main_view_get_extra_repeats(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);