mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-05-12 12:58:36 -07:00
Merge remote-tracking branch nestednonces into dev
This commit is contained in:
@@ -1,6 +1,5 @@
|
|||||||
#include <furi.h>
|
#include <furi.h>
|
||||||
#include <gui/gui.h>
|
#include <gui/gui.h>
|
||||||
#include <gui/canvas_i.h>
|
|
||||||
#include <input/input.h>
|
#include <input/input.h>
|
||||||
|
|
||||||
#define BUFFER_SIZE (32U)
|
#define BUFFER_SIZE (32U)
|
||||||
@@ -42,10 +41,11 @@ static DirectDraw* direct_draw_alloc(void) {
|
|||||||
static void direct_draw_free(DirectDraw* instance) {
|
static void direct_draw_free(DirectDraw* instance) {
|
||||||
furi_pubsub_unsubscribe(instance->input, instance->input_subscription);
|
furi_pubsub_unsubscribe(instance->input, instance->input_subscription);
|
||||||
|
|
||||||
instance->canvas = NULL;
|
|
||||||
gui_direct_draw_release(instance->gui);
|
gui_direct_draw_release(instance->gui);
|
||||||
furi_record_close(RECORD_GUI);
|
furi_record_close(RECORD_GUI);
|
||||||
furi_record_close(RECORD_INPUT_EVENTS);
|
furi_record_close(RECORD_INPUT_EVENTS);
|
||||||
|
|
||||||
|
free(instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void direct_draw_block(Canvas* canvas, uint32_t size, uint32_t counter) {
|
static void direct_draw_block(Canvas* canvas, uint32_t size, uint32_t counter) {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ App(
|
|||||||
icon="A_BadUsb_14",
|
icon="A_BadUsb_14",
|
||||||
order=70,
|
order=70,
|
||||||
resources="resources",
|
resources="resources",
|
||||||
fap_libs=["assets", "ble_profile"],
|
fap_libs=["assets"],
|
||||||
fap_icon="icon.png",
|
fap_icon="icon.png",
|
||||||
fap_category="USB",
|
fap_category="USB",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ 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 {
|
||||||
@@ -45,8 +44,6 @@ 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);
|
||||||
@@ -56,7 +53,6 @@ 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;
|
||||||
@@ -68,7 +64,6 @@ 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);
|
||||||
@@ -84,9 +79,6 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,6 @@ 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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
#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;
|
||||||
@@ -72,155 +69,6 @@ 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() {
|
||||||
typedef struct {
|
return &hid_api_usb;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,11 +7,6 @@ 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);
|
||||||
@@ -26,7 +21,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(BadUsbHidInterface interface);
|
const BadUsbHidApi* bad_usb_hid_get_interface();
|
||||||
|
|
||||||
void bad_usb_hid_ble_remove_pairing(void);
|
void bad_usb_hid_ble_remove_pairing(void);
|
||||||
|
|
||||||
|
|||||||
@@ -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, BadUsbHidInterface interface) {
|
BadUsbScript* bad_usb_script_open(FuriString* file_path) {
|
||||||
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, BadUsbHidInterface inte
|
|||||||
|
|
||||||
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(interface);
|
bad_usb->hid = bad_usb_hid_get_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);
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ typedef struct {
|
|||||||
|
|
||||||
typedef struct BadUsbScript BadUsbScript;
|
typedef struct BadUsbScript BadUsbScript;
|
||||||
|
|
||||||
BadUsbScript* bad_usb_script_open(FuriString* file_path, BadUsbHidInterface interface);
|
BadUsbScript* bad_usb_script_open(FuriString* file_path);
|
||||||
|
|
||||||
void bad_usb_script_close(BadUsbScript* bad_usb);
|
void bad_usb_script_close(BadUsbScript* bad_usb);
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@@ -1,88 +0,0 @@
|
|||||||
#include "../bad_usb_app_i.h"
|
|
||||||
|
|
||||||
enum SubmenuIndex {
|
|
||||||
ConfigIndexKeyboardLayout,
|
|
||||||
ConfigIndexInterface,
|
|
||||||
ConfigIndexBleUnpair,
|
|
||||||
};
|
|
||||||
|
|
||||||
const char* const interface_mode_text[2] = {
|
|
||||||
"USB",
|
|
||||||
"BLE",
|
|
||||||
};
|
|
||||||
|
|
||||||
void bad_usb_scene_config_select_callback(void* context, uint32_t index) {
|
|
||||||
BadUsbApp* bad_usb = context;
|
|
||||||
if(index != ConfigIndexInterface) {
|
|
||||||
view_dispatcher_send_custom_event(bad_usb->view_dispatcher, index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void bad_usb_scene_config_interface_callback(VariableItem* item) {
|
|
||||||
BadUsbApp* bad_usb = variable_item_get_context(item);
|
|
||||||
furi_assert(bad_usb);
|
|
||||||
uint8_t index = variable_item_get_current_value_index(item);
|
|
||||||
|
|
||||||
variable_item_set_current_value_text(item, interface_mode_text[index]);
|
|
||||||
bad_usb->interface = index;
|
|
||||||
|
|
||||||
view_dispatcher_send_custom_event(bad_usb->view_dispatcher, ConfigIndexInterface);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
VariableItem* item = variable_item_list_add(
|
|
||||||
var_item_list, "Interface", 2, bad_usb_scene_config_interface_callback, bad_usb);
|
|
||||||
if(bad_usb->interface == BadUsbHidInterfaceUsb) {
|
|
||||||
variable_item_set_current_value_index(item, 0);
|
|
||||||
variable_item_set_current_value_text(item, interface_mode_text[0]);
|
|
||||||
} else {
|
|
||||||
variable_item_set_current_value_index(item, 1);
|
|
||||||
variable_item_set_current_value_text(item, interface_mode_text[1]);
|
|
||||||
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 == ConfigIndexInterface) {
|
|
||||||
draw_menu(bad_usb);
|
|
||||||
} else if(event.event == ConfigIndexBleUnpair) {
|
|
||||||
bad_usb_hid_ble_remove_pairing();
|
|
||||||
} 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);
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
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)
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ 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, BadUsbSceneConfig);
|
scene_manager_next_scene(app->scene_manager, BadUsbSceneConfigLayout);
|
||||||
}
|
}
|
||||||
consumed = true;
|
consumed = true;
|
||||||
} else if(event.event == InputKeyOk) {
|
} else if(event.event == InputKeyOk) {
|
||||||
@@ -39,7 +39,7 @@ 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, app->interface);
|
app->bad_usb_script = bad_usb_script_open(app->file_path);
|
||||||
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;
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) {
|
|||||||
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, "Config");
|
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) {
|
||||||
|
|||||||
@@ -896,3 +896,49 @@ type: raw
|
|||||||
frequency: 38000
|
frequency: 38000
|
||||||
duty_cycle: 0.330000
|
duty_cycle: 0.330000
|
||||||
data: 4388 4399 572 1583 573 532 546 1585 571 1583 574 503 575 505 573 1584 572 504 574 505 573 1584 573 506 572 504 573 1584 573 1584 572 505 574 1611 545 506 573 1583 573 1585 571 1586 545 1609 547 531 547 1611 545 1608 548 1607 549 530 548 529 548 531 548 532 546 1610 546 533 545 530 547 1609 547 1610 547 1609 547 533 545 529 548 530 548 530 547 531 546 530 547 530 548 533 544 1608 548 1608 548 1610 546 1606 550 1609 547 5203 4388 4397 548 1609 547 531 547 1608 548 1608 548 530 548 530 548 1608 548 531 547 531 547 1610 546 531 547 530 548 1609 547 1611 546 532 547 1609 547 531 547 1608 548 1610 546 1609 547 1608 548 530 547 1609 547 1608 548 1609 547 531 546 530 548 530 547 530 547 1608 548 532 547 534 545 1608 548 1608 548 1609 547 530 548 531 547 531 547 532 546 531 546 531 547 532 546 530 548 1608 547 1608 548 1610 546 1608 548 1608 548
|
data: 4388 4399 572 1583 573 532 546 1585 571 1583 574 503 575 505 573 1584 572 504 574 505 573 1584 573 506 572 504 573 1584 573 1584 572 505 574 1611 545 506 573 1583 573 1585 571 1586 545 1609 547 531 547 1611 545 1608 548 1607 549 530 548 529 548 531 548 532 546 1610 546 533 545 530 547 1609 547 1610 547 1609 547 533 545 529 548 530 548 530 547 531 546 530 547 530 548 533 544 1608 548 1608 548 1610 546 1606 550 1609 547 5203 4388 4397 548 1609 547 531 547 1608 548 1608 548 530 548 530 548 1608 548 531 547 531 547 1610 546 531 547 530 548 1609 547 1611 546 532 547 1609 547 531 547 1608 548 1610 546 1609 547 1608 548 530 547 1609 547 1608 548 1609 547 531 546 530 548 530 547 530 547 1608 548 532 547 534 545 1608 548 1608 548 1609 547 530 548 531 547 531 547 532 546 531 546 531 547 532 546 530 548 1608 547 1608 548 1610 546 1608 548 1608 548
|
||||||
|
#
|
||||||
|
# Model: Panasonic CS-E9HKR
|
||||||
|
#
|
||||||
|
name: Dh
|
||||||
|
type: raw
|
||||||
|
frequency: 38000
|
||||||
|
duty_cycle: 0.330000
|
||||||
|
data: 3466 1748 445 367 502 1292 444 422 444 420 445 422 443 420 445 421 445 420 445 363 503 420 445 422 444 420 445 424 445 1292 445 421 445 451 414 421 444 421 444 421 445 422 443 423 446 1292 443 1292 445 1292 445 421 497 372 444 1293 445 420 445 421 444 421 444 421 445 421 444 421 444 420 445 422 444 420 445 421 444 421 444 421 445 422 443 356 509 420 445 421 445 429 436 421 444 421 444 414 452 420 445 421 444 421 444 423 443 421 444 421 444 421 444 423 443 420 445 424 445 1291 444 1294 444 421 444 421 444 421 444 422 444 423 445 9989 3463 1752 444 424 445 1293 443 422 444 392 473 421 444 416 449 423 443 421 444 421 444 421 444 423 443 422 443 425 444 1292 444 423 443 422 443 419 446 421 444 423 443 421 444 426 443 1292 444 1294 443 1294 443 421 444 425 444 1294 444 422 443 421 444 422 443 424 442 421 444 421 444 421 444 423 444 421 444 422 443 421 444 423 443 425 444 1293 444 421 444 421 444 1293 444 423 446 1292 445 321 546 421 444 421 444 385 480 423 443 425 444 1291 445 1293 443 422 445 422 443 421 444 421 496 370 444 422 443 421 496 370 443 421 444 1291 497 1239 444 1284 504 1237 447 1293 444 425 444 1293 496 371 494 1241 495 370 496 369 497 297 517 421 496 370 495 369 496 370 496 370 495 370 495 369 496 372 494 370 495 369 496 370 495 370 496 370 495 373 495 1240 495 1241 495 1240 497 369 496 371 494 371 443 422 496 369 496 368 497 370 496 369 496 372 497 1239 496 1241 496 1241 496 370 495 310 555 371 495 371 494 368 497 370 495 370 496 361 504 370 495 369 496 372 494 372 494 369 496 371 494 371 495 373 495 1240 497 370 495 371 495 370 495 369 496 370 495 370 495 1242 495 373 492 370 495 366 500 369 496 371 495 370 495 371 495 369 496 371 494 370 495 371 495 371 494 371 494 370 495 372 494 380 488 1241 496 374 495 1241 495 1240 495 1240 495 1241 495 1242 494 1244 494
|
||||||
|
#
|
||||||
|
name: Cool_hi
|
||||||
|
type: raw
|
||||||
|
frequency: 38000
|
||||||
|
duty_cycle: 0.330000
|
||||||
|
data: 3467 1751 442 425 444 1294 443 424 442 422 443 423 442 422 443 422 444 421 444 421 444 424 441 423 443 421 444 425 444 1294 443 424 442 423 442 422 443 425 440 423 443 423 442 427 442 1292 443 1322 415 1293 444 422 443 425 443 1295 443 424 441 421 444 423 442 424 442 425 440 423 442 423 442 424 442 292 573 421 444 423 442 422 444 422 443 422 443 423 442 425 441 423 442 425 440 421 444 424 442 422 443 422 443 422 443 424 442 422 443 422 443 422 443 424 442 421 444 426 443 1292 443 1294 444 423 442 422 443 423 442 424 442 426 442 9989 3465 1753 442 425 444 1292 445 424 442 424 441 421 444 422 443 422 444 423 442 423 442 352 513 423 443 423 442 425 443 1296 441 423 443 422 443 422 443 424 441 423 443 422 443 427 442 1293 442 1322 415 1293 444 421 444 425 443 1294 444 422 443 422 443 421 444 425 441 423 442 422 443 421 444 423 443 421 444 422 443 422 443 424 442 425 444 1261 475 422 443 422 443 1292 444 1293 442 1295 441 424 442 422 443 422 443 427 442 1322 415 1292 444 1292 444 1294 442 423 443 422 443 421 444 422 443 423 443 421 444 421 444 423 442 421 444 1291 444 1293 443 1294 442 1195 541 1293 444 425 443 1293 443 422 444 1322 414 421 444 422 443 423 443 422 443 423 442 422 443 425 441 422 443 422 443 423 442 423 443 422 443 422 443 421 444 423 444 421 444 424 444 1292 444 1293 444 1294 442 422 443 421 444 423 443 422 443 421 444 422 443 424 442 421 444 426 443 1291 444 1293 443 1293 444 422 443 421 444 422 444 421 444 422 443 421 444 423 443 423 442 421 444 422 443 423 444 421 444 421 444 421 444 422 444 425 444 1294 443 422 443 423 444 424 441 422 443 422 443 422 443 1294 443 422 443 421 444 424 442 421 444 413 452 421 444 423 443 421 444 422 443 422 443 422 444 421 444 422 443 422 443 423 443 426 443 1294 442 421 444 423 442 1293 443 1293 443 421 444 422 444 362 505
|
||||||
|
#
|
||||||
|
name: Cool_lo
|
||||||
|
type: raw
|
||||||
|
frequency: 38000
|
||||||
|
duty_cycle: 0.330000
|
||||||
|
data: 3467 1751 444 424 444 1291 446 422 444 420 445 420 445 419 446 421 445 378 487 421 444 420 445 420 446 421 444 423 446 1321 415 451 415 420 445 420 445 419 446 421 445 419 446 422 447 1290 445 1291 445 1291 446 420 445 424 445 1293 445 420 445 420 445 420 445 421 445 420 445 420 445 419 446 421 445 420 445 421 444 420 445 421 445 420 445 420 445 420 445 421 445 419 446 420 445 420 445 420 446 420 445 421 444 419 446 422 444 420 445 420 445 421 444 421 445 421 444 424 445 1290 445 1292 446 420 445 421 444 420 445 423 443 424 444 9988 3465 1751 446 423 445 1291 446 421 445 420 445 420 445 420 445 422 444 420 445 420 445 421 444 421 446 421 444 424 445 1291 446 421 445 421 444 420 445 420 445 420 446 421 444 423 446 1291 444 1293 444 1292 445 420 445 423 446 1294 444 419 446 420 445 421 444 422 444 420 445 421 444 420 445 421 445 421 444 420 445 420 445 422 444 421 444 420 445 420 445 421 444 1320 415 1291 445 1292 444 422 444 420 445 419 446 420 445 421 445 421 444 424 445 1291 446 421 445 420 445 420 445 421 444 421 445 421 444 421 444 420 445 420 445 1291 445 1292 443 1290 445 1291 446 1292 445 424 444 1293 444 421 444 1292 445 422 443 421 444 381 485 420 445 420 445 420 445 422 444 421 444 420 445 421 444 421 445 421 444 421 444 420 445 421 445 421 444 424 445 1291 444 1292 445 1291 446 420 445 419 446 422 444 421 444 420 445 420 445 421 445 420 445 423 446 1291 444 1291 446 1292 445 420 445 420 445 422 444 420 445 420 445 420 445 422 444 421 444 420 445 419 446 420 446 420 445 419 446 419 446 421 445 423 445 1293 444 419 446 421 445 420 445 419 446 420 445 420 445 1292 445 420 445 420 445 421 445 420 445 419 446 419 446 420 446 419 446 420 445 420 445 420 446 420 445 420 445 420 445 421 445 419 446 420 445 423 446 1292 445 1290 445 1290 445 1292 444 1291 445 1293 445
|
||||||
|
#
|
||||||
|
name: Heat_hi
|
||||||
|
type: raw
|
||||||
|
frequency: 38000
|
||||||
|
duty_cycle: 0.330000
|
||||||
|
data: 3463 1751 443 424 445 1293 443 422 444 420 445 421 444 421 444 422 444 421 444 421 444 421 444 353 513 422 443 425 444 1293 444 422 444 421 444 420 445 421 444 422 444 421 444 425 444 1291 444 1294 443 1293 444 421 444 424 445 1294 444 420 445 421 444 416 449 422 444 421 444 421 444 421 444 422 444 420 445 421 444 422 443 422 444 421 444 421 444 420 445 373 493 420 445 421 444 420 445 422 444 420 445 422 443 421 444 422 444 421 444 421 444 422 443 421 445 421 444 424 445 1291 444 1294 444 421 444 420 445 420 445 422 444 424 444 9991 3463 1751 445 425 444 1293 444 422 444 421 444 420 445 421 444 423 443 421 444 421 444 421 444 422 444 421 444 424 445 1293 444 422 444 420 445 422 443 421 444 424 442 421 444 425 444 1292 443 1292 445 1292 445 421 444 424 445 1293 445 421 444 421 444 421 444 422 444 421 444 421 444 422 443 422 444 423 442 421 444 423 442 423 443 425 443 1293 444 423 442 421 444 1292 444 422 443 425 443 1295 443 421 444 368 549 373 443 1293 496 1240 495 1240 495 1241 496 371 495 370 495 370 495 370 495 371 495 370 495 369 496 371 494 371 494 1240 442 1293 495 1240 496 1241 496 1240 497 373 496 1241 496 371 494 1184 553 370 495 370 495 370 496 370 443 423 494 370 443 424 495 370 442 423 442 422 443 424 442 423 442 423 442 425 440 424 442 424 441 427 442 1294 441 1294 443 1294 443 424 441 423 442 425 442 421 444 423 442 424 441 425 441 424 441 426 443 1293 442 1295 442 1295 441 424 441 394 471 425 441 423 442 423 442 424 441 424 442 424 441 423 442 423 442 424 442 423 442 424 441 423 442 424 442 428 440 1295 442 424 441 425 441 424 441 424 441 423 442 424 441 1294 443 424 441 424 441 424 442 423 442 423 442 423 442 425 441 424 441 424 441 424 441 424 442 424 441 449 416 423 442 424 442 427 442 1295 442 424 441 424 441 1295 442 427 442 1295 441 425 441 426 441
|
||||||
|
#
|
||||||
|
name: Heat_lo
|
||||||
|
type: raw
|
||||||
|
frequency: 38000
|
||||||
|
duty_cycle: 0.330000
|
||||||
|
data: 3461 1753 439 429 440 1298 439 427 439 426 439 426 439 426 439 427 439 426 439 426 439 426 439 427 439 426 439 430 438 1298 438 428 438 426 439 427 438 427 438 427 439 427 438 430 414 1321 439 1299 413 1323 414 451 414 455 414 1324 414 452 413 451 414 453 413 436 430 452 413 452 413 451 414 453 413 452 413 451 414 452 413 454 412 452 413 452 413 452 413 453 413 452 413 452 413 452 413 454 413 453 412 452 413 452 413 453 413 452 413 452 413 452 413 453 413 452 413 456 412 1323 412 1325 413 452 413 453 412 453 412 454 412 455 390 10044 3438 1779 412 457 412 1324 413 454 412 453 412 453 412 453 412 454 412 453 412 453 412 454 411 455 411 453 412 457 389 1347 390 477 389 476 389 476 389 476 389 402 465 475 390 479 390 1346 389 1348 389 1347 390 476 389 479 390 1348 390 476 389 476 389 476 389 477 389 475 390 476 389 476 389 477 389 476 389 476 389 476 389 478 388 480 389 1347 390 475 390 476 389 1347 390 476 389 479 389 1349 389 476 389 476 389 476 389 477 389 476 389 479 390 1348 389 477 389 476 389 476 389 476 389 476 390 476 389 476 389 476 389 476 389 1346 389 1347 389 1346 389 1347 390 1346 390 479 390 1347 390 476 389 1374 363 474 415 454 388 480 386 475 414 454 387 474 391 476 414 455 387 473 392 476 414 453 389 476 389 475 390 475 390 476 390 476 390 479 389 1346 389 1347 390 1348 389 476 389 476 389 477 389 476 389 476 389 475 390 477 389 440 425 479 389 1345 390 1347 390 1347 390 476 389 475 390 477 389 476 389 476 389 476 389 477 389 476 389 476 389 475 390 477 389 476 389 476 389 476 389 477 389 479 390 1347 390 476 389 477 389 476 389 476 389 476 389 476 389 1347 390 476 389 476 389 477 389 503 362 476 389 476 389 477 389 475 390 476 389 476 389 477 389 476 389 476 389 476 389 477 389 479 389 1347 390 479 390 1347 390 1348 389 476 389 476 389 477 389 477 390
|
||||||
|
#
|
||||||
|
name: Off
|
||||||
|
type: raw
|
||||||
|
frequency: 38000
|
||||||
|
duty_cycle: 0.330000
|
||||||
|
data: 3489 1725 493 375 493 1167 569 375 491 373 492 372 493 374 492 373 493 372 493 373 442 423 492 375 493 372 492 378 490 1245 443 423 493 374 491 375 490 374 442 424 492 374 491 379 440 1293 492 1245 443 1294 442 423 491 379 441 1296 491 375 441 423 442 423 442 425 441 423 442 425 440 424 441 425 441 424 441 421 444 424 441 425 441 423 442 423 442 423 442 453 413 424 442 451 414 424 441 426 440 424 441 424 441 424 441 425 441 424 441 424 441 424 441 426 440 425 440 427 442 1322 413 1296 442 424 441 423 442 424 441 424 442 427 441 9994 3463 1755 440 428 441 1296 441 426 440 425 440 426 439 425 440 426 440 423 442 425 440 425 440 372 494 425 440 428 440 1297 440 426 440 425 440 426 439 426 439 427 439 425 440 429 439 1297 439 1297 439 1297 440 426 439 429 440 1299 439 426 439 426 439 428 437 427 439 426 439 426 439 427 438 427 439 427 438 427 438 427 438 429 437 427 414 451 414 451 414 418 447 1323 414 452 413 455 414 1325 413 452 413 452 413 452 413 453 413 452 413 456 413 1324 413 453 413 452 413 453 412 452 413 454 412 453 412 453 412 453 412 452 413 1324 412 1323 412 1324 412 1324 412 1325 412 456 413 1324 412 404 461 1325 412 453 412 453 412 454 412 453 412 453 412 454 411 455 411 453 412 453 412 453 412 454 412 453 412 453 412 453 412 454 413 453 412 457 412 1323 413 1324 413 1324 412 453 412 453 412 454 413 453 412 453 412 453 412 454 412 452 413 456 412 1323 413 1324 413 1323 414 453 412 452 413 453 413 452 413 452 413 452 413 453 413 450 415 452 413 452 413 453 413 452 413 451 439 427 438 428 439 430 439 1298 439 426 439 428 438 426 439 428 437 426 439 427 438 1298 439 427 438 426 439 427 439 426 439 426 439 425 440 427 440 426 439 426 439 426 439 425 441 426 439 425 440 425 440 427 439 426 439 425 440 429 439 1297 440 1297 440 425 440 425 440 426 441 427 440
|
||||||
|
#
|
||||||
|
# Model: Maytag M6X06F2A
|
||||||
|
#
|
||||||
|
name: Off
|
||||||
|
type: parsed
|
||||||
|
protocol: NEC
|
||||||
|
address: 20 00 00 00
|
||||||
|
command: 02 00 00 00
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
Filetype: IR library file
|
Filetype: IR library file
|
||||||
Version: 1
|
Version: 1
|
||||||
#
|
#
|
||||||
# Model: NoName Unknown Audio remote
|
# Model: Yamaha RAV15 and NoName Unknown Audio remote
|
||||||
name: Play
|
name: Play
|
||||||
type: parsed
|
type: parsed
|
||||||
protocol: NEC
|
protocol: NEC
|
||||||
@@ -69,37 +69,6 @@ protocol: NECext
|
|||||||
address: 84 79 00 00
|
address: 84 79 00 00
|
||||||
command: 02 FD 00 00
|
command: 02 FD 00 00
|
||||||
#
|
#
|
||||||
# Model: Yamaha RAV15
|
|
||||||
name: Play
|
|
||||||
type: parsed
|
|
||||||
protocol: NEC
|
|
||||||
address: 00 00 00 00
|
|
||||||
command: 43 00 00 00
|
|
||||||
#
|
|
||||||
name: Vol_up
|
|
||||||
type: parsed
|
|
||||||
protocol: NEC
|
|
||||||
address: 00 00 00 00
|
|
||||||
command: 15 00 00 00
|
|
||||||
#
|
|
||||||
name: Vol_dn
|
|
||||||
type: parsed
|
|
||||||
protocol: NEC
|
|
||||||
address: 00 00 00 00
|
|
||||||
command: 07 00 00 00
|
|
||||||
#
|
|
||||||
name: Next
|
|
||||||
type: parsed
|
|
||||||
protocol: NEC
|
|
||||||
address: 00 00 00 00
|
|
||||||
command: 40 00 00 00
|
|
||||||
#
|
|
||||||
name: Prev
|
|
||||||
type: parsed
|
|
||||||
protocol: NEC
|
|
||||||
address: 00 00 00 00
|
|
||||||
command: 44 00 00 00
|
|
||||||
#
|
|
||||||
# Model: Yamaha RX-V375
|
# Model: Yamaha RX-V375
|
||||||
name: Power
|
name: Power
|
||||||
type: parsed
|
type: parsed
|
||||||
@@ -476,7 +445,7 @@ protocol: NEC
|
|||||||
address: 00 00 00 00
|
address: 00 00 00 00
|
||||||
command: 85 00 00 00
|
command: 85 00 00 00
|
||||||
#
|
#
|
||||||
#Sony audio remote RM-SC3
|
# Sony audio remote RM-SC3
|
||||||
name: Power
|
name: Power
|
||||||
type: parsed
|
type: parsed
|
||||||
protocol: SIRC
|
protocol: SIRC
|
||||||
@@ -506,3 +475,95 @@ type: parsed
|
|||||||
protocol: SIRC20
|
protocol: SIRC20
|
||||||
address: 3A 07 00 00
|
address: 3A 07 00 00
|
||||||
command: 39 00 00 00
|
command: 39 00 00 00
|
||||||
|
#
|
||||||
|
# Model: Sony MHC_GSX75
|
||||||
|
#
|
||||||
|
name: Pause
|
||||||
|
type: parsed
|
||||||
|
protocol: SIRC20
|
||||||
|
address: 3A 07 00 00
|
||||||
|
command: 38 00 00 00
|
||||||
|
#
|
||||||
|
# Model: Elac EA101EQ-G
|
||||||
|
#
|
||||||
|
name: Vol_up
|
||||||
|
type: parsed
|
||||||
|
protocol: NEC
|
||||||
|
address: 00 00 00 00
|
||||||
|
command: 46 00 00 00
|
||||||
|
#
|
||||||
|
name: Vol_dn
|
||||||
|
type: parsed
|
||||||
|
protocol: NEC
|
||||||
|
address: 00 00 00 00
|
||||||
|
command: 16 00 00 00
|
||||||
|
#
|
||||||
|
name: Power
|
||||||
|
type: parsed
|
||||||
|
protocol: NEC
|
||||||
|
address: 00 00 00 00
|
||||||
|
command: 18 00 00 00
|
||||||
|
#
|
||||||
|
name: Mute
|
||||||
|
type: parsed
|
||||||
|
protocol: NEC
|
||||||
|
address: 00 00 00 00
|
||||||
|
command: 55 00 00 00
|
||||||
|
#
|
||||||
|
# Model: Philips FW750C
|
||||||
|
#
|
||||||
|
name: Vol_up
|
||||||
|
type: parsed
|
||||||
|
protocol: RC5
|
||||||
|
address: 10 00 00 00
|
||||||
|
command: 10 00 00 00
|
||||||
|
#
|
||||||
|
name: Vol_dn
|
||||||
|
type: parsed
|
||||||
|
protocol: RC5
|
||||||
|
address: 10 00 00 00
|
||||||
|
command: 11 00 00 00
|
||||||
|
#
|
||||||
|
name: Play
|
||||||
|
type: parsed
|
||||||
|
protocol: RC5
|
||||||
|
address: 14 00 00 00
|
||||||
|
command: 35 00 00 00
|
||||||
|
#
|
||||||
|
name: Pause
|
||||||
|
type: parsed
|
||||||
|
protocol: RC5
|
||||||
|
address: 14 00 00 00
|
||||||
|
command: 36 00 00 00
|
||||||
|
#
|
||||||
|
name: Mute
|
||||||
|
type: parsed
|
||||||
|
protocol: RC5
|
||||||
|
address: 10 00 00 00
|
||||||
|
command: 0D 00 00 00
|
||||||
|
#
|
||||||
|
name: Power
|
||||||
|
type: parsed
|
||||||
|
protocol: RC5
|
||||||
|
address: 14 00 00 00
|
||||||
|
command: 0C 00 00 00
|
||||||
|
#
|
||||||
|
# Model: Pioneer VSX-D1-S
|
||||||
|
#
|
||||||
|
name: Vol_up
|
||||||
|
type: parsed
|
||||||
|
protocol: NECext
|
||||||
|
address: 00 7F 00 00
|
||||||
|
command: 0A F5 00 00
|
||||||
|
#
|
||||||
|
name: Mute
|
||||||
|
type: parsed
|
||||||
|
protocol: NECext
|
||||||
|
address: 00 7F 00 00
|
||||||
|
command: 5C A3 00 00
|
||||||
|
#
|
||||||
|
name: Power
|
||||||
|
type: parsed
|
||||||
|
protocol: NECext
|
||||||
|
address: 00 7F 00 00
|
||||||
|
command: 1E E1 00 00
|
||||||
|
|||||||
@@ -360,12 +360,6 @@ protocol: NECext
|
|||||||
address: 33 00 00 00
|
address: 33 00 00 00
|
||||||
command: 0B F4 00 00
|
command: 0B F4 00 00
|
||||||
#
|
#
|
||||||
name: Power
|
|
||||||
type: parsed
|
|
||||||
protocol: NECext
|
|
||||||
address: 83 55 00 00
|
|
||||||
command: 90 6F 00 00
|
|
||||||
#
|
|
||||||
name: Vol_dn
|
name: Vol_dn
|
||||||
type: parsed
|
type: parsed
|
||||||
protocol: NECext
|
protocol: NECext
|
||||||
@@ -738,12 +732,6 @@ protocol: NEC
|
|||||||
address: 00 00 00 00
|
address: 00 00 00 00
|
||||||
command: 8C 00 00 00
|
command: 8C 00 00 00
|
||||||
#
|
#
|
||||||
name: Power
|
|
||||||
type: parsed
|
|
||||||
protocol: NECext
|
|
||||||
address: 87 45 00 00
|
|
||||||
command: 17 E8 00 00
|
|
||||||
#
|
|
||||||
name: Vol_up
|
name: Vol_up
|
||||||
type: raw
|
type: raw
|
||||||
frequency: 38000
|
frequency: 38000
|
||||||
|
|||||||
@@ -1687,14 +1687,6 @@ protocol: NEC
|
|||||||
address: 40 00 00 00
|
address: 40 00 00 00
|
||||||
command: 10 00 00 00
|
command: 10 00 00 00
|
||||||
#
|
#
|
||||||
# Philips OLED 934/12
|
|
||||||
#
|
|
||||||
name: Power
|
|
||||||
type: parsed
|
|
||||||
protocol: RC6
|
|
||||||
address: 00 00 00 00
|
|
||||||
command: 0C 00 00 00
|
|
||||||
#
|
|
||||||
name: Mute
|
name: Mute
|
||||||
type: parsed
|
type: parsed
|
||||||
protocol: RC6
|
protocol: RC6
|
||||||
@@ -1725,44 +1717,6 @@ protocol: RC6
|
|||||||
address: 00 00 00 00
|
address: 00 00 00 00
|
||||||
command: 21 00 00 00
|
command: 21 00 00 00
|
||||||
#
|
#
|
||||||
# Model TCL 50P715X1
|
|
||||||
#
|
|
||||||
name: Power
|
|
||||||
type: parsed
|
|
||||||
protocol: RCA
|
|
||||||
address: 0F 00 00 00
|
|
||||||
command: 54 00 00 00
|
|
||||||
#
|
|
||||||
name: Mute
|
|
||||||
type: parsed
|
|
||||||
protocol: RCA
|
|
||||||
address: 0F 00 00 00
|
|
||||||
command: FC 00 00 00
|
|
||||||
#
|
|
||||||
name: Vol_up
|
|
||||||
type: parsed
|
|
||||||
protocol: RCA
|
|
||||||
address: 0F 00 00 00
|
|
||||||
command: F4 00 00 00
|
|
||||||
#
|
|
||||||
name: Vol_dn
|
|
||||||
type: parsed
|
|
||||||
protocol: RCA
|
|
||||||
address: 0F 00 00 00
|
|
||||||
command: 74 00 00 00
|
|
||||||
#
|
|
||||||
name: Ch_next
|
|
||||||
type: parsed
|
|
||||||
protocol: RCA
|
|
||||||
address: 0F 00 00 00
|
|
||||||
command: B4 00 00 00
|
|
||||||
#
|
|
||||||
name: Ch_prev
|
|
||||||
type: parsed
|
|
||||||
protocol: RCA
|
|
||||||
address: 0F 00 00 00
|
|
||||||
command: 34 00 00 00
|
|
||||||
#
|
|
||||||
# Model: JTC Genesis 5.5
|
# Model: JTC Genesis 5.5
|
||||||
#
|
#
|
||||||
name: Mute
|
name: Mute
|
||||||
@@ -1829,26 +1783,6 @@ protocol: NEC
|
|||||||
address: 01 00 00 00
|
address: 01 00 00 00
|
||||||
command: 17 00 00 00
|
command: 17 00 00 00
|
||||||
#
|
#
|
||||||
# Visio TV
|
|
||||||
#
|
|
||||||
name: Power
|
|
||||||
type: parsed
|
|
||||||
protocol: NEC
|
|
||||||
address: 04 00 00 00
|
|
||||||
command: 08 00 00 00
|
|
||||||
#
|
|
||||||
name: Vol_up
|
|
||||||
type: parsed
|
|
||||||
protocol: NEC
|
|
||||||
address: 04 00 00 00
|
|
||||||
command: 02 00 00 00
|
|
||||||
#
|
|
||||||
name: Vol_dn
|
|
||||||
type: parsed
|
|
||||||
protocol: NEC
|
|
||||||
address: 04 00 00 00
|
|
||||||
command: 03 00 00 00
|
|
||||||
#
|
|
||||||
name: Ch_next
|
name: Ch_next
|
||||||
type: parsed
|
type: parsed
|
||||||
protocol: NEC
|
protocol: NEC
|
||||||
@@ -1861,12 +1795,6 @@ protocol: NEC
|
|||||||
address: 04 00 00 00
|
address: 04 00 00 00
|
||||||
command: 01 00 00 00
|
command: 01 00 00 00
|
||||||
#
|
#
|
||||||
name: Mute
|
|
||||||
type: parsed
|
|
||||||
protocol: NEC
|
|
||||||
address: 04 00 00 00
|
|
||||||
command: 09 00 00 00
|
|
||||||
#
|
|
||||||
# Emerson TV
|
# Emerson TV
|
||||||
#
|
#
|
||||||
name: Power
|
name: Power
|
||||||
@@ -1942,4 +1870,111 @@ type: parsed
|
|||||||
protocol: NECext
|
protocol: NECext
|
||||||
address: EA C7 00 00
|
address: EA C7 00 00
|
||||||
command: 33 CC 00 00
|
command: 33 CC 00 00
|
||||||
|
#
|
||||||
|
# Model: Soniq E32W13B
|
||||||
|
#
|
||||||
|
name: Power
|
||||||
|
type: parsed
|
||||||
|
protocol: NECext
|
||||||
|
address: 72 DD 00 00
|
||||||
|
command: 0E F1 00 00
|
||||||
|
#
|
||||||
|
name: Mute
|
||||||
|
type: parsed
|
||||||
|
protocol: NECext
|
||||||
|
address: 72 DD 00 00
|
||||||
|
command: 1A E5 00 00
|
||||||
|
#
|
||||||
|
name: Vol_up
|
||||||
|
type: parsed
|
||||||
|
protocol: NECext
|
||||||
|
address: 72 DD 00 00
|
||||||
|
command: 49 B6 00 00
|
||||||
|
#
|
||||||
|
name: Vol_dn
|
||||||
|
type: parsed
|
||||||
|
protocol: NECext
|
||||||
|
address: 72 DD 00 00
|
||||||
|
command: 43 BC 00 00
|
||||||
|
#
|
||||||
|
name: Ch_next
|
||||||
|
type: parsed
|
||||||
|
protocol: NECext
|
||||||
|
address: 72 DD 00 00
|
||||||
|
command: 51 AE 00 00
|
||||||
|
#
|
||||||
|
name: Ch_prev
|
||||||
|
type: parsed
|
||||||
|
protocol: NECext
|
||||||
|
address: 72 DD 00 00
|
||||||
|
command: 4D B2 00 00
|
||||||
|
#
|
||||||
|
# Model: Hisense EN2B27
|
||||||
|
#
|
||||||
|
name: Power
|
||||||
|
type: parsed
|
||||||
|
protocol: NECext
|
||||||
|
address: 00 BF 00 00
|
||||||
|
command: 0D F2 00 00
|
||||||
|
#
|
||||||
|
name: Mute
|
||||||
|
type: parsed
|
||||||
|
protocol: NECext
|
||||||
|
address: 00 BF 00 00
|
||||||
|
command: 0E F1 00 00
|
||||||
|
#
|
||||||
|
name: Vol_up
|
||||||
|
type: parsed
|
||||||
|
protocol: NECext
|
||||||
|
address: 00 BF 00 00
|
||||||
|
command: 44 BB 00 00
|
||||||
|
#
|
||||||
|
name: Vol_dn
|
||||||
|
type: parsed
|
||||||
|
protocol: NECext
|
||||||
|
address: 00 BF 00 00
|
||||||
|
command: 43 BC 00 00
|
||||||
|
#
|
||||||
|
name: Ch_next
|
||||||
|
type: parsed
|
||||||
|
protocol: NECext
|
||||||
|
address: 00 BF 00 00
|
||||||
|
command: 4A B5 00 00
|
||||||
|
#
|
||||||
|
name: Ch_prev
|
||||||
|
type: parsed
|
||||||
|
protocol: NECext
|
||||||
|
address: 00 BF 00 00
|
||||||
|
command: 4B B4 00 00
|
||||||
|
#
|
||||||
|
# Model: Viano STV65UHD4K
|
||||||
|
#
|
||||||
|
name: Power
|
||||||
|
type: parsed
|
||||||
|
protocol: NECext
|
||||||
|
address: 00 7F 00 00
|
||||||
|
command: 1E E1 00 00
|
||||||
|
#
|
||||||
|
name: Vol_up
|
||||||
|
type: parsed
|
||||||
|
protocol: NECext
|
||||||
|
address: 00 7F 00 00
|
||||||
|
command: 0A F5 00 00
|
||||||
|
#
|
||||||
|
name: Vol_dn
|
||||||
|
type: parsed
|
||||||
|
protocol: NECext
|
||||||
|
address: 00 7F 00 00
|
||||||
|
command: 06 F9 00 00
|
||||||
|
#
|
||||||
|
name: Ch_next
|
||||||
|
type: parsed
|
||||||
|
protocol: NECext
|
||||||
|
address: 00 7F 00 00
|
||||||
|
command: 5B A4 00 00
|
||||||
|
#
|
||||||
|
name: Ch_prev
|
||||||
|
type: parsed
|
||||||
|
protocol: NECext
|
||||||
|
address: 00 7F 00 00
|
||||||
|
command: 18 E7 00 00
|
||||||
|
|||||||
@@ -2,6 +2,20 @@
|
|||||||
|
|
||||||
#define TAG "Mosgortrans"
|
#define TAG "Mosgortrans"
|
||||||
|
|
||||||
|
void render_section_header(
|
||||||
|
FuriString* str,
|
||||||
|
const char* name,
|
||||||
|
uint8_t prefix_separator_cnt,
|
||||||
|
uint8_t suffix_separator_cnt) {
|
||||||
|
for(uint8_t i = 0; i < prefix_separator_cnt; i++) {
|
||||||
|
furi_string_cat_printf(str, ":");
|
||||||
|
}
|
||||||
|
furi_string_cat_printf(str, "[ %s ]", name);
|
||||||
|
for(uint8_t i = 0; i < suffix_separator_cnt; i++) {
|
||||||
|
furi_string_cat_printf(str, ":");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void from_days_to_datetime(uint32_t days, DateTime* datetime, uint16_t start_year) {
|
void from_days_to_datetime(uint32_t days, DateTime* datetime, uint16_t start_year) {
|
||||||
uint32_t timestamp = days * 24 * 60 * 60;
|
uint32_t timestamp = days * 24 * 60 * 60;
|
||||||
DateTime start_datetime = {0};
|
DateTime start_datetime = {0};
|
||||||
|
|||||||
@@ -10,6 +10,11 @@
|
|||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
void render_section_header(
|
||||||
|
FuriString* str,
|
||||||
|
const char* name,
|
||||||
|
uint8_t prefix_separator_cnt,
|
||||||
|
uint8_t suffix_separator_cnt);
|
||||||
bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString* result);
|
bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString* result);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
|||||||
@@ -15,4 +15,11 @@ static constexpr auto nfc_app_api_table = sort(create_array_t<sym_entry>(
|
|||||||
API_METHOD(
|
API_METHOD(
|
||||||
mosgortrans_parse_transport_block,
|
mosgortrans_parse_transport_block,
|
||||||
bool,
|
bool,
|
||||||
(const MfClassicBlock* block, FuriString* result))));
|
(const MfClassicBlock* block, FuriString* result)),
|
||||||
|
API_METHOD(
|
||||||
|
render_section_header,
|
||||||
|
void,
|
||||||
|
(FuriString * str,
|
||||||
|
const char* name,
|
||||||
|
uint8_t prefix_separator_cnt,
|
||||||
|
uint8_t suffix_separator_cnt))));
|
||||||
|
|||||||
@@ -92,6 +92,15 @@ App(
|
|||||||
sources=["plugins/supported_cards/troika.c"],
|
sources=["plugins/supported_cards/troika.c"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
App(
|
||||||
|
appid="social_moscow_parser",
|
||||||
|
apptype=FlipperAppType.PLUGIN,
|
||||||
|
entry_point="social_moscow_plugin_ep",
|
||||||
|
targets=["f7"],
|
||||||
|
requires=["nfc"],
|
||||||
|
sources=["plugins/supported_cards/social_moscow.c"],
|
||||||
|
)
|
||||||
|
|
||||||
App(
|
App(
|
||||||
appid="washcity_parser",
|
appid="washcity_parser",
|
||||||
apptype=FlipperAppType.PLUGIN,
|
apptype=FlipperAppType.PLUGIN,
|
||||||
|
|||||||
@@ -559,6 +559,7 @@ static void nfc_protocol_support_scene_save_name_on_exit(NfcApp* instance) {
|
|||||||
*/
|
*/
|
||||||
enum {
|
enum {
|
||||||
NfcSceneEmulateStateWidget, /**< Widget view is displayed. */
|
NfcSceneEmulateStateWidget, /**< Widget view is displayed. */
|
||||||
|
NfcSceneEmulateStateWidgetLog, /**< Widget view with Log button is displayed */
|
||||||
NfcSceneEmulateStateTextBox, /**< TextBox view is displayed. */
|
NfcSceneEmulateStateTextBox, /**< TextBox view is displayed. */
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -633,12 +634,14 @@ static bool
|
|||||||
"Log",
|
"Log",
|
||||||
nfc_protocol_support_common_widget_callback,
|
nfc_protocol_support_common_widget_callback,
|
||||||
instance);
|
instance);
|
||||||
|
scene_manager_set_scene_state(
|
||||||
|
instance->scene_manager, NfcSceneEmulate, NfcSceneEmulateStateWidgetLog);
|
||||||
}
|
}
|
||||||
// Update TextBox data
|
// Update TextBox data
|
||||||
text_box_set_text(instance->text_box, furi_string_get_cstr(instance->text_box_store));
|
text_box_set_text(instance->text_box, furi_string_get_cstr(instance->text_box_store));
|
||||||
consumed = true;
|
consumed = true;
|
||||||
} else if(event.event == GuiButtonTypeCenter) {
|
} else if(event.event == GuiButtonTypeCenter) {
|
||||||
if(state == NfcSceneEmulateStateWidget) {
|
if(state == NfcSceneEmulateStateWidgetLog) {
|
||||||
view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewTextBox);
|
view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewTextBox);
|
||||||
scene_manager_set_scene_state(
|
scene_manager_set_scene_state(
|
||||||
instance->scene_manager, NfcSceneEmulate, NfcSceneEmulateStateTextBox);
|
instance->scene_manager, NfcSceneEmulate, NfcSceneEmulateStateTextBox);
|
||||||
@@ -649,7 +652,7 @@ static bool
|
|||||||
if(state == NfcSceneEmulateStateTextBox) {
|
if(state == NfcSceneEmulateStateTextBox) {
|
||||||
view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget);
|
view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget);
|
||||||
scene_manager_set_scene_state(
|
scene_manager_set_scene_state(
|
||||||
instance->scene_manager, NfcSceneEmulate, NfcSceneEmulateStateWidget);
|
instance->scene_manager, NfcSceneEmulate, NfcSceneEmulateStateWidgetLog);
|
||||||
consumed = true;
|
consumed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,21 @@
|
|||||||
|
|
||||||
#include <nfc/nfc_device.h>
|
#include <nfc/nfc_device.h>
|
||||||
#include <bit_lib/bit_lib.h>
|
#include <bit_lib/bit_lib.h>
|
||||||
|
#include <datetime.h>
|
||||||
#include <nfc/protocols/mf_classic/mf_classic_poller_sync.h>
|
#include <nfc/protocols/mf_classic/mf_classic_poller_sync.h>
|
||||||
|
|
||||||
#define TAG "Plantain"
|
#define TAG "Plantain"
|
||||||
|
|
||||||
|
void from_minutes_to_datetime(uint32_t minutes, DateTime* datetime, uint16_t start_year) {
|
||||||
|
uint32_t timestamp = minutes * 60;
|
||||||
|
DateTime start_datetime = {0};
|
||||||
|
start_datetime.year = start_year - 1;
|
||||||
|
start_datetime.month = 12;
|
||||||
|
start_datetime.day = 31;
|
||||||
|
timestamp += datetime_datetime_to_timestamp(&start_datetime);
|
||||||
|
datetime_timestamp_to_datetime(timestamp, datetime);
|
||||||
|
}
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint64_t a;
|
uint64_t a;
|
||||||
uint64_t b;
|
uint64_t b;
|
||||||
@@ -208,29 +219,92 @@ static bool plantain_parse(const NfcDevice* device, FuriString* parsed_data) {
|
|||||||
bit_lib_bytes_to_num_be(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data));
|
bit_lib_bytes_to_num_be(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data));
|
||||||
if(key != cfg.keys[cfg.data_sector].a) break;
|
if(key != cfg.keys[cfg.data_sector].a) break;
|
||||||
|
|
||||||
// Point to block 0 of sector 4, value 0
|
furi_string_printf(parsed_data, "\e#Plantain card\n");
|
||||||
const uint8_t* temp_ptr = data->block[16].data;
|
|
||||||
// Read first 4 bytes of block 0 of sector 4 from last to first and convert them to uint32_t
|
|
||||||
// 38 18 00 00 becomes 00 00 18 38, and equals to 6200 decimal
|
|
||||||
uint32_t balance =
|
|
||||||
((temp_ptr[3] << 24) | (temp_ptr[2] << 16) | (temp_ptr[1] << 8) | temp_ptr[0]) / 100;
|
|
||||||
// Read card number
|
|
||||||
// Point to block 0 of sector 0, value 0
|
|
||||||
temp_ptr = data->block[0].data;
|
|
||||||
// Read first 7 bytes of block 0 of sector 0 from last to first and convert them to uint64_t
|
|
||||||
// 04 31 16 8A 23 5C 80 becomes 80 5C 23 8A 16 31 04, and equals to 36130104729284868 decimal
|
|
||||||
uint8_t card_number_arr[7];
|
|
||||||
for(size_t i = 0; i < 7; i++) {
|
|
||||||
card_number_arr[i] = temp_ptr[6 - i];
|
|
||||||
}
|
|
||||||
// Copy card number to uint64_t
|
|
||||||
uint64_t card_number = 0;
|
uint64_t card_number = 0;
|
||||||
for(size_t i = 0; i < 7; i++) {
|
for(size_t i = 0; i < 7; i++) {
|
||||||
card_number = (card_number << 8) | card_number_arr[i];
|
card_number = (card_number << 8) | data->block[0].data[6 - i];
|
||||||
}
|
}
|
||||||
|
|
||||||
furi_string_printf(
|
// Print card number with 4-digit groups
|
||||||
parsed_data, "\e#Plantain\nNo.: %lluX\nBalance: %lu\n", card_number, balance);
|
furi_string_cat_printf(parsed_data, "Number: ");
|
||||||
|
FuriString* card_number_s = furi_string_alloc();
|
||||||
|
furi_string_cat_printf(card_number_s, "%llu", card_number);
|
||||||
|
FuriString* tmp_s = furi_string_alloc_set_str("9643 3078 ");
|
||||||
|
for(uint8_t i = 0; i < 24; i += 4) {
|
||||||
|
for(uint8_t j = 0; j < 4; j++) {
|
||||||
|
furi_string_push_back(tmp_s, furi_string_get_char(card_number_s, i + j));
|
||||||
|
}
|
||||||
|
furi_string_push_back(tmp_s, ' ');
|
||||||
|
}
|
||||||
|
furi_string_cat_printf(parsed_data, "%s\n", furi_string_get_cstr(tmp_s));
|
||||||
|
if(data->type == MfClassicType1k) {
|
||||||
|
//balance
|
||||||
|
uint32_t balance = 0;
|
||||||
|
for(uint8_t i = 0; i < 4; i++) {
|
||||||
|
balance = (balance << 8) | data->block[16].data[3 - i];
|
||||||
|
}
|
||||||
|
furi_string_cat_printf(parsed_data, "Balance: %ld rub\n", balance / 100);
|
||||||
|
|
||||||
|
//trips
|
||||||
|
uint8_t trips_metro = data->block[21].data[0];
|
||||||
|
uint8_t trips_ground = data->block[21].data[1];
|
||||||
|
furi_string_cat_printf(parsed_data, "Trips: %d\n", trips_metro + trips_ground);
|
||||||
|
//trip time
|
||||||
|
uint32_t last_trip_timestamp = 0;
|
||||||
|
for(uint8_t i = 0; i < 3; i++) {
|
||||||
|
last_trip_timestamp = (last_trip_timestamp << 8) | data->block[21].data[4 - i];
|
||||||
|
}
|
||||||
|
DateTime last_trip = {0};
|
||||||
|
from_minutes_to_datetime(last_trip_timestamp + 24 * 60, &last_trip, 2010);
|
||||||
|
furi_string_cat_printf(
|
||||||
|
parsed_data,
|
||||||
|
"Trip start: %02d.%02d.%04d %02d:%02d\n",
|
||||||
|
last_trip.day,
|
||||||
|
last_trip.month,
|
||||||
|
last_trip.year,
|
||||||
|
last_trip.hour,
|
||||||
|
last_trip.minute);
|
||||||
|
//validator
|
||||||
|
uint16_t validator = (data->block[20].data[5] << 8) | data->block[20].data[4];
|
||||||
|
furi_string_cat_printf(parsed_data, "Validator: %d\n", validator);
|
||||||
|
//tariff
|
||||||
|
uint16_t fare = (data->block[20].data[7] << 8) | data->block[20].data[6];
|
||||||
|
furi_string_cat_printf(parsed_data, "Tariff: %d rub\n", fare / 100);
|
||||||
|
//trips in metro
|
||||||
|
furi_string_cat_printf(parsed_data, "Trips (Metro): %d\n", trips_metro);
|
||||||
|
//trips on ground
|
||||||
|
furi_string_cat_printf(parsed_data, "Trips (Ground): %d\n", trips_ground);
|
||||||
|
//last payment
|
||||||
|
uint32_t last_payment_timestamp = 0;
|
||||||
|
for(uint8_t i = 0; i < 3; i++) {
|
||||||
|
last_payment_timestamp = (last_payment_timestamp << 8) |
|
||||||
|
data->block[18].data[4 - i];
|
||||||
|
}
|
||||||
|
DateTime last_payment_date = {0};
|
||||||
|
from_minutes_to_datetime(last_payment_timestamp + 24 * 60, &last_payment_date, 2010);
|
||||||
|
furi_string_cat_printf(
|
||||||
|
parsed_data,
|
||||||
|
"Last pay: %02d.%02d.%04d %02d:%02d\n",
|
||||||
|
last_payment_date.day,
|
||||||
|
last_payment_date.month,
|
||||||
|
last_payment_date.year,
|
||||||
|
last_payment_date.hour,
|
||||||
|
last_payment_date.minute);
|
||||||
|
//payment summ
|
||||||
|
uint16_t last_payment = (data->block[18].data[9] << 8) | data->block[18].data[8];
|
||||||
|
furi_string_cat_printf(parsed_data, "Amount: %d rub", last_payment / 100);
|
||||||
|
furi_string_free(card_number_s);
|
||||||
|
furi_string_free(tmp_s);
|
||||||
|
} else if(data->type == MfClassicType4k) {
|
||||||
|
//trips
|
||||||
|
uint8_t trips_metro = data->block[36].data[0];
|
||||||
|
uint8_t trips_ground = data->block[36].data[1];
|
||||||
|
furi_string_cat_printf(parsed_data, "Trips: %d\n", trips_metro + trips_ground);
|
||||||
|
//trips in metro
|
||||||
|
furi_string_cat_printf(parsed_data, "Trips (Metro): %d\n", trips_metro);
|
||||||
|
//trips on ground
|
||||||
|
furi_string_cat_printf(parsed_data, "Trips (Ground): %d\n", trips_ground);
|
||||||
|
}
|
||||||
parsed = true;
|
parsed = true;
|
||||||
} while(false);
|
} while(false);
|
||||||
|
|
||||||
|
|||||||
301
applications/main/nfc/plugins/supported_cards/social_moscow.c
Normal file
301
applications/main/nfc/plugins/supported_cards/social_moscow.c
Normal file
@@ -0,0 +1,301 @@
|
|||||||
|
#include "nfc_supported_card_plugin.h"
|
||||||
|
#include <core/check.h>
|
||||||
|
|
||||||
|
#include <flipper_application/flipper_application.h>
|
||||||
|
|
||||||
|
#include <nfc/nfc_device.h>
|
||||||
|
#include <bit_lib/bit_lib.h>
|
||||||
|
#include <nfc/protocols/mf_classic/mf_classic_poller_sync.h>
|
||||||
|
#include "../../api/mosgortrans/mosgortrans_util.h"
|
||||||
|
#include "furi_hal_rtc.h"
|
||||||
|
|
||||||
|
#define TAG "Social_Moscow"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint64_t a;
|
||||||
|
uint64_t b;
|
||||||
|
} MfClassicKeyPair;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const MfClassicKeyPair* keys;
|
||||||
|
uint32_t data_sector;
|
||||||
|
} SocialMoscowCardConfig;
|
||||||
|
|
||||||
|
static const MfClassicKeyPair social_moscow_1k_keys[] = {
|
||||||
|
{.a = 0xa0a1a2a3a4a5, .b = 0x7de02a7f6025},
|
||||||
|
{.a = 0x2735fc181807, .b = 0xbf23a53c1f63},
|
||||||
|
{.a = 0x2aba9519f574, .b = 0xcb9a1f2d7368},
|
||||||
|
{.a = 0x84fd7f7a12b6, .b = 0xc7c0adb3284f},
|
||||||
|
{.a = 0x73068f118c13, .b = 0x2b7f3253fac5},
|
||||||
|
{.a = 0x186d8c4b93f9, .b = 0x9f131d8c2057},
|
||||||
|
{.a = 0x3a4bba8adaf0, .b = 0x67362d90f973},
|
||||||
|
{.a = 0x8765b17968a2, .b = 0x6202a38f69e2},
|
||||||
|
{.a = 0x40ead80721ce, .b = 0x100533b89331},
|
||||||
|
{.a = 0x0db5e6523f7c, .b = 0x653a87594079},
|
||||||
|
{.a = 0x51119dae5216, .b = 0xd8a274b2e026},
|
||||||
|
{.a = 0x51119dae5216, .b = 0xd8a274b2e026},
|
||||||
|
{.a = 0x51119dae5216, .b = 0xd8a274b2e026},
|
||||||
|
{.a = 0x2aba9519f574, .b = 0xcb9a1f2d7368},
|
||||||
|
{.a = 0x84fd7f7a12b6, .b = 0xc7c0adb3284f},
|
||||||
|
{.a = 0xa0a1a2a3a4a5, .b = 0x7de02a7f6025}};
|
||||||
|
|
||||||
|
static const MfClassicKeyPair social_moscow_4k_keys[] = {
|
||||||
|
{.a = 0xa0a1a2a3a4a5, .b = 0x7de02a7f6025}, //1
|
||||||
|
{.a = 0x2735fc181807, .b = 0xbf23a53c1f63}, //2
|
||||||
|
{.a = 0x2aba9519f574, .b = 0xcb9a1f2d7368}, //3
|
||||||
|
{.a = 0x84fd7f7a12b6, .b = 0xc7c0adb3284f}, //4
|
||||||
|
{.a = 0x73068f118c13, .b = 0x2b7f3253fac5}, //5
|
||||||
|
{.a = 0x186d8c4b93f9, .b = 0x9f131d8c2057}, //6
|
||||||
|
{.a = 0x3a4bba8adaf0, .b = 0x67362d90f973}, //7
|
||||||
|
{.a = 0x8765b17968a2, .b = 0x6202a38f69e2}, //8
|
||||||
|
{.a = 0x40ead80721ce, .b = 0x100533b89331}, //9
|
||||||
|
{.a = 0x0db5e6523f7c, .b = 0x653a87594079}, //10
|
||||||
|
{.a = 0x51119dae5216, .b = 0xd8a274b2e026}, //11
|
||||||
|
{.a = 0x51119dae5216, .b = 0xd8a274b2e026}, //12
|
||||||
|
{.a = 0x51119dae5216, .b = 0xd8a274b2e026}, //13
|
||||||
|
{.a = 0xa0a1a2a3a4a5, .b = 0x7de02a7f6025}, //14
|
||||||
|
{.a = 0xa0a1a2a3a4a5, .b = 0x7de02a7f6025}, //15
|
||||||
|
{.a = 0xa0a1a2a3a4a5, .b = 0x7de02a7f6025}, //16
|
||||||
|
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //17
|
||||||
|
{.a = 0x2aba9519f574, .b = 0xcb9a1f2d7368}, //18
|
||||||
|
{.a = 0x84fd7f7a12b6, .b = 0xc7c0adb3284f}, //19
|
||||||
|
{.a = 0x2aba9519f574, .b = 0xcb9a1f2d7368}, //20
|
||||||
|
{.a = 0x84fd7f7a12b6, .b = 0xc7c0adb3284f}, //21
|
||||||
|
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //22
|
||||||
|
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //23
|
||||||
|
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //24
|
||||||
|
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //25
|
||||||
|
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //26
|
||||||
|
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //27
|
||||||
|
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //28
|
||||||
|
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //29
|
||||||
|
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //30
|
||||||
|
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //31
|
||||||
|
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //32
|
||||||
|
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //33
|
||||||
|
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //34
|
||||||
|
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //35
|
||||||
|
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //36
|
||||||
|
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //37
|
||||||
|
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //38
|
||||||
|
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //39
|
||||||
|
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //40
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool social_moscow_get_card_config(SocialMoscowCardConfig* config, MfClassicType type) {
|
||||||
|
bool success = true;
|
||||||
|
if(type == MfClassicType1k) {
|
||||||
|
config->data_sector = 15;
|
||||||
|
config->keys = social_moscow_1k_keys;
|
||||||
|
} else if(type == MfClassicType4k) {
|
||||||
|
config->data_sector = 15;
|
||||||
|
config->keys = social_moscow_4k_keys;
|
||||||
|
} else {
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool social_moscow_verify_type(Nfc* nfc, MfClassicType type) {
|
||||||
|
bool verified = false;
|
||||||
|
|
||||||
|
do {
|
||||||
|
SocialMoscowCardConfig cfg = {};
|
||||||
|
if(!social_moscow_get_card_config(&cfg, type)) break;
|
||||||
|
|
||||||
|
const uint8_t block_num = mf_classic_get_first_block_num_of_sector(cfg.data_sector);
|
||||||
|
FURI_LOG_D(TAG, "Verifying sector %lu", cfg.data_sector);
|
||||||
|
|
||||||
|
MfClassicKey key = {0};
|
||||||
|
bit_lib_num_to_bytes_be(cfg.keys[cfg.data_sector].a, COUNT_OF(key.data), key.data);
|
||||||
|
|
||||||
|
MfClassicAuthContext auth_context;
|
||||||
|
MfClassicError error =
|
||||||
|
mf_classic_poller_sync_auth(nfc, block_num, &key, MfClassicKeyTypeA, &auth_context);
|
||||||
|
if(error != MfClassicErrorNone) {
|
||||||
|
FURI_LOG_D(TAG, "Failed to read block %u: %d", block_num, error);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
FURI_LOG_D(TAG, "Verify success!");
|
||||||
|
verified = true;
|
||||||
|
} while(false);
|
||||||
|
|
||||||
|
return verified;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool social_moscow_verify(Nfc* nfc) {
|
||||||
|
return social_moscow_verify_type(nfc, MfClassicType1k) ||
|
||||||
|
social_moscow_verify_type(nfc, MfClassicType4k);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool social_moscow_read(Nfc* nfc, NfcDevice* device) {
|
||||||
|
furi_assert(nfc);
|
||||||
|
furi_assert(device);
|
||||||
|
|
||||||
|
bool is_read = false;
|
||||||
|
|
||||||
|
MfClassicData* data = mf_classic_alloc();
|
||||||
|
nfc_device_copy_data(device, NfcProtocolMfClassic, data);
|
||||||
|
|
||||||
|
do {
|
||||||
|
MfClassicType type = MfClassicType4k;
|
||||||
|
MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type);
|
||||||
|
if(error != MfClassicErrorNone) break;
|
||||||
|
|
||||||
|
data->type = type;
|
||||||
|
SocialMoscowCardConfig cfg = {};
|
||||||
|
if(!social_moscow_get_card_config(&cfg, data->type)) break;
|
||||||
|
|
||||||
|
MfClassicDeviceKeys keys = {};
|
||||||
|
for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) {
|
||||||
|
bit_lib_num_to_bytes_be(cfg.keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data);
|
||||||
|
FURI_BIT_SET(keys.key_a_mask, i);
|
||||||
|
bit_lib_num_to_bytes_be(cfg.keys[i].b, sizeof(MfClassicKey), keys.key_b[i].data);
|
||||||
|
FURI_BIT_SET(keys.key_b_mask, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
error = mf_classic_poller_sync_read(nfc, &keys, data);
|
||||||
|
if(error == MfClassicErrorNotPresent) {
|
||||||
|
FURI_LOG_W(TAG, "Failed to read data");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
nfc_device_set_data(device, NfcProtocolMfClassic, data);
|
||||||
|
|
||||||
|
is_read = (error == MfClassicErrorNone);
|
||||||
|
} while(false);
|
||||||
|
|
||||||
|
mf_classic_free(data);
|
||||||
|
|
||||||
|
return is_read;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t calculate_luhn(uint64_t number) {
|
||||||
|
// https://en.wikipedia.org/wiki/Luhn_algorithm
|
||||||
|
// Drop existing check digit to form payload
|
||||||
|
uint64_t payload = number / 10;
|
||||||
|
int sum = 0;
|
||||||
|
int position = 0;
|
||||||
|
|
||||||
|
while(payload > 0) {
|
||||||
|
int digit = payload % 10;
|
||||||
|
if(position % 2 == 0) {
|
||||||
|
digit *= 2;
|
||||||
|
}
|
||||||
|
if(digit > 9) {
|
||||||
|
digit = (digit / 10) + (digit % 10);
|
||||||
|
}
|
||||||
|
sum += digit;
|
||||||
|
payload /= 10;
|
||||||
|
position++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (10 - (sum % 10)) % 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t hex_num(uint64_t hex) {
|
||||||
|
uint64_t result = 0;
|
||||||
|
for(uint8_t i = 0; i < 8; ++i) {
|
||||||
|
uint8_t half_byte = hex & 0x0F;
|
||||||
|
uint64_t num = 0;
|
||||||
|
for(uint8_t j = 0; j < 4; ++j) {
|
||||||
|
num += (half_byte & 0x1) * (1 << j);
|
||||||
|
half_byte = half_byte >> 1;
|
||||||
|
}
|
||||||
|
result += num * pow(10, i);
|
||||||
|
hex = hex >> 4;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool social_moscow_parse(const NfcDevice* device, FuriString* parsed_data) {
|
||||||
|
furi_assert(device);
|
||||||
|
|
||||||
|
const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic);
|
||||||
|
|
||||||
|
bool parsed = false;
|
||||||
|
|
||||||
|
do {
|
||||||
|
// Verify card type
|
||||||
|
SocialMoscowCardConfig cfg = {};
|
||||||
|
if(!social_moscow_get_card_config(&cfg, data->type)) break;
|
||||||
|
|
||||||
|
// Verify key
|
||||||
|
const MfClassicSectorTrailer* sec_tr =
|
||||||
|
mf_classic_get_sector_trailer_by_sector(data, cfg.data_sector);
|
||||||
|
|
||||||
|
const uint64_t key_a =
|
||||||
|
bit_lib_bytes_to_num_be(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data));
|
||||||
|
const uint64_t key_b =
|
||||||
|
bit_lib_bytes_to_num_be(sec_tr->key_b.data, COUNT_OF(sec_tr->key_b.data));
|
||||||
|
if((key_a != cfg.keys[cfg.data_sector].a) || (key_b != cfg.keys[cfg.data_sector].b)) break;
|
||||||
|
|
||||||
|
uint32_t card_code = bit_lib_get_bits_32(data->block[60].data, 8, 24);
|
||||||
|
uint8_t card_region = bit_lib_get_bits(data->block[60].data, 32, 8);
|
||||||
|
uint64_t card_number = bit_lib_get_bits_64(data->block[60].data, 40, 40);
|
||||||
|
uint8_t card_control = bit_lib_get_bits(data->block[60].data, 80, 4);
|
||||||
|
uint64_t omc_number = bit_lib_get_bits_64(data->block[21].data, 8, 64);
|
||||||
|
uint8_t year = data->block[60].data[11];
|
||||||
|
uint8_t month = data->block[60].data[12];
|
||||||
|
|
||||||
|
uint64_t number = hex_num(card_control) + hex_num(card_number) * 10 +
|
||||||
|
hex_num(card_region) * 10 * 10000000000 +
|
||||||
|
hex_num(card_code) * 10 * 10000000000 * 100;
|
||||||
|
|
||||||
|
uint8_t luhn = calculate_luhn(number);
|
||||||
|
if(luhn != card_control) break;
|
||||||
|
|
||||||
|
FuriString* metro_result = furi_string_alloc();
|
||||||
|
FuriString* ground_result = furi_string_alloc();
|
||||||
|
bool is_metro_data_present =
|
||||||
|
mosgortrans_parse_transport_block(&data->block[4], metro_result);
|
||||||
|
bool is_ground_data_present =
|
||||||
|
mosgortrans_parse_transport_block(&data->block[16], ground_result);
|
||||||
|
furi_string_cat_printf(
|
||||||
|
parsed_data,
|
||||||
|
"\e#Social \ecard\nNumber: %lx %x %llx %x\nOMC: %llx\nValid for: %02x/%02x %02x%02x\n",
|
||||||
|
card_code,
|
||||||
|
card_region,
|
||||||
|
card_number,
|
||||||
|
card_control,
|
||||||
|
omc_number,
|
||||||
|
month,
|
||||||
|
year,
|
||||||
|
data->block[60].data[13],
|
||||||
|
data->block[60].data[14]);
|
||||||
|
if(is_metro_data_present && !furi_string_empty(metro_result)) {
|
||||||
|
render_section_header(parsed_data, "Metro", 22, 21);
|
||||||
|
furi_string_cat_printf(parsed_data, "%s\n", furi_string_get_cstr(metro_result));
|
||||||
|
}
|
||||||
|
if(is_ground_data_present && !furi_string_empty(ground_result)) {
|
||||||
|
render_section_header(parsed_data, "Ground", 21, 20);
|
||||||
|
furi_string_cat_printf(parsed_data, "%s\n", furi_string_get_cstr(ground_result));
|
||||||
|
}
|
||||||
|
furi_string_free(ground_result);
|
||||||
|
furi_string_free(metro_result);
|
||||||
|
parsed = true;
|
||||||
|
} while(false);
|
||||||
|
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Actual implementation of app<>plugin interface */
|
||||||
|
static const NfcSupportedCardsPlugin social_moscow_plugin = {
|
||||||
|
.protocol = NfcProtocolMfClassic,
|
||||||
|
.verify = social_moscow_verify,
|
||||||
|
.read = social_moscow_read,
|
||||||
|
.parse = social_moscow_parse,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Plugin descriptor to comply with basic plugin specification */
|
||||||
|
static const FlipperAppPluginDescriptor social_moscow_plugin_descriptor = {
|
||||||
|
.appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID,
|
||||||
|
.ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION,
|
||||||
|
.entry_point = &social_moscow_plugin,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Plugin entry point - must return a pointer to const descriptor */
|
||||||
|
const FlipperAppPluginDescriptor* social_moscow_plugin_ep() {
|
||||||
|
return &social_moscow_plugin_descriptor;
|
||||||
|
}
|
||||||
@@ -82,20 +82,6 @@ static const MfClassicKeyPair troika_4k_keys[] = {
|
|||||||
{.a = 0xBB52F8CCE07F, .b = 0x6B6119752C70}, //40
|
{.a = 0xBB52F8CCE07F, .b = 0x6B6119752C70}, //40
|
||||||
};
|
};
|
||||||
|
|
||||||
static void troika_render_section_header(
|
|
||||||
FuriString* str,
|
|
||||||
const char* name,
|
|
||||||
uint8_t prefix_separator_cnt,
|
|
||||||
uint8_t suffix_separator_cnt) {
|
|
||||||
for(uint8_t i = 0; i < prefix_separator_cnt; i++) {
|
|
||||||
furi_string_cat_printf(str, ":");
|
|
||||||
}
|
|
||||||
furi_string_cat_printf(str, "[ %s ]", name);
|
|
||||||
for(uint8_t i = 0; i < suffix_separator_cnt; i++) {
|
|
||||||
furi_string_cat_printf(str, ":");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool troika_get_card_config(TroikaCardConfig* config, MfClassicType type) {
|
static bool troika_get_card_config(TroikaCardConfig* config, MfClassicType type) {
|
||||||
bool success = true;
|
bool success = true;
|
||||||
|
|
||||||
@@ -212,23 +198,25 @@ static bool troika_parse(const NfcDevice* device, FuriString* parsed_data) {
|
|||||||
FuriString* ground_result = furi_string_alloc();
|
FuriString* ground_result = furi_string_alloc();
|
||||||
FuriString* tat_result = furi_string_alloc();
|
FuriString* tat_result = furi_string_alloc();
|
||||||
|
|
||||||
bool result1 = mosgortrans_parse_transport_block(&data->block[32], metro_result);
|
bool is_metro_data_present =
|
||||||
bool result2 = mosgortrans_parse_transport_block(&data->block[28], ground_result);
|
mosgortrans_parse_transport_block(&data->block[32], metro_result);
|
||||||
bool result3 = mosgortrans_parse_transport_block(&data->block[16], tat_result);
|
bool is_ground_data_present =
|
||||||
|
mosgortrans_parse_transport_block(&data->block[28], ground_result);
|
||||||
|
bool is_tat_data_present = mosgortrans_parse_transport_block(&data->block[16], tat_result);
|
||||||
|
|
||||||
furi_string_cat_printf(parsed_data, "\e#Troyka card\n");
|
furi_string_cat_printf(parsed_data, "\e#Troyka card\n");
|
||||||
if(result1 && !furi_string_empty(metro_result)) {
|
if(is_metro_data_present && !furi_string_empty(metro_result)) {
|
||||||
troika_render_section_header(parsed_data, "Metro", 22, 21);
|
render_section_header(parsed_data, "Metro", 22, 21);
|
||||||
furi_string_cat_printf(parsed_data, "%s\n", furi_string_get_cstr(metro_result));
|
furi_string_cat_printf(parsed_data, "%s\n", furi_string_get_cstr(metro_result));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(result2 && !furi_string_empty(ground_result)) {
|
if(is_ground_data_present && !furi_string_empty(ground_result)) {
|
||||||
troika_render_section_header(parsed_data, "Ediny", 22, 22);
|
render_section_header(parsed_data, "Ediny", 22, 22);
|
||||||
furi_string_cat_printf(parsed_data, "%s\n", furi_string_get_cstr(ground_result));
|
furi_string_cat_printf(parsed_data, "%s\n", furi_string_get_cstr(ground_result));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(result3 && !furi_string_empty(tat_result)) {
|
if(is_tat_data_present && !furi_string_empty(tat_result)) {
|
||||||
troika_render_section_header(parsed_data, "TAT", 24, 23);
|
render_section_header(parsed_data, "TAT", 24, 23);
|
||||||
furi_string_cat_printf(parsed_data, "%s\n", furi_string_get_cstr(tat_result));
|
furi_string_cat_printf(parsed_data, "%s\n", furi_string_get_cstr(tat_result));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,7 +224,7 @@ static bool troika_parse(const NfcDevice* device, FuriString* parsed_data) {
|
|||||||
furi_string_free(ground_result);
|
furi_string_free(ground_result);
|
||||||
furi_string_free(metro_result);
|
furi_string_free(metro_result);
|
||||||
|
|
||||||
parsed = result1 || result2 || result3;
|
parsed = is_metro_data_present || is_ground_data_present || is_tat_data_present;
|
||||||
} while(false);
|
} while(false);
|
||||||
|
|
||||||
return parsed;
|
return parsed;
|
||||||
|
|||||||
@@ -171,20 +171,25 @@ static void nfc_scene_mf_classic_dict_attack_prepare_view(NfcApp* instance) {
|
|||||||
do {
|
do {
|
||||||
instance->nfc_dict_context.enhanced_dict = true;
|
instance->nfc_dict_context.enhanced_dict = true;
|
||||||
|
|
||||||
// TODO: Check for errors
|
if(keys_dict_check_presence(NFC_APP_MF_CLASSIC_DICT_SYSTEM_NESTED_PATH)) {
|
||||||
storage_common_remove(instance->storage, NFC_APP_MF_CLASSIC_DICT_SYSTEM_NESTED_PATH);
|
storage_common_remove(
|
||||||
storage_common_copy(
|
instance->storage, NFC_APP_MF_CLASSIC_DICT_SYSTEM_NESTED_PATH);
|
||||||
instance->storage,
|
}
|
||||||
NFC_APP_MF_CLASSIC_DICT_SYSTEM_PATH,
|
if(keys_dict_check_presence(NFC_APP_MF_CLASSIC_DICT_SYSTEM_PATH)) {
|
||||||
NFC_APP_MF_CLASSIC_DICT_SYSTEM_NESTED_PATH);
|
storage_common_copy(
|
||||||
|
instance->storage,
|
||||||
|
NFC_APP_MF_CLASSIC_DICT_SYSTEM_PATH,
|
||||||
|
NFC_APP_MF_CLASSIC_DICT_SYSTEM_NESTED_PATH);
|
||||||
|
}
|
||||||
|
|
||||||
if(!keys_dict_check_presence(NFC_APP_MF_CLASSIC_DICT_USER_PATH)) {
|
if(!keys_dict_check_presence(NFC_APP_MF_CLASSIC_DICT_USER_PATH)) {
|
||||||
state = DictAttackStateSystemDictInProgress;
|
state = DictAttackStateSystemDictInProgress;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Check for errors
|
if(keys_dict_check_presence(NFC_APP_MF_CLASSIC_DICT_USER_NESTED_PATH)) {
|
||||||
storage_common_remove(instance->storage, NFC_APP_MF_CLASSIC_DICT_USER_NESTED_PATH);
|
storage_common_remove(instance->storage, NFC_APP_MF_CLASSIC_DICT_USER_NESTED_PATH);
|
||||||
|
}
|
||||||
storage_common_copy(
|
storage_common_copy(
|
||||||
instance->storage,
|
instance->storage,
|
||||||
NFC_APP_MF_CLASSIC_DICT_USER_PATH,
|
NFC_APP_MF_CLASSIC_DICT_USER_PATH,
|
||||||
@@ -376,6 +381,14 @@ void nfc_scene_mf_classic_dict_attack_on_exit(void* context) {
|
|||||||
instance->nfc_dict_context.msb_count = 0;
|
instance->nfc_dict_context.msb_count = 0;
|
||||||
instance->nfc_dict_context.enhanced_dict = false;
|
instance->nfc_dict_context.enhanced_dict = false;
|
||||||
|
|
||||||
|
// Clean up temporary files used for nested dictionary attack
|
||||||
|
if(keys_dict_check_presence(NFC_APP_MF_CLASSIC_DICT_USER_NESTED_PATH)) {
|
||||||
|
storage_common_remove(instance->storage, NFC_APP_MF_CLASSIC_DICT_USER_NESTED_PATH);
|
||||||
|
}
|
||||||
|
if(keys_dict_check_presence(NFC_APP_MF_CLASSIC_DICT_SYSTEM_NESTED_PATH)) {
|
||||||
|
storage_common_remove(instance->storage, NFC_APP_MF_CLASSIC_DICT_SYSTEM_NESTED_PATH);
|
||||||
|
}
|
||||||
|
|
||||||
nfc_blink_stop(instance);
|
nfc_blink_stop(instance);
|
||||||
notification_message(instance->notifications, &sequence_display_backlight_enforce_auto);
|
notification_message(instance->notifications, &sequence_display_backlight_enforce_auto);
|
||||||
}
|
}
|
||||||
|
|||||||
12
applications/system/bad_ble/application.fam
Normal file
12
applications/system/bad_ble/application.fam
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
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",
|
||||||
|
)
|
||||||
BIN
applications/system/bad_ble/assets/Bad_BLE_48x22.png
Normal file
BIN
applications/system/bad_ble/assets/Bad_BLE_48x22.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 145 B |
196
applications/system/bad_ble/bad_ble_app.c
Normal file
196
applications/system/bad_ble/bad_ble_app.c
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
#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;
|
||||||
|
}
|
||||||
11
applications/system/bad_ble/bad_ble_app.h
Normal file
11
applications/system/bad_ble/bad_ble_app.h
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct BadBleApp BadBleApp;
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
53
applications/system/bad_ble/bad_ble_app_i.h
Normal file
53
applications/system/bad_ble/bad_ble_app_i.h
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
#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;
|
||||||
157
applications/system/bad_ble/helpers/bad_ble_hid.c
Normal file
157
applications/system/bad_ble/helpers/bad_ble_hid.c
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
#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);
|
||||||
|
}
|
||||||
34
applications/system/bad_ble/helpers/bad_ble_hid.h
Normal file
34
applications/system/bad_ble/helpers/bad_ble_hid.h
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
#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
|
||||||
716
applications/system/bad_ble/helpers/ducky_script.c
Normal file
716
applications/system/bad_ble/helpers/ducky_script.c
Normal file
@@ -0,0 +1,716 @@
|
|||||||
|
#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);
|
||||||
|
}
|
||||||
55
applications/system/bad_ble/helpers/ducky_script.h
Normal file
55
applications/system/bad_ble/helpers/ducky_script.h
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
#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
|
||||||
241
applications/system/bad_ble/helpers/ducky_script_commands.c
Normal file
241
applications/system/bad_ble/helpers/ducky_script_commands.c
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
#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;
|
||||||
|
}
|
||||||
76
applications/system/bad_ble/helpers/ducky_script_i.h
Normal file
76
applications/system/bad_ble/helpers/ducky_script_i.h
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
#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
|
||||||
133
applications/system/bad_ble/helpers/ducky_script_keycodes.c
Normal file
133
applications/system/bad_ble/helpers/ducky_script_keycodes.c
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
#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;
|
||||||
|
}
|
||||||
BIN
applications/system/bad_ble/icon.png
Normal file
BIN
applications/system/bad_ble/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 96 B |
30
applications/system/bad_ble/scenes/bad_ble_scene.c
Normal file
30
applications/system/bad_ble/scenes/bad_ble_scene.c
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
#include "bad_ble_scene.h"
|
||||||
|
|
||||||
|
// Generate scene on_enter handlers array
|
||||||
|
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
|
||||||
|
void (*const bad_ble_scene_on_enter_handlers[])(void*) = {
|
||||||
|
#include "bad_ble_scene_config.h"
|
||||||
|
};
|
||||||
|
#undef ADD_SCENE
|
||||||
|
|
||||||
|
// Generate scene on_event handlers array
|
||||||
|
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event,
|
||||||
|
bool (*const bad_ble_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = {
|
||||||
|
#include "bad_ble_scene_config.h"
|
||||||
|
};
|
||||||
|
#undef ADD_SCENE
|
||||||
|
|
||||||
|
// Generate scene on_exit handlers array
|
||||||
|
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit,
|
||||||
|
void (*const bad_ble_scene_on_exit_handlers[])(void* context) = {
|
||||||
|
#include "bad_ble_scene_config.h"
|
||||||
|
};
|
||||||
|
#undef ADD_SCENE
|
||||||
|
|
||||||
|
// Initialize scene handlers configuration structure
|
||||||
|
const SceneManagerHandlers bad_ble_scene_handlers = {
|
||||||
|
.on_enter_handlers = bad_ble_scene_on_enter_handlers,
|
||||||
|
.on_event_handlers = bad_ble_scene_on_event_handlers,
|
||||||
|
.on_exit_handlers = bad_ble_scene_on_exit_handlers,
|
||||||
|
.scene_num = BadBleSceneNum,
|
||||||
|
};
|
||||||
29
applications/system/bad_ble/scenes/bad_ble_scene.h
Normal file
29
applications/system/bad_ble/scenes/bad_ble_scene.h
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#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
|
||||||
59
applications/system/bad_ble/scenes/bad_ble_scene_config.c
Normal file
59
applications/system/bad_ble/scenes/bad_ble_scene_config.c
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
#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);
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
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)
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
#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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
#include "../bad_ble_app_i.h"
|
||||||
|
|
||||||
|
void bad_ble_scene_confirm_unpair_widget_callback(
|
||||||
|
GuiButtonType type,
|
||||||
|
InputType input_type,
|
||||||
|
void* context) {
|
||||||
|
UNUSED(input_type);
|
||||||
|
SceneManagerEvent event = {.type = SceneManagerEventTypeCustom, .event = type};
|
||||||
|
bad_ble_scene_confirm_unpair_on_event(context, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
void bad_ble_scene_confirm_unpair_on_enter(void* context) {
|
||||||
|
BadBleApp* bad_ble = context;
|
||||||
|
Widget* widget = bad_ble->widget;
|
||||||
|
|
||||||
|
widget_add_button_element(
|
||||||
|
widget, GuiButtonTypeLeft, "Cancel", bad_ble_scene_confirm_unpair_widget_callback, context);
|
||||||
|
widget_add_button_element(
|
||||||
|
widget,
|
||||||
|
GuiButtonTypeRight,
|
||||||
|
"Unpair",
|
||||||
|
bad_ble_scene_confirm_unpair_widget_callback,
|
||||||
|
context);
|
||||||
|
|
||||||
|
widget_add_text_box_element(
|
||||||
|
widget, 0, 0, 128, 64, AlignCenter, AlignTop, "\e#Unpair the Device?\e#\n", false);
|
||||||
|
|
||||||
|
view_dispatcher_switch_to_view(bad_ble->view_dispatcher, BadBleAppViewWidget);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool bad_ble_scene_confirm_unpair_on_event(void* context, SceneManagerEvent event) {
|
||||||
|
BadBleApp* bad_ble = context;
|
||||||
|
SceneManager* scene_manager = bad_ble->scene_manager;
|
||||||
|
bool consumed = false;
|
||||||
|
|
||||||
|
if(event.type == SceneManagerEventTypeCustom) {
|
||||||
|
consumed = true;
|
||||||
|
if(event.event == GuiButtonTypeRight) {
|
||||||
|
scene_manager_next_scene(scene_manager, BadBleSceneUnpairDone);
|
||||||
|
} else if(event.event == GuiButtonTypeLeft) {
|
||||||
|
scene_manager_previous_scene(scene_manager);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return consumed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void bad_ble_scene_confirm_unpair_on_exit(void* context) {
|
||||||
|
BadBleApp* bad_ble = context;
|
||||||
|
Widget* widget = bad_ble->widget;
|
||||||
|
|
||||||
|
widget_reset(widget);
|
||||||
|
}
|
||||||
65
applications/system/bad_ble/scenes/bad_ble_scene_error.c
Normal file
65
applications/system/bad_ble/scenes/bad_ble_scene_error.c
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
#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);
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
#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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
#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);
|
||||||
|
}
|
||||||
65
applications/system/bad_ble/scenes/bad_ble_scene_work.c
Normal file
65
applications/system/bad_ble/scenes/bad_ble_scene_work.c
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
#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);
|
||||||
|
}
|
||||||
284
applications/system/bad_ble/views/bad_ble_view.c
Normal file
284
applications/system/bad_ble/views/bad_ble_view.c
Normal file
@@ -0,0 +1,284 @@
|
|||||||
|
#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;
|
||||||
|
}
|
||||||
26
applications/system/bad_ble/views/bad_ble_view.h
Normal file
26
applications/system/bad_ble/views/bad_ble_view.h
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#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);
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
#include "thread.h"
|
#include "thread.h"
|
||||||
#include "thread_list.h"
|
#include "thread_list_i.h"
|
||||||
#include "kernel.h"
|
#include "kernel.h"
|
||||||
#include "memmgr.h"
|
#include "memmgr.h"
|
||||||
#include "memmgr_heap.h"
|
#include "memmgr_heap.h"
|
||||||
@@ -65,6 +65,9 @@ struct FuriThread {
|
|||||||
// IMPORTANT: container MUST be the FIRST struct member
|
// IMPORTANT: container MUST be the FIRST struct member
|
||||||
static_assert(offsetof(FuriThread, container) == 0);
|
static_assert(offsetof(FuriThread, container) == 0);
|
||||||
|
|
||||||
|
// Our idle priority should be equal to the one from FreeRTOS
|
||||||
|
static_assert(FuriThreadPriorityIdle == tskIDLE_PRIORITY);
|
||||||
|
|
||||||
static size_t __furi_thread_stdout_write(FuriThread* thread, const char* data, size_t size);
|
static size_t __furi_thread_stdout_write(FuriThread* thread, const char* data, size_t size);
|
||||||
static int32_t __furi_thread_stdout_flush(FuriThread* thread);
|
static int32_t __furi_thread_stdout_flush(FuriThread* thread);
|
||||||
|
|
||||||
@@ -145,6 +148,8 @@ static void furi_thread_init_common(FuriThread* thread) {
|
|||||||
furi_thread_set_appid(thread, "driver");
|
furi_thread_set_appid(thread, "driver");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
thread->priority = FuriThreadPriorityNormal;
|
||||||
|
|
||||||
FuriHalRtcHeapTrackMode mode = furi_hal_rtc_get_heap_track_mode();
|
FuriHalRtcHeapTrackMode mode = furi_hal_rtc_get_heap_track_mode();
|
||||||
if(mode == FuriHalRtcHeapTrackModeAll) {
|
if(mode == FuriHalRtcHeapTrackModeAll) {
|
||||||
thread->heap_trace_enabled = true;
|
thread->heap_trace_enabled = true;
|
||||||
@@ -269,7 +274,7 @@ void furi_thread_set_context(FuriThread* thread, void* context) {
|
|||||||
void furi_thread_set_priority(FuriThread* thread, FuriThreadPriority priority) {
|
void furi_thread_set_priority(FuriThread* thread, FuriThreadPriority priority) {
|
||||||
furi_check(thread);
|
furi_check(thread);
|
||||||
furi_check(thread->state == FuriThreadStateStopped);
|
furi_check(thread->state == FuriThreadStateStopped);
|
||||||
furi_check(priority >= FuriThreadPriorityIdle && priority <= FuriThreadPriorityIsr);
|
furi_check(priority <= FuriThreadPriorityIsr);
|
||||||
thread->priority = priority;
|
thread->priority = priority;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,9 +286,7 @@ FuriThreadPriority furi_thread_get_priority(FuriThread* thread) {
|
|||||||
|
|
||||||
void furi_thread_set_current_priority(FuriThreadPriority priority) {
|
void furi_thread_set_current_priority(FuriThreadPriority priority) {
|
||||||
furi_check(priority <= FuriThreadPriorityIsr);
|
furi_check(priority <= FuriThreadPriorityIsr);
|
||||||
|
vTaskPrioritySet(NULL, priority);
|
||||||
UBaseType_t new_priority = priority ? priority : FuriThreadPriorityNormal;
|
|
||||||
vTaskPrioritySet(NULL, new_priority);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FuriThreadPriority furi_thread_get_current_priority(void) {
|
FuriThreadPriority furi_thread_get_current_priority(void) {
|
||||||
@@ -345,7 +348,6 @@ void furi_thread_start(FuriThread* thread) {
|
|||||||
furi_thread_set_state(thread, FuriThreadStateStarting);
|
furi_thread_set_state(thread, FuriThreadStateStarting);
|
||||||
|
|
||||||
uint32_t stack_depth = thread->stack_size / sizeof(StackType_t);
|
uint32_t stack_depth = thread->stack_size / sizeof(StackType_t);
|
||||||
UBaseType_t priority = thread->priority ? thread->priority : FuriThreadPriorityNormal;
|
|
||||||
|
|
||||||
thread->is_active = true;
|
thread->is_active = true;
|
||||||
|
|
||||||
@@ -355,7 +357,7 @@ void furi_thread_start(FuriThread* thread) {
|
|||||||
thread->name,
|
thread->name,
|
||||||
stack_depth,
|
stack_depth,
|
||||||
thread,
|
thread,
|
||||||
priority,
|
thread->priority,
|
||||||
thread->stack_buffer,
|
thread->stack_buffer,
|
||||||
&thread->container) == (TaskHandle_t)thread);
|
&thread->container) == (TaskHandle_t)thread);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,11 +30,10 @@ typedef enum {
|
|||||||
* @brief Enumeration of possible FuriThread priorities.
|
* @brief Enumeration of possible FuriThread priorities.
|
||||||
*/
|
*/
|
||||||
typedef enum {
|
typedef enum {
|
||||||
FuriThreadPriorityNone = 0, /**< Uninitialized, choose system default */
|
FuriThreadPriorityIdle = 0, /**< Idle priority */
|
||||||
FuriThreadPriorityIdle = 1, /**< Idle priority */
|
|
||||||
FuriThreadPriorityLowest = 14, /**< Lowest */
|
FuriThreadPriorityLowest = 14, /**< Lowest */
|
||||||
FuriThreadPriorityLow = 15, /**< Low */
|
FuriThreadPriorityLow = 15, /**< Low */
|
||||||
FuriThreadPriorityNormal = 16, /**< Normal */
|
FuriThreadPriorityNormal = 16, /**< Normal, system default */
|
||||||
FuriThreadPriorityHigh = 17, /**< High */
|
FuriThreadPriorityHigh = 17, /**< High */
|
||||||
FuriThreadPriorityHighest = 18, /**< Highest */
|
FuriThreadPriorityHighest = 18, /**< Highest */
|
||||||
FuriThreadPriorityIsr =
|
FuriThreadPriorityIsr =
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ FuriThreadListItem* furi_thread_list_get_or_insert(FuriThreadList* instance, Fur
|
|||||||
}
|
}
|
||||||
|
|
||||||
void furi_thread_list_process(FuriThreadList* instance, uint32_t runtime, uint32_t tick) {
|
void furi_thread_list_process(FuriThreadList* instance, uint32_t runtime, uint32_t tick) {
|
||||||
furi_check(instance);
|
furi_assert(instance);
|
||||||
|
|
||||||
instance->runtime_previous = instance->runtime_current;
|
instance->runtime_previous = instance->runtime_current;
|
||||||
instance->runtime_current = runtime;
|
instance->runtime_current = runtime;
|
||||||
|
|||||||
@@ -68,14 +68,6 @@ FuriThreadListItem* furi_thread_list_get_at(FuriThreadList* instance, size_t pos
|
|||||||
*/
|
*/
|
||||||
FuriThreadListItem* furi_thread_list_get_or_insert(FuriThreadList* instance, FuriThread* thread);
|
FuriThreadListItem* furi_thread_list_get_or_insert(FuriThreadList* instance, FuriThread* thread);
|
||||||
|
|
||||||
/** Process items in the FuriThreadList instance
|
|
||||||
*
|
|
||||||
* @param instance The instance
|
|
||||||
* @param[in] runtime The runtime of the system since start
|
|
||||||
* @param[in] tick The tick when processing happened
|
|
||||||
*/
|
|
||||||
void furi_thread_list_process(FuriThreadList* instance, uint32_t runtime, uint32_t tick);
|
|
||||||
|
|
||||||
/** Get percent of time spent in ISR
|
/** Get percent of time spent in ISR
|
||||||
*
|
*
|
||||||
* @param instance The instance
|
* @param instance The instance
|
||||||
|
|||||||
19
furi/core/thread_list_i.h
Normal file
19
furi/core/thread_list_i.h
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "thread_list.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/** Process items in the FuriThreadList instance
|
||||||
|
*
|
||||||
|
* @param instance The instance
|
||||||
|
* @param[in] runtime The runtime of the system since start
|
||||||
|
* @param[in] tick The tick when processing happened
|
||||||
|
*/
|
||||||
|
void furi_thread_list_process(FuriThreadList* instance, uint32_t runtime, uint32_t tick);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -1,29 +1,77 @@
|
|||||||
|
|
||||||
#include "bq27220.h"
|
#include "bq27220.h"
|
||||||
#include "bq27220_reg.h"
|
#include "bq27220_reg.h"
|
||||||
#include "bq27220_data_memory.h"
|
#include "bq27220_data_memory.h"
|
||||||
|
|
||||||
_Static_assert(sizeof(BQ27220DMGaugingConfig) == 2, "Incorrect structure size");
|
|
||||||
|
|
||||||
#include <furi.h>
|
#include <furi.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
#define TAG "Gauge"
|
#define TAG "Gauge"
|
||||||
|
|
||||||
static uint16_t bq27220_read_word(FuriHalI2cBusHandle* handle, uint8_t address) {
|
#define BQ27220_ID (0x0220u)
|
||||||
uint16_t buf = 0;
|
|
||||||
|
|
||||||
furi_hal_i2c_read_mem(
|
/** Delay between 2 writes into Subclass/MAC area. Fails at ~120us. */
|
||||||
handle, BQ27220_ADDRESS, address, (uint8_t*)&buf, 2, BQ27220_I2C_TIMEOUT);
|
#define BQ27220_MAC_WRITE_DELAY_US (250u)
|
||||||
|
|
||||||
return buf;
|
/** Delay between we ask chip to load data to MAC and it become valid. Fails at ~500us. */
|
||||||
|
#define BQ27220_SELECT_DELAY_US (1000u)
|
||||||
|
|
||||||
|
/** Delay between 2 control operations(like unseal or full access). Fails at ~2500us.*/
|
||||||
|
#define BQ27220_MAGIC_DELAY_US (5000u)
|
||||||
|
|
||||||
|
/** Delay before freshly written configuration can be read. Fails at ? */
|
||||||
|
#define BQ27220_CONFIG_DELAY_US (10000u)
|
||||||
|
|
||||||
|
/** Config apply delay. Must wait, or DM read returns garbage. */
|
||||||
|
#define BQ27220_CONFIG_APPLY_US (2000000u)
|
||||||
|
|
||||||
|
/** Timeout for common operations. */
|
||||||
|
#define BQ27220_TIMEOUT_COMMON_US (2000000u)
|
||||||
|
|
||||||
|
/** Timeout for reset operation. Normally reset takes ~2s. */
|
||||||
|
#define BQ27220_TIMEOUT_RESET_US (4000000u)
|
||||||
|
|
||||||
|
/** Timeout cycle interval */
|
||||||
|
#define BQ27220_TIMEOUT_CYCLE_INTERVAL_US (1000u)
|
||||||
|
|
||||||
|
/** Timeout cycles count helper */
|
||||||
|
#define BQ27220_TIMEOUT(timeout_us) ((timeout_us) / (BQ27220_TIMEOUT_CYCLE_INTERVAL_US))
|
||||||
|
|
||||||
|
#ifdef BQ27220_DEBUG
|
||||||
|
#define BQ27220_DEBUG_LOG(...) FURI_LOG_D(TAG, ##__VA_ARGS__)
|
||||||
|
#else
|
||||||
|
#define BQ27220_DEBUG_LOG(...)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static inline bool bq27220_read_reg(
|
||||||
|
FuriHalI2cBusHandle* handle,
|
||||||
|
uint8_t address,
|
||||||
|
uint8_t* buffer,
|
||||||
|
size_t buffer_size) {
|
||||||
|
return furi_hal_i2c_trx(
|
||||||
|
handle, BQ27220_ADDRESS, &address, 1, buffer, buffer_size, BQ27220_I2C_TIMEOUT);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool bq27220_control(FuriHalI2cBusHandle* handle, uint16_t control) {
|
static inline bool bq27220_write(
|
||||||
bool ret = furi_hal_i2c_write_mem(
|
FuriHalI2cBusHandle* handle,
|
||||||
handle, BQ27220_ADDRESS, CommandControl, (uint8_t*)&control, 2, BQ27220_I2C_TIMEOUT);
|
uint8_t address,
|
||||||
|
const uint8_t* buffer,
|
||||||
|
size_t buffer_size) {
|
||||||
|
return furi_hal_i2c_write_mem(
|
||||||
|
handle, BQ27220_ADDRESS, address, buffer, buffer_size, BQ27220_I2C_TIMEOUT);
|
||||||
|
}
|
||||||
|
|
||||||
return ret;
|
static inline bool bq27220_control(FuriHalI2cBusHandle* handle, uint16_t control) {
|
||||||
|
return bq27220_write(handle, CommandControl, (uint8_t*)&control, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint16_t bq27220_read_word(FuriHalI2cBusHandle* handle, uint8_t address) {
|
||||||
|
uint16_t buf = BQ27220_ERROR;
|
||||||
|
|
||||||
|
if(!bq27220_read_reg(handle, address, (uint8_t*)&buf, 2)) {
|
||||||
|
FURI_LOG_E(TAG, "bq27220_read_word failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint8_t bq27220_get_checksum(uint8_t* data, uint16_t len) {
|
static uint8_t bq27220_get_checksum(uint8_t* data, uint16_t len) {
|
||||||
@@ -56,49 +104,49 @@ static bool bq27220_parameter_check(
|
|||||||
if(update) {
|
if(update) {
|
||||||
// Datasheet contains incorrect procedure for memory update, more info:
|
// Datasheet contains incorrect procedure for memory update, more info:
|
||||||
// https://e2e.ti.com/support/power-management-group/power-management/f/power-management-forum/719878/bq27220-technical-reference-manual-sluubd4-is-missing-extended-data-commands-chapter
|
// https://e2e.ti.com/support/power-management-group/power-management/f/power-management-forum/719878/bq27220-technical-reference-manual-sluubd4-is-missing-extended-data-commands-chapter
|
||||||
|
// Also see note in the header
|
||||||
|
|
||||||
// 2. Write the address AND the parameter data to 0x3E+ (auto increment)
|
// Write the address AND the parameter data to 0x3E+ (auto increment)
|
||||||
if(!furi_hal_i2c_write_mem(
|
if(!bq27220_write(handle, CommandSelectSubclass, buffer, size + 2)) {
|
||||||
handle,
|
FURI_LOG_E(TAG, "DM write failed");
|
||||||
BQ27220_ADDRESS,
|
|
||||||
CommandSelectSubclass,
|
|
||||||
buffer,
|
|
||||||
size + 2,
|
|
||||||
BQ27220_I2C_TIMEOUT)) {
|
|
||||||
FURI_LOG_I(TAG, "DM write failed");
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
furi_delay_us(10000);
|
// We must wait, otherwise write will fail
|
||||||
|
furi_delay_us(BQ27220_MAC_WRITE_DELAY_US);
|
||||||
|
|
||||||
// 3. Calculate the check sum: 0xFF - (sum of address and data) OR 0xFF
|
// Calculate the check sum: 0xFF - (sum of address and data) OR 0xFF
|
||||||
uint8_t checksum = bq27220_get_checksum(buffer, size + 2);
|
uint8_t checksum = bq27220_get_checksum(buffer, size + 2);
|
||||||
// 4. Write the check sum to 0x60 and the total length of (address + parameter data + check sum + length) to 0x61
|
// Write the check sum to 0x60 and the total length of (address + parameter data + check sum + length) to 0x61
|
||||||
buffer[0] = checksum;
|
buffer[0] = checksum;
|
||||||
// 2 bytes address, `size` bytes data, 1 byte check sum, 1 byte length
|
// 2 bytes address, `size` bytes data, 1 byte check sum, 1 byte length
|
||||||
buffer[1] = 2 + size + 1 + 1;
|
buffer[1] = 2 + size + 1 + 1;
|
||||||
if(!furi_hal_i2c_write_mem(
|
if(!bq27220_write(handle, CommandMACDataSum, buffer, 2)) {
|
||||||
handle, BQ27220_ADDRESS, CommandMACDataSum, buffer, 2, BQ27220_I2C_TIMEOUT)) {
|
FURI_LOG_E(TAG, "CRC write failed");
|
||||||
FURI_LOG_I(TAG, "CRC write failed");
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
// Final wait as in gm.fs specification
|
||||||
furi_delay_us(10000);
|
furi_delay_us(BQ27220_CONFIG_DELAY_US);
|
||||||
ret = true;
|
ret = true;
|
||||||
} else {
|
} else {
|
||||||
if(!furi_hal_i2c_write_mem(
|
if(!bq27220_write(handle, CommandSelectSubclass, buffer, 2)) {
|
||||||
handle, BQ27220_ADDRESS, CommandSelectSubclass, buffer, 2, BQ27220_I2C_TIMEOUT)) {
|
FURI_LOG_E(TAG, "DM SelectSubclass for read failed");
|
||||||
FURI_LOG_I(TAG, "DM SelectSubclass for read failed");
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!furi_hal_i2c_rx(handle, BQ27220_ADDRESS, old_data, size, BQ27220_I2C_TIMEOUT)) {
|
// bqstudio uses 15ms wait delay here
|
||||||
FURI_LOG_I(TAG, "DM read failed");
|
furi_delay_us(BQ27220_SELECT_DELAY_US);
|
||||||
|
|
||||||
|
if(!bq27220_read_reg(handle, CommandMACData, old_data, size)) {
|
||||||
|
FURI_LOG_E(TAG, "DM read failed");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// bqstudio uses burst reads with continue(CommandSelectSubclass without argument) and ~5ms between burst
|
||||||
|
furi_delay_us(BQ27220_SELECT_DELAY_US);
|
||||||
|
|
||||||
if(*(uint32_t*)&(old_data[0]) != *(uint32_t*)&(buffer[2])) {
|
if(*(uint32_t*)&(old_data[0]) != *(uint32_t*)&(buffer[2])) {
|
||||||
FURI_LOG_W( //-V641
|
FURI_LOG_E( //-V641
|
||||||
TAG,
|
TAG,
|
||||||
"Data at 0x%04x(%zu): 0x%08lx!=0x%08lx",
|
"Data at 0x%04x(%zu): 0x%08lx!=0x%08lx",
|
||||||
address,
|
address,
|
||||||
@@ -119,22 +167,34 @@ static bool bq27220_data_memory_check(
|
|||||||
const BQ27220DMData* data_memory,
|
const BQ27220DMData* data_memory,
|
||||||
bool update) {
|
bool update) {
|
||||||
if(update) {
|
if(update) {
|
||||||
if(!bq27220_control(handle, Control_ENTER_CFG_UPDATE)) {
|
const uint16_t cfg_request = Control_ENTER_CFG_UPDATE;
|
||||||
|
if(!bq27220_write(
|
||||||
|
handle, CommandSelectSubclass, (uint8_t*)&cfg_request, sizeof(cfg_request))) {
|
||||||
FURI_LOG_E(TAG, "ENTER_CFG_UPDATE command failed");
|
FURI_LOG_E(TAG, "ENTER_CFG_UPDATE command failed");
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Wait for enter CFG update mode
|
// Wait for enter CFG update mode
|
||||||
uint32_t timeout = 100;
|
uint32_t timeout = BQ27220_TIMEOUT(BQ27220_TIMEOUT_COMMON_US);
|
||||||
OperationStatus status = {0};
|
Bq27220OperationStatus operation_status;
|
||||||
while((status.CFGUPDATE != true) && (timeout-- > 0)) {
|
while(--timeout > 0) {
|
||||||
bq27220_get_operation_status(handle, &status);
|
if(!bq27220_get_operation_status(handle, &operation_status)) {
|
||||||
|
FURI_LOG_W(TAG, "Failed to get operation status, retries left %lu", timeout);
|
||||||
|
} else if(operation_status.CFGUPDATE) {
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
furi_delay_us(BQ27220_TIMEOUT_CYCLE_INTERVAL_US);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(timeout == 0) {
|
if(timeout == 0) {
|
||||||
FURI_LOG_E(TAG, "CFGUPDATE mode failed");
|
FURI_LOG_E(
|
||||||
|
TAG,
|
||||||
|
"Enter CFGUPDATE mode failed, CFG %u, SEC %u",
|
||||||
|
operation_status.CFGUPDATE,
|
||||||
|
operation_status.SEC);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
BQ27220_DEBUG_LOG("Cycles left: %lu", timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process data memory records
|
// Process data memory records
|
||||||
@@ -179,43 +239,283 @@ static bool bq27220_data_memory_check(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Finalize configuration update
|
// Finalize configuration update
|
||||||
if(update) {
|
if(update && result) {
|
||||||
bq27220_control(handle, Control_EXIT_CFG_UPDATE_REINIT);
|
bq27220_control(handle, Control_EXIT_CFG_UPDATE_REINIT);
|
||||||
furi_delay_us(10000);
|
|
||||||
|
// Wait for gauge to apply new configuration
|
||||||
|
furi_delay_us(BQ27220_CONFIG_APPLY_US);
|
||||||
|
|
||||||
|
// ensure that we exited config update mode
|
||||||
|
uint32_t timeout = BQ27220_TIMEOUT(BQ27220_TIMEOUT_COMMON_US);
|
||||||
|
Bq27220OperationStatus operation_status;
|
||||||
|
while(--timeout > 0) {
|
||||||
|
if(!bq27220_get_operation_status(handle, &operation_status)) {
|
||||||
|
FURI_LOG_W(TAG, "Failed to get operation status, retries left %lu", timeout);
|
||||||
|
} else if(operation_status.CFGUPDATE != true) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
furi_delay_us(BQ27220_TIMEOUT_CYCLE_INTERVAL_US);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check timeout
|
||||||
|
if(timeout == 0) {
|
||||||
|
FURI_LOG_E(TAG, "Exit CFGUPDATE mode failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
BQ27220_DEBUG_LOG("Cycles left: %lu", timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool bq27220_init(FuriHalI2cBusHandle* handle) {
|
bool bq27220_init(FuriHalI2cBusHandle* handle, const BQ27220DMData* data_memory) {
|
||||||
// Request device number(chip PN)
|
bool result = false;
|
||||||
if(!bq27220_control(handle, Control_DEVICE_NUMBER)) {
|
bool reset_and_provisioning_required = false;
|
||||||
FURI_LOG_E(TAG, "Device is not present");
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
// Check control response
|
|
||||||
uint16_t data = 0;
|
|
||||||
data = bq27220_read_word(handle, CommandControl);
|
|
||||||
if(data != 0xFF00) {
|
|
||||||
FURI_LOG_E(TAG, "Invalid control response: %x", data);
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
data = bq27220_read_word(handle, CommandMACData);
|
do {
|
||||||
FURI_LOG_I(TAG, "Device Number %04x", data);
|
// Request device number(chip PN)
|
||||||
|
BQ27220_DEBUG_LOG("Checking device ID");
|
||||||
|
if(!bq27220_control(handle, Control_DEVICE_NUMBER)) {
|
||||||
|
FURI_LOG_E(TAG, "ID: Device is not responding");
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
// Enterprise wait(MAC read fails if less than 500us)
|
||||||
|
// bqstudio uses ~15ms
|
||||||
|
furi_delay_us(BQ27220_SELECT_DELAY_US);
|
||||||
|
// Read id data from MAC scratch space
|
||||||
|
uint16_t data = bq27220_read_word(handle, CommandMACData);
|
||||||
|
if(data != BQ27220_ID) {
|
||||||
|
FURI_LOG_E(TAG, "Invalid Device Number %04x != 0x0220", data);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
return data == 0x0220;
|
// Unseal device since we are going to read protected configuration
|
||||||
|
BQ27220_DEBUG_LOG("Unsealing");
|
||||||
|
if(!bq27220_unseal(handle)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to recover gauge from forever init
|
||||||
|
BQ27220_DEBUG_LOG("Checking initialization status");
|
||||||
|
Bq27220OperationStatus operation_status;
|
||||||
|
if(!bq27220_get_operation_status(handle, &operation_status)) {
|
||||||
|
FURI_LOG_E(TAG, "Failed to get operation status");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(!operation_status.INITCOMP || operation_status.CFGUPDATE) {
|
||||||
|
FURI_LOG_E(TAG, "Incorrect state, reset needed");
|
||||||
|
reset_and_provisioning_required = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure correct profile is selected
|
||||||
|
BQ27220_DEBUG_LOG("Checking chosen profile");
|
||||||
|
Bq27220ControlStatus control_status;
|
||||||
|
if(!bq27220_get_control_status(handle, &control_status)) {
|
||||||
|
FURI_LOG_E(TAG, "Failed to get control status");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(control_status.BATT_ID != 0) {
|
||||||
|
FURI_LOG_E(TAG, "Incorrect profile, reset needed");
|
||||||
|
reset_and_provisioning_required = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure correct configuration loaded into gauge DataMemory
|
||||||
|
// Only if reset is not required, otherwise we don't
|
||||||
|
if(!reset_and_provisioning_required) {
|
||||||
|
BQ27220_DEBUG_LOG("Checking data memory");
|
||||||
|
if(!bq27220_data_memory_check(handle, data_memory, false)) {
|
||||||
|
FURI_LOG_E(TAG, "Incorrect configuration data, reset needed");
|
||||||
|
reset_and_provisioning_required = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset needed
|
||||||
|
if(reset_and_provisioning_required) {
|
||||||
|
FURI_LOG_W(TAG, "Resetting device");
|
||||||
|
if(!bq27220_reset(handle)) {
|
||||||
|
FURI_LOG_E(TAG, "Failed to reset device");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get full access to read and modify parameters
|
||||||
|
// Also it looks like this step is totally unnecessary
|
||||||
|
BQ27220_DEBUG_LOG("Acquiring Full Access");
|
||||||
|
if(!bq27220_full_access(handle)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update memory
|
||||||
|
FURI_LOG_W(TAG, "Updating data memory");
|
||||||
|
bq27220_data_memory_check(handle, data_memory, true);
|
||||||
|
if(!bq27220_data_memory_check(handle, data_memory, false)) {
|
||||||
|
FURI_LOG_E(TAG, "Data memory update failed");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BQ27220_DEBUG_LOG("Sealing");
|
||||||
|
if(!bq27220_seal(handle)) {
|
||||||
|
FURI_LOG_E(TAG, "Seal failed");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = true;
|
||||||
|
} while(0);
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool bq27220_apply_data_memory(FuriHalI2cBusHandle* handle, const BQ27220DMData* data_memory) {
|
bool bq27220_reset(FuriHalI2cBusHandle* handle) {
|
||||||
FURI_LOG_I(TAG, "Verifying data memory");
|
bool result = false;
|
||||||
if(!bq27220_data_memory_check(handle, data_memory, false)) {
|
do {
|
||||||
FURI_LOG_I(TAG, "Updating data memory");
|
if(!bq27220_control(handle, Control_RESET)) {
|
||||||
bq27220_data_memory_check(handle, data_memory, true);
|
FURI_LOG_E(TAG, "Reset request failed");
|
||||||
}
|
break;
|
||||||
FURI_LOG_I(TAG, "Data memory verification complete");
|
};
|
||||||
|
|
||||||
return true;
|
uint32_t timeout = BQ27220_TIMEOUT(BQ27220_TIMEOUT_RESET_US);
|
||||||
|
Bq27220OperationStatus operation_status;
|
||||||
|
while(--timeout > 0) {
|
||||||
|
if(!bq27220_get_operation_status(handle, &operation_status)) {
|
||||||
|
FURI_LOG_W(TAG, "Failed to get operation status, retries left %lu", timeout);
|
||||||
|
} else if(operation_status.INITCOMP == true) {
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
furi_delay_us(BQ27220_TIMEOUT_CYCLE_INTERVAL_US);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(timeout == 0) {
|
||||||
|
FURI_LOG_E(TAG, "INITCOMP timeout after reset");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
BQ27220_DEBUG_LOG("Cycles left: %lu", timeout);
|
||||||
|
|
||||||
|
result = true;
|
||||||
|
} while(0);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool bq27220_seal(FuriHalI2cBusHandle* handle) {
|
||||||
|
Bq27220OperationStatus operation_status = {0};
|
||||||
|
bool result = false;
|
||||||
|
do {
|
||||||
|
if(!bq27220_get_operation_status(handle, &operation_status)) {
|
||||||
|
FURI_LOG_E(TAG, "Status query failed");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(operation_status.SEC == Bq27220OperationStatusSecSealed) {
|
||||||
|
result = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!bq27220_control(handle, Control_SEALED)) {
|
||||||
|
FURI_LOG_E(TAG, "Seal request failed");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
furi_delay_us(BQ27220_SELECT_DELAY_US);
|
||||||
|
|
||||||
|
if(!bq27220_get_operation_status(handle, &operation_status)) {
|
||||||
|
FURI_LOG_E(TAG, "Status query failed");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(operation_status.SEC != Bq27220OperationStatusSecSealed) {
|
||||||
|
FURI_LOG_E(TAG, "Seal failed");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = true;
|
||||||
|
} while(0);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool bq27220_unseal(FuriHalI2cBusHandle* handle) {
|
||||||
|
Bq27220OperationStatus operation_status = {0};
|
||||||
|
bool result = false;
|
||||||
|
do {
|
||||||
|
if(!bq27220_get_operation_status(handle, &operation_status)) {
|
||||||
|
FURI_LOG_E(TAG, "Status query failed");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(operation_status.SEC != Bq27220OperationStatusSecSealed) {
|
||||||
|
result = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hai, Kazuma desu
|
||||||
|
bq27220_control(handle, UnsealKey1);
|
||||||
|
furi_delay_us(BQ27220_MAGIC_DELAY_US);
|
||||||
|
bq27220_control(handle, UnsealKey2);
|
||||||
|
furi_delay_us(BQ27220_MAGIC_DELAY_US);
|
||||||
|
|
||||||
|
if(!bq27220_get_operation_status(handle, &operation_status)) {
|
||||||
|
FURI_LOG_E(TAG, "Status query failed");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(operation_status.SEC != Bq27220OperationStatusSecUnsealed) {
|
||||||
|
FURI_LOG_E(TAG, "Unseal failed %u", operation_status.SEC);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = true;
|
||||||
|
} while(0);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool bq27220_full_access(FuriHalI2cBusHandle* handle) {
|
||||||
|
bool result = false;
|
||||||
|
|
||||||
|
do {
|
||||||
|
uint32_t timeout = BQ27220_TIMEOUT(BQ27220_TIMEOUT_COMMON_US);
|
||||||
|
Bq27220OperationStatus operation_status;
|
||||||
|
while(--timeout > 0) {
|
||||||
|
if(!bq27220_get_operation_status(handle, &operation_status)) {
|
||||||
|
FURI_LOG_W(TAG, "Failed to get operation status, retries left %lu", timeout);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
furi_delay_us(BQ27220_TIMEOUT_CYCLE_INTERVAL_US);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(timeout == 0) {
|
||||||
|
FURI_LOG_E(TAG, "Failed to get operation status");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
BQ27220_DEBUG_LOG("Cycles left: %lu", timeout);
|
||||||
|
|
||||||
|
// Already full access
|
||||||
|
if(operation_status.SEC == Bq27220OperationStatusSecFull) {
|
||||||
|
result = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Must be unsealed to get full access
|
||||||
|
if(operation_status.SEC != Bq27220OperationStatusSecUnsealed) {
|
||||||
|
FURI_LOG_E(TAG, "Not in unsealed state");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Explosion!!!
|
||||||
|
bq27220_control(handle, FullAccessKey); //-V760
|
||||||
|
furi_delay_us(BQ27220_MAGIC_DELAY_US);
|
||||||
|
bq27220_control(handle, FullAccessKey);
|
||||||
|
furi_delay_us(BQ27220_MAGIC_DELAY_US);
|
||||||
|
|
||||||
|
if(!bq27220_get_operation_status(handle, &operation_status)) {
|
||||||
|
FURI_LOG_E(TAG, "Status query failed");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(operation_status.SEC != Bq27220OperationStatusSecFull) {
|
||||||
|
FURI_LOG_E(TAG, "Full access failed %u", operation_status.SEC);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = true;
|
||||||
|
} while(0);
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t bq27220_get_voltage(FuriHalI2cBusHandle* handle) {
|
uint16_t bq27220_get_voltage(FuriHalI2cBusHandle* handle) {
|
||||||
@@ -226,24 +526,30 @@ int16_t bq27220_get_current(FuriHalI2cBusHandle* handle) {
|
|||||||
return bq27220_read_word(handle, CommandCurrent);
|
return bq27220_read_word(handle, CommandCurrent);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool bq27220_get_battery_status(FuriHalI2cBusHandle* handle, BatteryStatus* battery_status) {
|
bool bq27220_get_control_status(FuriHalI2cBusHandle* handle, Bq27220ControlStatus* control_status) {
|
||||||
uint16_t data = bq27220_read_word(handle, CommandBatteryStatus);
|
return bq27220_read_reg(handle, CommandControl, (uint8_t*)control_status, 2);
|
||||||
if(data == BQ27220_ERROR) {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
*(uint16_t*)battery_status = data;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool bq27220_get_operation_status(FuriHalI2cBusHandle* handle, OperationStatus* operation_status) {
|
bool bq27220_get_battery_status(FuriHalI2cBusHandle* handle, Bq27220BatteryStatus* battery_status) {
|
||||||
uint16_t data = bq27220_read_word(handle, CommandOperationStatus);
|
return bq27220_read_reg(handle, CommandBatteryStatus, (uint8_t*)battery_status, 2);
|
||||||
if(data == BQ27220_ERROR) {
|
}
|
||||||
|
|
||||||
|
bool bq27220_get_operation_status(
|
||||||
|
FuriHalI2cBusHandle* handle,
|
||||||
|
Bq27220OperationStatus* operation_status) {
|
||||||
|
return bq27220_read_reg(handle, CommandOperationStatus, (uint8_t*)operation_status, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool bq27220_get_gauging_status(FuriHalI2cBusHandle* handle, Bq27220GaugingStatus* gauging_status) {
|
||||||
|
// Request gauging data to be loaded to MAC
|
||||||
|
if(!bq27220_control(handle, Control_GAUGING_STATUS)) {
|
||||||
|
FURI_LOG_E(TAG, "DM SelectSubclass for read failed");
|
||||||
return false;
|
return false;
|
||||||
} else {
|
|
||||||
*(uint16_t*)operation_status = data;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
// Wait for data being loaded to MAC
|
||||||
|
furi_delay_us(BQ27220_SELECT_DELAY_US);
|
||||||
|
// Read id data from MAC scratch space
|
||||||
|
return bq27220_read_reg(handle, CommandMACData, (uint8_t*)gauging_status, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t bq27220_get_temperature(FuriHalI2cBusHandle* handle) {
|
uint16_t bq27220_get_temperature(FuriHalI2cBusHandle* handle) {
|
||||||
|
|||||||
@@ -1,3 +1,31 @@
|
|||||||
|
/**
|
||||||
|
* @file bq27220.h
|
||||||
|
*
|
||||||
|
* Quite problematic chip with quite bad documentation.
|
||||||
|
*
|
||||||
|
* Couple things to keep in mind:
|
||||||
|
*
|
||||||
|
* - Datasheet and technical reference manual are full of bullshit
|
||||||
|
* - bqstudio is ignoring them
|
||||||
|
* - bqstudio i2c exchange tracing gives some ideas on timings that works, but there is a catch
|
||||||
|
* - bqstudio timings contradicts to gm.fs file specification
|
||||||
|
* - it's impossible to reproduce all situations in bqstudio
|
||||||
|
* - experiments with blackbox can not cover all edge cases
|
||||||
|
* - final timings are kinda blend between all of those sources
|
||||||
|
* - device behavior differs depending on i2c clock speed
|
||||||
|
* - The Hero Himmel would not have used this gauge in the first place
|
||||||
|
*
|
||||||
|
* Couple advises if you'll need to modify this driver:
|
||||||
|
* - Reset and wait for INITCOMP if something is not right.
|
||||||
|
* - Do not do partial config update, it takes unpredictable amount of time to apply.
|
||||||
|
* - Don't forget to reset chip before writing new config.
|
||||||
|
* - If something fails at config update stage, wait for 4 seconds before doing next cycle.
|
||||||
|
* - If you can program and lock chip at factory stage - do it. It will save you a lot of time.
|
||||||
|
* - Keep sealed or strange things may happen.
|
||||||
|
* - There is a condition when it may stuck at INITCOMP state, just "press reset button".
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
@@ -9,26 +37,45 @@
|
|||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
// Low byte, Low bit first
|
// Low byte, Low bit first
|
||||||
bool DSG : 1; // The device is in DISCHARGE
|
uint8_t BATT_ID : 3; /**< Battery Identification */
|
||||||
bool SYSDWN : 1; // System down bit indicating the system should shut down
|
bool SNOOZE : 1; /**< SNOOZE mode is enabled */
|
||||||
bool TDA : 1; // Terminate Discharge Alarm
|
bool BCA : 1; /**< fuel gauge board calibration routine is active */
|
||||||
bool BATTPRES : 1; // Battery Present detected
|
bool CCA : 1; /**< Coulomb Counter Calibration routine is active */
|
||||||
bool AUTH_GD : 1; // Detect inserted battery
|
uint8_t RSVD0 : 2; /**< Reserved */
|
||||||
bool OCVGD : 1; // Good OCV measurement taken
|
|
||||||
bool TCA : 1; // Terminate Charge Alarm
|
|
||||||
bool RSVD : 1; // Reserved
|
|
||||||
// High byte, Low bit first
|
// High byte, Low bit first
|
||||||
bool CHGINH : 1; // Charge inhibit
|
uint8_t RSVD1; /**< Reserved */
|
||||||
bool FC : 1; // Full-charged is detected
|
} Bq27220ControlStatus;
|
||||||
bool OTD : 1; // Overtemperature in discharge condition is detected
|
|
||||||
bool OTC : 1; // Overtemperature in charge condition is detected
|
|
||||||
bool SLEEP : 1; // Device is operating in SLEEP mode when set
|
|
||||||
bool OCVFAIL : 1; // Status bit indicating that the OCV reading failed due to current
|
|
||||||
bool OCVCOMP : 1; // An OCV measurement update is complete
|
|
||||||
bool FD : 1; // Full-discharge is detected
|
|
||||||
} BatteryStatus;
|
|
||||||
|
|
||||||
_Static_assert(sizeof(BatteryStatus) == 2, "Incorrect structure size");
|
_Static_assert(sizeof(Bq27220ControlStatus) == 2, "Incorrect Bq27220ControlStatus structure size");
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
// Low byte, Low bit first
|
||||||
|
bool DSG : 1; /**< The device is in DISCHARGE */
|
||||||
|
bool SYSDWN : 1; /**< System down bit indicating the system should shut down */
|
||||||
|
bool TDA : 1; /**< Terminate Discharge Alarm */
|
||||||
|
bool BATTPRES : 1; /**< Battery Present detected */
|
||||||
|
bool AUTH_GD : 1; /**< Detect inserted battery */
|
||||||
|
bool OCVGD : 1; /**< Good OCV measurement taken */
|
||||||
|
bool TCA : 1; /**< Terminate Charge Alarm */
|
||||||
|
bool RSVD : 1; /**< Reserved */
|
||||||
|
// High byte, Low bit first
|
||||||
|
bool CHGINH : 1; /**< Charge inhibit */
|
||||||
|
bool FC : 1; /**< Full-charged is detected */
|
||||||
|
bool OTD : 1; /**< Overtemperature in discharge condition is detected */
|
||||||
|
bool OTC : 1; /**< Overtemperature in charge condition is detected */
|
||||||
|
bool SLEEP : 1; /**< Device is operating in SLEEP mode when set */
|
||||||
|
bool OCVFAIL : 1; /**< Status bit indicating that the OCV reading failed due to current */
|
||||||
|
bool OCVCOMP : 1; /**< An OCV measurement update is complete */
|
||||||
|
bool FD : 1; /**< Full-discharge is detected */
|
||||||
|
} Bq27220BatteryStatus;
|
||||||
|
|
||||||
|
_Static_assert(sizeof(Bq27220BatteryStatus) == 2, "Incorrect Bq27220BatteryStatus structure size");
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
Bq27220OperationStatusSecSealed = 0b11,
|
||||||
|
Bq27220OperationStatusSecUnsealed = 0b10,
|
||||||
|
Bq27220OperationStatusSecFull = 0b01,
|
||||||
|
} Bq27220OperationStatusSec;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
// Low byte, Low bit first
|
// Low byte, Low bit first
|
||||||
@@ -40,53 +87,189 @@ typedef struct {
|
|||||||
bool SMTH : 1; /**< RemainingCapacity is scaled by smooth engine */
|
bool SMTH : 1; /**< RemainingCapacity is scaled by smooth engine */
|
||||||
bool BTPINT : 1; /**< BTP threshold has been crossed */
|
bool BTPINT : 1; /**< BTP threshold has been crossed */
|
||||||
// High byte, Low bit first
|
// High byte, Low bit first
|
||||||
uint8_t RSVD1 : 2;
|
uint8_t RSVD1 : 2; /**< Reserved */
|
||||||
bool CFGUPDATE : 1; /**< Gauge is in CONFIG UPDATE mode */
|
bool CFGUPDATE : 1; /**< Gauge is in CONFIG UPDATE mode */
|
||||||
uint8_t RSVD0 : 5;
|
uint8_t RSVD0 : 5; /**< Reserved */
|
||||||
} OperationStatus;
|
} Bq27220OperationStatus;
|
||||||
|
|
||||||
_Static_assert(sizeof(OperationStatus) == 2, "Incorrect structure size");
|
_Static_assert(
|
||||||
|
sizeof(Bq27220OperationStatus) == 2,
|
||||||
|
"Incorrect Bq27220OperationStatus structure size");
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
// Low byte, Low bit first
|
||||||
|
bool FD : 1; /**< Full Discharge */
|
||||||
|
bool FC : 1; /**< Full Charge */
|
||||||
|
bool TD : 1; /**< Terminate Discharge */
|
||||||
|
bool TC : 1; /**< Terminate Charge */
|
||||||
|
bool RSVD0 : 1; /**< Reserved */
|
||||||
|
bool EDV : 1; /**< Cell voltage is above or below EDV0 threshold */
|
||||||
|
bool DSG : 1; /**< DISCHARGE or RELAXATION */
|
||||||
|
bool CF : 1; /**< Battery conditioning is needed */
|
||||||
|
// High byte, Low bit first
|
||||||
|
uint8_t RSVD1 : 2; /**< Reserved */
|
||||||
|
bool FCCX : 1; /**< fcc1hz clock going into CC: 0 = 1 Hz, 1 = 16 Hz*/
|
||||||
|
uint8_t RSVD2 : 2; /**< Reserved */
|
||||||
|
bool EDV1 : 1; /**< Cell voltage is above or below EDV1 threshold */
|
||||||
|
bool EDV2 : 1; /**< Cell voltage is above or below EDV2 threshold */
|
||||||
|
bool VDQ : 1; /**< Charge cycle FCC update qualification */
|
||||||
|
} Bq27220GaugingStatus;
|
||||||
|
|
||||||
|
_Static_assert(sizeof(Bq27220GaugingStatus) == 2, "Incorrect Bq27220GaugingStatus structure size");
|
||||||
|
|
||||||
typedef struct BQ27220DMData BQ27220DMData;
|
typedef struct BQ27220DMData BQ27220DMData;
|
||||||
|
|
||||||
/** Initialize Driver
|
/** Initialize Driver
|
||||||
* @return true on success, false otherwise
|
*
|
||||||
|
* This routine performs a lot of things under the hood:
|
||||||
|
* - Verifies that gauge is present on i2c bus and got correct ID(0220)
|
||||||
|
* - Unseals gauge
|
||||||
|
* - Checks various internal statuses
|
||||||
|
* - Checks that current profile is 0
|
||||||
|
* - Checks configuration again provided data_memory
|
||||||
|
* - Reset gauge if something on previous stages was fishy
|
||||||
|
* - Updates configuration if needed
|
||||||
|
* - Sealing gauge to prevent configuration and state from accidental damage
|
||||||
|
*
|
||||||
|
* @param handle The I2C Bus handle
|
||||||
|
* @param[in] data_memory The data memory to be uploaded into gauge
|
||||||
|
*
|
||||||
|
* @return true on success, false otherwise
|
||||||
*/
|
*/
|
||||||
bool bq27220_init(FuriHalI2cBusHandle* handle);
|
bool bq27220_init(FuriHalI2cBusHandle* handle, const BQ27220DMData* data_memory);
|
||||||
|
|
||||||
/** Initialize Driver
|
/** Reset gauge
|
||||||
* @return true on success, false otherwise
|
*
|
||||||
|
* @param handle The I2C Bus handle
|
||||||
|
*
|
||||||
|
* @return true on success, false otherwise
|
||||||
*/
|
*/
|
||||||
bool bq27220_apply_data_memory(FuriHalI2cBusHandle* handle, const BQ27220DMData* data_memory);
|
bool bq27220_reset(FuriHalI2cBusHandle* handle);
|
||||||
|
|
||||||
/** Get battery voltage in mV or error */
|
/** Seal gauge access
|
||||||
|
*
|
||||||
|
* @param handle The I2C Bus handle
|
||||||
|
*
|
||||||
|
* @return true on success, false otherwise
|
||||||
|
*/
|
||||||
|
bool bq27220_seal(FuriHalI2cBusHandle* handle);
|
||||||
|
|
||||||
|
/** Unseal gauge access
|
||||||
|
*
|
||||||
|
* @param handle The I2C Bus handle
|
||||||
|
*
|
||||||
|
* @return true on success, false otherwise
|
||||||
|
*/
|
||||||
|
bool bq27220_unseal(FuriHalI2cBusHandle* handle);
|
||||||
|
|
||||||
|
/** Get full access
|
||||||
|
*
|
||||||
|
* @warning must be done in unsealed state
|
||||||
|
*
|
||||||
|
* @param handle The I2C Bus handle
|
||||||
|
*
|
||||||
|
* @return true on success, false otherwise
|
||||||
|
*/
|
||||||
|
bool bq27220_full_access(FuriHalI2cBusHandle* handle);
|
||||||
|
|
||||||
|
/** Get battery voltage
|
||||||
|
*
|
||||||
|
* @param handle The I2C Bus handle
|
||||||
|
*
|
||||||
|
* @return voltage in mV or BQ27220_ERROR
|
||||||
|
*/
|
||||||
uint16_t bq27220_get_voltage(FuriHalI2cBusHandle* handle);
|
uint16_t bq27220_get_voltage(FuriHalI2cBusHandle* handle);
|
||||||
|
|
||||||
/** Get current in mA or error*/
|
/** Get current
|
||||||
|
*
|
||||||
|
* @param handle The I2C Bus handle
|
||||||
|
*
|
||||||
|
* @return current in mA or BQ27220_ERROR
|
||||||
|
*/
|
||||||
int16_t bq27220_get_current(FuriHalI2cBusHandle* handle);
|
int16_t bq27220_get_current(FuriHalI2cBusHandle* handle);
|
||||||
|
|
||||||
/** Get battery status */
|
/** Get control status
|
||||||
bool bq27220_get_battery_status(FuriHalI2cBusHandle* handle, BatteryStatus* battery_status);
|
*
|
||||||
|
* @param handle The handle
|
||||||
|
* @param control_status The control status
|
||||||
|
*
|
||||||
|
* @return true on success, false otherwise
|
||||||
|
*/
|
||||||
|
bool bq27220_get_control_status(FuriHalI2cBusHandle* handle, Bq27220ControlStatus* control_status);
|
||||||
|
|
||||||
/** Get operation status */
|
/** Get battery status
|
||||||
bool bq27220_get_operation_status(FuriHalI2cBusHandle* handle, OperationStatus* operation_status);
|
*
|
||||||
|
* @param handle The handle
|
||||||
|
* @param battery_status The battery status
|
||||||
|
*
|
||||||
|
* @return true on success, false otherwise
|
||||||
|
*/
|
||||||
|
bool bq27220_get_battery_status(FuriHalI2cBusHandle* handle, Bq27220BatteryStatus* battery_status);
|
||||||
|
|
||||||
/** Get temperature in units of 0.1°K */
|
/** Get operation status
|
||||||
|
*
|
||||||
|
* @param handle The handle
|
||||||
|
* @param operation_status The operation status
|
||||||
|
*
|
||||||
|
* @return true on success, false otherwise
|
||||||
|
*/
|
||||||
|
bool bq27220_get_operation_status(
|
||||||
|
FuriHalI2cBusHandle* handle,
|
||||||
|
Bq27220OperationStatus* operation_status);
|
||||||
|
|
||||||
|
/** Get gauging status
|
||||||
|
*
|
||||||
|
* @param handle The handle
|
||||||
|
* @param gauging_status The gauging status
|
||||||
|
*
|
||||||
|
* @return true on success, false otherwise
|
||||||
|
*/
|
||||||
|
bool bq27220_get_gauging_status(FuriHalI2cBusHandle* handle, Bq27220GaugingStatus* gauging_status);
|
||||||
|
|
||||||
|
/** Get temperature
|
||||||
|
*
|
||||||
|
* @param handle The I2C Bus handle
|
||||||
|
*
|
||||||
|
* @return temperature in units of 0.1°K
|
||||||
|
*/
|
||||||
uint16_t bq27220_get_temperature(FuriHalI2cBusHandle* handle);
|
uint16_t bq27220_get_temperature(FuriHalI2cBusHandle* handle);
|
||||||
|
|
||||||
/** Get compensated full charge capacity in in mAh */
|
/** Get compensated full charge capacity
|
||||||
|
*
|
||||||
|
* @param handle The I2C Bus handle
|
||||||
|
*
|
||||||
|
* @return full charge capacity in mAh or BQ27220_ERROR
|
||||||
|
*/
|
||||||
uint16_t bq27220_get_full_charge_capacity(FuriHalI2cBusHandle* handle);
|
uint16_t bq27220_get_full_charge_capacity(FuriHalI2cBusHandle* handle);
|
||||||
|
|
||||||
/** Get design capacity in mAh */
|
/** Get design capacity
|
||||||
|
*
|
||||||
|
* @param handle The I2C Bus handle
|
||||||
|
*
|
||||||
|
* @return design capacity in mAh or BQ27220_ERROR
|
||||||
|
*/
|
||||||
uint16_t bq27220_get_design_capacity(FuriHalI2cBusHandle* handle);
|
uint16_t bq27220_get_design_capacity(FuriHalI2cBusHandle* handle);
|
||||||
|
|
||||||
/** Get remaining capacity in in mAh */
|
/** Get remaining capacity
|
||||||
|
*
|
||||||
|
* @param handle The I2C Bus handle
|
||||||
|
*
|
||||||
|
* @return remaining capacity in mAh or BQ27220_ERROR
|
||||||
|
*/
|
||||||
uint16_t bq27220_get_remaining_capacity(FuriHalI2cBusHandle* handle);
|
uint16_t bq27220_get_remaining_capacity(FuriHalI2cBusHandle* handle);
|
||||||
|
|
||||||
/** Get predicted remaining battery capacity in percents */
|
/** Get predicted remaining battery capacity
|
||||||
|
*
|
||||||
|
* @param handle The I2C Bus handle
|
||||||
|
*
|
||||||
|
* @return state of charge in percents or BQ27220_ERROR
|
||||||
|
*/
|
||||||
uint16_t bq27220_get_state_of_charge(FuriHalI2cBusHandle* handle);
|
uint16_t bq27220_get_state_of_charge(FuriHalI2cBusHandle* handle);
|
||||||
|
|
||||||
/** Get ratio of full charge capacity over design capacity in percents */
|
/** Get ratio of full charge capacity over design capacity
|
||||||
|
*
|
||||||
|
* @param handle The I2C Bus handle
|
||||||
|
*
|
||||||
|
* @return state of health in percents or BQ27220_ERROR
|
||||||
|
*/
|
||||||
uint16_t bq27220_get_state_of_health(FuriHalI2cBusHandle* handle);
|
uint16_t bq27220_get_state_of_health(FuriHalI2cBusHandle* handle);
|
||||||
|
|
||||||
void bq27220_change_design_capacity(FuriHalI2cBusHandle* handle, uint16_t capacity);
|
|
||||||
|
|||||||
@@ -82,3 +82,5 @@ typedef struct {
|
|||||||
const bool SME0 : 1;
|
const bool SME0 : 1;
|
||||||
const uint8_t RSVD3 : 3;
|
const uint8_t RSVD3 : 3;
|
||||||
} BQ27220DMGaugingConfig;
|
} BQ27220DMGaugingConfig;
|
||||||
|
|
||||||
|
_Static_assert(sizeof(BQ27220DMGaugingConfig) == 2, "Incorrect structure size");
|
||||||
|
|||||||
@@ -1,68 +1,76 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#define BQ27220_ADDRESS 0xAA
|
#define BQ27220_ADDRESS (0xAAu)
|
||||||
#define BQ27220_I2C_TIMEOUT 50
|
#define BQ27220_I2C_TIMEOUT (50u)
|
||||||
|
|
||||||
#define CommandControl 0x00
|
#define CommandControl (0x00u)
|
||||||
#define CommandAtRate 0x02
|
#define CommandAtRate (0x02u)
|
||||||
#define CommandAtRateTimeToEmpty 0x04
|
#define CommandAtRateTimeToEmpty (0x04u)
|
||||||
#define CommandTemperature 0x06
|
#define CommandTemperature (0x06u)
|
||||||
#define CommandVoltage 0x08
|
#define CommandVoltage (0x08u)
|
||||||
#define CommandBatteryStatus 0x0A
|
#define CommandBatteryStatus (0x0Au)
|
||||||
#define CommandCurrent 0x0C
|
#define CommandCurrent (0x0Cu)
|
||||||
#define CommandRemainingCapacity 0x10
|
#define CommandRemainingCapacity (0x10u)
|
||||||
#define CommandFullChargeCapacity 0x12
|
#define CommandFullChargeCapacity (0x12u)
|
||||||
#define CommandAverageCurrent 0x14
|
#define CommandAverageCurrent (0x14u)
|
||||||
#define CommandTimeToEmpty 0x16
|
#define CommandTimeToEmpty (0x16u)
|
||||||
#define CommandTimeToFull 0x18
|
#define CommandTimeToFull (0x18u)
|
||||||
#define CommandStandbyCurrent 0x1A
|
#define CommandStandbyCurrent (0x1Au)
|
||||||
#define CommandStandbyTimeToEmpty 0x1C
|
#define CommandStandbyTimeToEmpty (0x1Cu)
|
||||||
#define CommandMaxLoadCurrent 0x1E
|
#define CommandMaxLoadCurrent (0x1Eu)
|
||||||
#define CommandMaxLoadTimeToEmpty 0x20
|
#define CommandMaxLoadTimeToEmpty (0x20u)
|
||||||
#define CommandRawCoulombCount 0x22
|
#define CommandRawCoulombCount (0x22u)
|
||||||
#define CommandAveragePower 0x24
|
#define CommandAveragePower (0x24u)
|
||||||
#define CommandInternalTemperature 0x28
|
#define CommandInternalTemperature (0x28u)
|
||||||
#define CommandCycleCount 0x2A
|
#define CommandCycleCount (0x2Au)
|
||||||
#define CommandStateOfCharge 0x2C
|
#define CommandStateOfCharge (0x2Cu)
|
||||||
#define CommandStateOfHealth 0x2E
|
#define CommandStateOfHealth (0x2Eu)
|
||||||
#define CommandChargeVoltage 0x30
|
#define CommandChargeVoltage (0x30u)
|
||||||
#define CommandChargeCurrent 0x32
|
#define CommandChargeCurrent (0x32u)
|
||||||
#define CommandBTPDischargeSet 0x34
|
#define CommandBTPDischargeSet (0x34u)
|
||||||
#define CommandBTPChargeSet 0x36
|
#define CommandBTPChargeSet (0x36u)
|
||||||
#define CommandOperationStatus 0x3A
|
#define CommandOperationStatus (0x3Au)
|
||||||
#define CommandDesignCapacity 0x3C
|
#define CommandDesignCapacity (0x3Cu)
|
||||||
#define CommandSelectSubclass 0x3E
|
#define CommandSelectSubclass (0x3Eu)
|
||||||
#define CommandMACData 0x40
|
#define CommandMACData (0x40u)
|
||||||
#define CommandMACDataSum 0x60
|
#define CommandMACDataSum (0x60u)
|
||||||
#define CommandMACDataLen 0x61
|
#define CommandMACDataLen (0x61u)
|
||||||
#define CommandAnalogCount 0x79
|
#define CommandAnalogCount (0x79u)
|
||||||
#define CommandRawCurrent 0x7A
|
#define CommandRawCurrent (0x7Au)
|
||||||
#define CommandRawVoltage 0x7C
|
#define CommandRawVoltage (0x7Cu)
|
||||||
#define CommandRawIntTemp 0x7E
|
#define CommandRawIntTemp (0x7Eu)
|
||||||
|
|
||||||
#define Control_CONTROL_STATUS 0x0000
|
#define Control_CONTROL_STATUS (0x0000u)
|
||||||
#define Control_DEVICE_NUMBER 0x0001
|
#define Control_DEVICE_NUMBER (0x0001u)
|
||||||
#define Control_FW_VERSION 0x0002
|
#define Control_FW_VERSION (0x0002u)
|
||||||
#define Control_BOARD_OFFSET 0x0009
|
#define Control_HW_VERSION (0x0003u)
|
||||||
#define Control_CC_OFFSET 0x000A
|
#define Control_BOARD_OFFSET (0x0009u)
|
||||||
#define Control_CC_OFFSET_SAVE 0x000B
|
#define Control_CC_OFFSET (0x000Au)
|
||||||
#define Control_OCV_CMD 0x000C
|
#define Control_CC_OFFSET_SAVE (0x000Bu)
|
||||||
#define Control_BAT_INSERT 0x000D
|
#define Control_OCV_CMD (0x000Cu)
|
||||||
#define Control_BAT_REMOVE 0x000E
|
#define Control_BAT_INSERT (0x000Du)
|
||||||
#define Control_SET_SNOOZE 0x0013
|
#define Control_BAT_REMOVE (0x000Eu)
|
||||||
#define Control_CLEAR_SNOOZE 0x0014
|
#define Control_SET_SNOOZE (0x0013u)
|
||||||
#define Control_SET_PROFILE_1 0x0015
|
#define Control_CLEAR_SNOOZE (0x0014u)
|
||||||
#define Control_SET_PROFILE_2 0x0016
|
#define Control_SET_PROFILE_1 (0x0015u)
|
||||||
#define Control_SET_PROFILE_3 0x0017
|
#define Control_SET_PROFILE_2 (0x0016u)
|
||||||
#define Control_SET_PROFILE_4 0x0018
|
#define Control_SET_PROFILE_3 (0x0017u)
|
||||||
#define Control_SET_PROFILE_5 0x0019
|
#define Control_SET_PROFILE_4 (0x0018u)
|
||||||
#define Control_SET_PROFILE_6 0x001A
|
#define Control_SET_PROFILE_5 (0x0019u)
|
||||||
#define Control_CAL_TOGGLE 0x002D
|
#define Control_SET_PROFILE_6 (0x001Au)
|
||||||
#define Control_SEALED 0x0030
|
#define Control_CAL_TOGGLE (0x002Du)
|
||||||
#define Control_RESET 0x0041
|
#define Control_SEALED (0x0030u)
|
||||||
#define Control_EXIT_CAL 0x0080
|
#define Control_RESET (0x0041u)
|
||||||
#define Control_ENTER_CAL 0x0081
|
#define Control_OERATION_STATUS (0x0054u)
|
||||||
#define Control_ENTER_CFG_UPDATE 0x0090
|
#define Control_GAUGING_STATUS (0x0056u)
|
||||||
#define Control_EXIT_CFG_UPDATE_REINIT 0x0091
|
#define Control_EXIT_CAL (0x0080u)
|
||||||
#define Control_EXIT_CFG_UPDATE 0x0092
|
#define Control_ENTER_CAL (0x0081u)
|
||||||
#define Control_RETURN_TO_ROM 0x0F00
|
#define Control_ENTER_CFG_UPDATE (0x0090u)
|
||||||
|
#define Control_EXIT_CFG_UPDATE_REINIT (0x0091u)
|
||||||
|
#define Control_EXIT_CFG_UPDATE (0x0092u)
|
||||||
|
#define Control_RETURN_TO_ROM (0x0F00u)
|
||||||
|
|
||||||
|
#define UnsealKey1 (0x0414u)
|
||||||
|
#define UnsealKey2 (0x3672u)
|
||||||
|
|
||||||
|
#define FullAccessKey (0xffffu)
|
||||||
|
|||||||
@@ -335,8 +335,8 @@ bool protocol_em4100_write_data(ProtocolEM4100* protocol, void* data) {
|
|||||||
request->t5577.block[0] =
|
request->t5577.block[0] =
|
||||||
(LFRFID_T5577_MODULATION_MANCHESTER | protocol_em4100_get_t5577_bitrate(protocol) |
|
(LFRFID_T5577_MODULATION_MANCHESTER | protocol_em4100_get_t5577_bitrate(protocol) |
|
||||||
(2 << LFRFID_T5577_MAXBLOCK_SHIFT));
|
(2 << LFRFID_T5577_MAXBLOCK_SHIFT));
|
||||||
request->t5577.block[1] = protocol->encoded_data;
|
request->t5577.block[1] = protocol->encoded_data >> 32;
|
||||||
request->t5577.block[2] = protocol->encoded_data >> 32;
|
request->t5577.block[2] = protocol->encoded_data;
|
||||||
request->t5577.blocks_to_write = 3;
|
request->t5577.blocks_to_write = 3;
|
||||||
result = true;
|
result = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
// TODO: Store target key in CUID dictionary
|
// TODO: Store target key in CUID dictionary
|
||||||
// TODO: Fix rare nested_target_key 64 bug
|
// 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)
|
||||||
|
|
||||||
#define MF_CLASSIC_MAX_BUFF_SIZE (64)
|
#define MF_CLASSIC_MAX_BUFF_SIZE (64)
|
||||||
|
|
||||||
@@ -580,7 +581,7 @@ NfcCommand mf_classic_poller_handler_analyze_backdoor(MfClassicPoller* instance)
|
|||||||
size_t next_key_index = (current_key_index + 1) % mf_classic_backdoor_keys_count;
|
size_t next_key_index = (current_key_index + 1) % mf_classic_backdoor_keys_count;
|
||||||
uint8_t backdoor_version = mf_classic_backdoor_keys[next_key_index].type - 1;
|
uint8_t backdoor_version = mf_classic_backdoor_keys[next_key_index].type - 1;
|
||||||
|
|
||||||
FURI_LOG_E(TAG, "Trying backdoor v%d", backdoor_version);
|
FURI_LOG_D(TAG, "Trying backdoor v%d", backdoor_version);
|
||||||
dict_attack_ctx->current_key = mf_classic_backdoor_keys[next_key_index].key;
|
dict_attack_ctx->current_key = mf_classic_backdoor_keys[next_key_index].key;
|
||||||
|
|
||||||
// Attempt backdoor authentication
|
// Attempt backdoor authentication
|
||||||
@@ -588,11 +589,11 @@ NfcCommand mf_classic_poller_handler_analyze_backdoor(MfClassicPoller* instance)
|
|||||||
instance, 0, &dict_attack_ctx->current_key, MfClassicKeyTypeA, NULL, true);
|
instance, 0, &dict_attack_ctx->current_key, MfClassicKeyTypeA, NULL, true);
|
||||||
if((next_key_index == 0) &&
|
if((next_key_index == 0) &&
|
||||||
(error == MfClassicErrorProtocol || error == MfClassicErrorTimeout)) {
|
(error == MfClassicErrorProtocol || error == MfClassicErrorTimeout)) {
|
||||||
FURI_LOG_E(TAG, "No backdoor identified");
|
FURI_LOG_D(TAG, "No backdoor identified");
|
||||||
dict_attack_ctx->backdoor = MfClassicBackdoorNone;
|
dict_attack_ctx->backdoor = MfClassicBackdoorNone;
|
||||||
instance->state = MfClassicPollerStateRequestKey;
|
instance->state = MfClassicPollerStateRequestKey;
|
||||||
} else if(error == MfClassicErrorNone) {
|
} else if(error == MfClassicErrorNone) {
|
||||||
FURI_LOG_E(TAG, "Backdoor identified: v%d", backdoor_version);
|
FURI_LOG_I(TAG, "Backdoor identified: v%d", backdoor_version);
|
||||||
dict_attack_ctx->backdoor = mf_classic_backdoor_keys[next_key_index].type;
|
dict_attack_ctx->backdoor = mf_classic_backdoor_keys[next_key_index].type;
|
||||||
instance->state = MfClassicPollerStateBackdoorReadSector;
|
instance->state = MfClassicPollerStateBackdoorReadSector;
|
||||||
} else if(
|
} else if(
|
||||||
@@ -606,6 +607,7 @@ NfcCommand mf_classic_poller_handler_analyze_backdoor(MfClassicPoller* instance)
|
|||||||
}
|
}
|
||||||
|
|
||||||
NfcCommand mf_classic_poller_handler_backdoor_read_sector(MfClassicPoller* instance) {
|
NfcCommand mf_classic_poller_handler_backdoor_read_sector(MfClassicPoller* instance) {
|
||||||
|
// TODO: Reauth not needed
|
||||||
NfcCommand command = NfcCommandContinue;
|
NfcCommand command = NfcCommandContinue;
|
||||||
MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx;
|
MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx;
|
||||||
MfClassicError error = MfClassicErrorNone;
|
MfClassicError error = MfClassicErrorNone;
|
||||||
@@ -1071,12 +1073,10 @@ NfcCommand mf_classic_poller_handler_nested_analyze_prng(MfClassicPoller* instan
|
|||||||
|
|
||||||
if(hard_nt_count >= MF_CLASSIC_NESTED_NT_HARD_MINIMUM) {
|
if(hard_nt_count >= MF_CLASSIC_NESTED_NT_HARD_MINIMUM) {
|
||||||
dict_attack_ctx->prng_type = MfClassicPrngTypeHard;
|
dict_attack_ctx->prng_type = MfClassicPrngTypeHard;
|
||||||
// FIXME: E -> D
|
FURI_LOG_D(TAG, "Detected Hard PRNG");
|
||||||
FURI_LOG_E(TAG, "Detected Hard PRNG");
|
|
||||||
} else {
|
} else {
|
||||||
dict_attack_ctx->prng_type = MfClassicPrngTypeWeak;
|
dict_attack_ctx->prng_type = MfClassicPrngTypeWeak;
|
||||||
// FIXME: E -> D
|
FURI_LOG_D(TAG, "Detected Weak PRNG");
|
||||||
FURI_LOG_E(TAG, "Detected Weak PRNG");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
instance->state = MfClassicPollerStateNestedController;
|
instance->state = MfClassicPollerStateNestedController;
|
||||||
@@ -1092,11 +1092,9 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt(MfClassicPoller* instance
|
|||||||
|
|
||||||
if(error != MfClassicErrorNone) {
|
if(error != MfClassicErrorNone) {
|
||||||
dict_attack_ctx->prng_type = MfClassicPrngTypeNoTag;
|
dict_attack_ctx->prng_type = MfClassicPrngTypeNoTag;
|
||||||
// FIXME: E -> D
|
|
||||||
FURI_LOG_E(TAG, "Failed to collect nt");
|
FURI_LOG_E(TAG, "Failed to collect nt");
|
||||||
} else {
|
} else {
|
||||||
// FIXME: E -> D
|
FURI_LOG_T(TAG, "nt: %02x%02x%02x%02x", nt.data[0], nt.data[1], nt.data[2], nt.data[3]);
|
||||||
FURI_LOG_E(TAG, "nt: %02x%02x%02x%02x", nt.data[0], nt.data[1], nt.data[2], nt.data[3]);
|
|
||||||
uint32_t nt_data = bit_lib_bytes_to_num_be(nt.data, sizeof(MfClassicNt));
|
uint32_t nt_data = bit_lib_bytes_to_num_be(nt.data, sizeof(MfClassicNt));
|
||||||
if(!add_nested_nonce(
|
if(!add_nested_nonce(
|
||||||
&dict_attack_ctx->nested_nonce,
|
&dict_attack_ctx->nested_nonce,
|
||||||
@@ -1150,7 +1148,7 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance)
|
|||||||
return command;
|
return command;
|
||||||
}
|
}
|
||||||
|
|
||||||
FURI_LOG_E(TAG, "Full authentication successful");
|
FURI_LOG_D(TAG, "Full authentication successful");
|
||||||
|
|
||||||
nt_prev = bit_lib_bytes_to_num_be(auth_ctx.nt.data, sizeof(MfClassicNt));
|
nt_prev = bit_lib_bytes_to_num_be(auth_ctx.nt.data, sizeof(MfClassicNt));
|
||||||
|
|
||||||
@@ -1183,7 +1181,7 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance)
|
|||||||
|
|
||||||
dict_attack_ctx->calibrated = true;
|
dict_attack_ctx->calibrated = true;
|
||||||
|
|
||||||
FURI_LOG_E(TAG, "Static encrypted tag calibrated. Decrypted nonce: %08lx", nt_enc);
|
FURI_LOG_D(TAG, "Static encrypted tag calibrated. Decrypted nonce: %08lx", nt_enc);
|
||||||
|
|
||||||
instance->state = MfClassicPollerStateNestedController;
|
instance->state = MfClassicPollerStateNestedController;
|
||||||
return command;
|
return command;
|
||||||
@@ -1226,14 +1224,14 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(dict_attack_ctx->static_encrypted) {
|
if(dict_attack_ctx->static_encrypted) {
|
||||||
FURI_LOG_E(TAG, "Static encrypted nonce detected");
|
FURI_LOG_D(TAG, "Static encrypted nonce detected");
|
||||||
dict_attack_ctx->calibrated = true;
|
dict_attack_ctx->calibrated = true;
|
||||||
instance->state = MfClassicPollerStateNestedController;
|
instance->state = MfClassicPollerStateNestedController;
|
||||||
return command;
|
return command;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the distance between each nonce
|
// Find the distance between each nonce
|
||||||
FURI_LOG_E(TAG, "Calculating distance between nonces");
|
FURI_LOG_D(TAG, "Calculating distance between nonces");
|
||||||
uint64_t known_key = bit_lib_bytes_to_num_be(dict_attack_ctx->nested_known_key.data, 6);
|
uint64_t known_key = bit_lib_bytes_to_num_be(dict_attack_ctx->nested_known_key.data, 6);
|
||||||
uint8_t valid_distances = 0;
|
uint8_t valid_distances = 0;
|
||||||
for(uint32_t collection_cycle = 1; collection_cycle < MF_CLASSIC_NESTED_CALIBRATION_COUNT;
|
for(uint32_t collection_cycle = 1; collection_cycle < MF_CLASSIC_NESTED_CALIBRATION_COUNT;
|
||||||
@@ -1244,8 +1242,8 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance)
|
|||||||
for(int i = 0; i < 65535; i++) {
|
for(int i = 0; i < 65535; i++) {
|
||||||
uint32_t nth_successor = crypto1_prng_successor(nt_prev, i);
|
uint32_t nth_successor = crypto1_prng_successor(nt_prev, i);
|
||||||
if(nth_successor == decrypted_nt_enc) {
|
if(nth_successor == decrypted_nt_enc) {
|
||||||
FURI_LOG_E(TAG, "nt_enc (plain) %08lx", nth_successor);
|
FURI_LOG_D(TAG, "nt_enc (plain) %08lx", nth_successor);
|
||||||
FURI_LOG_E(TAG, "dist from nt prev: %i", i);
|
FURI_LOG_D(TAG, "dist from nt prev: %i", i);
|
||||||
distances[valid_distances++] = i;
|
distances[valid_distances++] = i;
|
||||||
nt_prev = nth_successor;
|
nt_prev = nth_successor;
|
||||||
found = true;
|
found = true;
|
||||||
@@ -1312,7 +1310,7 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance)
|
|||||||
|
|
||||||
mf_classic_poller_halt(instance);
|
mf_classic_poller_halt(instance);
|
||||||
uint16_t d_dist = dict_attack_ctx->d_max - dict_attack_ctx->d_min;
|
uint16_t d_dist = dict_attack_ctx->d_max - dict_attack_ctx->d_min;
|
||||||
FURI_LOG_E(
|
FURI_LOG_D(
|
||||||
TAG,
|
TAG,
|
||||||
"Calibration completed: min=%u max=%u static=%s",
|
"Calibration completed: min=%u max=%u static=%s",
|
||||||
dict_attack_ctx->d_min,
|
dict_attack_ctx->d_min,
|
||||||
@@ -1375,7 +1373,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
FURI_LOG_E(TAG, "Full authentication successful");
|
FURI_LOG_D(TAG, "Full authentication successful");
|
||||||
|
|
||||||
// Step 2: Perform nested authentication a variable number of times to get nt_enc at a different PRNG offset
|
// Step 2: Perform nested authentication a variable number of times to get nt_enc at a different PRNG offset
|
||||||
// eg. Collect most commonly observed nonce from 3 auths to known sector and 4th to target, then separately the
|
// eg. Collect most commonly observed nonce from 3 auths to known sector and 4th to target, then separately the
|
||||||
@@ -1429,7 +1427,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst
|
|||||||
// Ensure this isn't the same nonce as the previous collection
|
// Ensure this isn't the same nonce as the previous collection
|
||||||
if((dict_attack_ctx->nested_nonce.count == 1) &&
|
if((dict_attack_ctx->nested_nonce.count == 1) &&
|
||||||
(dict_attack_ctx->nested_nonce.nonces[0].nt_enc == nt_enc)) {
|
(dict_attack_ctx->nested_nonce.nonces[0].nt_enc == nt_enc)) {
|
||||||
FURI_LOG_E(TAG, "Duplicate nonce, dismissing collection attempt");
|
FURI_LOG_D(TAG, "Duplicate nonce, dismissing collection attempt");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1448,7 +1446,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst
|
|||||||
nth_successor, nth_successor ^ nt_enc, parity)) {
|
nth_successor, nth_successor ^ nt_enc, parity)) {
|
||||||
found_nt_cnt++;
|
found_nt_cnt++;
|
||||||
if(found_nt_cnt > 1) {
|
if(found_nt_cnt > 1) {
|
||||||
FURI_LOG_E(TAG, "Ambiguous nonce, dismissing collection attempt");
|
FURI_LOG_D(TAG, "Ambiguous nonce, dismissing collection attempt");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
found_nt = nth_successor;
|
found_nt = nth_successor;
|
||||||
@@ -1489,24 +1487,24 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst
|
|||||||
FURI_LOG_E(TAG, "Failed to add nested nonce to array. OOM?");
|
FURI_LOG_E(TAG, "Failed to add nested nonce to array. OOM?");
|
||||||
}
|
}
|
||||||
|
|
||||||
FURI_LOG_E(
|
FURI_LOG_D(
|
||||||
TAG,
|
TAG,
|
||||||
"Target: %u (nonce pair %u, key type %s, block %u)",
|
"Target: %u (nonce pair %u, key type %s, block %u)",
|
||||||
dict_attack_ctx->nested_target_key,
|
dict_attack_ctx->nested_target_key,
|
||||||
nonce_pair_index,
|
nonce_pair_index,
|
||||||
(target_key_type == MfClassicKeyTypeA) ? "A" : "B",
|
(target_key_type == MfClassicKeyTypeA) ? "A" : "B",
|
||||||
target_block);
|
target_block);
|
||||||
FURI_LOG_E(TAG, "cuid: %08lx", cuid);
|
FURI_LOG_T(TAG, "cuid: %08lx", cuid);
|
||||||
FURI_LOG_E(TAG, "nt_enc: %08lx", nt_enc);
|
FURI_LOG_T(TAG, "nt_enc: %08lx", nt_enc);
|
||||||
FURI_LOG_E(
|
FURI_LOG_T(
|
||||||
TAG,
|
TAG,
|
||||||
"parity: %u%u%u%u",
|
"parity: %u%u%u%u",
|
||||||
((parity >> 3) & 1),
|
((parity >> 3) & 1),
|
||||||
((parity >> 2) & 1),
|
((parity >> 2) & 1),
|
||||||
((parity >> 1) & 1),
|
((parity >> 1) & 1),
|
||||||
(parity & 1));
|
(parity & 1));
|
||||||
FURI_LOG_E(TAG, "nt_enc prev: %08lx", nt_prev);
|
FURI_LOG_T(TAG, "nt_enc prev: %08lx", nt_prev);
|
||||||
FURI_LOG_E(TAG, "nt_enc prev decrypted: %08lx", decrypted_nt_prev);
|
FURI_LOG_T(TAG, "nt_enc prev decrypted: %08lx", decrypted_nt_prev);
|
||||||
} while(false);
|
} while(false);
|
||||||
|
|
||||||
instance->state = MfClassicPollerStateNestedController;
|
instance->state = MfClassicPollerStateNestedController;
|
||||||
@@ -1609,7 +1607,7 @@ NfcCommand mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instanc
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
FURI_LOG_E(TAG, "Full authentication successful");
|
FURI_LOG_D(TAG, "Full authentication successful");
|
||||||
|
|
||||||
// Step 2: Collect nested nt and parity
|
// Step 2: Collect nested nt and parity
|
||||||
error = mf_classic_poller_auth_nested(
|
error = mf_classic_poller_auth_nested(
|
||||||
@@ -1661,7 +1659,7 @@ NfcCommand mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instanc
|
|||||||
dict_attack_ctx->mf_classic_user_dict,
|
dict_attack_ctx->mf_classic_user_dict,
|
||||||
is_weak);
|
is_weak);
|
||||||
if(key_candidate != NULL) {
|
if(key_candidate != NULL) {
|
||||||
FURI_LOG_E(
|
FURI_LOG_I(
|
||||||
TAG,
|
TAG,
|
||||||
"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)));
|
||||||
@@ -1677,7 +1675,7 @@ NfcCommand mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instanc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FURI_LOG_E(
|
FURI_LOG_D(
|
||||||
TAG,
|
TAG,
|
||||||
"Target: %u (key type %s, block %u) cuid: %08lx",
|
"Target: %u (key type %s, block %u) cuid: %08lx",
|
||||||
dict_attack_ctx->nested_target_key,
|
dict_attack_ctx->nested_target_key,
|
||||||
@@ -1858,6 +1856,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance
|
|||||||
} else if(dict_attack_ctx->prng_type == MfClassicPrngTypeNoTag) {
|
} else if(dict_attack_ctx->prng_type == MfClassicPrngTypeNoTag) {
|
||||||
FURI_LOG_E(TAG, "No tag detected");
|
FURI_LOG_E(TAG, "No tag detected");
|
||||||
// Free nonce array
|
// Free nonce array
|
||||||
|
// TODO: Consider using .count here
|
||||||
if(dict_attack_ctx->nested_nonce.nonces) {
|
if(dict_attack_ctx->nested_nonce.nonces) {
|
||||||
free(dict_attack_ctx->nested_nonce.nonces);
|
free(dict_attack_ctx->nested_nonce.nonces);
|
||||||
dict_attack_ctx->nested_nonce.nonces = NULL;
|
dict_attack_ctx->nested_nonce.nonces = NULL;
|
||||||
@@ -1868,6 +1867,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance
|
|||||||
}
|
}
|
||||||
if(dict_attack_ctx->nested_nonce.nonces) {
|
if(dict_attack_ctx->nested_nonce.nonces) {
|
||||||
// Free nonce array
|
// Free nonce array
|
||||||
|
// TODO: Consider using .count here
|
||||||
free(dict_attack_ctx->nested_nonce.nonces);
|
free(dict_attack_ctx->nested_nonce.nonces);
|
||||||
dict_attack_ctx->nested_nonce.nonces = NULL;
|
dict_attack_ctx->nested_nonce.nonces = NULL;
|
||||||
dict_attack_ctx->nested_nonce.count = 0;
|
dict_attack_ctx->nested_nonce.count = 0;
|
||||||
@@ -1881,15 +1881,19 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance
|
|||||||
(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 == MfClassicNestedPhaseDictAttackResume) {
|
||||||
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)) {
|
||||||
instance->state = MfClassicPollerStateNestedDictAttack;
|
instance->state = MfClassicPollerStateNestedDictAttack;
|
||||||
return command;
|
return command;
|
||||||
} else {
|
} else {
|
||||||
dict_attack_ctx->auth_passed = true;
|
dict_attack_ctx->auth_passed = true;
|
||||||
furi_assert(dict_attack_ctx->nested_nonce.nonces);
|
if(dict_attack_ctx->nested_nonce.count > 0) {
|
||||||
free(dict_attack_ctx->nested_nonce.nonces);
|
// Free nonce array
|
||||||
dict_attack_ctx->nested_nonce.nonces = NULL;
|
furi_assert(dict_attack_ctx->nested_nonce.nonces);
|
||||||
dict_attack_ctx->nested_nonce.count = 0;
|
free(dict_attack_ctx->nested_nonce.nonces);
|
||||||
|
dict_attack_ctx->nested_nonce.nonces = NULL;
|
||||||
|
dict_attack_ctx->nested_nonce.count = 0;
|
||||||
|
}
|
||||||
dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttack;
|
dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttack;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1942,7 +1946,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance
|
|||||||
dict_attack_ctx->nested_target_key = 0;
|
dict_attack_ctx->nested_target_key = 0;
|
||||||
if(mf_classic_is_card_read(instance->data)) {
|
if(mf_classic_is_card_read(instance->data)) {
|
||||||
// All keys have been collected
|
// All keys have been collected
|
||||||
FURI_LOG_E(TAG, "All keys collected and sectors read");
|
FURI_LOG_D(TAG, "All keys collected and sectors read");
|
||||||
dict_attack_ctx->nested_phase = MfClassicNestedPhaseFinished;
|
dict_attack_ctx->nested_phase = MfClassicNestedPhaseFinished;
|
||||||
instance->state = MfClassicPollerStateSuccess;
|
instance->state = MfClassicPollerStateSuccess;
|
||||||
return command;
|
return command;
|
||||||
@@ -2074,7 +2078,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance
|
|||||||
(!(is_weak) &&
|
(!(is_weak) &&
|
||||||
(dict_attack_ctx->attempt_count >= MF_CLASSIC_NESTED_HARD_RETRY_MAXIMUM))) {
|
(dict_attack_ctx->attempt_count >= MF_CLASSIC_NESTED_HARD_RETRY_MAXIMUM))) {
|
||||||
// Unpredictable, skip
|
// Unpredictable, skip
|
||||||
FURI_LOG_E(TAG, "Failed to collect nonce, skipping key");
|
FURI_LOG_W(TAG, "Failed to collect nonce, skipping key");
|
||||||
if(dict_attack_ctx->nested_nonce.nonces) {
|
if(dict_attack_ctx->nested_nonce.nonces) {
|
||||||
free(dict_attack_ctx->nested_nonce.nonces);
|
free(dict_attack_ctx->nested_nonce.nonces);
|
||||||
dict_attack_ctx->nested_nonce.nonces = NULL;
|
dict_attack_ctx->nested_nonce.nonces = NULL;
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
/** Bit Buffer
|
||||||
|
*
|
||||||
|
* Various bits and bytes manipulation tools.
|
||||||
|
*
|
||||||
|
* @file bit_buffer.h
|
||||||
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
@@ -10,118 +16,128 @@ extern "C" {
|
|||||||
|
|
||||||
typedef struct BitBuffer BitBuffer;
|
typedef struct BitBuffer BitBuffer;
|
||||||
|
|
||||||
// Construction, deletion, reset
|
/** Allocate a BitBuffer instance.
|
||||||
|
|
||||||
/**
|
|
||||||
* Allocate a BitBuffer instance.
|
|
||||||
*
|
*
|
||||||
* @param [in] capacity_bytes maximum buffer capacity, in bytes
|
* @param[in] capacity_bytes maximum buffer capacity, in bytes
|
||||||
* @return pointer to the allocated BitBuffer instance
|
*
|
||||||
|
* @return pointer to the allocated BitBuffer instance
|
||||||
*/
|
*/
|
||||||
BitBuffer* bit_buffer_alloc(size_t capacity_bytes);
|
BitBuffer* bit_buffer_alloc(size_t capacity_bytes);
|
||||||
|
|
||||||
/**
|
/** Delete a BitBuffer instance.
|
||||||
* Delete a BitBuffer instance.
|
|
||||||
*
|
*
|
||||||
* @param [in,out] buf pointer to a BitBuffer instance
|
* @param[in,out] buf pointer to a BitBuffer instance
|
||||||
*/
|
*/
|
||||||
void bit_buffer_free(BitBuffer* buf);
|
void bit_buffer_free(BitBuffer* buf);
|
||||||
|
|
||||||
/**
|
/** Clear all data from a BitBuffer instance.
|
||||||
* Clear all data from a BitBuffer instance.
|
|
||||||
*
|
*
|
||||||
* @param [in,out] buf pointer to a BitBuffer instance
|
* @param[in,out] buf pointer to a BitBuffer instance
|
||||||
*/
|
*/
|
||||||
void bit_buffer_reset(BitBuffer* buf);
|
void bit_buffer_reset(BitBuffer* buf);
|
||||||
|
|
||||||
// Copy and write
|
// Copy and write
|
||||||
|
|
||||||
/**
|
/** Copy another BitBuffer instance's contents to this one, replacing all of the
|
||||||
* Copy another BitBuffer instance's contents to this one, replacing
|
* original data.
|
||||||
* all of the original data.
|
|
||||||
* The destination capacity must be no less than the source data size.
|
|
||||||
*
|
*
|
||||||
* @param [in,out] buf pointer to a BitBuffer instance to copy into
|
* @warning The destination capacity must be no less than the source data
|
||||||
* @param [in] other pointer to a BitBuffer instance to copy from
|
* size.
|
||||||
* @note
|
*
|
||||||
|
* @param[in,out] buf pointer to a BitBuffer instance to copy into
|
||||||
|
* @param[in] other pointer to a BitBuffer instance to copy from
|
||||||
*/
|
*/
|
||||||
void bit_buffer_copy(BitBuffer* buf, const BitBuffer* other);
|
void bit_buffer_copy(BitBuffer* buf, const BitBuffer* other);
|
||||||
|
|
||||||
/**
|
/** Copy all BitBuffer instance's contents to this one, starting from
|
||||||
* Copy all BitBuffer instance's contents to this one, starting from start_index,
|
* start_index, replacing all of the original data.
|
||||||
* replacing all of the original data.
|
|
||||||
* The destination capacity must be no less than the source data size
|
|
||||||
* counting from start_index.
|
|
||||||
*
|
*
|
||||||
* @param [in,out] buf pointer to a BitBuffer instance to copy into
|
* @warning The destination capacity must be no less than the source data
|
||||||
* @param [in] other pointer to a BitBuffer instance to copy from
|
* size counting from start_index.
|
||||||
* @param [in] start_index index to begin copying source data from
|
*
|
||||||
|
* @param[in,out] buf pointer to a BitBuffer instance to copy into
|
||||||
|
* @param[in] other pointer to a BitBuffer instance to copy from
|
||||||
|
* @param[in] start_index index to begin copying source data from
|
||||||
*/
|
*/
|
||||||
void bit_buffer_copy_right(BitBuffer* buf, const BitBuffer* other, size_t start_index);
|
void bit_buffer_copy_right(BitBuffer* buf, const BitBuffer* other, size_t start_index);
|
||||||
|
|
||||||
/**
|
/** Copy all BitBuffer instance's contents to this one, ending with end_index,
|
||||||
* Copy all BitBuffer instance's contents to this one, ending with end_index,
|
|
||||||
* replacing all of the original data.
|
* replacing all of the original data.
|
||||||
* The destination capacity must be no less than the source data size
|
|
||||||
* counting to end_index.
|
|
||||||
*
|
*
|
||||||
* @param [in,out] buf pointer to a BitBuffer instance to copy into
|
* @warning The destination capacity must be no less than the source data
|
||||||
* @param [in] other pointer to a BitBuffer instance to copy from
|
* size counting to end_index.
|
||||||
* @param [in] end_index index to end copying source data at
|
*
|
||||||
|
* @param[in,out] buf pointer to a BitBuffer instance to copy into
|
||||||
|
* @param[in] other pointer to a BitBuffer instance to copy from
|
||||||
|
* @param[in] end_index index to end copying source data at
|
||||||
*/
|
*/
|
||||||
void bit_buffer_copy_left(BitBuffer* buf, const BitBuffer* other, size_t end_index);
|
void bit_buffer_copy_left(BitBuffer* buf, const BitBuffer* other, size_t end_index);
|
||||||
|
|
||||||
/**
|
/** Copy a byte array to a BitBuffer instance, replacing all of the original
|
||||||
* Copy a byte array to a BitBuffer instance, replacing all of the original data.
|
* data.
|
||||||
* The destination capacity must be no less than the source data size.
|
|
||||||
*
|
*
|
||||||
* @param [in,out] buf pointer to a BitBuffer instance to copy into
|
* @warning The destination capacity must be no less than the source data
|
||||||
* @param [in] data pointer to the byte array to be copied
|
* size.
|
||||||
* @param [in] size_bytes size of the data to be copied, in bytes
|
*
|
||||||
|
* @param[in,out] buf pointer to a BitBuffer instance to copy into
|
||||||
|
* @param[in] data pointer to the byte array to be copied
|
||||||
|
* @param[in] size_bytes size of the data to be copied, in bytes
|
||||||
*/
|
*/
|
||||||
void bit_buffer_copy_bytes(BitBuffer* buf, const uint8_t* data, size_t size_bytes);
|
void bit_buffer_copy_bytes(BitBuffer* buf, const uint8_t* data, size_t size_bytes);
|
||||||
|
|
||||||
/**
|
/** Copy a byte array to a BitBuffer instance, replacing all of the original
|
||||||
* Copy a byte array to a BitBuffer instance, replacing all of the original data.
|
* data.
|
||||||
* The destination capacity must be no less than the source data size.
|
|
||||||
*
|
*
|
||||||
* @param [in,out] buf pointer to a BitBuffer instance to copy into
|
* @warning The destination capacity must be no less than the source data
|
||||||
* @param [in] data pointer to the byte array to be copied
|
* size.
|
||||||
* @param [in] size_bits size of the data to be copied, in bits
|
*
|
||||||
|
* @param[in,out] buf pointer to a BitBuffer instance to copy into
|
||||||
|
* @param[in] data pointer to the byte array to be copied
|
||||||
|
* @param[in] size_bits size of the data to be copied, in bits
|
||||||
*/
|
*/
|
||||||
void bit_buffer_copy_bits(BitBuffer* buf, const uint8_t* data, size_t size_bits);
|
void bit_buffer_copy_bits(BitBuffer* buf, const uint8_t* data, size_t size_bits);
|
||||||
|
|
||||||
/**
|
/** Copy a byte with parity array to a BitBuffer instance, replacing all of the
|
||||||
* Copy a byte with parity array to a BitBuffer instance, replacing all of the original data.
|
* original data.
|
||||||
* The destination capacity must be no less than the source data size.
|
|
||||||
*
|
*
|
||||||
* @param [in,out] buf pointer to a BitBuffer instance to copy into
|
* @warning The destination capacity must be no less than the source data
|
||||||
* @param [in] data pointer to the byte array to be copied
|
* size.
|
||||||
* @param [in] size_bitss size of the data to be copied, in bits
|
*
|
||||||
|
* @param[in,out] buf pointer to a BitBuffer instance to copy into
|
||||||
|
* @param[in] data pointer to the byte array to be copied
|
||||||
|
* @param[in] size_bits size of the data to be copied, in bits
|
||||||
|
* @note Parity bits are placed starting with the most significant bit
|
||||||
|
* of each byte and moving up.
|
||||||
|
* @note Example: DDDDDDDD PDDDDDDD DPDDDDDD DDP...
|
||||||
*/
|
*/
|
||||||
void bit_buffer_copy_bytes_with_parity(BitBuffer* buf, const uint8_t* data, size_t size_bits);
|
void bit_buffer_copy_bytes_with_parity(BitBuffer* buf, const uint8_t* data, size_t size_bits);
|
||||||
|
|
||||||
/**
|
/** Write a BitBuffer instance's entire contents to an arbitrary memory location.
|
||||||
* Write a BitBuffer instance's entire contents to an arbitrary memory location.
|
|
||||||
* The destination memory must be allocated. Additionally, the destination
|
|
||||||
* capacity must be no less than the source data size.
|
|
||||||
*
|
*
|
||||||
* @param [in] buf pointer to a BitBuffer instance to write from
|
* @warning The destination memory must be allocated. Additionally, the
|
||||||
* @param [out] dest pointer to the destination memory location
|
* destination capacity must be no less than the source data size.
|
||||||
* @param [in] size_bytes maximum destination data size, in bytes
|
*
|
||||||
|
* @param[in] buf pointer to a BitBuffer instance to write from
|
||||||
|
* @param[out] dest pointer to the destination memory location
|
||||||
|
* @param[in] size_bytes maximum destination data size, in bytes
|
||||||
*/
|
*/
|
||||||
void bit_buffer_write_bytes(const BitBuffer* buf, void* dest, size_t size_bytes);
|
void bit_buffer_write_bytes(const BitBuffer* buf, void* dest, size_t size_bytes);
|
||||||
|
|
||||||
/**
|
/** Write a BitBuffer instance's entire contents to an arbitrary memory location.
|
||||||
* Write a BitBuffer instance's entire contents to an arbitrary memory location.
|
|
||||||
* Additionally, place a parity bit after each byte.
|
|
||||||
* The destination memory must be allocated. Additionally, the destination
|
|
||||||
* capacity must be no less than the source data size plus parity.
|
|
||||||
*
|
*
|
||||||
* @param [in] buf pointer to a BitBuffer instance to write from
|
* Additionally, place a parity bit after each byte.
|
||||||
* @param [out] dest pointer to the destination memory location
|
*
|
||||||
* @param [in] size_bytes maximum destination data size, in bytes
|
* @warning The destination memory must be allocated. Additionally, the
|
||||||
* @param [out] bits_written actual number of bits writen, in bits
|
* destination capacity must be no less than the source data size
|
||||||
|
* plus parity.
|
||||||
|
*
|
||||||
|
* @param[in] buf pointer to a BitBuffer instance to write from
|
||||||
|
* @param[out] dest pointer to the destination memory location
|
||||||
|
* @param[in] size_bytes maximum destination data size, in bytes
|
||||||
|
* @param[out] bits_written actual number of bits written, in bits
|
||||||
|
* @note Parity bits are placed starting with the most significant bit of
|
||||||
|
* each byte and moving up.
|
||||||
|
* @note Example: DDDDDDDD PDDDDDDD DPDDDDDD DDP...
|
||||||
*/
|
*/
|
||||||
void bit_buffer_write_bytes_with_parity(
|
void bit_buffer_write_bytes_with_parity(
|
||||||
const BitBuffer* buf,
|
const BitBuffer* buf,
|
||||||
@@ -129,15 +145,17 @@ void bit_buffer_write_bytes_with_parity(
|
|||||||
size_t size_bytes,
|
size_t size_bytes,
|
||||||
size_t* bits_written);
|
size_t* bits_written);
|
||||||
|
|
||||||
/**
|
/** Write a slice of BitBuffer instance's contents to an arbitrary memory
|
||||||
* Write a slice of BitBuffer instance's contents to an arbitrary memory location.
|
* location.
|
||||||
* The destination memory must be allocated. Additionally, the destination
|
|
||||||
* capacity must be no less than the requested slice size.
|
|
||||||
*
|
*
|
||||||
* @param [in] buf pointer to a BitBuffer instance to write from
|
* @warning The destination memory must be allocated. Additionally, the
|
||||||
* @param [out] dest pointer to the destination memory location
|
* destination capacity must be no less than the requested slice
|
||||||
* @param [in] start_index index to begin copying source data from
|
* size.
|
||||||
* @param [in] size_bytes data slice size, in bytes
|
*
|
||||||
|
* @param[in] buf pointer to a BitBuffer instance to write from
|
||||||
|
* @param[out] dest pointer to the destination memory location
|
||||||
|
* @param[in] start_index index to begin copying source data from
|
||||||
|
* @param[in] size_bytes data slice size, in bytes
|
||||||
*/
|
*/
|
||||||
void bit_buffer_write_bytes_mid(
|
void bit_buffer_write_bytes_mid(
|
||||||
const BitBuffer* buf,
|
const BitBuffer* buf,
|
||||||
@@ -147,176 +165,196 @@ void bit_buffer_write_bytes_mid(
|
|||||||
|
|
||||||
// Checks
|
// Checks
|
||||||
|
|
||||||
/**
|
/** Check whether a BitBuffer instance contains a partial byte (i.e.\ the bit
|
||||||
* Check whether a BitBuffer instance contains a partial byte (i.e. the bit count
|
* count is not divisible by 8).
|
||||||
* is not divisible by 8).
|
|
||||||
*
|
*
|
||||||
* @param [in] buf pointer to a BitBuffer instance to be checked
|
* @param[in] buf pointer to a BitBuffer instance to be checked
|
||||||
* @return true if the instance contains a partial byte, false otherwise
|
*
|
||||||
|
* @return true if the instance contains a partial byte, false otherwise
|
||||||
*/
|
*/
|
||||||
bool bit_buffer_has_partial_byte(const BitBuffer* buf);
|
bool bit_buffer_has_partial_byte(const BitBuffer* buf);
|
||||||
|
|
||||||
/**
|
/** Check whether a BitBuffer instance's contents start with the designated byte.
|
||||||
* Check whether a BitBuffer instance's contents start with the designated byte.
|
|
||||||
*
|
*
|
||||||
* @param [in] buf pointer to a BitBuffer instance to be checked
|
* @param[in] buf pointer to a BitBuffer instance to be checked
|
||||||
* @param [in] byte byte value to be checked against
|
* @param[in] byte byte value to be checked against
|
||||||
* @return true if data starts with designated byte, false otherwise
|
*
|
||||||
|
* @return true if data starts with designated byte, false otherwise
|
||||||
*/
|
*/
|
||||||
bool bit_buffer_starts_with_byte(const BitBuffer* buf, uint8_t byte);
|
bool bit_buffer_starts_with_byte(const BitBuffer* buf, uint8_t byte);
|
||||||
|
|
||||||
// Getters
|
// Getters
|
||||||
|
|
||||||
/**
|
/** Get a BitBuffer instance's capacity (i.e.\ the maximum possible amount of
|
||||||
* Get a BitBuffer instance's capacity (i.e. the maximum possible amount of data), in bytes.
|
* data), in bytes.
|
||||||
*
|
*
|
||||||
* @param [in] buf pointer to a BitBuffer instance to be queried
|
* @param[in] buf pointer to a BitBuffer instance to be queried
|
||||||
* @return capacity, in bytes
|
*
|
||||||
|
* @return capacity, in bytes
|
||||||
*/
|
*/
|
||||||
size_t bit_buffer_get_capacity_bytes(const BitBuffer* buf);
|
size_t bit_buffer_get_capacity_bytes(const BitBuffer* buf);
|
||||||
|
|
||||||
/**
|
/** Get a BitBuffer instance's data size (i.e.\ the amount of stored data), in
|
||||||
* Get a BitBuffer instance's data size (i.e. the amount of stored data), in bits.
|
* bits.
|
||||||
* Might be not divisible by 8 (see bit_buffer_is_partial_byte).
|
|
||||||
*
|
*
|
||||||
* @param [in] buf pointer to a BitBuffer instance to be queried
|
* @warning Might be not divisible by 8 (see bit_buffer_is_partial_byte).
|
||||||
* @return data size, in bits.
|
*
|
||||||
|
* @param[in] buf pointer to a BitBuffer instance to be queried
|
||||||
|
*
|
||||||
|
* @return data size, in bits.
|
||||||
*/
|
*/
|
||||||
size_t bit_buffer_get_size(const BitBuffer* buf);
|
size_t bit_buffer_get_size(const BitBuffer* buf);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a BitBuffer instance's data size (i.e. the amount of stored data), in bytes.
|
* Get a BitBuffer instance's data size (i.e.\ the amount of stored data), in
|
||||||
* If a partial byte is present, it is also counted.
|
* bytes.
|
||||||
*
|
*
|
||||||
* @param [in] buf pointer to a BitBuffer instance to be queried
|
* @warning If a partial byte is present, it is also counted.
|
||||||
* @return data size, in bytes.
|
*
|
||||||
|
* @param[in] buf pointer to a BitBuffer instance to be queried
|
||||||
|
*
|
||||||
|
* @return data size, in bytes.
|
||||||
*/
|
*/
|
||||||
size_t bit_buffer_get_size_bytes(const BitBuffer* buf);
|
size_t bit_buffer_get_size_bytes(const BitBuffer* buf);
|
||||||
|
|
||||||
/**
|
/** Get a byte value at a specified index in a BitBuffer instance.
|
||||||
* Get a byte value at a specified index in a BitBuffer instance.
|
|
||||||
* The index must be valid (i.e. less than the instance's data size in bytes).
|
|
||||||
*
|
*
|
||||||
* @param [in] buf pointer to a BitBuffer instance to be queried
|
* @warning The index must be valid (i.e.\ less than the instance's data size
|
||||||
* @param [in] index index of the byte in question
|
* in bytes).
|
||||||
|
*
|
||||||
|
* @param[in] buf pointer to a BitBuffer instance to be queried
|
||||||
|
* @param[in] index index of the byte in question
|
||||||
|
*
|
||||||
|
* @return byte value
|
||||||
*/
|
*/
|
||||||
uint8_t bit_buffer_get_byte(const BitBuffer* buf, size_t index);
|
uint8_t bit_buffer_get_byte(const BitBuffer* buf, size_t index);
|
||||||
|
|
||||||
/**
|
/** Get a byte value starting from the specified bit index in a BitBuffer
|
||||||
* Get a byte value starting from the specified bit index in a BitBuffer instance.
|
* instance.
|
||||||
* The resulting byte might correspond to a single byte (if the index is a multiple
|
|
||||||
* of 8), or two overlapping bytes combined.
|
|
||||||
* The index must be valid (i.e. less than the instance's data size in bits).
|
|
||||||
*
|
*
|
||||||
* @param [in] buf pointer to a BitBuffer instance to be queried
|
* @warning The resulting byte might correspond to a single byte (if the
|
||||||
* @param [in] index bit index of the byte in question
|
* index is a multiple of 8), or two overlapping bytes combined. The
|
||||||
|
* index must be valid (i.e.\ less than the instance's data size in
|
||||||
|
* bits).
|
||||||
|
*
|
||||||
|
* @param[in] buf pointer to a BitBuffer instance to be queried
|
||||||
|
* @param[in] index_bits bit index of the byte in question
|
||||||
|
*
|
||||||
|
* @return byte value
|
||||||
*/
|
*/
|
||||||
uint8_t bit_buffer_get_byte_from_bit(const BitBuffer* buf, size_t index_bits);
|
uint8_t bit_buffer_get_byte_from_bit(const BitBuffer* buf, size_t index_bits);
|
||||||
|
|
||||||
/**
|
/** Get the pointer to a BitBuffer instance's underlying data.
|
||||||
* Get the pointer to a BitBuffer instance's underlying data.
|
|
||||||
*
|
*
|
||||||
* @param [in] buf pointer to a BitBuffer instance to be queried
|
* @param[in] buf pointer to a BitBuffer instance to be queried
|
||||||
* @return pointer to the underlying data
|
*
|
||||||
|
* @return pointer to the underlying data
|
||||||
*/
|
*/
|
||||||
const uint8_t* bit_buffer_get_data(const BitBuffer* buf);
|
const uint8_t* bit_buffer_get_data(const BitBuffer* buf);
|
||||||
|
|
||||||
/**
|
/** Get the pointer to the parity data of a BitBuffer instance.
|
||||||
* Get the pointer to a BitBuffer instance's underlying data.
|
|
||||||
*
|
*
|
||||||
* @param [in] buf pointer to a BitBuffer instance to be queried
|
* @param[in] buf pointer to a BitBuffer instance to be queried
|
||||||
* @return pointer to the underlying data
|
*
|
||||||
|
* @return pointer to the parity data
|
||||||
*/
|
*/
|
||||||
const uint8_t* bit_buffer_get_parity(const BitBuffer* buf);
|
const uint8_t* bit_buffer_get_parity(const BitBuffer* buf);
|
||||||
|
|
||||||
// Setters
|
// Setters
|
||||||
|
|
||||||
/**
|
/** Set byte value at a specified index in a BitBuffer instance.
|
||||||
* Set byte value at a specified index in a BitBuffer instance.
|
|
||||||
* The index must be valid (i.e. less than the instance's data size in bytes).
|
|
||||||
*
|
*
|
||||||
* @param [in,out] buf pointer to a BitBuffer instance to be modified
|
* @warning The index must be valid (i.e.\ less than the instance's data
|
||||||
* @param [in] index index of the byte in question
|
* size in bytes).
|
||||||
* @param [in] byte byte value to be set at index
|
*
|
||||||
|
* @param[in,out] buf pointer to a BitBuffer instance to be modified
|
||||||
|
* @param[in] index index of the byte in question
|
||||||
|
* @param[in] byte byte value to be set at index
|
||||||
*/
|
*/
|
||||||
void bit_buffer_set_byte(BitBuffer* buf, size_t index, uint8_t byte);
|
void bit_buffer_set_byte(BitBuffer* buf, size_t index, uint8_t byte);
|
||||||
|
|
||||||
/**
|
/** Set byte and parity bit value at a specified index in a BitBuffer instance.
|
||||||
* Set byte and parity bit value at a specified index in a BitBuffer instance.
|
|
||||||
* The index must be valid (i.e. less than the instance's data size in bytes).
|
|
||||||
*
|
*
|
||||||
* @param [in,out] buf pointer to a BitBuffer instance to be modified
|
* @warning The index must be valid (i.e.\ less than the instance's data
|
||||||
* @param [in] index index of the byte in question
|
* size in bytes).
|
||||||
* @param [in] byte byte value to be set at index
|
*
|
||||||
* @param [in] parity parity bit value to be set at index
|
* @param[in,out] buff pointer to a BitBuffer instance to be modified
|
||||||
|
* @param[in] index index of the byte in question
|
||||||
|
* @param[in] byte byte value to be set at index
|
||||||
|
* @param[in] parity parity bit value to be set at index
|
||||||
*/
|
*/
|
||||||
void bit_buffer_set_byte_with_parity(BitBuffer* buff, size_t index, uint8_t byte, bool parity);
|
void bit_buffer_set_byte_with_parity(BitBuffer* buff, size_t index, uint8_t byte, bool parity);
|
||||||
|
|
||||||
/**
|
/** Resize a BitBuffer instance to a new size, in bits.
|
||||||
* Resize a BitBuffer instance to a new size, in bits.
|
|
||||||
* @warning May cause bugs. Use only if absolutely necessary.
|
|
||||||
*
|
*
|
||||||
* @param [in,out] buf pointer to a BitBuffer instance to be resized
|
* @warning May cause bugs. Use only if absolutely necessary.
|
||||||
* @param [in] new_size the new size of the buffer, in bits
|
*
|
||||||
|
* @param[in,out] buf pointer to a BitBuffer instance to be resized
|
||||||
|
* @param[in] new_size the new size of the buffer, in bits
|
||||||
*/
|
*/
|
||||||
void bit_buffer_set_size(BitBuffer* buf, size_t new_size);
|
void bit_buffer_set_size(BitBuffer* buf, size_t new_size);
|
||||||
|
|
||||||
/**
|
/** Resize a BitBuffer instance to a new size, in bytes.
|
||||||
* Resize a BitBuffer instance to a new size, in bytes.
|
|
||||||
* @warning May cause bugs. Use only if absolutely necessary.
|
|
||||||
*
|
*
|
||||||
* @param [in,out] buf pointer to a BitBuffer instance to be resized
|
* @warning May cause bugs. Use only if absolutely necessary.
|
||||||
* @param [in] new_size_bytes the new size of the buffer, in bytes
|
*
|
||||||
|
* @param[in,out] buf pointer to a BitBuffer instance to be resized
|
||||||
|
* @param[in] new_size_bytes the new size of the buffer, in bytes
|
||||||
*/
|
*/
|
||||||
void bit_buffer_set_size_bytes(BitBuffer* buf, size_t new_size_bytes);
|
void bit_buffer_set_size_bytes(BitBuffer* buf, size_t new_size_bytes);
|
||||||
|
|
||||||
// Modification
|
// Modification
|
||||||
|
|
||||||
/**
|
/** Append all BitBuffer's instance contents to this one.
|
||||||
* Append all BitBuffer's instance contents to this one. The destination capacity
|
|
||||||
* must be no less than its original data size plus source data size.
|
|
||||||
*
|
*
|
||||||
* @param [in,out] buf pointer to a BitBuffer instance to be appended to
|
* @warning The destination capacity must be no less than its original
|
||||||
* @param [in] other pointer to a BitBuffer instance to be appended
|
* data size plus source data size.
|
||||||
|
*
|
||||||
|
* @param[in,out] buf pointer to a BitBuffer instance to be appended to
|
||||||
|
* @param[in] other pointer to a BitBuffer instance to be appended
|
||||||
*/
|
*/
|
||||||
void bit_buffer_append(BitBuffer* buf, const BitBuffer* other);
|
void bit_buffer_append(BitBuffer* buf, const BitBuffer* other);
|
||||||
|
|
||||||
/**
|
/** Append a BitBuffer's instance contents to this one, starting from
|
||||||
* Append a BitBuffer's instance contents to this one, starting from start_index.
|
* start_index.
|
||||||
* The destination capacity must be no less than the source data size
|
|
||||||
* counting from start_index.
|
|
||||||
*
|
*
|
||||||
* @param [in,out] buf pointer to a BitBuffer instance to be appended to
|
* @warning The destination capacity must be no less than the source data
|
||||||
* @param [in] other pointer to a BitBuffer instance to be appended
|
* size counting from start_index.
|
||||||
* @param [in] start_index index to begin copying source data from
|
*
|
||||||
|
* @param[in,out] buf pointer to a BitBuffer instance to be appended to
|
||||||
|
* @param[in] other pointer to a BitBuffer instance to be appended
|
||||||
|
* @param[in] start_index index to begin copying source data from
|
||||||
*/
|
*/
|
||||||
void bit_buffer_append_right(BitBuffer* buf, const BitBuffer* other, size_t start_index);
|
void bit_buffer_append_right(BitBuffer* buf, const BitBuffer* other, size_t start_index);
|
||||||
|
|
||||||
/**
|
/** Append a byte to a BitBuffer instance.
|
||||||
* Append a byte to a BitBuffer instance.
|
|
||||||
* The destination capacity must be no less its original data size plus one.
|
|
||||||
*
|
*
|
||||||
* @param [in,out] buf pointer to a BitBuffer instance to be appended to
|
* @warning The destination capacity must be no less its original data
|
||||||
* @param [in] byte byte value to be appended
|
* size plus one.
|
||||||
|
*
|
||||||
|
* @param[in,out] buf pointer to a BitBuffer instance to be appended to
|
||||||
|
* @param[in] byte byte value to be appended
|
||||||
*/
|
*/
|
||||||
void bit_buffer_append_byte(BitBuffer* buf, uint8_t byte);
|
void bit_buffer_append_byte(BitBuffer* buf, uint8_t byte);
|
||||||
|
|
||||||
/**
|
/** Append a byte array to a BitBuffer instance.
|
||||||
* Append a byte array to a BitBuffer instance.
|
|
||||||
* The destination capacity must be no less its original data size plus source data size.
|
|
||||||
*
|
*
|
||||||
* @param [in,out] buf pointer to a BitBuffer instance to be appended to
|
* @warning The destination capacity must be no less its original data
|
||||||
* @param [in] data pointer to the byte array to be appended
|
* size plus source data size.
|
||||||
* @param [in] size_bytes size of the data to be appended, in bytes
|
*
|
||||||
|
* @param[in,out] buf pointer to a BitBuffer instance to be appended to
|
||||||
|
* @param[in] data pointer to the byte array to be appended
|
||||||
|
* @param[in] size_bytes size of the data to be appended, in bytes
|
||||||
*/
|
*/
|
||||||
void bit_buffer_append_bytes(BitBuffer* buf, const uint8_t* data, size_t size_bytes);
|
void bit_buffer_append_bytes(BitBuffer* buf, const uint8_t* data, size_t size_bytes);
|
||||||
|
|
||||||
/**
|
/** Append a bit to a BitBuffer instance.
|
||||||
* Append a bit to a BitBuffer instance.
|
|
||||||
* The destination capacity must be sufficient to accomodate the additional bit.
|
|
||||||
*
|
*
|
||||||
* @param [in,out] buf pointer to a BitBuffer instance to be appended to
|
* @warning The destination capacity must be sufficient to accommodate the
|
||||||
* @param [in] bit bit value to be appended
|
* additional bit.
|
||||||
|
*
|
||||||
|
* @param[in,out] buf pointer to a BitBuffer instance to be appended to
|
||||||
|
* @param[in] bit bit value to be appended
|
||||||
*/
|
*/
|
||||||
void bit_buffer_append_bit(BitBuffer* buf, bool bit);
|
void bit_buffer_append_bit(BitBuffer* buf, bool bit);
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ def generate(env):
|
|||||||
PVSOPTIONS=[
|
PVSOPTIONS=[
|
||||||
"@.pvsoptions",
|
"@.pvsoptions",
|
||||||
"-j${PVSNCORES}",
|
"-j${PVSNCORES}",
|
||||||
"--disableLicenseExpirationCheck",
|
# "--disableLicenseExpirationCheck",
|
||||||
# "--incremental", # kinda broken on PVS side
|
# "--incremental", # kinda broken on PVS side
|
||||||
],
|
],
|
||||||
PVSCONVOPTIONS=[
|
PVSCONVOPTIONS=[
|
||||||
|
|||||||
@@ -32,6 +32,16 @@ class FlipperFormatFile:
|
|||||||
raise Exception("Unexpected line: not `key:value`")
|
raise Exception("Unexpected line: not `key:value`")
|
||||||
return data[0].strip(), data[1].strip()
|
return data[0].strip(), data[1].strip()
|
||||||
|
|
||||||
|
def readComment(self):
|
||||||
|
if self.cursor == len(self.lines):
|
||||||
|
raise EOFError()
|
||||||
|
line = self.lines[self.cursor].strip()
|
||||||
|
if line.startswith("#"):
|
||||||
|
self.cursor += 1
|
||||||
|
return line[1:].strip()
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
def readKey(self, key: str):
|
def readKey(self, key: str):
|
||||||
k, v = self.readKeyValue()
|
k, v = self.readKeyValue()
|
||||||
if k != key:
|
if k != key:
|
||||||
@@ -67,7 +77,7 @@ class FlipperFormatFile:
|
|||||||
self.writeLine("")
|
self.writeLine("")
|
||||||
|
|
||||||
def writeComment(self, text: str):
|
def writeComment(self, text: str):
|
||||||
if text:
|
if text and len(text):
|
||||||
self.writeLine(f"# {text}")
|
self.writeLine(f"# {text}")
|
||||||
else:
|
else:
|
||||||
self.writeLine("#")
|
self.writeLine("#")
|
||||||
@@ -104,3 +114,4 @@ class FlipperFormatFile:
|
|||||||
def save(self, filename: str):
|
def save(self, filename: str):
|
||||||
with open(filename, "w", newline="\n") as file:
|
with open(filename, "w", newline="\n") as file:
|
||||||
file.write("\n".join(self.lines))
|
file.write("\n".join(self.lines))
|
||||||
|
file.write("\n")
|
||||||
|
|||||||
@@ -27,37 +27,51 @@ class Main(App):
|
|||||||
return 1
|
return 1
|
||||||
|
|
||||||
data = []
|
data = []
|
||||||
unique = {}
|
unique_combo = {}
|
||||||
|
unique_payload = {}
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
d = {}
|
d = {}
|
||||||
|
d["comments"] = []
|
||||||
|
while (comment := f.readComment()) is not None:
|
||||||
|
d["comments"].append(comment)
|
||||||
d["name"] = f.readKey("name")
|
d["name"] = f.readKey("name")
|
||||||
d["type"] = f.readKey("type")
|
d["type"] = f.readKey("type")
|
||||||
key = None
|
key_combo = f'{d["name"]}'
|
||||||
|
key_payload = None
|
||||||
if d["type"] == "parsed":
|
if d["type"] == "parsed":
|
||||||
d["protocol"] = f.readKey("protocol")
|
d["protocol"] = f.readKey("protocol")
|
||||||
d["address"] = f.readKey("address")
|
d["address"] = f.readKey("address")
|
||||||
d["command"] = f.readKey("command")
|
d["command"] = f.readKey("command")
|
||||||
key = f'{d["protocol"]}{d["address"]}{d["command"]}'
|
key_payload = f'{d["protocol"]}{d["address"]}{d["command"]}'
|
||||||
|
key_combo += key_payload
|
||||||
elif d["type"] == "raw":
|
elif d["type"] == "raw":
|
||||||
d["frequency"] = f.readKey("frequency")
|
d["frequency"] = f.readKey("frequency")
|
||||||
d["duty_cycle"] = f.readKey("duty_cycle")
|
d["duty_cycle"] = f.readKey("duty_cycle")
|
||||||
d["data"] = f.readKey("data")
|
d["data"] = f.readKey("data")
|
||||||
key = f'{d["frequency"]}{d["duty_cycle"]}{d["data"]}'
|
key_payload = f'{d["frequency"]}{d["duty_cycle"]}{d["data"]}'
|
||||||
|
key_combo += key_payload
|
||||||
else:
|
else:
|
||||||
raise Exception(f'Unknown type: {d["type"]}')
|
raise Exception(f'Unknown type: {d["type"]}')
|
||||||
if not key in unique:
|
|
||||||
unique[key] = d
|
if not key_combo in unique_combo:
|
||||||
|
unique_combo[key_combo] = d
|
||||||
data.append(d)
|
data.append(d)
|
||||||
|
# Check payload only
|
||||||
|
if not key_payload in unique_payload:
|
||||||
|
unique_payload[key_payload] = d
|
||||||
|
else:
|
||||||
|
self.logger.warning(f"Duplicate payload, check manually: {d}")
|
||||||
else:
|
else:
|
||||||
self.logger.warn(f"Duplicate key: {key}")
|
self.logger.info(f"Duplicate data removed: {d}")
|
||||||
except EOFError:
|
except EOFError:
|
||||||
break
|
break
|
||||||
# Form new file
|
# Form new file
|
||||||
f = FlipperFormatFile()
|
f = FlipperFormatFile()
|
||||||
f.setHeader(filetype, version)
|
f.setHeader(filetype, version)
|
||||||
for i in data:
|
for i in data:
|
||||||
f.writeComment(None)
|
for comment in i["comments"]:
|
||||||
|
f.writeComment(comment)
|
||||||
f.writeKey("name", i["name"])
|
f.writeKey("name", i["name"])
|
||||||
f.writeKey("type", i["type"])
|
f.writeKey("type", i["type"])
|
||||||
if i["type"] == "parsed":
|
if i["type"] == "parsed":
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ class Main(App):
|
|||||||
try:
|
try:
|
||||||
shutil.rmtree(self.output_dir_path)
|
shutil.rmtree(self.output_dir_path)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
self.logger.warn(f"Failed to clean output directory: {ex}")
|
self.logger.warning(f"Failed to clean output directory: {ex}")
|
||||||
|
|
||||||
if not exists(self.output_dir_path):
|
if not exists(self.output_dir_path):
|
||||||
self.logger.debug(f"Creating output directory {self.output_dir_path}")
|
self.logger.debug(f"Creating output directory {self.output_dir_path}")
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ class Main(App):
|
|||||||
return 3
|
return 3
|
||||||
|
|
||||||
if not self.layout_check(updater_stage_size, dfu_size, radio_addr):
|
if not self.layout_check(updater_stage_size, dfu_size, radio_addr):
|
||||||
self.logger.warn("Memory layout looks suspicious")
|
self.logger.warning("Memory layout looks suspicious")
|
||||||
if self.args.disclaimer != "yes":
|
if self.args.disclaimer != "yes":
|
||||||
self.show_disclaimer()
|
self.show_disclaimer()
|
||||||
return 2
|
return 2
|
||||||
@@ -205,7 +205,7 @@ class Main(App):
|
|||||||
|
|
||||||
def layout_check(self, stage_size, fw_size, radio_addr):
|
def layout_check(self, stage_size, fw_size, radio_addr):
|
||||||
if stage_size > self.UPDATER_SIZE_THRESHOLD:
|
if stage_size > self.UPDATER_SIZE_THRESHOLD:
|
||||||
self.logger.warn(
|
self.logger.warning(
|
||||||
f"Updater size {stage_size}b > {self.UPDATER_SIZE_THRESHOLD}b and is not loadable on older firmwares!"
|
f"Updater size {stage_size}b > {self.UPDATER_SIZE_THRESHOLD}b and is not loadable on older firmwares!"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -217,13 +217,13 @@ class Main(App):
|
|||||||
self.logger.debug(f"Expected reserved space size: {fw2stack_gap}")
|
self.logger.debug(f"Expected reserved space size: {fw2stack_gap}")
|
||||||
fw2stack_gap_pages = fw2stack_gap / self.FLASH_PAGE_SIZE
|
fw2stack_gap_pages = fw2stack_gap / self.FLASH_PAGE_SIZE
|
||||||
if fw2stack_gap_pages < 0:
|
if fw2stack_gap_pages < 0:
|
||||||
self.logger.warn(
|
self.logger.warning(
|
||||||
f"Firmware image overlaps C2 region and is not programmable!"
|
f"Firmware image overlaps C2 region and is not programmable!"
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
elif fw2stack_gap_pages < self.MIN_GAP_PAGES:
|
elif fw2stack_gap_pages < self.MIN_GAP_PAGES:
|
||||||
self.logger.warn(
|
self.logger.warning(
|
||||||
f"Expected reserved flash size is too small (~{int(fw2stack_gap_pages)} page(s), need >={self.MIN_GAP_PAGES} page(s))"
|
f"Expected reserved flash size is too small (~{int(fw2stack_gap_pages)} page(s), need >={self.MIN_GAP_PAGES} page(s))"
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
entry,status,name,type,params
|
entry,status,name,type,params
|
||||||
Version,+,74.0,,
|
Version,+,75.0,,
|
||||||
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,,
|
||||||
@@ -1652,7 +1652,6 @@ Function,+,furi_thread_list_free,void,FuriThreadList*
|
|||||||
Function,+,furi_thread_list_get_at,FuriThreadListItem*,"FuriThreadList*, size_t"
|
Function,+,furi_thread_list_get_at,FuriThreadListItem*,"FuriThreadList*, size_t"
|
||||||
Function,+,furi_thread_list_get_isr_time,float,FuriThreadList*
|
Function,+,furi_thread_list_get_isr_time,float,FuriThreadList*
|
||||||
Function,+,furi_thread_list_get_or_insert,FuriThreadListItem*,"FuriThreadList*, FuriThread*"
|
Function,+,furi_thread_list_get_or_insert,FuriThreadListItem*,"FuriThreadList*, FuriThread*"
|
||||||
Function,+,furi_thread_list_process,void,"FuriThreadList*, uint32_t, uint32_t"
|
|
||||||
Function,+,furi_thread_list_size,size_t,FuriThreadList*
|
Function,+,furi_thread_list_size,size_t,FuriThreadList*
|
||||||
Function,+,furi_thread_resume,void,FuriThreadId
|
Function,+,furi_thread_resume,void,FuriThreadId
|
||||||
Function,+,furi_thread_set_appid,void,"FuriThread*, const char*"
|
Function,+,furi_thread_set_appid,void,"FuriThread*, const char*"
|
||||||
|
|||||||
|
@@ -1,5 +1,5 @@
|
|||||||
entry,status,name,type,params
|
entry,status,name,type,params
|
||||||
Version,+,74.0,,
|
Version,+,75.0,,
|
||||||
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,,
|
||||||
@@ -1871,7 +1871,6 @@ Function,+,furi_thread_list_free,void,FuriThreadList*
|
|||||||
Function,+,furi_thread_list_get_at,FuriThreadListItem*,"FuriThreadList*, size_t"
|
Function,+,furi_thread_list_get_at,FuriThreadListItem*,"FuriThreadList*, size_t"
|
||||||
Function,+,furi_thread_list_get_isr_time,float,FuriThreadList*
|
Function,+,furi_thread_list_get_isr_time,float,FuriThreadList*
|
||||||
Function,+,furi_thread_list_get_or_insert,FuriThreadListItem*,"FuriThreadList*, FuriThread*"
|
Function,+,furi_thread_list_get_or_insert,FuriThreadListItem*,"FuriThreadList*, FuriThread*"
|
||||||
Function,+,furi_thread_list_process,void,"FuriThreadList*, uint32_t, uint32_t"
|
|
||||||
Function,+,furi_thread_list_size,size_t,FuriThreadList*
|
Function,+,furi_thread_list_size,size_t,FuriThreadList*
|
||||||
Function,+,furi_thread_resume,void,FuriThreadId
|
Function,+,furi_thread_resume,void,FuriThreadId
|
||||||
Function,+,furi_thread_set_appid,void,"FuriThread*, const char*"
|
Function,+,furi_thread_set_appid,void,"FuriThread*, const char*"
|
||||||
|
|||||||
|
@@ -73,18 +73,14 @@ void furi_hal_power_init(void) {
|
|||||||
// Find and init gauge
|
// Find and init gauge
|
||||||
size_t retry = 2;
|
size_t retry = 2;
|
||||||
while(retry > 0) {
|
while(retry > 0) {
|
||||||
furi_hal_power.gauge_ok = bq27220_init(&furi_hal_i2c_handle_power);
|
furi_hal_power.gauge_ok =
|
||||||
if(furi_hal_power.gauge_ok) {
|
bq27220_init(&furi_hal_i2c_handle_power, furi_hal_power_gauge_data_memory);
|
||||||
furi_hal_power.gauge_ok = bq27220_apply_data_memory(
|
|
||||||
&furi_hal_i2c_handle_power, furi_hal_power_gauge_data_memory);
|
|
||||||
}
|
|
||||||
if(furi_hal_power.gauge_ok) {
|
if(furi_hal_power.gauge_ok) {
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
// Normal startup time is 250ms
|
// Gauge need some time to think about it's behavior
|
||||||
// But if we try to access gauge at that stage it will become unresponsive
|
// We must wait, otherwise next init cycle will fail at unseal stage
|
||||||
// 2 seconds timeout needed to restart communication
|
furi_delay_us(4000000);
|
||||||
furi_delay_us(2020202);
|
|
||||||
}
|
}
|
||||||
retry--;
|
retry--;
|
||||||
}
|
}
|
||||||
@@ -110,8 +106,8 @@ void furi_hal_power_init(void) {
|
|||||||
bool furi_hal_power_gauge_is_ok(void) {
|
bool furi_hal_power_gauge_is_ok(void) {
|
||||||
bool ret = true;
|
bool ret = true;
|
||||||
|
|
||||||
BatteryStatus battery_status;
|
Bq27220BatteryStatus battery_status;
|
||||||
OperationStatus operation_status;
|
Bq27220OperationStatus operation_status;
|
||||||
|
|
||||||
furi_hal_i2c_acquire(&furi_hal_i2c_handle_power);
|
furi_hal_i2c_acquire(&furi_hal_i2c_handle_power);
|
||||||
|
|
||||||
@@ -132,7 +128,7 @@ bool furi_hal_power_gauge_is_ok(void) {
|
|||||||
bool furi_hal_power_is_shutdown_requested(void) {
|
bool furi_hal_power_is_shutdown_requested(void) {
|
||||||
bool ret = false;
|
bool ret = false;
|
||||||
|
|
||||||
BatteryStatus battery_status;
|
Bq27220BatteryStatus battery_status;
|
||||||
|
|
||||||
furi_hal_i2c_acquire(&furi_hal_i2c_handle_power);
|
furi_hal_i2c_acquire(&furi_hal_i2c_handle_power);
|
||||||
|
|
||||||
@@ -593,8 +589,8 @@ void furi_hal_power_debug_get(PropertyValueCallback out, void* context) {
|
|||||||
PropertyValueContext property_context = {
|
PropertyValueContext property_context = {
|
||||||
.key = key, .value = value, .out = out, .sep = '.', .last = false, .context = context};
|
.key = key, .value = value, .out = out, .sep = '.', .last = false, .context = context};
|
||||||
|
|
||||||
BatteryStatus battery_status;
|
Bq27220BatteryStatus battery_status;
|
||||||
OperationStatus operation_status;
|
Bq27220OperationStatus operation_status;
|
||||||
|
|
||||||
furi_hal_i2c_acquire(&furi_hal_i2c_handle_power);
|
furi_hal_i2c_acquire(&furi_hal_i2c_handle_power);
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
#define TAG "FuriHalRandom"
|
#define TAG "FuriHalRandom"
|
||||||
|
|
||||||
static uint32_t furi_hal_random_read_rng(void) {
|
static uint32_t furi_hal_random_read_rng(void) {
|
||||||
while(LL_RNG_IsActiveFlag_CECS(RNG) && LL_RNG_IsActiveFlag_SECS(RNG) &&
|
while(LL_RNG_IsActiveFlag_CECS(RNG) || LL_RNG_IsActiveFlag_SECS(RNG) ||
|
||||||
!LL_RNG_IsActiveFlag_DRDY(RNG)) {
|
!LL_RNG_IsActiveFlag_DRDY(RNG)) {
|
||||||
/* Error handling as described in RM0434, pg. 582-583 */
|
/* Error handling as described in RM0434, pg. 582-583 */
|
||||||
if(LL_RNG_IsActiveFlag_CECS(RNG)) {
|
if(LL_RNG_IsActiveFlag_CECS(RNG)) {
|
||||||
|
|||||||
Reference in New Issue
Block a user