Jelajahi Sumber

Update combo_cracker.c

Charles The Great 8 bulan lalu
induk
melakukan
f2746169f1
1 mengubah file dengan 344 tambahan dan 151 penghapusan
  1. 344 151
      combo_cracker.c

+ 344 - 151
combo_cracker.c

@@ -1,41 +1,75 @@
 #include <furi.h>
+#include <furi_hal.h>
 #include <gui/gui.h>
+#include <gui/view.h>
+#include <gui/view_dispatcher.h>
+#include <gui/modules/submenu.h>
+#include <gui/modules/widget.h>
+#include <notification/notification.h>
+#include <notification/notification_messages.h>
 #include <input/input.h>
-#include <stdlib.h>
-#include <string.h>
-#include <stdio.h>
+#include "combo_cracker_icons.h"
 
-#define SCREEN_HEIGHT 64
-#define MAX_VALUES    10
+#define TAG "ComboLockCracker"
+
+#define BACKLIGHT_ON 1
+
+#define MAX_VALUES 10
+
+// thank you derek jamison! ;)
+typedef enum {
+    ComboViewSubmenu,
+    ComboViewCracker,
+    ComboViewResults,
+    ComboViewAbout,
+} ComboView;
+
+typedef enum {
+    ComboEventIdRedrawScreen = 0,
+    ComboEventIdCalculateCombo = 1,
+} ComboEventId;
 
 typedef enum {
-    SCREEN_COMBO_ENTRY,
-    SCREEN_RESULTS,
-    SCREEN_ABOUT
-} ScreenState;
+    ComboSubmenuIndexCracker,
+    ComboSubmenuIndexAbout,
+} ComboSubmenuIndex;
+
+typedef struct {
+    ViewDispatcher* view_dispatcher;
+    NotificationApp* notifications;
+    Submenu* submenu;
+    View* view_cracker;
+    Widget* widget_results;
+    Widget* widget_about;
+
+    FuriTimer* timer;
+} ComboLockCrackerApp;
 
 typedef struct {
     int first_lock;
     int second_lock;
     float resistance;
-    bool exit;
     int selected;
     char result[256];
-    ViewPort* view_port;
-    Gui* gui;
-    ScreenState screen_state;
-} ComboCrackerState;
-
-char* float_to_char(float num) {
+} ComboLockCrackerModel;
+
+/**
+ * @brief      helper to convert float to string
+ * @param      num   the float number to convert
+ * @return     string representation of the float
+ */
+static char* float_to_char(float num) {
     static char buffer[8];
     snprintf(buffer, sizeof(buffer), "%.1f", (double)num);
     return buffer;
 }
 
