Przeglądaj źródła

Merge combo_cracker from https://github.com/CharlesTheGreat77/ComboCracker-FZ

WillyJL 7 miesięcy temu
rodzic
commit
14db79d52d

+ 7 - 1
combo_cracker/README.md

@@ -40,7 +40,8 @@ ABOUT -> RIGHT - Brief description
 
 
 # Main Menu 📺 
-![Main Menu](https://github.com/user-attachments/assets/8fcc9a16-8765-425c-a1a9-a3be7ef6c4d8)
+![Main-Menu](https://github.com/user-attachments/assets/5483bbae-05ba-465c-a3a4-bd6809954f8a)
+
 
 # Combination Output 🔒 
 ![Combo Output](https://github.com/user-attachments/assets/0af467b1-27f7-45b5-971a-efd6bf1d58be)
@@ -48,4 +49,9 @@ ABOUT -> RIGHT - Brief description
 
 # 🙏 Credits & Acknowledgement:
 Inspired by: [Samy Kamkar’s](https://github.com/samyk) lock cracking research
+
+
 Built for: [Flipper Zero](https://github.com/flipperdevices/flipperzero-firmware)
+
+# Roadmap
+Update the input to allow "press and hold" to increase/decrease the value(s).

+ 5 - 3
combo_cracker/application.fam

@@ -4,10 +4,12 @@ App(
     apptype=FlipperAppType.EXTERNAL,
     entry_point="combo_cracker_app",
     stack_size=4 * 1024,
-    requires=["gui", "input"],
+    requires=[
+        "gui",
+    ],
     order=10,
-    fap_icon="assets/app.png",
+    fap_icon="icons/app10x10.png",
     fap_category="Tools",
     fap_icon_assets="assets",
-    fap_description="Crack Combo Locks by feeling the gates and resistance, then calculating the value to pop.",
+    fap_description="Crack combo locks in 8 attempts or less",
 )

BIN
combo_cracker/assets/lock32x32.png


+ 344 - 151
combo_cracker/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;
 }

BIN
combo_cracker/combo_cracker.fap


+ 2 - 0
combo_cracker/docs/CHANGELOG.md

@@ -0,0 +1,2 @@
+## v0.1
+- Initial release by [CharlesTheGreat77](@CharlesTheGreat77)

+ 21 - 0
combo_cracker/docs/README.md

@@ -0,0 +1,21 @@
+# Combo Cracker
+**Combo Cracker** is an on-the-go combination lock cracking tool for the **Flipper Zero**, inspired by security researcher [Samy Kamkar](https://github.com/samyk)’s work on the mechanical vulnerabilities in *Master Lock* combination padlocks.
+
+Using a clever approach/exploit and feedback from the lock’s dial resistance, you can determine the combination in **just 8 attempts or less** — instead of the known issues which deduce such to 100 or so brute-force attempts.
+
+# How it works
+This Flipper Zero app allows you to input physical resistance value(s) and "lock positions" observed from turning the lock dial. The app uses that data to run Kamkar’s approach to output a short list of combinations. You can find information about how such works by watching Samy Kamkar's wonderful [video(s)](https://www.youtube.com/watch?v=qkolWO6pAL8)!
+
+# Usage
+UP/DOWN - Select the Lock/Resistance position(s)
+
+LEFT/RIGHT - Increment/Decrease the position
+
+ABOUT -> RIGHT - Brief description
+
+
+# Credits & Acknowledgement:
+Inspired by: [Samy Kamkar’s](https://github.com/samyk) lock cracking research
+
+
+Built for: [Flipper Zero](https://github.com/flipperdevices/flipperzero-firmware)

+ 0 - 0
combo_cracker/assets/app.png → combo_cracker/icons/app10x10.png


BIN
combo_cracker/screenshots/screenshot1.png


BIN
combo_cracker/screenshots/screenshot2.png