From cfb9c991cb025445091bfc0bb9ef23d15cc0a4b2 Mon Sep 17 00:00:00 2001 From: Nikolay Marchuk Date: Sun, 6 Oct 2024 22:56:35 +0700 Subject: [PATCH 1/6] furi_hal_random: Wait for ready state and no errors before sampling (#3933) When random output is not ready, but error state flags are not set, sampling of random generator samples zero until next value is ready. --- targets/f7/furi_hal/furi_hal_random.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/targets/f7/furi_hal/furi_hal_random.c b/targets/f7/furi_hal/furi_hal_random.c index 8b75a05c5..6269a90e1 100644 --- a/targets/f7/furi_hal/furi_hal_random.c +++ b/targets/f7/furi_hal/furi_hal_random.c @@ -11,7 +11,7 @@ #define TAG "FuriHalRandom" 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)) { /* Error handling as described in RM0434, pg. 582-583 */ if(LL_RNG_IsActiveFlag_CECS(RNG)) { From 6ead328bb7e703dc0d0db40cc05ed45ce3bcb5bf Mon Sep 17 00:00:00 2001 From: assasinfil Date: Sun, 6 Oct 2024 19:33:07 +0300 Subject: [PATCH 2/6] Moscow social card parser (#3464) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Updated troyka layout (full version) * Changed to furi func * Small refactor * Bitlib refactor * Moved to API * Rollback troyka parser * Fix functions * Added MSK Social card parser * Parser func refactor start * Layout E3 refactored * Layout E4 refactored * Layout 6 refactored * Layout E5 refactored * Layout 2 refactored * Layout E5 fix * Layout E6 refactored, valid_date need fix * Layout E6 fix * Layout FCB refactored * Layout F0B refactored * Layout 8 refactored * Layout A refactored * Layout C refactored * Layout D refactored * Layout E1 refactored * Layout E2 refactored * Old code cleanup * Memory cleanup * Unused imports cleanup * Keys struct refactor * Keys struct refactor * Layout E1 fix * Added debug info for layout and department * Fix social card parse validation * Added card number validation * Added transport data ui improvements from Astrrra's troyka render func. Co-authored-by: gornekich Co-authored-by: あく --- .../nfc/api/mosgortrans/mosgortrans_util.c | 14 + .../nfc/api/mosgortrans/mosgortrans_util.h | 5 + .../main/nfc/api/nfc_app_api_table_i.h | 9 +- applications/main/nfc/application.fam | 9 + .../plugins/supported_cards/social_moscow.c | 301 ++++++++++++++++++ .../main/nfc/plugins/supported_cards/troika.c | 36 +-- 6 files changed, 349 insertions(+), 25 deletions(-) create mode 100644 applications/main/nfc/plugins/supported_cards/social_moscow.c diff --git a/applications/main/nfc/api/mosgortrans/mosgortrans_util.c b/applications/main/nfc/api/mosgortrans/mosgortrans_util.c index 3138d790b..261f24ce0 100644 --- a/applications/main/nfc/api/mosgortrans/mosgortrans_util.c +++ b/applications/main/nfc/api/mosgortrans/mosgortrans_util.c @@ -2,6 +2,20 @@ #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) { uint32_t timestamp = days * 24 * 60 * 60; DateTime start_datetime = {0}; diff --git a/applications/main/nfc/api/mosgortrans/mosgortrans_util.h b/applications/main/nfc/api/mosgortrans/mosgortrans_util.h index 2dc469c45..e8cbd7a37 100644 --- a/applications/main/nfc/api/mosgortrans/mosgortrans_util.h +++ b/applications/main/nfc/api/mosgortrans/mosgortrans_util.h @@ -10,6 +10,11 @@ extern "C" { #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); #ifdef __cplusplus diff --git a/applications/main/nfc/api/nfc_app_api_table_i.h b/applications/main/nfc/api/nfc_app_api_table_i.h index bf0e926ee..790fa5766 100644 --- a/applications/main/nfc/api/nfc_app_api_table_i.h +++ b/applications/main/nfc/api/nfc_app_api_table_i.h @@ -15,4 +15,11 @@ static constexpr auto nfc_app_api_table = sort(create_array_t( API_METHOD( mosgortrans_parse_transport_block, 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)))); diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index 898434bb7..180be6224 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -92,6 +92,15 @@ App( 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( appid="washcity_parser", apptype=FlipperAppType.PLUGIN, diff --git a/applications/main/nfc/plugins/supported_cards/social_moscow.c b/applications/main/nfc/plugins/supported_cards/social_moscow.c new file mode 100644 index 000000000..ed2ee6c1d --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/social_moscow.c @@ -0,0 +1,301 @@ +#include "nfc_supported_card_plugin.h" +#include + +#include + +#include +#include +#include +#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; +} diff --git a/applications/main/nfc/plugins/supported_cards/troika.c b/applications/main/nfc/plugins/supported_cards/troika.c index 0c93fa59a..bd36d40e5 100644 --- a/applications/main/nfc/plugins/supported_cards/troika.c +++ b/applications/main/nfc/plugins/supported_cards/troika.c @@ -82,20 +82,6 @@ static const MfClassicKeyPair troika_4k_keys[] = { {.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) { bool success = true; @@ -212,23 +198,25 @@ static bool troika_parse(const NfcDevice* device, FuriString* parsed_data) { FuriString* ground_result = furi_string_alloc(); FuriString* tat_result = furi_string_alloc(); - bool result1 = mosgortrans_parse_transport_block(&data->block[32], metro_result); - bool result2 = mosgortrans_parse_transport_block(&data->block[28], ground_result); - bool result3 = mosgortrans_parse_transport_block(&data->block[16], tat_result); + bool is_metro_data_present = + mosgortrans_parse_transport_block(&data->block[32], metro_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"); - if(result1 && !furi_string_empty(metro_result)) { - troika_render_section_header(parsed_data, "Metro", 22, 21); + 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(result2 && !furi_string_empty(ground_result)) { - troika_render_section_header(parsed_data, "Ediny", 22, 22); + if(is_ground_data_present && !furi_string_empty(ground_result)) { + render_section_header(parsed_data, "Ediny", 22, 22); furi_string_cat_printf(parsed_data, "%s\n", furi_string_get_cstr(ground_result)); } - if(result3 && !furi_string_empty(tat_result)) { - troika_render_section_header(parsed_data, "TAT", 24, 23); + if(is_tat_data_present && !furi_string_empty(tat_result)) { + render_section_header(parsed_data, "TAT", 24, 23); 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(metro_result); - parsed = result1 || result2 || result3; + parsed = is_metro_data_present || is_ground_data_present || is_tat_data_present; } while(false); return parsed; From c3dc0ae6b985d4420b94376bebdd34a6dd90fbc8 Mon Sep 17 00:00:00 2001 From: assasinfil Date: Sun, 6 Oct 2024 19:48:12 +0300 Subject: [PATCH 3/6] Plantain parser improvements (#3469) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Refactored card nubmer and balance * Podorozhnik refactor * Balance fix and BSK card support added Co-authored-by: あく --- .../nfc/plugins/supported_cards/plantain.c | 112 +++++++++++++++--- 1 file changed, 93 insertions(+), 19 deletions(-) diff --git a/applications/main/nfc/plugins/supported_cards/plantain.c b/applications/main/nfc/plugins/supported_cards/plantain.c index bed964554..59253194e 100644 --- a/applications/main/nfc/plugins/supported_cards/plantain.c +++ b/applications/main/nfc/plugins/supported_cards/plantain.c @@ -4,10 +4,21 @@ #include #include +#include #include #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 { uint64_t a; 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)); if(key != cfg.keys[cfg.data_sector].a) break; - // Point to block 0 of sector 4, value 0 - 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 + furi_string_printf(parsed_data, "\e#Plantain card\n"); uint64_t card_number = 0; 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( - parsed_data, "\e#Plantain\nNo.: %lluX\nBalance: %lu\n", card_number, balance); + // Print card number with 4-digit groups + furi_string_cat_printf(parsed_data, "Number: "); + FuriString* card_number_s = furi_string_alloc(); + furi_string_cat_printf(card_number_s, "%lld", 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; } while(false); From 8c14361e6ae213f8f827a44acf69d6e1d52d5e70 Mon Sep 17 00:00:00 2001 From: RebornedBrain <138568282+RebornedBrain@users.noreply.github.com> Date: Sun, 6 Oct 2024 19:55:13 +0300 Subject: [PATCH 4/6] [FL-3830] Emulation freeze (#3930) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .../nfc/helpers/protocol_support/nfc_protocol_support.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c index 7a07404fd..0d63dc56b 100644 --- a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c @@ -559,6 +559,7 @@ static void nfc_protocol_support_scene_save_name_on_exit(NfcApp* instance) { */ enum { NfcSceneEmulateStateWidget, /**< Widget view is displayed. */ + NfcSceneEmulateStateWidgetLog, /**< Widget view with Log button is displayed */ NfcSceneEmulateStateTextBox, /**< TextBox view is displayed. */ }; @@ -633,12 +634,14 @@ static bool "Log", nfc_protocol_support_common_widget_callback, instance); + scene_manager_set_scene_state( + instance->scene_manager, NfcSceneEmulate, NfcSceneEmulateStateWidgetLog); } // Update TextBox data text_box_set_text(instance->text_box, furi_string_get_cstr(instance->text_box_store)); consumed = true; } else if(event.event == GuiButtonTypeCenter) { - if(state == NfcSceneEmulateStateWidget) { + if(state == NfcSceneEmulateStateWidgetLog) { view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewTextBox); scene_manager_set_scene_state( instance->scene_manager, NfcSceneEmulate, NfcSceneEmulateStateTextBox); @@ -649,7 +652,7 @@ static bool if(state == NfcSceneEmulateStateTextBox) { view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); scene_manager_set_scene_state( - instance->scene_manager, NfcSceneEmulate, NfcSceneEmulateStateWidget); + instance->scene_manager, NfcSceneEmulate, NfcSceneEmulateStateWidgetLog); consumed = true; } } From 0469ef0e55295bd19be82b9be32dd45c11ffd542 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Sun, 6 Oct 2024 19:36:05 +0100 Subject: [PATCH 5/6] FuriHal, drivers: rework gauge initialization routine (#3912) * FuriHal, drivers: rework gauge initialization, ensure that we can recover from any kind of internal/external issue * Make PVS happy * Format sources * bq27220: add gaps injection into write operations * Drivers: bq27220 cleanup and various fixes * Drivers: bq27220 verbose logging and full access routine fix * Drivers: better cfg mode exit handling in bq27220 driver * Drivers: rewrite bq27220 based on bqstudio+ev2400, experiments and guessing. Fixes all known issues. * PVS: hello license check * Drivers: minimize reset count in bq27220 init sequence * Drivers: bq27220 hide debug logging, reorganize routine to ensure predictable result and minimum amount of interaction with gauge, add documentation and notes. * Drivers: more reliable bq27220_full_access routine * Drivers: replace some warning with error in bq27220 * Drivers: move static asserts to headers in bq27220 * Fix PVS warnings * Drivers: simplify logic in bq27220 --------- Co-authored-by: hedger --- .../nfc/plugins/supported_cards/plantain.c | 2 +- lib/drivers/bq27220.c | 468 +++++++++++++++--- lib/drivers/bq27220.h | 265 ++++++++-- lib/drivers/bq27220_data_memory.h | 2 + lib/drivers/bq27220_reg.h | 136 ++--- scripts/fbt_tools/pvsstudio.py | 2 +- targets/f7/furi_hal/furi_hal_power.c | 24 +- 7 files changed, 697 insertions(+), 202 deletions(-) diff --git a/applications/main/nfc/plugins/supported_cards/plantain.c b/applications/main/nfc/plugins/supported_cards/plantain.c index 59253194e..c38140de2 100644 --- a/applications/main/nfc/plugins/supported_cards/plantain.c +++ b/applications/main/nfc/plugins/supported_cards/plantain.c @@ -228,7 +228,7 @@ static bool plantain_parse(const NfcDevice* device, FuriString* parsed_data) { // Print card number with 4-digit groups furi_string_cat_printf(parsed_data, "Number: "); FuriString* card_number_s = furi_string_alloc(); - furi_string_cat_printf(card_number_s, "%lld", card_number); + 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++) { diff --git a/lib/drivers/bq27220.c b/lib/drivers/bq27220.c index a3a88603d..d60e287da 100644 --- a/lib/drivers/bq27220.c +++ b/lib/drivers/bq27220.c @@ -1,29 +1,77 @@ - #include "bq27220.h" #include "bq27220_reg.h" #include "bq27220_data_memory.h" -_Static_assert(sizeof(BQ27220DMGaugingConfig) == 2, "Incorrect structure size"); - #include #include #define TAG "Gauge" -static uint16_t bq27220_read_word(FuriHalI2cBusHandle* handle, uint8_t address) { - uint16_t buf = 0; +#define BQ27220_ID (0x0220u) - furi_hal_i2c_read_mem( - handle, BQ27220_ADDRESS, address, (uint8_t*)&buf, 2, BQ27220_I2C_TIMEOUT); +/** Delay between 2 writes into Subclass/MAC area. Fails at ~120us. */ +#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) { - bool ret = furi_hal_i2c_write_mem( - handle, BQ27220_ADDRESS, CommandControl, (uint8_t*)&control, 2, BQ27220_I2C_TIMEOUT); +static inline bool bq27220_write( + FuriHalI2cBusHandle* handle, + 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) { @@ -56,49 +104,49 @@ static bool bq27220_parameter_check( if(update) { // 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 + // Also see note in the header - // 2. Write the address AND the parameter data to 0x3E+ (auto increment) - if(!furi_hal_i2c_write_mem( - handle, - BQ27220_ADDRESS, - CommandSelectSubclass, - buffer, - size + 2, - BQ27220_I2C_TIMEOUT)) { - FURI_LOG_I(TAG, "DM write failed"); + // Write the address AND the parameter data to 0x3E+ (auto increment) + if(!bq27220_write(handle, CommandSelectSubclass, buffer, size + 2)) { + FURI_LOG_E(TAG, "DM write failed"); 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); - // 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; // 2 bytes address, `size` bytes data, 1 byte check sum, 1 byte length buffer[1] = 2 + size + 1 + 1; - if(!furi_hal_i2c_write_mem( - handle, BQ27220_ADDRESS, CommandMACDataSum, buffer, 2, BQ27220_I2C_TIMEOUT)) { - FURI_LOG_I(TAG, "CRC write failed"); + if(!bq27220_write(handle, CommandMACDataSum, buffer, 2)) { + FURI_LOG_E(TAG, "CRC write failed"); break; } - - furi_delay_us(10000); + // Final wait as in gm.fs specification + furi_delay_us(BQ27220_CONFIG_DELAY_US); ret = true; } else { - if(!furi_hal_i2c_write_mem( - handle, BQ27220_ADDRESS, CommandSelectSubclass, buffer, 2, BQ27220_I2C_TIMEOUT)) { - FURI_LOG_I(TAG, "DM SelectSubclass for read failed"); + if(!bq27220_write(handle, CommandSelectSubclass, buffer, 2)) { + FURI_LOG_E(TAG, "DM SelectSubclass for read failed"); break; } - if(!furi_hal_i2c_rx(handle, BQ27220_ADDRESS, old_data, size, BQ27220_I2C_TIMEOUT)) { - FURI_LOG_I(TAG, "DM read failed"); + // bqstudio uses 15ms wait delay here + furi_delay_us(BQ27220_SELECT_DELAY_US); + + if(!bq27220_read_reg(handle, CommandMACData, old_data, size)) { + FURI_LOG_E(TAG, "DM read failed"); 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])) { - FURI_LOG_W( //-V641 + FURI_LOG_E( //-V641 TAG, "Data at 0x%04x(%zu): 0x%08lx!=0x%08lx", address, @@ -119,22 +167,34 @@ static bool bq27220_data_memory_check( const BQ27220DMData* data_memory, bool 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"); return false; }; // Wait for enter CFG update mode - uint32_t timeout = 100; - OperationStatus status = {0}; - while((status.CFGUPDATE != true) && (timeout-- > 0)) { - bq27220_get_operation_status(handle, &status); + 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) { + break; + }; + furi_delay_us(BQ27220_TIMEOUT_CYCLE_INTERVAL_US); } 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; } + BQ27220_DEBUG_LOG("Cycles left: %lu", timeout); } // Process data memory records @@ -179,43 +239,283 @@ static bool bq27220_data_memory_check( } // Finalize configuration update - if(update) { + if(update && result) { 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; } -bool bq27220_init(FuriHalI2cBusHandle* handle) { - // Request device number(chip PN) - if(!bq27220_control(handle, Control_DEVICE_NUMBER)) { - 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; - }; +bool bq27220_init(FuriHalI2cBusHandle* handle, const BQ27220DMData* data_memory) { + bool result = false; + bool reset_and_provisioning_required = false; - data = bq27220_read_word(handle, CommandMACData); - FURI_LOG_I(TAG, "Device Number %04x", data); + do { + // 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) { - FURI_LOG_I(TAG, "Verifying data memory"); - if(!bq27220_data_memory_check(handle, data_memory, false)) { - FURI_LOG_I(TAG, "Updating data memory"); - bq27220_data_memory_check(handle, data_memory, true); - } - FURI_LOG_I(TAG, "Data memory verification complete"); +bool bq27220_reset(FuriHalI2cBusHandle* handle) { + bool result = false; + do { + if(!bq27220_control(handle, Control_RESET)) { + FURI_LOG_E(TAG, "Reset request failed"); + break; + }; - 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) { @@ -226,24 +526,30 @@ int16_t bq27220_get_current(FuriHalI2cBusHandle* handle) { return bq27220_read_word(handle, CommandCurrent); } -bool bq27220_get_battery_status(FuriHalI2cBusHandle* handle, BatteryStatus* battery_status) { - uint16_t data = bq27220_read_word(handle, CommandBatteryStatus); - if(data == BQ27220_ERROR) { - return false; - } else { - *(uint16_t*)battery_status = data; - return true; - } +bool bq27220_get_control_status(FuriHalI2cBusHandle* handle, Bq27220ControlStatus* control_status) { + return bq27220_read_reg(handle, CommandControl, (uint8_t*)control_status, 2); } -bool bq27220_get_operation_status(FuriHalI2cBusHandle* handle, OperationStatus* operation_status) { - uint16_t data = bq27220_read_word(handle, CommandOperationStatus); - if(data == BQ27220_ERROR) { +bool bq27220_get_battery_status(FuriHalI2cBusHandle* handle, Bq27220BatteryStatus* battery_status) { + return bq27220_read_reg(handle, CommandBatteryStatus, (uint8_t*)battery_status, 2); +} + +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; - } 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) { diff --git a/lib/drivers/bq27220.h b/lib/drivers/bq27220.h index fc76e318f..cdfcb20b1 100644 --- a/lib/drivers/bq27220.h +++ b/lib/drivers/bq27220.h @@ -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 #include @@ -9,26 +37,45 @@ 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 + uint8_t BATT_ID : 3; /**< Battery Identification */ + bool SNOOZE : 1; /**< SNOOZE mode is enabled */ + bool BCA : 1; /**< fuel gauge board calibration routine is active */ + bool CCA : 1; /**< Coulomb Counter Calibration routine is active */ + uint8_t RSVD0 : 2; /**< 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 -} BatteryStatus; + uint8_t RSVD1; /**< Reserved */ +} Bq27220ControlStatus; -_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 { // Low byte, Low bit first @@ -40,53 +87,189 @@ typedef struct { bool SMTH : 1; /**< RemainingCapacity is scaled by smooth engine */ bool BTPINT : 1; /**< BTP threshold has been crossed */ // High byte, Low bit first - uint8_t RSVD1 : 2; + uint8_t RSVD1 : 2; /**< Reserved */ bool CFGUPDATE : 1; /**< Gauge is in CONFIG UPDATE mode */ - uint8_t RSVD0 : 5; -} OperationStatus; + uint8_t RSVD0 : 5; /**< Reserved */ +} 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; /** 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 - * @return true on success, false otherwise +/** Reset gauge + * + * @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); -/** 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); -/** Get battery status */ -bool bq27220_get_battery_status(FuriHalI2cBusHandle* handle, BatteryStatus* battery_status); +/** Get control 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 */ -bool bq27220_get_operation_status(FuriHalI2cBusHandle* handle, OperationStatus* operation_status); +/** Get battery 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); -/** 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); -/** 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); -/** 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); -/** 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); -/** 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); - -void bq27220_change_design_capacity(FuriHalI2cBusHandle* handle, uint16_t capacity); diff --git a/lib/drivers/bq27220_data_memory.h b/lib/drivers/bq27220_data_memory.h index 30f2dae1e..0bd9348d2 100644 --- a/lib/drivers/bq27220_data_memory.h +++ b/lib/drivers/bq27220_data_memory.h @@ -82,3 +82,5 @@ typedef struct { const bool SME0 : 1; const uint8_t RSVD3 : 3; } BQ27220DMGaugingConfig; + +_Static_assert(sizeof(BQ27220DMGaugingConfig) == 2, "Incorrect structure size"); diff --git a/lib/drivers/bq27220_reg.h b/lib/drivers/bq27220_reg.h index 1c1ec9d8f..2d93e31d0 100644 --- a/lib/drivers/bq27220_reg.h +++ b/lib/drivers/bq27220_reg.h @@ -1,68 +1,76 @@ #pragma once -#define BQ27220_ADDRESS 0xAA -#define BQ27220_I2C_TIMEOUT 50 +#define BQ27220_ADDRESS (0xAAu) +#define BQ27220_I2C_TIMEOUT (50u) -#define CommandControl 0x00 -#define CommandAtRate 0x02 -#define CommandAtRateTimeToEmpty 0x04 -#define CommandTemperature 0x06 -#define CommandVoltage 0x08 -#define CommandBatteryStatus 0x0A -#define CommandCurrent 0x0C -#define CommandRemainingCapacity 0x10 -#define CommandFullChargeCapacity 0x12 -#define CommandAverageCurrent 0x14 -#define CommandTimeToEmpty 0x16 -#define CommandTimeToFull 0x18 -#define CommandStandbyCurrent 0x1A -#define CommandStandbyTimeToEmpty 0x1C -#define CommandMaxLoadCurrent 0x1E -#define CommandMaxLoadTimeToEmpty 0x20 -#define CommandRawCoulombCount 0x22 -#define CommandAveragePower 0x24 -#define CommandInternalTemperature 0x28 -#define CommandCycleCount 0x2A -#define CommandStateOfCharge 0x2C -#define CommandStateOfHealth 0x2E -#define CommandChargeVoltage 0x30 -#define CommandChargeCurrent 0x32 -#define CommandBTPDischargeSet 0x34 -#define CommandBTPChargeSet 0x36 -#define CommandOperationStatus 0x3A -#define CommandDesignCapacity 0x3C -#define CommandSelectSubclass 0x3E -#define CommandMACData 0x40 -#define CommandMACDataSum 0x60 -#define CommandMACDataLen 0x61 -#define CommandAnalogCount 0x79 -#define CommandRawCurrent 0x7A -#define CommandRawVoltage 0x7C -#define CommandRawIntTemp 0x7E +#define CommandControl (0x00u) +#define CommandAtRate (0x02u) +#define CommandAtRateTimeToEmpty (0x04u) +#define CommandTemperature (0x06u) +#define CommandVoltage (0x08u) +#define CommandBatteryStatus (0x0Au) +#define CommandCurrent (0x0Cu) +#define CommandRemainingCapacity (0x10u) +#define CommandFullChargeCapacity (0x12u) +#define CommandAverageCurrent (0x14u) +#define CommandTimeToEmpty (0x16u) +#define CommandTimeToFull (0x18u) +#define CommandStandbyCurrent (0x1Au) +#define CommandStandbyTimeToEmpty (0x1Cu) +#define CommandMaxLoadCurrent (0x1Eu) +#define CommandMaxLoadTimeToEmpty (0x20u) +#define CommandRawCoulombCount (0x22u) +#define CommandAveragePower (0x24u) +#define CommandInternalTemperature (0x28u) +#define CommandCycleCount (0x2Au) +#define CommandStateOfCharge (0x2Cu) +#define CommandStateOfHealth (0x2Eu) +#define CommandChargeVoltage (0x30u) +#define CommandChargeCurrent (0x32u) +#define CommandBTPDischargeSet (0x34u) +#define CommandBTPChargeSet (0x36u) +#define CommandOperationStatus (0x3Au) +#define CommandDesignCapacity (0x3Cu) +#define CommandSelectSubclass (0x3Eu) +#define CommandMACData (0x40u) +#define CommandMACDataSum (0x60u) +#define CommandMACDataLen (0x61u) +#define CommandAnalogCount (0x79u) +#define CommandRawCurrent (0x7Au) +#define CommandRawVoltage (0x7Cu) +#define CommandRawIntTemp (0x7Eu) -#define Control_CONTROL_STATUS 0x0000 -#define Control_DEVICE_NUMBER 0x0001 -#define Control_FW_VERSION 0x0002 -#define Control_BOARD_OFFSET 0x0009 -#define Control_CC_OFFSET 0x000A -#define Control_CC_OFFSET_SAVE 0x000B -#define Control_OCV_CMD 0x000C -#define Control_BAT_INSERT 0x000D -#define Control_BAT_REMOVE 0x000E -#define Control_SET_SNOOZE 0x0013 -#define Control_CLEAR_SNOOZE 0x0014 -#define Control_SET_PROFILE_1 0x0015 -#define Control_SET_PROFILE_2 0x0016 -#define Control_SET_PROFILE_3 0x0017 -#define Control_SET_PROFILE_4 0x0018 -#define Control_SET_PROFILE_5 0x0019 -#define Control_SET_PROFILE_6 0x001A -#define Control_CAL_TOGGLE 0x002D -#define Control_SEALED 0x0030 -#define Control_RESET 0x0041 -#define Control_EXIT_CAL 0x0080 -#define Control_ENTER_CAL 0x0081 -#define Control_ENTER_CFG_UPDATE 0x0090 -#define Control_EXIT_CFG_UPDATE_REINIT 0x0091 -#define Control_EXIT_CFG_UPDATE 0x0092 -#define Control_RETURN_TO_ROM 0x0F00 +#define Control_CONTROL_STATUS (0x0000u) +#define Control_DEVICE_NUMBER (0x0001u) +#define Control_FW_VERSION (0x0002u) +#define Control_HW_VERSION (0x0003u) +#define Control_BOARD_OFFSET (0x0009u) +#define Control_CC_OFFSET (0x000Au) +#define Control_CC_OFFSET_SAVE (0x000Bu) +#define Control_OCV_CMD (0x000Cu) +#define Control_BAT_INSERT (0x000Du) +#define Control_BAT_REMOVE (0x000Eu) +#define Control_SET_SNOOZE (0x0013u) +#define Control_CLEAR_SNOOZE (0x0014u) +#define Control_SET_PROFILE_1 (0x0015u) +#define Control_SET_PROFILE_2 (0x0016u) +#define Control_SET_PROFILE_3 (0x0017u) +#define Control_SET_PROFILE_4 (0x0018u) +#define Control_SET_PROFILE_5 (0x0019u) +#define Control_SET_PROFILE_6 (0x001Au) +#define Control_CAL_TOGGLE (0x002Du) +#define Control_SEALED (0x0030u) +#define Control_RESET (0x0041u) +#define Control_OERATION_STATUS (0x0054u) +#define Control_GAUGING_STATUS (0x0056u) +#define Control_EXIT_CAL (0x0080u) +#define Control_ENTER_CAL (0x0081u) +#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) diff --git a/scripts/fbt_tools/pvsstudio.py b/scripts/fbt_tools/pvsstudio.py index 1a55278dc..6097a8dc9 100644 --- a/scripts/fbt_tools/pvsstudio.py +++ b/scripts/fbt_tools/pvsstudio.py @@ -47,7 +47,7 @@ def generate(env): PVSOPTIONS=[ "@.pvsoptions", "-j${PVSNCORES}", - "--disableLicenseExpirationCheck", + # "--disableLicenseExpirationCheck", # "--incremental", # kinda broken on PVS side ], PVSCONVOPTIONS=[ diff --git a/targets/f7/furi_hal/furi_hal_power.c b/targets/f7/furi_hal/furi_hal_power.c index ccbc521a6..37c6a8b1b 100644 --- a/targets/f7/furi_hal/furi_hal_power.c +++ b/targets/f7/furi_hal/furi_hal_power.c @@ -73,18 +73,14 @@ void furi_hal_power_init(void) { // Find and init gauge size_t retry = 2; while(retry > 0) { - furi_hal_power.gauge_ok = bq27220_init(&furi_hal_i2c_handle_power); - if(furi_hal_power.gauge_ok) { - furi_hal_power.gauge_ok = bq27220_apply_data_memory( - &furi_hal_i2c_handle_power, furi_hal_power_gauge_data_memory); - } + furi_hal_power.gauge_ok = + bq27220_init(&furi_hal_i2c_handle_power, furi_hal_power_gauge_data_memory); if(furi_hal_power.gauge_ok) { break; } else { - // Normal startup time is 250ms - // But if we try to access gauge at that stage it will become unresponsive - // 2 seconds timeout needed to restart communication - furi_delay_us(2020202); + // Gauge need some time to think about it's behavior + // We must wait, otherwise next init cycle will fail at unseal stage + furi_delay_us(4000000); } retry--; } @@ -110,8 +106,8 @@ void furi_hal_power_init(void) { bool furi_hal_power_gauge_is_ok(void) { bool ret = true; - BatteryStatus battery_status; - OperationStatus operation_status; + Bq27220BatteryStatus battery_status; + Bq27220OperationStatus operation_status; 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 ret = false; - BatteryStatus battery_status; + Bq27220BatteryStatus battery_status; 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 = { .key = key, .value = value, .out = out, .sep = '.', .last = false, .context = context}; - BatteryStatus battery_status; - OperationStatus operation_status; + Bq27220BatteryStatus battery_status; + Bq27220OperationStatus operation_status; furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); From 0eaad8bf64f01a6f932647a9cda5475dd9ea1524 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Mon, 7 Oct 2024 04:21:31 +0900 Subject: [PATCH 6/6] [FL-3896] Split BadUSB into BadUSB and BadBLE (#3931) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Remove BLE from BadUSB * Add the BadBLE app * Format images to 1-bit B/W * BadUsb: remove dead bits and pieces Co-authored-by: あく --- applications/main/bad_usb/application.fam | 2 +- applications/main/bad_usb/bad_usb_app.c | 8 - applications/main/bad_usb/bad_usb_app_i.h | 1 - .../main/bad_usb/helpers/bad_usb_hid.c | 156 +--- .../main/bad_usb/helpers/bad_usb_hid.h | 7 +- .../main/bad_usb/helpers/ducky_script.c | 4 +- .../main/bad_usb/helpers/ducky_script.h | 2 +- .../bad_usb/scenes/bad_usb_scene_config.c | 88 --- .../bad_usb/scenes/bad_usb_scene_config.h | 1 - .../main/bad_usb/scenes/bad_usb_scene_work.c | 4 +- .../main/bad_usb/views/bad_usb_view.c | 2 +- applications/system/bad_ble/application.fam | 12 + .../system/bad_ble/assets/Bad_BLE_48x22.png | Bin 0 -> 145 bytes applications/system/bad_ble/bad_ble_app.c | 196 +++++ applications/system/bad_ble/bad_ble_app.h | 11 + applications/system/bad_ble/bad_ble_app_i.h | 53 ++ .../system/bad_ble/helpers/bad_ble_hid.c | 157 ++++ .../system/bad_ble/helpers/bad_ble_hid.h | 34 + .../system/bad_ble/helpers/ducky_script.c | 716 ++++++++++++++++++ .../system/bad_ble/helpers/ducky_script.h | 55 ++ .../bad_ble/helpers/ducky_script_commands.c | 241 ++++++ .../system/bad_ble/helpers/ducky_script_i.h | 76 ++ .../bad_ble/helpers/ducky_script_keycodes.c | 133 ++++ applications/system/bad_ble/icon.png | Bin 0 -> 96 bytes .../system/bad_ble/scenes/bad_ble_scene.c | 30 + .../system/bad_ble/scenes/bad_ble_scene.h | 29 + .../bad_ble/scenes/bad_ble_scene_config.c | 59 ++ .../bad_ble/scenes/bad_ble_scene_config.h | 7 + .../scenes/bad_ble_scene_config_layout.c | 49 ++ .../scenes/bad_ble_scene_confirm_unpair.c | 53 ++ .../bad_ble/scenes/bad_ble_scene_error.c | 65 ++ .../scenes/bad_ble_scene_file_select.c | 46 ++ .../scenes/bad_ble_scene_unpair_done.c | 37 + .../bad_ble/scenes/bad_ble_scene_work.c | 65 ++ .../system/bad_ble/views/bad_ble_view.c | 284 +++++++ .../system/bad_ble/views/bad_ble_view.h | 26 + 36 files changed, 2444 insertions(+), 265 deletions(-) delete mode 100644 applications/main/bad_usb/scenes/bad_usb_scene_config.c create mode 100644 applications/system/bad_ble/application.fam create mode 100644 applications/system/bad_ble/assets/Bad_BLE_48x22.png create mode 100644 applications/system/bad_ble/bad_ble_app.c create mode 100644 applications/system/bad_ble/bad_ble_app.h create mode 100644 applications/system/bad_ble/bad_ble_app_i.h create mode 100644 applications/system/bad_ble/helpers/bad_ble_hid.c create mode 100644 applications/system/bad_ble/helpers/bad_ble_hid.h create mode 100644 applications/system/bad_ble/helpers/ducky_script.c create mode 100644 applications/system/bad_ble/helpers/ducky_script.h create mode 100644 applications/system/bad_ble/helpers/ducky_script_commands.c create mode 100644 applications/system/bad_ble/helpers/ducky_script_i.h create mode 100644 applications/system/bad_ble/helpers/ducky_script_keycodes.c create mode 100644 applications/system/bad_ble/icon.png create mode 100644 applications/system/bad_ble/scenes/bad_ble_scene.c create mode 100644 applications/system/bad_ble/scenes/bad_ble_scene.h create mode 100644 applications/system/bad_ble/scenes/bad_ble_scene_config.c create mode 100644 applications/system/bad_ble/scenes/bad_ble_scene_config.h create mode 100644 applications/system/bad_ble/scenes/bad_ble_scene_config_layout.c create mode 100644 applications/system/bad_ble/scenes/bad_ble_scene_confirm_unpair.c create mode 100644 applications/system/bad_ble/scenes/bad_ble_scene_error.c create mode 100644 applications/system/bad_ble/scenes/bad_ble_scene_file_select.c create mode 100644 applications/system/bad_ble/scenes/bad_ble_scene_unpair_done.c create mode 100644 applications/system/bad_ble/scenes/bad_ble_scene_work.c create mode 100644 applications/system/bad_ble/views/bad_ble_view.c create mode 100644 applications/system/bad_ble/views/bad_ble_view.h diff --git a/applications/main/bad_usb/application.fam b/applications/main/bad_usb/application.fam index 8d3909fcc..9844e248d 100644 --- a/applications/main/bad_usb/application.fam +++ b/applications/main/bad_usb/application.fam @@ -7,7 +7,7 @@ App( icon="A_BadUsb_14", order=70, resources="resources", - fap_libs=["assets", "ble_profile"], + fap_libs=["assets"], fap_icon="icon.png", fap_category="USB", ) diff --git a/applications/main/bad_usb/bad_usb_app.c b/applications/main/bad_usb/bad_usb_app.c index 2d2d4be86..1ee92bdf3 100644 --- a/applications/main/bad_usb/bad_usb_app.c +++ b/applications/main/bad_usb/bad_usb_app.c @@ -35,7 +35,6 @@ static void bad_usb_load_settings(BadUsbApp* app) { FuriString* temp_str = furi_string_alloc(); uint32_t version = 0; - uint32_t interface = 0; if(flipper_format_file_open_existing(fff, BAD_USB_SETTINGS_PATH)) { do { @@ -45,8 +44,6 @@ static void bad_usb_load_settings(BadUsbApp* app) { 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; } while(0); @@ -56,7 +53,6 @@ static void bad_usb_load_settings(BadUsbApp* app) { if(state) { furi_string_set(app->keyboard_layout, temp_str); - app->interface = interface; Storage* fs_api = furi_record_open(RECORD_STORAGE); FileInfo layout_file_info; @@ -68,7 +64,6 @@ static void bad_usb_load_settings(BadUsbApp* app) { } } else { furi_string_set(app->keyboard_layout, BAD_USB_SETTINGS_DEFAULT_LAYOUT); - app->interface = BadUsbHidInterfaceUsb; } 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)) 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); } diff --git a/applications/main/bad_usb/bad_usb_app_i.h b/applications/main/bad_usb/bad_usb_app_i.h index a4dd57d8b..e63d0044c 100644 --- a/applications/main/bad_usb/bad_usb_app_i.h +++ b/applications/main/bad_usb/bad_usb_app_i.h @@ -41,7 +41,6 @@ struct BadUsbApp { BadUsb* bad_usb_view; BadUsbScript* bad_usb_script; - BadUsbHidInterface interface; FuriHalUsbInterface* usb_if_prev; }; diff --git a/applications/main/bad_usb/helpers/bad_usb_hid.c b/applications/main/bad_usb/helpers/bad_usb_hid.c index 5d7076314..dcba7b5e9 100644 --- a/applications/main/bad_usb/helpers/bad_usb_hid.c +++ b/applications/main/bad_usb/helpers/bad_usb_hid.c @@ -1,12 +1,9 @@ #include "bad_usb_hid.h" #include -#include #include #define TAG "BadUSB HID" -#define HID_BT_KEYS_STORAGE_NAME ".bt_hid.keys" - void* hid_usb_init(FuriHalUsbHidConfig* hid_cfg) { furi_check(furi_hal_usb_set_config(&usb_hid, hid_cfg)); return NULL; @@ -72,155 +69,6 @@ static const BadUsbHidApi hid_api_usb = { .release_all = hid_usb_release_all, .get_led_state = hid_usb_get_led_state, }; - -typedef struct { - Bt* bt; - FuriHalBleProfileBase* profile; - HidStateCallback state_callback; - void* callback_context; - bool is_connected; -} BleHidInstance; - -static const BleProfileHidParams ble_hid_params = { - .device_name_prefix = "BadUSB", - .mac_xor = 0x0002, -}; - -static void hid_ble_connection_status_callback(BtStatus status, void* context) { - furi_assert(context); - BleHidInstance* ble_hid = context; - ble_hid->is_connected = (status == BtStatusConnected); - if(ble_hid->state_callback) { - ble_hid->state_callback(ble_hid->is_connected, ble_hid->callback_context); - } -} - -void* hid_ble_init(FuriHalUsbHidConfig* hid_cfg) { - UNUSED(hid_cfg); - BleHidInstance* ble_hid = malloc(sizeof(BleHidInstance)); - ble_hid->bt = furi_record_open(RECORD_BT); - bt_disconnect(ble_hid->bt); - - // Wait 2nd core to update nvm storage - furi_delay_ms(200); - - bt_keys_storage_set_storage_path(ble_hid->bt, APP_DATA_PATH(HID_BT_KEYS_STORAGE_NAME)); - - ble_hid->profile = bt_profile_start(ble_hid->bt, ble_profile_hid, (void*)&ble_hid_params); - furi_check(ble_hid->profile); - - furi_hal_bt_start_advertising(); - - bt_set_status_changed_callback(ble_hid->bt, hid_ble_connection_status_callback, ble_hid); - - return ble_hid; -} - -void hid_ble_deinit(void* inst) { - BleHidInstance* ble_hid = inst; - furi_assert(ble_hid); - - bt_set_status_changed_callback(ble_hid->bt, NULL, NULL); - bt_disconnect(ble_hid->bt); - - // Wait 2nd core to update nvm storage - furi_delay_ms(200); - bt_keys_storage_set_default_path(ble_hid->bt); - - furi_check(bt_profile_restore_default(ble_hid->bt)); - furi_record_close(RECORD_BT); - free(ble_hid); -} - -void hid_ble_set_state_callback(void* inst, HidStateCallback cb, void* context) { - BleHidInstance* ble_hid = inst; - furi_assert(ble_hid); - ble_hid->state_callback = cb; - ble_hid->callback_context = context; -} - -bool hid_ble_is_connected(void* inst) { - BleHidInstance* ble_hid = inst; - furi_assert(ble_hid); - return ble_hid->is_connected; -} - -bool hid_ble_kb_press(void* inst, uint16_t button) { - BleHidInstance* ble_hid = inst; - furi_assert(ble_hid); - return ble_profile_hid_kb_press(ble_hid->profile, button); -} - -bool hid_ble_kb_release(void* inst, uint16_t button) { - BleHidInstance* ble_hid = inst; - furi_assert(ble_hid); - return ble_profile_hid_kb_release(ble_hid->profile, button); -} - -bool hid_ble_consumer_press(void* inst, uint16_t button) { - BleHidInstance* ble_hid = inst; - furi_assert(ble_hid); - return ble_profile_hid_consumer_key_press(ble_hid->profile, button); -} - -bool hid_ble_consumer_release(void* inst, uint16_t button) { - BleHidInstance* ble_hid = inst; - furi_assert(ble_hid); - return ble_profile_hid_consumer_key_release(ble_hid->profile, button); -} - -bool hid_ble_release_all(void* inst) { - BleHidInstance* ble_hid = inst; - furi_assert(ble_hid); - bool state = ble_profile_hid_kb_release_all(ble_hid->profile); - state &= ble_profile_hid_consumer_key_release_all(ble_hid->profile); - return state; -} - -uint8_t hid_ble_get_led_state(void* inst) { - UNUSED(inst); - FURI_LOG_W(TAG, "hid_ble_get_led_state not implemented"); - return 0; -} - -static const BadUsbHidApi hid_api_ble = { - .init = hid_ble_init, - .deinit = hid_ble_deinit, - .set_state_callback = hid_ble_set_state_callback, - .is_connected = hid_ble_is_connected, - - .kb_press = hid_ble_kb_press, - .kb_release = hid_ble_kb_release, - .consumer_press = hid_ble_consumer_press, - .consumer_release = hid_ble_consumer_release, - .release_all = hid_ble_release_all, - .get_led_state = hid_ble_get_led_state, -}; - -const BadUsbHidApi* bad_usb_hid_get_interface(BadUsbHidInterface interface) { - if(interface == BadUsbHidInterfaceUsb) { - return &hid_api_usb; - } else { - return &hid_api_ble; - } -} - -void bad_usb_hid_ble_remove_pairing(void) { - Bt* bt = furi_record_open(RECORD_BT); - bt_disconnect(bt); - - // Wait 2nd core to update nvm storage - furi_delay_ms(200); - - furi_hal_bt_stop_advertising(); - - bt_keys_storage_set_storage_path(bt, APP_DATA_PATH(HID_BT_KEYS_STORAGE_NAME)); - bt_forget_bonded_devices(bt); - - // Wait 2nd core to update nvm storage - furi_delay_ms(200); - bt_keys_storage_set_default_path(bt); - - furi_check(bt_profile_restore_default(bt)); - furi_record_close(RECORD_BT); +const BadUsbHidApi* bad_usb_hid_get_interface() { + return &hid_api_usb; } diff --git a/applications/main/bad_usb/helpers/bad_usb_hid.h b/applications/main/bad_usb/helpers/bad_usb_hid.h index 71d3a58e7..feaaacd54 100644 --- a/applications/main/bad_usb/helpers/bad_usb_hid.h +++ b/applications/main/bad_usb/helpers/bad_usb_hid.h @@ -7,11 +7,6 @@ extern "C" { #include #include -typedef enum { - BadUsbHidInterfaceUsb, - BadUsbHidInterfaceBle, -} BadUsbHidInterface; - typedef struct { void* (*init)(FuriHalUsbHidConfig* hid_cfg); void (*deinit)(void* inst); @@ -26,7 +21,7 @@ typedef struct { uint8_t (*get_led_state)(void* inst); } 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); diff --git a/applications/main/bad_usb/helpers/ducky_script.c b/applications/main/bad_usb/helpers/ducky_script.c index ccc3caa81..d730fdba4 100644 --- a/applications/main/bad_usb/helpers/ducky_script.c +++ b/applications/main/bad_usb/helpers/ducky_script.c @@ -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))); } -BadUsbScript* bad_usb_script_open(FuriString* file_path, BadUsbHidInterface interface) { +BadUsbScript* bad_usb_script_open(FuriString* file_path) { furi_assert(file_path); 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.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); furi_thread_start(bad_usb->thread); diff --git a/applications/main/bad_usb/helpers/ducky_script.h b/applications/main/bad_usb/helpers/ducky_script.h index 9519623f6..43969d7b6 100644 --- a/applications/main/bad_usb/helpers/ducky_script.h +++ b/applications/main/bad_usb/helpers/ducky_script.h @@ -34,7 +34,7 @@ typedef struct { 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); diff --git a/applications/main/bad_usb/scenes/bad_usb_scene_config.c b/applications/main/bad_usb/scenes/bad_usb_scene_config.c deleted file mode 100644 index 1acf3acb1..000000000 --- a/applications/main/bad_usb/scenes/bad_usb_scene_config.c +++ /dev/null @@ -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); -} diff --git a/applications/main/bad_usb/scenes/bad_usb_scene_config.h b/applications/main/bad_usb/scenes/bad_usb_scene_config.h index 423aedc51..e640bb556 100644 --- a/applications/main/bad_usb/scenes/bad_usb_scene_config.h +++ b/applications/main/bad_usb/scenes/bad_usb_scene_config.h @@ -1,5 +1,4 @@ ADD_SCENE(bad_usb, file_select, FileSelect) ADD_SCENE(bad_usb, work, Work) ADD_SCENE(bad_usb, error, Error) -ADD_SCENE(bad_usb, config, Config) ADD_SCENE(bad_usb, config_layout, ConfigLayout) diff --git a/applications/main/bad_usb/scenes/bad_usb_scene_work.c b/applications/main/bad_usb/scenes/bad_usb_scene_work.c index 0a383f029..0afc056b6 100644 --- a/applications/main/bad_usb/scenes/bad_usb_scene_work.c +++ b/applications/main/bad_usb/scenes/bad_usb_scene_work.c @@ -20,7 +20,7 @@ bool bad_usb_scene_work_on_event(void* context, SceneManagerEvent event) { bad_usb_script_close(app->bad_usb_script); app->bad_usb_script = NULL; - scene_manager_next_scene(app->scene_manager, BadUsbSceneConfig); + scene_manager_next_scene(app->scene_manager, BadUsbSceneConfigLayout); } consumed = true; } 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) { 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); FuriString* file_name; diff --git a/applications/main/bad_usb/views/bad_usb_view.c b/applications/main/bad_usb/views/bad_usb_view.c index 728f84348..7fb0b1434 100644 --- a/applications/main/bad_usb/views/bad_usb_view.c +++ b/applications/main/bad_usb/views/bad_usb_view.c @@ -47,7 +47,7 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) { if((state == BadUsbStateIdle) || (state == BadUsbStateDone) || (state == BadUsbStateNotConnected)) { elements_button_center(canvas, "Run"); - elements_button_left(canvas, "Config"); + elements_button_left(canvas, "Layout"); } else if((state == BadUsbStateRunning) || (state == BadUsbStateDelay)) { elements_button_center(canvas, "Stop"); if(!model->pause_wait) { diff --git a/applications/system/bad_ble/application.fam b/applications/system/bad_ble/application.fam new file mode 100644 index 000000000..e00e6eefc --- /dev/null +++ b/applications/system/bad_ble/application.fam @@ -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", +) diff --git a/applications/system/bad_ble/assets/Bad_BLE_48x22.png b/applications/system/bad_ble/assets/Bad_BLE_48x22.png new file mode 100644 index 0000000000000000000000000000000000000000..5f6fa6f4694972b23d9d0a219f404f16c18f6403 GIT binary patch literal 145 zcmeAS@N?(olHy`uVBq!ia0vp^20$#v2qYNR^(cYp2u~Nskcv6JXAQX;3mdKI;Vst091lAb^rhX literal 0 HcmV?d00001 diff --git a/applications/system/bad_ble/bad_ble_app.c b/applications/system/bad_ble/bad_ble_app.c new file mode 100644 index 000000000..f24337198 --- /dev/null +++ b/applications/system/bad_ble/bad_ble_app.c @@ -0,0 +1,196 @@ +#include "bad_ble_app_i.h" +#include +#include +#include +#include +#include + +#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; +} diff --git a/applications/system/bad_ble/bad_ble_app.h b/applications/system/bad_ble/bad_ble_app.h new file mode 100644 index 000000000..11954836e --- /dev/null +++ b/applications/system/bad_ble/bad_ble_app.h @@ -0,0 +1,11 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct BadBleApp BadBleApp; + +#ifdef __cplusplus +} +#endif diff --git a/applications/system/bad_ble/bad_ble_app_i.h b/applications/system/bad_ble/bad_ble_app_i.h new file mode 100644 index 000000000..d1f739beb --- /dev/null +++ b/applications/system/bad_ble/bad_ble_app_i.h @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#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; diff --git a/applications/system/bad_ble/helpers/bad_ble_hid.c b/applications/system/bad_ble/helpers/bad_ble_hid.c new file mode 100644 index 000000000..c34b3c646 --- /dev/null +++ b/applications/system/bad_ble/helpers/bad_ble_hid.c @@ -0,0 +1,157 @@ +#include "bad_ble_hid.h" +#include +#include +#include + +#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); +} diff --git a/applications/system/bad_ble/helpers/bad_ble_hid.h b/applications/system/bad_ble/helpers/bad_ble_hid.h new file mode 100644 index 000000000..b06385d6d --- /dev/null +++ b/applications/system/bad_ble/helpers/bad_ble_hid.h @@ -0,0 +1,34 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +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 diff --git a/applications/system/bad_ble/helpers/ducky_script.c b/applications/system/bad_ble/helpers/ducky_script.c new file mode 100644 index 000000000..a903fbdc4 --- /dev/null +++ b/applications/system/bad_ble/helpers/ducky_script.c @@ -0,0 +1,716 @@ +#include +#include +#include +#include +#include +#include +#include +#include "ducky_script.h" +#include "ducky_script_i.h" +#include + +#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); +} diff --git a/applications/system/bad_ble/helpers/ducky_script.h b/applications/system/bad_ble/helpers/ducky_script.h new file mode 100644 index 000000000..044cae825 --- /dev/null +++ b/applications/system/bad_ble/helpers/ducky_script.h @@ -0,0 +1,55 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#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 diff --git a/applications/system/bad_ble/helpers/ducky_script_commands.c b/applications/system/bad_ble/helpers/ducky_script_commands.c new file mode 100644 index 000000000..f70c5eba4 --- /dev/null +++ b/applications/system/bad_ble/helpers/ducky_script_commands.c @@ -0,0 +1,241 @@ +#include +#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; +} diff --git a/applications/system/bad_ble/helpers/ducky_script_i.h b/applications/system/bad_ble/helpers/ducky_script_i.h new file mode 100644 index 000000000..a5581d206 --- /dev/null +++ b/applications/system/bad_ble/helpers/ducky_script_i.h @@ -0,0 +1,76 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#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 diff --git a/applications/system/bad_ble/helpers/ducky_script_keycodes.c b/applications/system/bad_ble/helpers/ducky_script_keycodes.c new file mode 100644 index 000000000..290618c13 --- /dev/null +++ b/applications/system/bad_ble/helpers/ducky_script_keycodes.c @@ -0,0 +1,133 @@ +#include +#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; +} diff --git a/applications/system/bad_ble/icon.png b/applications/system/bad_ble/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..27355f8dbab9f62f03c3114bd345117b72703df2 GIT binary patch literal 96 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2xkYHHq`AGmsse8IOhE&W+{!>5Ur5*F)|L$Vj tulr2n@!{d|IW8GdR-3Ztdxz&lMuxeII5)+8P-F*b^>p=fS?83{1OR(_90~vc literal 0 HcmV?d00001 diff --git a/applications/system/bad_ble/scenes/bad_ble_scene.c b/applications/system/bad_ble/scenes/bad_ble_scene.c new file mode 100644 index 000000000..351bb1e79 --- /dev/null +++ b/applications/system/bad_ble/scenes/bad_ble_scene.c @@ -0,0 +1,30 @@ +#include "bad_ble_scene.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const bad_ble_scene_on_enter_handlers[])(void*) = { +#include "bad_ble_scene_config.h" +}; +#undef ADD_SCENE + +// Generate scene on_event handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event, +bool (*const bad_ble_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = { +#include "bad_ble_scene_config.h" +}; +#undef ADD_SCENE + +// Generate scene on_exit handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit, +void (*const bad_ble_scene_on_exit_handlers[])(void* context) = { +#include "bad_ble_scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers bad_ble_scene_handlers = { + .on_enter_handlers = bad_ble_scene_on_enter_handlers, + .on_event_handlers = bad_ble_scene_on_event_handlers, + .on_exit_handlers = bad_ble_scene_on_exit_handlers, + .scene_num = BadBleSceneNum, +}; diff --git a/applications/system/bad_ble/scenes/bad_ble_scene.h b/applications/system/bad_ble/scenes/bad_ble_scene.h new file mode 100644 index 000000000..25b19fc4b --- /dev/null +++ b/applications/system/bad_ble/scenes/bad_ble_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) BadBleScene##id, +typedef enum { +#include "bad_ble_scene_config.h" + BadBleSceneNum, +} BadBleScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers bad_ble_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "bad_ble_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_event handlers declaration +#define ADD_SCENE(prefix, name, id) \ + bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event); +#include "bad_ble_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_exit handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context); +#include "bad_ble_scene_config.h" +#undef ADD_SCENE diff --git a/applications/system/bad_ble/scenes/bad_ble_scene_config.c b/applications/system/bad_ble/scenes/bad_ble_scene_config.c new file mode 100644 index 000000000..1f64f1903 --- /dev/null +++ b/applications/system/bad_ble/scenes/bad_ble_scene_config.c @@ -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); +} diff --git a/applications/system/bad_ble/scenes/bad_ble_scene_config.h b/applications/system/bad_ble/scenes/bad_ble_scene_config.h new file mode 100644 index 000000000..5675fca59 --- /dev/null +++ b/applications/system/bad_ble/scenes/bad_ble_scene_config.h @@ -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) diff --git a/applications/system/bad_ble/scenes/bad_ble_scene_config_layout.c b/applications/system/bad_ble/scenes/bad_ble_scene_config_layout.c new file mode 100644 index 000000000..594525dd7 --- /dev/null +++ b/applications/system/bad_ble/scenes/bad_ble_scene_config_layout.c @@ -0,0 +1,49 @@ +#include "../bad_ble_app_i.h" +#include + +static bool bad_ble_layout_select(BadBleApp* bad_ble) { + furi_assert(bad_ble); + + FuriString* predefined_path; + predefined_path = furi_string_alloc(); + if(!furi_string_empty(bad_ble->keyboard_layout)) { + furi_string_set(predefined_path, bad_ble->keyboard_layout); + } else { + furi_string_set(predefined_path, BAD_BLE_APP_PATH_LAYOUT_FOLDER); + } + + DialogsFileBrowserOptions browser_options; + dialog_file_browser_set_basic_options( + &browser_options, BAD_BLE_APP_LAYOUT_EXTENSION, &I_keyboard_10px); + 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; +} diff --git a/applications/system/bad_ble/scenes/bad_ble_scene_confirm_unpair.c b/applications/system/bad_ble/scenes/bad_ble_scene_confirm_unpair.c new file mode 100644 index 000000000..63f1e92cf --- /dev/null +++ b/applications/system/bad_ble/scenes/bad_ble_scene_confirm_unpair.c @@ -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); +} diff --git a/applications/system/bad_ble/scenes/bad_ble_scene_error.c b/applications/system/bad_ble/scenes/bad_ble_scene_error.c new file mode 100644 index 000000000..c9c2b12da --- /dev/null +++ b/applications/system/bad_ble/scenes/bad_ble_scene_error.c @@ -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); +} diff --git a/applications/system/bad_ble/scenes/bad_ble_scene_file_select.c b/applications/system/bad_ble/scenes/bad_ble_scene_file_select.c new file mode 100644 index 000000000..2a182a874 --- /dev/null +++ b/applications/system/bad_ble/scenes/bad_ble_scene_file_select.c @@ -0,0 +1,46 @@ +#include "../bad_ble_app_i.h" +#include +#include + +static bool bad_ble_file_select(BadBleApp* bad_ble) { + furi_assert(bad_ble); + + DialogsFileBrowserOptions browser_options; + dialog_file_browser_set_basic_options( + &browser_options, BAD_BLE_APP_SCRIPT_EXTENSION, &I_badusb_10px); + browser_options.base_path = BAD_BLE_APP_BASE_FOLDER; + browser_options.skip_assets = true; + + // Input events and views are managed by file_browser + bool res = dialog_file_browser_show( + bad_ble->dialogs, bad_ble->file_path, bad_ble->file_path, &browser_options); + + return res; +} + +void bad_ble_scene_file_select_on_enter(void* context) { + BadBleApp* bad_ble = context; + + 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; +} diff --git a/applications/system/bad_ble/scenes/bad_ble_scene_unpair_done.c b/applications/system/bad_ble/scenes/bad_ble_scene_unpair_done.c new file mode 100644 index 000000000..4c1fe3366 --- /dev/null +++ b/applications/system/bad_ble/scenes/bad_ble_scene_unpair_done.c @@ -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); +} diff --git a/applications/system/bad_ble/scenes/bad_ble_scene_work.c b/applications/system/bad_ble/scenes/bad_ble_scene_work.c new file mode 100644 index 000000000..ff71edc3c --- /dev/null +++ b/applications/system/bad_ble/scenes/bad_ble_scene_work.c @@ -0,0 +1,65 @@ +#include "../helpers/ducky_script.h" +#include "../bad_ble_app_i.h" +#include "../views/bad_ble_view.h" +#include +#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); +} diff --git a/applications/system/bad_ble/views/bad_ble_view.c b/applications/system/bad_ble/views/bad_ble_view.c new file mode 100644 index 000000000..28f935733 --- /dev/null +++ b/applications/system/bad_ble/views/bad_ble_view.c @@ -0,0 +1,284 @@ +#include "bad_ble_view.h" +#include "../helpers/ducky_script.h" +#include +#include +#include +#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; +} diff --git a/applications/system/bad_ble/views/bad_ble_view.h b/applications/system/bad_ble/views/bad_ble_view.h new file mode 100644 index 000000000..e26488818 --- /dev/null +++ b/applications/system/bad_ble/views/bad_ble_view.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#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);