1
mirror of https://github.com/DarkFlippers/unleashed-firmware.git synced 2025-12-12 04:34:43 +04:00

merge ofw pr 4136 [ci skip]

BadUSB: Full USB/BLE parameter customization, UI improvements, and more
by Willy-JL
This commit is contained in:
MX
2025-04-06 04:56:08 +03:00
parent 324b8ddb95
commit 3745ae2241
31 changed files with 1841 additions and 206 deletions

View File

@@ -7,7 +7,7 @@ App(
icon="A_BadUsb_14",
order=70,
resources="resources",
fap_libs=["assets", "ble_profile"],
fap_libs=["assets"],
fap_icon="icon.png",
fap_category="USB",
)

View File

@@ -31,52 +31,123 @@ static void bad_usb_app_tick_event_callback(void* context) {
static void bad_usb_load_settings(BadUsbApp* app) {
Storage* storage = furi_record_open(RECORD_STORAGE);
FlipperFormat* fff = flipper_format_file_alloc(storage);
bool state = false;
bool loaded = false;
BadUsbHidConfig* hid_cfg = &app->user_hid_cfg;
FuriString* temp_str = furi_string_alloc();
uint32_t version = 0;
uint32_t interface = 0;
uint32_t temp_uint = 0;
if(flipper_format_file_open_existing(fff, BAD_USB_SETTINGS_PATH)) {
do {
if(!flipper_format_read_header(fff, temp_str, &version)) break;
if(!flipper_format_read_header(fff, temp_str, &temp_uint)) break;
if((strcmp(furi_string_get_cstr(temp_str), BAD_USB_SETTINGS_FILE_TYPE) != 0) ||
(version != BAD_USB_SETTINGS_VERSION))
(temp_uint != BAD_USB_SETTINGS_VERSION))
break;
if(!flipper_format_read_string(fff, "layout", temp_str)) break;
if(!flipper_format_read_uint32(fff, "interface", &interface, 1)) break;
if(interface > BadUsbHidInterfaceBle) break;
if(flipper_format_read_string(fff, "layout", temp_str)) {
furi_string_set(app->keyboard_layout, temp_str);
FileInfo layout_file_info;
FS_Error file_check_err = storage_common_stat(
storage, furi_string_get_cstr(app->keyboard_layout), &layout_file_info);
if((file_check_err != FSE_OK) || (layout_file_info.size != 256)) {
furi_string_set(app->keyboard_layout, BAD_USB_SETTINGS_DEFAULT_LAYOUT);
}
} else {
furi_string_set(app->keyboard_layout, BAD_USB_SETTINGS_DEFAULT_LAYOUT);
flipper_format_rewind(fff);
}
state = true;
if(!flipper_format_read_uint32(fff, "interface", &temp_uint, 1) ||
temp_uint >= BadUsbHidInterfaceMAX) {
temp_uint = BadUsbHidInterfaceUsb;
flipper_format_rewind(fff);
}
app->interface = temp_uint;
if(!flipper_format_read_bool(fff, "ble_bonding", &hid_cfg->ble.bonding, 1)) {
hid_cfg->ble.bonding = true;
flipper_format_rewind(fff);
}
if(!flipper_format_read_uint32(fff, "ble_pairing", &temp_uint, 1) ||
temp_uint >= GapPairingCount) {
temp_uint = GapPairingPinCodeVerifyYesNo;
flipper_format_rewind(fff);
}
hid_cfg->ble.pairing = temp_uint;
if(flipper_format_read_string(fff, "ble_name", temp_str)) {
strlcpy(
hid_cfg->ble.name, furi_string_get_cstr(temp_str), sizeof(hid_cfg->ble.name));
} else {
hid_cfg->ble.name[0] = '\0';
flipper_format_rewind(fff);
}
if(!flipper_format_read_hex(
fff, "ble_mac", hid_cfg->ble.mac, sizeof(hid_cfg->ble.mac))) {
memset(hid_cfg->ble.mac, 0, sizeof(hid_cfg->ble.mac));
flipper_format_rewind(fff);
}
if(flipper_format_read_string(fff, "usb_manuf", temp_str)) {
strlcpy(
hid_cfg->usb.manuf,
furi_string_get_cstr(temp_str),
sizeof(hid_cfg->usb.manuf));
} else {
hid_cfg->usb.manuf[0] = '\0';
flipper_format_rewind(fff);
}
if(flipper_format_read_string(fff, "usb_product", temp_str)) {
strlcpy(
hid_cfg->usb.product,
furi_string_get_cstr(temp_str),
sizeof(hid_cfg->usb.product));
} else {
hid_cfg->usb.product[0] = '\0';
flipper_format_rewind(fff);
}
if(!flipper_format_read_uint32(fff, "usb_vid", &hid_cfg->usb.vid, 1)) {
hid_cfg->usb.vid = 0;
flipper_format_rewind(fff);
}
if(!flipper_format_read_uint32(fff, "usb_pid", &hid_cfg->usb.pid, 1)) {
hid_cfg->usb.pid = 0;
flipper_format_rewind(fff);
}
loaded = true;
} while(0);
}
flipper_format_free(fff);
furi_record_close(RECORD_STORAGE);
if(state) {
furi_string_set(app->keyboard_layout, temp_str);
app->interface = interface;
Storage* fs_api = furi_record_open(RECORD_STORAGE);
FileInfo layout_file_info;
FS_Error file_check_err = storage_common_stat(
fs_api, furi_string_get_cstr(app->keyboard_layout), &layout_file_info);
furi_record_close(RECORD_STORAGE);
if((file_check_err != FSE_OK) || (layout_file_info.size != 256)) {
furi_string_set(app->keyboard_layout, BAD_USB_SETTINGS_DEFAULT_LAYOUT);
}
} else {
furi_string_set(app->keyboard_layout, BAD_USB_SETTINGS_DEFAULT_LAYOUT);
app->interface = BadUsbHidInterfaceUsb;
}
furi_string_free(temp_str);
flipper_format_free(fff);
furi_record_close(RECORD_STORAGE);
if(!loaded) {
furi_string_set(app->keyboard_layout, BAD_USB_SETTINGS_DEFAULT_LAYOUT);
app->interface = BadUsbHidInterfaceUsb;
hid_cfg->ble.name[0] = '\0';
memset(hid_cfg->ble.mac, 0, sizeof(hid_cfg->ble.mac));
hid_cfg->ble.bonding = true;
hid_cfg->ble.pairing = GapPairingPinCodeVerifyYesNo;
hid_cfg->usb.vid = 0;
hid_cfg->usb.pid = 0;
hid_cfg->usb.manuf[0] = '\0';
hid_cfg->usb.product[0] = '\0';
}
}
static void bad_usb_save_settings(BadUsbApp* app) {
Storage* storage = furi_record_open(RECORD_STORAGE);
FlipperFormat* fff = flipper_format_file_alloc(storage);
BadUsbHidConfig* hid_cfg = &app->user_hid_cfg;
uint32_t temp_uint = 0;
if(flipper_format_file_open_always(fff, BAD_USB_SETTINGS_PATH)) {
do {
@@ -84,9 +155,19 @@ static void bad_usb_save_settings(BadUsbApp* app) {
fff, BAD_USB_SETTINGS_FILE_TYPE, BAD_USB_SETTINGS_VERSION))
break;
if(!flipper_format_write_string(fff, "layout", app->keyboard_layout)) break;
uint32_t interface_id = app->interface;
if(!flipper_format_write_uint32(fff, "interface", (const uint32_t*)&interface_id, 1))
temp_uint = app->interface;
if(!flipper_format_write_uint32(fff, "interface", &temp_uint, 1)) break;
if(!flipper_format_write_bool(fff, "ble_bonding", &hid_cfg->ble.bonding, 1)) break;
temp_uint = hid_cfg->ble.pairing;
if(!flipper_format_write_uint32(fff, "ble_pairing", &temp_uint, 1)) break;
if(!flipper_format_write_string_cstr(fff, "ble_name", hid_cfg->ble.name)) break;
if(!flipper_format_write_hex(
fff, "ble_mac", (uint8_t*)&hid_cfg->ble.mac, sizeof(hid_cfg->ble.mac)))
break;
if(!flipper_format_write_string_cstr(fff, "usb_manuf", hid_cfg->usb.manuf)) break;
if(!flipper_format_write_string_cstr(fff, "usb_product", hid_cfg->usb.product)) break;
if(!flipper_format_write_uint32(fff, "usb_vid", &hid_cfg->usb.vid, 1)) break;
if(!flipper_format_write_uint32(fff, "usb_pid", &hid_cfg->usb.pid, 1)) break;
} while(0);
}
@@ -121,7 +202,7 @@ BadUsbApp* bad_usb_app_alloc(char* arg) {
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
view_dispatcher_set_tick_event_callback(
app->view_dispatcher, bad_usb_app_tick_event_callback, 500);
app->view_dispatcher, bad_usb_app_tick_event_callback, 250);
view_dispatcher_set_custom_event_callback(
app->view_dispatcher, bad_usb_app_custom_event_callback);
view_dispatcher_set_navigation_event_callback(
@@ -146,6 +227,14 @@ BadUsbApp* bad_usb_app_alloc(char* arg) {
view_dispatcher_add_view(
app->view_dispatcher, BadUsbAppViewWork, bad_usb_view_get_view(app->bad_usb_view));
app->text_input = text_input_alloc();
view_dispatcher_add_view(
app->view_dispatcher, BadUsbAppViewTextInput, text_input_get_view(app->text_input));
app->byte_input = byte_input_alloc();
view_dispatcher_add_view(
app->view_dispatcher, BadUsbAppViewByteInput, byte_input_get_view(app->byte_input));
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
if(furi_hal_usb_is_locked()) {
@@ -157,6 +246,7 @@ BadUsbApp* bad_usb_app_alloc(char* arg) {
furi_check(furi_hal_usb_set_config(NULL, NULL));
if(!furi_string_empty(app->file_path)) {
scene_manager_set_scene_state(app->scene_manager, BadUsbSceneWork, true);
scene_manager_next_scene(app->scene_manager, BadUsbSceneWork);
} else {
furi_string_set(app->file_path, BAD_USB_APP_BASE_FOLDER);
@@ -191,6 +281,14 @@ void bad_usb_app_free(BadUsbApp* app) {
view_dispatcher_remove_view(app->view_dispatcher, BadUsbAppViewConfig);
variable_item_list_free(app->var_item_list);
// Text Input
view_dispatcher_remove_view(app->view_dispatcher, BadUsbAppViewTextInput);
text_input_free(app->text_input);
// Byte Input
view_dispatcher_remove_view(app->view_dispatcher, BadUsbAppViewByteInput);
byte_input_free(app->byte_input);
// View dispatcher
view_dispatcher_free(app->view_dispatcher);
scene_manager_free(app->scene_manager);

View File

@@ -12,6 +12,8 @@
#include <dialogs/dialogs.h>
#include <notification/notification_messages.h>
#include <gui/modules/variable_item_list.h>
#include <gui/modules/text_input.h>
#include <gui/modules/byte_input.h>
#include <gui/modules/widget.h>
#include <gui/modules/popup.h>
#include "views/bad_usb_view.h"
@@ -36,6 +38,13 @@ struct BadUsbApp {
Widget* widget;
Popup* popup;
VariableItemList* var_item_list;
TextInput* text_input;
ByteInput* byte_input;
char ble_name_buf[FURI_HAL_BT_ADV_NAME_LENGTH];
uint8_t ble_mac_buf[GAP_MAC_ADDR_SIZE];
char usb_name_buf[HID_MANUF_PRODUCT_NAME_LEN];
uint16_t usb_vidpid_buf[2];
BadUsbAppError error;
FuriString* file_path;
@@ -44,6 +53,8 @@ struct BadUsbApp {
BadUsbScript* bad_usb_script;
BadUsbHidInterface interface;
BadUsbHidConfig user_hid_cfg;
BadUsbHidConfig script_hid_cfg;
FuriHalUsbInterface* usb_if_prev;
};
@@ -52,6 +63,8 @@ typedef enum {
BadUsbAppViewPopup,
BadUsbAppViewWork,
BadUsbAppViewConfig,
BadUsbAppViewByteInput,
BadUsbAppViewTextInput,
} BadUsbAppView;
void bad_usb_set_interface(BadUsbApp* app, BadUsbHidInterface interface);

View File

@@ -1,5 +1,5 @@
#include "bad_usb_hid.h"
#include <extra_profiles/hid_profile.h>
#include "ble_hid_profile.h"
#include <bt/bt_service/bt.h>
#include <storage/storage.h>
@@ -7,8 +7,14 @@
#define HID_BT_KEYS_STORAGE_NAME ".bt_hid.keys"
void* hid_usb_init(FuriHalUsbHidConfig* hid_cfg) {
furi_check(furi_hal_usb_set_config(&usb_hid, hid_cfg));
void hid_usb_adjust_config(BadUsbHidConfig* hid_cfg) {
if(hid_cfg->usb.vid == 0) hid_cfg->usb.vid = HID_VID_DEFAULT;
if(hid_cfg->usb.pid == 0) hid_cfg->usb.pid = HID_PID_DEFAULT;
}
void* hid_usb_init(BadUsbHidConfig* hid_cfg) {
hid_usb_adjust_config(hid_cfg);
furi_check(furi_hal_usb_set_config(&usb_hid, &hid_cfg->usb));
return NULL;
}
@@ -86,6 +92,7 @@ uint8_t hid_usb_get_led_state(void* inst) {
}
static const BadUsbHidApi hid_api_usb = {
.adjust_config = hid_usb_adjust_config,
.init = hid_usb_init,
.deinit = hid_usb_deinit,
.set_state_callback = hid_usb_set_state_callback,
@@ -111,11 +118,6 @@ typedef struct {
bool is_connected;
} BleHidInstance;
static const BleProfileHidParams ble_hid_params = {
.device_name_prefix = "BadUSB",
.mac_xor = 0x0002,
};
static void hid_ble_connection_status_callback(BtStatus status, void* context) {
furi_assert(context);
BleHidInstance* ble_hid = context;
@@ -125,8 +127,38 @@ static void hid_ble_connection_status_callback(BtStatus status, void* context) {
}
}
void* hid_ble_init(FuriHalUsbHidConfig* hid_cfg) {
UNUSED(hid_cfg);
void hid_ble_adjust_config(BadUsbHidConfig* hid_cfg) {
const uint8_t* normal_mac = furi_hal_version_get_ble_mac();
uint8_t empty_mac[GAP_MAC_ADDR_SIZE] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t default_mac[GAP_MAC_ADDR_SIZE] = {0x6c, 0x7a, 0xd8, 0xac, 0x57, 0x72}; // furi_hal_bt
if(memcmp(hid_cfg->ble.mac, empty_mac, sizeof(hid_cfg->ble.mac)) == 0 ||
memcmp(hid_cfg->ble.mac, normal_mac, sizeof(hid_cfg->ble.mac)) == 0 ||
memcmp(hid_cfg->ble.mac, default_mac, sizeof(hid_cfg->ble.mac)) == 0) {
// Derive badusb MAC from Flipper MAC
memcpy(hid_cfg->ble.mac, normal_mac, sizeof(hid_cfg->ble.mac));
hid_cfg->ble.mac[2]++;
uint16_t badusb_mac_xor = 0x0002;
hid_cfg->ble.mac[0] ^= badusb_mac_xor;
hid_cfg->ble.mac[1] ^= badusb_mac_xor >> 8;
}
if(hid_cfg->ble.name[0] == '\0') {
// Derive badusb name from Flipper name
const char* badusb_device_name_prefix = "BadUSB";
snprintf(
hid_cfg->ble.name,
sizeof(hid_cfg->ble.name),
"%s %s",
badusb_device_name_prefix,
furi_hal_version_get_name_ptr());
}
if(hid_cfg->ble.pairing >= GapPairingCount) {
hid_cfg->ble.pairing = GapPairingPinCodeVerifyYesNo;
}
}
void* hid_ble_init(BadUsbHidConfig* hid_cfg) {
BleHidInstance* ble_hid = malloc(sizeof(BleHidInstance));
ble_hid->bt = furi_record_open(RECORD_BT);
bt_disconnect(ble_hid->bt);
@@ -136,7 +168,8 @@ void* hid_ble_init(FuriHalUsbHidConfig* hid_cfg) {
bt_keys_storage_set_storage_path(ble_hid->bt, APP_DATA_PATH(HID_BT_KEYS_STORAGE_NAME));
ble_hid->profile = bt_profile_start(ble_hid->bt, ble_profile_hid, (void*)&ble_hid_params);
hid_ble_adjust_config(hid_cfg);
ble_hid->profile = bt_profile_start(ble_hid->bt, ble_profile_hid, &hid_cfg->ble);
furi_check(ble_hid->profile);
furi_hal_bt_start_advertising();
@@ -236,6 +269,7 @@ uint8_t hid_ble_get_led_state(void* inst) {
}
static const BadUsbHidApi hid_api_ble = {
.adjust_config = hid_ble_adjust_config,
.init = hid_ble_init,
.deinit = hid_ble_deinit,
.set_state_callback = hid_ble_set_state_callback,

View File

@@ -7,13 +7,22 @@ extern "C" {
#include <furi.h>
#include <furi_hal.h>
#include "ble_hid_profile.h"
typedef enum {
BadUsbHidInterfaceUsb,
BadUsbHidInterfaceBle,
BadUsbHidInterfaceMAX,
} BadUsbHidInterface;
typedef struct {
void* (*init)(FuriHalUsbHidConfig* hid_cfg);
BleProfileHidParams ble;
FuriHalUsbHidConfig usb;
} BadUsbHidConfig;
typedef struct {
void (*adjust_config)(BadUsbHidConfig* hid_cfg);
void* (*init)(BadUsbHidConfig* hid_cfg);
void (*deinit)(void* inst);
void (*set_state_callback)(void* inst, HidStateCallback cb, void* context);
bool (*is_connected)(void* inst);

View File

@@ -0,0 +1,429 @@
#include "ble_hid_profile.h"
// Based on <lib/ble_profile/extra_profiles/hid_profile.c>
#include <furi_hal_usb_hid.h>
#include <services/dev_info_service.h>
#include <services/battery_service.h>
#include "ble_hid_service.h"
#include <furi.h>
#include <usb_hid.h>
#include <ble/ble.h>
#define HID_INFO_BASE_USB_SPECIFICATION (0x0101)
#define HID_INFO_COUNTRY_CODE (0x00)
#define BLE_PROFILE_HID_INFO_FLAG_REMOTE_WAKE_MSK (0x01)
#define BLE_PROFILE_HID_INFO_FLAG_NORMALLY_CONNECTABLE_MSK (0x02)
#define BLE_PROFILE_HID_KB_MAX_KEYS (6)
#define BLE_PROFILE_CONSUMER_MAX_KEYS (1)
// Report ids cant be 0
enum HidReportId {
ReportIdKeyboard = 1,
ReportIdMouse = 2,
ReportIdConsumer = 3,
};
// Report numbers corresponded to the report id with an offset of 1
enum HidInputNumber {
ReportNumberKeyboard = 0,
ReportNumberMouse = 1,
ReportNumberConsumer = 2,
};
typedef struct {
uint8_t mods;
uint8_t reserved;
uint8_t key[BLE_PROFILE_HID_KB_MAX_KEYS];
} FURI_PACKED FuriHalBtHidKbReport;
typedef struct {
uint8_t btn;
int8_t x;
int8_t y;
int8_t wheel;
} FURI_PACKED FuriHalBtHidMouseReport;
typedef struct {
uint16_t key[BLE_PROFILE_CONSUMER_MAX_KEYS];
} FURI_PACKED FuriHalBtHidConsumerReport;
// keyboard+mouse+consumer hid report
static const uint8_t ble_profile_hid_report_map_data[] = {
// Keyboard Report
HID_USAGE_PAGE(HID_PAGE_DESKTOP),
HID_USAGE(HID_DESKTOP_KEYBOARD),
HID_COLLECTION(HID_APPLICATION_COLLECTION),
HID_REPORT_ID(ReportIdKeyboard),
HID_USAGE_PAGE(HID_DESKTOP_KEYPAD),
HID_USAGE_MINIMUM(HID_KEYBOARD_L_CTRL),
HID_USAGE_MAXIMUM(HID_KEYBOARD_R_GUI),
HID_LOGICAL_MINIMUM(0),
HID_LOGICAL_MAXIMUM(1),
HID_REPORT_SIZE(1),
HID_REPORT_COUNT(8),
HID_INPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE),
HID_REPORT_COUNT(1),
HID_REPORT_SIZE(8),
HID_INPUT(HID_IOF_CONSTANT | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE),
HID_USAGE_PAGE(HID_PAGE_LED),
HID_REPORT_COUNT(8),
HID_REPORT_SIZE(1),
HID_USAGE_MINIMUM(1),
HID_USAGE_MAXIMUM(8),
HID_OUTPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE),
HID_REPORT_COUNT(BLE_PROFILE_HID_KB_MAX_KEYS),
HID_REPORT_SIZE(8),
HID_LOGICAL_MINIMUM(0),
HID_LOGICAL_MAXIMUM(101),
HID_USAGE_PAGE(HID_DESKTOP_KEYPAD),
HID_USAGE_MINIMUM(0),
HID_USAGE_MAXIMUM(101),
HID_INPUT(HID_IOF_DATA | HID_IOF_ARRAY | HID_IOF_ABSOLUTE),
HID_END_COLLECTION,
// Mouse Report
HID_USAGE_PAGE(HID_PAGE_DESKTOP),
HID_USAGE(HID_DESKTOP_MOUSE),
HID_COLLECTION(HID_APPLICATION_COLLECTION),
HID_USAGE(HID_DESKTOP_POINTER),
HID_COLLECTION(HID_PHYSICAL_COLLECTION),
HID_REPORT_ID(ReportIdMouse),
HID_USAGE_PAGE(HID_PAGE_BUTTON),
HID_USAGE_MINIMUM(1),
HID_USAGE_MAXIMUM(3),
HID_LOGICAL_MINIMUM(0),
HID_LOGICAL_MAXIMUM(1),
HID_REPORT_COUNT(3),
HID_REPORT_SIZE(1),
HID_INPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE),
HID_REPORT_SIZE(1),
HID_REPORT_COUNT(5),
HID_INPUT(HID_IOF_CONSTANT | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE),
HID_USAGE_PAGE(HID_PAGE_DESKTOP),
HID_USAGE(HID_DESKTOP_X),
HID_USAGE(HID_DESKTOP_Y),
HID_USAGE(HID_DESKTOP_WHEEL),
HID_LOGICAL_MINIMUM(-127),
HID_LOGICAL_MAXIMUM(127),
HID_REPORT_SIZE(8),
HID_REPORT_COUNT(3),
HID_INPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_RELATIVE),
HID_END_COLLECTION,
HID_END_COLLECTION,
// Consumer Report
HID_USAGE_PAGE(HID_PAGE_CONSUMER),
HID_USAGE(HID_CONSUMER_CONTROL),
HID_COLLECTION(HID_APPLICATION_COLLECTION),
HID_REPORT_ID(ReportIdConsumer),
HID_LOGICAL_MINIMUM(0),
HID_RI_LOGICAL_MAXIMUM(16, 0x3FF),
HID_USAGE_MINIMUM(0),
HID_RI_USAGE_MAXIMUM(16, 0x3FF),
HID_REPORT_COUNT(BLE_PROFILE_CONSUMER_MAX_KEYS),
HID_REPORT_SIZE(16),
HID_INPUT(HID_IOF_DATA | HID_IOF_ARRAY | HID_IOF_ABSOLUTE),
HID_END_COLLECTION,
};
typedef struct {
FuriHalBleProfileBase base;
FuriHalBtHidKbReport* kb_report;
FuriHalBtHidMouseReport* mouse_report;
FuriHalBtHidConsumerReport* consumer_report;
BleServiceBattery* battery_svc;
BleServiceDevInfo* dev_info_svc;
BleServiceHid* hid_svc;
} BleProfileHid;
_Static_assert(offsetof(BleProfileHid, base) == 0, "Wrong layout");
static FuriHalBleProfileBase* ble_profile_hid_start(FuriHalBleProfileParams profile_params) {
UNUSED(profile_params);
BleProfileHid* profile = malloc(sizeof(BleProfileHid));
profile->base.config = ble_profile_hid;
profile->battery_svc = ble_svc_battery_start(true);
profile->dev_info_svc = ble_svc_dev_info_start();
profile->hid_svc = ble_svc_hid_start();
// Configure HID Keyboard
profile->kb_report = malloc(sizeof(FuriHalBtHidKbReport));
profile->mouse_report = malloc(sizeof(FuriHalBtHidMouseReport));
profile->consumer_report = malloc(sizeof(FuriHalBtHidConsumerReport));
// Configure Report Map characteristic
ble_svc_hid_update_report_map(
profile->hid_svc,
ble_profile_hid_report_map_data,
sizeof(ble_profile_hid_report_map_data));
// Configure HID Information characteristic
uint8_t hid_info_val[4] = {
HID_INFO_BASE_USB_SPECIFICATION & 0x00ff,
(HID_INFO_BASE_USB_SPECIFICATION & 0xff00) >> 8,
HID_INFO_COUNTRY_CODE,
BLE_PROFILE_HID_INFO_FLAG_REMOTE_WAKE_MSK |
BLE_PROFILE_HID_INFO_FLAG_NORMALLY_CONNECTABLE_MSK,
};
ble_svc_hid_update_info(profile->hid_svc, hid_info_val);
return &profile->base;
}
static void ble_profile_hid_stop(FuriHalBleProfileBase* profile) {
furi_check(profile);
furi_check(profile->config == ble_profile_hid);
BleProfileHid* hid_profile = (BleProfileHid*)profile;
ble_svc_battery_stop(hid_profile->battery_svc);
ble_svc_dev_info_stop(hid_profile->dev_info_svc);
ble_svc_hid_stop(hid_profile->hid_svc);
free(hid_profile->kb_report);
free(hid_profile->mouse_report);
free(hid_profile->consumer_report);
}
bool ble_profile_hid_kb_press(FuriHalBleProfileBase* profile, uint16_t button) {
furi_check(profile);
furi_check(profile->config == ble_profile_hid);
BleProfileHid* hid_profile = (BleProfileHid*)profile;
FuriHalBtHidKbReport* kb_report = hid_profile->kb_report;
for(uint8_t i = 0; i < BLE_PROFILE_HID_KB_MAX_KEYS; i++) {
if(kb_report->key[i] == 0) {
kb_report->key[i] = button & 0xFF;
break;
}
}
kb_report->mods |= (button >> 8);
return ble_svc_hid_update_input_report(
hid_profile->hid_svc,
ReportNumberKeyboard,
(uint8_t*)kb_report,
sizeof(FuriHalBtHidKbReport));
}
bool ble_profile_hid_kb_release(FuriHalBleProfileBase* profile, uint16_t button) {
furi_check(profile);
furi_check(profile->config == ble_profile_hid);
BleProfileHid* hid_profile = (BleProfileHid*)profile;
FuriHalBtHidKbReport* kb_report = hid_profile->kb_report;
for(uint8_t i = 0; i < BLE_PROFILE_HID_KB_MAX_KEYS; i++) {
if(kb_report->key[i] == (button & 0xFF)) {
kb_report->key[i] = 0;
break;
}
}
kb_report->mods &= ~(button >> 8);
return ble_svc_hid_update_input_report(
hid_profile->hid_svc,
ReportNumberKeyboard,
(uint8_t*)kb_report,
sizeof(FuriHalBtHidKbReport));
}
bool ble_profile_hid_kb_release_all(FuriHalBleProfileBase* profile) {
furi_check(profile);
furi_check(profile->config == ble_profile_hid);
BleProfileHid* hid_profile = (BleProfileHid*)profile;
FuriHalBtHidKbReport* kb_report = hid_profile->kb_report;
for(uint8_t i = 0; i < BLE_PROFILE_HID_KB_MAX_KEYS; i++) {
kb_report->key[i] = 0;
}
kb_report->mods = 0;
return ble_svc_hid_update_input_report(
hid_profile->hid_svc,
ReportNumberKeyboard,
(uint8_t*)kb_report,
sizeof(FuriHalBtHidKbReport));
}
bool ble_profile_hid_consumer_key_press(FuriHalBleProfileBase* profile, uint16_t button) {
furi_check(profile);
furi_check(profile->config == ble_profile_hid);
BleProfileHid* hid_profile = (BleProfileHid*)profile;
FuriHalBtHidConsumerReport* consumer_report = hid_profile->consumer_report;
for(uint8_t i = 0; i < BLE_PROFILE_CONSUMER_MAX_KEYS; i++) { //-V1008
if(consumer_report->key[i] == 0) {
consumer_report->key[i] = button;
break;
}
}
return ble_svc_hid_update_input_report(
hid_profile->hid_svc,
ReportNumberConsumer,
(uint8_t*)consumer_report,
sizeof(FuriHalBtHidConsumerReport));
}
bool ble_profile_hid_consumer_key_release(FuriHalBleProfileBase* profile, uint16_t button) {
furi_check(profile);
furi_check(profile->config == ble_profile_hid);
BleProfileHid* hid_profile = (BleProfileHid*)profile;
FuriHalBtHidConsumerReport* consumer_report = hid_profile->consumer_report;
for(uint8_t i = 0; i < BLE_PROFILE_CONSUMER_MAX_KEYS; i++) { //-V1008
if(consumer_report->key[i] == button) {
consumer_report->key[i] = 0;
break;
}
}
return ble_svc_hid_update_input_report(
hid_profile->hid_svc,
ReportNumberConsumer,
(uint8_t*)consumer_report,
sizeof(FuriHalBtHidConsumerReport));
}
bool ble_profile_hid_consumer_key_release_all(FuriHalBleProfileBase* profile) {
furi_check(profile);
furi_check(profile->config == ble_profile_hid);
BleProfileHid* hid_profile = (BleProfileHid*)profile;
FuriHalBtHidConsumerReport* consumer_report = hid_profile->consumer_report;
for(uint8_t i = 0; i < BLE_PROFILE_CONSUMER_MAX_KEYS; i++) { //-V1008
consumer_report->key[i] = 0;
}
return ble_svc_hid_update_input_report(
hid_profile->hid_svc,
ReportNumberConsumer,
(uint8_t*)consumer_report,
sizeof(FuriHalBtHidConsumerReport));
}
bool ble_profile_hid_mouse_move(FuriHalBleProfileBase* profile, int8_t dx, int8_t dy) {
furi_check(profile);
furi_check(profile->config == ble_profile_hid);
BleProfileHid* hid_profile = (BleProfileHid*)profile;
FuriHalBtHidMouseReport* mouse_report = hid_profile->mouse_report;
mouse_report->x = dx;
mouse_report->y = dy;
bool state = ble_svc_hid_update_input_report(
hid_profile->hid_svc,
ReportNumberMouse,
(uint8_t*)mouse_report,
sizeof(FuriHalBtHidMouseReport));
mouse_report->x = 0;
mouse_report->y = 0;
return state;
}
bool ble_profile_hid_mouse_press(FuriHalBleProfileBase* profile, uint8_t button) {
furi_check(profile);
furi_check(profile->config == ble_profile_hid);
BleProfileHid* hid_profile = (BleProfileHid*)profile;
FuriHalBtHidMouseReport* mouse_report = hid_profile->mouse_report;
mouse_report->btn |= button;
return ble_svc_hid_update_input_report(
hid_profile->hid_svc,
ReportNumberMouse,
(uint8_t*)mouse_report,
sizeof(FuriHalBtHidMouseReport));
}
bool ble_profile_hid_mouse_release(FuriHalBleProfileBase* profile, uint8_t button) {
furi_check(profile);
furi_check(profile->config == ble_profile_hid);
BleProfileHid* hid_profile = (BleProfileHid*)profile;
FuriHalBtHidMouseReport* mouse_report = hid_profile->mouse_report;
mouse_report->btn &= ~button;
return ble_svc_hid_update_input_report(
hid_profile->hid_svc,
ReportNumberMouse,
(uint8_t*)mouse_report,
sizeof(FuriHalBtHidMouseReport));
}
bool ble_profile_hid_mouse_release_all(FuriHalBleProfileBase* profile) {
furi_check(profile);
furi_check(profile->config == ble_profile_hid);
BleProfileHid* hid_profile = (BleProfileHid*)profile;
FuriHalBtHidMouseReport* mouse_report = hid_profile->mouse_report;
mouse_report->btn = 0;
return ble_svc_hid_update_input_report(
hid_profile->hid_svc,
ReportNumberMouse,
(uint8_t*)mouse_report,
sizeof(FuriHalBtHidMouseReport));
}
bool ble_profile_hid_mouse_scroll(FuriHalBleProfileBase* profile, int8_t delta) {
furi_check(profile);
furi_check(profile->config == ble_profile_hid);
BleProfileHid* hid_profile = (BleProfileHid*)profile;
FuriHalBtHidMouseReport* mouse_report = hid_profile->mouse_report;
mouse_report->wheel = delta;
bool state = ble_svc_hid_update_input_report(
hid_profile->hid_svc,
ReportNumberMouse,
(uint8_t*)mouse_report,
sizeof(FuriHalBtHidMouseReport));
mouse_report->wheel = 0;
return state;
}
// AN5289: 4.7, in order to use flash controller interval must be at least 25ms + advertisement, which is 30 ms
// Since we don't use flash controller anymore interval can be lowered to 7.5ms
#define CONNECTION_INTERVAL_MIN (0x0006)
// Up to 45 ms
#define CONNECTION_INTERVAL_MAX (0x24)
static GapConfig template_config = {
.adv_service =
{
.UUID_Type = UUID_TYPE_16,
.Service_UUID_16 = HUMAN_INTERFACE_DEVICE_SERVICE_UUID,
},
.appearance_char = GAP_APPEARANCE_KEYBOARD,
.bonding_mode = true,
.pairing_method = GapPairingPinCodeVerifyYesNo,
.conn_param =
{
.conn_int_min = CONNECTION_INTERVAL_MIN,
.conn_int_max = CONNECTION_INTERVAL_MAX,
.slave_latency = 0,
.supervisor_timeout = 0,
},
};
static void ble_profile_hid_get_config(GapConfig* config, FuriHalBleProfileParams profile_params) {
furi_check(profile_params);
BleProfileHidParams* hid_profile_params = profile_params;
furi_check(config);
memcpy(config, &template_config, sizeof(GapConfig));
// Set MAC address
memcpy(config->mac_address, hid_profile_params->mac, sizeof(config->mac_address));
// Set advertise name
config->adv_name[0] = furi_hal_version_get_ble_local_device_name_ptr()[0];
strlcpy(config->adv_name + 1, hid_profile_params->name, sizeof(config->adv_name) - 1);
// Set bonding mode
config->bonding_mode = hid_profile_params->bonding;
// Set pairing method
config->pairing_method = hid_profile_params->pairing;
}
static const FuriHalBleProfileTemplate profile_callbacks = {
.start = ble_profile_hid_start,
.stop = ble_profile_hid_stop,
.get_gap_config = ble_profile_hid_get_config,
};
const FuriHalBleProfileTemplate* ble_profile_hid = &profile_callbacks;

View File

@@ -0,0 +1,109 @@
#pragma once
// Based on <lib/ble_profile/extra_profiles/hid_profile.h>
#include <furi_ble/profile_interface.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* Optional arguments to pass along with profile template as
* FuriHalBleProfileParams for tuning profile behavior
**/
typedef struct {
char name[FURI_HAL_BT_ADV_NAME_LENGTH]; /**< Full device name */
uint8_t mac[GAP_MAC_ADDR_SIZE]; /**< Full device address */
bool bonding; /**< Save paired devices */
GapPairing pairing; /**< Pairing security method */
} BleProfileHidParams;
/** Hid Keyboard Profile descriptor */
extern const FuriHalBleProfileTemplate* ble_profile_hid;
/** Press keyboard button
*
* @param profile profile instance
* @param button button code from HID specification
*
* @return true on success
*/
bool ble_profile_hid_kb_press(FuriHalBleProfileBase* profile, uint16_t button);
/** Release keyboard button
*
* @param profile profile instance
* @param button button code from HID specification
*
* @return true on success
*/
bool ble_profile_hid_kb_release(FuriHalBleProfileBase* profile, uint16_t button);
/** Release all keyboard buttons
*
* @param profile profile instance
* @return true on success
*/
bool ble_profile_hid_kb_release_all(FuriHalBleProfileBase* profile);
/** Set the following consumer key to pressed state and send HID report
*
* @param profile profile instance
* @param button key code
*/
bool ble_profile_hid_consumer_key_press(FuriHalBleProfileBase* profile, uint16_t button);
/** Set the following consumer key to released state and send HID report
*
* @param profile profile instance
* @param button key code
*/
bool ble_profile_hid_consumer_key_release(FuriHalBleProfileBase* profile, uint16_t button);
/** Set consumer key to released state and send HID report
*
* @param profile profile instance
* @param button key code
*/
bool ble_profile_hid_consumer_key_release_all(FuriHalBleProfileBase* profile);
/** Set mouse movement and send HID report
*
* @param profile profile instance
* @param dx x coordinate delta
* @param dy y coordinate delta
*/
bool ble_profile_hid_mouse_move(FuriHalBleProfileBase* profile, int8_t dx, int8_t dy);
/** Set mouse button to pressed state and send HID report
*
* @param profile profile instance
* @param button key code
*/
bool ble_profile_hid_mouse_press(FuriHalBleProfileBase* profile, uint8_t button);
/** Set mouse button to released state and send HID report
*
* @param profile profile instance
* @param button key code
*/
bool ble_profile_hid_mouse_release(FuriHalBleProfileBase* profile, uint8_t button);
/** Set mouse button to released state and send HID report
*
* @param profile profile instance
* @param button key code
*/
bool ble_profile_hid_mouse_release_all(FuriHalBleProfileBase* profile);
/** Set mouse wheel position and send HID report
*
* @param profile profile instance
* @param delta number of scroll steps
*/
bool ble_profile_hid_mouse_scroll(FuriHalBleProfileBase* profile, int8_t delta);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,325 @@
#include "ble_hid_service.h"
// Based on <lib/ble_profile/extra_services/hid_service.c>
#include "app_common.h" // IWYU pragma: keep
#include <ble/ble.h>
#include <furi_ble/event_dispatcher.h>
#include <furi_ble/gatt.h>
#include <furi.h>
#include <stdint.h>
#define TAG "BleHid"
#define BLE_SVC_HID_REPORT_MAP_MAX_LEN (255)
#define BLE_SVC_HID_REPORT_MAX_LEN (255)
#define BLE_SVC_HID_REPORT_REF_LEN (2)
#define BLE_SVC_HID_INFO_LEN (4)
#define BLE_SVC_HID_CONTROL_POINT_LEN (1)
#define BLE_SVC_HID_INPUT_REPORT_COUNT (3)
#define BLE_SVC_HID_OUTPUT_REPORT_COUNT (0)
#define BLE_SVC_HID_FEATURE_REPORT_COUNT (0)
#define BLE_SVC_HID_REPORT_COUNT \
(BLE_SVC_HID_INPUT_REPORT_COUNT + BLE_SVC_HID_OUTPUT_REPORT_COUNT + \
BLE_SVC_HID_FEATURE_REPORT_COUNT)
typedef enum {
HidSvcGattCharacteristicProtocolMode = 0,
HidSvcGattCharacteristicReportMap,
HidSvcGattCharacteristicInfo,
HidSvcGattCharacteristicCtrlPoint,
HidSvcGattCharacteristicCount,
} HidSvcGattCharacteristicId;
typedef struct {
uint8_t report_idx;
uint8_t report_type;
} HidSvcReportId;
static_assert(sizeof(HidSvcReportId) == sizeof(uint16_t), "HidSvcReportId must be 2 bytes");
static const Service_UUID_t ble_svc_hid_uuid = {
.Service_UUID_16 = HUMAN_INTERFACE_DEVICE_SERVICE_UUID,
};
static bool ble_svc_hid_char_desc_data_callback(
const void* context,
const uint8_t** data,
uint16_t* data_len) {
const HidSvcReportId* report_id = context;
*data_len = sizeof(HidSvcReportId);
if(data) {
*data = (const uint8_t*)report_id;
}
return false;
}
typedef struct {
const void* data_ptr;
uint16_t data_len;
} HidSvcDataWrapper;
static bool ble_svc_hid_report_data_callback(
const void* context,
const uint8_t** data,
uint16_t* data_len) {
const HidSvcDataWrapper* report_data = context;
if(data) {
*data = report_data->data_ptr;
*data_len = report_data->data_len;
} else {
*data_len = BLE_SVC_HID_REPORT_MAP_MAX_LEN;
}
return false;
}
static const BleGattCharacteristicParams ble_svc_hid_chars[HidSvcGattCharacteristicCount] = {
[HidSvcGattCharacteristicProtocolMode] =
{.name = "Protocol Mode",
.data_prop_type = FlipperGattCharacteristicDataFixed,
.data.fixed.length = 1,
.uuid.Char_UUID_16 = PROTOCOL_MODE_CHAR_UUID,
.uuid_type = UUID_TYPE_16,
.char_properties = CHAR_PROP_READ | CHAR_PROP_WRITE_WITHOUT_RESP,
.security_permissions = ATTR_PERMISSION_NONE,
.gatt_evt_mask = GATT_NOTIFY_ATTRIBUTE_WRITE,
.is_variable = CHAR_VALUE_LEN_CONSTANT},
[HidSvcGattCharacteristicReportMap] =
{.name = "Report Map",
.data_prop_type = FlipperGattCharacteristicDataCallback,
.data.callback.fn = ble_svc_hid_report_data_callback,
.data.callback.context = NULL,
.uuid.Char_UUID_16 = REPORT_MAP_CHAR_UUID,
.uuid_type = UUID_TYPE_16,
.char_properties = CHAR_PROP_READ,
.security_permissions = ATTR_PERMISSION_NONE,
.gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS,
.is_variable = CHAR_VALUE_LEN_VARIABLE},
[HidSvcGattCharacteristicInfo] =
{.name = "HID Information",
.data_prop_type = FlipperGattCharacteristicDataFixed,
.data.fixed.length = BLE_SVC_HID_INFO_LEN,
.data.fixed.ptr = NULL,
.uuid.Char_UUID_16 = HID_INFORMATION_CHAR_UUID,
.uuid_type = UUID_TYPE_16,
.char_properties = CHAR_PROP_READ,
.security_permissions = ATTR_PERMISSION_NONE,
.gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS,
.is_variable = CHAR_VALUE_LEN_CONSTANT},
[HidSvcGattCharacteristicCtrlPoint] =
{.name = "HID Control Point",
.data_prop_type = FlipperGattCharacteristicDataFixed,
.data.fixed.length = BLE_SVC_HID_CONTROL_POINT_LEN,
.uuid.Char_UUID_16 = HID_CONTROL_POINT_CHAR_UUID,
.uuid_type = UUID_TYPE_16,
.char_properties = CHAR_PROP_WRITE_WITHOUT_RESP,
.security_permissions = ATTR_PERMISSION_NONE,
.gatt_evt_mask = GATT_NOTIFY_ATTRIBUTE_WRITE,
.is_variable = CHAR_VALUE_LEN_CONSTANT},
};
static const BleGattCharacteristicDescriptorParams ble_svc_hid_char_descr_template = {
.uuid_type = UUID_TYPE_16,
.uuid.Char_UUID_16 = REPORT_REFERENCE_DESCRIPTOR_UUID,
.max_length = BLE_SVC_HID_REPORT_REF_LEN,
.data_callback.fn = ble_svc_hid_char_desc_data_callback,
.security_permissions = ATTR_PERMISSION_NONE,
.access_permissions = ATTR_ACCESS_READ_WRITE,
.gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS,
.is_variable = CHAR_VALUE_LEN_CONSTANT,
};
static const BleGattCharacteristicParams ble_svc_hid_report_template = {
.name = "Report",
.data_prop_type = FlipperGattCharacteristicDataCallback,
.data.callback.fn = ble_svc_hid_report_data_callback,
.data.callback.context = NULL,
.uuid.Char_UUID_16 = REPORT_CHAR_UUID,
.uuid_type = UUID_TYPE_16,
.char_properties = CHAR_PROP_READ | CHAR_PROP_NOTIFY,
.security_permissions = ATTR_PERMISSION_NONE,
.gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS,
.is_variable = CHAR_VALUE_LEN_VARIABLE,
};
struct BleServiceHid {
uint16_t svc_handle;
BleGattCharacteristicInstance chars[HidSvcGattCharacteristicCount];
BleGattCharacteristicInstance input_report_chars[BLE_SVC_HID_INPUT_REPORT_COUNT];
BleGattCharacteristicInstance output_report_chars[BLE_SVC_HID_OUTPUT_REPORT_COUNT];
BleGattCharacteristicInstance feature_report_chars[BLE_SVC_HID_FEATURE_REPORT_COUNT];
GapSvcEventHandler* event_handler;
};
static BleEventAckStatus ble_svc_hid_event_handler(void* event, void* context) {
UNUSED(context);
BleEventAckStatus ret = BleEventNotAck;
hci_event_pckt* event_pckt = (hci_event_pckt*)(((hci_uart_pckt*)event)->data);
evt_blecore_aci* blecore_evt = (evt_blecore_aci*)event_pckt->data;
// aci_gatt_attribute_modified_event_rp0* attribute_modified;
if(event_pckt->evt == HCI_VENDOR_SPECIFIC_DEBUG_EVT_CODE) {
if(blecore_evt->ecode == ACI_GATT_ATTRIBUTE_MODIFIED_VSEVT_CODE) {
// Process modification events
ret = BleEventAckFlowEnable;
} else if(blecore_evt->ecode == ACI_GATT_SERVER_CONFIRMATION_VSEVT_CODE) {
// Process notification confirmation
ret = BleEventAckFlowEnable;
}
}
return ret;
}
BleServiceHid* ble_svc_hid_start(void) {
BleServiceHid* hid_svc = malloc(sizeof(BleServiceHid));
// Register event handler
hid_svc->event_handler =
ble_event_dispatcher_register_svc_handler(ble_svc_hid_event_handler, hid_svc);
/**
* Add Human Interface Device Service
*/
if(!ble_gatt_service_add(
UUID_TYPE_16,
&ble_svc_hid_uuid,
PRIMARY_SERVICE,
2 + /* protocol mode */
(4 * BLE_SVC_HID_INPUT_REPORT_COUNT) + (3 * BLE_SVC_HID_OUTPUT_REPORT_COUNT) +
(3 * BLE_SVC_HID_FEATURE_REPORT_COUNT) + 1 + 2 + 2 +
2, /* Service + Report Map + HID Information + HID Control Point */
&hid_svc->svc_handle)) {
free(hid_svc);
return NULL;
}
// Maintain previously defined characteristic order
ble_gatt_characteristic_init(
hid_svc->svc_handle,
&ble_svc_hid_chars[HidSvcGattCharacteristicProtocolMode],
&hid_svc->chars[HidSvcGattCharacteristicProtocolMode]);
uint8_t protocol_mode = 1;
ble_gatt_characteristic_update(
hid_svc->svc_handle,
&hid_svc->chars[HidSvcGattCharacteristicProtocolMode],
&protocol_mode);
// reports
BleGattCharacteristicDescriptorParams ble_svc_hid_char_descr;
BleGattCharacteristicParams report_char;
HidSvcReportId report_id;
memcpy(
&ble_svc_hid_char_descr, &ble_svc_hid_char_descr_template, sizeof(ble_svc_hid_char_descr));
memcpy(&report_char, &ble_svc_hid_report_template, sizeof(report_char));
ble_svc_hid_char_descr.data_callback.context = &report_id;
report_char.descriptor_params = &ble_svc_hid_char_descr;
typedef struct {
uint8_t report_type;
uint8_t report_count;
BleGattCharacteristicInstance* chars;
} HidSvcReportCharProps;
HidSvcReportCharProps hid_report_chars[] = {
{0x01, BLE_SVC_HID_INPUT_REPORT_COUNT, hid_svc->input_report_chars},
{0x02, BLE_SVC_HID_OUTPUT_REPORT_COUNT, hid_svc->output_report_chars},
{0x03, BLE_SVC_HID_FEATURE_REPORT_COUNT, hid_svc->feature_report_chars},
};
for(size_t report_type_idx = 0; report_type_idx < COUNT_OF(hid_report_chars);
report_type_idx++) {
report_id.report_type = hid_report_chars[report_type_idx].report_type;
for(size_t report_idx = 0; report_idx < hid_report_chars[report_type_idx].report_count;
report_idx++) {
report_id.report_idx = report_idx + 1;
ble_gatt_characteristic_init(
hid_svc->svc_handle,
&report_char,
&hid_report_chars[report_type_idx].chars[report_idx]);
}
}
// Setup remaining characteristics
for(size_t i = HidSvcGattCharacteristicReportMap; i < HidSvcGattCharacteristicCount; i++) {
ble_gatt_characteristic_init(
hid_svc->svc_handle, &ble_svc_hid_chars[i], &hid_svc->chars[i]);
}
return hid_svc;
}
bool ble_svc_hid_update_report_map(BleServiceHid* hid_svc, const uint8_t* data, uint16_t len) {
furi_assert(data);
furi_assert(hid_svc);
HidSvcDataWrapper report_data = {
.data_ptr = data,
.data_len = len,
};
return ble_gatt_characteristic_update(
hid_svc->svc_handle, &hid_svc->chars[HidSvcGattCharacteristicReportMap], &report_data);
}
bool ble_svc_hid_update_input_report(
BleServiceHid* hid_svc,
uint8_t input_report_num,
uint8_t* data,
uint16_t len) {
furi_assert(data);
furi_assert(hid_svc);
furi_assert(input_report_num < BLE_SVC_HID_INPUT_REPORT_COUNT);
HidSvcDataWrapper report_data = {
.data_ptr = data,
.data_len = len,
};
return ble_gatt_characteristic_update(
hid_svc->svc_handle, &hid_svc->input_report_chars[input_report_num], &report_data);
}
bool ble_svc_hid_update_info(BleServiceHid* hid_svc, uint8_t* data) {
furi_assert(data);
furi_assert(hid_svc);
return ble_gatt_characteristic_update(
hid_svc->svc_handle, &hid_svc->chars[HidSvcGattCharacteristicInfo], &data);
}
void ble_svc_hid_stop(BleServiceHid* hid_svc) {
furi_assert(hid_svc);
ble_event_dispatcher_unregister_svc_handler(hid_svc->event_handler);
// Delete characteristics
for(size_t i = 0; i < HidSvcGattCharacteristicCount; i++) {
ble_gatt_characteristic_delete(hid_svc->svc_handle, &hid_svc->chars[i]);
}
typedef struct {
uint8_t report_count;
BleGattCharacteristicInstance* chars;
} HidSvcReportCharProps;
HidSvcReportCharProps hid_report_chars[] = {
{BLE_SVC_HID_INPUT_REPORT_COUNT, hid_svc->input_report_chars},
{BLE_SVC_HID_OUTPUT_REPORT_COUNT, hid_svc->output_report_chars},
{BLE_SVC_HID_FEATURE_REPORT_COUNT, hid_svc->feature_report_chars},
};
for(size_t report_type_idx = 0; report_type_idx < COUNT_OF(hid_report_chars);
report_type_idx++) {
for(size_t report_idx = 0; report_idx < hid_report_chars[report_type_idx].report_count;
report_idx++) {
ble_gatt_characteristic_delete(
hid_svc->svc_handle, &hid_report_chars[report_type_idx].chars[report_idx]);
}
}
// Delete service
ble_gatt_service_delete(hid_svc->svc_handle);
free(hid_svc);
}

View File

@@ -0,0 +1,31 @@
#pragma once
// Based on <lib/ble_profile/extra_services/hid_service.h>
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct BleServiceHid BleServiceHid;
BleServiceHid* ble_svc_hid_start(void);
void ble_svc_hid_stop(BleServiceHid* service);
bool ble_svc_hid_update_report_map(BleServiceHid* service, const uint8_t* data, uint16_t len);
bool ble_svc_hid_update_input_report(
BleServiceHid* service,
uint8_t input_report_num,
uint8_t* data,
uint16_t len);
// Expects data to be of length BLE_SVC_HID_INFO_LEN (4 bytes)
bool ble_svc_hid_update_info(BleServiceHid* service, uint8_t* data);
#ifdef __cplusplus
}
#endif

View File

@@ -25,6 +25,8 @@ typedef enum {
} WorkerEvtFlags;
static const char ducky_cmd_id[] = {"ID"};
static const char ducky_cmd_bt_id[] = {"BT_ID"};
static const char ducky_cmd_ble_id[] = {"BLE_ID"};
static const uint8_t numpad_keys[10] = {
HID_KEYPAD_0,
@@ -40,11 +42,8 @@ static const uint8_t numpad_keys[10] = {
};
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;
char* first_space = strchr(line, ' ');
return first_space ? (first_space - line) : 0;
}
bool ducky_is_line_end(const char chr) {
@@ -180,71 +179,100 @@ static bool ducky_string_next(BadUsbScript* bad_usb) {
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* line_cstr = furi_string_get_cstr(line);
if(line_len == 0) {
return SCRIPT_STATE_NEXT_LINE; // Skip empty lines
}
FURI_LOG_D(WORKER_TAG, "line:%s", line_tmp);
FURI_LOG_D(WORKER_TAG, "line:%s", line_cstr);
// Ducky Lang Functions
int32_t cmd_result = ducky_execute_cmd(bad_usb, line_tmp);
int32_t cmd_result = ducky_execute_cmd(bad_usb, line_cstr);
if(cmd_result != SCRIPT_STATE_CMD_UNKNOWN) {
return cmd_result;
}
// Mouse Keys
uint16_t key = ducky_get_mouse_keycode_by_name(line_tmp);
uint16_t key = ducky_get_mouse_keycode_by_name(line_cstr);
if(key != HID_MOUSE_INVALID) {
bad_usb->hid->mouse_press(bad_usb->hid_inst, key);
bad_usb->hid->mouse_release(bad_usb->hid_inst, key);
return 0;
}
// Special keys + modifiers
key = ducky_get_keycode(bad_usb, line_tmp, false);
if(key == HID_KEYBOARD_NONE) {
return ducky_error(bad_usb, "No keycode defined for %s", line_tmp);
}
if((key & 0xFF00) != 0) {
// It's a modifier key
uint32_t offset = ducky_get_command_len(line_tmp) + 1;
// ducky_get_command_len() returns 0 without space, so check for != 1
if(offset != 1 && line_len > offset) {
// It's also a key combination
key |= ducky_get_keycode(bad_usb, line_tmp + offset, true);
}
// Parse chain of modifiers linked by spaces and hyphens
uint16_t modifiers = 0;
while(1) {
key = ducky_get_next_modifier_keycode_by_name(&line_cstr);
if(key == HID_KEYBOARD_NONE) break;
modifiers |= key;
char next_char = *line_cstr;
if(next_char == ' ' || next_char == '-') line_cstr++;
}
// Main key
char next_char = *line_cstr;
uint16_t main_key = ducky_get_keycode_by_name(line_cstr);
if(!main_key && next_char) main_key = BADUSB_ASCII_TO_KEY(bad_usb, next_char);
key = modifiers | main_key;
if(key == 0 && next_char) ducky_error(bad_usb, "No keycode defined for %s", line_cstr);
bad_usb->hid->kb_press(bad_usb->hid_inst, key);
bad_usb->hid->kb_release(bad_usb->hid_inst, key);
return 0;
}
static bool ducky_set_usb_id(BadUsbScript* bad_usb, const char* line) {
if(sscanf(line, "%lX:%lX", &bad_usb->hid_cfg.vid, &bad_usb->hid_cfg.pid) == 2) {
bad_usb->hid_cfg.manuf[0] = '\0';
bad_usb->hid_cfg.product[0] = '\0';
FuriHalUsbHidConfig* usb_hid_cfg = &bad_usb->hid_cfg->usb;
if(sscanf(line, "%lX:%lX", &usb_hid_cfg->vid, &usb_hid_cfg->pid) == 2) {
usb_hid_cfg->manuf[0] = '\0';
usb_hid_cfg->product[0] = '\0';
uint8_t id_len = ducky_get_command_len(line);
if(!ducky_is_line_end(line[id_len + 1])) {
sscanf(
&line[id_len + 1],
"%31[^\r\n:]:%31[^\r\n]",
bad_usb->hid_cfg.manuf,
bad_usb->hid_cfg.product);
usb_hid_cfg->manuf,
usb_hid_cfg->product);
}
FURI_LOG_D(
WORKER_TAG,
"set id: %04lX:%04lX mfr:%s product:%s",
bad_usb->hid_cfg.vid,
bad_usb->hid_cfg.pid,
bad_usb->hid_cfg.manuf,
bad_usb->hid_cfg.product);
usb_hid_cfg->vid,
usb_hid_cfg->pid,
usb_hid_cfg->manuf,
usb_hid_cfg->product);
return true;
}
return false;
}
static bool ducky_set_ble_id(BadUsbScript* bad_usb, const char* line) {
BleProfileHidParams* ble_hid_cfg = &bad_usb->hid_cfg->ble;
size_t line_len = strlen(line);
size_t mac_len = sizeof(ble_hid_cfg->mac) * 3; // 2 hex chars + separator per byte
if(line_len < mac_len + 1) return false; // MAC + at least 1 char for name
for(size_t i = 0; i < sizeof(ble_hid_cfg->mac); i++) {
const char* hex_byte = &line[i * 3];
// This sscanf() doesn't work well with %02hhX, need to use a u32
uint32_t temp_uint;
if(sscanf(hex_byte, "%02lX", &temp_uint) != 1) {
return false;
}
ble_hid_cfg->mac[sizeof(ble_hid_cfg->mac) - 1 - i] = temp_uint;
}
strlcpy(ble_hid_cfg->name, line + mac_len, sizeof(ble_hid_cfg->name));
FURI_LOG_D(WORKER_TAG, "set ble id: %s", line);
return true;
}
static void bad_usb_hid_state_callback(bool state, void* context) {
furi_assert(context);
BadUsbScript* bad_usb = context;
@@ -283,17 +311,30 @@ static bool ducky_script_preload(BadUsbScript* bad_usb, File* script_file) {
}
} while(ret > 0);
const char* line_tmp = furi_string_get_cstr(bad_usb->line);
bool id_set = false; // Looking for ID command at first line
if(strncmp(line_tmp, ducky_cmd_id, strlen(ducky_cmd_id)) == 0) {
id_set = ducky_set_usb_id(bad_usb, &line_tmp[strlen(ducky_cmd_id) + 1]);
if(bad_usb->load_id_cfg) {
const char* line_tmp = furi_string_get_cstr(bad_usb->line);
BadUsbHidInterface interface = *bad_usb->interface;
// Look for ID/BLE_ID/BT_ID command on first line
if(strncmp(line_tmp, ducky_cmd_id, strlen(ducky_cmd_id)) == 0) {
if(ducky_set_usb_id(bad_usb, &line_tmp[strlen(ducky_cmd_id) + 1])) {
interface = BadUsbHidInterfaceUsb;
}
} else if(
strncmp(line_tmp, ducky_cmd_ble_id, strlen(ducky_cmd_ble_id)) == 0 ||
strncmp(line_tmp, ducky_cmd_bt_id, strlen(ducky_cmd_bt_id)) == 0) {
if(ducky_set_ble_id(bad_usb, &line_tmp[ducky_get_command_len(line_tmp) + 1])) {
interface = BadUsbHidInterfaceBle;
}
}
// Auto-switch based on ID/BLE_ID/BT_ID command, user can override manually after
if(interface != *bad_usb->interface) {
*bad_usb->interface = interface;
bad_usb->hid = bad_usb_hid_get_interface(*bad_usb->interface);
}
}
if(id_set) {
bad_usb->hid_inst = bad_usb->hid->init(&bad_usb->hid_cfg);
} else {
bad_usb->hid_inst = bad_usb->hid->init(NULL);
}
bad_usb->hid_inst = bad_usb->hid->init(bad_usb->hid_cfg);
bad_usb->hid->set_state_callback(bad_usb->hid_inst, bad_usb_hid_state_callback, bad_usb);
storage_file_seek(script_file, 0, true);
@@ -396,9 +437,12 @@ static int32_t bad_usb_worker(void* context) {
bad_usb->line = furi_string_alloc();
bad_usb->line_prev = furi_string_alloc();
bad_usb->string_print = furi_string_alloc();
bad_usb->st.elapsed = 0;
while(1) {
uint32_t start = furi_get_tick();
if(worker_state == BadUsbStateInit) { // State: initialization
start = 0;
if(storage_file_open(
script_file,
furi_string_get_cstr(bad_usb->file_path),
@@ -408,7 +452,7 @@ static int32_t bad_usb_worker(void* context) {
if(bad_usb->hid->is_connected(bad_usb->hid_inst)) {
worker_state = BadUsbStateIdle; // Ready to run
} else {
worker_state = BadUsbStateNotConnected; // USB not connected
worker_state = BadUsbStateNotConnected; // Not connected
}
} else {
worker_state = BadUsbStateScriptError; // Script preload error
@@ -419,7 +463,8 @@ static int32_t bad_usb_worker(void* context) {
}
bad_usb->st.state = worker_state;
} else if(worker_state == BadUsbStateNotConnected) { // State: USB not connected
} else if(worker_state == BadUsbStateNotConnected) { // State: Not connected
start = 0;
uint32_t flags = bad_usb_flags_get(
WorkerEvtEnd | WorkerEvtConnect | WorkerEvtDisconnect | WorkerEvtStartStop,
FuriWaitForever);
@@ -429,11 +474,12 @@ static int32_t bad_usb_worker(void* context) {
} else if(flags & WorkerEvtConnect) {
worker_state = BadUsbStateIdle; // Ready to run
} else if(flags & WorkerEvtStartStop) {
worker_state = BadUsbStateWillRun; // Will run when USB is connected
worker_state = BadUsbStateWillRun; // Will run when connected
}
bad_usb->st.state = worker_state;
} else if(worker_state == BadUsbStateIdle) { // State: ready to start
start = 0;
uint32_t flags = bad_usb_flags_get(
WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtDisconnect, FuriWaitForever);
@@ -452,12 +498,14 @@ static int32_t bad_usb_worker(void* context) {
bad_usb->file_end = false;
storage_file_seek(script_file, 0, true);
worker_state = BadUsbStateRunning;
bad_usb->st.elapsed = 0;
} else if(flags & WorkerEvtDisconnect) {
worker_state = BadUsbStateNotConnected; // USB disconnected
worker_state = BadUsbStateNotConnected; // Disconnected
}
bad_usb->st.state = worker_state;
} else if(worker_state == BadUsbStateWillRun) { // State: start on connection
start = 0;
uint32_t flags = bad_usb_flags_get(
WorkerEvtEnd | WorkerEvtConnect | WorkerEvtStartStop, FuriWaitForever);
@@ -482,6 +530,7 @@ static int32_t bad_usb_worker(void* context) {
if(flags == (unsigned)FuriFlagErrorTimeout) {
// If nothing happened - start script execution
worker_state = BadUsbStateRunning;
bad_usb->st.elapsed = 0;
} else if(flags & WorkerEvtStartStop) {
worker_state = BadUsbStateIdle;
furi_thread_flags_clear(WorkerEvtStartStop);
@@ -492,7 +541,7 @@ static int32_t bad_usb_worker(void* context) {
bad_usb->st.state = worker_state;
} else if(worker_state == BadUsbStateRunning) { // State: running
uint16_t delay_cur = (delay_val > 1000) ? (1000) : (delay_val);
uint16_t delay_cur = (delay_val > 100) ? (100) : (delay_val);
uint32_t flags = furi_thread_flags_wait(
WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtPauseResume | WorkerEvtDisconnect,
FuriFlagWaitAny,
@@ -506,19 +555,21 @@ static int32_t bad_usb_worker(void* context) {
worker_state = BadUsbStateIdle; // Stop executing script
bad_usb->hid->release_all(bad_usb->hid_inst);
} else if(flags & WorkerEvtDisconnect) {
worker_state = BadUsbStateNotConnected; // USB disconnected
worker_state = BadUsbStateNotConnected; // Disconnected
bad_usb->hid->release_all(bad_usb->hid_inst);
} else if(flags & WorkerEvtPauseResume) {
pause_state = BadUsbStateRunning;
worker_state = BadUsbStatePaused; // Pause
}
bad_usb->st.state = worker_state;
bad_usb->st.elapsed += (furi_get_tick() - start);
continue;
} else if(
(flags == (unsigned)FuriFlagErrorTimeout) ||
(flags == (unsigned)FuriFlagErrorResource)) {
if(delay_val > 0) {
bad_usb->st.delay_remain--;
bad_usb->st.elapsed += (furi_get_tick() - start);
continue;
}
bad_usb->st.state = BadUsbStateRunning;
@@ -533,6 +584,7 @@ static int32_t bad_usb_worker(void* context) {
worker_state = BadUsbStateIdle;
bad_usb->st.state = BadUsbStateDone;
bad_usb->hid->release_all(bad_usb->hid_inst);
bad_usb->st.elapsed += (furi_get_tick() - start);
continue;
} else if(delay_val == SCRIPT_STATE_STRING_START) { // Start printing string with delays
delay_val = bad_usb->defdelay;
@@ -541,14 +593,15 @@ static int32_t bad_usb_worker(void* context) {
} else if(delay_val == SCRIPT_STATE_WAIT_FOR_BTN) { // set state to wait for user input
worker_state = BadUsbStateWaitForBtn;
bad_usb->st.state = BadUsbStateWaitForBtn; // Show long delays
} else if(delay_val > 1000) {
} else if(delay_val > 100) {
bad_usb->st.state = BadUsbStateDelay; // Show long delays
bad_usb->st.delay_remain = delay_val / 1000;
bad_usb->st.delay_remain = delay_val / 100;
}
} else {
furi_check((flags & FuriFlagError) == 0);
}
} else if(worker_state == BadUsbStateWaitForBtn) { // State: Wait for button Press
start = 0;
uint32_t flags = bad_usb_flags_get(
WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtPauseResume | WorkerEvtDisconnect,
FuriWaitForever);
@@ -559,13 +612,14 @@ static int32_t bad_usb_worker(void* context) {
delay_val = 0;
worker_state = BadUsbStateRunning;
} else if(flags & WorkerEvtDisconnect) {
worker_state = BadUsbStateNotConnected; // USB disconnected
worker_state = BadUsbStateNotConnected; // Disconnected
bad_usb->hid->release_all(bad_usb->hid_inst);
}
bad_usb->st.state = worker_state;
continue;
}
} else if(worker_state == BadUsbStatePaused) { // State: Paused
start = 0;
uint32_t flags = bad_usb_flags_get(
WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtPauseResume | WorkerEvtDisconnect,
FuriWaitForever);
@@ -577,14 +631,14 @@ static int32_t bad_usb_worker(void* context) {
bad_usb->st.state = worker_state;
bad_usb->hid->release_all(bad_usb->hid_inst);
} else if(flags & WorkerEvtDisconnect) {
worker_state = BadUsbStateNotConnected; // USB disconnected
worker_state = BadUsbStateNotConnected; // Disconnected
bad_usb->st.state = worker_state;
bad_usb->hid->release_all(bad_usb->hid_inst);
} else if(flags & WorkerEvtPauseResume) {
if(pause_state == BadUsbStateRunning) {
if(delay_val > 0) {
bad_usb->st.state = BadUsbStateDelay;
bad_usb->st.delay_remain = delay_val / 1000;
bad_usb->st.delay_remain = delay_val / 100;
} else {
bad_usb->st.state = BadUsbStateRunning;
delay_val = 0;
@@ -611,13 +665,14 @@ static int32_t bad_usb_worker(void* context) {
worker_state = BadUsbStateIdle; // Stop executing script
bad_usb->hid->release_all(bad_usb->hid_inst);
} else if(flags & WorkerEvtDisconnect) {
worker_state = BadUsbStateNotConnected; // USB disconnected
worker_state = BadUsbStateNotConnected; // Disconnected
bad_usb->hid->release_all(bad_usb->hid_inst);
} else if(flags & WorkerEvtPauseResume) {
pause_state = BadUsbStateStringDelay;
worker_state = BadUsbStatePaused; // Pause
}
bad_usb->st.state = worker_state;
bad_usb->st.elapsed += (furi_get_tick() - start);
continue;
} else if(
(flags == (unsigned)FuriFlagErrorTimeout) ||
@@ -633,6 +688,7 @@ static int32_t bad_usb_worker(void* context) {
} else if(
(worker_state == BadUsbStateFileError) ||
(worker_state == BadUsbStateScriptError)) { // State: error
start = 0;
uint32_t flags =
bad_usb_flags_get(WorkerEvtEnd, FuriWaitForever); // Waiting for exit command
@@ -640,6 +696,9 @@ static int32_t bad_usb_worker(void* context) {
break;
}
}
if(start) {
bad_usb->st.elapsed += (furi_get_tick() - start);
}
}
bad_usb->hid->set_state_callback(bad_usb->hid_inst, NULL, NULL);
@@ -662,7 +721,11 @@ static void bad_usb_script_set_default_keyboard_layout(BadUsbScript* bad_usb) {
memcpy(bad_usb->layout, hid_asciimap, MIN(sizeof(hid_asciimap), sizeof(bad_usb->layout)));
}
BadUsbScript* bad_usb_script_open(FuriString* file_path, BadUsbHidInterface interface) {
BadUsbScript* bad_usb_script_open(
FuriString* file_path,
BadUsbHidInterface* interface,
BadUsbHidConfig* hid_cfg,
bool load_id_cfg) {
furi_assert(file_path);
BadUsbScript* bad_usb = malloc(sizeof(BadUsbScript));
@@ -672,7 +735,10 @@ BadUsbScript* bad_usb_script_open(FuriString* file_path, BadUsbHidInterface inte
bad_usb->st.state = BadUsbStateInit;
bad_usb->st.error[0] = '\0';
bad_usb->hid = bad_usb_hid_get_interface(interface);
bad_usb->interface = interface;
bad_usb->hid_cfg = hid_cfg;
bad_usb->load_id_cfg = load_id_cfg;
bad_usb->hid = bad_usb_hid_get_interface(*bad_usb->interface);
bad_usb->thread = furi_thread_alloc_ex("BadUsbWorker", 2048, bad_usb_worker, bad_usb);
furi_thread_start(bad_usb->thread);

View File

@@ -30,11 +30,16 @@ typedef struct {
uint32_t delay_remain;
size_t error_line;
char error[64];
uint32_t elapsed;
} BadUsbState;
typedef struct BadUsbScript BadUsbScript;
BadUsbScript* bad_usb_script_open(FuriString* file_path, BadUsbHidInterface interface);
BadUsbScript* bad_usb_script_open(
FuriString* file_path,
BadUsbHidInterface* interface,
BadUsbHidConfig* hid_cfg,
bool load_id_cfg);
void bad_usb_script_close(BadUsbScript* bad_usb);

View File

@@ -256,6 +256,8 @@ static int32_t ducky_fnc_mouse_move(BadUsbScript* bad_usb, const char* line, int
static const DuckyCmd ducky_commands[] = {
{"REM", NULL, -1},
{"ID", NULL, -1},
{"BT_ID", NULL, -1},
{"BLE_ID", NULL, -1},
{"DELAY", ducky_fnc_delay, -1},
{"STRING", ducky_fnc_string, 0},
{"STRINGLN", ducky_fnc_string, 1},

View File

@@ -22,7 +22,9 @@ extern "C" {
#define HID_MOUSE_NONE 0
struct BadUsbScript {
FuriHalUsbHidConfig hid_cfg;
BadUsbHidInterface* interface;
BadUsbHidConfig* hid_cfg;
bool load_id_cfg;
const BadUsbHidApi* hid;
void* hid_inst;
FuriThread* thread;
@@ -54,6 +56,8 @@ uint32_t ducky_get_command_len(const char* line);
bool ducky_is_line_end(const char chr);
uint16_t ducky_get_next_modifier_keycode_by_name(const char** param);
uint16_t ducky_get_keycode_by_name(const char* param);
uint16_t ducky_get_media_keycode_by_name(const char* param);

View File

@@ -6,21 +6,16 @@ typedef struct {
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},
static const DuckyKey ducky_modifier_keys[] = {
{"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},
};
static const DuckyKey ducky_keys[] = {
{"DOWNARROW", HID_KEYBOARD_DOWN_ARROW},
{"DOWN", HID_KEYBOARD_DOWN_ARROW},
{"LEFTARROW", HID_KEYBOARD_LEFT_ARROW},
@@ -119,6 +114,23 @@ static const DuckyKey ducky_mouse_keys[] = {
{"WHEEL_CLICK", HID_MOUSE_BTN_WHEEL},
};
uint16_t ducky_get_next_modifier_keycode_by_name(const char** param) {
const char* input_str = *param;
for(size_t i = 0; i < COUNT_OF(ducky_modifier_keys); i++) {
size_t key_cmd_len = strlen(ducky_modifier_keys[i].name);
if((strncmp(input_str, ducky_modifier_keys[i].name, key_cmd_len) == 0)) {
char next_char_after_key = input_str[key_cmd_len];
if(ducky_is_line_end(next_char_after_key) || (next_char_after_key == '-')) {
*param = &input_str[key_cmd_len];
return ducky_modifier_keys[i].keycode;
}
}
}
return HID_KEYBOARD_NONE;
}
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);

View File

@@ -1,10 +1,68 @@
#include "../bad_usb_app_i.h"
enum SubmenuIndex {
enum ConfigIndex {
ConfigIndexKeyboardLayout,
ConfigIndexBleUnpair,
ConfigIndexConnection,
};
enum ConfigIndexBle {
ConfigIndexBlePersistPairing = ConfigIndexConnection + 1,
ConfigIndexBlePairingMode,
ConfigIndexBleSetDeviceName,
ConfigIndexBleSetMacAddress,
ConfigIndexBleRandomizeMacAddress,
ConfigIndexBleRestoreDefaults,
ConfigIndexBleRemovePairing,
};
enum ConfigIndexUsb {
ConfigIndexUsbSetManufacturerName = ConfigIndexConnection + 1,
ConfigIndexUsbSetProductName,
ConfigIndexUsbSetVidPid,
ConfigIndexUsbRandomizeVidPid,
ConfigIndexUsbRestoreDefaults,
};
void bad_usb_scene_config_connection_callback(VariableItem* item) {
BadUsbApp* bad_usb = variable_item_get_context(item);
bad_usb_set_interface(
bad_usb,
bad_usb->interface == BadUsbHidInterfaceBle ? BadUsbHidInterfaceUsb :
BadUsbHidInterfaceBle);
variable_item_set_current_value_text(
item, bad_usb->interface == BadUsbHidInterfaceBle ? "BLE" : "USB");
view_dispatcher_send_custom_event(bad_usb->view_dispatcher, ConfigIndexConnection);
}
void bad_usb_scene_config_ble_persist_pairing_callback(VariableItem* item) {
BadUsbApp* bad_usb = variable_item_get_context(item);
bool value = variable_item_get_current_value_index(item);
const BadUsbHidApi* hid = bad_usb_hid_get_interface(bad_usb->interface);
// Apply to current script config
bad_usb->script_hid_cfg.ble.bonding = value;
hid->adjust_config(&bad_usb->script_hid_cfg);
// Set in user config to save in settings file
bad_usb->user_hid_cfg.ble.bonding = value;
variable_item_set_current_value_text(item, value ? "ON" : "OFF");
}
const char* const ble_pairing_mode_names[GapPairingCount] = {
"YesNo",
"PIN Type",
"PIN Y/N",
};
void bad_usb_scene_config_ble_pairing_mode_callback(VariableItem* item) {
BadUsbApp* bad_usb = variable_item_get_context(item);
uint8_t index = variable_item_get_current_value_index(item);
const BadUsbHidApi* hid = bad_usb_hid_get_interface(bad_usb->interface);
// Apply to current script config
bad_usb->script_hid_cfg.ble.pairing = index;
hid->adjust_config(&bad_usb->script_hid_cfg);
// Set in user config to save in settings file
bad_usb->user_hid_cfg.ble.pairing = index;
variable_item_set_current_value_text(item, ble_pairing_mode_names[index]);
}
void bad_usb_scene_config_select_callback(void* context, uint32_t index) {
BadUsbApp* bad_usb = context;
@@ -13,12 +71,59 @@ void bad_usb_scene_config_select_callback(void* context, uint32_t index) {
static void draw_menu(BadUsbApp* bad_usb) {
VariableItemList* var_item_list = bad_usb->var_item_list;
VariableItem* item;
variable_item_list_reset(var_item_list);
variable_item_list_add(var_item_list, "Keyboard Layout (global)", 0, NULL, NULL);
variable_item_list_add(var_item_list, "Remove Pairing", 0, NULL, NULL);
item = variable_item_list_add(
var_item_list, "Connection", 2, bad_usb_scene_config_connection_callback, bad_usb);
variable_item_set_current_value_index(item, bad_usb->interface == BadUsbHidInterfaceBle);
variable_item_set_current_value_text(
item, bad_usb->interface == BadUsbHidInterfaceBle ? "BLE" : "USB");
if(bad_usb->interface == BadUsbHidInterfaceBle) {
BleProfileHidParams* ble_hid_cfg = &bad_usb->script_hid_cfg.ble;
item = variable_item_list_add(
var_item_list,
"Persist Pairing",
2,
bad_usb_scene_config_ble_persist_pairing_callback,
bad_usb);
variable_item_set_current_value_index(item, ble_hid_cfg->bonding);
variable_item_set_current_value_text(item, ble_hid_cfg->bonding ? "ON" : "OFF");
item = variable_item_list_add(
var_item_list,
"Pairing Mode",
GapPairingCount,
bad_usb_scene_config_ble_pairing_mode_callback,
bad_usb);
variable_item_set_current_value_index(item, ble_hid_cfg->pairing);
variable_item_set_current_value_text(item, ble_pairing_mode_names[ble_hid_cfg->pairing]);
variable_item_list_add(var_item_list, "Set Device Name", 0, NULL, NULL);
variable_item_list_add(var_item_list, "Set MAC Address", 0, NULL, NULL);
variable_item_list_add(var_item_list, "Randomize MAC Address", 0, NULL, NULL);
variable_item_list_add(var_item_list, "Restore BLE Defaults", 0, NULL, NULL);
variable_item_list_add(var_item_list, "Remove BLE Pairing", 0, NULL, NULL);
} else {
variable_item_list_add(var_item_list, "Set Manufacturer Name", 0, NULL, NULL);
variable_item_list_add(var_item_list, "Set Product Name", 0, NULL, NULL);
variable_item_list_add(var_item_list, "Set VID and PID", 0, NULL, NULL);
variable_item_list_add(var_item_list, "Randomize VID and PID", 0, NULL, NULL);
variable_item_list_add(var_item_list, "Restore USB Defaults", 0, NULL, NULL);
}
}
void bad_usb_scene_config_on_enter(void* context) {
@@ -28,7 +133,8 @@ void bad_usb_scene_config_on_enter(void* context) {
variable_item_list_set_enter_callback(
var_item_list, bad_usb_scene_config_select_callback, bad_usb);
draw_menu(bad_usb);
variable_item_list_set_selected_item(var_item_list, 0);
variable_item_list_set_selected_item(
var_item_list, scene_manager_get_scene_state(bad_usb->scene_manager, BadUsbSceneConfig));
view_dispatcher_switch_to_view(bad_usb->view_dispatcher, BadUsbAppViewConfig);
}
@@ -38,13 +144,110 @@ bool bad_usb_scene_config_on_event(void* context, SceneManagerEvent event) {
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
scene_manager_set_scene_state(bad_usb->scene_manager, BadUsbSceneConfig, event.event);
consumed = true;
if(event.event == ConfigIndexKeyboardLayout) {
const BadUsbHidApi* hid = bad_usb_hid_get_interface(bad_usb->interface);
switch(event.event) {
case ConfigIndexKeyboardLayout:
scene_manager_next_scene(bad_usb->scene_manager, BadUsbSceneConfigLayout);
} else if(event.event == ConfigIndexBleUnpair) {
scene_manager_next_scene(bad_usb->scene_manager, BadUsbSceneConfirmUnpair);
break;
case ConfigIndexConnection:
// Refresh default values for new interface
hid->adjust_config(&bad_usb->script_hid_cfg);
// Redraw menu with new interface options
draw_menu(bad_usb);
break;
default:
break;
}
if(bad_usb->interface == BadUsbHidInterfaceBle) {
switch(event.event) {
case ConfigIndexBleSetDeviceName:
scene_manager_next_scene(bad_usb->scene_manager, BadUsbSceneConfigBleName);
break;
case ConfigIndexBleSetMacAddress:
scene_manager_next_scene(bad_usb->scene_manager, BadUsbSceneConfigBleMac);
break;
case ConfigIndexBleRandomizeMacAddress:
// Apply to current script config
furi_hal_random_fill_buf(
bad_usb->script_hid_cfg.ble.mac, sizeof(bad_usb->script_hid_cfg.ble.mac));
bad_usb->script_hid_cfg.ble.mac[sizeof(bad_usb->script_hid_cfg.ble.mac) - 1] |=
0b11 << 6; // Set 2 MSB for Random Static Address
hid->adjust_config(&bad_usb->script_hid_cfg);
// Set in user config to save in settings file
memcpy(
bad_usb->user_hid_cfg.ble.mac,
bad_usb->script_hid_cfg.ble.mac,
sizeof(bad_usb->user_hid_cfg.ble.mac));
scene_manager_next_scene(bad_usb->scene_manager, BadUsbSceneDone);
break;
case ConfigIndexBleRestoreDefaults:
// Apply to current script config
bad_usb->script_hid_cfg.ble.name[0] = '\0';
memset(
bad_usb->script_hid_cfg.ble.mac, 0, sizeof(bad_usb->script_hid_cfg.ble.mac));
bad_usb->script_hid_cfg.ble.bonding = true;
bad_usb->script_hid_cfg.ble.pairing = GapPairingPinCodeVerifyYesNo;
hid->adjust_config(&bad_usb->script_hid_cfg);
// Set in user config to save in settings file
memcpy(
&bad_usb->user_hid_cfg.ble,
&bad_usb->script_hid_cfg.ble,
sizeof(bad_usb->user_hid_cfg.ble));
scene_manager_next_scene(bad_usb->scene_manager, BadUsbSceneDone);
break;
case ConfigIndexBleRemovePairing:
scene_manager_next_scene(bad_usb->scene_manager, BadUsbSceneConfirmUnpair);
break;
default:
break;
}
} else {
furi_crash("Unknown key type");
switch(event.event) {
case ConfigIndexUsbSetManufacturerName:
scene_manager_set_scene_state(
bad_usb->scene_manager, BadUsbSceneConfigUsbName, true);
scene_manager_next_scene(bad_usb->scene_manager, BadUsbSceneConfigUsbName);
break;
case ConfigIndexUsbSetProductName:
scene_manager_set_scene_state(
bad_usb->scene_manager, BadUsbSceneConfigUsbName, false);
scene_manager_next_scene(bad_usb->scene_manager, BadUsbSceneConfigUsbName);
break;
case ConfigIndexUsbSetVidPid:
scene_manager_next_scene(bad_usb->scene_manager, BadUsbSceneConfigUsbVidPid);
break;
case ConfigIndexUsbRandomizeVidPid:
furi_hal_random_fill_buf(
(void*)bad_usb->usb_vidpid_buf, sizeof(bad_usb->usb_vidpid_buf));
// Apply to current script config
bad_usb->script_hid_cfg.usb.vid = bad_usb->usb_vidpid_buf[0];
bad_usb->script_hid_cfg.usb.pid = bad_usb->usb_vidpid_buf[1];
hid->adjust_config(&bad_usb->script_hid_cfg);
// Set in user config to save in settings file
bad_usb->user_hid_cfg.usb.vid = bad_usb->script_hid_cfg.usb.vid;
bad_usb->user_hid_cfg.usb.pid = bad_usb->script_hid_cfg.usb.pid;
scene_manager_next_scene(bad_usb->scene_manager, BadUsbSceneDone);
break;
case ConfigIndexUsbRestoreDefaults:
// Apply to current script config
bad_usb->script_hid_cfg.usb.vid = 0;
bad_usb->script_hid_cfg.usb.pid = 0;
bad_usb->script_hid_cfg.usb.manuf[0] = '\0';
bad_usb->script_hid_cfg.usb.product[0] = '\0';
hid->adjust_config(&bad_usb->script_hid_cfg);
// Set in user config to save in settings file
memcpy(
&bad_usb->user_hid_cfg.usb,
&bad_usb->script_hid_cfg.usb,
sizeof(bad_usb->user_hid_cfg.usb));
scene_manager_next_scene(bad_usb->scene_manager, BadUsbSceneDone);
break;
default:
break;
}
}
}

View File

@@ -3,5 +3,9 @@ ADD_SCENE(bad_usb, work, Work)
ADD_SCENE(bad_usb, error, Error)
ADD_SCENE(bad_usb, config, Config)
ADD_SCENE(bad_usb, config_layout, ConfigLayout)
ADD_SCENE(bad_usb, config_ble_name, ConfigBleName)
ADD_SCENE(bad_usb, config_ble_mac, ConfigBleMac)
ADD_SCENE(bad_usb, config_usb_name, ConfigUsbName)
ADD_SCENE(bad_usb, config_usb_vidpid, ConfigUsbVidPid)
ADD_SCENE(bad_usb, confirm_unpair, ConfirmUnpair)
ADD_SCENE(bad_usb, unpair_done, UnpairDone)
ADD_SCENE(bad_usb, done, Done)

View File

@@ -0,0 +1,73 @@
#include "../bad_usb_app_i.h"
enum ByteInputResult {
ByteInputResultOk,
};
static void reverse_mac_addr(uint8_t mac_addr[GAP_MAC_ADDR_SIZE]) {
uint8_t tmp;
for(size_t i = 0; i < GAP_MAC_ADDR_SIZE / 2; i++) {
tmp = mac_addr[i];
mac_addr[i] = mac_addr[GAP_MAC_ADDR_SIZE - 1 - i];
mac_addr[GAP_MAC_ADDR_SIZE - 1 - i] = tmp;
}
}
void bad_usb_scene_config_ble_mac_byte_input_callback(void* context) {
BadUsbApp* bad_usb = context;
view_dispatcher_send_custom_event(bad_usb->view_dispatcher, ByteInputResultOk);
}
void bad_usb_scene_config_ble_mac_on_enter(void* context) {
BadUsbApp* bad_usb = context;
ByteInput* byte_input = bad_usb->byte_input;
memcpy(bad_usb->ble_mac_buf, bad_usb->script_hid_cfg.ble.mac, sizeof(bad_usb->ble_mac_buf));
reverse_mac_addr(bad_usb->ble_mac_buf);
byte_input_set_header_text(byte_input, "Set BLE MAC address");
byte_input_set_result_callback(
byte_input,
bad_usb_scene_config_ble_mac_byte_input_callback,
NULL,
bad_usb,
bad_usb->ble_mac_buf,
sizeof(bad_usb->ble_mac_buf));
view_dispatcher_switch_to_view(bad_usb->view_dispatcher, BadUsbAppViewByteInput);
}
bool bad_usb_scene_config_ble_mac_on_event(void* context, SceneManagerEvent event) {
BadUsbApp* bad_usb = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
consumed = true;
if(event.event == ByteInputResultOk) {
const BadUsbHidApi* hid = bad_usb_hid_get_interface(bad_usb->interface);
reverse_mac_addr(bad_usb->ble_mac_buf);
// Apply to current script config
memcpy(
bad_usb->script_hid_cfg.ble.mac,
bad_usb->ble_mac_buf,
sizeof(bad_usb->script_hid_cfg.ble.mac));
hid->adjust_config(&bad_usb->script_hid_cfg);
// Set in user config to save in settings file
memcpy(
bad_usb->user_hid_cfg.ble.mac,
bad_usb->ble_mac_buf,
sizeof(bad_usb->user_hid_cfg.ble.mac));
}
scene_manager_previous_scene(bad_usb->scene_manager);
}
return consumed;
}
void bad_usb_scene_config_ble_mac_on_exit(void* context) {
BadUsbApp* bad_usb = context;
ByteInput* byte_input = bad_usb->byte_input;
byte_input_set_result_callback(byte_input, NULL, NULL, NULL, NULL, 0);
byte_input_set_header_text(byte_input, "");
}

View File

@@ -0,0 +1,62 @@
#include "../bad_usb_app_i.h"
enum TextInputResult {
TextInputResultOk,
};
static void bad_usb_scene_config_ble_name_text_input_callback(void* context) {
BadUsbApp* bad_usb = context;
view_dispatcher_send_custom_event(bad_usb->view_dispatcher, TextInputResultOk);
}
void bad_usb_scene_config_ble_name_on_enter(void* context) {
BadUsbApp* bad_usb = context;
TextInput* text_input = bad_usb->text_input;
strlcpy(
bad_usb->ble_name_buf, bad_usb->script_hid_cfg.ble.name, sizeof(bad_usb->ble_name_buf));
text_input_set_header_text(text_input, "Set BLE device name");
text_input_set_result_callback(
text_input,
bad_usb_scene_config_ble_name_text_input_callback,
bad_usb,
bad_usb->ble_name_buf,
sizeof(bad_usb->ble_name_buf),
true);
view_dispatcher_switch_to_view(bad_usb->view_dispatcher, BadUsbAppViewTextInput);
}
bool bad_usb_scene_config_ble_name_on_event(void* context, SceneManagerEvent event) {
BadUsbApp* bad_usb = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
consumed = true;
if(event.event == TextInputResultOk) {
const BadUsbHidApi* hid = bad_usb_hid_get_interface(bad_usb->interface);
// Apply to current script config
strlcpy(
bad_usb->script_hid_cfg.ble.name,
bad_usb->ble_name_buf,
sizeof(bad_usb->script_hid_cfg.ble.name));
hid->adjust_config(&bad_usb->script_hid_cfg);
// Set in user config to save in settings file
strlcpy(
bad_usb->user_hid_cfg.ble.name,
bad_usb->ble_name_buf,
sizeof(bad_usb->user_hid_cfg.ble.name));
}
scene_manager_previous_scene(bad_usb->scene_manager);
}
return consumed;
}
void bad_usb_scene_config_ble_name_on_exit(void* context) {
BadUsbApp* bad_usb = context;
TextInput* text_input = bad_usb->text_input;
text_input_reset(text_input);
}

View File

@@ -29,21 +29,17 @@ static bool bad_usb_layout_select(BadUsbApp* bad_usb) {
void bad_usb_scene_config_layout_on_enter(void* context) {
BadUsbApp* bad_usb = context;
if(bad_usb_layout_select(bad_usb)) {
scene_manager_search_and_switch_to_previous_scene(bad_usb->scene_manager, BadUsbSceneWork);
} else {
scene_manager_previous_scene(bad_usb->scene_manager);
}
bad_usb_layout_select(bad_usb);
scene_manager_previous_scene(bad_usb->scene_manager);
}
bool bad_usb_scene_config_layout_on_event(void* context, SceneManagerEvent event) {
UNUSED(context);
UNUSED(event);
// BadUsbApp* bad_usb = context;
return false;
}
void bad_usb_scene_config_layout_on_exit(void* context) {
UNUSED(context);
// BadUsbApp* bad_usb = context;
}

View File

@@ -0,0 +1,86 @@
#include "../bad_usb_app_i.h"
enum TextInputResult {
TextInputResultOk,
};
static void bad_usb_scene_config_usb_name_text_input_callback(void* context) {
BadUsbApp* bad_usb = context;
view_dispatcher_send_custom_event(bad_usb->view_dispatcher, TextInputResultOk);
}
void bad_usb_scene_config_usb_name_on_enter(void* context) {
BadUsbApp* bad_usb = context;
TextInput* text_input = bad_usb->text_input;
if(scene_manager_get_scene_state(bad_usb->scene_manager, BadUsbSceneConfigUsbName)) {
strlcpy(
bad_usb->usb_name_buf,
bad_usb->script_hid_cfg.usb.manuf,
sizeof(bad_usb->usb_name_buf));
text_input_set_header_text(text_input, "Set USB manufacturer name");
} else {
strlcpy(
bad_usb->usb_name_buf,
bad_usb->script_hid_cfg.usb.product,
sizeof(bad_usb->usb_name_buf));
text_input_set_header_text(text_input, "Set USB product name");
}
text_input_set_result_callback(
text_input,
bad_usb_scene_config_usb_name_text_input_callback,
bad_usb,
bad_usb->usb_name_buf,
sizeof(bad_usb->usb_name_buf),
true);
view_dispatcher_switch_to_view(bad_usb->view_dispatcher, BadUsbAppViewTextInput);
}
bool bad_usb_scene_config_usb_name_on_event(void* context, SceneManagerEvent event) {
BadUsbApp* bad_usb = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
consumed = true;
if(event.event == TextInputResultOk) {
const BadUsbHidApi* hid = bad_usb_hid_get_interface(bad_usb->interface);
if(scene_manager_get_scene_state(bad_usb->scene_manager, BadUsbSceneConfigUsbName)) {
// Apply to current script config
strlcpy(
bad_usb->script_hid_cfg.usb.manuf,
bad_usb->usb_name_buf,
sizeof(bad_usb->script_hid_cfg.usb.manuf));
hid->adjust_config(&bad_usb->script_hid_cfg);
// Set in user config to save in settings file
strlcpy(
bad_usb->user_hid_cfg.usb.manuf,
bad_usb->usb_name_buf,
sizeof(bad_usb->user_hid_cfg.usb.manuf));
} else {
// Apply to current script config
strlcpy(
bad_usb->script_hid_cfg.usb.product,
bad_usb->usb_name_buf,
sizeof(bad_usb->script_hid_cfg.usb.product));
hid->adjust_config(&bad_usb->script_hid_cfg);
// Set in user config to save in settings file
strlcpy(
bad_usb->user_hid_cfg.usb.product,
bad_usb->usb_name_buf,
sizeof(bad_usb->user_hid_cfg.usb.product));
}
}
scene_manager_previous_scene(bad_usb->scene_manager);
}
return consumed;
}
void bad_usb_scene_config_usb_name_on_exit(void* context) {
BadUsbApp* bad_usb = context;
TextInput* text_input = bad_usb->text_input;
text_input_reset(text_input);
}

View File

@@ -0,0 +1,59 @@
#include "../bad_usb_app_i.h"
enum ByteInputResult {
ByteInputResultOk,
};
void bad_usb_scene_config_usb_vidpid_byte_input_callback(void* context) {
BadUsbApp* bad_usb = context;
view_dispatcher_send_custom_event(bad_usb->view_dispatcher, ByteInputResultOk);
}
void bad_usb_scene_config_usb_vidpid_on_enter(void* context) {
BadUsbApp* bad_usb = context;
ByteInput* byte_input = bad_usb->byte_input;
bad_usb->usb_vidpid_buf[0] = __builtin_bswap16(bad_usb->script_hid_cfg.usb.vid);
bad_usb->usb_vidpid_buf[1] = __builtin_bswap16(bad_usb->script_hid_cfg.usb.pid);
byte_input_set_header_text(byte_input, "Set USB VID:PID");
byte_input_set_result_callback(
byte_input,
bad_usb_scene_config_usb_vidpid_byte_input_callback,
NULL,
bad_usb,
(void*)bad_usb->usb_vidpid_buf,
sizeof(bad_usb->usb_vidpid_buf));
view_dispatcher_switch_to_view(bad_usb->view_dispatcher, BadUsbAppViewByteInput);
}
bool bad_usb_scene_config_usb_vidpid_on_event(void* context, SceneManagerEvent event) {
BadUsbApp* bad_usb = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
consumed = true;
if(event.event == ByteInputResultOk) {
const BadUsbHidApi* hid = bad_usb_hid_get_interface(bad_usb->interface);
// Apply to current script config
bad_usb->script_hid_cfg.usb.vid = __builtin_bswap16(bad_usb->usb_vidpid_buf[0]);
bad_usb->script_hid_cfg.usb.pid = __builtin_bswap16(bad_usb->usb_vidpid_buf[1]);
hid->adjust_config(&bad_usb->script_hid_cfg);
// Set in user config to save in settings file
bad_usb->user_hid_cfg.usb.vid = bad_usb->script_hid_cfg.usb.vid;
bad_usb->user_hid_cfg.usb.pid = bad_usb->script_hid_cfg.usb.pid;
}
scene_manager_previous_scene(bad_usb->scene_manager);
}
return consumed;
}
void bad_usb_scene_config_usb_vidpid_on_exit(void* context) {
BadUsbApp* bad_usb = context;
ByteInput* byte_input = bad_usb->byte_input;
byte_input_set_result_callback(byte_input, NULL, NULL, NULL, NULL, 0);
byte_input_set_header_text(byte_input, "");
}

View File

@@ -36,7 +36,8 @@ bool bad_usb_scene_confirm_unpair_on_event(void* context, SceneManagerEvent even
if(event.type == SceneManagerEventTypeCustom) {
consumed = true;
if(event.event == GuiButtonTypeRight) {
scene_manager_next_scene(scene_manager, BadUsbSceneUnpairDone);
bad_usb_hid_ble_remove_pairing();
scene_manager_next_scene(scene_manager, BadUsbSceneDone);
} else if(event.event == GuiButtonTypeLeft) {
scene_manager_previous_scene(scene_manager);
}

View File

@@ -1,19 +1,17 @@
#include "../bad_usb_app_i.h"
static void bad_usb_scene_unpair_done_popup_callback(void* context) {
static void bad_usb_scene_done_popup_callback(void* context) {
BadUsbApp* bad_usb = context;
scene_manager_search_and_switch_to_previous_scene(bad_usb->scene_manager, BadUsbSceneConfig);
}
void bad_usb_scene_unpair_done_on_enter(void* context) {
void bad_usb_scene_done_on_enter(void* context) {
BadUsbApp* bad_usb = context;
Popup* popup = bad_usb->popup;
bad_usb_hid_ble_remove_pairing();
popup_set_icon(popup, 48, 4, &I_DolphinDone_80x58);
popup_set_header(popup, "Done", 20, 19, AlignLeft, AlignBottom);
popup_set_callback(popup, bad_usb_scene_unpair_done_popup_callback);
popup_set_callback(popup, bad_usb_scene_done_popup_callback);
popup_set_context(popup, bad_usb);
popup_set_timeout(popup, 1500);
popup_enable_timeout(popup);
@@ -21,7 +19,7 @@ void bad_usb_scene_unpair_done_on_enter(void* context) {
view_dispatcher_switch_to_view(bad_usb->view_dispatcher, BadUsbAppViewPopup);
}
bool bad_usb_scene_unpair_done_on_event(void* context, SceneManagerEvent event) {
bool bad_usb_scene_done_on_event(void* context, SceneManagerEvent event) {
BadUsbApp* bad_usb = context;
UNUSED(bad_usb);
UNUSED(event);
@@ -30,7 +28,7 @@ bool bad_usb_scene_unpair_done_on_event(void* context, SceneManagerEvent event)
return consumed;
}
void bad_usb_scene_unpair_done_on_exit(void* context) {
void bad_usb_scene_done_on_exit(void* context) {
BadUsbApp* bad_usb = context;
Popup* popup = bad_usb->popup;
UNUSED(popup);

View File

@@ -1,5 +1,4 @@
#include "../bad_usb_app_i.h"
#include <furi_hal_power.h>
#include <storage/storage.h>
static bool bad_usb_file_select(BadUsbApp* bad_usb) {
@@ -27,6 +26,7 @@ void bad_usb_scene_file_select_on_enter(void* context) {
}
if(bad_usb_file_select(bad_usb)) {
scene_manager_set_scene_state(bad_usb->scene_manager, BadUsbSceneWork, true);
scene_manager_next_scene(bad_usb->scene_manager, BadUsbSceneWork);
} else {
view_dispatcher_stop(bad_usb->view_dispatcher);
@@ -36,11 +36,9 @@ void bad_usb_scene_file_select_on_enter(void* context) {
bool bad_usb_scene_file_select_on_event(void* context, SceneManagerEvent event) {
UNUSED(context);
UNUSED(event);
// BadUsbApp* bad_usb = context;
return false;
}
void bad_usb_scene_file_select_on_exit(void* context) {
UNUSED(context);
// BadUsbApp* bad_usb = context;
}

View File

@@ -20,11 +20,8 @@ bool bad_usb_scene_work_on_event(void* context, SceneManagerEvent event) {
bad_usb_script_close(app->bad_usb_script);
app->bad_usb_script = NULL;
if(app->interface == BadUsbHidInterfaceBle) {
scene_manager_next_scene(app->scene_manager, BadUsbSceneConfig);
} else {
scene_manager_next_scene(app->scene_manager, BadUsbSceneConfigLayout);
}
scene_manager_set_scene_state(app->scene_manager, BadUsbSceneConfig, 0);
scene_manager_next_scene(app->scene_manager, BadUsbSceneConfig);
}
consumed = true;
} else if(event.event == InputKeyOk) {
@@ -37,7 +34,9 @@ bool bad_usb_scene_work_on_event(void* context, SceneManagerEvent event) {
app->interface == BadUsbHidInterfaceBle ? BadUsbHidInterfaceUsb :
BadUsbHidInterfaceBle);
bad_usb_script_close(app->bad_usb_script);
app->bad_usb_script = bad_usb_script_open(app->file_path, app->interface);
app->bad_usb_script = bad_usb_script_open(
app->file_path, &app->interface, &app->script_hid_cfg, false);
bad_usb_script_set_keyboard_layout(app->bad_usb_script, app->keyboard_layout);
} else {
bad_usb_script_pause_resume(app->bad_usb_script);
}
@@ -45,6 +44,7 @@ bool bad_usb_scene_work_on_event(void* context, SceneManagerEvent event) {
}
} else if(event.type == SceneManagerEventTypeTick) {
bad_usb_view_set_state(app->bad_usb_view, bad_usb_script_get_state(app->bad_usb_script));
bad_usb_view_set_interface(app->bad_usb_view, app->interface);
}
return consumed;
}
@@ -54,7 +54,18 @@ void bad_usb_scene_work_on_enter(void* context) {
bad_usb_view_set_interface(app->bad_usb_view, app->interface);
app->bad_usb_script = bad_usb_script_open(app->file_path, app->interface);
// Opening script the first time:
// - copy user settings as base config
// - load ID/BLE_ID/BT_ID config if present
// Then disable this until next script selected so user can customize options
bool first_script_load = scene_manager_get_scene_state(app->scene_manager, BadUsbSceneWork);
if(first_script_load) {
memcpy(&app->script_hid_cfg, &app->user_hid_cfg, sizeof(app->script_hid_cfg));
scene_manager_set_scene_state(app->scene_manager, BadUsbSceneWork, false);
}
// Interface and config are passed as pointers as ID/BLE_ID/BT_ID config can modify them
app->bad_usb_script = bad_usb_script_open(
app->file_path, &app->interface, &app->script_hid_cfg, first_script_load);
bad_usb_script_set_keyboard_layout(app->bad_usb_script, app->keyboard_layout);
FuriString* file_name;

View File

@@ -24,8 +24,7 @@ typedef struct {
static void bad_usb_draw_callback(Canvas* canvas, void* _model) {
BadUsbModel* model = _model;
FuriString* disp_str;
disp_str = furi_string_alloc_set(model->file_name);
FuriString* 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));
@@ -35,6 +34,8 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) {
} else {
furi_string_printf(disp_str, "(%s)", model->layout);
}
uint32_t e = model->state.elapsed;
furi_string_cat_printf(disp_str, " %02lu:%02lu.%ld", e / 60 / 1000, e / 1000, e % 1000);
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));
@@ -52,13 +53,8 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) {
if((state == BadUsbStateIdle) || (state == BadUsbStateDone) ||
(state == BadUsbStateNotConnected)) {
elements_button_center(canvas, "Run");
if(model->interface == BadUsbHidInterfaceBle) {
elements_button_right(canvas, "USB");
elements_button_left(canvas, "Config");
} else {
elements_button_right(canvas, "BLE");
elements_button_left(canvas, "Layout");
}
elements_button_left(canvas, "Config");
elements_button_right(canvas, model->interface == BadUsbHidInterfaceBle ? "USB" : "BLE");
} else if((state == BadUsbStateRunning) || (state == BadUsbStateDelay)) {
elements_button_center(canvas, "Stop");
if(!model->pause_wait) {
@@ -90,77 +86,85 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) {
canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "ERROR");
} else if(state == BadUsbStateScriptError) {
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 %zu", model->state.error_line);
canvas_draw_str_aligned(
canvas, 127, 46, AlignRight, AlignBottom, furi_string_get_cstr(disp_str));
furi_string_reset(disp_str);
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);
canvas_set_font(canvas, FontPrimary);
canvas_draw_str_aligned(canvas, 127, 33, AlignRight, AlignBottom, "ERROR:");
} else if(state == BadUsbStateIdle) {
canvas_draw_icon(canvas, 4, 26, &I_Smile_18x18);
furi_string_printf(disp_str, "0/%zu", model->state.line_nb);
canvas_draw_str_aligned(
canvas, 124, 47, AlignRight, AlignBottom, furi_string_get_cstr(disp_str));
canvas_set_font(canvas, FontBigNumbers);
canvas_draw_str_aligned(canvas, 114, 40, AlignRight, AlignBottom, "0");
canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14);
canvas_draw_str_aligned(canvas, 112, 37, AlignRight, AlignBottom, "0");
canvas_draw_icon(canvas, 115, 23, &I_Percent_10x14);
} else if(state == BadUsbStateRunning) {
if(model->anim_frame == 0) {
canvas_draw_icon(canvas, 4, 23, &I_EviSmile1_18x21);
} else {
canvas_draw_icon(canvas, 4, 23, &I_EviSmile2_18x21);
}
furi_string_printf(disp_str, "%zu/%zu", model->state.line_cur, model->state.line_nb);
canvas_draw_str_aligned(
canvas, 124, 47, AlignRight, AlignBottom, furi_string_get_cstr(disp_str));
canvas_set_font(canvas, FontBigNumbers);
furi_string_printf(
disp_str, "%zu", ((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, 112, 37, AlignRight, AlignBottom, furi_string_get_cstr(disp_str));
canvas_draw_icon(canvas, 115, 23, &I_Percent_10x14);
} else if(state == BadUsbStateDone) {
canvas_draw_icon(canvas, 4, 23, &I_EviSmile1_18x21);
furi_string_printf(disp_str, "%zu/%zu", model->state.line_nb, model->state.line_nb);
canvas_draw_str_aligned(
canvas, 124, 47, AlignRight, AlignBottom, furi_string_get_cstr(disp_str));
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);
canvas_draw_str_aligned(canvas, 112, 37, AlignRight, AlignBottom, "100");
canvas_draw_icon(canvas, 115, 23, &I_Percent_10x14);
} else if(state == BadUsbStateDelay) {
if(model->anim_frame == 0) {
canvas_draw_icon(canvas, 4, 23, &I_EviWaiting1_18x21);
} else {
canvas_draw_icon(canvas, 4, 23, &I_EviWaiting2_18x21);
}
uint32_t delay = model->state.delay_remain / 10;
if(delay) {
furi_string_printf(disp_str, "Delay %lus", delay);
canvas_draw_str_aligned(
canvas, 4, 61, AlignLeft, AlignBottom, furi_string_get_cstr(disp_str));
}
furi_string_printf(disp_str, "%zu/%zu", model->state.line_cur, model->state.line_nb);
canvas_draw_str_aligned(
canvas, 124, 47, AlignRight, AlignBottom, furi_string_get_cstr(disp_str));
canvas_set_font(canvas, FontBigNumbers);
furi_string_printf(
disp_str, "%zu", ((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);
canvas, 112, 37, AlignRight, AlignBottom, furi_string_get_cstr(disp_str));
canvas_draw_icon(canvas, 115, 23, &I_Percent_10x14);
} else if((state == BadUsbStatePaused) || (state == BadUsbStateWaitForBtn)) {
if(model->anim_frame == 0) {
canvas_draw_icon(canvas, 4, 23, &I_EviWaiting1_18x21);
} else {
canvas_draw_icon(canvas, 4, 23, &I_EviWaiting2_18x21);
}
if(state != BadUsbStateWaitForBtn) {
canvas_draw_str_aligned(canvas, 4, 61, AlignLeft, AlignBottom, "Paused");
}
furi_string_printf(disp_str, "%zu/%zu", model->state.line_cur, model->state.line_nb);
canvas_draw_str_aligned(
canvas, 124, 47, AlignRight, AlignBottom, furi_string_get_cstr(disp_str));
canvas_set_font(canvas, FontBigNumbers);
furi_string_printf(
disp_str, "%zu", ((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);
canvas_draw_str_aligned(canvas, 127, 50, AlignRight, AlignBottom, "Paused");
furi_string_reset(disp_str);
canvas, 112, 37, AlignRight, AlignBottom, furi_string_get_cstr(disp_str));
canvas_draw_icon(canvas, 115, 23, &I_Percent_10x14);
} else {
canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18);
}

View File

@@ -9,12 +9,12 @@
#include <usb_hid.h>
#include <ble/ble.h>
#define HID_INFO_BASE_USB_SPECIFICATION (0x0101)
#define HID_INFO_COUNTRY_CODE (0x00)
#define BLE_PROFILE_HID_INFO_FLAG_REMOTE_WAKE_MSK (0x01)
#define HID_INFO_BASE_USB_SPECIFICATION (0x0101)
#define HID_INFO_COUNTRY_CODE (0x00)
#define BLE_PROFILE_HID_INFO_FLAG_REMOTE_WAKE_MSK (0x01)
#define BLE_PROFILE_HID_INFO_FLAG_NORMALLY_CONNECTABLE_MSK (0x02)
#define BLE_PROFILE_HID_KB_MAX_KEYS (6)
#define BLE_PROFILE_HID_KB_MAX_KEYS (6)
#define BLE_PROFILE_CONSUMER_MAX_KEYS (1)
// Report ids cant be 0
@@ -380,10 +380,11 @@ bool ble_profile_hid_mouse_scroll(FuriHalBleProfileBase* profile, int8_t delta)
#define CONNECTION_INTERVAL_MAX (0x24)
static GapConfig template_config = {
.adv_service = {
.UUID_Type = UUID_TYPE_16,
.Service_UUID_16 = HUMAN_INTERFACE_DEVICE_SERVICE_UUID,
},
.adv_service =
{
.UUID_Type = UUID_TYPE_16,
.Service_UUID_16 = HUMAN_INTERFACE_DEVICE_SERVICE_UUID,
},
.appearance_char = GAP_APPEARANCE_KEYBOARD,
.bonding_mode = true,
.pairing_method = GapPairingPinCodeVerifyYesNo,
@@ -412,19 +413,17 @@ static void ble_profile_hid_get_config(GapConfig* config, FuriHalBleProfileParam
}
// Set advertise name
memset(config->adv_name, 0, sizeof(config->adv_name));
FuriString* name = furi_string_alloc_set(furi_hal_version_get_ble_local_device_name_ptr());
const char* clicker_str = "Control";
if(hid_profile_params && hid_profile_params->device_name_prefix) {
clicker_str = hid_profile_params->device_name_prefix;
}
furi_string_replace_str(name, "Flipper", clicker_str);
if(furi_string_size(name) >= sizeof(config->adv_name)) {
furi_string_left(name, sizeof(config->adv_name) - 1);
}
memcpy(config->adv_name, furi_string_get_cstr(name), furi_string_size(name));
furi_string_free(name);
snprintf(
config->adv_name,
sizeof(config->adv_name),
"%c%s %s",
furi_hal_version_get_ble_local_device_name_ptr()[0],
clicker_str,
furi_hal_version_get_name_ptr());
}
static const FuriHalBleProfileTemplate profile_callbacks = {

View File

@@ -12,9 +12,6 @@
#define HID_INTERVAL 2
#define HID_VID_DEFAULT 0x046D
#define HID_PID_DEFAULT 0xC529
struct HidIntfDescriptor {
struct usb_interface_descriptor hid;
struct usb_hid_descriptor hid_desc;

View File

@@ -9,6 +9,11 @@
extern "C" {
#endif
#define HID_MANUF_PRODUCT_NAME_LEN 32
#define HID_VID_DEFAULT 0x046D
#define HID_PID_DEFAULT 0xC529
/** Max number of simultaneously pressed keys (keyboard) */
#define HID_KB_MAX_KEYS 6
/** Max number of simultaneously pressed keys (consumer control) */
@@ -166,10 +171,11 @@ static const uint16_t hid_asciimap[] = {
};
typedef struct {
// Note: vid/pid should be uint16_t and are treated as such
uint32_t vid;
uint32_t pid;
char manuf[32];
char product[32];
char manuf[HID_MANUF_PRODUCT_NAME_LEN];
char product[HID_MANUF_PRODUCT_NAME_LEN];
} FuriHalUsbHidConfig;
typedef void (*HidStateCallback)(bool state, void* context);

View File

@@ -14,11 +14,12 @@
extern "C" {
#endif
#define FURI_HAL_VERSION_NAME_LENGTH 8
#define FURI_HAL_VERSION_ARRAY_NAME_LENGTH (FURI_HAL_VERSION_NAME_LENGTH + 1)
#define FURI_HAL_BT_ADV_NAME_LENGTH (18 + 1) // 18 characters + null terminator
#define FURI_HAL_VERSION_DEVICE_NAME_LENGTH \
(1 + FURI_HAL_BT_ADV_NAME_LENGTH) // Used for custom BT name, BLE symbol + name
#define FURI_HAL_VERSION_NAME_LENGTH (8)
#define FURI_HAL_VERSION_ARRAY_NAME_LENGTH (FURI_HAL_VERSION_NAME_LENGTH + 1)
/** 31b BLE Adv - 3b flags - 2b name prefix - 4b service uuid - 3b tx power = 19, + 1b null terminator (not present in packet) */
#define FURI_HAL_BT_ADV_NAME_LENGTH (20)
/** BLE symbol + name */
#define FURI_HAL_VERSION_DEVICE_NAME_LENGTH (1 + FURI_HAL_BT_ADV_NAME_LENGTH)
/** OTP Versions enum */
typedef enum {