From b0c2cba2a05000c5fddbb977281d82fcfa104c7a Mon Sep 17 00:00:00 2001 From: yocvito Date: Thu, 5 Jan 2023 14:20:02 +0100 Subject: [PATCH] Adds basic bad BLE app (cannot modify adv name yet, cannot efficiently send keys with harcoded timeouts between press/release) --- applications/main/bad_ble/application.fam | 18 + applications/main/bad_ble/bad_ble_app.c | 161 ++++ applications/main/bad_ble/bad_ble_app.h | 11 + applications/main/bad_ble/bad_ble_app_i.h | 50 ++ applications/main/bad_ble/bad_ble_script.c | 783 ++++++++++++++++++ applications/main/bad_ble/bad_ble_script.h | 49 ++ .../main/bad_ble/bad_ble_settings_filename.h | 3 + applications/main/bad_ble/badusb_10px.png | Bin 0 -> 576 bytes .../bad_ble/images/ActiveConnection_50x64.png | Bin 0 -> 3842 bytes .../main/bad_ble/images/Clock_18x18.png | Bin 0 -> 1083 bytes .../main/bad_ble/images/Error_18x18.png | Bin 0 -> 1083 bytes .../main/bad_ble/images/EviSmile1_18x21.png | Bin 0 -> 3645 bytes .../main/bad_ble/images/EviSmile2_18x21.png | Bin 0 -> 3649 bytes .../main/bad_ble/images/EviWaiting1_18x21.png | Bin 0 -> 13020 bytes .../main/bad_ble/images/EviWaiting2_18x21.png | Bin 0 -> 12913 bytes .../main/bad_ble/images/Percent_10x14.png | Bin 0 -> 3624 bytes .../main/bad_ble/images/SDQuestion_35x43.png | Bin 0 -> 1950 bytes .../main/bad_ble/images/Smile_18x18.png | Bin 0 -> 1080 bytes .../main/bad_ble/images/UsbTree_48x22.png | Bin 0 -> 3653 bytes .../main/bad_ble/images/badusb_10px.png | Bin 0 -> 576 bytes .../main/bad_ble/images/keyboard_10px.png | Bin 0 -> 147 bytes .../main/bad_ble/scenes/bad_ble_scene.c | 30 + .../main/bad_ble/scenes/bad_ble_scene.h | 29 + .../bad_ble/scenes/bad_ble_scene_config.c | 52 ++ .../bad_ble/scenes/bad_ble_scene_config.h | 5 + .../scenes/bad_ble_scene_config_layout.c | 47 ++ .../main/bad_ble/scenes/bad_ble_scene_error.c | 83 ++ .../scenes/bad_ble_scene_file_select.c | 51 ++ .../main/bad_ble/scenes/bad_ble_scene_work.c | 54 ++ applications/main/bad_ble/switch_ble.py | 24 + .../main/bad_ble/views/bad_ble_view.c | 237 ++++++ .../main/bad_ble/views/bad_ble_view.h | 21 + 32 files changed, 1708 insertions(+) create mode 100644 applications/main/bad_ble/application.fam create mode 100644 applications/main/bad_ble/bad_ble_app.c create mode 100644 applications/main/bad_ble/bad_ble_app.h create mode 100644 applications/main/bad_ble/bad_ble_app_i.h create mode 100644 applications/main/bad_ble/bad_ble_script.c create mode 100644 applications/main/bad_ble/bad_ble_script.h create mode 100644 applications/main/bad_ble/bad_ble_settings_filename.h create mode 100644 applications/main/bad_ble/badusb_10px.png create mode 100644 applications/main/bad_ble/images/ActiveConnection_50x64.png create mode 100644 applications/main/bad_ble/images/Clock_18x18.png create mode 100644 applications/main/bad_ble/images/Error_18x18.png create mode 100644 applications/main/bad_ble/images/EviSmile1_18x21.png create mode 100644 applications/main/bad_ble/images/EviSmile2_18x21.png create mode 100644 applications/main/bad_ble/images/EviWaiting1_18x21.png create mode 100644 applications/main/bad_ble/images/EviWaiting2_18x21.png create mode 100644 applications/main/bad_ble/images/Percent_10x14.png create mode 100644 applications/main/bad_ble/images/SDQuestion_35x43.png create mode 100644 applications/main/bad_ble/images/Smile_18x18.png create mode 100644 applications/main/bad_ble/images/UsbTree_48x22.png create mode 100644 applications/main/bad_ble/images/badusb_10px.png create mode 100644 applications/main/bad_ble/images/keyboard_10px.png create mode 100644 applications/main/bad_ble/scenes/bad_ble_scene.c create mode 100644 applications/main/bad_ble/scenes/bad_ble_scene.h create mode 100644 applications/main/bad_ble/scenes/bad_ble_scene_config.c create mode 100644 applications/main/bad_ble/scenes/bad_ble_scene_config.h create mode 100644 applications/main/bad_ble/scenes/bad_ble_scene_config_layout.c create mode 100644 applications/main/bad_ble/scenes/bad_ble_scene_error.c create mode 100644 applications/main/bad_ble/scenes/bad_ble_scene_file_select.c create mode 100644 applications/main/bad_ble/scenes/bad_ble_scene_work.c create mode 100644 applications/main/bad_ble/switch_ble.py create mode 100644 applications/main/bad_ble/views/bad_ble_view.c create mode 100644 applications/main/bad_ble/views/bad_ble_view.h diff --git a/applications/main/bad_ble/application.fam b/applications/main/bad_ble/application.fam new file mode 100644 index 000000000..ac4106eac --- /dev/null +++ b/applications/main/bad_ble/application.fam @@ -0,0 +1,18 @@ +App( + appid="bad_ble", + name="Bad BLE", + apptype=FlipperAppType.EXTERNAL, + entry_point="bad_ble_app", + cdefines=["APP_BAD_BLE"], + requires=[ + "gui", + "dialogs", + ], + stack_size=2 * 1024, + # icon="A_BadUsb_14", + order=70, + fap_category="Main", + fap_icon="badusb_10px.png", + fap_icon_assets="images", + fap_libs=["assets"], +) diff --git a/applications/main/bad_ble/bad_ble_app.c b/applications/main/bad_ble/bad_ble_app.c new file mode 100644 index 000000000..47ea7b4d5 --- /dev/null +++ b/applications/main/bad_ble/bad_ble_app.c @@ -0,0 +1,161 @@ +#include "bad_ble_app_i.h" +#include "bad_ble_settings_filename.h" +#include +#include +#include +#include + +#include +#include + +#define BAD_BLE_SETTINGS_PATH BAD_BLE_APP_BASE_FOLDER "/" BAD_BLE_SETTINGS_FILE_NAME + +static bool bad_ble_app_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + BadBleApp* app = context; + return scene_manager_handle_custom_event(app->scene_manager, event); +} + +static bool bad_ble_app_back_event_callback(void* context) { + furi_assert(context); + BadBleApp* app = context; + return scene_manager_handle_back_event(app->scene_manager); +} + +static void bad_ble_app_tick_event_callback(void* context) { + furi_assert(context); + BadBleApp* app = context; + scene_manager_handle_tick_event(app->scene_manager); +} + +static void bad_ble_load_settings(BadBleApp* app) { + File* settings_file = storage_file_alloc(furi_record_open(RECORD_STORAGE)); + if(storage_file_open(settings_file, BAD_BLE_SETTINGS_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) { + char chr; + while((storage_file_read(settings_file, &chr, 1) == 1) && + !storage_file_eof(settings_file) && !isspace(chr)) { + furi_string_push_back(app->keyboard_layout, chr); + } + } + storage_file_close(settings_file); + storage_file_free(settings_file); +} + +static void bad_ble_save_settings(BadBleApp* app) { + File* settings_file = storage_file_alloc(furi_record_open(RECORD_STORAGE)); + if(storage_file_open(settings_file, BAD_BLE_SETTINGS_PATH, FSAM_WRITE, FSOM_OPEN_ALWAYS)) { + storage_file_write( + settings_file, + furi_string_get_cstr(app->keyboard_layout), + furi_string_size(app->keyboard_layout)); + storage_file_write(settings_file, "\n", 1); + } + storage_file_close(settings_file); + storage_file_free(settings_file); +} + +BadBleApp* bad_ble_app_alloc(char* arg) { + BadBleApp* app = malloc(sizeof(BadBleApp)); + + app->bad_ble_script = NULL; + + app->file_path = furi_string_alloc(); + app->keyboard_layout = furi_string_alloc(); + if(arg && strlen(arg)) { + furi_string_set(app->file_path, arg); + } + + bad_ble_load_settings(app); + + app->gui = furi_record_open(RECORD_GUI); + app->notifications = furi_record_open(RECORD_NOTIFICATION); + app->dialogs = furi_record_open(RECORD_DIALOGS); + + app->view_dispatcher = view_dispatcher_alloc(); + view_dispatcher_enable_queue(app->view_dispatcher); + + app->scene_manager = scene_manager_alloc(&bad_ble_scene_handlers, app); + + view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + view_dispatcher_set_tick_event_callback( + app->view_dispatcher, bad_ble_app_tick_event_callback, 500); + view_dispatcher_set_custom_event_callback( + app->view_dispatcher, bad_ble_app_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + app->view_dispatcher, bad_ble_app_back_event_callback); + + // Custom Widget + app->widget = widget_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, BadBleAppViewError, widget_get_view(app->widget)); + + app->submenu = submenu_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, BadBleAppViewConfig, submenu_get_view(app->submenu)); + + app->bad_ble_view = bad_ble_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, BadBleAppViewWork, bad_ble_get_view(app->bad_ble_view)); + + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + + Bt* bt = furi_record_open(RECORD_BT); + app->bt = bt; + if(!furi_string_empty(app->file_path)) { + app->bad_ble_script = bad_ble_script_open(app->file_path, bt); + bad_ble_script_set_keyboard_layout(app->bad_ble_script, app->keyboard_layout); + scene_manager_next_scene(app->scene_manager, BadBleSceneWork); + } else { + furi_string_set(app->file_path, BAD_BLE_APP_BASE_FOLDER); + scene_manager_next_scene(app->scene_manager, BadBleSceneFileSelect); + } + + return app; +} + +void bad_ble_app_free(BadBleApp* app) { + furi_assert(app); + + if(app->bad_ble_script) { + bad_ble_script_close(app->bad_ble_script); + app->bad_ble_script = NULL; + } + + // Views + view_dispatcher_remove_view(app->view_dispatcher, BadBleAppViewWork); + bad_ble_free(app->bad_ble_view); + + // Custom Widget + view_dispatcher_remove_view(app->view_dispatcher, BadBleAppViewError); + widget_free(app->widget); + + // Submenu + view_dispatcher_remove_view(app->view_dispatcher, BadBleAppViewConfig); + submenu_free(app->submenu); + + // View dispatcher + view_dispatcher_free(app->view_dispatcher); + scene_manager_free(app->scene_manager); + + // Close records + furi_record_close(RECORD_GUI); + furi_record_close(RECORD_NOTIFICATION); + furi_record_close(RECORD_DIALOGS); + furi_record_close(RECORD_BT); + + bad_ble_save_settings(app); + + furi_string_free(app->file_path); + furi_string_free(app->keyboard_layout); + + free(app); +} + +int32_t bad_ble_app(void* p) { + BadBleApp* bad_ble_app = bad_ble_app_alloc((char*)p); + + view_dispatcher_run(bad_ble_app->view_dispatcher); + + bad_ble_app_free(bad_ble_app); + return 0; +} diff --git a/applications/main/bad_ble/bad_ble_app.h b/applications/main/bad_ble/bad_ble_app.h new file mode 100644 index 000000000..11954836e --- /dev/null +++ b/applications/main/bad_ble/bad_ble_app.h @@ -0,0 +1,11 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct BadBleApp BadBleApp; + +#ifdef __cplusplus +} +#endif diff --git a/applications/main/bad_ble/bad_ble_app_i.h b/applications/main/bad_ble/bad_ble_app_i.h new file mode 100644 index 000000000..1fd172988 --- /dev/null +++ b/applications/main/bad_ble/bad_ble_app_i.h @@ -0,0 +1,50 @@ +#pragma once + +#include "bad_ble_app.h" +#include "scenes/bad_ble_scene.h" +#include "bad_ble_script.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "views/bad_ble_view.h" + +#define BAD_BLE_APP_BASE_FOLDER ANY_PATH("BadUsb") +#define BAD_BLE_APP_PATH_LAYOUT_FOLDER BAD_BLE_APP_BASE_FOLDER "/layouts" +#define BAD_BLE_APP_SCRIPT_EXTENSION ".txt" +#define BAD_BLE_APP_LAYOUT_EXTENSION ".kl" + +typedef enum { + BadBleAppErrorNoFiles, + BadBleAppErrorCloseRpc, +} BadBleAppError; + +struct BadBleApp { + Gui* gui; + ViewDispatcher* view_dispatcher; + SceneManager* scene_manager; + NotificationApp* notifications; + DialogsApp* dialogs; + Widget* widget; + Submenu* submenu; + + BadBleAppError error; + FuriString* file_path; + FuriString* keyboard_layout; + BadBle* bad_ble_view; + BadBleScript* bad_ble_script; + + Bt* bt; +}; + +typedef enum { + BadBleAppViewError, + BadBleAppViewWork, + BadBleAppViewConfig, +} BadBleAppView; \ No newline at end of file diff --git a/applications/main/bad_ble/bad_ble_script.c b/applications/main/bad_ble/bad_ble_script.c new file mode 100644 index 000000000..6489b54f7 --- /dev/null +++ b/applications/main/bad_ble/bad_ble_script.c @@ -0,0 +1,783 @@ +#include +#include +#include +#include +#include +#include +#include +#include "bad_ble_script.h" +#include + +#include + +#define HID_BT_KEYS_STORAGE_PATH EXT_PATH("apps/Tools/.bt_hid.keys") + +#define TAG "BadBle" +#define WORKER_TAG TAG "Worker" +#define FILE_BUFFER_LEN 16 + +#define SCRIPT_STATE_ERROR (-1) +#define SCRIPT_STATE_END (-2) +#define SCRIPT_STATE_NEXT_LINE (-3) + +#define BADBLE_ASCII_TO_KEY(script, x) \ + (((uint8_t)x < 128) ? (script->layout[(uint8_t)x]) : HID_KEYBOARD_NONE) + +typedef enum { + WorkerEvtToggle = (1 << 0), + WorkerEvtEnd = (1 << 1), + WorkerEvtConnect = (1 << 2), + WorkerEvtDisconnect = (1 << 3), +} WorkerEvtFlags; + + +typedef enum { + LevelRssi122_100, + LevelRssi99_80, + LevelRssi79_60, + LevelRssi59_40, + LevelRssi39_0, + LevelRssiNum, +} LevelRssiDelays; + +const uint8_t bt_hid_delays[LevelRssiNum] = { + 75, // LevelRssi122_100 + 50, // LevelRssi99_80 + 35, // LevelRssi79_60 + 25, // LevelRssi59_40 + 10, // LevelRssi39_0 +}; + +struct BadBleScript { + BadBleState st; + FuriString* file_path; + uint32_t defdelay; + uint16_t layout[128]; + FuriThread* thread; + uint8_t file_buf[FILE_BUFFER_LEN + 1]; + uint8_t buf_start; + uint8_t buf_len; + bool file_end; + FuriString* line; + + FuriString* line_prev; + uint32_t repeat_cnt; + + File* debug_file; + Bt* bt; +}; + +typedef struct { + char* name; + uint16_t keycode; +} DuckyKey; + +static const DuckyKey ducky_keys[] = { + {"CTRL-ALT", KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_ALT}, + {"CTRL-SHIFT", KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_SHIFT}, + {"ALT-SHIFT", KEY_MOD_LEFT_ALT | KEY_MOD_LEFT_SHIFT}, + {"ALT-GUI", KEY_MOD_LEFT_ALT | KEY_MOD_LEFT_GUI}, + {"GUI-SHIFT", KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT}, + + {"CTRL", KEY_MOD_LEFT_CTRL}, + {"CONTROL", KEY_MOD_LEFT_CTRL}, + {"SHIFT", KEY_MOD_LEFT_SHIFT}, + {"ALT", KEY_MOD_LEFT_ALT}, + {"GUI", KEY_MOD_LEFT_GUI}, + {"WINDOWS", KEY_MOD_LEFT_GUI}, + + {"DOWNARROW", HID_KEYBOARD_DOWN_ARROW}, + {"DOWN", HID_KEYBOARD_DOWN_ARROW}, + {"LEFTARROW", HID_KEYBOARD_LEFT_ARROW}, + {"LEFT", HID_KEYBOARD_LEFT_ARROW}, + {"RIGHTARROW", HID_KEYBOARD_RIGHT_ARROW}, + {"RIGHT", HID_KEYBOARD_RIGHT_ARROW}, + {"UPARROW", HID_KEYBOARD_UP_ARROW}, + {"UP", HID_KEYBOARD_UP_ARROW}, + + {"ENTER", HID_KEYBOARD_RETURN}, + {"BREAK", HID_KEYBOARD_PAUSE}, + {"PAUSE", HID_KEYBOARD_PAUSE}, + {"CAPSLOCK", HID_KEYBOARD_CAPS_LOCK}, + {"DELETE", HID_KEYBOARD_DELETE}, + {"BACKSPACE", HID_KEYPAD_BACKSPACE}, + {"END", HID_KEYBOARD_END}, + {"ESC", HID_KEYBOARD_ESCAPE}, + {"ESCAPE", HID_KEYBOARD_ESCAPE}, + {"HOME", HID_KEYBOARD_HOME}, + {"INSERT", HID_KEYBOARD_INSERT}, + {"NUMLOCK", HID_KEYPAD_NUMLOCK}, + {"PAGEUP", HID_KEYBOARD_PAGE_UP}, + {"PAGEDOWN", HID_KEYBOARD_PAGE_DOWN}, + {"PRINTSCREEN", HID_KEYBOARD_PRINT_SCREEN}, + {"SCROLLLOCK", HID_KEYBOARD_SCROLL_LOCK}, + {"SPACE", HID_KEYBOARD_SPACEBAR}, + {"TAB", HID_KEYBOARD_TAB}, + {"MENU", HID_KEYBOARD_APPLICATION}, + {"APP", HID_KEYBOARD_APPLICATION}, + + {"F1", HID_KEYBOARD_F1}, + {"F2", HID_KEYBOARD_F2}, + {"F3", HID_KEYBOARD_F3}, + {"F4", HID_KEYBOARD_F4}, + {"F5", HID_KEYBOARD_F5}, + {"F6", HID_KEYBOARD_F6}, + {"F7", HID_KEYBOARD_F7}, + {"F8", HID_KEYBOARD_F8}, + {"F9", HID_KEYBOARD_F9}, + {"F10", HID_KEYBOARD_F10}, + {"F11", HID_KEYBOARD_F11}, + {"F12", HID_KEYBOARD_F12}, +}; + +static const char ducky_cmd_comment[] = {"REM"}; +static const char ducky_cmd_id[] = {"ID"}; +static const char ducky_cmd_delay[] = {"DELAY "}; +static const char ducky_cmd_string[] = {"STRING "}; +static const char ducky_cmd_defdelay_1[] = {"DEFAULT_DELAY "}; +static const char ducky_cmd_defdelay_2[] = {"DEFAULTDELAY "}; +static const char ducky_cmd_repeat[] = {"REPEAT "}; +static const char ducky_cmd_sysrq[] = {"SYSRQ "}; + +static const char ducky_cmd_altchar[] = {"ALTCHAR "}; +static const char ducky_cmd_altstr_1[] = {"ALTSTRING "}; +static const char ducky_cmd_altstr_2[] = {"ALTCODE "}; + +static const char ducky_cmd_lang[] = {"DUCKY_LANG"}; + +static const uint8_t numpad_keys[10] = { + HID_KEYPAD_0, + HID_KEYPAD_1, + HID_KEYPAD_2, + HID_KEYPAD_3, + HID_KEYPAD_4, + HID_KEYPAD_5, + HID_KEYPAD_6, + HID_KEYPAD_7, + HID_KEYPAD_8, + HID_KEYPAD_9, +}; + +/** + * @brief Wait until there are enough free slots in the keyboard buffer + * + * @param n_free_chars Number of free slots to wait for (and consider the buffer not full) +*/ +static void bt_hid_hold_while_keyboard_buffer_full(uint8_t n_free_chars, int32_t timeout) { + uint32_t start = furi_get_tick(); + uint32_t timeout_ms = timeout <= -1 ? 0 : timeout; + while(furi_hal_bt_hid_kb_free_slots(n_free_chars) == false) { + furi_delay_ms(100); + + if(timeout != -1 && (furi_get_tick() - start) > timeout_ms) { + break; + } + } +} + +static bool ducky_get_number(const char* param, uint32_t* val) { + uint32_t value = 0; + if(sscanf(param, "%lu", &value) == 1) { + *val = value; + return true; + } + return false; +} + +static uint32_t ducky_get_command_len(const char* line) { + uint32_t len = strlen(line); + for(uint32_t i = 0; i < len; i++) { + if(line[i] == ' ') return i; + } + return 0; +} + +static bool ducky_is_line_end(const char chr) { + return ((chr == ' ') || (chr == '\0') || (chr == '\r') || (chr == '\n')); +} + +static void ducky_numlock_on() { + if((furi_hal_hid_get_led_state() & HID_KB_LED_NUM) == 0) { + bt_hid_hold_while_keyboard_buffer_full(1, -1); + furi_hal_bt_hid_kb_press(HID_KEYBOARD_LOCK_NUM_LOCK); + FURI_LOG_I(WORKER_TAG, "BT RSSI: %f\r", (double)furi_hal_bt_get_rssi()); + furi_delay_ms(25); + furi_hal_bt_hid_kb_release(HID_KEYBOARD_LOCK_NUM_LOCK); + } +} + +static bool ducky_numpad_press(const char num) { + if((num < '0') || (num > '9')) return false; + + uint16_t key = numpad_keys[num - '0']; + bt_hid_hold_while_keyboard_buffer_full(1, -1); + FURI_LOG_I(WORKER_TAG, "Pressing %c\r\n", num); + + furi_hal_bt_hid_kb_press(key); + FURI_LOG_I(WORKER_TAG, "BT RSSI: %f\r", (double)furi_hal_bt_get_rssi()); + furi_delay_ms(25); + furi_hal_bt_hid_kb_release(key); + + return true; +} + +static bool ducky_altchar(const char* charcode) { + uint8_t i = 0; + bool state = false; + + FURI_LOG_I(WORKER_TAG, "char %s", charcode); + + bt_hid_hold_while_keyboard_buffer_full(1, -1); + furi_hal_bt_hid_kb_press(KEY_MOD_LEFT_ALT); + + while(!ducky_is_line_end(charcode[i])) { + state = ducky_numpad_press(charcode[i]); + if(state == false) break; + i++; + } + + furi_hal_bt_hid_kb_release(KEY_MOD_LEFT_ALT); + return state; +} + +static bool ducky_altstring(const char* param) { + uint32_t i = 0; + bool state = false; + + while(param[i] != '\0') { + if((param[i] < ' ') || (param[i] > '~')) { + i++; + continue; // Skip non-printable chars + } + + char temp_str[4]; + snprintf(temp_str, 4, "%u", param[i]); + + state = ducky_altchar(temp_str); + if(state == false) break; + i++; + } + return state; +} + +static bool ducky_string(BadBleScript* bad_ble, const char* param) { + uint32_t i = 0; + while(param[i] != '\0') { + uint16_t keycode = BADBLE_ASCII_TO_KEY(bad_ble, param[i]); + if(keycode != HID_KEYBOARD_NONE) { + bt_hid_hold_while_keyboard_buffer_full(1, -1); + furi_hal_bt_hid_kb_press(keycode); + FURI_LOG_I(WORKER_TAG, "BT RSSI: %f\r", (double)furi_hal_bt_get_rssi()); + furi_delay_ms(25); + furi_hal_bt_hid_kb_release(keycode); + } + i++; + } + return true; +} + +static uint16_t ducky_get_keycode(BadBleScript* bad_ble, const char* param, bool accept_chars) { + for(size_t i = 0; i < (sizeof(ducky_keys) / sizeof(ducky_keys[0])); i++) { + size_t key_cmd_len = strlen(ducky_keys[i].name); + if((strncmp(param, ducky_keys[i].name, key_cmd_len) == 0) && + (ducky_is_line_end(param[key_cmd_len]))) { + return ducky_keys[i].keycode; + } + } + if((accept_chars) && (strlen(param) > 0)) { + return (BADBLE_ASCII_TO_KEY(bad_ble, param[0]) & 0xFF); + } + return 0; +} + +static int32_t + ducky_parse_line(BadBleScript* bad_ble, FuriString* line, char* error, size_t error_len) { + uint32_t line_len = furi_string_size(line); + const char* line_tmp = furi_string_get_cstr(line); + bool state = false; + + if(line_len == 0) { + return SCRIPT_STATE_NEXT_LINE; // Skip empty lines + } + + FURI_LOG_D(WORKER_TAG, "line:%s", line_tmp); + + // General commands + if(strncmp(line_tmp, ducky_cmd_comment, strlen(ducky_cmd_comment)) == 0) { + // REM - comment line + return (0); + } else if(strncmp(line_tmp, ducky_cmd_id, strlen(ducky_cmd_id)) == 0) { + // ID - executed in ducky_script_preload + return (0); + } else if(strncmp(line_tmp, ducky_cmd_lang, strlen(ducky_cmd_lang)) == 0) { + // DUCKY_LANG - ignore command to retain compatibility with existing scripts + return (0); + } else if(strncmp(line_tmp, ducky_cmd_delay, strlen(ducky_cmd_delay)) == 0) { + // DELAY + line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; + uint32_t delay_val = 0; + state = ducky_get_number(line_tmp, &delay_val); + if((state) && (delay_val > 0)) { + return (int32_t)delay_val; + } + if(error != NULL) { + snprintf(error, error_len, "Invalid number %s", line_tmp); + } + return SCRIPT_STATE_ERROR; + } else if( + (strncmp(line_tmp, ducky_cmd_defdelay_1, strlen(ducky_cmd_defdelay_1)) == 0) || + (strncmp(line_tmp, ducky_cmd_defdelay_2, strlen(ducky_cmd_defdelay_2)) == 0)) { + // DEFAULT_DELAY + line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; + state = ducky_get_number(line_tmp, &bad_ble->defdelay); + if(!state && error != NULL) { + snprintf(error, error_len, "Invalid number %s", line_tmp); + } + return (state) ? (0) : SCRIPT_STATE_ERROR; + } else if(strncmp(line_tmp, ducky_cmd_string, strlen(ducky_cmd_string)) == 0) { + // STRING + line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; + state = ducky_string(bad_ble, line_tmp); + if(!state && error != NULL) { + snprintf(error, error_len, "Invalid string %s", line_tmp); + } + return (state) ? (0) : SCRIPT_STATE_ERROR; + } else if(strncmp(line_tmp, ducky_cmd_altchar, strlen(ducky_cmd_altchar)) == 0) { + // ALTCHAR + line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; + ducky_numlock_on(); + state = ducky_altchar(line_tmp); + if(!state && error != NULL) { + snprintf(error, error_len, "Invalid altchar %s", line_tmp); + } + return (state) ? (0) : SCRIPT_STATE_ERROR; + } else if( + (strncmp(line_tmp, ducky_cmd_altstr_1, strlen(ducky_cmd_altstr_1)) == 0) || + (strncmp(line_tmp, ducky_cmd_altstr_2, strlen(ducky_cmd_altstr_2)) == 0)) { + // ALTSTRING + line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; + ducky_numlock_on(); + state = ducky_altstring(line_tmp); + if(!state && error != NULL) { + snprintf(error, error_len, "Invalid altstring %s", line_tmp); + } + return (state) ? (0) : SCRIPT_STATE_ERROR; + } else if(strncmp(line_tmp, ducky_cmd_repeat, strlen(ducky_cmd_repeat)) == 0) { + // REPEAT + line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; + state = ducky_get_number(line_tmp, &bad_ble->repeat_cnt); + if(!state && error != NULL) { + snprintf(error, error_len, "Invalid number %s", line_tmp); + } + return (state) ? (0) : SCRIPT_STATE_ERROR; + } else if(strncmp(line_tmp, ducky_cmd_sysrq, strlen(ducky_cmd_sysrq)) == 0) { + // SYSRQ + line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; + uint16_t key = ducky_get_keycode(bad_ble, line_tmp, true); + bt_hid_hold_while_keyboard_buffer_full(1, -1); + furi_hal_bt_hid_kb_press(KEY_MOD_LEFT_ALT | HID_KEYBOARD_PRINT_SCREEN); + furi_hal_bt_hid_kb_press(key); + FURI_LOG_I(WORKER_TAG, "BT RSSI: %f\r", (double)furi_hal_bt_get_rssi()); + furi_delay_ms(25); + furi_hal_bt_hid_kb_release(key); + furi_hal_bt_hid_kb_release(KEY_MOD_LEFT_ALT | HID_KEYBOARD_PRINT_SCREEN); + return (0); + } else { + // Special keys + modifiers + uint16_t key = ducky_get_keycode(bad_ble, line_tmp, false); + if(key == HID_KEYBOARD_NONE) { + if(error != NULL) { + snprintf(error, error_len, "No keycode defined for %s", line_tmp); + } + return SCRIPT_STATE_ERROR; + } + if((key & 0xFF00) != 0) { + // It's a modifier key + line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; + key |= ducky_get_keycode(bad_ble, line_tmp, true); + } + FURI_LOG_I(WORKER_TAG, "Special key pressed %x\r\n", key); + furi_hal_bt_hid_kb_press(key); + FURI_LOG_I(WORKER_TAG, "BT RSSI: %f\r", (double)furi_hal_bt_get_rssi()); + furi_delay_ms(25); + furi_hal_bt_hid_kb_release(key); + return (0); + } +} + +static bool ducky_script_preload(BadBleScript* bad_ble, File* script_file) { + uint8_t ret = 0; + uint32_t line_len = 0; + + furi_string_reset(bad_ble->line); + + do { + ret = storage_file_read(script_file, bad_ble->file_buf, FILE_BUFFER_LEN); + for(uint16_t i = 0; i < ret; i++) { + if(bad_ble->file_buf[i] == '\n' && line_len > 0) { + bad_ble->st.line_nb++; + line_len = 0; + } else { + if(bad_ble->st.line_nb == 0) { // Save first line + furi_string_push_back(bad_ble->line, bad_ble->file_buf[i]); + } + line_len++; + } + } + if(storage_file_eof(script_file)) { + if(line_len > 0) { + bad_ble->st.line_nb++; + break; + } + } + } while(ret > 0); + + storage_file_seek(script_file, 0, true); + furi_string_reset(bad_ble->line); + + return true; +} + +static int32_t ducky_script_execute_next(BadBleScript* bad_ble, File* script_file) { + int32_t delay_val = 0; + + if(bad_ble->repeat_cnt > 0) { + bad_ble->repeat_cnt--; + delay_val = ducky_parse_line( + bad_ble, bad_ble->line_prev, bad_ble->st.error, sizeof(bad_ble->st.error)); + if(delay_val == SCRIPT_STATE_NEXT_LINE) { // Empty line + return 0; + } else if(delay_val < 0) { // Script error + bad_ble->st.error_line = bad_ble->st.line_cur - 1; + FURI_LOG_E(WORKER_TAG, "Unknown command at line %u", bad_ble->st.line_cur - 1U); + return SCRIPT_STATE_ERROR; + } else { + return (delay_val + bad_ble->defdelay); + } + } + + furi_string_set(bad_ble->line_prev, bad_ble->line); + furi_string_reset(bad_ble->line); + + while(1) { + if(bad_ble->buf_len == 0) { + bad_ble->buf_len = storage_file_read(script_file, bad_ble->file_buf, FILE_BUFFER_LEN); + if(storage_file_eof(script_file)) { + if((bad_ble->buf_len < FILE_BUFFER_LEN) && (bad_ble->file_end == false)) { + bad_ble->file_buf[bad_ble->buf_len] = '\n'; + bad_ble->buf_len++; + bad_ble->file_end = true; + } + } + + bad_ble->buf_start = 0; + if(bad_ble->buf_len == 0) return SCRIPT_STATE_END; + } + for(uint8_t i = bad_ble->buf_start; i < (bad_ble->buf_start + bad_ble->buf_len); i++) { + if(bad_ble->file_buf[i] == '\n' && furi_string_size(bad_ble->line) > 0) { + bad_ble->st.line_cur++; + bad_ble->buf_len = bad_ble->buf_len + bad_ble->buf_start - (i + 1); + bad_ble->buf_start = i + 1; + furi_string_trim(bad_ble->line); + delay_val = ducky_parse_line( + bad_ble, bad_ble->line, bad_ble->st.error, sizeof(bad_ble->st.error)); + if(delay_val == SCRIPT_STATE_NEXT_LINE) { // Empty line + return 0; + } else if(delay_val < 0) { + bad_ble->st.error_line = bad_ble->st.line_cur; + if(delay_val == SCRIPT_STATE_NEXT_LINE) { + snprintf( + bad_ble->st.error, sizeof(bad_ble->st.error), "Forbidden empty line"); + FURI_LOG_E( + WORKER_TAG, "Forbidden empty line at line %u", bad_ble->st.line_cur); + } else { + FURI_LOG_E(WORKER_TAG, "Unknown command at line %u", bad_ble->st.line_cur); + } + return SCRIPT_STATE_ERROR; + } else { + return (delay_val + bad_ble->defdelay); + } + } else { + furi_string_push_back(bad_ble->line, bad_ble->file_buf[i]); + } + } + bad_ble->buf_len = 0; + if(bad_ble->file_end) return SCRIPT_STATE_END; + } + + return 0; +} + +static void bad_ble_hid_state_callback(BtStatus status, void* context) { + furi_assert(context); + BadBleScript* bad_ble = (BadBleScript*)context; + bool state = (status == BtStatusConnected); + + if(state == true) + furi_thread_flags_set(furi_thread_get_id(bad_ble->thread), WorkerEvtConnect); + else + furi_thread_flags_set(furi_thread_get_id(bad_ble->thread), WorkerEvtDisconnect); +} + +static int32_t bad_ble_worker(void* context) { + BadBleScript* bad_ble = context; + + BadBleWorkerState worker_state = BadBleStateInit; + int32_t delay_val = 0; + + // init ble hid + bt_disconnect(bad_ble->bt); + + // Wait 2nd core to update nvm storage + furi_delay_ms(200); + + bt_keys_storage_set_storage_path(bad_ble->bt, HID_BT_KEYS_STORAGE_PATH); + + //bt_set_adv_name(bad_ble->bt, "Keyboard K99"); + + furi_hal_bt_modify_profile_adv_name("Keyboard K99", BtProfileHidKeyboard); + + if(!bt_set_profile(bad_ble->bt, BtProfileHidKeyboard)) { + FURI_LOG_E(TAG, "Failed to switch to HID profile"); + return -1; + } + + furi_hal_bt_start_advertising(); + + FURI_LOG_I(WORKER_TAG, "Init"); + File* script_file = storage_file_alloc(furi_record_open(RECORD_STORAGE)); + bad_ble->line = furi_string_alloc(); + bad_ble->line_prev = furi_string_alloc(); + + bt_set_status_changed_callback(bad_ble->bt, bad_ble_hid_state_callback, bad_ble); + while(1) { + if(worker_state == BadBleStateInit) { // State: initialization + if(storage_file_open( + script_file, + furi_string_get_cstr(bad_ble->file_path), + FSAM_READ, + FSOM_OPEN_EXISTING)) { + if((ducky_script_preload(bad_ble, script_file)) && (bad_ble->st.line_nb > 0)) { + worker_state = BadBleStateNotConnected; // Ready to run + } else { + worker_state = BadBleStateScriptError; // Script preload error + } + } else { + FURI_LOG_E(WORKER_TAG, "File open error"); + worker_state = BadBleStateFileError; // File open error + } + bad_ble->st.state = worker_state; + + } else if(worker_state == BadBleStateNotConnected) { // State: ble not connected + uint32_t flags = furi_thread_flags_wait( + WorkerEvtEnd | WorkerEvtConnect | WorkerEvtToggle, + FuriFlagWaitAny, + FuriWaitForever); + furi_check((flags & FuriFlagError) == 0); + if(flags & WorkerEvtEnd) { + break; + } else if(flags & WorkerEvtConnect) { + worker_state = BadBleStateIdle; // Ready to run + } else if(flags & WorkerEvtToggle) { + worker_state = BadBleStateWillRun; // Will run when ble is connected + } + bad_ble->st.state = worker_state; + + } else if(worker_state == BadBleStateIdle) { // State: ready to start + uint32_t flags = furi_thread_flags_wait( + WorkerEvtEnd | WorkerEvtToggle | WorkerEvtDisconnect, + FuriFlagWaitAny, + FuriWaitForever); + furi_check((flags & FuriFlagError) == 0); + if(flags & WorkerEvtEnd) { + break; + } else if(flags & WorkerEvtToggle) { // Start executing script + DOLPHIN_DEED(DolphinDeedBadUsbPlayScript); + delay_val = 0; + bad_ble->buf_len = 0; + bad_ble->st.line_cur = 0; + bad_ble->defdelay = 0; + bad_ble->repeat_cnt = 0; + bad_ble->file_end = false; + storage_file_seek(script_file, 0, true); + worker_state = BadBleStateRunning; + } else if(flags & WorkerEvtDisconnect) { + worker_state = BadBleStateNotConnected; // ble disconnected + } + bad_ble->st.state = worker_state; + + } else if(worker_state == BadBleStateWillRun) { // State: start on connection + uint32_t flags = furi_thread_flags_wait( + WorkerEvtEnd | WorkerEvtConnect | WorkerEvtToggle, + FuriFlagWaitAny, + FuriWaitForever); + furi_check((flags & FuriFlagError) == 0); + if(flags & WorkerEvtEnd) { + break; + } else if(flags & WorkerEvtConnect) { // Start executing script + DOLPHIN_DEED(DolphinDeedBadUsbPlayScript); + delay_val = 0; + bad_ble->buf_len = 0; + bad_ble->st.line_cur = 0; + bad_ble->defdelay = 0; + bad_ble->repeat_cnt = 0; + bad_ble->file_end = false; + storage_file_seek(script_file, 0, true); + // extra time for PC to recognize Flipper as keyboard + furi_thread_flags_wait(0, FuriFlagWaitAny, 1500); + worker_state = BadBleStateRunning; + } else if(flags & WorkerEvtToggle) { // Cancel scheduled execution + worker_state = BadBleStateNotConnected; + } + bad_ble->st.state = worker_state; + + } else if(worker_state == BadBleStateRunning) { // State: running + uint16_t delay_cur = (delay_val > 1000) ? (1000) : (delay_val); + uint32_t flags = furi_thread_flags_wait( + WorkerEvtEnd | WorkerEvtToggle | WorkerEvtDisconnect, FuriFlagWaitAny, delay_cur); + delay_val -= delay_cur; + if(!(flags & FuriFlagError)) { + if(flags & WorkerEvtEnd) { + break; + } else if(flags & WorkerEvtToggle) { + worker_state = BadBleStateIdle; // Stop executing script + furi_hal_bt_hid_kb_release_all(); + } else if(flags & WorkerEvtDisconnect) { + worker_state = BadBleStateNotConnected; // ble disconnected + furi_hal_bt_hid_kb_release_all(); + } + bad_ble->st.state = worker_state; + continue; + } else if( + (flags == (unsigned)FuriFlagErrorTimeout) || + (flags == (unsigned)FuriFlagErrorResource)) { + if(delay_val > 0) { + bad_ble->st.delay_remain--; + continue; + } + bad_ble->st.state = BadBleStateRunning; + delay_val = ducky_script_execute_next(bad_ble, script_file); + if(delay_val == SCRIPT_STATE_ERROR) { // Script error + delay_val = 0; + worker_state = BadBleStateScriptError; + bad_ble->st.state = worker_state; + } else if(delay_val == SCRIPT_STATE_END) { // End of script + delay_val = 0; + worker_state = BadBleStateIdle; + bad_ble->st.state = BadBleStateDone; + furi_hal_bt_hid_kb_release_all(); + continue; + } else if(delay_val > 1000) { + bad_ble->st.state = BadBleStateDelay; // Show long delays + bad_ble->st.delay_remain = delay_val / 1000; + } + } else { + furi_check((flags & FuriFlagError) == 0); + } + + } else if( + (worker_state == BadBleStateFileError) || + (worker_state == BadBleStateScriptError)) { // State: error + uint32_t flags = furi_thread_flags_wait( + WorkerEvtEnd, FuriFlagWaitAny, FuriWaitForever); // Waiting for exit command + furi_check((flags & FuriFlagError) == 0); + if(flags & WorkerEvtEnd) { + break; + } + } + } + + // release all keys + bt_hid_hold_while_keyboard_buffer_full(6, 3000); + + // stop ble + bt_set_status_changed_callback(bad_ble->bt, NULL, NULL); + + bt_disconnect(bad_ble->bt); + + // Wait 2nd core to update nvm storage + furi_delay_ms(200); + + bt_keys_storage_set_default_path(bad_ble->bt); + + if(!bt_set_profile(bad_ble->bt, BtProfileSerial)) { + FURI_LOG_E(TAG, "Failed to switch to Serial profile"); + } + + storage_file_close(script_file); + storage_file_free(script_file); + furi_string_free(bad_ble->line); + furi_string_free(bad_ble->line_prev); + + FURI_LOG_I(WORKER_TAG, "End"); + + return 0; +} + +static void bad_ble_script_set_default_keyboard_layout(BadBleScript* bad_ble) { + furi_assert(bad_ble); + memset(bad_ble->layout, HID_KEYBOARD_NONE, sizeof(bad_ble->layout)); + memcpy(bad_ble->layout, hid_asciimap, MIN(sizeof(hid_asciimap), sizeof(bad_ble->layout))); +} + +BadBleScript* bad_ble_script_open(FuriString* file_path, Bt* bt) { + furi_assert(file_path); + + BadBleScript* bad_ble = malloc(sizeof(BadBleScript)); + bad_ble->file_path = furi_string_alloc(); + furi_string_set(bad_ble->file_path, file_path); + bad_ble_script_set_default_keyboard_layout(bad_ble); + + bad_ble->st.state = BadBleStateInit; + bad_ble->st.error[0] = '\0'; + + bad_ble->bt = bt; + + bad_ble->thread = furi_thread_alloc_ex("BadBleWorker", 2048, bad_ble_worker, bad_ble); + furi_thread_start(bad_ble->thread); + return bad_ble; +} //-V773 + +void bad_ble_script_close(BadBleScript* bad_ble) { + furi_assert(bad_ble); + furi_record_close(RECORD_STORAGE); + furi_thread_flags_set(furi_thread_get_id(bad_ble->thread), WorkerEvtEnd); + furi_thread_join(bad_ble->thread); + furi_thread_free(bad_ble->thread); + furi_string_free(bad_ble->file_path); + free(bad_ble); +} + +void bad_ble_script_set_keyboard_layout(BadBleScript* bad_ble, FuriString* layout_path) { + furi_assert(bad_ble); + + if((bad_ble->st.state == BadBleStateRunning) || (bad_ble->st.state == BadBleStateDelay)) { + // do not update keyboard layout while a script is running + return; + } + + File* layout_file = storage_file_alloc(furi_record_open(RECORD_STORAGE)); + if(!furi_string_empty(layout_path)) { + if(storage_file_open( + layout_file, furi_string_get_cstr(layout_path), FSAM_READ, FSOM_OPEN_EXISTING)) { + uint16_t layout[128]; + if(storage_file_read(layout_file, layout, sizeof(layout)) == sizeof(layout)) { + memcpy(bad_ble->layout, layout, sizeof(layout)); + } + } + storage_file_close(layout_file); + } else { + bad_ble_script_set_default_keyboard_layout(bad_ble); + } + storage_file_free(layout_file); +} + +void bad_ble_script_toggle(BadBleScript* bad_ble) { + furi_assert(bad_ble); + furi_thread_flags_set(furi_thread_get_id(bad_ble->thread), WorkerEvtToggle); +} + +BadBleState* bad_ble_script_get_state(BadBleScript* bad_ble) { + furi_assert(bad_ble); + return &(bad_ble->st); +} diff --git a/applications/main/bad_ble/bad_ble_script.h b/applications/main/bad_ble/bad_ble_script.h new file mode 100644 index 000000000..f1553e1db --- /dev/null +++ b/applications/main/bad_ble/bad_ble_script.h @@ -0,0 +1,49 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +typedef struct BadBleScript BadBleScript; + +typedef enum { + BadBleStateInit, + BadBleStateNotConnected, + BadBleStateIdle, + BadBleStateWillRun, + BadBleStateRunning, + BadBleStateDelay, + BadBleStateDone, + BadBleStateScriptError, + BadBleStateFileError, +} BadBleWorkerState; + +typedef struct { + BadBleWorkerState state; + uint16_t line_cur; + uint16_t line_nb; + uint32_t delay_remain; + uint16_t error_line; + char error[64]; +} BadBleState; + +BadBleScript* bad_ble_script_open(FuriString* file_path, Bt *bt); + +void bad_ble_script_close(BadBleScript* bad_ble); + +void bad_ble_script_set_keyboard_layout(BadBleScript* bad_ble, FuriString* layout_path); + +void bad_ble_script_start(BadBleScript* bad_ble); + +void bad_ble_script_stop(BadBleScript* bad_ble); + +void bad_ble_script_toggle(BadBleScript* bad_ble); + +BadBleState* bad_ble_script_get_state(BadBleScript* bad_ble); + +#ifdef __cplusplus +} +#endif diff --git a/applications/main/bad_ble/bad_ble_settings_filename.h b/applications/main/bad_ble/bad_ble_settings_filename.h new file mode 100644 index 000000000..73245ad5f --- /dev/null +++ b/applications/main/bad_ble/bad_ble_settings_filename.h @@ -0,0 +1,3 @@ +#pragma once + +#define BAD_BLE_SETTINGS_FILE_NAME ".BadBle.settings" diff --git a/applications/main/bad_ble/badusb_10px.png b/applications/main/bad_ble/badusb_10px.png new file mode 100644 index 0000000000000000000000000000000000000000..037474aa3bc9c2e1aca79a68483e69980432bcf5 GIT binary patch literal 576 zcmV-G0>AxEX>4Tx04R}tkv&MmKpe$i(`rSk4t5Z6$WWau6cusQDionYs1;guFuC*#nlvOW zE{=k0!NHHks)LKOt`4q(Aou~|=;Wm6A|?JWDYS_3;J6>}?mh0_0Yan9G%FATG`(u3 z5^*t;T@{0`5D-8=V(6BcWz0!Z5}xDh9zMR_MR}I@xj#prnzI<-6NzV;VOEJZh^IHJ z2Iqa^Fe}O`@j3ChNf#u3C`-Nm{=@yu+qV-Xlle$#1U1~DPPFA zta9Gstd(o5bx;1nP)=W2<~q$0B(R7jND!f*h7!uCB1)@HiiH&I$36VRj$a~|Laq`R zITlcX2HEk0|H1EWt^DMKn-q!zT`#u%F$x5Cfo9#dzmILZc>?&Kfh)c3uQY&}Ptxmc zEph}5Yy%h9ZB5w&E_Z;TCqp)6NAlAY@_FF>jJ_!g4Bi60Yi@6?eVjf3Y3eF@0~{Oz zV+G1y_jq?tXK(+WY4!I5C=YUpXXIhH00006VoOIv0RI600RN!9r;`8x010qNS#tmY z3ljhU3ljkVnw%H_000McNliru<^lu`HWXkp{t5s906j@WK~xyijZi@f05Awj>HlAL zr$MwDdI>{Qf+U53tOUR#xOeyy)jcQo#JNRv)7r6DVVK|+*(cmT+R+EbO(O#X#REG4 O0000&vafZq~f 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 literal 0 HcmV?d00001 diff --git a/applications/main/bad_ble/images/Error_18x18.png b/applications/main/bad_ble/images/Error_18x18.png new file mode 100644 index 0000000000000000000000000000000000000000..16a5a74d96686c9ff2d9d96984a285f3885cffbe GIT binary patch literal 1083 zcmbVLO=#0l91nDeA53H@GKcca5EPcrrb`o6$JVsAu+G{QR&T!My{=(RUY5MA>A)Sl zdD4@f#M8iCym|7VCs7c=gNnj#9z6JU@F>)meoPNz2Ls9f|6cyT-~an|dGX5V(KAOm zjvFl&tO}E3@q0MIzVO5rW@4P?YIKP-Xd4EYn?t0ILD7XPxPl?-ti8fB9GBQ|sx?|G zEtocOMHt(Nk?S)w$IZ+}KD1Xc1$DgQcp3i3(`P(zP=;SlmE@A2#Z9NM8Q`VO#j3rz zY8!~3y$og|lM%R>LJ+wvFEpbJ-{Uoz9$!m5=$X*f4Bro`Rw{!m2{6z_MX+UA2D%|4 zSci7KJ_S@+RU}!H6itw2GijKb1_lq$+y$s%R;>KM89Qb8CZ)b9N$qx9Y$rt$tVoJs z7?P|?swyxGA?$b*MuHbk4jC*Q+JWO!hj<`ngmtn`Gdv5mpM&d{N_)g!IH(k>nG``^ zQbbvD-8iwHbx14tZy5Vpht-acr3wzodSJ7LG$w~&R=k59#fB^z^J?I*uE3T>>~$A= zv}k2`_D4hxGLuL*QZ`HpN(v?gZCb}d+E%e($Qrg470Wh8L!SNcdRo!)nwHX%a#B%p z*|~I9OY7;JrO#Vx(vXMPq8C!=*?8#NVZH}g?Le%V4KSo6s1ni|jzPIeC<&Xy2dXNj zz{L`@9WTDQ6nCkgw1op_1EYLET+l1C>Fg7Np-(rEjMD;|PN}R0nkLjCM1rR3EG3vi zX~a_KYA3hc1AOxR-^6tGo!e{*7ot=XaSLN&)^x7*$R z_;8nL#iBJ=jXt&Rz8&Mh$jFComtIimxkqQi literal 0 HcmV?d00001 diff --git a/applications/main/bad_ble/images/EviSmile1_18x21.png b/applications/main/bad_ble/images/EviSmile1_18x21.png new file mode 100644 index 0000000000000000000000000000000000000000..987af32587ca7fbada8810abd0cabf788c9c04f9 GIT binary patch literal 3645 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 V0A_Pz|1^geur#waEi!h!{2vr@XiES9 literal 0 HcmV?d00001 diff --git a/applications/main/bad_ble/images/EviSmile2_18x21.png b/applications/main/bad_ble/images/EviSmile2_18x21.png new file mode 100644 index 0000000000000000000000000000000000000000..7e28c9f018a0f2cb572e5f5ce1db2e5c71511dd9 GIT binary patch literal 3649 zcmaJ@c{r5q+kPw+itIu%M!c0}78H}QFQW$2*hZ4Z7z}1<3}z&ew2&=Z)`Subsgy~! zitI#@P?jtS4GGE8H@&~N_xJtr^*zV&JokNH_j#Vzbzj%@9LIeHV`nWYq96hQfT#`1 z0?QjEd9RF+0Ph;}C*NUXe8#ULo#uHtV0i zpB@kifK}N-&El^4;@1HD1#wA}#^}o;&eAdx*(j%m^SvUdoXcZ*`#3(PF_(|WI-St} zqC8ae=xiu=Zf@=ETJ==+)OshYYiERnq1_+?Ndf*|q9 zw&y-u8UbKlfW-`FlpC+}-J=5h0IgShuVmBc&!{Slx(fhG0!F}+Q``9xu|Tu7W3x2S zybCCIc<3bpqyRtwE6fZGl!yYe-)xMw0R6?uLvlcW{_bKSAdU~n*k`?$-{dK9$|(}7 z$zT5*$YYy;wFT?T_##{%!>#!vYPJBu@wmjDCZ~Xi3^UDk0Hn_knD3G55CEYC@}NC+ zBgG!HXby@GsBcT{NI%-6Bh5*Dr4aIUeq>B#?0LX_GrZh>ac|*qaCUl@suXHU0NuF* z02EfcpKaI@2Vhw7wu}<20TUT!xLGY7;brQC6l@H=Cl*ZN%^I9@D*lLQ^JY0e6Li z0oyjQo?w$KR9aHUB&W~87nIXBgp)%=0ro}vdb`Kl9<>G3hkxPYj}^o91Oq1Fi&|F| zwkHANKDuz$3IHV6ttOag@Btm^g&zT+`qQoxcT(igFNFZWA}{hlx#_kY&!pM)V%g7> zs_W(W@mnoScI>S;6gS&C9C0fjt?%u(@*XE1%ysS(K&kux;8 zt*3V7KHpV+QCQHlSx5@6g19W<8Q%}?6q3t`7X;%`y4NBKLDQF|kAWMT>4p5oW`0TT zDAli8bZLXQ6DB_r2b)3gnDv-yYgkI;gJS}3_=8NI+)-ADd6^g3&CuQH9+8&s->p!w z2O04=zo`4@ryvG!HYT1B(G3&xzWNS-;_4;KQ&(^b>P@nQ37npDf*wH$cPLm!u|5~i z723-m8zD6-bn=4u^MLb-iPktY&iszrtZId1m5_^Y)CJh{zre|N>?_nlC084mo{0O2 zI4idL7nMCKxoRi>5|i>sM(q`Axi)SmqN0`vx7lvvj~Ya26*?3e^@x+Q(dsjaIPL(8;)$RkGdjuG7xDC!NpUwsLxi`B*IcM)q!Rv69o%;)7+K*br<2 zrt6qTL9NHe`5y$)2N$EQ@-CtZ90`>#<>ORjU&4tCII}*wv%rj||8-kWw+E}U=-@4D ziouXGXb1Da5^uJ5l6TJJ=?*@zm-k2J4c=uR=~U?y?L4C;pk=Iezt6AKyEMG?&_L)w z?SSVTeNJ|6W`G++%Q4B(%vnN^5i3E$RR^n%RYg|~26cTldQF&NO$#rzE{RRQ@3vkd ze=As$`^@d*b}Ju(>Ixl9ln;RE6Xx3!37`D0lQ`Y;7e?<$wE0#gHTV{E+Z6o8QU7wu z=c67|&d8fh-R;TN{XiV@H^h6A;Ddz?g^lC2`#VznGrg<2D_%3&+nY6q*!}F5*?5EA zZ2w$*?Yrv1^|@*}vpK8Gy~M&x*`u&TgGESjI1_Et8kKl-hSo zD)k*^91f#1g4%-vXw@@?qq;AO8;V~{yZ9*j+ziZF)RVh?G_g%GJvd#?fm{?*M7a^# zmO7#ErK;!A>!pIMr&&X#@5pc7w<8B-c3xvz?=1f3xt&CG6@R-qi35(yM%_t!>PAd(bMgZ zg)Wa+2VCYTljJkxR?kZBKL9V${(P*$fpMC#qS?nDcU|+TiC;)4zWU_wpxLpU6#4 zcedq*7`p1YCWh%pUzbdOU_228GQ&W2*-sQvY?Y+GUdW2Jx2(;N%RhF%l5@oH+GLJ% z>aza(!)MKZ_+GTP3VNv{Y>(AoCCOiVqPl47Y|;0D-SzJDJ1v8h?3C;RtSBk1LgOv8 za$lvrw}wWt=s0VV+^U#-sdZ&sbv1BtP$nQ6-Cag6M>i8R- zVeie)tE$`2%ZAk?mSZ^O5BoVx*M$*qo#j(m)mR6)5N(({w#ti1n(sN==G*olZ38og z!#aKSV-0+T(?@iXmxb#Y#_RB<70LeYbKE}|R||5KPAXZ~R{jjiouAKDY~ClS_&l{>hpNygN0#F}8NJ3%A}szkM~ftFDYyyh!KX zExw0nQf*SM?qnesZm*Yi4xZ(5xK+bVHOd+L)=f4si`_p6O+~NlSB$2@HrF957Z%qd z4Adlew@P`2C63`h^=5?N=|sTPi|R=P*^u!*L@W{S#X8+WGz0(vb&?~FfwM&;2vo8* z{uf4@Nv84G0AOg$q~QtvLQ0B@n?xg8$Y<@aDhF5HRR(2*V!<{!dUiTMWpYN+*I2 zX~VP#P$(31$Uxf*?};aPdTN5;P&f<%)rG)xwV+UhjsZef7xd2q=DDMLc_XkER{uET zt&m_}27`uxKte)7v_o{XsdOI*%)r0^0)<21a4jA}E09HD;F(&KK-J$07Q{dTokU}h zs1(pIMZ709h=Bz2LjBJf$h3cDDS`jwiI*`56HkM{w4uMw^c!ev`~O49CbG!PuFtq0m? zizkpMzbyOzrr6pdY$$;YJcU5Cu|R@(BHAR97sATS%0LGSgX`*;8o*$d=K4?=%=Dm{ zf&L+jL#Db=z2965Dj|qWq%eMSz5dJ9`6KsNJCJES&lW^FDVXSGMW>QMf1esb`g1JM zKkEI%_4;!xus?DkykH=|YWu%x{oBNApkLE}TbH-^xA}<_UdPjUt$l7jBboPGY{j4* zEqUY57+@fIgLlscFg6yZj?96SL>n;xBqVV7=g*4MrZFc|8Zg#q#Rey{Ywmgxt<5DE zn>{g#)i*sWWaOWVf0#?<-q|qEZthKQh$%lzIx_&3hz5B~zOjwq1H8`xkrFLe+<4l6 NjisGMnc1oH{{vTjXlVcd literal 0 HcmV?d00001 diff --git a/applications/main/bad_ble/images/EviWaiting1_18x21.png b/applications/main/bad_ble/images/EviWaiting1_18x21.png new file mode 100644 index 0000000000000000000000000000000000000000..d39d2173329d5317fc2cdfb18738922a5eeec6fe GIT binary patch literal 13020 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%0DogpcYr`$ z++qJ}>3@a%wq1X@`yto-TOabSio8kl{&AQ7JzRfg^#9`P_cZuloB>JwpGp2Leg7lZ zf8_eN6!^Em|B0^u$n|e2@Na?t6J7t`SN77L~l0$9an+)c;9a+DZRZZS8{^eP~v1|B4dJsqoRU?V`GD5l`ss3 nQ0V!FVYmG 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 literal 0 HcmV?d00001 diff --git a/applications/main/bad_ble/images/SDQuestion_35x43.png b/applications/main/bad_ble/images/SDQuestion_35x43.png new file mode 100644 index 0000000000000000000000000000000000000000..9b9c9a58e3257f926677533f8cc99ffb19dd74f5 GIT binary patch literal 1950 zcmcIlTWs7!6g5TAQV0(rqPBcsIS921UeDNG@7i=zCA)d7sMC-vG>8gyJRa{_+4UIP z$!=1i2t*O^Q!1)9DGybMBGF3a6SW|L6h5LVfJD{LegT4thW>zPQHvPws{yrXept!t zv3=&;bMHMf^XAC#V8_NS8##{a$PeX4*}aQh-5b`i|CfLH7_-|w{?PLw$KCsIeBHqv zeQy)T-TjJN7>~xyod%|r1hT0`619rY&>XjYN6klgf<(MUimsOtE`R=|z`J%v*qt1RpF9hwQq*vxPN&rD$57IyUT+iM0RsE`QpwMy9wjao*i^BQa%zm^2P4v8i*LT?<9 zA2&z%EDZ>+C4h(lkolCJfSRgm;7MKvGLS%0g0cuT1E>Z}@y(yWq6M~NjOGTKvDi~a zC`FNPNK&<0O;nWx4T=)fbzK6oB+DX0h~cysp_=H0T`h(j331^1kxM;3W<(a9j4}dK z+DM_|w`skwSteF6sfK(BCP1803uv0FLo1awI*j_KSd^yTn-YhGX`e`=B&3r8CjC>y zi@I9D{1T05SfaPk*8co2g*I*n^e2OIy*xISNSRa^cgV1?uFp5J0YMQB3Y3;xjT&i1 zyC$M##e?pUVhLRKj&_L)9?Wld>u%HCq;SStTN}Y*kccThRe>U`i)-U2J}i z;>oxY@%)BuZHgI3yPAgMs43vsNJP47iHfQ!qLpHl$)u9T2onYB=@#3rz-223l~=OH zs_a-*O3@VL0MW4s7KyDoOqHyNDzSA4a2n~Dsk#w2OUpDcsm-dZtbCu(W=8_*xMlVs z93AZA^Zi*3>Y66X2`KP3HXIsM5Hp%vK}90@UNN>klflv*azobR>E=QjBQG^aWtXqJ z(?B?06d3`>ZXmYMeC^(>%xg-hL0c^mM!Jei8nBQ$Q56NGx5!#@TNg^V5+9y5Z&x22##B&{B?u64ye+M3KZ=XlsY71%@jTp=Dy zHDISkcY5&@J8>5Bx!%I~{^i3@-@m}$cNaW+{nKk_j+x(U>F+k}c?6!^@X+fADi3lO z_rLKG{`yum;ZU>ayk!Z+q>VzZOn^bqQ>?UO0Drz@_TNg$rjt zNcQEpt?wS&gMaKe^8V2SorGL{ZtT%R?yPd~`n9FG(}zAOOPl5My;t&Z3(*F9I?g}- zvp)ag@#QCe{o%9hrGrn+&dpu9`rFcD;MqUV>^-?JG4|Gpeamy-PL6)jzu5T`{Cd7~ fwr}T&J8Rt1;5!ej|9##1_yo=O59dzx?S1thQe1#H literal 0 HcmV?d00001 diff --git a/applications/main/bad_ble/images/Smile_18x18.png b/applications/main/bad_ble/images/Smile_18x18.png new file mode 100644 index 0000000000000000000000000000000000000000..d2aae0dc37f4fd3453e3254e4a9cd33e753019b9 GIT binary patch literal 1080 zcmbVLJ#W)M7&c8+qM}MELKSpCuFFs)Y@Z#c{!l}k#tDs7mne-y28Nz}m&R)SfqiY< z4uB9sLckATVq#%Hs2l3Q1QOyGfS@}AVuP`q)1)v|9k67-@15W0dA}dmS8j}rPL47R zGgezN8&sZ8-)x3{VeT%b;u5K}$ZF6gT^M1egaRA0H4m=i28L+o&PP1QFqv()*&;1# z*>D0+fT@j;cp*hI%-nnuLT3XL*2e3uU*vx7zvEaJ6}ejl3s_+pcig4j2(Rw0G@acI zM@QWJb#^W>D1nCwWD{@GkBy|r^>_`cr`ICK_Dsk|kvj^iW!2eo5MfpoB;El4u&OQ~ zXhX-gudysn5(NmG@5E2@q*zI{+@mnPP;j!6Um4dX=XxVaNzv4P`YD{^Q<+S3CtE#B#lQbQVzaWishSKy`@I9nd} zNzE*B^pAjCWaN>m7aUmNr2@?J%B+fc&5<#$0|h&hjDdsEfafH<&L6>F3u3`r0*gJ5$o2K7!rg18fetSk!! zcE*B^>!&wY(=Ht)ZQ{t?#;6(v9@{Ik;hqqJuFkd*z#5Nc3o@;NqVP6^h*xB_pyXiz zX^5t9gh&5dK9L3`rnBfRGu{%*@`}%nU@OQM`!(1OQ<4W;ujl6PKk82bKxDoK1UX zo}>m`0Kh6NfkrXcT(O$~?vj|eaa~ljkh7%J?o55q-pc^**!UVz%AwNJcZ=vQQ!y_yREN&p7I;^V?R@fe^{%dt@s zXxsxlc6jC`1SA1K05i-K3_K74rWULXw*ftciTyG_Pww7A0pJD?khb4yAFuH%z{BYR zMuWGy2FPIpI{XI@Z;U9mGZ-)qpVz!D zC!7F?`RKrz%K(sAwGwB1kOyc@&HoAj<=^(4x}PM2t``6R^PCF@9-Hjg`C5`yEt>gS zp}bm#7q{Kqc;~)q12NO>BN2Or?(9i1k#(#_^zc7_%qN$#JAFv3CZ@;JDzk(SR}XzG{X zhEkc+q)F=EIAy#V-`5C&Ut7OcZUsxa@boy}2i_p#m-m(AuGQxRcF=WpxkaSp`gh2c zC?X?XE?*7kg{q#+*;V$AJvD_%y-B)>=YwrqSYqjNljly z1fE8)K&c6(@w?*fZmu=G87Y-S)I|@Y1#|ad@{`1m>Jp4b`S0F_o2KPFINn;A{XyVC zG~)INYf?_IJ;dQkp@dFQx@v2Nv{`e$W?t93bfOP&*%vZFFBAM6sc4b;bpPJU_2>l3`PxHC8>lVccdtm86m{h`B z@nl4@b8>j{_yb3$KqsRv<^y5Jhfcd5o_0QW&(i6c{ntXl57H023Kg7Q6&@;X!-Qbs z?AwpK=T*9ITwKMAJiQ!cnR6MH=ZG(@m%X7ZT@NSBVokeg&U}*^{hkHYN zD|i^%SnxioZtce8I3WDLDol)auToiBube*>H+5#E1{cYmr%ZH0DrDLrQN-So5|No0MhNEoVb#rt_lnN0xQ>sY#7VQnyQh zy}V1t&J09G^NagM8AY|h8KeQpVaYi4PW43xaxZLZeM)F5eQNu({t|9Ub&0gpuF$eq zT%r32{YV&%9@G*XKrNrlAJTbKSX=mJ!o^44=T2bOyspf>WAV-6slll-4y1x>1?1bI z&B>#3Kgv3vzhBJDc$Lv#^ojK0a|^QW+`}~+tql1lw>LMscKA%o*Q|n!f|~jG zameZ5)2^r2DirOWWvXR&LrIN&wI>HFn$LP543UJ@wh2DNdPCmZp|`J8-m3%;AS+eE zyTjAMcdTcx9a(MOi2GSJ#GI3!wcX~y^O|Rrr{aR#g=c*Jd`kRj{C9WgZo9GV)pp2E zLn+gpf+DU;v_wj^%$)oRUc28%BfUfFtw5I43HeoMiyB(7dw1;Rc7Xx0aLTo1S=`Msb8`>^~1 zFah|f40Z(j0s8{u%1?{gRB^h*KEdg$BegxX$g5uidB+3NwKGT39aHG|;?e%xmoj4$ zZOz#s2CllU@nL#Vx5QJQ8jVJROzk0i>_!X7HVP7RmolR4EGlzvg|s?6Isn|FU8*U?mAA_yDl38WeNq8Y=#IP+OtHPFG#YaMAmikolMFVh0(Ihp z_JH^1_Z1c4i_&2g@sI7{|8cXoa6i*SpIzB1Q7EH%8^%Nk_lX z?}Yj-#&&-Bq7M&d!TQDo7pq z!bzGce}0hR;$LBLZjs#i-oiAM!m_#uT zb|R{RSekjH9ORt}&bRA%Sqi5WtSU=?g>ztE@j(r`aW2_8S^JT*De+ULD&)w0E(!AsLJ zAwoaU{cfRgj7RI0y&K{f+A`j;P?3?9HTK@2?DXTD4ep zsaUGqh|5w^k{6MynDc5&94dHPAkqFd-1!%CGVtN}z{c>}v3Bfw&y4U&OnX%^vv8iq zd06-e(V)_xRNlr!&fZ%uYU?}4VROm`8Y-01_OBan+Rt~a;u{Ly*)1E6hi$GymM_h( zMd+*U=6+Sm(k-xb2Z}d61VPXGDE!rIt_%qTPh z=&%+{6Ay(#L5KCVyl|d4yr-uI2o8nAAW$6$Oh*$6MQH0IbaX)fTwrcEnwK{MV{Z9R zFzyTq_NCLQ2nZx3Bt$DjTZ=;Tfxz_j^&wC=1P<5adT0hR$#fh;lN_k>o57qAh^G;$ zbRvZe+G50cQiAA6FgMlz?14o6mzEs(Po20GgD`MZ2uusQwWr^XHa7piD~a^4cOV@@ z_;0@dCvl)7lS+VK2!WI!8lKxZZCgfTL4rjucA3?=sr^Qs|UGUkVijhimDA z_S@j_MDmtJ{cnhk4Z@lnNXL=!1Z#67m`kEXBzhq%^~?`xTk2`+Xq)K4U>0V2P#DbQ zkg2}jVe`W#I#Au;SaS+Kh(sXMe`CG=i`Drfc1sQG#ddqN zqs6alTPLJ!v25!n&e7W3#F5dAxEX>4Tx04R}tkv&MmKpe$i(`rSk4t5Z6$WWau6cusQDionYs1;guFuC*#nlvOW zE{=k0!NHHks)LKOt`4q(Aou~|=;Wm6A|?JWDYS_3;J6>}?mh0_0Yan9G%FATG`(u3 z5^*t;T@{0`5D-8=V(6BcWz0!Z5}xDh9zMR_MR}I@xj#prnzI<-6NzV;VOEJZh^IHJ z2Iqa^Fe}O`@j3ChNf#u3C`-Nm{=@yu+qV-Xlle$#1U1~DPPFA zta9Gstd(o5bx;1nP)=W2<~q$0B(R7jND!f*h7!uCB1)@HiiH&I$36VRj$a~|Laq`R zITlcX2HEk0|H1EWt^DMKn-q!zT`#u%F$x5Cfo9#dzmILZc>?&Kfh)c3uQY&}Ptxmc zEph}5Yy%h9ZB5w&E_Z;TCqp)6NAlAY@_FF>jJ_!g4Bi60Yi@6?eVjf3Y3eF@0~{Oz zV+G1y_jq?tXK(+WY4!I5C=YUpXXIhH00006VoOIv0RI600RN!9r;`8x010qNS#tmY z3ljhU3ljkVnw%H_000McNliru<^lu`HWXkp{t5s906j@WK~xyijZi@f05Awj>HlAL zr$MwDdI>{Qf+U53tOUR#xOeyy)jcQo#JNRv)7r6DVVK|+*(cmT+R+EbO(O#X#REG4 O0000gN=z( mnGc5;aHhO3XW%q4U|?wcz_Hx?k)a$=ErX}4pUXO@geCyYPARJZ literal 0 HcmV?d00001 diff --git a/applications/main/bad_ble/scenes/bad_ble_scene.c b/applications/main/bad_ble/scenes/bad_ble_scene.c new file mode 100644 index 000000000..351bb1e79 --- /dev/null +++ b/applications/main/bad_ble/scenes/bad_ble_scene.c @@ -0,0 +1,30 @@ +#include "bad_ble_scene.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const bad_ble_scene_on_enter_handlers[])(void*) = { +#include "bad_ble_scene_config.h" +}; +#undef ADD_SCENE + +// Generate scene on_event handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event, +bool (*const bad_ble_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = { +#include "bad_ble_scene_config.h" +}; +#undef ADD_SCENE + +// Generate scene on_exit handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit, +void (*const bad_ble_scene_on_exit_handlers[])(void* context) = { +#include "bad_ble_scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers bad_ble_scene_handlers = { + .on_enter_handlers = bad_ble_scene_on_enter_handlers, + .on_event_handlers = bad_ble_scene_on_event_handlers, + .on_exit_handlers = bad_ble_scene_on_exit_handlers, + .scene_num = BadBleSceneNum, +}; diff --git a/applications/main/bad_ble/scenes/bad_ble_scene.h b/applications/main/bad_ble/scenes/bad_ble_scene.h new file mode 100644 index 000000000..25b19fc4b --- /dev/null +++ b/applications/main/bad_ble/scenes/bad_ble_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) BadBleScene##id, +typedef enum { +#include "bad_ble_scene_config.h" + BadBleSceneNum, +} BadBleScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers bad_ble_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "bad_ble_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_event handlers declaration +#define ADD_SCENE(prefix, name, id) \ + bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event); +#include "bad_ble_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_exit handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context); +#include "bad_ble_scene_config.h" +#undef ADD_SCENE diff --git a/applications/main/bad_ble/scenes/bad_ble_scene_config.c b/applications/main/bad_ble/scenes/bad_ble_scene_config.c new file mode 100644 index 000000000..d1bbac5ee --- /dev/null +++ b/applications/main/bad_ble/scenes/bad_ble_scene_config.c @@ -0,0 +1,52 @@ +#include "../bad_ble_app_i.h" +#include "furi_hal_power.h" + +enum SubmenuIndex { + SubmenuIndexKeyboardLayout, +}; + +void bad_ble_scene_config_submenu_callback(void* context, uint32_t index) { + BadBleApp* bad_ble = context; + view_dispatcher_send_custom_event(bad_ble->view_dispatcher, index); +} + +void bad_ble_scene_config_on_enter(void* context) { + BadBleApp* bad_ble = context; + Submenu* submenu = bad_ble->submenu; + + submenu_add_item( + submenu, + "Keyboard layout", + SubmenuIndexKeyboardLayout, + bad_ble_scene_config_submenu_callback, + bad_ble); + + submenu_set_selected_item( + submenu, scene_manager_get_scene_state(bad_ble->scene_manager, BadBleSceneConfig)); + + view_dispatcher_switch_to_view(bad_ble->view_dispatcher, BadBleAppViewConfig); +} + +bool bad_ble_scene_config_on_event(void* context, SceneManagerEvent event) { + BadBleApp* bad_ble = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_set_scene_state(bad_ble->scene_manager, BadBleSceneConfig, event.event); + consumed = true; + if(event.event == SubmenuIndexKeyboardLayout) { + scene_manager_next_scene(bad_ble->scene_manager, BadBleSceneConfigLayout); + } else { + furi_crash("Unknown key type"); + } + } + + return consumed; +} + +void bad_ble_scene_config_on_exit(void* context) { + BadBleApp* bad_ble = context; + Submenu* submenu = bad_ble->submenu; + + submenu_reset(submenu); +} diff --git a/applications/main/bad_ble/scenes/bad_ble_scene_config.h b/applications/main/bad_ble/scenes/bad_ble_scene_config.h new file mode 100644 index 000000000..addd8d9fa --- /dev/null +++ b/applications/main/bad_ble/scenes/bad_ble_scene_config.h @@ -0,0 +1,5 @@ +ADD_SCENE(bad_ble, file_select, FileSelect) +ADD_SCENE(bad_ble, work, Work) +ADD_SCENE(bad_ble, error, Error) +ADD_SCENE(bad_ble, config, Config) +ADD_SCENE(bad_ble, config_layout, ConfigLayout) diff --git a/applications/main/bad_ble/scenes/bad_ble_scene_config_layout.c b/applications/main/bad_ble/scenes/bad_ble_scene_config_layout.c new file mode 100644 index 000000000..aabb1b900 --- /dev/null +++ b/applications/main/bad_ble/scenes/bad_ble_scene_config_layout.c @@ -0,0 +1,47 @@ +#include "../bad_ble_app_i.h" +#include "furi_hal_power.h" +#include + +static bool bad_ble_layout_select(BadBleApp* bad_ble) { + furi_assert(bad_ble); + + FuriString* predefined_path; + predefined_path = furi_string_alloc(); + if(!furi_string_empty(bad_ble->keyboard_layout)) { + furi_string_set(predefined_path, bad_ble->keyboard_layout); + } else { + furi_string_set(predefined_path, BAD_BLE_APP_PATH_LAYOUT_FOLDER); + } + + DialogsFileBrowserOptions browser_options; + dialog_file_browser_set_basic_options( + &browser_options, BAD_BLE_APP_LAYOUT_EXTENSION, &I_keyboard_10px); + + // Input events and views are managed by file_browser + bool res = dialog_file_browser_show( + bad_ble->dialogs, bad_ble->keyboard_layout, predefined_path, &browser_options); + + furi_string_free(predefined_path); + return res; +} + +void bad_ble_scene_config_layout_on_enter(void* context) { + BadBleApp* bad_ble = context; + + if(bad_ble_layout_select(bad_ble)) { + bad_ble_script_set_keyboard_layout(bad_ble->bad_ble_script, bad_ble->keyboard_layout); + } + scene_manager_previous_scene(bad_ble->scene_manager); +} + +bool bad_ble_scene_config_layout_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + // BadBleApp* bad_ble = context; + return false; +} + +void bad_ble_scene_config_layout_on_exit(void* context) { + UNUSED(context); + // BadBleApp* bad_ble = context; +} diff --git a/applications/main/bad_ble/scenes/bad_ble_scene_error.c b/applications/main/bad_ble/scenes/bad_ble_scene_error.c new file mode 100644 index 000000000..fb8524eb1 --- /dev/null +++ b/applications/main/bad_ble/scenes/bad_ble_scene_error.c @@ -0,0 +1,83 @@ +#include "../bad_ble_app_i.h" +#include "../../../settings/desktop_settings/desktop_settings_app.h" + +typedef enum { + BadBleCustomEventErrorBack, +} BadBleCustomEvent; + +static void + bad_ble_scene_error_event_callback(GuiButtonType result, InputType type, void* context) { + furi_assert(context); + BadBleApp* app = context; + + if((result == GuiButtonTypeLeft) && (type == InputTypeShort)) { + view_dispatcher_send_custom_event(app->view_dispatcher, BadBleCustomEventErrorBack); + } +} + +void bad_ble_scene_error_on_enter(void* context) { + BadBleApp* app = context; + DesktopSettings* settings = malloc(sizeof(DesktopSettings)); + DESKTOP_SETTINGS_LOAD(settings); + + if(app->error == BadBleAppErrorNoFiles) { + widget_add_icon_element(app->widget, 0, 0, &I_SDQuestion_35x43); + widget_add_string_multiline_element( + app->widget, + 81, + 4, + AlignCenter, + AlignTop, + FontSecondary, + "No SD card or\napp data found.\nThis app will not\nwork without\nrequired files."); + widget_add_button_element( + app->widget, GuiButtonTypeLeft, "Back", bad_ble_scene_error_event_callback, app); + } else if(app->error == BadBleAppErrorCloseRpc) { + widget_add_icon_element(app->widget, 78, 0, &I_ActiveConnection_50x64); + if (settings->sfw_mode) { + widget_add_string_multiline_element( + app->widget, 3, 2, AlignLeft, AlignTop, FontPrimary, "Connection\nis active!"); + widget_add_string_multiline_element( + app->widget, + 3, + 30, + AlignLeft, + AlignTop, + FontSecondary, + "Disconnect from\nPC or phone to\nuse this function."); + } + else { + widget_add_string_multiline_element( + app->widget, 3, 2, AlignLeft, AlignTop, FontPrimary, "I am not\na whore!"); + widget_add_string_multiline_element( + app->widget, + 3, + 30, + AlignLeft, + AlignTop, + FontSecondary, + "Pull out from\nPC or phone to\nuse me like this."); + } + } + + view_dispatcher_switch_to_view(app->view_dispatcher, BadBleAppViewError); + free(settings); +} + +bool bad_ble_scene_error_on_event(void* context, SceneManagerEvent event) { + BadBleApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == BadBleCustomEventErrorBack) { + view_dispatcher_stop(app->view_dispatcher); + consumed = true; + } + } + return consumed; +} + +void bad_ble_scene_error_on_exit(void* context) { + BadBleApp* app = context; + widget_reset(app->widget); +} diff --git a/applications/main/bad_ble/scenes/bad_ble_scene_file_select.c b/applications/main/bad_ble/scenes/bad_ble_scene_file_select.c new file mode 100644 index 000000000..d60f6a246 --- /dev/null +++ b/applications/main/bad_ble/scenes/bad_ble_scene_file_select.c @@ -0,0 +1,51 @@ +#include "../bad_ble_app_i.h" +#include "furi_hal_power.h" +#include + +static bool bad_ble_file_select(BadBleApp* bad_ble) { + furi_assert(bad_ble); + + DialogsFileBrowserOptions browser_options; + dialog_file_browser_set_basic_options( + &browser_options, BAD_BLE_APP_SCRIPT_EXTENSION, &I_badusb_10px); + browser_options.base_path = BAD_BLE_APP_BASE_FOLDER; + browser_options.skip_assets = true; + + // Input events and views are managed by file_browser + bool res = dialog_file_browser_show( + bad_ble->dialogs, bad_ble->file_path, bad_ble->file_path, &browser_options); + + return res; +} + +void bad_ble_scene_file_select_on_enter(void* context) { + BadBleApp* bad_ble = context; + + // furi_hal_ble_disable(); + if(bad_ble->bad_ble_script) { + bad_ble_script_close(bad_ble->bad_ble_script); + bad_ble->bad_ble_script = NULL; + } + + if(bad_ble_file_select(bad_ble)) { + bad_ble->bad_ble_script = bad_ble_script_open(bad_ble->file_path, bad_ble->bt); + bad_ble_script_set_keyboard_layout(bad_ble->bad_ble_script, bad_ble->keyboard_layout); + + scene_manager_next_scene(bad_ble->scene_manager, BadBleSceneWork); + } else { + // furi_hal_ble_enable(); + view_dispatcher_stop(bad_ble->view_dispatcher); + } +} + +bool bad_ble_scene_file_select_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + // BadBleApp* bad_ble = context; + return false; +} + +void bad_ble_scene_file_select_on_exit(void* context) { + UNUSED(context); + // BadBleApp* bad_ble = context; +} \ No newline at end of file diff --git a/applications/main/bad_ble/scenes/bad_ble_scene_work.c b/applications/main/bad_ble/scenes/bad_ble_scene_work.c new file mode 100644 index 000000000..a2c009fc3 --- /dev/null +++ b/applications/main/bad_ble/scenes/bad_ble_scene_work.c @@ -0,0 +1,54 @@ +#include "../bad_ble_script.h" +#include "../bad_ble_app_i.h" +#include "../views/bad_ble_view.h" +#include "furi_hal.h" +#include "toolbox/path.h" + +void bad_ble_scene_work_button_callback(InputKey key, void* context) { + furi_assert(context); + BadBleApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, key); +} + +bool bad_ble_scene_work_on_event(void* context, SceneManagerEvent event) { + BadBleApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == InputKeyLeft) { + scene_manager_next_scene(app->scene_manager, BadBleSceneConfig); + consumed = true; + } else if(event.event == InputKeyOk) { + bad_ble_script_toggle(app->bad_ble_script); + consumed = true; + } + } else if(event.type == SceneManagerEventTypeTick) { + bad_ble_set_state(app->bad_ble_view, bad_ble_script_get_state(app->bad_ble_script)); + } + return consumed; +} + +void bad_ble_scene_work_on_enter(void* context) { + BadBleApp* app = context; + + FuriString* file_name; + file_name = furi_string_alloc(); + path_extract_filename(app->file_path, file_name, true); + bad_ble_set_file_name(app->bad_ble_view, furi_string_get_cstr(file_name)); + furi_string_free(file_name); + + FuriString* layout; + layout = furi_string_alloc(); + path_extract_filename(app->keyboard_layout, layout, true); + bad_ble_set_layout(app->bad_ble_view, furi_string_get_cstr(layout)); + furi_string_free(layout); + + bad_ble_set_state(app->bad_ble_view, bad_ble_script_get_state(app->bad_ble_script)); + + bad_ble_set_button_callback(app->bad_ble_view, bad_ble_scene_work_button_callback, app); + view_dispatcher_switch_to_view(app->view_dispatcher, BadBleAppViewWork); +} + +void bad_ble_scene_work_on_exit(void* context) { + UNUSED(context); +} diff --git a/applications/main/bad_ble/switch_ble.py b/applications/main/bad_ble/switch_ble.py new file mode 100644 index 000000000..32ed65492 --- /dev/null +++ b/applications/main/bad_ble/switch_ble.py @@ -0,0 +1,24 @@ +import os +import re + +def analyze_and_replace(directory): + # Recursively search for .c and .h files in the given directory + for root, dirs, files in os.walk(directory): + for file in files: + if file.endswith(".c") or file.endswith(".h"): + # Read the contents of the file + with open(os.path.join(root, file), "r") as f: + contents = f.read() + + # Replace all occurrences of "BadUsb" and "bad_usb" with "BadBle" and "bad_ble" + contents = contents.replace("usb", "ble") + contents = contents.replace("USB", "BLE") + contents = contents.replace("Usb", "Ble") + + + # Write the modified contents back to the file + with open(os.path.join(root, file), "w") as f: + f.write(contents) + +# Test the function with a sample directory +analyze_and_replace(".") diff --git a/applications/main/bad_ble/views/bad_ble_view.c b/applications/main/bad_ble/views/bad_ble_view.c new file mode 100644 index 000000000..fa5c099a5 --- /dev/null +++ b/applications/main/bad_ble/views/bad_ble_view.c @@ -0,0 +1,237 @@ +#include "bad_ble_view.h" +#include "../bad_ble_script.h" +#include +#include +#include +#include "../../../settings/desktop_settings/desktop_settings_app.h" + +#define MAX_NAME_LEN 64 + +struct BadBle { + View* view; + BadBleButtonCallback callback; + void* context; +}; + +typedef struct { + char file_name[MAX_NAME_LEN]; + char layout[MAX_NAME_LEN]; + BadBleState state; + uint8_t anim_frame; +} BadBleModel; + +static void bad_ble_draw_callback(Canvas* canvas, void* _model) { + BadBleModel* model = _model; + + FuriString* disp_str; + disp_str = furi_string_alloc_set(model->file_name); + elements_string_fit_width(canvas, disp_str, 128 - 2); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 2, 8, furi_string_get_cstr(disp_str)); + DesktopSettings* settings = malloc(sizeof(DesktopSettings)); + DESKTOP_SETTINGS_LOAD(settings); + + if(strlen(model->layout) == 0) { + furi_string_set(disp_str, "(default)"); + } else { + furi_string_reset(disp_str); + furi_string_push_back(disp_str, '('); + for(size_t i = 0; i < strlen(model->layout); i++) + furi_string_push_back(disp_str, model->layout[i]); + furi_string_push_back(disp_str, ')'); + } + elements_string_fit_width(canvas, disp_str, 128 - 2); + canvas_draw_str( + canvas, 2, 8 + canvas_current_font_height(canvas), furi_string_get_cstr(disp_str)); + + furi_string_reset(disp_str); + + canvas_draw_icon(canvas, 22, 24, &I_UsbTree_48x22); + + if((model->state.state == BadBleStateIdle) || (model->state.state == BadBleStateDone) || + (model->state.state == BadBleStateNotConnected)) { + if (settings->sfw_mode) { + elements_button_center(canvas, "Start"); + } + else { + elements_button_center(canvas, "Cum"); + } + } else if((model->state.state == BadBleStateRunning) || (model->state.state == BadBleStateDelay)) { + elements_button_center(canvas, "Stop"); + } else if(model->state.state == BadBleStateWillRun) { + elements_button_center(canvas, "Cancel"); + } + + if((model->state.state == BadBleStateNotConnected) || + (model->state.state == BadBleStateIdle) || (model->state.state == BadBleStateDone)) { + elements_button_left(canvas, "Config"); + } + + if(model->state.state == BadBleStateNotConnected) { + canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18); + canvas_set_font(canvas, FontPrimary); + if (settings->sfw_mode) { + canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Connect me"); + canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "to a computer"); + } + else { + canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Plug me"); + canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "in, Daddy"); + } + } else if(model->state.state == BadBleStateWillRun) { + canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18); + canvas_set_font(canvas, FontPrimary); + if (settings->sfw_mode) { + canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Will run"); + } + else { + canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Will cum"); + } + canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "on connect"); + } else if(model->state.state == BadBleStateFileError) { + canvas_draw_icon(canvas, 4, 26, &I_Error_18x18); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "File"); + canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "ERROR"); + } else if(model->state.state == BadBleStateScriptError) { + canvas_draw_icon(canvas, 4, 26, &I_Error_18x18); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 127, 33, AlignRight, AlignBottom, "ERROR:"); + canvas_set_font(canvas, FontSecondary); + furi_string_printf(disp_str, "line %u", model->state.error_line); + canvas_draw_str_aligned( + canvas, 127, 46, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); + furi_string_reset(disp_str); + canvas_draw_str_aligned(canvas, 127, 56, AlignRight, AlignBottom, model->state.error); + } else if(model->state.state == BadBleStateIdle) { + canvas_draw_icon(canvas, 4, 26, &I_Smile_18x18); + canvas_set_font(canvas, FontBigNumbers); + canvas_draw_str_aligned(canvas, 114, 40, AlignRight, AlignBottom, "0"); + canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14); + } else if(model->state.state == BadBleStateRunning) { + if(model->anim_frame == 0) { + canvas_draw_icon(canvas, 4, 23, &I_EviSmile1_18x21); + } else { + canvas_draw_icon(canvas, 4, 23, &I_EviSmile2_18x21); + } + canvas_set_font(canvas, FontBigNumbers); + furi_string_printf( + disp_str, "%u", ((model->state.line_cur - 1) * 100) / model->state.line_nb); + canvas_draw_str_aligned( + canvas, 114, 40, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); + furi_string_reset(disp_str); + canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14); + } else if(model->state.state == BadBleStateDone) { + canvas_draw_icon(canvas, 4, 23, &I_EviSmile1_18x21); + canvas_set_font(canvas, FontBigNumbers); + canvas_draw_str_aligned(canvas, 114, 40, AlignRight, AlignBottom, "100"); + furi_string_reset(disp_str); + canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14); + } else if(model->state.state == BadBleStateDelay) { + if(model->anim_frame == 0) { + canvas_draw_icon(canvas, 4, 23, &I_EviWaiting1_18x21); + } else { + canvas_draw_icon(canvas, 4, 23, &I_EviWaiting2_18x21); + } + canvas_set_font(canvas, FontBigNumbers); + furi_string_printf( + disp_str, "%u", ((model->state.line_cur - 1) * 100) / model->state.line_nb); + canvas_draw_str_aligned( + canvas, 114, 40, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); + furi_string_reset(disp_str); + canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14); + canvas_set_font(canvas, FontSecondary); + furi_string_printf(disp_str, "delay %lus", model->state.delay_remain); + canvas_draw_str_aligned( + canvas, 127, 50, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); + furi_string_reset(disp_str); + } else { + canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18); + } + + furi_string_free(disp_str); + free(settings); +} + +static bool bad_ble_input_callback(InputEvent* event, void* context) { + furi_assert(context); + BadBle* bad_ble = context; + bool consumed = false; + + if(event->type == InputTypeShort) { + if((event->key == InputKeyLeft) || (event->key == InputKeyOk)) { + consumed = true; + furi_assert(bad_ble->callback); + bad_ble->callback(event->key, bad_ble->context); + } + } + + return consumed; +} + +BadBle* bad_ble_alloc() { + BadBle* bad_ble = malloc(sizeof(BadBle)); + + bad_ble->view = view_alloc(); + view_allocate_model(bad_ble->view, ViewModelTypeLocking, sizeof(BadBleModel)); + view_set_context(bad_ble->view, bad_ble); + view_set_draw_callback(bad_ble->view, bad_ble_draw_callback); + view_set_input_callback(bad_ble->view, bad_ble_input_callback); + + return bad_ble; +} + +void bad_ble_free(BadBle* bad_ble) { + furi_assert(bad_ble); + view_free(bad_ble->view); + free(bad_ble); +} + +View* bad_ble_get_view(BadBle* bad_ble) { + furi_assert(bad_ble); + return bad_ble->view; +} + +void bad_ble_set_button_callback(BadBle* bad_ble, BadBleButtonCallback callback, void* context) { + furi_assert(bad_ble); + furi_assert(callback); + with_view_model( + bad_ble->view, + BadBleModel * model, + { + UNUSED(model); + bad_ble->callback = callback; + bad_ble->context = context; + }, + true); +} + +void bad_ble_set_file_name(BadBle* bad_ble, const char* name) { + furi_assert(name); + with_view_model( + bad_ble->view, + BadBleModel * model, + { strlcpy(model->file_name, name, MAX_NAME_LEN); }, + true); +} + +void bad_ble_set_layout(BadBle* bad_ble, const char* layout) { + furi_assert(layout); + with_view_model( + bad_ble->view, + BadBleModel * model, + { strlcpy(model->layout, layout, MAX_NAME_LEN); }, + true); +} + +void bad_ble_set_state(BadBle* bad_ble, BadBleState* st) { + furi_assert(st); + with_view_model( + bad_ble->view, + BadBleModel * model, + { + memcpy(&(model->state), st, sizeof(BadBleState)); + model->anim_frame ^= 1; + }, + true); +} diff --git a/applications/main/bad_ble/views/bad_ble_view.h b/applications/main/bad_ble/views/bad_ble_view.h new file mode 100644 index 000000000..ccbd1d97b --- /dev/null +++ b/applications/main/bad_ble/views/bad_ble_view.h @@ -0,0 +1,21 @@ +#pragma once + +#include +#include "../bad_ble_script.h" + +typedef struct BadBle BadBle; +typedef void (*BadBleButtonCallback)(InputKey key, void* context); + +BadBle* bad_ble_alloc(); + +void bad_ble_free(BadBle* bad_ble); + +View* bad_ble_get_view(BadBle* bad_ble); + +void bad_ble_set_button_callback(BadBle* bad_ble, BadBleButtonCallback callback, void* context); + +void bad_ble_set_file_name(BadBle* bad_ble, const char* name); + +void bad_ble_set_layout(BadBle* bad_ble, const char* layout); + +void bad_ble_set_state(BadBle* bad_ble, BadBleState* st);