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

[FL-3134] BadUSB: Script interpreter refactoring (#2485)

* Script command and character tables
* Non-blocking stringdelay, docs update
* altchar/altstring fix
* Layout select UI fix
* Remove debug print

Co-authored-by: あく <alleteam@gmail.com>
Nikolay Minaylov 2 лет назад
Родитель
Сommit
0444a80f19

+ 1 - 1
applications/main/bad_usb/bad_usb_app_i.h

@@ -2,7 +2,7 @@
 
 #include "bad_usb_app.h"
 #include "scenes/bad_usb_scene.h"
-#include "bad_usb_script.h"
+#include "helpers/ducky_script.h"
 
 #include <gui/gui.h>
 #include <assets_icons.h>

+ 184 - 144
applications/main/bad_usb/bad_usb_script.c → applications/main/bad_usb/helpers/ducky_script.c

@@ -5,17 +5,13 @@
 #include <lib/toolbox/args.h>
 #include <furi_hal_usb_hid.h>
 #include <storage/storage.h>
-#include "bad_usb_script.h"
-#include "mnemonic.h"
+#include "ducky_script.h"
+#include "ducky_script_i.h"
 #include <dolphin/dolphin.h>
 
 #define TAG "BadUSB"
 #define WORKER_TAG TAG "Worker"
 
-#define SCRIPT_STATE_ERROR (-1)
-#define SCRIPT_STATE_END (-2)
-#define SCRIPT_STATE_NEXT_LINE (-3)
-
 #define BADUSB_ASCII_TO_KEY(script, x) \
     (((uint8_t)x < 128) ? (script->layout[(uint8_t)x]) : HID_KEYBOARD_NONE)
 
@@ -26,87 +22,20 @@ typedef enum {
     WorkerEvtDisconnect = (1 << 3),
 } WorkerEvtFlags;
 
-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},
-    {"GUI-CTRL", KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_CTRL},
-
-    {"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_FORWARD},
-    {"BACKSPACE", HID_KEYBOARD_DELETE},
-    {"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_stringln[] = {"STRINGLN "};
-static const char ducky_cmd_defdelay_1[] = {"DEFAULT_DELAY "};
-static const char ducky_cmd_defdelay_2[] = {"DEFAULTDELAY "};
-static const char ducky_cmd_stringdelay_1[] = {"STRINGDELAY "};
-static const char ducky_cmd_stringdelay_2[] = {"STRING_DELAY "};
-static const char ducky_cmd_repeat[] = {"REPEAT "};
-static const char ducky_cmd_sysrq[] = {"SYSRQ "};
-static const char ducky_cmd_hold[] = {"HOLD "};
-static const char ducky_cmd_release[] = {"RELEASE "};
-
-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 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,
+};
 
 uint32_t ducky_get_command_len(const char* line) {
     uint32_t len = strlen(line);
@@ -121,76 +50,150 @@ bool ducky_is_line_end(const char chr) {
 }
 
 uint16_t ducky_get_keycode(BadUsbScript* bad_usb, 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;
-        }
+    uint16_t keycode = ducky_get_keycode_by_name(param);
+    if(keycode != HID_KEYBOARD_NONE) {
+        return keycode;
     }
+
     if((accept_chars) && (strlen(param) > 0)) {
         return (BADUSB_ASCII_TO_KEY(bad_usb, param[0]) & 0xFF);
     }
     return 0;
 }
 
-static int32_t
-    ducky_parse_line(BadUsbScript* bad_usb, FuriString* line, char* error, size_t error_len) {
+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;
+}
+
+void ducky_numlock_on() {
+    if((furi_hal_hid_get_led_state() & HID_KB_LED_NUM) == 0) {
+        furi_hal_hid_kb_press(HID_KEYBOARD_LOCK_NUM_LOCK);
+        furi_hal_hid_kb_release(HID_KEYBOARD_LOCK_NUM_LOCK);
+    }
+}
+bool ducky_numpad_press(const char num) {
+    if((num < '0') || (num > '9')) return false;
+
+    uint16_t key = numpad_keys[num - '0'];
+    furi_hal_hid_kb_press(key);
+    furi_hal_hid_kb_release(key);
+
+    return true;
+}
+
+bool ducky_altchar(const char* charcode) {
+    uint8_t i = 0;
+    bool state = false;
+
+    furi_hal_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_hid_kb_release(KEY_MOD_LEFT_ALT);
+    return state;
+}
+
+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;
+}
+
+int32_t ducky_error(BadUsbScript* bad_usb, const char* text, ...) {
+    va_list args;
+    va_start(args, text);
+
+    vsnprintf(bad_usb->st.error, sizeof(bad_usb->st.error), text, args);
+
+    va_end(args);
+    return SCRIPT_STATE_ERROR;
+}
+
+bool ducky_string(BadUsbScript* bad_usb, const char* param) {
+    uint32_t i = 0;
+
+    while(param[i] != '\0') {
+        if(param[i] != '\n') {
+            uint16_t keycode = BADUSB_ASCII_TO_KEY(bad_usb, param[i]);
+            if(keycode != HID_KEYBOARD_NONE) {
+                furi_hal_hid_kb_press(keycode);
+                furi_hal_hid_kb_release(keycode);
+            }
+        } else {
+            furi_hal_hid_kb_press(HID_KEYBOARD_RETURN);
+            furi_hal_hid_kb_release(HID_KEYBOARD_RETURN);
+        }
+        i++;
+    }
+    bad_usb->stringdelay = 0;
+    return true;
+}
+
+static bool ducky_string_next(BadUsbScript* bad_usb) {
+    if(bad_usb->string_print_pos >= furi_string_size(bad_usb->string_print)) {
+        return true;
+    }
+
+    char print_char = furi_string_get_char(bad_usb->string_print, bad_usb->string_print_pos);
+
+    if(print_char != '\n') {
+        uint16_t keycode = BADUSB_ASCII_TO_KEY(bad_usb, print_char);
+        if(keycode != HID_KEYBOARD_NONE) {
+            furi_hal_hid_kb_press(keycode);
+            furi_hal_hid_kb_release(keycode);
+        }
+    } else {
+        furi_hal_hid_kb_press(HID_KEYBOARD_RETURN);
+        furi_hal_hid_kb_release(HID_KEYBOARD_RETURN);
+    }
+
+    bad_usb->string_print_pos++;
+
+    return false;
+}
+
+static int32_t ducky_parse_line(BadUsbScript* bad_usb, FuriString* line) {
     uint32_t line_len = furi_string_size(line);
     const char* line_tmp = furi_string_get_cstr(line);
-    const char* ducky_cmd_table[] = {
-        ducky_cmd_comment,
-        ducky_cmd_id,
-        ducky_cmd_delay,
-        ducky_cmd_string,
-        ducky_cmd_defdelay_1,
-        ducky_cmd_defdelay_2,
-        ducky_cmd_stringdelay_1,
-        ducky_cmd_stringdelay_2,
-        ducky_cmd_repeat,
-        ducky_cmd_sysrq,
-        ducky_cmd_altchar,
-        ducky_cmd_altstr_1,
-        ducky_cmd_altstr_2,
-        ducky_cmd_stringln,
-        ducky_cmd_hold,
-        ducky_cmd_release,
-        NULL};
-    int32_t (*fnc_ptr[])(BadUsbScript*, FuriString*, const char*, char*, size_t) = {
-        &ducky_fnc_noop,
-        &ducky_fnc_noop,
-        &ducky_fnc_delay,
-        &ducky_fnc_string,
-        &ducky_fnc_defdelay,
-        &ducky_fnc_defdelay,
-        &ducky_fnc_strdelay,
-        &ducky_fnc_strdelay,
-        &ducky_fnc_repeat,
-        &ducky_fnc_sysrq,
-        &ducky_fnc_altchar,
-        &ducky_fnc_altstring,
-        &ducky_fnc_altstring,
-        &ducky_fnc_stringln,
-        &ducky_fnc_hold,
-        &ducky_fnc_release,
-        NULL};
 
     if(line_len == 0) {
         return SCRIPT_STATE_NEXT_LINE; // Skip empty lines
     }
     FURI_LOG_D(WORKER_TAG, "line:%s", line_tmp);
+
     // Ducky Lang Functions
-    for(size_t i = 0; ducky_cmd_table[i]; i++) {
-        if(strncmp(line_tmp, ducky_cmd_table[i], strlen(ducky_cmd_table[i])) == 0)
-            return ((fnc_ptr[i])(bad_usb, line, line_tmp, error, error_len));
+    int32_t cmd_result = ducky_execute_cmd(bad_usb, line_tmp);
+    if(cmd_result != SCRIPT_STATE_CMD_UNKNOWN) {
+        return cmd_result;
     }
+
     // Special keys + modifiers
     uint16_t key = ducky_get_keycode(bad_usb, 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;
+        return ducky_error(bad_usb, "No keycode defined for %s", line_tmp);
     }
     if((key & 0xFF00) != 0) {
         // It's a modifier key
@@ -199,7 +202,7 @@ static int32_t
     }
     furi_hal_hid_kb_press(key);
     furi_hal_hid_kb_release(key);
-    return (0);
+    return 0;
 }
 
 static bool ducky_set_usb_id(BadUsbScript* bad_usb, const char* line) {
@@ -277,8 +280,7 @@ static int32_t ducky_script_execute_next(BadUsbScript* bad_usb, File* script_fil
 
     if(bad_usb->repeat_cnt > 0) {
         bad_usb->repeat_cnt--;
-        delay_val = ducky_parse_line(
-            bad_usb, bad_usb->line_prev, bad_usb->st.error, sizeof(bad_usb->st.error));
+        delay_val = ducky_parse_line(bad_usb, bad_usb->line_prev);
         if(delay_val == SCRIPT_STATE_NEXT_LINE) { // Empty line
             return 0;
         } else if(delay_val < 0) { // Script error
@@ -313,10 +315,11 @@ static int32_t ducky_script_execute_next(BadUsbScript* bad_usb, File* script_fil
                 bad_usb->buf_len = bad_usb->buf_len + bad_usb->buf_start - (i + 1);
                 bad_usb->buf_start = i + 1;
                 furi_string_trim(bad_usb->line);
-                delay_val = ducky_parse_line(
-                    bad_usb, bad_usb->line, bad_usb->st.error, sizeof(bad_usb->st.error));
+                delay_val = ducky_parse_line(bad_usb, bad_usb->line);
                 if(delay_val == SCRIPT_STATE_NEXT_LINE) { // Empty line
                     return 0;
+                } else if(delay_val == SCRIPT_STATE_STRING_START) { // Print string with delays
+                    return delay_val;
                 } else if(delay_val < 0) {
                     bad_usb->st.error_line = bad_usb->st.line_cur;
                     FURI_LOG_E(WORKER_TAG, "Unknown command at line %u", bad_usb->st.line_cur);
@@ -339,10 +342,11 @@ static void bad_usb_hid_state_callback(bool state, void* context) {
     furi_assert(context);
     BadUsbScript* bad_usb = context;
 
-    if(state == true)
+    if(state == true) {
         furi_thread_flags_set(furi_thread_get_id(bad_usb->thread), WorkerEvtConnect);
-    else
+    } else {
         furi_thread_flags_set(furi_thread_get_id(bad_usb->thread), WorkerEvtDisconnect);
+    }
 }
 
 static uint32_t bad_usb_flags_get(uint32_t flags_mask, uint32_t timeout) {
@@ -368,6 +372,7 @@ static int32_t bad_usb_worker(void* context) {
     File* script_file = storage_file_alloc(furi_record_open(RECORD_STORAGE));
     bad_usb->line = furi_string_alloc();
     bad_usb->line_prev = furi_string_alloc();
+    bad_usb->string_print = furi_string_alloc();
 
     furi_hal_hid_set_state_callback(bad_usb_hid_state_callback, bad_usb);
 
@@ -420,6 +425,7 @@ static int32_t bad_usb_worker(void* context) {
                 bad_usb->defdelay = 0;
                 bad_usb->stringdelay = 0;
                 bad_usb->repeat_cnt = 0;
+                bad_usb->key_hold_nb = 0;
                 bad_usb->file_end = false;
                 storage_file_seek(script_file, 0, true);
                 worker_state = BadUsbStateRunning;
@@ -492,12 +498,17 @@ static int32_t bad_usb_worker(void* context) {
                     delay_val = 0;
                     worker_state = BadUsbStateScriptError;
                     bad_usb->st.state = worker_state;
+                    furi_hal_hid_kb_release_all();
                 } else if(delay_val == SCRIPT_STATE_END) { // End of script
                     delay_val = 0;
                     worker_state = BadUsbStateIdle;
                     bad_usb->st.state = BadUsbStateDone;
                     furi_hal_hid_kb_release_all();
                     continue;
+                } else if(delay_val == SCRIPT_STATE_STRING_START) { // Start printing string with delays
+                    delay_val = bad_usb->defdelay;
+                    bad_usb->string_print_pos = 0;
+                    worker_state = BadUsbStateStringDelay;
                 } else if(delay_val > 1000) {
                     bad_usb->st.state = BadUsbStateDelay; // Show long delays
                     bad_usb->st.delay_remain = delay_val / 1000;
@@ -505,7 +516,35 @@ static int32_t bad_usb_worker(void* context) {
             } else {
                 furi_check((flags & FuriFlagError) == 0);
             }
+        } else if(worker_state == BadUsbStateStringDelay) { // State: print string with delays
+            uint32_t flags = furi_thread_flags_wait(
+                WorkerEvtEnd | WorkerEvtToggle | WorkerEvtDisconnect,
+                FuriFlagWaitAny,
+                bad_usb->stringdelay);
 
+            if(!(flags & FuriFlagError)) {
+                if(flags & WorkerEvtEnd) {
+                    break;
+                } else if(flags & WorkerEvtToggle) {
+                    worker_state = BadUsbStateIdle; // Stop executing script
+                    furi_hal_hid_kb_release_all();
+                } else if(flags & WorkerEvtDisconnect) {
+                    worker_state = BadUsbStateNotConnected; // USB disconnected
+                    furi_hal_hid_kb_release_all();
+                }
+                bad_usb->st.state = worker_state;
+                continue;
+            } else if(
+                (flags == (unsigned)FuriFlagErrorTimeout) ||
+                (flags == (unsigned)FuriFlagErrorResource)) {
+                bool string_end = ducky_string_next(bad_usb);
+                if(string_end) {
+                    bad_usb->stringdelay = 0;
+                    worker_state = BadUsbStateRunning;
+                }
+            } else {
+                furi_check((flags & FuriFlagError) == 0);
+            }
         } else if(
             (worker_state == BadUsbStateFileError) ||
             (worker_state == BadUsbStateScriptError)) { // State: error
@@ -524,6 +563,7 @@ static int32_t bad_usb_worker(void* context) {
     storage_file_free(script_file);
     furi_string_free(bad_usb->line);
     furi_string_free(bad_usb->line_prev);
+    furi_string_free(bad_usb->string_print);
 
     FURI_LOG_I(WORKER_TAG, "End");
 

+ 2 - 25
applications/main/bad_usb/bad_usb_script.h → applications/main/bad_usb/helpers/ducky_script.h

@@ -7,8 +7,6 @@ extern "C" {
 #include <furi.h>
 #include <furi_hal.h>
 
-#define FILE_BUFFER_LEN 16
-
 typedef enum {
     BadUsbStateInit,
     BadUsbStateNotConnected,
@@ -16,6 +14,7 @@ typedef enum {
     BadUsbStateWillRun,
     BadUsbStateRunning,
     BadUsbStateDelay,
+    BadUsbStateStringDelay,
     BadUsbStateDone,
     BadUsbStateScriptError,
     BadUsbStateFileError,
@@ -30,23 +29,7 @@ typedef struct {
     char error[64];
 } BadUsbState;
 
-typedef struct BadUsbScript {
-    FuriHalUsbHidConfig hid_cfg;
-    BadUsbState st;
-    FuriString* file_path;
-    uint32_t defdelay;
-    uint16_t layout[128];
-    uint32_t stringdelay;
-    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;
-} BadUsbScript;
+typedef struct BadUsbScript BadUsbScript;
 
 BadUsbScript* bad_usb_script_open(FuriString* file_path);
 
@@ -62,12 +45,6 @@ void bad_usb_script_toggle(BadUsbScript* bad_usb);
 
 BadUsbState* bad_usb_script_get_state(BadUsbScript* bad_usb);
 
-uint16_t ducky_get_keycode(BadUsbScript* bad_usb, const char* param, bool accept_chars);
-
-uint32_t ducky_get_command_len(const char* line);
-
-bool ducky_is_line_end(const char chr);
-
 #ifdef __cplusplus
 }
 #endif

+ 177 - 0
applications/main/bad_usb/helpers/ducky_script_commands.c

@@ -0,0 +1,177 @@
+#include <furi_hal.h>
+#include <furi_hal_usb_hid.h>
+#include "ducky_script.h"
+#include "ducky_script_i.h"
+
+typedef int32_t (*DuckyCmdCallback)(BadUsbScript* bad_usb, const char* line, int32_t param);
+
+typedef struct {
+    char* name;
+    DuckyCmdCallback callback;
+    int32_t param;
+} DuckyCmd;
+
+static int32_t ducky_fnc_delay(BadUsbScript* bad_usb, const char* line, int32_t param) {
+    UNUSED(param);
+
+    line = &line[ducky_get_command_len(line) + 1];
+    uint32_t delay_val = 0;
+    bool state = ducky_get_number(line, &delay_val);
+    if((state) && (delay_val > 0)) {
+        return (int32_t)delay_val;
+    }
+
+    return ducky_error(bad_usb, "Invalid number %s", line);
+}
+
+static int32_t ducky_fnc_defdelay(BadUsbScript* bad_usb, const char* line, int32_t param) {
+    UNUSED(param);
+
+    line = &line[ducky_get_command_len(line) + 1];
+    bool state = ducky_get_number(line, &bad_usb->defdelay);
+    if(!state) {
+        return ducky_error(bad_usb, "Invalid number %s", line);
+    }
+    return 0;
+}
+
+static int32_t ducky_fnc_strdelay(BadUsbScript* bad_usb, const char* line, int32_t param) {
+    UNUSED(param);
+
+    line = &line[ducky_get_command_len(line) + 1];
+    bool state = ducky_get_number(line, &bad_usb->stringdelay);
+    if(!state) {
+        return ducky_error(bad_usb, "Invalid number %s", line);
+    }
+    return 0;
+}
+
+static int32_t ducky_fnc_string(BadUsbScript* bad_usb, const char* line, int32_t param) {
+    line = &line[ducky_get_command_len(line) + 1];
+    furi_string_set_str(bad_usb->string_print, line);
+    if(param == 1) {
+        furi_string_cat(bad_usb->string_print, "\n");
+    }
+
+    if(bad_usb->stringdelay == 0) { // stringdelay not set - run command immidiately
+        bool state = ducky_string(bad_usb, furi_string_get_cstr(bad_usb->string_print));
+        if(!state) {
+            return ducky_error(bad_usb, "Invalid string %s", line);
+        }
+    } else { // stringdelay is set - run command in thread to keep handling external events
+        return SCRIPT_STATE_STRING_START;
+    }
+
+    return 0;
+}
+
+static int32_t ducky_fnc_repeat(BadUsbScript* bad_usb, const char* line, int32_t param) {
+    UNUSED(param);
+
+    line = &line[ducky_get_command_len(line) + 1];
+    bool state = ducky_get_number(line, &bad_usb->repeat_cnt);
+    if((!state) || (bad_usb->repeat_cnt == 0)) {
+        return ducky_error(bad_usb, "Invalid number %s", line);
+    }
+    return 0;
+}
+
+static int32_t ducky_fnc_sysrq(BadUsbScript* bad_usb, const char* line, int32_t param) {
+    UNUSED(param);
+
+    line = &line[ducky_get_command_len(line) + 1];
+    uint16_t key = ducky_get_keycode(bad_usb, line, true);
+    furi_hal_hid_kb_press(KEY_MOD_LEFT_ALT | HID_KEYBOARD_PRINT_SCREEN);
+    furi_hal_hid_kb_press(key);
+    furi_hal_hid_kb_release_all();
+    return 0;
+}
+
+static int32_t ducky_fnc_altchar(BadUsbScript* bad_usb, const char* line, int32_t param) {
+    UNUSED(param);
+
+    line = &line[ducky_get_command_len(line) + 1];
+    ducky_numlock_on();
+    bool state = ducky_altchar(line);
+    if(!state) {
+        return ducky_error(bad_usb, "Invalid altchar %s", line);
+    }
+    return 0;
+}
+
+static int32_t ducky_fnc_altstring(BadUsbScript* bad_usb, const char* line, int32_t param) {
+    UNUSED(param);
+
+    line = &line[ducky_get_command_len(line) + 1];
+    ducky_numlock_on();
+    bool state = ducky_altstring(line);
+    if(!state) {
+        return ducky_error(bad_usb, "Invalid altstring %s", line);
+    }
+    return 0;
+}
+
+static int32_t ducky_fnc_hold(BadUsbScript* bad_usb, const char* line, int32_t param) {
+    UNUSED(param);
+
+    line = &line[ducky_get_command_len(line) + 1];
+    uint16_t key = ducky_get_keycode(bad_usb, line, true);
+    if(key == HID_KEYBOARD_NONE) {
+        return ducky_error(bad_usb, "No keycode defined for %s", line);
+    }
+    bad_usb->key_hold_nb++;
+    if(bad_usb->key_hold_nb > (HID_KB_MAX_KEYS - 1)) {
+        return ducky_error(bad_usb, "Too many keys are hold");
+    }
+    furi_hal_hid_kb_press(key);
+    return 0;
+}
+
+static int32_t ducky_fnc_release(BadUsbScript* bad_usb, const char* line, int32_t param) {
+    UNUSED(param);
+
+    line = &line[ducky_get_command_len(line) + 1];
+    uint16_t key = ducky_get_keycode(bad_usb, line, true);
+    if(key == HID_KEYBOARD_NONE) {
+        return ducky_error(bad_usb, "No keycode defined for %s", line);
+    }
+    if(bad_usb->key_hold_nb == 0) {
+        return ducky_error(bad_usb, "No keys are hold");
+    }
+    bad_usb->key_hold_nb--;
+    furi_hal_hid_kb_release(key);
+    return 0;
+}
+
+static const DuckyCmd ducky_commands[] = {
+    {"REM ", NULL, -1},
+    {"ID ", NULL, -1},
+    {"DELAY ", ducky_fnc_delay, -1},
+    {"STRING ", ducky_fnc_string, 0},
+    {"STRINGLN ", ducky_fnc_string, 1},
+    {"DEFAULT_DELAY ", ducky_fnc_defdelay, -1},
+    {"DEFAULTDELAY ", ducky_fnc_defdelay, -1},
+    {"STRINGDELAY ", ducky_fnc_strdelay, -1},
+    {"STRING_DELAY ", ducky_fnc_strdelay, -1},
+    {"REPEAT ", ducky_fnc_repeat, -1},
+    {"SYSRQ ", ducky_fnc_sysrq, -1},
+    {"ALTCHAR ", ducky_fnc_altchar, -1},
+    {"ALTSTRING ", ducky_fnc_altstring, -1},
+    {"ALTCODE ", ducky_fnc_altstring, -1},
+    {"HOLD ", ducky_fnc_hold, -1},
+    {"RELEASE ", ducky_fnc_release, -1},
+};
+
+int32_t ducky_execute_cmd(BadUsbScript* bad_usb, const char* line) {
+    for(size_t i = 0; i < COUNT_OF(ducky_commands); i++) {
+        if(strncmp(line, ducky_commands[i].name, strlen(ducky_commands[i].name)) == 0) {
+            if(ducky_commands[i].callback == NULL) {
+                return 0;
+            } else {
+                return ((ducky_commands[i].callback)(bad_usb, line, ducky_commands[i].param));
+            }
+        }
+    }
+
+    return SCRIPT_STATE_CMD_UNKNOWN;
+}

+ 69 - 0
applications/main/bad_usb/helpers/ducky_script_i.h

@@ -0,0 +1,69 @@
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <furi.h>
+#include <furi_hal.h>
+#include "ducky_script.h"
+
+#define SCRIPT_STATE_ERROR (-1)
+#define SCRIPT_STATE_END (-2)
+#define SCRIPT_STATE_NEXT_LINE (-3)
+#define SCRIPT_STATE_CMD_UNKNOWN (-4)
+#define SCRIPT_STATE_STRING_START (-5)
+
+#define FILE_BUFFER_LEN 16
+
+struct BadUsbScript {
+    FuriHalUsbHidConfig hid_cfg;
+    FuriThread* thread;
+    BadUsbState st;
+
+    FuriString* file_path;
+    uint8_t file_buf[FILE_BUFFER_LEN + 1];
+    uint8_t buf_start;
+    uint8_t buf_len;
+    bool file_end;
+
+    uint32_t defdelay;
+    uint32_t stringdelay;
+    uint16_t layout[128];
+
+    FuriString* line;
+    FuriString* line_prev;
+    uint32_t repeat_cnt;
+    uint8_t key_hold_nb;
+
+    FuriString* string_print;
+    size_t string_print_pos;
+};
+
+uint16_t ducky_get_keycode(BadUsbScript* bad_usb, const char* param, bool accept_chars);
+
+uint32_t ducky_get_command_len(const char* line);
+
+bool ducky_is_line_end(const char chr);
+
+uint16_t ducky_get_keycode_by_name(const char* param);
+
+bool ducky_get_number(const char* param, uint32_t* val);
+
+void ducky_numlock_on(void);
+
+bool ducky_numpad_press(const char num);
+
+bool ducky_altchar(const char* charcode);
+
+bool ducky_altstring(const char* param);
+
+bool ducky_string(BadUsbScript* bad_usb, const char* param);
+
+int32_t ducky_execute_cmd(BadUsbScript* bad_usb, const char* line);
+
+int32_t ducky_error(BadUsbScript* bad_usb, const char* text, ...);
+
+#ifdef __cplusplus
+}
+#endif

+ 79 - 0
applications/main/bad_usb/helpers/ducky_script_keycodes.c

@@ -0,0 +1,79 @@
+#include <furi_hal.h>
+#include <furi_hal_usb_hid.h>
+#include "ducky_script_i.h"
+
+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},
+    {"GUI-CTRL", KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_CTRL},
+
+    {"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_FORWARD},
+    {"BACKSPACE", HID_KEYBOARD_DELETE},
+    {"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},
+};
+
+uint16_t ducky_get_keycode_by_name(const char* param) {
+    for(size_t i = 0; i < COUNT_OF(ducky_keys); 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;
+        }
+    }
+
+    return HID_KEYBOARD_NONE;
+}

+ 0 - 327
applications/main/bad_usb/mnemonic.c

@@ -1,327 +0,0 @@
-#include <furi_hal.h>
-#include <furi_hal_usb_hid.h>
-#include "mnemonic.h"
-
-#define TAG "BadUSB"
-#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 BADUSB_ASCII_TO_KEY(script, x) \
-    (((uint8_t)x < 128) ? (script->layout[(uint8_t)x]) : HID_KEYBOARD_NONE)
-
-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,
-};
-
-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 void ducky_numlock_on() {
-    if((furi_hal_hid_get_led_state() & HID_KB_LED_NUM) == 0) {
-        furi_hal_hid_kb_press(HID_KEYBOARD_LOCK_NUM_LOCK);
-        furi_hal_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'];
-    furi_hal_hid_kb_press(key);
-    furi_hal_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);
-
-    furi_hal_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_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(BadUsbScript* bad_usb, const char* param) {
-    uint32_t i = 0;
-
-    while(param[i] != '\0') {
-        uint16_t keycode = BADUSB_ASCII_TO_KEY(bad_usb, param[i]);
-        if(keycode != HID_KEYBOARD_NONE) {
-            furi_hal_hid_kb_press(keycode);
-            furi_hal_hid_kb_release(keycode);
-            if(bad_usb->stringdelay > 0) {
-                furi_delay_ms(bad_usb->stringdelay);
-            }
-        }
-        i++;
-    }
-    bad_usb->stringdelay = 0;
-    return true;
-}
-
-int32_t ducky_fnc_noop(
-    BadUsbScript* bad_usb,
-    FuriString* line,
-    const char* line_tmp,
-    char* error,
-    size_t error_len) {
-    (void)bad_usb;
-    (void)line;
-    (void)line_tmp;
-    (void)error;
-    (void)error_len;
-    return (0);
-}
-
-int32_t ducky_fnc_delay(
-    BadUsbScript* bad_usb,
-    FuriString* line,
-    const char* line_tmp,
-    char* error,
-    size_t error_len) {
-    bool state = false;
-    (void)bad_usb;
-    (void)line;
-
-    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;
-}
-
-int32_t ducky_fnc_defdelay(
-    BadUsbScript* bad_usb,
-    FuriString* line,
-    const char* line_tmp,
-    char* error,
-    size_t error_len) {
-    bool state = false;
-    (void)line;
-
-    line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
-    state = ducky_get_number(line_tmp, &bad_usb->defdelay);
-    if(!state && error != NULL) {
-        snprintf(error, error_len, "Invalid number %s", line_tmp);
-    }
-    return (state) ? (0) : SCRIPT_STATE_ERROR;
-}
-
-int32_t ducky_fnc_strdelay(
-    BadUsbScript* bad_usb,
-    FuriString* line,
-    const char* line_tmp,
-    char* error,
-    size_t error_len) {
-    bool state = false;
-    (void)line;
-
-    line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
-    state = ducky_get_number(line_tmp, &bad_usb->stringdelay);
-    if((state) && (bad_usb->stringdelay > 0)) {
-        return state;
-    }
-    if(error != NULL) {
-        snprintf(error, error_len, "Invalid number %s", line_tmp);
-    }
-    return SCRIPT_STATE_ERROR;
-}
-
-int32_t ducky_fnc_string(
-    BadUsbScript* bad_usb,
-    FuriString* line,
-    const char* line_tmp,
-    char* error,
-    size_t error_len) {
-    bool state = false;
-    (void)line;
-
-    line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
-    state = ducky_string(bad_usb, line_tmp);
-    if(!state && error != NULL) {
-        snprintf(error, error_len, "Invalid string %s", line_tmp);
-    }
-    return (state) ? (0) : SCRIPT_STATE_ERROR;
-}
-
-int32_t ducky_fnc_repeat(
-    BadUsbScript* bad_usb,
-    FuriString* line,
-    const char* line_tmp,
-    char* error,
-    size_t error_len) {
-    bool state = false;
-    (void)line;
-
-    line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
-    state = ducky_get_number(line_tmp, &bad_usb->repeat_cnt);
-    if(!state && error != NULL) {
-        snprintf(error, error_len, "Invalid number %s", line_tmp);
-    }
-    return (state) ? (0) : SCRIPT_STATE_ERROR;
-}
-
-int32_t ducky_fnc_sysrq(
-    BadUsbScript* bad_usb,
-    FuriString* line,
-    const char* line_tmp,
-    char* error,
-    size_t error_len) {
-    (void)error;
-    (void)error_len;
-    (void)line;
-    line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
-    uint16_t key = ducky_get_keycode(bad_usb, line_tmp, true);
-    furi_hal_hid_kb_press(KEY_MOD_LEFT_ALT | HID_KEYBOARD_PRINT_SCREEN);
-    furi_hal_hid_kb_press(key);
-    furi_hal_hid_kb_release_all();
-    return (0);
-}
-
-int32_t ducky_fnc_altchar(
-    BadUsbScript* bad_usb,
-    FuriString* line,
-    const char* line_tmp,
-    char* error,
-    size_t error_len) {
-    bool state = false;
-    (void)bad_usb;
-    (void)line;
-
-    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;
-}
-
-int32_t ducky_fnc_altstring(
-    BadUsbScript* bad_usb,
-    FuriString* line,
-    const char* line_tmp,
-    char* error,
-    size_t error_len) {
-    bool state = false;
-    (void)bad_usb;
-    (void)line;
-
-    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;
-}
-
-int32_t ducky_fnc_stringln(
-    BadUsbScript* bad_usb,
-    FuriString* line,
-    const char* line_tmp,
-    char* error,
-    size_t error_len) {
-    bool state = false;
-    (void)line;
-
-    line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
-    state = ducky_string(bad_usb, line_tmp);
-    if(!state && error != NULL) {
-        snprintf(error, error_len, "Invalid string %s", line_tmp);
-    }
-    furi_hal_hid_kb_press(HID_KEYBOARD_RETURN);
-    furi_hal_hid_kb_release(HID_KEYBOARD_RETURN);
-    return (state) ? (0) : SCRIPT_STATE_ERROR;
-}
-
-int32_t ducky_fnc_hold(
-    BadUsbScript* bad_usb,
-    FuriString* line,
-    const char* line_tmp,
-    char* error,
-    size_t error_len) {
-    (void)line;
-    line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
-    uint16_t key = ducky_get_keycode(bad_usb, line_tmp, true);
-    if(key == HID_KEYBOARD_NONE) {
-        if(error != NULL) {
-            snprintf(error, error_len, "No keycode defined for %s", line_tmp);
-        }
-        return SCRIPT_STATE_ERROR;
-    }
-    furi_hal_hid_kb_press(key);
-    return (0);
-}
-
-int32_t ducky_fnc_release(
-    BadUsbScript* bad_usb,
-    FuriString* line,
-    const char* line_tmp,
-    char* error,
-    size_t error_len) {
-    (void)line;
-    line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
-    uint16_t key = ducky_get_keycode(bad_usb, line_tmp, true);
-    if(key == HID_KEYBOARD_NONE) {
-        if(error != NULL) {
-            snprintf(error, error_len, "No keycode defined for %s", line_tmp);
-        }
-        return SCRIPT_STATE_ERROR;
-    }
-    furi_hal_hid_kb_release(key);
-    return (0);
-}

+ 0 - 96
applications/main/bad_usb/mnemonic.h

@@ -1,96 +0,0 @@
-#pragma once
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#include "bad_usb_script.h"
-
-// A no opperation function
-int32_t ducky_fnc_noop(
-    BadUsbScript* bad_usb,
-    FuriString* line,
-    const char* line_tmp,
-    char* error,
-    size_t error_len);
-// DELAY
-int32_t ducky_fnc_delay(
-    BadUsbScript* bad_usb,
-    FuriString* line,
-    const char* line_tmp,
-    char* error,
-    size_t error_len);
-// DEFAULTDELAY
-int32_t ducky_fnc_defdelay(
-    BadUsbScript* bad_usb,
-    FuriString* line,
-    const char* line_tmp,
-    char* error,
-    size_t error_len);
-// STRINGDELAY
-int32_t ducky_fnc_strdelay(
-    BadUsbScript* bad_usb,
-    FuriString* line,
-    const char* line_tmp,
-    char* error,
-    size_t error_len);
-// STRING
-int32_t ducky_fnc_string(
-    BadUsbScript* bad_usb,
-    FuriString* line,
-    const char* line_tmp,
-    char* error,
-    size_t error_len);
-// STRINGLN
-int32_t ducky_fnc_stringln(
-    BadUsbScript* bad_usb,
-    FuriString* line,
-    const char* line_tmp,
-    char* error,
-    size_t error_len);
-// REPEAT
-int32_t ducky_fnc_repeat(
-    BadUsbScript* bad_usb,
-    FuriString* line,
-    const char* line_tmp,
-    char* error,
-    size_t error_len);
-// SYSRQ
-int32_t ducky_fnc_sysrq(
-    BadUsbScript* bad_usb,
-    FuriString* line,
-    const char* line_tmp,
-    char* error,
-    size_t error_len);
-// ALTCHAR
-int32_t ducky_fnc_altchar(
-    BadUsbScript* bad_usb,
-    FuriString* line,
-    const char* line_tmp,
-    char* error,
-    size_t error_len);
-// ALTSTRING
-int32_t ducky_fnc_altstring(
-    BadUsbScript* bad_usb,
-    FuriString* line,
-    const char* line_tmp,
-    char* error,
-    size_t error_len);
-// HOLD
-int32_t ducky_fnc_hold(
-    BadUsbScript* bad_usb,
-    FuriString* line,
-    const char* line_tmp,
-    char* error,
-    size_t error_len);
-// RELEASE
-int32_t ducky_fnc_release(
-    BadUsbScript* bad_usb,
-    FuriString* line,
-    const char* line_tmp,
-    char* error,
-    size_t error_len);
-
-#ifdef __cplusplus
-}
-#endif

+ 1 - 1
applications/main/bad_usb/scenes/bad_usb_scene_config.c

@@ -17,7 +17,7 @@ void bad_usb_scene_config_on_enter(void* context) {
 
     submenu_add_item(
         submenu,
-        "Keyboard Layout",
+        "Keyboard Layout (global)",
         SubmenuIndexKeyboardLayout,
         bad_usb_scene_config_submenu_callback,
         bad_usb);

+ 3 - 1
applications/main/bad_usb/scenes/bad_usb_scene_config_layout.c

@@ -33,8 +33,10 @@ void bad_usb_scene_config_layout_on_enter(void* context) {
 
     if(bad_usb_layout_select(bad_usb)) {
         bad_usb_script_set_keyboard_layout(bad_usb->bad_usb_script, bad_usb->keyboard_layout);
+        scene_manager_search_and_switch_to_previous_scene(bad_usb->scene_manager, BadUsbSceneWork);
+    } else {
+        scene_manager_previous_scene(bad_usb->scene_manager);
     }
-    scene_manager_previous_scene(bad_usb->scene_manager);
 }
 
 bool bad_usb_scene_config_layout_on_event(void* context, SceneManagerEvent event) {

+ 1 - 1
applications/main/bad_usb/scenes/bad_usb_scene_work.c

@@ -1,4 +1,4 @@
-#include "../bad_usb_script.h"
+#include "../helpers/ducky_script.h"
 #include "../bad_usb_app_i.h"
 #include "../views/bad_usb_view.h"
 #include <furi_hal.h>

+ 7 - 2
applications/main/bad_usb/views/bad_usb_view.c

@@ -1,5 +1,5 @@
 #include "bad_usb_view.h"
-#include "../bad_usb_script.h"
+#include "../helpers/ducky_script.h"
 #include <toolbox/path.h>
 #include <gui/elements.h>
 #include <assets_icons.h>
@@ -79,7 +79,12 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) {
         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);
+
+        furi_string_set_str(disp_str, model->state.error);
+        elements_string_fit_width(canvas, disp_str, canvas_width(canvas));
+        canvas_draw_str_aligned(
+            canvas, 127, 56, AlignRight, AlignBottom, furi_string_get_cstr(disp_str));
+        furi_string_reset(disp_str);
     } else if(model->state.state == BadUsbStateIdle) {
         canvas_draw_icon(canvas, 4, 26, &I_Smile_18x18);
         canvas_set_font(canvas, FontBigNumbers);

+ 1 - 1
applications/main/bad_usb/views/bad_usb_view.h

@@ -1,7 +1,7 @@
 #pragma once
 
 #include <gui/view.h>
-#include "../bad_usb_script.h"
+#include "../helpers/ducky_script.h"
 
 typedef struct BadUsb BadUsb;
 typedef void (*BadUsbButtonCallback)(InputKey key, void* context);

+ 1 - 1
applications/services/gui/modules/submenu.c

@@ -98,7 +98,7 @@ static void submenu_view_draw_callback(Canvas* canvas, void* _model) {
 
             FuriString* disp_str;
             disp_str = furi_string_alloc_set(SubmenuItemArray_cref(it)->label);
-            elements_string_fit_width(canvas, disp_str, item_width - 20);
+            elements_string_fit_width(canvas, disp_str, item_width - (6 * 2));
 
             canvas_draw_str(
                 canvas,

+ 46 - 36
documentation/file_formats/BadUsbScriptFormat.md

@@ -11,18 +11,18 @@ BadUsb app can execute only text scrips from `.txt` files, no compilation is req
 ## Comment line
 
 Just a single comment line. The interpreter will ignore all text after the REM command.
-|Command|Parameters|Notes|
-|-|-|-|
-|REM|Comment text||
+| Command | Parameters   | Notes |
+| ------- | ------------ | ----- |
+| REM     | Comment text |       |
 
 ## Delay
 
 Pause script execution by a defined time.
-|Command|Parameters|Notes|
-|-|-|-|
-|DELAY|Delay value in ms|Single delay|
-|DEFAULT_DELAY|Delay value in ms|Add delay before every next command|
-|DEFAULTDELAY|Delay value in ms|Same as DEFAULT_DELAY|
+| Command       | Parameters        | Notes                               |
+| ------------- | ----------------- | ----------------------------------- |
+| DELAY         | Delay value in ms | Single delay                        |
+| DEFAULT_DELAY | Delay value in ms | Add delay before every next command |
+| DEFAULTDELAY  | Delay value in ms | Same as DEFAULT_DELAY               |
 
 ## Special keys
 
@@ -56,32 +56,42 @@ Pause script execution by a defined time.
 ## Modifier keys
 
 Can be combined with a special key command or a single character.
-|Command|Notes|
-|-|-|
-|CONTROL / CTRL||
-|SHIFT||
-|ALT||
-|WINDOWS / GUI||
-|CTRL-ALT|CTRL+ALT|
-|CTRL-SHIFT|CTRL+SHIFT|
-|ALT-SHIFT|ALT+SHIFT|
-|ALT-GUI|ALT+WIN|
-|GUI-SHIFT|WIN+SHIFT|
-|GUI-CTRL|WIN+CTRL|
+| Command        | Notes      |
+| -------------- | ---------- |
+| CONTROL / CTRL |            |
+| SHIFT          |            |
+| ALT            |            |
+| WINDOWS / GUI  |            |
+| CTRL-ALT       | CTRL+ALT   |
+| CTRL-SHIFT     | CTRL+SHIFT |
+| ALT-SHIFT      | ALT+SHIFT  |
+| ALT-GUI        | ALT+WIN    |
+| GUI-SHIFT      | WIN+SHIFT  |
+| GUI-CTRL       | WIN+CTRL   |
+
+## Key hold and release
+
+Up to 5 keys can be hold simultaneously.
+| Command | Parameters                      | Notes                                     |
+| ------- | ------------------------------- | ----------------------------------------- |
+| HOLD    | Special key or single character | Press and hold key untill RELEASE command |
+| RELEASE | Special key or single character | Release key                               |
+
 
 ## String
 
-| Command | Parameters  | Notes             |
-| ------- | ----------- | ----------------- |
-| STRING  | Text string | Print text string |
+| Command  | Parameters  | Notes                                      |
+| -------  | ----------- | -----------------                          |
+| STRING   | Text string | Print text string                          |
+| STRINGLN | Text string | Print text string and press enter after it |
 
 ## String delay
 
 Delay between keypresses.
-|Command|Parameters|Notes|
-|-|-|-|
-|STRING_DELAY|Delay value in ms|Applied once to next appearing string|
-|STRINGDELAY|Delay value in ms|Same as STRING_DELAY|
+| Command      | Parameters        | Notes                                         |
+| ------------ | ----------------- | --------------------------------------------- |
+| STRING_DELAY | Delay value in ms | Applied once to next appearing STRING command |
+| STRINGDELAY  | Delay value in ms | Same as STRING_DELAY                          |
 
 ## Repeat
 
@@ -91,19 +101,19 @@ Delay between keypresses.
 
 ## ALT+Numpad input
 
-On Windows and some Linux systems, you can print characters by pressing `ALT` key and entering its code on Numpad.
-|Command|Parameters|Notes|
-|-|-|-|
-|ALTCHAR|Character code|Print single character|
-|ALTSTRING|Text string|Print text string using ALT+Numpad method|
-|ALTCODE|Text string|Same as ALTSTRING, presents in some Duckyscript implementations|
+On Windows and some Linux systems, you can print characters by holding `ALT` key and entering its code on Numpad.
+| Command   | Parameters     | Notes                                                           |
+| --------- | -------------- | --------------------------------------------------------------- |
+| ALTCHAR   | Character code | Print single character                                          |
+| ALTSTRING | Text string    | Print text string using ALT+Numpad method                       |
+| ALTCODE   | Text string    | Same as ALTSTRING, presents in some Duckyscript implementations |
 
 ## SysRq
 
 Send [SysRq command](https://en.wikipedia.org/wiki/Magic_SysRq_key)
-|Command|Parameters|Notes|
-|-|-|-|
-|SYSRQ|Single character||
+| Command | Parameters       | Notes |
+| ------- | ---------------- | ----- |
+| SYSRQ   | Single character |       |
 
 ## USB device ID
 

+ 0 - 3
firmware/targets/f7/furi_hal/furi_hal_usb_hid.c

@@ -11,9 +11,6 @@
 #define HID_EP_OUT 0x01
 #define HID_EP_SZ 0x10
 
-#define HID_KB_MAX_KEYS 6
-#define HID_CONSUMER_MAX_KEYS 2
-
 #define HID_INTERVAL 2
 
 #define HID_VID_DEFAULT 0x046D

+ 5 - 0
firmware/targets/furi_hal_include/furi_hal_usb_hid.h

@@ -9,6 +9,11 @@
 extern "C" {
 #endif
 
+/** Max number of simultaneously pressed keys (keyboard) */
+#define HID_KB_MAX_KEYS 6
+/** Max number of simultaneously pressed keys (consumer control) */
+#define HID_CONSUMER_MAX_KEYS 2
+
 #define HID_KEYBOARD_NONE 0x00
 
 /** HID keyboard modifier keys */