Merge remote-tracking branch 'noproto/dev' into ulcdict

This commit is contained in:
noproto
2024-10-28 21:24:23 -04:00
71 changed files with 701 additions and 3012 deletions

View File

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

View File

@@ -35,6 +35,7 @@ static void bad_usb_load_settings(BadUsbApp* app) {
FuriString* temp_str = furi_string_alloc(); FuriString* temp_str = furi_string_alloc();
uint32_t version = 0; uint32_t version = 0;
uint32_t interface = 0;
if(flipper_format_file_open_existing(fff, BAD_USB_SETTINGS_PATH)) { if(flipper_format_file_open_existing(fff, BAD_USB_SETTINGS_PATH)) {
do { do {
@@ -44,6 +45,8 @@ static void bad_usb_load_settings(BadUsbApp* app) {
break; break;
if(!flipper_format_read_string(fff, "layout", temp_str)) 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;
state = true; state = true;
} while(0); } while(0);
@@ -53,6 +56,7 @@ static void bad_usb_load_settings(BadUsbApp* app) {
if(state) { if(state) {
furi_string_set(app->keyboard_layout, temp_str); furi_string_set(app->keyboard_layout, temp_str);
app->interface = interface;
Storage* fs_api = furi_record_open(RECORD_STORAGE); Storage* fs_api = furi_record_open(RECORD_STORAGE);
FileInfo layout_file_info; FileInfo layout_file_info;
@@ -64,6 +68,7 @@ static void bad_usb_load_settings(BadUsbApp* app) {
} }
} else { } else {
furi_string_set(app->keyboard_layout, BAD_USB_SETTINGS_DEFAULT_LAYOUT); furi_string_set(app->keyboard_layout, BAD_USB_SETTINGS_DEFAULT_LAYOUT);
app->interface = BadUsbHidInterfaceUsb;
} }
furi_string_free(temp_str); furi_string_free(temp_str);
@@ -79,6 +84,9 @@ static void bad_usb_save_settings(BadUsbApp* app) {
fff, BAD_USB_SETTINGS_FILE_TYPE, BAD_USB_SETTINGS_VERSION)) fff, BAD_USB_SETTINGS_FILE_TYPE, BAD_USB_SETTINGS_VERSION))
break; break;
if(!flipper_format_write_string(fff, "layout", app->keyboard_layout)) 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))
break;
} while(0); } while(0);
} }
@@ -86,6 +94,11 @@ static void bad_usb_save_settings(BadUsbApp* app) {
furi_record_close(RECORD_STORAGE); furi_record_close(RECORD_STORAGE);
} }
void bad_usb_set_interface(BadUsbApp* app, BadUsbHidInterface interface) {
app->interface = interface;
bad_usb_view_set_interface(app->bad_usb_view, interface);
}
BadUsbApp* bad_usb_app_alloc(char* arg) { BadUsbApp* bad_usb_app_alloc(char* arg) {
BadUsbApp* app = malloc(sizeof(BadUsbApp)); BadUsbApp* app = malloc(sizeof(BadUsbApp));
@@ -117,7 +130,11 @@ BadUsbApp* bad_usb_app_alloc(char* arg) {
// Custom Widget // Custom Widget
app->widget = widget_alloc(); app->widget = widget_alloc();
view_dispatcher_add_view( view_dispatcher_add_view(
app->view_dispatcher, BadUsbAppViewError, widget_get_view(app->widget)); app->view_dispatcher, BadUsbAppViewWidget, widget_get_view(app->widget));
// Popup
app->popup = popup_alloc();
view_dispatcher_add_view(app->view_dispatcher, BadUsbAppViewPopup, popup_get_view(app->popup));
app->var_item_list = variable_item_list_alloc(); app->var_item_list = variable_item_list_alloc();
view_dispatcher_add_view( view_dispatcher_add_view(
@@ -163,9 +180,13 @@ void bad_usb_app_free(BadUsbApp* app) {
bad_usb_view_free(app->bad_usb_view); bad_usb_view_free(app->bad_usb_view);
// Custom Widget // Custom Widget
view_dispatcher_remove_view(app->view_dispatcher, BadUsbAppViewError); view_dispatcher_remove_view(app->view_dispatcher, BadUsbAppViewWidget);
widget_free(app->widget); widget_free(app->widget);
// Popup
view_dispatcher_remove_view(app->view_dispatcher, BadUsbAppViewPopup);
popup_free(app->popup);
// Config menu // Config menu
view_dispatcher_remove_view(app->view_dispatcher, BadUsbAppViewConfig); view_dispatcher_remove_view(app->view_dispatcher, BadUsbAppViewConfig);
variable_item_list_free(app->var_item_list); variable_item_list_free(app->var_item_list);

View File

@@ -13,6 +13,7 @@
#include <notification/notification_messages.h> #include <notification/notification_messages.h>
#include <gui/modules/variable_item_list.h> #include <gui/modules/variable_item_list.h>
#include <gui/modules/widget.h> #include <gui/modules/widget.h>
#include <gui/modules/popup.h>
#include "views/bad_usb_view.h" #include "views/bad_usb_view.h"
#include <furi_hal_usb.h> #include <furi_hal_usb.h>
@@ -33,6 +34,7 @@ struct BadUsbApp {
NotificationApp* notifications; NotificationApp* notifications;
DialogsApp* dialogs; DialogsApp* dialogs;
Widget* widget; Widget* widget;
Popup* popup;
VariableItemList* var_item_list; VariableItemList* var_item_list;
BadUsbAppError error; BadUsbAppError error;
@@ -41,11 +43,15 @@ struct BadUsbApp {
BadUsb* bad_usb_view; BadUsb* bad_usb_view;
BadUsbScript* bad_usb_script; BadUsbScript* bad_usb_script;
BadUsbHidInterface interface;
FuriHalUsbInterface* usb_if_prev; FuriHalUsbInterface* usb_if_prev;
}; };
typedef enum { typedef enum {
BadUsbAppViewError, BadUsbAppViewWidget,
BadUsbAppViewPopup,
BadUsbAppViewWork, BadUsbAppViewWork,
BadUsbAppViewConfig, BadUsbAppViewConfig,
} BadUsbAppView; } BadUsbAppView;
void bad_usb_set_interface(BadUsbApp* app, BadUsbHidInterface interface);

View File

@@ -1,9 +1,12 @@
#include "bad_usb_hid.h" #include "bad_usb_hid.h"
#include <extra_profiles/hid_profile.h> #include <extra_profiles/hid_profile.h>
#include <bt/bt_service/bt.h>
#include <storage/storage.h> #include <storage/storage.h>
#define TAG "BadUSB HID" #define TAG "BadUSB HID"
#define HID_BT_KEYS_STORAGE_NAME ".bt_hid.keys"
void* hid_usb_init(FuriHalUsbHidConfig* hid_cfg) { void* hid_usb_init(FuriHalUsbHidConfig* hid_cfg) {
furi_check(furi_hal_usb_set_config(&usb_hid, hid_cfg)); furi_check(furi_hal_usb_set_config(&usb_hid, hid_cfg));
return NULL; return NULL;
@@ -69,6 +72,155 @@ static const BadUsbHidApi hid_api_usb = {
.release_all = hid_usb_release_all, .release_all = hid_usb_release_all,
.get_led_state = hid_usb_get_led_state, .get_led_state = hid_usb_get_led_state,
}; };
const BadUsbHidApi* bad_usb_hid_get_interface() {
return &hid_api_usb; typedef struct {
Bt* bt;
FuriHalBleProfileBase* profile;
HidStateCallback state_callback;
void* callback_context;
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;
ble_hid->is_connected = (status == BtStatusConnected);
if(ble_hid->state_callback) {
ble_hid->state_callback(ble_hid->is_connected, ble_hid->callback_context);
}
}
void* hid_ble_init(FuriHalUsbHidConfig* hid_cfg) {
UNUSED(hid_cfg);
BleHidInstance* ble_hid = malloc(sizeof(BleHidInstance));
ble_hid->bt = furi_record_open(RECORD_BT);
bt_disconnect(ble_hid->bt);
// Wait 2nd core to update nvm storage
furi_delay_ms(200);
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);
furi_check(ble_hid->profile);
furi_hal_bt_start_advertising();
bt_set_status_changed_callback(ble_hid->bt, hid_ble_connection_status_callback, ble_hid);
return ble_hid;
}
void hid_ble_deinit(void* inst) {
BleHidInstance* ble_hid = inst;
furi_assert(ble_hid);
bt_set_status_changed_callback(ble_hid->bt, NULL, NULL);
bt_disconnect(ble_hid->bt);
// Wait 2nd core to update nvm storage
furi_delay_ms(200);
bt_keys_storage_set_default_path(ble_hid->bt);
furi_check(bt_profile_restore_default(ble_hid->bt));
furi_record_close(RECORD_BT);
free(ble_hid);
}
void hid_ble_set_state_callback(void* inst, HidStateCallback cb, void* context) {
BleHidInstance* ble_hid = inst;
furi_assert(ble_hid);
ble_hid->state_callback = cb;
ble_hid->callback_context = context;
}
bool hid_ble_is_connected(void* inst) {
BleHidInstance* ble_hid = inst;
furi_assert(ble_hid);
return ble_hid->is_connected;
}
bool hid_ble_kb_press(void* inst, uint16_t button) {
BleHidInstance* ble_hid = inst;
furi_assert(ble_hid);
return ble_profile_hid_kb_press(ble_hid->profile, button);
}
bool hid_ble_kb_release(void* inst, uint16_t button) {
BleHidInstance* ble_hid = inst;
furi_assert(ble_hid);
return ble_profile_hid_kb_release(ble_hid->profile, button);
}
bool hid_ble_consumer_press(void* inst, uint16_t button) {
BleHidInstance* ble_hid = inst;
furi_assert(ble_hid);
return ble_profile_hid_consumer_key_press(ble_hid->profile, button);
}
bool hid_ble_consumer_release(void* inst, uint16_t button) {
BleHidInstance* ble_hid = inst;
furi_assert(ble_hid);
return ble_profile_hid_consumer_key_release(ble_hid->profile, button);
}
bool hid_ble_release_all(void* inst) {
BleHidInstance* ble_hid = inst;
furi_assert(ble_hid);
bool state = ble_profile_hid_kb_release_all(ble_hid->profile);
state &= ble_profile_hid_consumer_key_release_all(ble_hid->profile);
return state;
}
uint8_t hid_ble_get_led_state(void* inst) {
UNUSED(inst);
FURI_LOG_W(TAG, "hid_ble_get_led_state not implemented");
return 0;
}
static const BadUsbHidApi hid_api_ble = {
.init = hid_ble_init,
.deinit = hid_ble_deinit,
.set_state_callback = hid_ble_set_state_callback,
.is_connected = hid_ble_is_connected,
.kb_press = hid_ble_kb_press,
.kb_release = hid_ble_kb_release,
.consumer_press = hid_ble_consumer_press,
.consumer_release = hid_ble_consumer_release,
.release_all = hid_ble_release_all,
.get_led_state = hid_ble_get_led_state,
};
const BadUsbHidApi* bad_usb_hid_get_interface(BadUsbHidInterface interface) {
if(interface == BadUsbHidInterfaceUsb) {
return &hid_api_usb;
} else {
return &hid_api_ble;
}
}
void bad_usb_hid_ble_remove_pairing(void) {
Bt* bt = furi_record_open(RECORD_BT);
bt_disconnect(bt);
// Wait 2nd core to update nvm storage
furi_delay_ms(200);
furi_hal_bt_stop_advertising();
bt_keys_storage_set_storage_path(bt, APP_DATA_PATH(HID_BT_KEYS_STORAGE_NAME));
bt_forget_bonded_devices(bt);
// Wait 2nd core to update nvm storage
furi_delay_ms(200);
bt_keys_storage_set_default_path(bt);
furi_check(bt_profile_restore_default(bt));
furi_record_close(RECORD_BT);
} }

View File

@@ -7,6 +7,11 @@ extern "C" {
#include <furi.h> #include <furi.h>
#include <furi_hal.h> #include <furi_hal.h>
typedef enum {
BadUsbHidInterfaceUsb,
BadUsbHidInterfaceBle,
} BadUsbHidInterface;
typedef struct { typedef struct {
void* (*init)(FuriHalUsbHidConfig* hid_cfg); void* (*init)(FuriHalUsbHidConfig* hid_cfg);
void (*deinit)(void* inst); void (*deinit)(void* inst);
@@ -21,7 +26,7 @@ typedef struct {
uint8_t (*get_led_state)(void* inst); uint8_t (*get_led_state)(void* inst);
} BadUsbHidApi; } BadUsbHidApi;
const BadUsbHidApi* bad_usb_hid_get_interface(); const BadUsbHidApi* bad_usb_hid_get_interface(BadUsbHidInterface interface);
void bad_usb_hid_ble_remove_pairing(void); void bad_usb_hid_ble_remove_pairing(void);

View File

@@ -650,7 +650,7 @@ 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))); memcpy(bad_usb->layout, hid_asciimap, MIN(sizeof(hid_asciimap), sizeof(bad_usb->layout)));
} }
BadUsbScript* bad_usb_script_open(FuriString* file_path) { BadUsbScript* bad_usb_script_open(FuriString* file_path, BadUsbHidInterface interface) {
furi_assert(file_path); furi_assert(file_path);
BadUsbScript* bad_usb = malloc(sizeof(BadUsbScript)); BadUsbScript* bad_usb = malloc(sizeof(BadUsbScript));
@@ -660,7 +660,7 @@ BadUsbScript* bad_usb_script_open(FuriString* file_path) {
bad_usb->st.state = BadUsbStateInit; bad_usb->st.state = BadUsbStateInit;
bad_usb->st.error[0] = '\0'; bad_usb->st.error[0] = '\0';
bad_usb->hid = bad_usb_hid_get_interface(); bad_usb->hid = bad_usb_hid_get_interface(interface);
bad_usb->thread = furi_thread_alloc_ex("BadUsbWorker", 2048, bad_usb_worker, bad_usb); bad_usb->thread = furi_thread_alloc_ex("BadUsbWorker", 2048, bad_usb_worker, bad_usb);
furi_thread_start(bad_usb->thread); furi_thread_start(bad_usb->thread);

View File

@@ -34,7 +34,7 @@ typedef struct {
typedef struct BadUsbScript BadUsbScript; typedef struct BadUsbScript BadUsbScript;
BadUsbScript* bad_usb_script_open(FuriString* file_path); BadUsbScript* bad_usb_script_open(FuriString* file_path, BadUsbHidInterface interface);
void bad_usb_script_close(BadUsbScript* bad_usb); void bad_usb_script_close(BadUsbScript* bad_usb);

View File

@@ -1,12 +1,17 @@
REM This is BadUSB demo script for ChromeOS by kowalski7cc REM This is BadUSB demo script for Chrome and ChromeOS by kowalski7cc
REM Exit from Overview
ESC
REM Open a new tab REM Open a new tab
CTRL t CTRL t
REM wait for some slower chromebooks REM wait for some slower chromebooks
DELAY 1000 DELAY 1000
REM Make sure we have omnibox focus
CTRL l
DELAY 200
REM Open an empty editable page REM Open an empty editable page
DEFAULT_DELAY 50 DEFAULT_DELAY 50
STRING data:text/html, <html contenteditable autofocus><style>body{font-family:monospace;} STRING data:text/html, <html contenteditable autofocus><title>Flipper Zero BadUSB Demo</title><style>body{font-family:monospace;}
ENTER ENTER
DELAY 500 DELAY 500

View File

@@ -5,14 +5,22 @@ REM Check the `lsusb` command to know your own devices IDs
REM This is BadUSB demo script for Linux/Gnome REM This is BadUSB demo script for Linux/Gnome
REM Exit from Overview
ESC
DELAY 200
REM Open terminal window REM Open terminal window
DELAY 1000
ALT F2 ALT F2
DELAY 500 DELAY 1000
STRING gnome-terminal --maximize REM Let's guess user terminal, based on (almost) glib order with ptyxis now default in Fedora 41
DELAY 500 STRING sh -c "xdg-terminal-exec||kgx||ptyxis||gnome-terminal||mate-terminal||xfce4-terminal||tilix||konsole||xterm"
DELAY 300
ENTER
REM It can take a bit to open the correct terminal
DELAY 1500
REM Make sure we are running in a POSIX-compliant shell
STRING env sh
ENTER ENTER
DELAY 750
REM Clear the screen in case some banner was displayed REM Clear the screen in case some banner was displayed
STRING clear STRING clear

View File

@@ -0,0 +1,59 @@
#include "../bad_usb_app_i.h"
enum SubmenuIndex {
ConfigIndexKeyboardLayout,
ConfigIndexBleUnpair,
};
void bad_usb_scene_config_select_callback(void* context, uint32_t index) {
BadUsbApp* bad_usb = context;
view_dispatcher_send_custom_event(bad_usb->view_dispatcher, index);
}
static void draw_menu(BadUsbApp* bad_usb) {
VariableItemList* var_item_list = bad_usb->var_item_list;
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);
}
void bad_usb_scene_config_on_enter(void* context) {
BadUsbApp* bad_usb = context;
VariableItemList* var_item_list = bad_usb->var_item_list;
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);
view_dispatcher_switch_to_view(bad_usb->view_dispatcher, BadUsbAppViewConfig);
}
bool bad_usb_scene_config_on_event(void* context, SceneManagerEvent event) {
BadUsbApp* bad_usb = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
consumed = true;
if(event.event == ConfigIndexKeyboardLayout) {
scene_manager_next_scene(bad_usb->scene_manager, BadUsbSceneConfigLayout);
} else if(event.event == ConfigIndexBleUnpair) {
scene_manager_next_scene(bad_usb->scene_manager, BadUsbSceneConfirmUnpair);
} else {
furi_crash("Unknown key type");
}
}
return consumed;
}
void bad_usb_scene_config_on_exit(void* context) {
BadUsbApp* bad_usb = context;
VariableItemList* var_item_list = bad_usb->var_item_list;
variable_item_list_reset(var_item_list);
}

View File

@@ -1,4 +1,7 @@
ADD_SCENE(bad_usb, file_select, FileSelect) ADD_SCENE(bad_usb, file_select, FileSelect)
ADD_SCENE(bad_usb, work, Work) ADD_SCENE(bad_usb, work, Work)
ADD_SCENE(bad_usb, error, Error) ADD_SCENE(bad_usb, error, Error)
ADD_SCENE(bad_usb, config, Config)
ADD_SCENE(bad_usb, config_layout, ConfigLayout) ADD_SCENE(bad_usb, config_layout, ConfigLayout)
ADD_SCENE(bad_usb, confirm_unpair, ConfirmUnpair)
ADD_SCENE(bad_usb, unpair_done, UnpairDone)

View File

@@ -1,42 +1,42 @@
#include "../bad_ble_app_i.h" #include "../bad_usb_app_i.h"
void bad_ble_scene_confirm_unpair_widget_callback( void bad_usb_scene_confirm_unpair_widget_callback(
GuiButtonType type, GuiButtonType type,
InputType input_type, InputType input_type,
void* context) { void* context) {
UNUSED(input_type); UNUSED(input_type);
SceneManagerEvent event = {.type = SceneManagerEventTypeCustom, .event = type}; SceneManagerEvent event = {.type = SceneManagerEventTypeCustom, .event = type};
bad_ble_scene_confirm_unpair_on_event(context, event); bad_usb_scene_confirm_unpair_on_event(context, event);
} }
void bad_ble_scene_confirm_unpair_on_enter(void* context) { void bad_usb_scene_confirm_unpair_on_enter(void* context) {
BadBleApp* bad_ble = context; BadUsbApp* bad_usb = context;
Widget* widget = bad_ble->widget; Widget* widget = bad_usb->widget;
widget_add_button_element( widget_add_button_element(
widget, GuiButtonTypeLeft, "Cancel", bad_ble_scene_confirm_unpair_widget_callback, context); widget, GuiButtonTypeLeft, "Cancel", bad_usb_scene_confirm_unpair_widget_callback, context);
widget_add_button_element( widget_add_button_element(
widget, widget,
GuiButtonTypeRight, GuiButtonTypeRight,
"Unpair", "Unpair",
bad_ble_scene_confirm_unpair_widget_callback, bad_usb_scene_confirm_unpair_widget_callback,
context); context);
widget_add_text_box_element( widget_add_text_box_element(
widget, 0, 0, 128, 64, AlignCenter, AlignTop, "\e#Unpair the Device?\e#\n", false); widget, 0, 0, 128, 64, AlignCenter, AlignTop, "\e#Unpair the Device?\e#\n", false);
view_dispatcher_switch_to_view(bad_ble->view_dispatcher, BadBleAppViewWidget); view_dispatcher_switch_to_view(bad_usb->view_dispatcher, BadUsbAppViewWidget);
} }
bool bad_ble_scene_confirm_unpair_on_event(void* context, SceneManagerEvent event) { bool bad_usb_scene_confirm_unpair_on_event(void* context, SceneManagerEvent event) {
BadBleApp* bad_ble = context; BadUsbApp* bad_usb = context;
SceneManager* scene_manager = bad_ble->scene_manager; SceneManager* scene_manager = bad_usb->scene_manager;
bool consumed = false; bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) { if(event.type == SceneManagerEventTypeCustom) {
consumed = true; consumed = true;
if(event.event == GuiButtonTypeRight) { if(event.event == GuiButtonTypeRight) {
scene_manager_next_scene(scene_manager, BadBleSceneUnpairDone); scene_manager_next_scene(scene_manager, BadUsbSceneUnpairDone);
} else if(event.event == GuiButtonTypeLeft) { } else if(event.event == GuiButtonTypeLeft) {
scene_manager_previous_scene(scene_manager); scene_manager_previous_scene(scene_manager);
} }
@@ -45,9 +45,9 @@ bool bad_ble_scene_confirm_unpair_on_event(void* context, SceneManagerEvent even
return consumed; return consumed;
} }
void bad_ble_scene_confirm_unpair_on_exit(void* context) { void bad_usb_scene_confirm_unpair_on_exit(void* context) {
BadBleApp* bad_ble = context; BadUsbApp* bad_usb = context;
Widget* widget = bad_ble->widget; Widget* widget = bad_usb->widget;
widget_reset(widget); widget_reset(widget);
} }

View File

@@ -43,7 +43,7 @@ void bad_usb_scene_error_on_enter(void* context) {
"Disconnect from\nPC or phone to\nuse this function."); "Disconnect from\nPC or phone to\nuse this function.");
} }
view_dispatcher_switch_to_view(app->view_dispatcher, BadUsbAppViewError); view_dispatcher_switch_to_view(app->view_dispatcher, BadUsbAppViewWidget);
} }
bool bad_usb_scene_error_on_event(void* context, SceneManagerEvent event) { bool bad_usb_scene_error_on_event(void* context, SceneManagerEvent event) {

View File

@@ -0,0 +1,39 @@
#include "../bad_usb_app_i.h"
static void bad_usb_scene_unpair_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) {
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_context(popup, bad_usb);
popup_set_timeout(popup, 1500);
popup_enable_timeout(popup);
view_dispatcher_switch_to_view(bad_usb->view_dispatcher, BadUsbAppViewPopup);
}
bool bad_usb_scene_unpair_done_on_event(void* context, SceneManagerEvent event) {
BadUsbApp* bad_usb = context;
UNUSED(bad_usb);
UNUSED(event);
bool consumed = false;
return consumed;
}
void bad_usb_scene_unpair_done_on_exit(void* context) {
BadUsbApp* bad_usb = context;
Popup* popup = bad_usb->popup;
UNUSED(popup);
popup_reset(popup);
}

View File

@@ -20,14 +20,27 @@ bool bad_usb_scene_work_on_event(void* context, SceneManagerEvent event) {
bad_usb_script_close(app->bad_usb_script); bad_usb_script_close(app->bad_usb_script);
app->bad_usb_script = NULL; app->bad_usb_script = NULL;
scene_manager_next_scene(app->scene_manager, BadUsbSceneConfigLayout); if(app->interface == BadUsbHidInterfaceBle) {
scene_manager_next_scene(app->scene_manager, BadUsbSceneConfig);
} else {
scene_manager_next_scene(app->scene_manager, BadUsbSceneConfigLayout);
}
} }
consumed = true; consumed = true;
} else if(event.event == InputKeyOk) { } else if(event.event == InputKeyOk) {
bad_usb_script_start_stop(app->bad_usb_script); bad_usb_script_start_stop(app->bad_usb_script);
consumed = true; consumed = true;
} else if(event.event == InputKeyRight) { } else if(event.event == InputKeyRight) {
bad_usb_script_pause_resume(app->bad_usb_script); if(bad_usb_view_is_idle_state(app->bad_usb_view)) {
bad_usb_set_interface(
app,
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);
} else {
bad_usb_script_pause_resume(app->bad_usb_script);
}
consumed = true; consumed = true;
} }
} else if(event.type == SceneManagerEventTypeTick) { } else if(event.type == SceneManagerEventTypeTick) {
@@ -39,7 +52,9 @@ bool bad_usb_scene_work_on_event(void* context, SceneManagerEvent event) {
void bad_usb_scene_work_on_enter(void* context) { void bad_usb_scene_work_on_enter(void* context) {
BadUsbApp* app = context; BadUsbApp* app = context;
app->bad_usb_script = bad_usb_script_open(app->file_path); bad_usb_view_set_interface(app->bad_usb_view, app->interface);
app->bad_usb_script = bad_usb_script_open(app->file_path, app->interface);
bad_usb_script_set_keyboard_layout(app->bad_usb_script, app->keyboard_layout); bad_usb_script_set_keyboard_layout(app->bad_usb_script, app->keyboard_layout);
FuriString* file_name; FuriString* file_name;

View File

@@ -18,6 +18,7 @@ typedef struct {
BadUsbState state; BadUsbState state;
bool pause_wait; bool pause_wait;
uint8_t anim_frame; uint8_t anim_frame;
BadUsbHidInterface interface;
} BadUsbModel; } BadUsbModel;
static void bad_usb_draw_callback(Canvas* canvas, void* _model) { static void bad_usb_draw_callback(Canvas* canvas, void* _model) {
@@ -40,14 +41,24 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) {
furi_string_reset(disp_str); furi_string_reset(disp_str);
canvas_draw_icon(canvas, 22, 24, &I_UsbTree_48x22); if(model->interface == BadUsbHidInterfaceBle) {
canvas_draw_icon(canvas, 22, 24, &I_Bad_BLE_48x22);
} else {
canvas_draw_icon(canvas, 22, 24, &I_UsbTree_48x22);
}
BadUsbWorkerState state = model->state.state; BadUsbWorkerState state = model->state.state;
if((state == BadUsbStateIdle) || (state == BadUsbStateDone) || if((state == BadUsbStateIdle) || (state == BadUsbStateDone) ||
(state == BadUsbStateNotConnected)) { (state == BadUsbStateNotConnected)) {
elements_button_center(canvas, "Run"); elements_button_center(canvas, "Run");
elements_button_left(canvas, "Layout"); 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");
}
} else if((state == BadUsbStateRunning) || (state == BadUsbStateDelay)) { } else if((state == BadUsbStateRunning) || (state == BadUsbStateDelay)) {
elements_button_center(canvas, "Stop"); elements_button_center(canvas, "Stop");
if(!model->pause_wait) { if(!model->pause_wait) {
@@ -266,6 +277,10 @@ void bad_usb_view_set_state(BadUsb* bad_usb, BadUsbState* st) {
true); true);
} }
void bad_usb_view_set_interface(BadUsb* bad_usb, BadUsbHidInterface interface) {
with_view_model(bad_usb->view, BadUsbModel * model, { model->interface = interface; }, true);
}
bool bad_usb_view_is_idle_state(BadUsb* bad_usb) { bool bad_usb_view_is_idle_state(BadUsb* bad_usb) {
bool is_idle = false; bool is_idle = false;
with_view_model( with_view_model(

View File

@@ -23,4 +23,6 @@ void bad_usb_view_set_layout(BadUsb* bad_usb, const char* layout);
void bad_usb_view_set_state(BadUsb* bad_usb, BadUsbState* st); void bad_usb_view_set_state(BadUsb* bad_usb, BadUsbState* st);
void bad_usb_view_set_interface(BadUsb* bad_usb, BadUsbHidInterface interface);
bool bad_usb_view_is_idle_state(BadUsb* bad_usb); bool bad_usb_view_is_idle_state(BadUsb* bad_usb);

View File

@@ -209,6 +209,15 @@ App(
sources=["plugins/supported_cards/hworld.c"], sources=["plugins/supported_cards/hworld.c"],
) )
App(
appid="trt_parser",
apptype=FlipperAppType.PLUGIN,
entry_point="trt_plugin_ep",
targets=["f7"],
requires=["nfc"],
sources=["plugins/supported_cards/trt.c"],
)
App( App(
appid="nfc_start", appid="nfc_start",
targets=["f7"], targets=["f7"],

View File

@@ -0,0 +1,97 @@
// Flipper Zero parser for for Tianjin Railway Transit (TRT)
// https://en.wikipedia.org/wiki/Tianjin_Metro
// Reverse engineering and parser development by @Torron (Github: @zinongli)
#include "nfc_supported_card_plugin.h"
#include <flipper_application.h>
#include <nfc/protocols/mf_ultralight/mf_ultralight.h>
#include <bit_lib.h>
#define TAG "TrtParser"
#define LATEST_SALE_MARKER 0x02
#define FULL_SALE_TIME_STAMP_PAGE 0x09
#define BALANCE_PAGE 0x08
#define SALE_RECORD_TIME_STAMP_A 0x0C
#define SALE_RECORD_TIME_STAMP_B 0x0E
#define SALE_YEAR_OFFSET 2000
static bool trt_parse(const NfcDevice* device, FuriString* parsed_data) {
furi_assert(device);
furi_assert(parsed_data);
const MfUltralightData* data = nfc_device_get_data(device, NfcProtocolMfUltralight);
bool parsed = false;
do {
uint8_t latest_sale_page = 0;
// Look for sale record signature
if(data->page[SALE_RECORD_TIME_STAMP_A].data[0] == LATEST_SALE_MARKER) {
latest_sale_page = SALE_RECORD_TIME_STAMP_A;
} else if(data->page[SALE_RECORD_TIME_STAMP_B].data[0] == LATEST_SALE_MARKER) {
latest_sale_page = SALE_RECORD_TIME_STAMP_B;
} else {
break;
}
// Check if the sale record was backed up
const uint8_t* partial_record_pointer = &data->page[latest_sale_page - 1].data[0];
const uint8_t* full_record_pointer = &data->page[FULL_SALE_TIME_STAMP_PAGE].data[0];
uint32_t latest_sale_record = bit_lib_get_bits_32(partial_record_pointer, 3, 20);
uint32_t latest_sale_full_record = bit_lib_get_bits_32(full_record_pointer, 0, 27);
if(latest_sale_record != (latest_sale_full_record & 0xFFFFF))
break; // check if the copy matches
if((latest_sale_record == 0) || (latest_sale_full_record == 0))
break; // prevent false positive
// Parse date
// yyy yyyymmmm dddddhhh hhnnnnnn
uint16_t sale_year = ((latest_sale_full_record & 0x7F00000) >> 20) + SALE_YEAR_OFFSET;
uint8_t sale_month = (latest_sale_full_record & 0xF0000) >> 16;
uint8_t sale_day = (latest_sale_full_record & 0xF800) >> 11;
uint8_t sale_hour = (latest_sale_full_record & 0x7C0) >> 6;
uint8_t sale_minute = latest_sale_full_record & 0x3F;
// Parse balance
uint16_t balance = bit_lib_get_bits_16(&data->page[BALANCE_PAGE].data[2], 0, 16);
uint16_t balance_yuan = balance / 100;
uint8_t balance_cent = balance % 100;
// Format string for rendering
furi_string_cat_printf(parsed_data, "\e#TRT Tianjin Metro\n");
furi_string_cat_printf(parsed_data, "Single-Use Ticket\n");
furi_string_cat_printf(parsed_data, "Balance: %u.%02u RMB\n", balance_yuan, balance_cent);
furi_string_cat_printf(
parsed_data,
"Sale Date: \n%04u-%02d-%02d %02d:%02d",
sale_year,
sale_month,
sale_day,
sale_hour,
sale_minute);
parsed = true;
} while(false);
return parsed;
}
/* Actual implementation of app<>plugin interface */
static const NfcSupportedCardsPlugin trt_plugin = {
.protocol = NfcProtocolMfUltralight,
.verify = NULL,
.read = NULL,
.parse = trt_parse,
};
/* Plugin descriptor to comply with basic plugin specification */
static const FlipperAppPluginDescriptor trt_plugin_descriptor = {
.appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID,
.ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION,
.entry_point = &trt_plugin,
};
/* Plugin entry point - must return a pointer to const descriptor */
const FlipperAppPluginDescriptor* trt_plugin_ep(void) {
return &trt_plugin_descriptor;
}

View File

@@ -1351,3 +1351,8 @@ E69DD9015A43
C8382A233993 C8382A233993
7B304F2A12A6 7B304F2A12A6
FC9418BF788B FC9418BF788B
# H World Hotel Chain Room Keys
543071543071
5F01015F0101
200510241234

View File

@@ -45,6 +45,7 @@ static void dict_attack_draw_callback(Canvas* canvas, void* model) {
furi_string_set(m->header, "PRNG Analysis"); furi_string_set(m->header, "PRNG Analysis");
break; break;
case MfClassicNestedPhaseDictAttack: case MfClassicNestedPhaseDictAttack:
case MfClassicNestedPhaseDictAttackVerify:
case MfClassicNestedPhaseDictAttackResume: case MfClassicNestedPhaseDictAttackResume:
furi_string_set(m->header, "Nested Dictionary"); furi_string_set(m->header, "Nested Dictionary");
break; break;
@@ -91,6 +92,7 @@ static void dict_attack_draw_callback(Canvas* canvas, void* model) {
float dict_progress = 0; float dict_progress = 0;
if(m->nested_phase == MfClassicNestedPhaseAnalyzePRNG || if(m->nested_phase == MfClassicNestedPhaseAnalyzePRNG ||
m->nested_phase == MfClassicNestedPhaseDictAttack || m->nested_phase == MfClassicNestedPhaseDictAttack ||
m->nested_phase == MfClassicNestedPhaseDictAttackVerify ||
m->nested_phase == MfClassicNestedPhaseDictAttackResume) { m->nested_phase == MfClassicNestedPhaseDictAttackResume) {
// Phase: Nested dictionary attack // Phase: Nested dictionary attack
uint8_t target_sector = uint8_t target_sector =

View File

@@ -999,12 +999,13 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) {
chat_event = subghz_chat_worker_get_event_chat(subghz_chat); chat_event = subghz_chat_worker_get_event_chat(subghz_chat);
switch(chat_event.event) { switch(chat_event.event) {
case SubGhzChatEventInputData: case SubGhzChatEventInputData:
if(chat_event.c == CliKeyETX) { if(chat_event.c == CliSymbolAsciiETX) {
printf("\r\n"); printf("\r\n");
chat_event.event = SubGhzChatEventUserExit; chat_event.event = SubGhzChatEventUserExit;
subghz_chat_worker_put_event_chat(subghz_chat, &chat_event); subghz_chat_worker_put_event_chat(subghz_chat, &chat_event);
break; break;
} else if((chat_event.c == CliKeyBackspace) || (chat_event.c == CliKeyDEL)) { } else if(
(chat_event.c == CliSymbolAsciiBackspace) || (chat_event.c == CliSymbolAsciiDel)) {
size_t len = furi_string_utf8_length(input); size_t len = furi_string_utf8_length(input);
if(len > furi_string_utf8_length(name)) { if(len > furi_string_utf8_length(name)) {
printf("%s", "\e[D\e[1P"); printf("%s", "\e[D\e[1P");
@@ -1026,7 +1027,7 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) {
} }
furi_string_set(input, sysmsg); furi_string_set(input, sysmsg);
} }
} else if(chat_event.c == CliKeyCR) { } else if(chat_event.c == CliSymbolAsciiCR) {
printf("\r\n"); printf("\r\n");
furi_string_push_back(input, '\r'); furi_string_push_back(input, '\r');
furi_string_push_back(input, '\n'); furi_string_push_back(input, '\n');
@@ -1040,7 +1041,7 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) {
furi_string_printf(input, "%s", furi_string_get_cstr(name)); furi_string_printf(input, "%s", furi_string_get_cstr(name));
printf("%s", furi_string_get_cstr(input)); printf("%s", furi_string_get_cstr(input));
fflush(stdout); fflush(stdout);
} else if(chat_event.c == CliKeyLF) { } else if(chat_event.c == CliSymbolAsciiLF) {
//cut out the symbol \n //cut out the symbol \n
} else { } else {
putc(chat_event.c, stdout); putc(chat_event.c, stdout);

View File

@@ -1,6 +1,5 @@
#pragma once #pragma once
#include <cli/cli.h> #include <cli/cli.h>
#include <cli/cli_ansi.h>
void subghz_on_system_start(void); void subghz_on_system_start(void);

View File

@@ -1,15 +1,12 @@
#include "cli_i.h" #include "cli_i.h"
#include "cli_commands.h" #include "cli_commands.h"
#include "cli_vcp.h" #include "cli_vcp.h"
#include "cli_ansi.h"
#include <furi_hal_version.h> #include <furi_hal_version.h>
#include <loader/loader.h> #include <loader/loader.h>
#define TAG "CliSrv" #define TAG "CliSrv"
#define CLI_INPUT_LEN_LIMIT 256 #define CLI_INPUT_LEN_LIMIT 256
#define CLI_PROMPT ">: " // qFlipper does not recognize us if we use escape sequences :(
#define CLI_PROMPT_LENGTH 3 // printable characters
Cli* cli_alloc(void) { Cli* cli_alloc(void) {
Cli* cli = malloc(sizeof(Cli)); Cli* cli = malloc(sizeof(Cli));
@@ -88,7 +85,7 @@ bool cli_cmd_interrupt_received(Cli* cli) {
char c = '\0'; char c = '\0';
if(cli_is_connected(cli)) { if(cli_is_connected(cli)) {
if(cli->session->rx((uint8_t*)&c, 1, 0) == 1) { if(cli->session->rx((uint8_t*)&c, 1, 0) == 1) {
return c == CliKeyETX; return c == CliSymbolAsciiETX;
} }
} else { } else {
return true; return true;
@@ -105,8 +102,7 @@ void cli_print_usage(const char* cmd, const char* usage, const char* arg) {
} }
void cli_motd(void) { void cli_motd(void) {
printf(ANSI_FLIPPER_BRAND_ORANGE printf("\r\n"
"\r\n"
" _.-------.._ -,\r\n" " _.-------.._ -,\r\n"
" .-\"```\"--..,,_/ /`-, -, \\ \r\n" " .-\"```\"--..,,_/ /`-, -, \\ \r\n"
" .:\" /:/ /'\\ \\ ,_..., `. | |\r\n" " .:\" /:/ /'\\ \\ ,_..., `. | |\r\n"
@@ -120,11 +116,12 @@ void cli_motd(void) {
" _L_ _ ___ ___ ___ ___ ____--\"`___ _ ___\r\n" " _L_ _ ___ ___ ___ ___ ____--\"`___ _ ___\r\n"
"| __|| | |_ _|| _ \\| _ \\| __|| _ \\ / __|| | |_ _|\r\n" "| __|| | |_ _|| _ \\| _ \\| __|| _ \\ / __|| | |_ _|\r\n"
"| _| | |__ | | | _/| _/| _| | / | (__ | |__ | |\r\n" "| _| | |__ | | | _/| _/| _| | / | (__ | |__ | |\r\n"
"|_| |____||___||_| |_| |___||_|_\\ \\___||____||___|\r\n" ANSI_RESET "|_| |____||___||_| |_| |___||_|_\\ \\___||____||___|\r\n"
"\r\n" ANSI_FG_BR_WHITE "Welcome to " ANSI_FLIPPER_BRAND_ORANGE "\r\n"
"Flipper Zero" ANSI_FG_BR_WHITE " Command Line Interface!\r\n" "Welcome to Flipper Zero Command Line Interface!\r\n"
"Read the manual: https://docs.flipper.net/development/cli\r\n" "Read the manual: https://docs.flipper.net/development/cli\r\n"
"Run `help` or `?` to list available commands\r\n" ANSI_RESET "\r\n"); "Run `help` or `?` to list available commands\r\n"
"\r\n");
const Version* firmware_version = furi_hal_version_get_firmware_version(); const Version* firmware_version = furi_hal_version_get_firmware_version();
if(firmware_version) { if(firmware_version) {
@@ -145,7 +142,7 @@ void cli_nl(Cli* cli) {
void cli_prompt(Cli* cli) { void cli_prompt(Cli* cli) {
UNUSED(cli); UNUSED(cli);
printf("\r\n" CLI_PROMPT "%s", furi_string_get_cstr(cli->line)); printf("\r\n>: %s", furi_string_get_cstr(cli->line));
fflush(stdout); fflush(stdout);
} }
@@ -168,7 +165,7 @@ static void cli_handle_backspace(Cli* cli) {
cli->cursor_position--; cli->cursor_position--;
} else { } else {
cli_putc(cli, CliKeyBell); cli_putc(cli, CliSymbolAsciiBell);
} }
} }
@@ -244,7 +241,7 @@ static void cli_handle_enter(Cli* cli) {
printf( printf(
"`%s` command not found, use `help` or `?` to list all available commands", "`%s` command not found, use `help` or `?` to list all available commands",
furi_string_get_cstr(command)); furi_string_get_cstr(command));
cli_putc(cli, CliKeyBell); cli_putc(cli, CliSymbolAsciiBell);
} }
cli_reset(cli); cli_reset(cli);
@@ -308,85 +305,8 @@ static void cli_handle_autocomplete(Cli* cli) {
cli_prompt(cli); cli_prompt(cli);
} }
typedef enum { static void cli_handle_escape(Cli* cli, char c) {
CliCharClassWord, if(c == 'A') {
CliCharClassSpace,
CliCharClassOther,
} CliCharClass;
/**
* @brief Determines the class that a character belongs to
*
* The return value of this function should not be used on its own; it should
* only be used for comparing it with other values returned by this function.
* This function is used internally in `cli_skip_run`.
*/
static CliCharClass cli_char_class(char c) {
if((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_') {
return CliCharClassWord;
} else if(c == ' ') {
return CliCharClassSpace;
} else {
return CliCharClassOther;
}
}
typedef enum {
CliSkipDirectionLeft,
CliSkipDirectionRight,
} CliSkipDirection;
/**
* @brief Skips a run of a class of characters
*
* @param string Input string
* @param original_pos Position to start the search at
* @param direction Direction in which to perform the search
* @returns The position at which the run ends
*/
static size_t cli_skip_run(FuriString* string, size_t original_pos, CliSkipDirection direction) {
if(furi_string_size(string) == 0) return original_pos;
if(direction == CliSkipDirectionLeft && original_pos == 0) return original_pos;
if(direction == CliSkipDirectionRight && original_pos == furi_string_size(string))
return original_pos;
int8_t look_offset = (direction == CliSkipDirectionLeft) ? -1 : 0;
int8_t increment = (direction == CliSkipDirectionLeft) ? -1 : 1;
int32_t position = original_pos;
CliCharClass start_class =
cli_char_class(furi_string_get_char(string, position + look_offset));
while(true) {
position += increment;
if(position < 0) break;
if(position >= (int32_t)furi_string_size(string)) break;
if(cli_char_class(furi_string_get_char(string, position + look_offset)) != start_class)
break;
}
return MAX(0, position);
}
void cli_process_input(Cli* cli) {
CliKeyCombo combo = cli_read_ansi_key_combo(cli);
FURI_LOG_T(TAG, "code=0x%02x, mod=0x%x\r\n", combo.key, combo.modifiers);
if(combo.key == CliKeyTab) {
cli_handle_autocomplete(cli);
} else if(combo.key == CliKeySOH) {
furi_delay_ms(33); // We are too fast, Minicom is not ready yet
cli_motd();
cli_prompt(cli);
} else if(combo.key == CliKeyETX) {
cli_reset(cli);
cli_prompt(cli);
} else if(combo.key == CliKeyEOT) {
cli_reset(cli);
} else if(combo.key == CliKeyUp && combo.modifiers == CliModKeyNo) {
// Use previous command if line buffer is empty // Use previous command if line buffer is empty
if(furi_string_size(cli->line) == 0 && furi_string_cmp(cli->line, cli->last_line) != 0) { if(furi_string_size(cli->line) == 0 && furi_string_cmp(cli->line, cli->last_line) != 0) {
// Set line buffer and cursor position // Set line buffer and cursor position
@@ -395,85 +315,67 @@ void cli_process_input(Cli* cli) {
// Show new line to user // Show new line to user
printf("%s", furi_string_get_cstr(cli->line)); printf("%s", furi_string_get_cstr(cli->line));
} }
} else if(c == 'B') {
} else if(combo.key == CliKeyDown && combo.modifiers == CliModKeyNo) { } else if(c == 'C') {
// Clear input buffer
furi_string_reset(cli->line);
cli->cursor_position = 0;
printf("\r" CLI_PROMPT "\e[0K");
} else if(combo.key == CliKeyRight && combo.modifiers == CliModKeyNo) {
// Move right
if(cli->cursor_position < furi_string_size(cli->line)) { if(cli->cursor_position < furi_string_size(cli->line)) {
cli->cursor_position++; cli->cursor_position++;
printf("\e[C"); printf("\e[C");
} }
} else if(c == 'D') {
} else if(combo.key == CliKeyLeft && combo.modifiers == CliModKeyNo) {
// Move left
if(cli->cursor_position > 0) { if(cli->cursor_position > 0) {
cli->cursor_position--; cli->cursor_position--;
printf("\e[D"); printf("\e[D");
} }
}
fflush(stdout);
}
} else if(combo.key == CliKeyHome && combo.modifiers == CliModKeyNo) { void cli_process_input(Cli* cli) {
// Move to beginning of line char in_chr = cli_getc(cli);
cli->cursor_position = 0; size_t rx_len;
printf("\e[%uG", CLI_PROMPT_LENGTH + 1); // columns start at 1 \(-_-)/
} else if(combo.key == CliKeyEnd && combo.modifiers == CliModKeyNo) { if(in_chr == CliSymbolAsciiTab) {
// Move to end of line cli_handle_autocomplete(cli);
cli->cursor_position = furi_string_size(cli->line); } else if(in_chr == CliSymbolAsciiSOH) {
printf("\e[%zuG", CLI_PROMPT_LENGTH + cli->cursor_position + 1); furi_delay_ms(33); // We are too fast, Minicom is not ready yet
cli_motd();
} else if( cli_prompt(cli);
combo.modifiers == CliModKeyCtrl && } else if(in_chr == CliSymbolAsciiETX) {
(combo.key == CliKeyLeft || combo.key == CliKeyRight)) { cli_reset(cli);
// Skip run of similar chars to the left or right cli_prompt(cli);
CliSkipDirection direction = (combo.key == CliKeyLeft) ? CliSkipDirectionLeft : } else if(in_chr == CliSymbolAsciiEOT) {
CliSkipDirectionRight; cli_reset(cli);
cli->cursor_position = cli_skip_run(cli->line, cli->cursor_position, direction); } else if(in_chr == CliSymbolAsciiEsc) {
printf("\e[%zuG", CLI_PROMPT_LENGTH + cli->cursor_position + 1); rx_len = cli_read(cli, (uint8_t*)&in_chr, 1);
if((rx_len > 0) && (in_chr == '[')) {
} else if(combo.key == CliKeyBackspace || combo.key == CliKeyDEL) { cli_read(cli, (uint8_t*)&in_chr, 1);
cli_handle_escape(cli, in_chr);
} else {
cli_putc(cli, CliSymbolAsciiBell);
}
} else if(in_chr == CliSymbolAsciiBackspace || in_chr == CliSymbolAsciiDel) {
cli_handle_backspace(cli); cli_handle_backspace(cli);
} else if(in_chr == CliSymbolAsciiCR) {
} else if(combo.key == CliKeyETB) { // Ctrl + Backspace
// Delete run of similar chars to the left
size_t run_start = cli_skip_run(cli->line, cli->cursor_position, CliSkipDirectionLeft);
furi_string_replace_at(cli->line, run_start, cli->cursor_position - run_start, "");
cli->cursor_position = run_start;
printf(
"\e[%zuG%s\e[0K\e[%zuG", // move cursor, print second half of line, erase remains, move cursor again
CLI_PROMPT_LENGTH + cli->cursor_position + 1,
furi_string_get_cstr(cli->line) + run_start,
CLI_PROMPT_LENGTH + run_start + 1);
} else if(combo.key == CliKeyCR) {
cli_handle_enter(cli); cli_handle_enter(cli);
} else if( } else if(
(combo.key >= 0x20 && combo.key < 0x7F) && //-V560 (in_chr >= 0x20 && in_chr < 0x7F) && //-V560
(furi_string_size(cli->line) < CLI_INPUT_LEN_LIMIT)) { (furi_string_size(cli->line) < CLI_INPUT_LEN_LIMIT)) {
if(cli->cursor_position == furi_string_size(cli->line)) { if(cli->cursor_position == furi_string_size(cli->line)) {
furi_string_push_back(cli->line, combo.key); furi_string_push_back(cli->line, in_chr);
cli_putc(cli, combo.key); cli_putc(cli, in_chr);
} else { } else {
// Insert character to line buffer // Insert character to line buffer
const char in_str[2] = {combo.key, 0}; const char in_str[2] = {in_chr, 0};
furi_string_replace_at(cli->line, cli->cursor_position, 0, in_str); furi_string_replace_at(cli->line, cli->cursor_position, 0, in_str);
// Print character in replace mode // Print character in replace mode
printf("\e[4h%c\e[4l", combo.key); printf("\e[4h%c\e[4l", in_chr);
fflush(stdout); fflush(stdout);
} }
cli->cursor_position++; cli->cursor_position++;
} else { } else {
cli_putc(cli, CliKeyBell); cli_putc(cli, CliSymbolAsciiBell);
} }
fflush(stdout);
} }
void cli_add_command( void cli_add_command(

View File

@@ -10,12 +10,26 @@
extern "C" { extern "C" {
#endif #endif
typedef enum {
CliSymbolAsciiSOH = 0x01,
CliSymbolAsciiETX = 0x03,
CliSymbolAsciiEOT = 0x04,
CliSymbolAsciiBell = 0x07,
CliSymbolAsciiBackspace = 0x08,
CliSymbolAsciiTab = 0x09,
CliSymbolAsciiLF = 0x0A,
CliSymbolAsciiCR = 0x0D,
CliSymbolAsciiEsc = 0x1B,
CliSymbolAsciiUS = 0x1F,
CliSymbolAsciiSpace = 0x20,
CliSymbolAsciiDel = 0x7F,
} CliSymbols;
typedef enum { typedef enum {
CliCommandFlagDefault = 0, /**< Default, loader lock is used */ CliCommandFlagDefault = 0, /**< Default, loader lock is used */
CliCommandFlagParallelSafe = CliCommandFlagParallelSafe =
(1 << 0), /**< Safe to run in parallel with other apps, loader lock is not used */ (1 << 0), /**< Safe to run in parallel with other apps, loader lock is not used */
CliCommandFlagInsomniaSafe = (1 << 1), /**< Safe to run with insomnia mode on */ CliCommandFlagInsomniaSafe = (1 << 1), /**< Safe to run with insomnia mode on */
CliCommandFlagHidden = (1 << 2), /**< Not shown in `help` */
} CliCommandFlag; } CliCommandFlag;
#define RECORD_CLI "cli" #define RECORD_CLI "cli"

View File

@@ -1,76 +0,0 @@
#include "cli_ansi.h"
/**
* @brief Converts a single character representing a special key into the enum
* representation
*/
static CliKey cli_ansi_key_from_mnemonic(char c) {
switch(c) {
case 'A':
return CliKeyUp;
case 'B':
return CliKeyDown;
case 'C':
return CliKeyRight;
case 'D':
return CliKeyLeft;
case 'F':
return CliKeyEnd;
case 'H':
return CliKeyHome;
default:
return CliKeyUnrecognized;
}
}
CliKeyCombo cli_read_ansi_key_combo(Cli* cli) {
char ch = cli_getc(cli);
if(ch != CliKeyEsc)
return (CliKeyCombo){
.modifiers = CliModKeyNo,
.key = ch,
};
ch = cli_getc(cli);
// ESC ESC -> ESC
if(ch == '\e')
return (CliKeyCombo){
.modifiers = CliModKeyNo,
.key = '\e',
};
// ESC <char> -> Alt + <char>
if(ch != '[')
return (CliKeyCombo){
.modifiers = CliModKeyAlt,
.key = cli_getc(cli),
};
ch = cli_getc(cli);
// ESC [ 1
if(ch == '1') {
// ESC [ 1 ; <modifier bitfield> <key mnemonic>
if(cli_getc(cli) == ';') {
CliModKey modifiers = (cli_getc(cli) - '0'); // convert following digit to a number
modifiers &= ~1;
return (CliKeyCombo){
.modifiers = modifiers,
.key = cli_ansi_key_from_mnemonic(cli_getc(cli)),
};
}
return (CliKeyCombo){
.modifiers = CliModKeyNo,
.key = CliKeyUnrecognized,
};
}
// ESC [ <key mnemonic>
return (CliKeyCombo){
.modifiers = CliModKeyNo,
.key = cli_ansi_key_from_mnemonic(ch),
};
}

View File

@@ -1,94 +0,0 @@
#pragma once
#include "cli.h"
#ifdef __cplusplus
extern "C" {
#endif
#define ANSI_RESET "\e[0m"
#define ANSI_BOLD "\e[1m"
#define ANSI_FAINT "\e[2m"
#define ANSI_FG_BLACK "\e[30m"
#define ANSI_FG_RED "\e[31m"
#define ANSI_FG_GREEN "\e[32m"
#define ANSI_FG_YELLOW "\e[33m"
#define ANSI_FG_BLUE "\e[34m"
#define ANSI_FG_MAGENTA "\e[35m"
#define ANSI_FG_CYAN "\e[36m"
#define ANSI_FG_WHITE "\e[37m"
#define ANSI_FG_BR_BLACK "\e[90m"
#define ANSI_FG_BR_RED "\e[91m"
#define ANSI_FG_BR_GREEN "\e[92m"
#define ANSI_FG_BR_YELLOW "\e[93m"
#define ANSI_FG_BR_BLUE "\e[94m"
#define ANSI_FG_BR_MAGENTA "\e[95m"
#define ANSI_FG_BR_CYAN "\e[96m"
#define ANSI_FG_BR_WHITE "\e[97m"
#define ANSI_BG_BLACK "\e[40m"
#define ANSI_BG_RED "\e[41m"
#define ANSI_BG_GREEN "\e[42m"
#define ANSI_BG_YELLOW "\e[43m"
#define ANSI_BG_BLUE "\e[44m"
#define ANSI_BG_MAGENTA "\e[45m"
#define ANSI_BG_CYAN "\e[46m"
#define ANSI_BG_WHITE "\e[47m"
#define ANSI_BG_BR_BLACK "\e[100m"
#define ANSI_BG_BR_RED "\e[101m"
#define ANSI_BG_BR_GREEN "\e[102m"
#define ANSI_BG_BR_YELLOW "\e[103m"
#define ANSI_BG_BR_BLUE "\e[104m"
#define ANSI_BG_BR_MAGENTA "\e[105m"
#define ANSI_BG_BR_CYAN "\e[106m"
#define ANSI_BG_BR_WHITE "\e[107m"
#define ANSI_FLIPPER_BRAND_ORANGE "\e[38;2;255;130;0m"
typedef enum {
CliKeyUnrecognized = 0,
CliKeySOH = 0x01,
CliKeyETX = 0x03,
CliKeyEOT = 0x04,
CliKeyBell = 0x07,
CliKeyBackspace = 0x08,
CliKeyTab = 0x09,
CliKeyLF = 0x0A,
CliKeyCR = 0x0D,
CliKeyETB = 0x17,
CliKeyEsc = 0x1B,
CliKeyUS = 0x1F,
CliKeySpace = 0x20,
CliKeyDEL = 0x7F,
CliKeySpecial = 0x80,
CliKeyLeft,
CliKeyRight,
CliKeyUp,
CliKeyDown,
CliKeyHome,
CliKeyEnd,
} CliKey;
typedef enum {
CliModKeyNo = 0,
CliModKeyAlt = 2,
CliModKeyCtrl = 4,
CliModKeyMeta = 8,
} CliModKey;
typedef struct {
CliModKey modifiers;
CliKey key;
} CliKeyCombo;
/**
* @brief Reads a key or key combination
*/
CliKeyCombo cli_read_ansi_key_combo(Cli* cli);
#ifdef __cplusplus
}
#endif

View File

@@ -1,6 +1,5 @@
#include "cli_commands.h" #include "cli_commands.h"
#include "cli_command_gpio.h" #include "cli_command_gpio.h"
#include "cli_ansi.h"
#include <core/thread.h> #include <core/thread.h>
#include <furi_hal.h> #include <furi_hal.h>
@@ -8,10 +7,10 @@
#include <task_control_block.h> #include <task_control_block.h>
#include <time.h> #include <time.h>
#include <notification/notification_messages.h> #include <notification/notification_messages.h>
#include <notification/notification_app.h>
#include <loader/loader.h> #include <loader/loader.h>
#include <lib/toolbox/args.h> #include <lib/toolbox/args.h>
#include <lib/toolbox/strint.h> #include <lib/toolbox/strint.h>
#include <storage/storage.h>
// Close to ISO, `date +'%Y-%m-%d %H:%M:%S %u'` // Close to ISO, `date +'%Y-%m-%d %H:%M:%S %u'`
#define CLI_DATE_FORMAT "%.4d-%.2d-%.2d %.2d:%.2d:%.2d %d" #define CLI_DATE_FORMAT "%.4d-%.2d-%.2d %.2d:%.2d:%.2d %d"
@@ -54,196 +53,37 @@ void cli_command_info(Cli* cli, FuriString* args, void* context) {
} }
} }
// Lil Easter egg :>
void cli_command_neofetch(Cli* cli, FuriString* args, void* context) {
UNUSED(cli);
UNUSED(args);
UNUSED(context);
static const char* const neofetch_logo[] = {
" _.-------.._ -,",
" .-\"```\"--..,,_/ /`-, -, \\ ",
" .:\" /:/ /'\\ \\ ,_..., `. | |",
" / ,----/:/ /`\\ _\\~`_-\"` _;",
" ' / /`\"\"\"'\\ \\ \\.~`_-' ,-\"'/ ",
" | | | 0 | | .-' ,/` /",
" | ,..\\ \\ ,.-\"` ,/` /",
"; : `/`\"\"\\` ,/--==,/-----,",
"| `-...| -.___-Z:_______J...---;",
": ` _-'",
};
#define NEOFETCH_COLOR ANSI_FLIPPER_BRAND_ORANGE
// Determine logo parameters
size_t logo_height = COUNT_OF(neofetch_logo), logo_width = 0;
for(size_t i = 0; i < logo_height; i++)
logo_width = MAX(logo_width, strlen(neofetch_logo[i]));
logo_width += 4; // space between logo and info
// Format hostname delimiter
const size_t size_of_hostname = 4 + strlen(furi_hal_version_get_name_ptr());
char delimiter[64];
memset(delimiter, '-', size_of_hostname);
delimiter[size_of_hostname] = '\0';
// Get heap info
size_t heap_total = memmgr_get_total_heap();
size_t heap_used = heap_total - memmgr_get_free_heap();
uint16_t heap_percent = (100 * heap_used) / heap_total;
// Get storage info
Storage* storage = furi_record_open(RECORD_STORAGE);
uint64_t ext_total, ext_free, ext_used, ext_percent;
storage_common_fs_info(storage, "/ext", &ext_total, &ext_free);
ext_used = ext_total - ext_free;
ext_percent = (100 * ext_used) / ext_total;
ext_used /= 1024 * 1024;
ext_total /= 1024 * 1024;
furi_record_close(RECORD_STORAGE);
// Get battery info
uint16_t charge_percent = furi_hal_power_get_pct();
const char* charge_state;
if(furi_hal_power_is_charging()) {
if((charge_percent < 100) && (!furi_hal_power_is_charging_done())) {
charge_state = "charging";
} else {
charge_state = "charged";
}
} else {
charge_state = "discharging";
}
// Get misc info
uint32_t uptime = furi_get_tick() / furi_kernel_get_tick_frequency();
const Version* version = version_get();
uint16_t major, minor;
furi_hal_info_get_api_version(&major, &minor);
// Print ASCII art with info
const size_t info_height = 16;
for(size_t i = 0; i < MAX(logo_height, info_height); i++) {
printf(NEOFETCH_COLOR "%-*s", logo_width, (i < logo_height) ? neofetch_logo[i] : "");
switch(i) {
case 0: // you@<hostname>
printf("you" ANSI_RESET "@" NEOFETCH_COLOR "%s", furi_hal_version_get_name_ptr());
break;
case 1: // delimiter
printf(ANSI_RESET "%s", delimiter);
break;
case 2: // OS: FURI <edition> <branch> <version> <commit> (SDK <maj>.<min>)
printf(
"OS" ANSI_RESET ": FURI %s %s %s %s (SDK %hu.%hu)",
version_get_version(version),
version_get_gitbranch(version),
version_get_version(version),
version_get_githash(version),
major,
minor);
break;
case 3: // Host: <model> <hostname>
printf(
"Host" ANSI_RESET ": %s %s",
furi_hal_version_get_model_code(),
furi_hal_version_get_device_name_ptr());
break;
case 4: // Kernel: FreeRTOS <maj>.<min>.<build>
printf(
"Kernel" ANSI_RESET ": FreeRTOS %d.%d.%d",
tskKERNEL_VERSION_MAJOR,
tskKERNEL_VERSION_MINOR,
tskKERNEL_VERSION_BUILD);
break;
case 5: // Uptime: ?h?m?s
printf(
"Uptime" ANSI_RESET ": %luh%lum%lus",
uptime / 60 / 60,
uptime / 60 % 60,
uptime % 60);
break;
case 6: // ST7567 128x64 @ 1 bpp in 1.4"
printf("Display" ANSI_RESET ": ST7567 128x64 @ 1 bpp in 1.4\"");
break;
case 7: // DE: GuiSrv
printf("DE" ANSI_RESET ": GuiSrv");
break;
case 8: // Shell: CliSrv
printf("Shell" ANSI_RESET ": CliSrv");
break;
case 9: // CPU: STM32WB55RG @ 64 MHz
printf("CPU" ANSI_RESET ": STM32WB55RG @ 64 MHz");
break;
case 10: // Memory: <used> / <total> B (??%)
printf(
"Memory" ANSI_RESET ": %zu / %zu B (%hu%%)", heap_used, heap_total, heap_percent);
break;
case 11: // Disk (/ext): <used> / <total> MiB (??%)
printf(
"Disk (/ext)" ANSI_RESET ": %llu / %llu MiB (%llu%%)",
ext_used,
ext_total,
ext_percent);
break;
case 12: // Battery: ??% (<state>)
printf("Battery" ANSI_RESET ": %hu%% (%s)" ANSI_RESET, charge_percent, charge_state);
break;
case 13: // empty space
break;
case 14: // Colors (line 1)
for(size_t j = 30; j <= 37; j++)
printf("\e[%dm███", j);
break;
case 15: // Colors (line 2)
for(size_t j = 90; j <= 97; j++)
printf("\e[%dm███", j);
break;
default:
break;
}
printf("\r\n");
}
printf(ANSI_RESET);
#undef NEOFETCH_COLOR
}
void cli_command_help(Cli* cli, FuriString* args, void* context) { void cli_command_help(Cli* cli, FuriString* args, void* context) {
UNUSED(args);
UNUSED(context); UNUSED(context);
printf("Commands available:"); printf("Commands available:");
// Count non-hidden commands // Command count
CliCommandTree_it_t it_count; const size_t commands_count = CliCommandTree_size(cli->commands);
CliCommandTree_it(it_count, cli->commands); const size_t commands_count_mid = commands_count / 2 + commands_count % 2;
size_t commands_count = 0;
while(!CliCommandTree_end_p(it_count)) {
if(!(CliCommandTree_cref(it_count)->value_ptr->flags & CliCommandFlagHidden))
commands_count++;
CliCommandTree_next(it_count);
}
// Create iterators starting at different positions // Use 2 iterators from start and middle to show 2 columns
const size_t columns = 3; CliCommandTree_it_t it_left;
const size_t commands_per_column = (commands_count / columns) + (commands_count % columns); CliCommandTree_it(it_left, cli->commands);
CliCommandTree_it_t iterators[columns]; CliCommandTree_it_t it_right;
for(size_t c = 0; c < columns; c++) { CliCommandTree_it(it_right, cli->commands);
CliCommandTree_it(iterators[c], cli->commands); for(size_t i = 0; i < commands_count_mid; i++)
for(size_t i = 0; i < c * commands_per_column; i++) CliCommandTree_next(it_right);
CliCommandTree_next(iterators[c]);
}
// Print commands // Iterate throw tree
for(size_t r = 0; r < commands_per_column; r++) { for(size_t i = 0; i < commands_count_mid; i++) {
printf("\r\n"); printf("\r\n");
// Left Column
for(size_t c = 0; c < columns; c++) { if(!CliCommandTree_end_p(it_left)) {
if(!CliCommandTree_end_p(iterators[c])) { printf("%-30s", furi_string_get_cstr(*CliCommandTree_ref(it_left)->key_ptr));
const CliCommandTree_itref_t* item = CliCommandTree_cref(iterators[c]); CliCommandTree_next(it_left);
if(!(item->value_ptr->flags & CliCommandFlagHidden)) {
printf("%-30s", furi_string_get_cstr(*item->key_ptr));
}
CliCommandTree_next(iterators[c]);
}
} }
} // Right Column
if(!CliCommandTree_end_p(it_right)) {
printf("%s", furi_string_get_cstr(*CliCommandTree_ref(it_right)->key_ptr));
CliCommandTree_next(it_right);
}
};
if(furi_string_size(args) > 0) { if(furi_string_size(args) > 0) {
cli_nl(cli); cli_nl(cli);
@@ -477,13 +317,24 @@ void cli_command_sysctl(Cli* cli, FuriString* args, void* context) {
void cli_command_vibro(Cli* cli, FuriString* args, void* context) { void cli_command_vibro(Cli* cli, FuriString* args, void* context) {
UNUSED(cli); UNUSED(cli);
UNUSED(context); UNUSED(context);
if(!furi_string_cmp(args, "0")) { if(!furi_string_cmp(args, "0")) {
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
notification_message_block(notification, &sequence_reset_vibro); notification_message_block(notification, &sequence_reset_vibro);
furi_record_close(RECORD_NOTIFICATION); furi_record_close(RECORD_NOTIFICATION);
} else if(!furi_string_cmp(args, "1")) { } else if(!furi_string_cmp(args, "1")) {
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagStealthMode)) {
printf("Flipper is in stealth mode. Unmute the device to control vibration.");
return;
}
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
notification_message_block(notification, &sequence_set_vibro_on); if(notification->settings.vibro_on) {
notification_message_block(notification, &sequence_set_vibro_on);
} else {
printf("Vibro is disabled in settings. Enable it to control vibration.");
}
furi_record_close(RECORD_NOTIFICATION); furi_record_close(RECORD_NOTIFICATION);
} else { } else {
cli_print_usage("vibro", "<1|0>", furi_string_get_cstr(args)); cli_print_usage("vibro", "<1|0>", furi_string_get_cstr(args));
@@ -552,18 +403,16 @@ static void cli_command_top(Cli* cli, FuriString* args, void* context) {
int interval = 1000; int interval = 1000;
args_read_int_and_trim(args, &interval); args_read_int_and_trim(args, &interval);
if(interval) printf("\e[2J\e[?25l"); // Clear display, hide cursor
FuriThreadList* thread_list = furi_thread_list_alloc(); FuriThreadList* thread_list = furi_thread_list_alloc();
while(!cli_cmd_interrupt_received(cli)) { while(!cli_cmd_interrupt_received(cli)) {
uint32_t tick = furi_get_tick(); uint32_t tick = furi_get_tick();
furi_thread_enumerate(thread_list); furi_thread_enumerate(thread_list);
if(interval) printf("\e[0;0f"); // Return to 0,0 if(interval) printf("\e[2J\e[0;0f"); // Clear display and return to 0
uint32_t uptime = tick / furi_kernel_get_tick_frequency(); uint32_t uptime = tick / furi_kernel_get_tick_frequency();
printf( printf(
"\rThreads: %zu, ISR Time: %0.2f%%, Uptime: %luh%lum%lus\e[0K\r\n", "Threads: %zu, ISR Time: %0.2f%%, Uptime: %luh%lum%lus\r\n",
furi_thread_list_size(thread_list), furi_thread_list_size(thread_list),
(double)furi_thread_list_get_isr_time(thread_list), (double)furi_thread_list_get_isr_time(thread_list),
uptime / 60 / 60, uptime / 60 / 60,
@@ -571,14 +420,14 @@ static void cli_command_top(Cli* cli, FuriString* args, void* context) {
uptime % 60); uptime % 60);
printf( printf(
"\rHeap: total %zu, free %zu, minimum %zu, max block %zu\e[0K\r\n\r\n", "Heap: total %zu, free %zu, minimum %zu, max block %zu\r\n\r\n",
memmgr_get_total_heap(), memmgr_get_total_heap(),
memmgr_get_free_heap(), memmgr_get_free_heap(),
memmgr_get_minimum_free_heap(), memmgr_get_minimum_free_heap(),
memmgr_heap_get_max_free_block()); memmgr_heap_get_max_free_block());
printf( printf(
"\r%-17s %-20s %-10s %5s %12s %6s %10s %7s %5s\e[0K\r\n", "%-17s %-20s %-10s %5s %12s %6s %10s %7s %5s\r\n",
"AppID", "AppID",
"Name", "Name",
"State", "State",
@@ -592,7 +441,7 @@ static void cli_command_top(Cli* cli, FuriString* args, void* context) {
for(size_t i = 0; i < furi_thread_list_size(thread_list); i++) { for(size_t i = 0; i < furi_thread_list_size(thread_list); i++) {
const FuriThreadListItem* item = furi_thread_list_get_at(thread_list, i); const FuriThreadListItem* item = furi_thread_list_get_at(thread_list, i);
printf( printf(
"\r%-17s %-20s %-10s %5d 0x%08lx %6lu %10lu %7zu %5.1f\e[0K\r\n", "%-17s %-20s %-10s %5d 0x%08lx %6lu %10lu %7zu %5.1f\r\n",
item->app_id, item->app_id,
item->name, item->name,
item->state, item->state,
@@ -611,8 +460,6 @@ static void cli_command_top(Cli* cli, FuriString* args, void* context) {
} }
} }
furi_thread_list_free(thread_list); furi_thread_list_free(thread_list);
if(interval) printf("\e[?25h"); // Show cursor
} }
void cli_command_free(Cli* cli, FuriString* args, void* context) { void cli_command_free(Cli* cli, FuriString* args, void* context) {
@@ -664,12 +511,6 @@ void cli_commands_init(Cli* cli) {
cli_add_command(cli, "!", CliCommandFlagParallelSafe, cli_command_info, (void*)true); cli_add_command(cli, "!", CliCommandFlagParallelSafe, cli_command_info, (void*)true);
cli_add_command(cli, "info", CliCommandFlagParallelSafe, cli_command_info, NULL); cli_add_command(cli, "info", CliCommandFlagParallelSafe, cli_command_info, NULL);
cli_add_command(cli, "device_info", CliCommandFlagParallelSafe, cli_command_info, (void*)true); cli_add_command(cli, "device_info", CliCommandFlagParallelSafe, cli_command_info, (void*)true);
cli_add_command(
cli,
"neofetch",
CliCommandFlagParallelSafe | CliCommandFlagHidden,
cli_command_neofetch,
NULL);
cli_add_command(cli, "?", CliCommandFlagParallelSafe, cli_command_help, NULL); cli_add_command(cli, "?", CliCommandFlagParallelSafe, cli_command_help, NULL);
cli_add_command(cli, "help", CliCommandFlagParallelSafe, cli_command_help, NULL); cli_add_command(cli, "help", CliCommandFlagParallelSafe, cli_command_help, NULL);

View File

@@ -3,7 +3,6 @@
#include <lib/toolbox/args.h> #include <lib/toolbox/args.h>
#include <cli/cli.h> #include <cli/cli.h>
#include <cli/cli_ansi.h>
void crypto_cli_print_usage(void) { void crypto_cli_print_usage(void) {
printf("Usage:\r\n"); printf("Usage:\r\n");
@@ -46,14 +45,14 @@ void crypto_cli_encrypt(Cli* cli, FuriString* args) {
input = furi_string_alloc(); input = furi_string_alloc();
char c; char c;
while(cli_read(cli, (uint8_t*)&c, 1) == 1) { while(cli_read(cli, (uint8_t*)&c, 1) == 1) {
if(c == CliKeyETX) { if(c == CliSymbolAsciiETX) {
printf("\r\n"); printf("\r\n");
break; break;
} else if(c >= 0x20 && c < 0x7F) { } else if(c >= 0x20 && c < 0x7F) {
putc(c, stdout); putc(c, stdout);
fflush(stdout); fflush(stdout);
furi_string_push_back(input, c); furi_string_push_back(input, c);
} else if(c == CliKeyCR) { } else if(c == CliSymbolAsciiCR) {
printf("\r\n"); printf("\r\n");
furi_string_cat(input, "\r\n"); furi_string_cat(input, "\r\n");
} }
@@ -121,14 +120,14 @@ void crypto_cli_decrypt(Cli* cli, FuriString* args) {
hex_input = furi_string_alloc(); hex_input = furi_string_alloc();
char c; char c;
while(cli_read(cli, (uint8_t*)&c, 1) == 1) { while(cli_read(cli, (uint8_t*)&c, 1) == 1) {
if(c == CliKeyETX) { if(c == CliSymbolAsciiETX) {
printf("\r\n"); printf("\r\n");
break; break;
} else if(c >= 0x20 && c < 0x7F) { } else if(c >= 0x20 && c < 0x7F) {
putc(c, stdout); putc(c, stdout);
fflush(stdout); fflush(stdout);
furi_string_push_back(hex_input, c); furi_string_push_back(hex_input, c);
} else if(c == CliKeyCR) { } else if(c == CliSymbolAsciiCR) {
printf("\r\n"); printf("\r\n");
} }
} }

View File

@@ -523,7 +523,7 @@ int32_t desktop_srv(void* p) {
scene_manager_next_scene(desktop->scene_manager, DesktopSceneMain); scene_manager_next_scene(desktop->scene_manager, DesktopSceneMain);
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagLock)) { if(desktop_pin_code_is_set()) {
desktop_lock(desktop); desktop_lock(desktop);
} }

View File

@@ -2,7 +2,6 @@
#include <furi_hal.h> #include <furi_hal.h>
#include <cli/cli.h> #include <cli/cli.h>
#include <cli/cli_ansi.h>
#include <lib/toolbox/args.h> #include <lib/toolbox/args.h>
#include <lib/toolbox/dir_walk.h> #include <lib/toolbox/dir_walk.h>
#include <lib/toolbox/md5_calc.h> #include <lib/toolbox/md5_calc.h>
@@ -225,7 +224,7 @@ static void storage_cli_write(Cli* cli, FuriString* path, FuriString* args) {
while(true) { while(true) {
uint8_t symbol = cli_getc(cli); uint8_t symbol = cli_getc(cli);
if(symbol == CliKeyETX) { if(symbol == CliSymbolAsciiETX) {
size_t write_size = read_index % buffer_size; size_t write_size = read_index % buffer_size;
if(write_size > 0) { if(write_size > 0) {

View File

@@ -1,12 +0,0 @@
App(
appid="bad_ble",
name="Bad BLE",
apptype=FlipperAppType.EXTERNAL,
entry_point="bad_ble_app",
stack_size=2 * 1024,
icon="A_BadUsb_14",
fap_libs=["assets", "ble_profile"],
fap_icon="icon.png",
fap_icon_assets="assets",
fap_category="Bluetooth",
)

View File

@@ -1,196 +0,0 @@
#include "bad_ble_app_i.h"
#include <furi.h>
#include <furi_hal.h>
#include <storage/storage.h>
#include <lib/toolbox/path.h>
#include <flipper_format/flipper_format.h>
#define BAD_BLE_SETTINGS_PATH BAD_BLE_APP_BASE_FOLDER "/.badble.settings"
#define BAD_BLE_SETTINGS_FILE_TYPE "Flipper BadBLE Settings File"
#define BAD_BLE_SETTINGS_VERSION 1
#define BAD_BLE_SETTINGS_DEFAULT_LAYOUT BAD_BLE_APP_PATH_LAYOUT_FOLDER "/en-US.kl"
static bool bad_ble_app_custom_event_callback(void* context, uint32_t event) {
furi_assert(context);
BadBleApp* app = context;
return scene_manager_handle_custom_event(app->scene_manager, event);
}
static bool bad_ble_app_back_event_callback(void* context) {
furi_assert(context);
BadBleApp* app = context;
return scene_manager_handle_back_event(app->scene_manager);
}
static void bad_ble_app_tick_event_callback(void* context) {
furi_assert(context);
BadBleApp* app = context;
scene_manager_handle_tick_event(app->scene_manager);
}
static void bad_ble_load_settings(BadBleApp* app) {
Storage* storage = furi_record_open(RECORD_STORAGE);
FlipperFormat* fff = flipper_format_file_alloc(storage);
bool state = false;
FuriString* temp_str = furi_string_alloc();
uint32_t version = 0;
if(flipper_format_file_open_existing(fff, BAD_BLE_SETTINGS_PATH)) {
do {
if(!flipper_format_read_header(fff, temp_str, &version)) break;
if((strcmp(furi_string_get_cstr(temp_str), BAD_BLE_SETTINGS_FILE_TYPE) != 0) ||
(version != BAD_BLE_SETTINGS_VERSION))
break;
if(!flipper_format_read_string(fff, "layout", temp_str)) break;
state = true;
} while(0);
}
flipper_format_free(fff);
furi_record_close(RECORD_STORAGE);
if(state) {
furi_string_set(app->keyboard_layout, temp_str);
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_BLE_SETTINGS_DEFAULT_LAYOUT);
}
} else {
furi_string_set(app->keyboard_layout, BAD_BLE_SETTINGS_DEFAULT_LAYOUT);
}
furi_string_free(temp_str);
}
static void bad_ble_save_settings(BadBleApp* app) {
Storage* storage = furi_record_open(RECORD_STORAGE);
FlipperFormat* fff = flipper_format_file_alloc(storage);
if(flipper_format_file_open_always(fff, BAD_BLE_SETTINGS_PATH)) {
do {
if(!flipper_format_write_header_cstr(
fff, BAD_BLE_SETTINGS_FILE_TYPE, BAD_BLE_SETTINGS_VERSION))
break;
if(!flipper_format_write_string(fff, "layout", app->keyboard_layout)) break;
} while(0);
}
flipper_format_free(fff);
furi_record_close(RECORD_STORAGE);
}
BadBleApp* bad_ble_app_alloc(char* arg) {
BadBleApp* app = malloc(sizeof(BadBleApp));
app->bad_ble_script = NULL;
app->file_path = furi_string_alloc();
app->keyboard_layout = furi_string_alloc();
if(arg && strlen(arg)) {
furi_string_set(app->file_path, arg);
}
bad_ble_load_settings(app);
app->gui = furi_record_open(RECORD_GUI);
app->notifications = furi_record_open(RECORD_NOTIFICATION);
app->dialogs = furi_record_open(RECORD_DIALOGS);
app->view_dispatcher = view_dispatcher_alloc();
app->scene_manager = scene_manager_alloc(&bad_ble_scene_handlers, app);
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
view_dispatcher_set_tick_event_callback(
app->view_dispatcher, bad_ble_app_tick_event_callback, 500);
view_dispatcher_set_custom_event_callback(
app->view_dispatcher, bad_ble_app_custom_event_callback);
view_dispatcher_set_navigation_event_callback(
app->view_dispatcher, bad_ble_app_back_event_callback);
// Custom Widget
app->widget = widget_alloc();
view_dispatcher_add_view(
app->view_dispatcher, BadBleAppViewWidget, widget_get_view(app->widget));
// Popup
app->popup = popup_alloc();
view_dispatcher_add_view(app->view_dispatcher, BadBleAppViewPopup, popup_get_view(app->popup));
app->var_item_list = variable_item_list_alloc();
view_dispatcher_add_view(
app->view_dispatcher,
BadBleAppViewConfig,
variable_item_list_get_view(app->var_item_list));
app->bad_ble_view = bad_ble_view_alloc();
view_dispatcher_add_view(
app->view_dispatcher, BadBleAppViewWork, bad_ble_view_get_view(app->bad_ble_view));
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
if(!furi_string_empty(app->file_path)) {
scene_manager_next_scene(app->scene_manager, BadBleSceneWork);
} else {
furi_string_set(app->file_path, BAD_BLE_APP_BASE_FOLDER);
scene_manager_next_scene(app->scene_manager, BadBleSceneFileSelect);
}
return app;
}
void bad_ble_app_free(BadBleApp* app) {
furi_assert(app);
if(app->bad_ble_script) {
bad_ble_script_close(app->bad_ble_script);
app->bad_ble_script = NULL;
}
// Views
view_dispatcher_remove_view(app->view_dispatcher, BadBleAppViewWork);
bad_ble_view_free(app->bad_ble_view);
// Custom Widget
view_dispatcher_remove_view(app->view_dispatcher, BadBleAppViewWidget);
widget_free(app->widget);
// Popup
view_dispatcher_remove_view(app->view_dispatcher, BadBleAppViewPopup);
popup_free(app->popup);
// Config menu
view_dispatcher_remove_view(app->view_dispatcher, BadBleAppViewConfig);
variable_item_list_free(app->var_item_list);
// View dispatcher
view_dispatcher_free(app->view_dispatcher);
scene_manager_free(app->scene_manager);
// Close records
furi_record_close(RECORD_GUI);
furi_record_close(RECORD_NOTIFICATION);
furi_record_close(RECORD_DIALOGS);
bad_ble_save_settings(app);
furi_string_free(app->file_path);
furi_string_free(app->keyboard_layout);
free(app);
}
int32_t bad_ble_app(void* p) {
BadBleApp* bad_ble_app = bad_ble_app_alloc((char*)p);
view_dispatcher_run(bad_ble_app->view_dispatcher);
bad_ble_app_free(bad_ble_app);
return 0;
}

View File

@@ -1,11 +0,0 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
typedef struct BadBleApp BadBleApp;
#ifdef __cplusplus
}
#endif

View File

@@ -1,53 +0,0 @@
#pragma once
#include "bad_ble_app.h"
#include "scenes/bad_ble_scene.h"
#include "helpers/ducky_script.h"
#include "helpers/bad_ble_hid.h"
#include <gui/gui.h>
#include <assets_icons.h>
#include <gui/view_dispatcher.h>
#include <gui/scene_manager.h>
#include <dialogs/dialogs.h>
#include <notification/notification_messages.h>
#include <gui/modules/variable_item_list.h>
#include <gui/modules/widget.h>
#include <gui/modules/popup.h>
#include "views/bad_ble_view.h"
#define BAD_BLE_APP_BASE_FOLDER EXT_PATH("badusb")
#define BAD_BLE_APP_PATH_LAYOUT_FOLDER BAD_BLE_APP_BASE_FOLDER "/assets/layouts"
#define BAD_BLE_APP_SCRIPT_EXTENSION ".txt"
#define BAD_BLE_APP_LAYOUT_EXTENSION ".kl"
typedef enum {
BadBleAppErrorNoFiles,
BadBleAppErrorCloseRpc,
} BadBleAppError;
struct BadBleApp {
Gui* gui;
ViewDispatcher* view_dispatcher;
SceneManager* scene_manager;
NotificationApp* notifications;
DialogsApp* dialogs;
Widget* widget;
Popup* popup;
VariableItemList* var_item_list;
BadBleAppError error;
FuriString* file_path;
FuriString* keyboard_layout;
BadBle* bad_ble_view;
BadBleScript* bad_ble_script;
BadBleHidInterface interface;
};
typedef enum {
BadBleAppViewWidget,
BadBleAppViewPopup,
BadBleAppViewWork,
BadBleAppViewConfig,
} BadBleAppView;

View File

@@ -1,157 +0,0 @@
#include "bad_ble_hid.h"
#include <extra_profiles/hid_profile.h>
#include <bt/bt_service/bt.h>
#include <storage/storage.h>
#define TAG "BadBLE HID"
#define HID_BT_KEYS_STORAGE_NAME ".bt_hid.keys"
typedef struct {
Bt* bt;
FuriHalBleProfileBase* profile;
HidStateCallback state_callback;
void* callback_context;
bool is_connected;
} BleHidInstance;
static const BleProfileHidParams ble_hid_params = {
.device_name_prefix = "BadBLE",
.mac_xor = 0x0002,
};
static void hid_ble_connection_status_callback(BtStatus status, void* context) {
furi_assert(context);
BleHidInstance* ble_hid = context;
ble_hid->is_connected = (status == BtStatusConnected);
if(ble_hid->state_callback) {
ble_hid->state_callback(ble_hid->is_connected, ble_hid->callback_context);
}
}
void* hid_ble_init(FuriHalUsbHidConfig* hid_cfg) {
UNUSED(hid_cfg);
BleHidInstance* ble_hid = malloc(sizeof(BleHidInstance));
ble_hid->bt = furi_record_open(RECORD_BT);
bt_disconnect(ble_hid->bt);
// Wait 2nd core to update nvm storage
furi_delay_ms(200);
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);
furi_check(ble_hid->profile);
furi_hal_bt_start_advertising();
bt_set_status_changed_callback(ble_hid->bt, hid_ble_connection_status_callback, ble_hid);
return ble_hid;
}
void hid_ble_deinit(void* inst) {
BleHidInstance* ble_hid = inst;
furi_assert(ble_hid);
bt_set_status_changed_callback(ble_hid->bt, NULL, NULL);
bt_disconnect(ble_hid->bt);
// Wait 2nd core to update nvm storage
furi_delay_ms(200);
bt_keys_storage_set_default_path(ble_hid->bt);
furi_check(bt_profile_restore_default(ble_hid->bt));
furi_record_close(RECORD_BT);
free(ble_hid);
}
void hid_ble_set_state_callback(void* inst, HidStateCallback cb, void* context) {
BleHidInstance* ble_hid = inst;
furi_assert(ble_hid);
ble_hid->state_callback = cb;
ble_hid->callback_context = context;
}
bool hid_ble_is_connected(void* inst) {
BleHidInstance* ble_hid = inst;
furi_assert(ble_hid);
return ble_hid->is_connected;
}
bool hid_ble_kb_press(void* inst, uint16_t button) {
BleHidInstance* ble_hid = inst;
furi_assert(ble_hid);
return ble_profile_hid_kb_press(ble_hid->profile, button);
}
bool hid_ble_kb_release(void* inst, uint16_t button) {
BleHidInstance* ble_hid = inst;
furi_assert(ble_hid);
return ble_profile_hid_kb_release(ble_hid->profile, button);
}
bool hid_ble_consumer_press(void* inst, uint16_t button) {
BleHidInstance* ble_hid = inst;
furi_assert(ble_hid);
return ble_profile_hid_consumer_key_press(ble_hid->profile, button);
}
bool hid_ble_consumer_release(void* inst, uint16_t button) {
BleHidInstance* ble_hid = inst;
furi_assert(ble_hid);
return ble_profile_hid_consumer_key_release(ble_hid->profile, button);
}
bool hid_ble_release_all(void* inst) {
BleHidInstance* ble_hid = inst;
furi_assert(ble_hid);
bool state = ble_profile_hid_kb_release_all(ble_hid->profile);
state &= ble_profile_hid_consumer_key_release_all(ble_hid->profile);
return state;
}
uint8_t hid_ble_get_led_state(void* inst) {
UNUSED(inst);
FURI_LOG_W(TAG, "hid_ble_get_led_state not implemented");
return 0;
}
static const BadBleHidApi hid_api_ble = {
.init = hid_ble_init,
.deinit = hid_ble_deinit,
.set_state_callback = hid_ble_set_state_callback,
.is_connected = hid_ble_is_connected,
.kb_press = hid_ble_kb_press,
.kb_release = hid_ble_kb_release,
.consumer_press = hid_ble_consumer_press,
.consumer_release = hid_ble_consumer_release,
.release_all = hid_ble_release_all,
.get_led_state = hid_ble_get_led_state,
};
const BadBleHidApi* bad_ble_hid_get_interface(BadBleHidInterface interface) {
UNUSED(interface);
return &hid_api_ble;
}
void bad_ble_hid_ble_remove_pairing(void) {
Bt* bt = furi_record_open(RECORD_BT);
bt_disconnect(bt);
// Wait 2nd core to update nvm storage
furi_delay_ms(200);
furi_hal_bt_stop_advertising();
bt_keys_storage_set_storage_path(bt, APP_DATA_PATH(HID_BT_KEYS_STORAGE_NAME));
bt_forget_bonded_devices(bt);
// Wait 2nd core to update nvm storage
furi_delay_ms(200);
bt_keys_storage_set_default_path(bt);
furi_check(bt_profile_restore_default(bt));
furi_record_close(RECORD_BT);
}

View File

@@ -1,34 +0,0 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <furi.h>
#include <furi_hal.h>
typedef enum {
BadBleHidInterfaceBle,
} BadBleHidInterface;
typedef struct {
void* (*init)(FuriHalUsbHidConfig* hid_cfg);
void (*deinit)(void* inst);
void (*set_state_callback)(void* inst, HidStateCallback cb, void* context);
bool (*is_connected)(void* inst);
bool (*kb_press)(void* inst, uint16_t button);
bool (*kb_release)(void* inst, uint16_t button);
bool (*consumer_press)(void* inst, uint16_t button);
bool (*consumer_release)(void* inst, uint16_t button);
bool (*release_all)(void* inst);
uint8_t (*get_led_state)(void* inst);
} BadBleHidApi;
const BadBleHidApi* bad_ble_hid_get_interface(BadBleHidInterface interface);
void bad_ble_hid_ble_remove_pairing(void);
#ifdef __cplusplus
}
#endif

View File

@@ -1,716 +0,0 @@
#include <furi.h>
#include <furi_hal.h>
#include <gui/gui.h>
#include <input/input.h>
#include <lib/toolbox/args.h>
#include <lib/toolbox/strint.h>
#include <storage/storage.h>
#include "ducky_script.h"
#include "ducky_script_i.h"
#include <dolphin/dolphin.h>
#define TAG "BadBle"
#define WORKER_TAG TAG "Worker"
#define BADUSB_ASCII_TO_KEY(script, x) \
(((uint8_t)x < 128) ? (script->layout[(uint8_t)x]) : HID_KEYBOARD_NONE)
typedef enum {
WorkerEvtStartStop = (1 << 0),
WorkerEvtPauseResume = (1 << 1),
WorkerEvtEnd = (1 << 2),
WorkerEvtConnect = (1 << 3),
WorkerEvtDisconnect = (1 << 4),
} WorkerEvtFlags;
static const char ducky_cmd_id[] = {"ID"};
static const uint8_t numpad_keys[10] = {
HID_KEYPAD_0,
HID_KEYPAD_1,
HID_KEYPAD_2,
HID_KEYPAD_3,
HID_KEYPAD_4,
HID_KEYPAD_5,
HID_KEYPAD_6,
HID_KEYPAD_7,
HID_KEYPAD_8,
HID_KEYPAD_9,
};
uint32_t ducky_get_command_len(const char* line) {
uint32_t len = strlen(line);
for(uint32_t i = 0; i < len; i++) {
if(line[i] == ' ') return i;
}
return 0;
}
bool ducky_is_line_end(const char chr) {
return (chr == ' ') || (chr == '\0') || (chr == '\r') || (chr == '\n');
}
uint16_t ducky_get_keycode(BadBleScript* bad_ble, const char* param, bool accept_chars) {
uint16_t keycode = ducky_get_keycode_by_name(param);
if(keycode != HID_KEYBOARD_NONE) {
return keycode;
}
if((accept_chars) && (strlen(param) > 0)) {
return BADUSB_ASCII_TO_KEY(bad_ble, param[0]) & 0xFF;
}
return 0;
}
bool ducky_get_number(const char* param, uint32_t* val) {
uint32_t value = 0;
if(strint_to_uint32(param, NULL, &value, 10) == StrintParseNoError) {
*val = value;
return true;
}
return false;
}
void ducky_numlock_on(BadBleScript* bad_ble) {
if((bad_ble->hid->get_led_state(bad_ble->hid_inst) & HID_KB_LED_NUM) == 0) {
bad_ble->hid->kb_press(bad_ble->hid_inst, HID_KEYBOARD_LOCK_NUM_LOCK);
bad_ble->hid->kb_release(bad_ble->hid_inst, HID_KEYBOARD_LOCK_NUM_LOCK);
}
}
bool ducky_numpad_press(BadBleScript* bad_ble, const char num) {
if((num < '0') || (num > '9')) return false;
uint16_t key = numpad_keys[num - '0'];
bad_ble->hid->kb_press(bad_ble->hid_inst, key);
bad_ble->hid->kb_release(bad_ble->hid_inst, key);
return true;
}
bool ducky_altchar(BadBleScript* bad_ble, const char* charcode) {
uint8_t i = 0;
bool state = false;
bad_ble->hid->kb_press(bad_ble->hid_inst, KEY_MOD_LEFT_ALT);
while(!ducky_is_line_end(charcode[i])) {
state = ducky_numpad_press(bad_ble, charcode[i]);
if(state == false) break;
i++;
}
bad_ble->hid->kb_release(bad_ble->hid_inst, KEY_MOD_LEFT_ALT);
return state;
}
bool ducky_altstring(BadBleScript* bad_ble, const char* param) {
uint32_t i = 0;
bool state = false;
while(param[i] != '\0') {
if((param[i] < ' ') || (param[i] > '~')) {
i++;
continue; // Skip non-printable chars
}
char temp_str[4];
snprintf(temp_str, 4, "%u", param[i]);
state = ducky_altchar(bad_ble, temp_str);
if(state == false) break;
i++;
}
return state;
}
int32_t ducky_error(BadBleScript* bad_ble, const char* text, ...) {
va_list args;
va_start(args, text);
vsnprintf(bad_ble->st.error, sizeof(bad_ble->st.error), text, args);
va_end(args);
return SCRIPT_STATE_ERROR;
}
bool ducky_string(BadBleScript* bad_ble, const char* param) {
uint32_t i = 0;
while(param[i] != '\0') {
if(param[i] != '\n') {
uint16_t keycode = BADUSB_ASCII_TO_KEY(bad_ble, param[i]);
if(keycode != HID_KEYBOARD_NONE) {
bad_ble->hid->kb_press(bad_ble->hid_inst, keycode);
bad_ble->hid->kb_release(bad_ble->hid_inst, keycode);
}
} else {
bad_ble->hid->kb_press(bad_ble->hid_inst, HID_KEYBOARD_RETURN);
bad_ble->hid->kb_release(bad_ble->hid_inst, HID_KEYBOARD_RETURN);
}
i++;
}
bad_ble->stringdelay = 0;
return true;
}
static bool ducky_string_next(BadBleScript* bad_ble) {
if(bad_ble->string_print_pos >= furi_string_size(bad_ble->string_print)) {
return true;
}
char print_char = furi_string_get_char(bad_ble->string_print, bad_ble->string_print_pos);
if(print_char != '\n') {
uint16_t keycode = BADUSB_ASCII_TO_KEY(bad_ble, print_char);
if(keycode != HID_KEYBOARD_NONE) {
bad_ble->hid->kb_press(bad_ble->hid_inst, keycode);
bad_ble->hid->kb_release(bad_ble->hid_inst, keycode);
}
} else {
bad_ble->hid->kb_press(bad_ble->hid_inst, HID_KEYBOARD_RETURN);
bad_ble->hid->kb_release(bad_ble->hid_inst, HID_KEYBOARD_RETURN);
}
bad_ble->string_print_pos++;
return false;
}
static int32_t ducky_parse_line(BadBleScript* bad_ble, FuriString* line) {
uint32_t line_len = furi_string_size(line);
const char* line_tmp = 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);
// Ducky Lang Functions
int32_t cmd_result = ducky_execute_cmd(bad_ble, line_tmp);
if(cmd_result != SCRIPT_STATE_CMD_UNKNOWN) {
return cmd_result;
}
// Special keys + modifiers
uint16_t key = ducky_get_keycode(bad_ble, line_tmp, false);
if(key == HID_KEYBOARD_NONE) {
return ducky_error(bad_ble, "No keycode defined for %s", line_tmp);
}
if((key & 0xFF00) != 0) {
// It's a modifier key
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
key |= ducky_get_keycode(bad_ble, line_tmp, true);
}
bad_ble->hid->kb_press(bad_ble->hid_inst, key);
bad_ble->hid->kb_release(bad_ble->hid_inst, key);
return 0;
}
static bool ducky_set_usb_id(BadBleScript* bad_ble, const char* line) {
if(sscanf(line, "%lX:%lX", &bad_ble->hid_cfg.vid, &bad_ble->hid_cfg.pid) == 2) {
bad_ble->hid_cfg.manuf[0] = '\0';
bad_ble->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_ble->hid_cfg.manuf,
bad_ble->hid_cfg.product);
}
FURI_LOG_D(
WORKER_TAG,
"set id: %04lX:%04lX mfr:%s product:%s",
bad_ble->hid_cfg.vid,
bad_ble->hid_cfg.pid,
bad_ble->hid_cfg.manuf,
bad_ble->hid_cfg.product);
return true;
}
return false;
}
static void bad_ble_hid_state_callback(bool state, void* context) {
furi_assert(context);
BadBleScript* bad_ble = context;
if(state == true) {
furi_thread_flags_set(furi_thread_get_id(bad_ble->thread), WorkerEvtConnect);
} else {
furi_thread_flags_set(furi_thread_get_id(bad_ble->thread), WorkerEvtDisconnect);
}
}
static bool ducky_script_preload(BadBleScript* bad_ble, File* script_file) {
uint8_t ret = 0;
uint32_t line_len = 0;
furi_string_reset(bad_ble->line);
do {
ret = storage_file_read(script_file, bad_ble->file_buf, FILE_BUFFER_LEN);
for(uint16_t i = 0; i < ret; i++) {
if(bad_ble->file_buf[i] == '\n' && line_len > 0) {
bad_ble->st.line_nb++;
line_len = 0;
} else {
if(bad_ble->st.line_nb == 0) { // Save first line
furi_string_push_back(bad_ble->line, bad_ble->file_buf[i]);
}
line_len++;
}
}
if(storage_file_eof(script_file)) {
if(line_len > 0) {
bad_ble->st.line_nb++;
break;
}
}
} while(ret > 0);
const char* line_tmp = furi_string_get_cstr(bad_ble->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_ble, &line_tmp[strlen(ducky_cmd_id) + 1]);
}
if(id_set) {
bad_ble->hid_inst = bad_ble->hid->init(&bad_ble->hid_cfg);
} else {
bad_ble->hid_inst = bad_ble->hid->init(NULL);
}
bad_ble->hid->set_state_callback(bad_ble->hid_inst, bad_ble_hid_state_callback, bad_ble);
storage_file_seek(script_file, 0, true);
furi_string_reset(bad_ble->line);
return true;
}
static int32_t ducky_script_execute_next(BadBleScript* bad_ble, File* script_file) {
int32_t delay_val = 0;
if(bad_ble->repeat_cnt > 0) {
bad_ble->repeat_cnt--;
delay_val = ducky_parse_line(bad_ble, bad_ble->line_prev);
if(delay_val == SCRIPT_STATE_NEXT_LINE) { // Empty line
return 0;
} else if(delay_val == SCRIPT_STATE_STRING_START) { // Print string with delays
return delay_val;
} else if(delay_val == SCRIPT_STATE_WAIT_FOR_BTN) { // wait for button
return delay_val;
} else if(delay_val < 0) { // Script error
bad_ble->st.error_line = bad_ble->st.line_cur - 1;
FURI_LOG_E(WORKER_TAG, "Unknown command at line %zu", bad_ble->st.line_cur - 1U);
return SCRIPT_STATE_ERROR;
} else {
return delay_val + bad_ble->defdelay;
}
}
furi_string_set(bad_ble->line_prev, bad_ble->line);
furi_string_reset(bad_ble->line);
while(1) {
if(bad_ble->buf_len == 0) {
bad_ble->buf_len = storage_file_read(script_file, bad_ble->file_buf, FILE_BUFFER_LEN);
if(storage_file_eof(script_file)) {
if((bad_ble->buf_len < FILE_BUFFER_LEN) && (bad_ble->file_end == false)) {
bad_ble->file_buf[bad_ble->buf_len] = '\n';
bad_ble->buf_len++;
bad_ble->file_end = true;
}
}
bad_ble->buf_start = 0;
if(bad_ble->buf_len == 0) return SCRIPT_STATE_END;
}
for(uint8_t i = bad_ble->buf_start; i < (bad_ble->buf_start + bad_ble->buf_len); i++) {
if(bad_ble->file_buf[i] == '\n' && furi_string_size(bad_ble->line) > 0) {
bad_ble->st.line_cur++;
bad_ble->buf_len = bad_ble->buf_len + bad_ble->buf_start - (i + 1);
bad_ble->buf_start = i + 1;
furi_string_trim(bad_ble->line);
delay_val = ducky_parse_line(bad_ble, bad_ble->line);
if(delay_val == SCRIPT_STATE_NEXT_LINE) { // Empty line
return 0;
} else if(delay_val == SCRIPT_STATE_STRING_START) { // Print string with delays
return delay_val;
} else if(delay_val == SCRIPT_STATE_WAIT_FOR_BTN) { // wait for button
return delay_val;
} else if(delay_val < 0) {
bad_ble->st.error_line = bad_ble->st.line_cur;
FURI_LOG_E(WORKER_TAG, "Unknown command at line %zu", bad_ble->st.line_cur);
return SCRIPT_STATE_ERROR;
} else {
return delay_val + bad_ble->defdelay;
}
} else {
furi_string_push_back(bad_ble->line, bad_ble->file_buf[i]);
}
}
bad_ble->buf_len = 0;
if(bad_ble->file_end) return SCRIPT_STATE_END;
}
return 0;
}
static uint32_t bad_ble_flags_get(uint32_t flags_mask, uint32_t timeout) {
uint32_t flags = furi_thread_flags_get();
furi_check((flags & FuriFlagError) == 0);
if(flags == 0) {
flags = furi_thread_flags_wait(flags_mask, FuriFlagWaitAny, timeout);
furi_check(((flags & FuriFlagError) == 0) || (flags == (unsigned)FuriFlagErrorTimeout));
} else {
uint32_t state = furi_thread_flags_clear(flags);
furi_check((state & FuriFlagError) == 0);
}
return flags;
}
static int32_t bad_ble_worker(void* context) {
BadBleScript* bad_ble = context;
BadBleWorkerState worker_state = BadBleStateInit;
BadBleWorkerState pause_state = BadBleStateRunning;
int32_t delay_val = 0;
FURI_LOG_I(WORKER_TAG, "Init");
File* script_file = storage_file_alloc(furi_record_open(RECORD_STORAGE));
bad_ble->line = furi_string_alloc();
bad_ble->line_prev = furi_string_alloc();
bad_ble->string_print = furi_string_alloc();
while(1) {
if(worker_state == BadBleStateInit) { // State: initialization
if(storage_file_open(
script_file,
furi_string_get_cstr(bad_ble->file_path),
FSAM_READ,
FSOM_OPEN_EXISTING)) {
if((ducky_script_preload(bad_ble, script_file)) && (bad_ble->st.line_nb > 0)) {
if(bad_ble->hid->is_connected(bad_ble->hid_inst)) {
worker_state = BadBleStateIdle; // Ready to run
} else {
worker_state = BadBleStateNotConnected; // USB not connected
}
} else {
worker_state = BadBleStateScriptError; // Script preload error
}
} else {
FURI_LOG_E(WORKER_TAG, "File open error");
worker_state = BadBleStateFileError; // File open error
}
bad_ble->st.state = worker_state;
} else if(worker_state == BadBleStateNotConnected) { // State: USB not connected
uint32_t flags = bad_ble_flags_get(
WorkerEvtEnd | WorkerEvtConnect | WorkerEvtDisconnect | WorkerEvtStartStop,
FuriWaitForever);
if(flags & WorkerEvtEnd) {
break;
} else if(flags & WorkerEvtConnect) {
worker_state = BadBleStateIdle; // Ready to run
} else if(flags & WorkerEvtStartStop) {
worker_state = BadBleStateWillRun; // Will run when USB is connected
}
bad_ble->st.state = worker_state;
} else if(worker_state == BadBleStateIdle) { // State: ready to start
uint32_t flags = bad_ble_flags_get(
WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtDisconnect, FuriWaitForever);
if(flags & WorkerEvtEnd) {
break;
} else if(flags & WorkerEvtStartStop) { // Start executing script
dolphin_deed(DolphinDeedBadUsbPlayScript);
delay_val = 0;
bad_ble->buf_len = 0;
bad_ble->st.line_cur = 0;
bad_ble->defdelay = 0;
bad_ble->stringdelay = 0;
bad_ble->defstringdelay = 0;
bad_ble->repeat_cnt = 0;
bad_ble->key_hold_nb = 0;
bad_ble->file_end = false;
storage_file_seek(script_file, 0, true);
worker_state = BadBleStateRunning;
} else if(flags & WorkerEvtDisconnect) {
worker_state = BadBleStateNotConnected; // USB disconnected
}
bad_ble->st.state = worker_state;
} else if(worker_state == BadBleStateWillRun) { // State: start on connection
uint32_t flags = bad_ble_flags_get(
WorkerEvtEnd | WorkerEvtConnect | WorkerEvtStartStop, FuriWaitForever);
if(flags & WorkerEvtEnd) {
break;
} else if(flags & WorkerEvtConnect) { // Start executing script
dolphin_deed(DolphinDeedBadUsbPlayScript);
delay_val = 0;
bad_ble->buf_len = 0;
bad_ble->st.line_cur = 0;
bad_ble->defdelay = 0;
bad_ble->stringdelay = 0;
bad_ble->defstringdelay = 0;
bad_ble->repeat_cnt = 0;
bad_ble->file_end = false;
storage_file_seek(script_file, 0, true);
// extra time for PC to recognize Flipper as keyboard
flags = furi_thread_flags_wait(
WorkerEvtEnd | WorkerEvtDisconnect | WorkerEvtStartStop,
FuriFlagWaitAny | FuriFlagNoClear,
1500);
if(flags == (unsigned)FuriFlagErrorTimeout) {
// If nothing happened - start script execution
worker_state = BadBleStateRunning;
} else if(flags & WorkerEvtStartStop) {
worker_state = BadBleStateIdle;
furi_thread_flags_clear(WorkerEvtStartStop);
}
} else if(flags & WorkerEvtStartStop) { // Cancel scheduled execution
worker_state = BadBleStateNotConnected;
}
bad_ble->st.state = worker_state;
} else if(worker_state == BadBleStateRunning) { // State: running
uint16_t delay_cur = (delay_val > 1000) ? (1000) : (delay_val);
uint32_t flags = furi_thread_flags_wait(
WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtPauseResume | WorkerEvtDisconnect,
FuriFlagWaitAny,
delay_cur);
delay_val -= delay_cur;
if(!(flags & FuriFlagError)) {
if(flags & WorkerEvtEnd) {
break;
} else if(flags & WorkerEvtStartStop) {
worker_state = BadBleStateIdle; // Stop executing script
bad_ble->hid->release_all(bad_ble->hid_inst);
} else if(flags & WorkerEvtDisconnect) {
worker_state = BadBleStateNotConnected; // USB disconnected
bad_ble->hid->release_all(bad_ble->hid_inst);
} else if(flags & WorkerEvtPauseResume) {
pause_state = BadBleStateRunning;
worker_state = BadBleStatePaused; // Pause
}
bad_ble->st.state = worker_state;
continue;
} else if(
(flags == (unsigned)FuriFlagErrorTimeout) ||
(flags == (unsigned)FuriFlagErrorResource)) {
if(delay_val > 0) {
bad_ble->st.delay_remain--;
continue;
}
bad_ble->st.state = BadBleStateRunning;
delay_val = ducky_script_execute_next(bad_ble, script_file);
if(delay_val == SCRIPT_STATE_ERROR) { // Script error
delay_val = 0;
worker_state = BadBleStateScriptError;
bad_ble->st.state = worker_state;
bad_ble->hid->release_all(bad_ble->hid_inst);
} else if(delay_val == SCRIPT_STATE_END) { // End of script
delay_val = 0;
worker_state = BadBleStateIdle;
bad_ble->st.state = BadBleStateDone;
bad_ble->hid->release_all(bad_ble->hid_inst);
continue;
} else if(delay_val == SCRIPT_STATE_STRING_START) { // Start printing string with delays
delay_val = bad_ble->defdelay;
bad_ble->string_print_pos = 0;
worker_state = BadBleStateStringDelay;
} else if(delay_val == SCRIPT_STATE_WAIT_FOR_BTN) { // set state to wait for user input
worker_state = BadBleStateWaitForBtn;
bad_ble->st.state = BadBleStateWaitForBtn; // Show long delays
} else if(delay_val > 1000) {
bad_ble->st.state = BadBleStateDelay; // Show long delays
bad_ble->st.delay_remain = delay_val / 1000;
}
} else {
furi_check((flags & FuriFlagError) == 0);
}
} else if(worker_state == BadBleStateWaitForBtn) { // State: Wait for button Press
uint32_t flags = bad_ble_flags_get(
WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtPauseResume | WorkerEvtDisconnect,
FuriWaitForever);
if(!(flags & FuriFlagError)) {
if(flags & WorkerEvtEnd) {
break;
} else if(flags & WorkerEvtStartStop) {
delay_val = 0;
worker_state = BadBleStateRunning;
} else if(flags & WorkerEvtDisconnect) {
worker_state = BadBleStateNotConnected; // USB disconnected
bad_ble->hid->release_all(bad_ble->hid_inst);
}
bad_ble->st.state = worker_state;
continue;
}
} else if(worker_state == BadBleStatePaused) { // State: Paused
uint32_t flags = bad_ble_flags_get(
WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtPauseResume | WorkerEvtDisconnect,
FuriWaitForever);
if(!(flags & FuriFlagError)) {
if(flags & WorkerEvtEnd) {
break;
} else if(flags & WorkerEvtStartStop) {
worker_state = BadBleStateIdle; // Stop executing script
bad_ble->st.state = worker_state;
bad_ble->hid->release_all(bad_ble->hid_inst);
} else if(flags & WorkerEvtDisconnect) {
worker_state = BadBleStateNotConnected; // USB disconnected
bad_ble->st.state = worker_state;
bad_ble->hid->release_all(bad_ble->hid_inst);
} else if(flags & WorkerEvtPauseResume) {
if(pause_state == BadBleStateRunning) {
if(delay_val > 0) {
bad_ble->st.state = BadBleStateDelay;
bad_ble->st.delay_remain = delay_val / 1000;
} else {
bad_ble->st.state = BadBleStateRunning;
delay_val = 0;
}
worker_state = BadBleStateRunning; // Resume
} else if(pause_state == BadBleStateStringDelay) {
bad_ble->st.state = BadBleStateRunning;
worker_state = BadBleStateStringDelay; // Resume
}
}
continue;
}
} else if(worker_state == BadBleStateStringDelay) { // State: print string with delays
uint32_t delay = (bad_ble->stringdelay == 0) ? bad_ble->defstringdelay :
bad_ble->stringdelay;
uint32_t flags = bad_ble_flags_get(
WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtPauseResume | WorkerEvtDisconnect,
delay);
if(!(flags & FuriFlagError)) {
if(flags & WorkerEvtEnd) {
break;
} else if(flags & WorkerEvtStartStop) {
worker_state = BadBleStateIdle; // Stop executing script
bad_ble->hid->release_all(bad_ble->hid_inst);
} else if(flags & WorkerEvtDisconnect) {
worker_state = BadBleStateNotConnected; // USB disconnected
bad_ble->hid->release_all(bad_ble->hid_inst);
} else if(flags & WorkerEvtPauseResume) {
pause_state = BadBleStateStringDelay;
worker_state = BadBleStatePaused; // Pause
}
bad_ble->st.state = worker_state;
continue;
} else if(
(flags == (unsigned)FuriFlagErrorTimeout) ||
(flags == (unsigned)FuriFlagErrorResource)) {
bool string_end = ducky_string_next(bad_ble);
if(string_end) {
bad_ble->stringdelay = 0;
worker_state = BadBleStateRunning;
}
} else {
furi_check((flags & FuriFlagError) == 0);
}
} else if(
(worker_state == BadBleStateFileError) ||
(worker_state == BadBleStateScriptError)) { // State: error
uint32_t flags =
bad_ble_flags_get(WorkerEvtEnd, FuriWaitForever); // Waiting for exit command
if(flags & WorkerEvtEnd) {
break;
}
}
}
bad_ble->hid->set_state_callback(bad_ble->hid_inst, NULL, NULL);
bad_ble->hid->deinit(bad_ble->hid_inst);
storage_file_close(script_file);
storage_file_free(script_file);
furi_string_free(bad_ble->line);
furi_string_free(bad_ble->line_prev);
furi_string_free(bad_ble->string_print);
FURI_LOG_I(WORKER_TAG, "End");
return 0;
}
static void bad_ble_script_set_default_keyboard_layout(BadBleScript* bad_ble) {
furi_assert(bad_ble);
memset(bad_ble->layout, HID_KEYBOARD_NONE, sizeof(bad_ble->layout));
memcpy(bad_ble->layout, hid_asciimap, MIN(sizeof(hid_asciimap), sizeof(bad_ble->layout)));
}
BadBleScript* bad_ble_script_open(FuriString* file_path, BadBleHidInterface interface) {
furi_assert(file_path);
BadBleScript* bad_ble = malloc(sizeof(BadBleScript));
bad_ble->file_path = furi_string_alloc();
furi_string_set(bad_ble->file_path, file_path);
bad_ble_script_set_default_keyboard_layout(bad_ble);
bad_ble->st.state = BadBleStateInit;
bad_ble->st.error[0] = '\0';
bad_ble->hid = bad_ble_hid_get_interface(interface);
bad_ble->thread = furi_thread_alloc_ex("BadBleWorker", 2048, bad_ble_worker, bad_ble);
furi_thread_start(bad_ble->thread);
return bad_ble;
} //-V773
void bad_ble_script_close(BadBleScript* bad_ble) {
furi_assert(bad_ble);
furi_thread_flags_set(furi_thread_get_id(bad_ble->thread), WorkerEvtEnd);
furi_thread_join(bad_ble->thread);
furi_thread_free(bad_ble->thread);
furi_string_free(bad_ble->file_path);
free(bad_ble);
}
void bad_ble_script_set_keyboard_layout(BadBleScript* bad_ble, FuriString* layout_path) {
furi_assert(bad_ble);
if((bad_ble->st.state == BadBleStateRunning) || (bad_ble->st.state == BadBleStateDelay)) {
// do not update keyboard layout while a script is running
return;
}
File* layout_file = storage_file_alloc(furi_record_open(RECORD_STORAGE));
if(!furi_string_empty(layout_path)) { //-V1051
if(storage_file_open(
layout_file, furi_string_get_cstr(layout_path), FSAM_READ, FSOM_OPEN_EXISTING)) {
uint16_t layout[128];
if(storage_file_read(layout_file, layout, sizeof(layout)) == sizeof(layout)) {
memcpy(bad_ble->layout, layout, sizeof(layout));
}
}
storage_file_close(layout_file);
} else {
bad_ble_script_set_default_keyboard_layout(bad_ble);
}
storage_file_free(layout_file);
}
void bad_ble_script_start_stop(BadBleScript* bad_ble) {
furi_assert(bad_ble);
furi_thread_flags_set(furi_thread_get_id(bad_ble->thread), WorkerEvtStartStop);
}
void bad_ble_script_pause_resume(BadBleScript* bad_ble) {
furi_assert(bad_ble);
furi_thread_flags_set(furi_thread_get_id(bad_ble->thread), WorkerEvtPauseResume);
}
BadBleState* bad_ble_script_get_state(BadBleScript* bad_ble) {
furi_assert(bad_ble);
return &(bad_ble->st);
}

View File

@@ -1,55 +0,0 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <furi.h>
#include <furi_hal.h>
#include "bad_ble_hid.h"
typedef enum {
BadBleStateInit,
BadBleStateNotConnected,
BadBleStateIdle,
BadBleStateWillRun,
BadBleStateRunning,
BadBleStateDelay,
BadBleStateStringDelay,
BadBleStateWaitForBtn,
BadBleStatePaused,
BadBleStateDone,
BadBleStateScriptError,
BadBleStateFileError,
} BadBleWorkerState;
typedef struct {
BadBleWorkerState state;
size_t line_cur;
size_t line_nb;
uint32_t delay_remain;
size_t error_line;
char error[64];
} BadBleState;
typedef struct BadBleScript BadBleScript;
BadBleScript* bad_ble_script_open(FuriString* file_path, BadBleHidInterface interface);
void bad_ble_script_close(BadBleScript* bad_ble);
void bad_ble_script_set_keyboard_layout(BadBleScript* bad_ble, FuriString* layout_path);
void bad_ble_script_start(BadBleScript* bad_ble);
void bad_ble_script_stop(BadBleScript* bad_ble);
void bad_ble_script_start_stop(BadBleScript* bad_ble);
void bad_ble_script_pause_resume(BadBleScript* bad_ble);
BadBleState* bad_ble_script_get_state(BadBleScript* bad_ble);
#ifdef __cplusplus
}
#endif

View File

@@ -1,241 +0,0 @@
#include <furi_hal.h>
#include "ducky_script.h"
#include "ducky_script_i.h"
typedef int32_t (*DuckyCmdCallback)(BadBleScript* bad_usb, const char* line, int32_t param);
typedef struct {
char* name;
DuckyCmdCallback callback;
int32_t param;
} DuckyCmd;
static int32_t ducky_fnc_delay(BadBleScript* bad_usb, const char* line, int32_t param) {
UNUSED(param);
line = &line[ducky_get_command_len(line) + 1];
uint32_t delay_val = 0;
bool state = ducky_get_number(line, &delay_val);
if((state) && (delay_val > 0)) {
return (int32_t)delay_val;
}
return ducky_error(bad_usb, "Invalid number %s", line);
}
static int32_t ducky_fnc_defdelay(BadBleScript* bad_usb, const char* line, int32_t param) {
UNUSED(param);
line = &line[ducky_get_command_len(line) + 1];
bool state = ducky_get_number(line, &bad_usb->defdelay);
if(!state) {
return ducky_error(bad_usb, "Invalid number %s", line);
}
return 0;
}
static int32_t ducky_fnc_strdelay(BadBleScript* bad_usb, const char* line, int32_t param) {
UNUSED(param);
line = &line[ducky_get_command_len(line) + 1];
bool state = ducky_get_number(line, &bad_usb->stringdelay);
if(!state) {
return ducky_error(bad_usb, "Invalid number %s", line);
}
return 0;
}
static int32_t ducky_fnc_defstrdelay(BadBleScript* bad_usb, const char* line, int32_t param) {
UNUSED(param);
line = &line[ducky_get_command_len(line) + 1];
bool state = ducky_get_number(line, &bad_usb->defstringdelay);
if(!state) {
return ducky_error(bad_usb, "Invalid number %s", line);
}
return 0;
}
static int32_t ducky_fnc_string(BadBleScript* bad_usb, const char* line, int32_t param) {
line = &line[ducky_get_command_len(line) + 1];
furi_string_set_str(bad_usb->string_print, line);
if(param == 1) {
furi_string_cat(bad_usb->string_print, "\n");
}
if(bad_usb->stringdelay == 0 &&
bad_usb->defstringdelay == 0) { // stringdelay not set - run command immediately
bool state = ducky_string(bad_usb, furi_string_get_cstr(bad_usb->string_print));
if(!state) {
return ducky_error(bad_usb, "Invalid string %s", line);
}
} else { // stringdelay is set - run command in thread to keep handling external events
return SCRIPT_STATE_STRING_START;
}
return 0;
}
static int32_t ducky_fnc_repeat(BadBleScript* bad_usb, const char* line, int32_t param) {
UNUSED(param);
line = &line[ducky_get_command_len(line) + 1];
bool state = ducky_get_number(line, &bad_usb->repeat_cnt);
if((!state) || (bad_usb->repeat_cnt == 0)) {
return ducky_error(bad_usb, "Invalid number %s", line);
}
return 0;
}
static int32_t ducky_fnc_sysrq(BadBleScript* bad_usb, const char* line, int32_t param) {
UNUSED(param);
line = &line[ducky_get_command_len(line) + 1];
uint16_t key = ducky_get_keycode(bad_usb, line, true);
bad_usb->hid->kb_press(bad_usb->hid_inst, KEY_MOD_LEFT_ALT | HID_KEYBOARD_PRINT_SCREEN);
bad_usb->hid->kb_press(bad_usb->hid_inst, key);
bad_usb->hid->release_all(bad_usb->hid_inst);
return 0;
}
static int32_t ducky_fnc_altchar(BadBleScript* bad_usb, const char* line, int32_t param) {
UNUSED(param);
line = &line[ducky_get_command_len(line) + 1];
ducky_numlock_on(bad_usb);
bool state = ducky_altchar(bad_usb, line);
if(!state) {
return ducky_error(bad_usb, "Invalid altchar %s", line);
}
return 0;
}
static int32_t ducky_fnc_altstring(BadBleScript* bad_usb, const char* line, int32_t param) {
UNUSED(param);
line = &line[ducky_get_command_len(line) + 1];
ducky_numlock_on(bad_usb);
bool state = ducky_altstring(bad_usb, line);
if(!state) {
return ducky_error(bad_usb, "Invalid altstring %s", line);
}
return 0;
}
static int32_t ducky_fnc_hold(BadBleScript* bad_usb, const char* line, int32_t param) {
UNUSED(param);
line = &line[ducky_get_command_len(line) + 1];
uint16_t key = ducky_get_keycode(bad_usb, line, true);
if(key == HID_KEYBOARD_NONE) {
return ducky_error(bad_usb, "No keycode defined for %s", line);
}
bad_usb->key_hold_nb++;
if(bad_usb->key_hold_nb > (HID_KB_MAX_KEYS - 1)) {
return ducky_error(bad_usb, "Too many keys are hold");
}
bad_usb->hid->kb_press(bad_usb->hid_inst, key);
return 0;
}
static int32_t ducky_fnc_release(BadBleScript* bad_usb, const char* line, int32_t param) {
UNUSED(param);
line = &line[ducky_get_command_len(line) + 1];
uint16_t key = ducky_get_keycode(bad_usb, line, true);
if(key == HID_KEYBOARD_NONE) {
return ducky_error(bad_usb, "No keycode defined for %s", line);
}
if(bad_usb->key_hold_nb == 0) {
return ducky_error(bad_usb, "No keys are hold");
}
bad_usb->key_hold_nb--;
bad_usb->hid->kb_release(bad_usb->hid_inst, key);
return 0;
}
static int32_t ducky_fnc_media(BadBleScript* bad_usb, const char* line, int32_t param) {
UNUSED(param);
line = &line[ducky_get_command_len(line) + 1];
uint16_t key = ducky_get_media_keycode_by_name(line);
if(key == HID_CONSUMER_UNASSIGNED) {
return ducky_error(bad_usb, "No keycode defined for %s", line);
}
bad_usb->hid->consumer_press(bad_usb->hid_inst, key);
bad_usb->hid->consumer_release(bad_usb->hid_inst, key);
return 0;
}
static int32_t ducky_fnc_globe(BadBleScript* bad_usb, const char* line, int32_t param) {
UNUSED(param);
line = &line[ducky_get_command_len(line) + 1];
uint16_t key = ducky_get_keycode(bad_usb, line, true);
if(key == HID_KEYBOARD_NONE) {
return ducky_error(bad_usb, "No keycode defined for %s", line);
}
bad_usb->hid->consumer_press(bad_usb->hid_inst, HID_CONSUMER_FN_GLOBE);
bad_usb->hid->kb_press(bad_usb->hid_inst, key);
bad_usb->hid->kb_release(bad_usb->hid_inst, key);
bad_usb->hid->consumer_release(bad_usb->hid_inst, HID_CONSUMER_FN_GLOBE);
return 0;
}
static int32_t ducky_fnc_waitforbutton(BadBleScript* bad_usb, const char* line, int32_t param) {
UNUSED(param);
UNUSED(bad_usb);
UNUSED(line);
return SCRIPT_STATE_WAIT_FOR_BTN;
}
static const DuckyCmd ducky_commands[] = {
{"REM", NULL, -1},
{"ID", NULL, -1},
{"DELAY", ducky_fnc_delay, -1},
{"STRING", ducky_fnc_string, 0},
{"STRINGLN", ducky_fnc_string, 1},
{"DEFAULT_DELAY", ducky_fnc_defdelay, -1},
{"DEFAULTDELAY", ducky_fnc_defdelay, -1},
{"STRINGDELAY", ducky_fnc_strdelay, -1},
{"STRING_DELAY", ducky_fnc_strdelay, -1},
{"DEFAULT_STRING_DELAY", ducky_fnc_defstrdelay, -1},
{"DEFAULTSTRINGDELAY", ducky_fnc_defstrdelay, -1},
{"REPEAT", ducky_fnc_repeat, -1},
{"SYSRQ", ducky_fnc_sysrq, -1},
{"ALTCHAR", ducky_fnc_altchar, -1},
{"ALTSTRING", ducky_fnc_altstring, -1},
{"ALTCODE", ducky_fnc_altstring, -1},
{"HOLD", ducky_fnc_hold, -1},
{"RELEASE", ducky_fnc_release, -1},
{"WAIT_FOR_BUTTON_PRESS", ducky_fnc_waitforbutton, -1},
{"MEDIA", ducky_fnc_media, -1},
{"GLOBE", ducky_fnc_globe, -1},
};
#define TAG "BadBle"
#define WORKER_TAG TAG "Worker"
int32_t ducky_execute_cmd(BadBleScript* bad_usb, const char* line) {
size_t cmd_word_len = strcspn(line, " ");
for(size_t i = 0; i < COUNT_OF(ducky_commands); i++) {
size_t cmd_compare_len = strlen(ducky_commands[i].name);
if(cmd_compare_len != cmd_word_len) {
continue;
}
if(strncmp(line, ducky_commands[i].name, cmd_compare_len) == 0) {
if(ducky_commands[i].callback == NULL) {
return 0;
} else {
return (ducky_commands[i].callback)(bad_usb, line, ducky_commands[i].param);
}
}
}
return SCRIPT_STATE_CMD_UNKNOWN;
}

View File

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

View File

@@ -1,133 +0,0 @@
#include <furi_hal.h>
#include "ducky_script_i.h"
typedef struct {
char* name;
uint16_t keycode;
} DuckyKey;
static const DuckyKey ducky_keys[] = {
{"CTRL-ALT", KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_ALT},
{"CTRL-SHIFT", KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_SHIFT},
{"ALT-SHIFT", KEY_MOD_LEFT_ALT | KEY_MOD_LEFT_SHIFT},
{"ALT-GUI", KEY_MOD_LEFT_ALT | KEY_MOD_LEFT_GUI},
{"GUI-SHIFT", KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT},
{"GUI-CTRL", KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_CTRL},
{"CTRL", KEY_MOD_LEFT_CTRL},
{"CONTROL", KEY_MOD_LEFT_CTRL},
{"SHIFT", KEY_MOD_LEFT_SHIFT},
{"ALT", KEY_MOD_LEFT_ALT},
{"GUI", KEY_MOD_LEFT_GUI},
{"WINDOWS", KEY_MOD_LEFT_GUI},
{"DOWNARROW", HID_KEYBOARD_DOWN_ARROW},
{"DOWN", HID_KEYBOARD_DOWN_ARROW},
{"LEFTARROW", HID_KEYBOARD_LEFT_ARROW},
{"LEFT", HID_KEYBOARD_LEFT_ARROW},
{"RIGHTARROW", HID_KEYBOARD_RIGHT_ARROW},
{"RIGHT", HID_KEYBOARD_RIGHT_ARROW},
{"UPARROW", HID_KEYBOARD_UP_ARROW},
{"UP", HID_KEYBOARD_UP_ARROW},
{"ENTER", HID_KEYBOARD_RETURN},
{"BREAK", HID_KEYBOARD_PAUSE},
{"PAUSE", HID_KEYBOARD_PAUSE},
{"CAPSLOCK", HID_KEYBOARD_CAPS_LOCK},
{"DELETE", HID_KEYBOARD_DELETE_FORWARD},
{"BACKSPACE", HID_KEYBOARD_DELETE},
{"END", HID_KEYBOARD_END},
{"ESC", HID_KEYBOARD_ESCAPE},
{"ESCAPE", HID_KEYBOARD_ESCAPE},
{"HOME", HID_KEYBOARD_HOME},
{"INSERT", HID_KEYBOARD_INSERT},
{"NUMLOCK", HID_KEYPAD_NUMLOCK},
{"PAGEUP", HID_KEYBOARD_PAGE_UP},
{"PAGEDOWN", HID_KEYBOARD_PAGE_DOWN},
{"PRINTSCREEN", HID_KEYBOARD_PRINT_SCREEN},
{"SCROLLLOCK", HID_KEYBOARD_SCROLL_LOCK},
{"SPACE", HID_KEYBOARD_SPACEBAR},
{"TAB", HID_KEYBOARD_TAB},
{"MENU", HID_KEYBOARD_APPLICATION},
{"APP", HID_KEYBOARD_APPLICATION},
{"F1", HID_KEYBOARD_F1},
{"F2", HID_KEYBOARD_F2},
{"F3", HID_KEYBOARD_F3},
{"F4", HID_KEYBOARD_F4},
{"F5", HID_KEYBOARD_F5},
{"F6", HID_KEYBOARD_F6},
{"F7", HID_KEYBOARD_F7},
{"F8", HID_KEYBOARD_F8},
{"F9", HID_KEYBOARD_F9},
{"F10", HID_KEYBOARD_F10},
{"F11", HID_KEYBOARD_F11},
{"F12", HID_KEYBOARD_F12},
{"F13", HID_KEYBOARD_F13},
{"F14", HID_KEYBOARD_F14},
{"F15", HID_KEYBOARD_F15},
{"F16", HID_KEYBOARD_F16},
{"F17", HID_KEYBOARD_F17},
{"F18", HID_KEYBOARD_F18},
{"F19", HID_KEYBOARD_F19},
{"F20", HID_KEYBOARD_F20},
{"F21", HID_KEYBOARD_F21},
{"F22", HID_KEYBOARD_F22},
{"F23", HID_KEYBOARD_F23},
{"F24", HID_KEYBOARD_F24},
};
static const DuckyKey ducky_media_keys[] = {
{"POWER", HID_CONSUMER_POWER},
{"REBOOT", HID_CONSUMER_RESET},
{"SLEEP", HID_CONSUMER_SLEEP},
{"LOGOFF", HID_CONSUMER_AL_LOGOFF},
{"EXIT", HID_CONSUMER_AC_EXIT},
{"HOME", HID_CONSUMER_AC_HOME},
{"BACK", HID_CONSUMER_AC_BACK},
{"FORWARD", HID_CONSUMER_AC_FORWARD},
{"REFRESH", HID_CONSUMER_AC_REFRESH},
{"SNAPSHOT", HID_CONSUMER_SNAPSHOT},
{"PLAY", HID_CONSUMER_PLAY},
{"PAUSE", HID_CONSUMER_PAUSE},
{"PLAY_PAUSE", HID_CONSUMER_PLAY_PAUSE},
{"NEXT_TRACK", HID_CONSUMER_SCAN_NEXT_TRACK},
{"PREV_TRACK", HID_CONSUMER_SCAN_PREVIOUS_TRACK},
{"STOP", HID_CONSUMER_STOP},
{"EJECT", HID_CONSUMER_EJECT},
{"MUTE", HID_CONSUMER_MUTE},
{"VOLUME_UP", HID_CONSUMER_VOLUME_INCREMENT},
{"VOLUME_DOWN", HID_CONSUMER_VOLUME_DECREMENT},
{"FN", HID_CONSUMER_FN_GLOBE},
{"BRIGHT_UP", HID_CONSUMER_BRIGHTNESS_INCREMENT},
{"BRIGHT_DOWN", HID_CONSUMER_BRIGHTNESS_DECREMENT},
};
uint16_t ducky_get_keycode_by_name(const char* param) {
for(size_t i = 0; i < COUNT_OF(ducky_keys); i++) {
size_t key_cmd_len = strlen(ducky_keys[i].name);
if((strncmp(param, ducky_keys[i].name, key_cmd_len) == 0) &&
(ducky_is_line_end(param[key_cmd_len]))) {
return ducky_keys[i].keycode;
}
}
return HID_KEYBOARD_NONE;
}
uint16_t ducky_get_media_keycode_by_name(const char* param) {
for(size_t i = 0; i < COUNT_OF(ducky_media_keys); i++) {
size_t key_cmd_len = strlen(ducky_media_keys[i].name);
if((strncmp(param, ducky_media_keys[i].name, key_cmd_len) == 0) &&
(ducky_is_line_end(param[key_cmd_len]))) {
return ducky_media_keys[i].keycode;
}
}
return HID_CONSUMER_UNASSIGNED;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 B

View File

@@ -1,30 +0,0 @@
#include "bad_ble_scene.h"
// Generate scene on_enter handlers array
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
void (*const bad_ble_scene_on_enter_handlers[])(void*) = {
#include "bad_ble_scene_config.h"
};
#undef ADD_SCENE
// Generate scene on_event handlers array
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event,
bool (*const bad_ble_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = {
#include "bad_ble_scene_config.h"
};
#undef ADD_SCENE
// Generate scene on_exit handlers array
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit,
void (*const bad_ble_scene_on_exit_handlers[])(void* context) = {
#include "bad_ble_scene_config.h"
};
#undef ADD_SCENE
// Initialize scene handlers configuration structure
const SceneManagerHandlers bad_ble_scene_handlers = {
.on_enter_handlers = bad_ble_scene_on_enter_handlers,
.on_event_handlers = bad_ble_scene_on_event_handlers,
.on_exit_handlers = bad_ble_scene_on_exit_handlers,
.scene_num = BadBleSceneNum,
};

View File

@@ -1,29 +0,0 @@
#pragma once
#include <gui/scene_manager.h>
// Generate scene id and total number
#define ADD_SCENE(prefix, name, id) BadBleScene##id,
typedef enum {
#include "bad_ble_scene_config.h"
BadBleSceneNum,
} BadBleScene;
#undef ADD_SCENE
extern const SceneManagerHandlers bad_ble_scene_handlers;
// Generate scene on_enter handlers declaration
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
#include "bad_ble_scene_config.h"
#undef ADD_SCENE
// Generate scene on_event handlers declaration
#define ADD_SCENE(prefix, name, id) \
bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event);
#include "bad_ble_scene_config.h"
#undef ADD_SCENE
// Generate scene on_exit handlers declaration
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context);
#include "bad_ble_scene_config.h"
#undef ADD_SCENE

View File

@@ -1,59 +0,0 @@
#include "../bad_ble_app_i.h"
enum SubmenuIndex {
ConfigIndexKeyboardLayout,
ConfigIndexBleUnpair,
};
void bad_ble_scene_config_select_callback(void* context, uint32_t index) {
BadBleApp* bad_ble = context;
view_dispatcher_send_custom_event(bad_ble->view_dispatcher, index);
}
static void draw_menu(BadBleApp* bad_ble) {
VariableItemList* var_item_list = bad_ble->var_item_list;
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, "Unpair Device", 0, NULL, NULL);
}
void bad_ble_scene_config_on_enter(void* context) {
BadBleApp* bad_ble = context;
VariableItemList* var_item_list = bad_ble->var_item_list;
variable_item_list_set_enter_callback(
var_item_list, bad_ble_scene_config_select_callback, bad_ble);
draw_menu(bad_ble);
variable_item_list_set_selected_item(var_item_list, 0);
view_dispatcher_switch_to_view(bad_ble->view_dispatcher, BadBleAppViewConfig);
}
bool bad_ble_scene_config_on_event(void* context, SceneManagerEvent event) {
BadBleApp* bad_ble = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
consumed = true;
if(event.event == ConfigIndexKeyboardLayout) {
scene_manager_next_scene(bad_ble->scene_manager, BadBleSceneConfigLayout);
} else if(event.event == ConfigIndexBleUnpair) {
scene_manager_next_scene(bad_ble->scene_manager, BadBleSceneConfirmUnpair);
} else {
furi_crash("Unknown key type");
}
}
return consumed;
}
void bad_ble_scene_config_on_exit(void* context) {
BadBleApp* bad_ble = context;
VariableItemList* var_item_list = bad_ble->var_item_list;
variable_item_list_reset(var_item_list);
}

View File

@@ -1,7 +0,0 @@
ADD_SCENE(bad_ble, file_select, FileSelect)
ADD_SCENE(bad_ble, work, Work)
ADD_SCENE(bad_ble, error, Error)
ADD_SCENE(bad_ble, config, Config)
ADD_SCENE(bad_ble, config_layout, ConfigLayout)
ADD_SCENE(bad_ble, confirm_unpair, ConfirmUnpair)
ADD_SCENE(bad_ble, unpair_done, UnpairDone)

View File

@@ -1,49 +0,0 @@
#include "../bad_ble_app_i.h"
#include <storage/storage.h>
static bool bad_ble_layout_select(BadBleApp* bad_ble) {
furi_assert(bad_ble);
FuriString* predefined_path;
predefined_path = furi_string_alloc();
if(!furi_string_empty(bad_ble->keyboard_layout)) {
furi_string_set(predefined_path, bad_ble->keyboard_layout);
} else {
furi_string_set(predefined_path, BAD_BLE_APP_PATH_LAYOUT_FOLDER);
}
DialogsFileBrowserOptions browser_options;
dialog_file_browser_set_basic_options(
&browser_options, BAD_BLE_APP_LAYOUT_EXTENSION, &I_keyboard_10px);
browser_options.base_path = BAD_BLE_APP_PATH_LAYOUT_FOLDER;
browser_options.skip_assets = false;
// Input events and views are managed by file_browser
bool res = dialog_file_browser_show(
bad_ble->dialogs, bad_ble->keyboard_layout, predefined_path, &browser_options);
furi_string_free(predefined_path);
return res;
}
void bad_ble_scene_config_layout_on_enter(void* context) {
BadBleApp* bad_ble = context;
if(bad_ble_layout_select(bad_ble)) {
scene_manager_search_and_switch_to_previous_scene(bad_ble->scene_manager, BadBleSceneWork);
} else {
scene_manager_previous_scene(bad_ble->scene_manager);
}
}
bool bad_ble_scene_config_layout_on_event(void* context, SceneManagerEvent event) {
UNUSED(context);
UNUSED(event);
// BadBleApp* bad_ble = context;
return false;
}
void bad_ble_scene_config_layout_on_exit(void* context) {
UNUSED(context);
// BadBleApp* bad_ble = context;
}

View File

@@ -1,65 +0,0 @@
#include "../bad_ble_app_i.h"
typedef enum {
BadBleCustomEventErrorBack,
} BadBleCustomEvent;
static void
bad_ble_scene_error_event_callback(GuiButtonType result, InputType type, void* context) {
furi_assert(context);
BadBleApp* app = context;
if((result == GuiButtonTypeLeft) && (type == InputTypeShort)) {
view_dispatcher_send_custom_event(app->view_dispatcher, BadBleCustomEventErrorBack);
}
}
void bad_ble_scene_error_on_enter(void* context) {
BadBleApp* app = context;
if(app->error == BadBleAppErrorNoFiles) {
widget_add_icon_element(app->widget, 0, 0, &I_SDQuestion_35x43);
widget_add_string_multiline_element(
app->widget,
81,
4,
AlignCenter,
AlignTop,
FontSecondary,
"No SD card or\napp data found.\nThis app will not\nwork without\nrequired files.");
widget_add_button_element(
app->widget, GuiButtonTypeLeft, "Back", bad_ble_scene_error_event_callback, app);
} else if(app->error == BadBleAppErrorCloseRpc) {
widget_add_icon_element(app->widget, 78, 0, &I_ActiveConnection_50x64);
widget_add_string_multiline_element(
app->widget, 3, 2, AlignLeft, AlignTop, FontPrimary, "Connection\nIs Active!");
widget_add_string_multiline_element(
app->widget,
3,
30,
AlignLeft,
AlignTop,
FontSecondary,
"Disconnect from\nPC or phone to\nuse this function.");
}
view_dispatcher_switch_to_view(app->view_dispatcher, BadBleAppViewWidget);
}
bool bad_ble_scene_error_on_event(void* context, SceneManagerEvent event) {
BadBleApp* app = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == BadBleCustomEventErrorBack) {
view_dispatcher_stop(app->view_dispatcher);
consumed = true;
}
}
return consumed;
}
void bad_ble_scene_error_on_exit(void* context) {
BadBleApp* app = context;
widget_reset(app->widget);
}

View File

@@ -1,46 +0,0 @@
#include "../bad_ble_app_i.h"
#include <furi_hal_power.h>
#include <storage/storage.h>
static bool bad_ble_file_select(BadBleApp* bad_ble) {
furi_assert(bad_ble);
DialogsFileBrowserOptions browser_options;
dialog_file_browser_set_basic_options(
&browser_options, BAD_BLE_APP_SCRIPT_EXTENSION, &I_badusb_10px);
browser_options.base_path = BAD_BLE_APP_BASE_FOLDER;
browser_options.skip_assets = true;
// Input events and views are managed by file_browser
bool res = dialog_file_browser_show(
bad_ble->dialogs, bad_ble->file_path, bad_ble->file_path, &browser_options);
return res;
}
void bad_ble_scene_file_select_on_enter(void* context) {
BadBleApp* bad_ble = context;
if(bad_ble->bad_ble_script) {
bad_ble_script_close(bad_ble->bad_ble_script);
bad_ble->bad_ble_script = NULL;
}
if(bad_ble_file_select(bad_ble)) {
scene_manager_next_scene(bad_ble->scene_manager, BadBleSceneWork);
} else {
view_dispatcher_stop(bad_ble->view_dispatcher);
}
}
bool bad_ble_scene_file_select_on_event(void* context, SceneManagerEvent event) {
UNUSED(context);
UNUSED(event);
// BadBleApp* bad_ble = context;
return false;
}
void bad_ble_scene_file_select_on_exit(void* context) {
UNUSED(context);
// BadBleApp* bad_ble = context;
}

View File

@@ -1,37 +0,0 @@
#include "../bad_ble_app_i.h"
static void bad_ble_scene_unpair_done_popup_callback(void* context) {
BadBleApp* bad_ble = context;
scene_manager_search_and_switch_to_previous_scene(bad_ble->scene_manager, BadBleSceneConfig);
}
void bad_ble_scene_unpair_done_on_enter(void* context) {
BadBleApp* bad_ble = context;
Popup* popup = bad_ble->popup;
popup_set_icon(popup, 48, 4, &I_DolphinDone_80x58);
popup_set_header(popup, "Done", 20, 19, AlignLeft, AlignBottom);
popup_set_callback(popup, bad_ble_scene_unpair_done_popup_callback);
popup_set_context(popup, bad_ble);
popup_set_timeout(popup, 1000);
popup_enable_timeout(popup);
view_dispatcher_switch_to_view(bad_ble->view_dispatcher, BadBleAppViewPopup);
}
bool bad_ble_scene_unpair_done_on_event(void* context, SceneManagerEvent event) {
BadBleApp* bad_ble = context;
UNUSED(bad_ble);
UNUSED(event);
bool consumed = false;
return consumed;
}
void bad_ble_scene_unpair_done_on_exit(void* context) {
BadBleApp* bad_ble = context;
Popup* popup = bad_ble->popup;
popup_reset(popup);
}

View File

@@ -1,65 +0,0 @@
#include "../helpers/ducky_script.h"
#include "../bad_ble_app_i.h"
#include "../views/bad_ble_view.h"
#include <furi_hal.h>
#include "toolbox/path.h"
void bad_ble_scene_work_button_callback(InputKey key, void* context) {
furi_assert(context);
BadBleApp* app = context;
view_dispatcher_send_custom_event(app->view_dispatcher, key);
}
bool bad_ble_scene_work_on_event(void* context, SceneManagerEvent event) {
BadBleApp* app = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == InputKeyLeft) {
if(bad_ble_view_is_idle_state(app->bad_ble_view)) {
bad_ble_script_close(app->bad_ble_script);
app->bad_ble_script = NULL;
scene_manager_next_scene(app->scene_manager, BadBleSceneConfig);
}
consumed = true;
} else if(event.event == InputKeyOk) {
bad_ble_script_start_stop(app->bad_ble_script);
consumed = true;
} else if(event.event == InputKeyRight) {
bad_ble_script_pause_resume(app->bad_ble_script);
consumed = true;
}
} else if(event.type == SceneManagerEventTypeTick) {
bad_ble_view_set_state(app->bad_ble_view, bad_ble_script_get_state(app->bad_ble_script));
}
return consumed;
}
void bad_ble_scene_work_on_enter(void* context) {
BadBleApp* app = context;
app->bad_ble_script = bad_ble_script_open(app->file_path, app->interface);
bad_ble_script_set_keyboard_layout(app->bad_ble_script, app->keyboard_layout);
FuriString* file_name;
file_name = furi_string_alloc();
path_extract_filename(app->file_path, file_name, true);
bad_ble_view_set_file_name(app->bad_ble_view, furi_string_get_cstr(file_name));
furi_string_free(file_name);
FuriString* layout;
layout = furi_string_alloc();
path_extract_filename(app->keyboard_layout, layout, true);
bad_ble_view_set_layout(app->bad_ble_view, furi_string_get_cstr(layout));
furi_string_free(layout);
bad_ble_view_set_state(app->bad_ble_view, bad_ble_script_get_state(app->bad_ble_script));
bad_ble_view_set_button_callback(app->bad_ble_view, bad_ble_scene_work_button_callback, app);
view_dispatcher_switch_to_view(app->view_dispatcher, BadBleAppViewWork);
}
void bad_ble_scene_work_on_exit(void* context) {
UNUSED(context);
}

View File

@@ -1,284 +0,0 @@
#include "bad_ble_view.h"
#include "../helpers/ducky_script.h"
#include <toolbox/path.h>
#include <gui/elements.h>
#include <assets_icons.h>
#include "bad_ble_icons.h"
#define MAX_NAME_LEN 64
struct BadBle {
View* view;
BadBleButtonCallback callback;
void* context;
};
typedef struct {
char file_name[MAX_NAME_LEN];
char layout[MAX_NAME_LEN];
BadBleState state;
bool pause_wait;
uint8_t anim_frame;
} BadBleModel;
static void bad_ble_draw_callback(Canvas* canvas, void* _model) {
BadBleModel* model = _model;
FuriString* disp_str;
disp_str = furi_string_alloc_set(model->file_name);
elements_string_fit_width(canvas, disp_str, 128 - 2);
canvas_set_font(canvas, FontSecondary);
canvas_draw_str(canvas, 2, 8, furi_string_get_cstr(disp_str));
if(strlen(model->layout) == 0) {
furi_string_set(disp_str, "(default)");
} else {
furi_string_printf(disp_str, "(%s)", model->layout);
}
elements_string_fit_width(canvas, disp_str, 128 - 2);
canvas_draw_str(
canvas, 2, 8 + canvas_current_font_height(canvas), furi_string_get_cstr(disp_str));
furi_string_reset(disp_str);
canvas_draw_icon(canvas, 22, 24, &I_Bad_BLE_48x22);
BadBleWorkerState state = model->state.state;
if((state == BadBleStateIdle) || (state == BadBleStateDone) ||
(state == BadBleStateNotConnected)) {
elements_button_center(canvas, "Run");
elements_button_left(canvas, "Config");
} else if((state == BadBleStateRunning) || (state == BadBleStateDelay)) {
elements_button_center(canvas, "Stop");
if(!model->pause_wait) {
elements_button_right(canvas, "Pause");
}
} else if(state == BadBleStatePaused) {
elements_button_center(canvas, "End");
elements_button_right(canvas, "Resume");
} else if(state == BadBleStateWaitForBtn) {
elements_button_center(canvas, "Press to continue");
} else if(state == BadBleStateWillRun) {
elements_button_center(canvas, "Cancel");
}
if(state == BadBleStateNotConnected) {
canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18);
canvas_set_font(canvas, FontPrimary);
canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Connect");
canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "to device");
} else if(state == BadBleStateWillRun) {
canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18);
canvas_set_font(canvas, FontPrimary);
canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Will run");
canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "on connect");
} else if(state == BadBleStateFileError) {
canvas_draw_icon(canvas, 4, 26, &I_Error_18x18);
canvas_set_font(canvas, FontPrimary);
canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "File");
canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "ERROR");
} else if(state == BadBleStateScriptError) {
canvas_draw_icon(canvas, 4, 26, &I_Error_18x18);
canvas_set_font(canvas, FontPrimary);
canvas_draw_str_aligned(canvas, 127, 33, AlignRight, AlignBottom, "ERROR:");
canvas_set_font(canvas, FontSecondary);
furi_string_printf(disp_str, "line %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);
} else if(state == BadBleStateIdle) {
canvas_draw_icon(canvas, 4, 26, &I_Smile_18x18);
canvas_set_font(canvas, FontBigNumbers);
canvas_draw_str_aligned(canvas, 114, 40, AlignRight, AlignBottom, "0");
canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14);
} else if(state == BadBleStateRunning) {
if(model->anim_frame == 0) {
canvas_draw_icon(canvas, 4, 23, &I_EviSmile1_18x21);
} else {
canvas_draw_icon(canvas, 4, 23, &I_EviSmile2_18x21);
}
canvas_set_font(canvas, FontBigNumbers);
furi_string_printf(
disp_str, "%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);
} else if(state == BadBleStateDone) {
canvas_draw_icon(canvas, 4, 23, &I_EviSmile1_18x21);
canvas_set_font(canvas, FontBigNumbers);
canvas_draw_str_aligned(canvas, 114, 40, AlignRight, AlignBottom, "100");
furi_string_reset(disp_str);
canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14);
} else if(state == BadBleStateDelay) {
if(model->anim_frame == 0) {
canvas_draw_icon(canvas, 4, 23, &I_EviWaiting1_18x21);
} else {
canvas_draw_icon(canvas, 4, 23, &I_EviWaiting2_18x21);
}
canvas_set_font(canvas, FontBigNumbers);
furi_string_printf(
disp_str, "%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);
} else if((state == BadBleStatePaused) || (state == BadBleStateWaitForBtn)) {
if(model->anim_frame == 0) {
canvas_draw_icon(canvas, 4, 23, &I_EviWaiting1_18x21);
} else {
canvas_draw_icon(canvas, 4, 23, &I_EviWaiting2_18x21);
}
canvas_set_font(canvas, FontBigNumbers);
furi_string_printf(
disp_str, "%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);
} else {
canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18);
}
furi_string_free(disp_str);
}
static bool bad_ble_input_callback(InputEvent* event, void* context) {
furi_assert(context);
BadBle* bad_ble = context;
bool consumed = false;
if(event->type == InputTypeShort) {
if(event->key == InputKeyLeft) {
consumed = true;
furi_assert(bad_ble->callback);
bad_ble->callback(event->key, bad_ble->context);
} else if(event->key == InputKeyOk) {
with_view_model(
bad_ble->view, BadBleModel * model, { model->pause_wait = false; }, true);
consumed = true;
furi_assert(bad_ble->callback);
bad_ble->callback(event->key, bad_ble->context);
} else if(event->key == InputKeyRight) {
with_view_model(
bad_ble->view,
BadBleModel * model,
{
if((model->state.state == BadBleStateRunning) ||
(model->state.state == BadBleStateDelay)) {
model->pause_wait = true;
}
},
true);
consumed = true;
furi_assert(bad_ble->callback);
bad_ble->callback(event->key, bad_ble->context);
}
}
return consumed;
}
BadBle* bad_ble_view_alloc(void) {
BadBle* bad_ble = malloc(sizeof(BadBle));
bad_ble->view = view_alloc();
view_allocate_model(bad_ble->view, ViewModelTypeLocking, sizeof(BadBleModel));
view_set_context(bad_ble->view, bad_ble);
view_set_draw_callback(bad_ble->view, bad_ble_draw_callback);
view_set_input_callback(bad_ble->view, bad_ble_input_callback);
return bad_ble;
}
void bad_ble_view_free(BadBle* bad_ble) {
furi_assert(bad_ble);
view_free(bad_ble->view);
free(bad_ble);
}
View* bad_ble_view_get_view(BadBle* bad_ble) {
furi_assert(bad_ble);
return bad_ble->view;
}
void bad_ble_view_set_button_callback(
BadBle* bad_ble,
BadBleButtonCallback callback,
void* context) {
furi_assert(bad_ble);
furi_assert(callback);
with_view_model(
bad_ble->view,
BadBleModel * model,
{
UNUSED(model);
bad_ble->callback = callback;
bad_ble->context = context;
},
true);
}
void bad_ble_view_set_file_name(BadBle* bad_ble, const char* name) {
furi_assert(name);
with_view_model(
bad_ble->view,
BadBleModel * model,
{ strlcpy(model->file_name, name, MAX_NAME_LEN); },
true);
}
void bad_ble_view_set_layout(BadBle* bad_ble, const char* layout) {
furi_assert(layout);
with_view_model(
bad_ble->view,
BadBleModel * model,
{ strlcpy(model->layout, layout, MAX_NAME_LEN); },
true);
}
void bad_ble_view_set_state(BadBle* bad_ble, BadBleState* st) {
furi_assert(st);
with_view_model(
bad_ble->view,
BadBleModel * model,
{
memcpy(&(model->state), st, sizeof(BadBleState));
model->anim_frame ^= 1;
if(model->state.state == BadBleStatePaused) {
model->pause_wait = false;
}
},
true);
}
bool bad_ble_view_is_idle_state(BadBle* bad_ble) {
bool is_idle = false;
with_view_model(
bad_ble->view,
BadBleModel * model,
{
if((model->state.state == BadBleStateIdle) ||
(model->state.state == BadBleStateDone) ||
(model->state.state == BadBleStateNotConnected)) {
is_idle = true;
}
},
false);
return is_idle;
}

View File

@@ -1,26 +0,0 @@
#pragma once
#include <gui/view.h>
#include "../helpers/ducky_script.h"
typedef struct BadBle BadBle;
typedef void (*BadBleButtonCallback)(InputKey key, void* context);
BadBle* bad_ble_view_alloc(void);
void bad_ble_view_free(BadBle* bad_ble);
View* bad_ble_view_get_view(BadBle* bad_ble);
void bad_ble_view_set_button_callback(
BadBle* bad_ble,
BadBleButtonCallback callback,
void* context);
void bad_ble_view_set_file_name(BadBle* bad_ble, const char* name);
void bad_ble_view_set_layout(BadBle* bad_ble, const char* layout);
void bad_ble_view_set_state(BadBle* bad_ble, BadBleState* st);
bool bad_ble_view_is_idle_state(BadBle* bad_ble);

View File

@@ -2,10 +2,10 @@ let serial = require("serial");
serial.setup("usart", 230400); serial.setup("usart", 230400);
while (1) { while (1) {
let rx_data = serial.readBytes(1, 0); let rx_data = serial.readBytes(1, 1000);
if (rx_data !== undefined) { if (rx_data !== undefined) {
serial.write(rx_data); serial.write(rx_data);
let data_view = Uint8Array(rx_data); let data_view = Uint8Array(rx_data);
print("0x" + toString(data_view[0], 16)); print("0x" + toString(data_view[0], 16));
} }
} }

View File

@@ -78,4 +78,4 @@ export declare function print(string: string, delay?: number): void;
* @param string The string to print * @param string The string to print
* @param delay How many milliseconds to wait between key presses * @param delay How many milliseconds to wait between key presses
*/ */
export declare function println(): void; export declare function println(string: string, delay?: number): void;

View File

Before

Width:  |  Height:  |  Size: 145 B

After

Width:  |  Height:  |  Size: 145 B

View File

@@ -9,7 +9,7 @@ It's important to regularly update your Developer Board to ensure that you have
## Step 1. Install the micro Flipper Build Tool ## Step 1. Install the micro Flipper Build Tool
is a cross-platform tool developed and supported by our team that enables basic development tasks for Flipper Zero, such as building and debugging applications, flashing firmware, creating VS Code development configurations, and flashing firmware to the Wi-Fi Developer Board. [micro Flipper Build Tool (uFBT)](https://pypi.org/project/ufbt/) is a cross-platform tool developed and supported by our team that enables basic development tasks for Flipper Zero, such as building and debugging applications, flashing firmware, creating VS Code development configurations, and flashing firmware to the Wi-Fi Developer Board.
**On Linux & macOS:** **On Linux & macOS:**
@@ -43,15 +43,15 @@ To update the firmware, you need to switch your Developer Board to Bootloader mo
- **Windows:** Go to **Device Manager** and expand the **Ports (COM & LPT)** section. - **Windows:** Go to **Device Manager** and expand the **Ports (COM & LPT)** section.
2. Connect the Developer Board to your computer using a USB-C cable. 2. Connect the Developer Board to your computer using a USB-C cable.
\image html https://cdn.flipperzero.one/Flipper_Zero_Wi-Fi_devboard_update_wired_connection.jpg width=700 \image html https://cdn.flipperzero.one/Flipper_Zero_Wi-Fi_devboard_update_wired_connection.jpg width=700
3. Switch your Developer Board to Bootloader mode: 3. Switch your Developer Board to Bootloader mode:
1. Press and hold the **BOOT** button. 3.1. Press and hold the **BOOT** button.
2. Press the **RESET** button while holding the **BOOT** button.
3. Release the **BOOT** button.
3.2. Press the **RESET** button while holding the **BOOT** button.
3.3. Release the **BOOT** button.
\image html https://cdn.flipperzero.one/Flipper_Zero_Wi-Fi_devboard_reboot_to_bootloader.png width=700 \image html https://cdn.flipperzero.one/Flipper_Zero_Wi-Fi_devboard_reboot_to_bootloader.png width=700
4. Repeat **Step 1** and view the name of your Developer Board that appeared in the list. 4. Repeat **Step 1** and view the name of your Developer Board that appeared in the list.
@@ -101,7 +101,6 @@ To fix it, try doing the following:
## Step 4. Finish the installation ## Step 4. Finish the installation
1. Reboot the Developer Board by pressing the **RESET** button. 1. Reboot the Developer Board by pressing the **RESET** button.
\image html https://cdn.flipperzero.one/Flipper_Zero_Wi-Fi_devboard_reboot_after_flashing.jpg width=700 \image html https://cdn.flipperzero.one/Flipper_Zero_Wi-Fi_devboard_reboot_after_flashing.jpg width=700
2. Disconnect and reconnect the USB-C cable. 2. Disconnect and reconnect the USB-C cable.

View File

@@ -1,4 +1,4 @@
# Get started with the Dev Board {#dev_board_get_started} # Get started with the Devboard {#dev_board_get_started}
\image html https://cdn.flipperzero.one/Flipper_Zero_WiFi_developer_board_box_CDN.jpg width=700 \image html https://cdn.flipperzero.one/Flipper_Zero_WiFi_developer_board_box_CDN.jpg width=700

View File

@@ -1,4 +1,4 @@
# Reading logs via the Dev Board {#dev_board_reading_logs} # Reading logs via the Devboard {#dev_board_reading_logs}
The Developer Board allows you to read Flipper Zero logs via UART. Unlike reading logs via the command-line interface (CLI), the Developer Board enables you to collect logs from the device directly to a serial console independently from the operating system of Flipper Zero. It allows you to see the device's logs when it's loading, updating, or crashing. It's useful for debugging and troubleshooting during software development. The Developer Board allows you to read Flipper Zero logs via UART. Unlike reading logs via the command-line interface (CLI), the Developer Board enables you to collect logs from the device directly to a serial console independently from the operating system of Flipper Zero. It allows you to see the device's logs when it's loading, updating, or crashing. It's useful for debugging and troubleshooting during software development.

View File

@@ -54,7 +54,6 @@ To connect the Developer Board in **Wi-Fi client** mode, you need to configure i
4. Select the **STA** mode and enter your network's **SSID** (name) and **password**. For convenience, you can click the **+** button to see the list of nearby 2.4 GHz networks (5 GHz networks aren't supported). 4. Select the **STA** mode and enter your network's **SSID** (name) and **password**. For convenience, you can click the **+** button to see the list of nearby 2.4 GHz networks (5 GHz networks aren't supported).
5. Save the configuration and reboot the Developer Board. 5. Save the configuration and reboot the Developer Board.
\image html https://cdn.flipperzero.one/Flipper_Zero_WiFi_devboard_connect_to_WiFi_CDN.jpg width=700 \image html https://cdn.flipperzero.one/Flipper_Zero_WiFi_devboard_connect_to_WiFi_CDN.jpg width=700
6. Now, you can access the Devboard's web interface at [http://blackmagic.local](https://blackmagic.local) via the existing Wi-Fi network without losing connection to the internet. 6. Now, you can access the Devboard's web interface at [http://blackmagic.local](https://blackmagic.local) via the existing Wi-Fi network without losing connection to the internet.

View File

@@ -1,7 +1,8 @@
/** /**
@mainpage Overview @mainpage notitle
## Welcome to Flipper Developer Documentation!
Welcome to the Flipper Developer Documentation! ---
This documentation is intended for developers interested in modifying the Flipper Zero firmware, creating Apps and JavaScript programs, or working on external hardware modules for the device. This documentation is intended for developers interested in modifying the Flipper Zero firmware, creating Apps and JavaScript programs, or working on external hardware modules for the device.

View File

@@ -119,8 +119,6 @@ FuriStatus furi_timer_stop(FuriTimer* instance) {
furi_check(xTimerStop(hTimer, portMAX_DELAY) == pdPASS); furi_check(xTimerStop(hTimer, portMAX_DELAY) == pdPASS);
furi_timer_flush();
return FuriStatusOk; return FuriStatusOk;
} }

View File

@@ -15,6 +15,9 @@
* Example: * Example:
* ./fbt --extra-define=DIGITAL_SIGNAL_DEBUG_OUTPUT_PIN=gpio_ext_pb3 * ./fbt --extra-define=DIGITAL_SIGNAL_DEBUG_OUTPUT_PIN=gpio_ext_pb3
*/ */
#ifdef DIGITAL_SIGNAL_DEBUG_OUTPUT_PIN
#include <furi_hal.h>
#endif
#define TAG "DigitalSequence" #define TAG "DigitalSequence"

View File

@@ -543,6 +543,22 @@ void mf_classic_set_key_not_found(
} }
} }
MfClassicKey
mf_classic_get_key(const MfClassicData* data, uint8_t sector_num, MfClassicKeyType key_type) {
furi_check(data);
furi_check(sector_num < mf_classic_get_total_sectors_num(data->type));
furi_check(key_type == MfClassicKeyTypeA || key_type == MfClassicKeyTypeB);
const MfClassicSectorTrailer* sector_trailer =
mf_classic_get_sector_trailer_by_sector(data, sector_num);
if(key_type == MfClassicKeyTypeA) {
return sector_trailer->key_a;
} else {
return sector_trailer->key_b;
}
}
bool mf_classic_is_block_read(const MfClassicData* data, uint8_t block_num) { bool mf_classic_is_block_read(const MfClassicData* data, uint8_t block_num) {
furi_check(data); furi_check(data);

View File

@@ -213,6 +213,9 @@ void mf_classic_set_key_not_found(
uint8_t sector_num, uint8_t sector_num,
MfClassicKeyType key_type); MfClassicKeyType key_type);
MfClassicKey
mf_classic_get_key(const MfClassicData* data, uint8_t sector_num, MfClassicKeyType key_type);
bool mf_classic_is_block_read(const MfClassicData* data, uint8_t block_num); bool mf_classic_is_block_read(const MfClassicData* data, uint8_t block_num);
void mf_classic_set_block_read(MfClassicData* data, uint8_t block_num, MfClassicBlock* block_data); void mf_classic_set_block_read(MfClassicData* data, uint8_t block_num, MfClassicBlock* block_data);

View File

@@ -8,9 +8,9 @@
// TODO: Buffer writes for Hardnested, set state to Log when finished and sum property matches // TODO: Buffer writes for Hardnested, set state to Log when finished and sum property matches
// TODO: Store target key in CUID dictionary // TODO: Store target key in CUID dictionary
// TODO: Fix rare nested_target_key 64 bug
// TODO: Dead code for malloc returning NULL? // TODO: Dead code for malloc returning NULL?
// TODO: Auth1 static encrypted exists (rare) // TODO: Auth1 static encrypted exists (rare)
// TODO: Use keys found by NFC plugins, cached keys
#define MF_CLASSIC_MAX_BUFF_SIZE (64) #define MF_CLASSIC_MAX_BUFF_SIZE (64)
@@ -1663,7 +1663,7 @@ NfcCommand mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instanc
"Found key candidate %06llx", "Found key candidate %06llx",
bit_lib_bytes_to_num_be(key_candidate->data, sizeof(MfClassicKey))); bit_lib_bytes_to_num_be(key_candidate->data, sizeof(MfClassicKey)));
dict_attack_ctx->current_key = *key_candidate; dict_attack_ctx->current_key = *key_candidate;
dict_attack_ctx->reuse_key_sector = (target_block / 4); dict_attack_ctx->reuse_key_sector = target_sector;
dict_attack_ctx->current_key_type = target_key_type; dict_attack_ctx->current_key_type = target_key_type;
free(key_candidate); free(key_candidate);
break; break;
@@ -1780,7 +1780,7 @@ NfcCommand mf_classic_poller_handler_nested_log(MfClassicPoller* instance) {
bool mf_classic_nested_is_target_key_found(MfClassicPoller* instance, bool is_dict_attack) { bool mf_classic_nested_is_target_key_found(MfClassicPoller* instance, bool is_dict_attack) {
MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx;
bool is_weak = dict_attack_ctx->prng_type == MfClassicPrngTypeWeak; bool is_weak = dict_attack_ctx->prng_type == MfClassicPrngTypeWeak;
uint8_t nested_target_key = dict_attack_ctx->nested_target_key; uint16_t nested_target_key = dict_attack_ctx->nested_target_key;
MfClassicKeyType target_key_type; MfClassicKeyType target_key_type;
uint8_t target_sector; uint8_t target_sector;
@@ -1818,12 +1818,13 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance
bool initial_dict_attack_iter = false; bool initial_dict_attack_iter = false;
if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseNone) { if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseNone) {
dict_attack_ctx->auth_passed = true; dict_attack_ctx->auth_passed = true;
dict_attack_ctx->nested_known_key = dict_attack_ctx->current_key;
bool backdoor_present = (dict_attack_ctx->backdoor != MfClassicBackdoorNone); bool backdoor_present = (dict_attack_ctx->backdoor != MfClassicBackdoorNone);
if(!(backdoor_present)) { if(!(backdoor_present)) {
for(uint8_t sector = 0; sector < instance->sectors_total; sector++) { for(uint8_t sector = 0; sector < instance->sectors_total; sector++) {
for(uint8_t key_type = 0; key_type < 2; key_type++) { for(uint8_t key_type = 0; key_type < 2; key_type++) {
if(mf_classic_is_key_found(instance->data, sector, key_type)) { if(mf_classic_is_key_found(instance->data, sector, key_type)) {
dict_attack_ctx->nested_known_key =
mf_classic_get_key(instance->data, sector, key_type);
dict_attack_ctx->nested_known_key_sector = sector; dict_attack_ctx->nested_known_key_sector = sector;
dict_attack_ctx->nested_known_key_type = key_type; dict_attack_ctx->nested_known_key_type = key_type;
break; break;
@@ -1832,6 +1833,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance
} }
dict_attack_ctx->nested_phase = MfClassicNestedPhaseAnalyzePRNG; dict_attack_ctx->nested_phase = MfClassicNestedPhaseAnalyzePRNG;
} else { } else {
dict_attack_ctx->nested_known_key = dict_attack_ctx->current_key;
dict_attack_ctx->nested_known_key_sector = 0; dict_attack_ctx->nested_known_key_sector = 0;
dict_attack_ctx->nested_known_key_type = MfClassicKeyTypeA; dict_attack_ctx->nested_known_key_type = MfClassicKeyTypeA;
dict_attack_ctx->prng_type = MfClassicPrngTypeWeak; dict_attack_ctx->prng_type = MfClassicPrngTypeWeak;
@@ -1879,9 +1881,10 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance
uint16_t dict_target_key_max = (dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) ? uint16_t dict_target_key_max = (dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) ?
(instance->sectors_total * 2) : (instance->sectors_total * 2) :
(instance->sectors_total * 16); (instance->sectors_total * 16);
if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttackResume) { if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttackVerify) {
if(!(mf_classic_nested_is_target_key_found(instance, true)) && if(!(mf_classic_nested_is_target_key_found(instance, true)) &&
(dict_attack_ctx->nested_nonce.count > 0)) { (dict_attack_ctx->nested_nonce.count > 0)) {
dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttackResume;
instance->state = MfClassicPollerStateNestedDictAttack; instance->state = MfClassicPollerStateNestedDictAttack;
return command; return command;
} else { } else {
@@ -1896,7 +1899,8 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance
dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttack; dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttack;
} }
} }
if((dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttack) && if((dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttack ||
dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttackResume) &&
(dict_attack_ctx->nested_target_key < dict_target_key_max)) { (dict_attack_ctx->nested_target_key < dict_target_key_max)) {
bool is_last_iter_for_hard_key = bool is_last_iter_for_hard_key =
((!is_weak) && ((dict_attack_ctx->nested_target_key % 8) == 7)); ((!is_weak) && ((dict_attack_ctx->nested_target_key % 8) == 7));
@@ -1920,11 +1924,14 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance
NULL; NULL;
} }
if((is_weak || is_last_iter_for_hard_key) && dict_attack_ctx->nested_nonce.count > 0) { if((is_weak || is_last_iter_for_hard_key) && dict_attack_ctx->nested_nonce.count > 0) {
// Key reuse // Key verify and reuse
dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttackResume; dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttackVerify;
dict_attack_ctx->auth_passed = false; dict_attack_ctx->auth_passed = false;
instance->state = MfClassicPollerStateKeyReuseStartNoOffset; instance->state = MfClassicPollerStateKeyReuseStartNoOffset;
return command; return command;
} else if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttackResume) {
dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttack;
dict_attack_ctx->auth_passed = true;
} }
if(!(dict_attack_ctx->auth_passed)) { if(!(dict_attack_ctx->auth_passed)) {
dict_attack_ctx->attempt_count++; dict_attack_ctx->attempt_count++;
@@ -2004,9 +2011,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance
} }
uint16_t nonce_collect_key_max; uint16_t nonce_collect_key_max;
if(dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) { if(dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) {
nonce_collect_key_max = dict_attack_ctx->static_encrypted ? nonce_collect_key_max = instance->sectors_total * 4;
((instance->sectors_total * 4) - 2) :
(instance->sectors_total * 4);
} else { } else {
nonce_collect_key_max = instance->sectors_total * 2; nonce_collect_key_max = instance->sectors_total * 2;
} }
@@ -2047,30 +2052,6 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance
dict_attack_ctx->attempt_count = 0; dict_attack_ctx->attempt_count = 0;
} }
dict_attack_ctx->auth_passed = true; dict_attack_ctx->auth_passed = true;
if(!(dict_attack_ctx->current_key_checked)) {
dict_attack_ctx->current_key_checked = true;
// Check if the nested target key is a known key
if(mf_classic_nested_is_target_key_found(instance, false)) {
// Continue to next key
if(!(dict_attack_ctx->static_encrypted)) {
dict_attack_ctx->nested_target_key++;
dict_attack_ctx->current_key_checked = false;
}
instance->state = MfClassicPollerStateNestedController;
return command;
}
// If it is not a known key, we'll need to calibrate for static encrypted backdoored tags
if((dict_attack_ctx->backdoor == MfClassicBackdoorAuth3) &&
(dict_attack_ctx->nested_target_key < nonce_collect_key_max) &&
!(recalibrated)) {
dict_attack_ctx->calibrated = false;
dict_attack_ctx->nested_phase = MfClassicNestedPhaseRecalibrate;
instance->state = MfClassicPollerStateNestedController;
return command;
}
}
// If we have tried to collect this nonce too many times, skip // If we have tried to collect this nonce too many times, skip
if((is_weak && (dict_attack_ctx->attempt_count >= MF_CLASSIC_NESTED_RETRY_MAXIMUM)) || if((is_weak && (dict_attack_ctx->attempt_count >= MF_CLASSIC_NESTED_RETRY_MAXIMUM)) ||
@@ -2096,12 +2077,52 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance
dict_attack_ctx->attempt_count = 0; dict_attack_ctx->attempt_count = 0;
} }
FURI_LOG_D(
TAG,
"Nested target key: %u (max: %u)",
dict_attack_ctx->nested_target_key,
nonce_collect_key_max);
if(!(dict_attack_ctx->current_key_checked)) {
if(dict_attack_ctx->nested_target_key == nonce_collect_key_max) {
// All nonces have been collected
FURI_LOG_D(TAG, "All nonces collected");
instance->state = MfClassicPollerStateNestedController;
return command;
}
dict_attack_ctx->current_key_checked = true;
// Check if the nested target key is a known key
if(mf_classic_nested_is_target_key_found(instance, false)) {
// Continue to next key
if(!(dict_attack_ctx->static_encrypted)) {
dict_attack_ctx->nested_target_key++;
dict_attack_ctx->current_key_checked = false;
}
instance->state = MfClassicPollerStateNestedController;
return command;
}
// If it is not a known key, we'll need to calibrate for static encrypted backdoored tags
if((dict_attack_ctx->backdoor == MfClassicBackdoorAuth3) &&
(dict_attack_ctx->nested_target_key < nonce_collect_key_max) &&
!(recalibrated)) {
dict_attack_ctx->calibrated = false;
dict_attack_ctx->nested_phase = MfClassicNestedPhaseRecalibrate;
instance->state = MfClassicPollerStateNestedController;
return command;
}
}
FURI_LOG_T(TAG, "Collecting a nonce");
// Collect a nonce // Collect a nonce
dict_attack_ctx->auth_passed = false; dict_attack_ctx->auth_passed = false;
instance->state = MfClassicPollerStateNestedCollectNtEnc; instance->state = MfClassicPollerStateNestedCollectNtEnc;
return command; return command;
} }
} }
dict_attack_ctx->nested_target_key = 0;
dict_attack_ctx->nested_phase = MfClassicNestedPhaseFinished; dict_attack_ctx->nested_phase = MfClassicNestedPhaseFinished;
instance->state = MfClassicPollerStateSuccess; instance->state = MfClassicPollerStateSuccess;
return command; return command;

View File

@@ -52,14 +52,15 @@ typedef enum {
* @brief MfClassic poller nested attack phase. * @brief MfClassic poller nested attack phase.
*/ */
typedef enum { typedef enum {
MfClassicNestedPhaseNone, MfClassicNestedPhaseNone, /**< No nested attack has taken place yet. */
MfClassicNestedPhaseAnalyzePRNG, MfClassicNestedPhaseAnalyzePRNG, /**< Analyze nonces produced by the PRNG to determine if they fit a weak PRNG */
MfClassicNestedPhaseDictAttack, MfClassicNestedPhaseDictAttack, /**< Search keys which match the expected PRNG properties and parity for collected nonces */
MfClassicNestedPhaseDictAttackResume, MfClassicNestedPhaseDictAttackVerify, /**< Verify candidate keys by authenticating to the sector with the key */
MfClassicNestedPhaseCalibrate, MfClassicNestedPhaseDictAttackResume, /**< Resume nested dictionary attack from the last tested (invalid) key */
MfClassicNestedPhaseRecalibrate, MfClassicNestedPhaseCalibrate, /**< Perform necessary calculations to recover the plaintext nonce during later collection phase (weak PRNG tags only) */
MfClassicNestedPhaseCollectNtEnc, MfClassicNestedPhaseRecalibrate, /**< Collect the next plaintext static encrypted nonce for backdoor static encrypted nonce nested attack */
MfClassicNestedPhaseFinished, MfClassicNestedPhaseCollectNtEnc, /**< Log nonces collected during nested authentication for key recovery */
MfClassicNestedPhaseFinished, /**< Nested attack has finished */
} MfClassicNestedPhase; } MfClassicNestedPhase;
/** /**

View File

@@ -5,7 +5,7 @@
#include <nfc/helpers/iso14443_crc.h> #include <nfc/helpers/iso14443_crc.h>
#define TAG "MfCLassicPoller" #define TAG "MfClassicPoller"
MfClassicError mf_classic_process_error(Iso14443_3aError error) { MfClassicError mf_classic_process_error(Iso14443_3aError error) {
MfClassicError ret = MfClassicErrorNone; MfClassicError ret = MfClassicErrorNone;

View File

@@ -1,5 +1,5 @@
entry,status,name,type,params entry,status,name,type,params
Version,+,77.2,, Version,+,77.3,,
Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt.h,,
Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,,
Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli.h,,
1 entry status name type params
2 Version + 77.2 77.3
3 Header + applications/services/bt/bt_service/bt.h
4 Header + applications/services/bt/bt_service/bt_keys_storage.h
5 Header + applications/services/cli/cli.h

View File

@@ -1,5 +1,5 @@
entry,status,name,type,params entry,status,name,type,params
Version,+,77.2,, Version,+,77.3,,
Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,,
Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt.h,,
Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,,
@@ -2512,6 +2512,7 @@ Function,+,mf_classic_get_base_data,Iso14443_3aData*,const MfClassicData*
Function,+,mf_classic_get_blocks_num_in_sector,uint8_t,uint8_t Function,+,mf_classic_get_blocks_num_in_sector,uint8_t,uint8_t
Function,+,mf_classic_get_device_name,const char*,"const MfClassicData*, NfcDeviceNameType" Function,+,mf_classic_get_device_name,const char*,"const MfClassicData*, NfcDeviceNameType"
Function,+,mf_classic_get_first_block_num_of_sector,uint8_t,uint8_t Function,+,mf_classic_get_first_block_num_of_sector,uint8_t,uint8_t
Function,+,mf_classic_get_key,MfClassicKey,"const MfClassicData*, uint8_t, MfClassicKeyType"
Function,+,mf_classic_get_read_sectors_and_keys,void,"const MfClassicData*, uint8_t*, uint8_t*" Function,+,mf_classic_get_read_sectors_and_keys,void,"const MfClassicData*, uint8_t*, uint8_t*"
Function,+,mf_classic_get_sector_by_block,uint8_t,uint8_t Function,+,mf_classic_get_sector_by_block,uint8_t,uint8_t
Function,+,mf_classic_get_sector_trailer_by_sector,MfClassicSectorTrailer*,"const MfClassicData*, uint8_t" Function,+,mf_classic_get_sector_trailer_by_sector,MfClassicSectorTrailer*,"const MfClassicData*, uint8_t"
1 entry status name type params
2 Version + 77.2 77.3
3 Header + applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h
4 Header + applications/services/bt/bt_service/bt.h
5 Header + applications/services/bt/bt_service/bt_keys_storage.h
2512 Function + mf_classic_get_blocks_num_in_sector uint8_t uint8_t
2513 Function + mf_classic_get_device_name const char* const MfClassicData*, NfcDeviceNameType
2514 Function + mf_classic_get_first_block_num_of_sector uint8_t uint8_t
2515 Function + mf_classic_get_key MfClassicKey const MfClassicData*, uint8_t, MfClassicKeyType
2516 Function + mf_classic_get_read_sectors_and_keys void const MfClassicData*, uint8_t*, uint8_t*
2517 Function + mf_classic_get_sector_by_block uint8_t uint8_t
2518 Function + mf_classic_get_sector_trailer_by_sector MfClassicSectorTrailer* const MfClassicData*, uint8_t