-// calculate the value for the combo lock
-// Samy is my hero ;) -> https://www.youtube.com/watch?v=qkolWO6pAL8
-void calculate_combo(ComboCrackerState* app) {
-    float sticky_number = app->resistance;
+/**
+ * @brief      calculate the combination based on inputs
+ * @param      model   the model containing input values
+ */
+static void calculate_combo(ComboLockCrackerModel* model) {
+    float sticky_number = model->resistance;
     int sticky_as_int = (int)sticky_number;
 
     int first_digit;
@@ -49,8 +83,8 @@ void calculate_combo(ComboCrackerState* app) {
     first_digit = first_digit % 40;
     int remainder = first_digit % 4;
 
-    int a = app->first_lock;
-    int b = app->second_lock;
+    int a = model->first_lock;
+    int b = model->second_lock;
 
     // third digit isn't too bad
     int third_position_values[MAX_VALUES];
@@ -78,7 +112,6 @@ void calculate_combo(ComboCrackerState* app) {
         second_position_values[second_count++] = row_2;
     }
 
-    // sort that biih
     for(int i = 0; i < second_count - 1; i++) {
         for(int j = i + 1; j < second_count; j++) {
             if(second_position_values[i] > second_position_values[j]) {
@@ -89,174 +122,334 @@ void calculate_combo(ComboCrackerState* app) {
         }
     }
 
-    // result to push to result_callback
-    snprintf(app->result, sizeof(app->result), "First Pin: %d\nSecond Pin(s): ", first_digit);
+    int pos = 0;
+    int written = snprintf(
+        model->result + pos,
+        sizeof(model->result) - pos,
+        "First Pin: %d\nSecond Pin(s): ",
+        first_digit);
+    if(written < 0 || written >= (int)(sizeof(model->result) - pos)) return;
+    pos += written;
+
     for(int i = 0; i < second_count; i++) {
-        char buf[6];
-        snprintf(buf, sizeof(buf), "%d", second_position_values[i]);
-        strcat(app->result, buf);
-        if(i < second_count - 1) strcat(app->result, ", ");
-        if(i == 3) strcat(app->result, "\n -> ");
+        written = snprintf(
+            model->result + pos, sizeof(model->result) - pos, "%d", second_position_values[i]);
+        if(written < 0 || written >= (int)(sizeof(model->result) - pos)) return;
+        pos += written;
+
+        if(i < second_count - 1) {
+            const char* sep = (i == 3) ? ",\n -> " : ", ";
+            written = snprintf(model->result + pos, sizeof(model->result) - pos, "%s", sep);
+            if(written < 0 || written >= (int)(sizeof(model->result) - pos)) return;
+            pos += written;
+        }
     }
 
-    strcat(app->result, "\nThird Pin(s): ");
+    written = snprintf(model->result + pos, sizeof(model->result) - pos, "\nThird Pin(s): ");
+    if(written < 0 || written >= (int)(sizeof(model->result) - pos)) return;
+    pos += written;
+
     for(int i = 0; i < third_count; i++) {
-        char buf[5];
-        snprintf(buf, sizeof(buf), "%d", third_position_values[i]);
-        strcat(app->result, buf);
-        if(i < third_count - 1) strcat(app->result, ", "); // may be more than one sometimes
-        // deduce to 8 attempts by popping -> (third_digit +/- 2)
+        written = snprintf(
+            model->result + pos, sizeof(model->result) - pos, "%d", third_position_values[i]);
+        if(written < 0 || written >= (int)(sizeof(model->result) - pos)) return;
+        pos += written;
+
+        if(i < third_count - 1) {
+            written = snprintf(model->result + pos, sizeof(model->result) - pos, ", ");
+            if(written < 0 || written >= (int)(sizeof(model->result) - pos)) return;
+            pos += written;
+        }
     }
 }
 
-// main screen to push the first, second, and resistance positions
-void draw_callback(Canvas* canvas, void* ctx) {
-    ComboCrackerState* app = ctx;
-
-    canvas_clear(canvas);
-    canvas_set_font(canvas, FontPrimary);
-
-    char buf[16];
-
-    canvas_draw_str(canvas, 2, 12, "First Lock:");
-    snprintf(buf, sizeof(buf), "%s%d", app->selected == 0 ? ">" : " ", app->first_lock);
-    canvas_draw_str(canvas, 100, 12, buf);
-
-    canvas_draw_str(canvas, 2, 24, "Second Lock:");
-    snprintf(buf, sizeof(buf), "%s%d", app->selected == 1 ? ">" : " ", app->second_lock);
-    canvas_draw_str(canvas, 100, 24, buf);
-
-    canvas_draw_str(canvas, 2, 36, "Resistance:");
-    snprintf(
-        buf, sizeof(buf), "%s%s", app->selected == 2 ? ">" : " ", float_to_char(app->resistance));
-    canvas_draw_str(canvas, 100, 36, buf);
-
-    snprintf(
-        buf, sizeof(buf), "%sAbout", app->selected == 3 ? ">" : " "); // ugly but we rollin wit it
-    canvas_draw_str(canvas, 2, 48, buf);
-
-    canvas_draw_str(canvas, 2, 62, "OK to calculate"); // is there an OK icon??
+/**
+ * @brief      callback for exiting the application.
+ * @details    this function is called when user press back button.
+ * @param      _context  the context - unused
+ * @return     next view id
+ */
+static uint32_t combo_navigation_exit_callback(void* _context) {
+    UNUSED(_context);
+    return VIEW_NONE;
 }
 
-// push the calc to screen
-void result_draw_callback(Canvas* canvas, void* ctx) {
-    ComboCrackerState* app = ctx;
-    canvas_clear(canvas);
-    canvas_set_font(canvas, FontSecondary);
-
-    int y = 12;
-    char result_copy[256];
-    strncpy(result_copy, app->result, sizeof(result_copy));
-    char* line = strtok(result_copy, "\n");
+/**
+ * @brief      callback for returning to submenu.
+ * @details    this function is called when user press back button.
+ * @param      _context  the context - unused
+ * @return     next view id
+ */
+static uint32_t combo_navigation_submenu_callback(void* _context) {
+    UNUSED(_context);
+    return ComboViewSubmenu;
+}
 
-    while(line && y < SCREEN_HEIGHT - 10) {
-        canvas_draw_str(canvas, 2, y, line);
-        y += 10;
-        line = strtok(NULL, "\n");
+/**
+ * @brief      handle submenu item selection.
+ * @details    this function is called when user selects an item from the submenu.
+ * @param      context  the context - ComboLockCrackerApp object.
+ * @param      index    the ComboSubmenuIndex item that was clicked.
+ */
+static void combo_submenu_callback(void* context, uint32_t index) {
+    ComboLockCrackerApp* app = (ComboLockCrackerApp*)context;
+    switch(index) {
+    case ComboSubmenuIndexCracker:
+        view_dispatcher_switch_to_view(app->view_dispatcher, ComboViewCracker);
+        break;
+    case ComboSubmenuIndexAbout:
+        view_dispatcher_switch_to_view(app->view_dispatcher, ComboViewAbout);
+        break;
+    default:
+        break;
     }
-
-    canvas_draw_str(canvas, 2, SCREEN_HEIGHT - 2, "Back to edit");
 }
 
-void about_draw_callback(Canvas* canvas, void* ctx) {
-    UNUSED(ctx);
+/**
+ * @brief      callback for drawing the cracker screen.
+ * @details    this function is called when the screen needs to be redrawn.
+ * @param      canvas  the canvas to draw on.
+ * @param      model   the model - ComboLockCrackerModel object.
+ */
+static void combo_view_cracker_draw_callback(Canvas* canvas, void* model) {
+    ComboLockCrackerModel* my_model = (ComboLockCrackerModel*)model;
+
     canvas_clear(canvas);
     canvas_set_font(canvas, FontSecondary);
 
-    int y = 12;
-    canvas_draw_str(canvas, 2, y, "Combo Lock Cracker");
-    y += 10;
-    canvas_draw_str(canvas, 2, y, "Based on Samy Kamkar's");
-    y += 10;
-    canvas_draw_str(canvas, 2, y, "Master Lock research.");
-    y += 10;
-    canvas_draw_str(canvas, 2, y, "Crack Combo Locks in 8 tries");
-    y += 10;
-    canvas_draw_str(canvas, 2, y, "https://samy.pl/master/");
-    y += 18;
-    canvas_draw_str(canvas, 2, SCREEN_HEIGHT - 2, "Back to main menu");
+    char buf[16];
+    int icon_width = 32;
+    int icon_x = 128 - icon_width - 2; // moved 3 pixels to the right
+    int icon_y = 2;
+    int text_x = 2;
+    int value_x = 75;
+    int indicator_offset = -5;
+
+    canvas_draw_str(canvas, text_x, 12, "First Lock:");
+    snprintf(buf, sizeof(buf), "%d", my_model->first_lock);
+    canvas_draw_str(
+        canvas,
+        value_x + (my_model->selected == 0 ? indicator_offset : 0),
+        12,
+        (my_model->selected == 0 ? ">" : ""));
+    canvas_draw_str(canvas, value_x, 12, buf);
+
+    canvas_draw_str(canvas, text_x, 24, "Second Lock:");
+    snprintf(buf, sizeof(buf), "%d", my_model->second_lock);
+    canvas_draw_str(
+        canvas,
+        value_x + (my_model->selected == 1 ? indicator_offset : 0),
+        24,
+        (my_model->selected == 1 ? ">" : ""));
+    canvas_draw_str(canvas, value_x, 24, buf);
+
+    canvas_draw_str(canvas, text_x, 36, "Resistance:");
+    snprintf(buf, sizeof(buf), "%s", float_to_char(my_model->resistance));
+    canvas_draw_str(
+        canvas,
+        value_x + (my_model->selected == 2 ? indicator_offset : 0),
+        36,
+        (my_model->selected == 2 ? ">" : ""));
+    canvas_draw_str(canvas, value_x, 36, buf);
+
+    canvas_draw_str(canvas, text_x, 62, "OK to calculate");
+    canvas_draw_icon(canvas, icon_x, icon_y, &I_lock32x32);
 }
 
-void input_callback(InputEvent* event, void* ctx);
-void result_input_callback(InputEvent* event, void* ctx) {
-    ComboCrackerState* app = ctx;
-    if(event->type == InputTypeShort && event->key == InputKeyBack) {
-        app->screen_state = SCREEN_COMBO_ENTRY;
-        view_port_draw_callback_set(app->view_port, draw_callback, app);
-        view_port_input_callback_set(app->view_port, input_callback, app);
-    }
-    view_port_update(app->view_port);
-}
+/**
+ * @brief      callback for cracker screen input.
+ * @details    this function is called when the user presses a button while on the cracker screen.
+ * @param      event    the event - InputEvent object.
+ * @param      context  the context - ComboLockCrackerApp object.
+ * @return     true if the event was handled, false otherwise.
+ */
+static bool combo_view_cracker_input_callback(InputEvent* event, void* context) {
+    ComboLockCrackerApp* app = (ComboLockCrackerApp*)context;
 
-void input_callback(InputEvent* event, void* ctx) {
-    ComboCrackerState* app = ctx;
     if(event->type == InputTypeShort) {
+        bool redraw = true;
         switch(event->key) {
         case InputKeyUp:
-            app->selected = (app->selected + 3) % 4;
+            with_view_model(
+                app->view_cracker,
+                ComboLockCrackerModel * model,
+                { model->selected = (model->selected > 0) ? model->selected - 1 : 2; },
+                redraw);
             break;
         case InputKeyDown:
-            app->selected = (app->selected + 1) % 4;
+            with_view_model(
+                app->view_cracker,
+                ComboLockCrackerModel * model,
+                { model->selected = (model->selected < 2) ? model->selected + 1 : 0; },
+                redraw);
             break;
         case InputKeyLeft:
-            if(app->selected == 0 && app->first_lock > 0) app->first_lock--;
-            if(app->selected == 1 && app->second_lock > 0) app->second_lock--;
-            if(app->selected == 2 && app->resistance > 0) app->resistance -= 0.5;
+            with_view_model(
+                app->view_cracker,
+                ComboLockCrackerModel * model,
+                {
+                    if(model->selected == 0 && model->first_lock > 0) model->first_lock--;
+                    if(model->selected == 1 && model->second_lock > 0) model->second_lock--;
+                    if(model->selected == 2 && model->resistance > 0) model->resistance -= 0.5f;
+                },
+                redraw);
             break;
-        case InputKeyRight: // can't figure out how to accomodate for long presses for fast incrementation??
-            if(app->selected == 0 && app->first_lock < 10) app->first_lock++;
-            if(app->selected == 1 && app->second_lock < 10) app->second_lock++;
-            if(app->selected == 2 && app->resistance < 39.5) app->resistance += 0.5;
-            if(app->selected == 3) { // about description
-                app->screen_state = SCREEN_ABOUT;
-                view_port_draw_callback_set(app->view_port, about_draw_callback, app);
-                view_port_input_callback_set(app->view_port, result_input_callback, app);
-            }
+        case InputKeyRight:
+            with_view_model(
+                app->view_cracker,
+                ComboLockCrackerModel * model,
+                {
+                    if(model->selected == 0 && model->first_lock < 39) model->first_lock++;
+                    if(model->selected == 1 && model->second_lock < 39) model->second_lock++;
+                    if(model->selected == 2 && model->resistance < 39.5f)
+                        model->resistance += 0.5f;
+                },
+                redraw);
             break;
         case InputKeyOk:
-            calculate_combo(app);
-            app->screen_state = SCREEN_RESULTS;
-            view_port_draw_callback_set(app->view_port, result_draw_callback, app);
-            view_port_input_callback_set(app->view_port, result_input_callback, app);
-            break;
-        case InputKeyBack:
-            app->exit = true;
-            break;
+            view_dispatcher_send_custom_event(app->view_dispatcher, ComboEventIdCalculateCombo);
+            return true;
         default:
             break;
         }
     }
-    view_port_update(app->view_port);
+
+    return false;
 }
 
-int32_t combo_cracker_app(void* p) {
-    UNUSED(p);
+/**
+ * @brief      callback for custom events.
+ * @details    this function is called when a custom event is sent to the view dispatcher.
+ * @param      event    the event id - ComboEventId value.
+ * @param      context  the context - ComboLockCrackerApp object.
+ */
+static bool combo_view_cracker_custom_event_callback(uint32_t event, void* context) {
+    ComboLockCrackerApp* app = (ComboLockCrackerApp*)context;
+
+    switch(event) {
+    case ComboEventIdRedrawScreen: {
+        bool redraw = true;
+        with_view_model(
+            app->view_cracker, ComboLockCrackerModel * _model, { UNUSED(_model); }, redraw);
+        return true;
+    }
+    case ComboEventIdCalculateCombo: {
+        bool redraw = false;
+        with_view_model(
+            app->view_cracker,
+            ComboLockCrackerModel * model,
+            {
+                calculate_combo(model);
+                widget_reset(app->widget_results);
+                widget_add_text_scroll_element(app->widget_results, 0, 0, 128, 64, model->result);
+            },
+            redraw);
+
+        view_dispatcher_switch_to_view(app->view_dispatcher, ComboViewResults);
+        return true;
+    }
+    default:
+        return false;
+    }
+}
 
-    ComboCrackerState* app = malloc(sizeof(ComboCrackerState));
-    app->first_lock = 0;
-    app->second_lock = 0;
-    app->resistance = 0.0f;
-    app->selected = 0;
-    app->exit = false;
-    app->screen_state = SCREEN_COMBO_ENTRY;
-    app->result[0] = '\0';
+static ComboLockCrackerApp* combo_app_alloc() {
+    ComboLockCrackerApp* app = (ComboLockCrackerApp*)malloc(sizeof(ComboLockCrackerApp));
+
+    Gui* gui = furi_record_open(RECORD_GUI);
+
+    app->view_dispatcher = view_dispatcher_alloc();
+    view_dispatcher_attach_to_gui(app->view_dispatcher, gui, ViewDispatcherTypeFullscreen);
+    view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
+
+    app->submenu = submenu_alloc();
+    submenu_add_item(
+        app->submenu, "Crack Lock", ComboSubmenuIndexCracker, combo_submenu_callback, app);
+    submenu_add_item(app->submenu, "About", ComboSubmenuIndexAbout, combo_submenu_callback, app);
+    view_set_previous_callback(submenu_get_view(app->submenu), combo_navigation_exit_callback);
+    view_dispatcher_add_view(
+        app->view_dispatcher, ComboViewSubmenu, submenu_get_view(app->submenu));
+    view_dispatcher_switch_to_view(app->view_dispatcher, ComboViewSubmenu);
+
+    app->view_cracker = view_alloc();
+    view_set_draw_callback(app->view_cracker, combo_view_cracker_draw_callback);
+    view_set_input_callback(app->view_cracker, combo_view_cracker_input_callback);
+    view_set_previous_callback(app->view_cracker, combo_navigation_submenu_callback);
+    view_set_context(app->view_cracker, app);
+    view_set_custom_callback(app->view_cracker, combo_view_cracker_custom_event_callback);
+    view_allocate_model(app->view_cracker, ViewModelTypeLockFree, sizeof(ComboLockCrackerModel));
+
+    ComboLockCrackerModel* model = view_get_model(app->view_cracker);
+    model->first_lock = 0;
+    model->second_lock = 0;
+    model->resistance = 0.0f;
+    model->selected = 0;
+    model->result[0] = '\0';
+
+    view_dispatcher_add_view(app->view_dispatcher, ComboViewCracker, app->view_cracker);
+
+    app->widget_results = widget_alloc();
+    view_set_previous_callback(
+        widget_get_view(app->widget_results), combo_navigation_submenu_callback);
+    view_dispatcher_add_view(
+        app->view_dispatcher, ComboViewResults, widget_get_view(app->widget_results));
+
+    app->widget_about = widget_alloc();
+    widget_add_text_scroll_element(
+        app->widget_about,
+        0,
+        0,
+        128,
+        64,
+        "Combo Lock Cracker\n"
+        "---\n"
+        "Based on Samy Kamkar's Master Lock research.\n"
+        "Crack Combo Locks in 8 tries\n"
+        "https://samy.pl/master/\n"
+        "README at https://github.com/CharlesTheGreat77/ComboCracker-FZ\n");
+    view_set_previous_callback(
+        widget_get_view(app->widget_about), combo_navigation_submenu_callback);
+    view_dispatcher_add_view(
+        app->view_dispatcher, ComboViewAbout, widget_get_view(app->widget_about));
+
+    app->notifications = furi_record_open(RECORD_NOTIFICATION);
+
+    notification_message(app->notifications, &sequence_display_backlight_enforce_on);
+
+    return app;
+}
 
-    app->view_port = view_port_alloc();
-    view_port_draw_callback_set(app->view_port, draw_callback, app);
-    view_port_input_callback_set(app->view_port, input_callback, app);
+/**
+ * @brief      free the combo application.
+ * @details    this function frees the application resources.
+ * @param      app  the application object.
+ */
+static void combo_app_free(ComboLockCrackerApp* app) {
+#ifdef BACKLIGHT_ON
+    notification_message(app->notifications, &sequence_display_backlight_enforce_auto);
+#endif
+    furi_record_close(RECORD_NOTIFICATION);
+
+    view_dispatcher_remove_view(app->view_dispatcher, ComboViewAbout);
+    widget_free(app->widget_about);
+    view_dispatcher_remove_view(app->view_dispatcher, ComboViewResults);
+    widget_free(app->widget_results);
+    view_dispatcher_remove_view(app->view_dispatcher, ComboViewCracker);
+    view_free(app->view_cracker);
+    view_dispatcher_remove_view(app->view_dispatcher, ComboViewSubmenu);
+    submenu_free(app->submenu);
+    view_dispatcher_free(app->view_dispatcher);
+    furi_record_close(RECORD_GUI);
 
-    app->gui = furi_record_open(RECORD_GUI);
-    gui_add_view_port(app->gui, app->view_port, GuiLayerFullscreen);
+    free(app);
+}
 
-    while(!app->exit) {
-        furi_delay_ms(50);
-    }
+int32_t combo_cracker_app(void* _p) {
+    UNUSED(_p);
 
-    view_port_enabled_set(app->view_port, false);
-    gui_remove_view_port(app->gui, app->view_port);
-    view_port_free(app->view_port);
-    furi_record_close(RECORD_GUI);
-    free(app);
+    ComboLockCrackerApp* app = combo_app_alloc();
+    view_dispatcher_run(app->view_dispatcher);
 
+    combo_app_free(app);
     return 0;
 }