diff --git a/.drone.yml b/.drone.yml index 7776bce6b..a03063528 100644 --- a/.drone.yml +++ b/.drone.yml @@ -85,7 +85,7 @@ steps: - zip -r artifacts-extra-apps/flipper-z-f7-update-${DRONE_TAG}e.zip artifacts-extra-apps/f7-update-${DRONE_TAG}e - zip -r artifacts-ofw-anims/flipper-z-f7-update-${DRONE_TAG}n.zip artifacts-ofw-anims/f7-update-${DRONE_TAG}n - zip -r artifacts-default/flipper-z-f7-update-${DRONE_TAG}.zip artifacts-default/f7-update-${DRONE_TAG} - - tar czpf artifacts-default/flipper-z-any-scripts-${DRONE_TAG}.tgz scripts debug + - tar czpf artifacts-default/flipper-z-any-scripts-${DRONE_TAG}.tgz scripts - rm -rf artifacts-extra-apps/f7-update-${DRONE_TAG} - rm -rf artifacts-ofw-anims/f7-update-${DRONE_TAG} - rm -rf artifacts-default/f7-update-${DRONE_TAG} diff --git a/.vscode/example/tasks.json b/.vscode/example/tasks.json index 28e67d456..16437cb10 100644 --- a/.vscode/example/tasks.json +++ b/.vscode/example/tasks.json @@ -13,7 +13,7 @@ "label": "[Debug] Build", "group": "build", "type": "shell", - "command": "./fbt" + "command": "./fbt FIRMWARE_APP_SET=debug_pack" }, { "label": "[Release] Flash (ST-Link)", @@ -25,7 +25,7 @@ "label": "[Debug] Flash (ST-Link)", "group": "build", "type": "shell", - "command": "./fbt FORCE=1 flash" + "command": "./fbt FIRMWARE_APP_SET=debug_pack FORCE=1 flash" }, { "label": "[Release] Flash (blackmagic)", @@ -37,7 +37,7 @@ "label": "[Debug] Flash (blackmagic)", "group": "build", "type": "shell", - "command": "./fbt FORCE=1 flash_blackmagic" + "command": "./fbt FIRMWARE_APP_SET=debug_pack FORCE=1 flash_blackmagic" }, { "label": "[Release] Flash (JLink)", @@ -49,7 +49,7 @@ "label": "[Debug] Flash (JLink)", "group": "build", "type": "shell", - "command": "./fbt FORCE=1 jflash" + "command": "./fbt FIRMWARE_APP_SET=debug_pack FORCE=1 jflash" }, { "label": "[Release] Build update bundle", @@ -61,7 +61,7 @@ "label": "[Debug] Build update bundle", "group": "build", "type": "shell", - "command": "./fbt updater_package" + "command": "./fbt FIRMWARE_APP_SET=debug_pack updater_package" }, { "label": "[Release] Build updater", @@ -73,13 +73,13 @@ "label": "[Debug] Build updater", "group": "build", "type": "shell", - "command": "./fbt updater_all" + "command": "./fbt FIRMWARE_APP_SET=debug_pack updater_all" }, { "label": "[Debug] Flash (USB, w/o resources)", "group": "build", "type": "shell", - "command": "./fbt FORCE=1 flash_usb" + "command": "./fbt FIRMWARE_APP_SET=debug_pack FORCE=1 flash_usb" }, { "label": "[Release] Flash (USB, w/o resources)", @@ -97,7 +97,7 @@ "label": "[Debug] Flash (USB, with resources)", "group": "build", "type": "shell", - "command": "./fbt FORCE=1 flash_usb_full" + "command": "./fbt FIRMWARE_APP_SET=debug_pack FORCE=1 flash_usb_full" }, { "label": "[Release] Flash (USB, with resources)", diff --git a/CHANGELOG.md b/CHANGELOG.md index bd3ff9a6b..a16fc8256 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,33 @@ -### New changes +### New changes +* **Warning! After installing, Desktop settings (Favoutite apps, PIN Code, AutoLock time..) will be resetted to default due to OFW changes, Please set your PIN code, Favourite apps again in Settings->Desktop** +* New way of changing device name -> **Now can be changed in Settings->Desktop** (by @xMasterX and @Willy-JL) +* Plugins: BadBT plugin (BT version of BadKB) [(by Willy-JL, ClaraCrazy, XFW contributors)](https://github.com/ClaraCrazy/Flipper-Xtreme/tree/dev/applications/main/bad_kb) +* Plugins: WiFi Marauder -> Added sniff pmkid on selected aps from 0.10.4 update (by @clipboard1) +* Plugins: SubGHz Bruteforcer -> Increase delay just a little bit to fix some cases when receiver will not get codes and decrease manual transmit delay +* Plugins: UART Terminal -> Fix crashes on plugin load with RX connected +* NFC: Mifare mini with SAK 0x89 support +* SubGHz: **CAME Atomo - Add manually support and custom buttons support** +* SubGHz: Fix crashes when deleting signals using right arrow button in `Read` mode +* SubGHz: Restore Rx indication after deletion after Memory is FULL (by @wosk | PR #464) +* SubGHz: **App refactoring** (OFW code ported + our code was refactored/cleaned up too) (by @gid9798 and @xMasterX | PR #461) +* SubGHz: Using scene manager functions in DecodeRAW (by @gid9798 | PR #462) +* SubGHz: Protocols and custom buttons refactoring (by @gid9798 | PR #465) +* SubGHz: Move `counter increase` setting out of debug, change max value +* GUI: Submenu locked elements (by @Willy-JL and @giacomoferretti) +* GUI: Text Input improvements, added cursor and ability to set minimal length (by @Willy-JL) +* BT API: Functions that allow to change bt mac address and device broadcasted name (by @Willy-JL and XFW contributors) +* Infrared: `External output` move out of debug and add power option for external modules +* Infrared: Updated universal remote assets (by @amec0e | PR #474) * Extra pack: Some app fixes +* FBT: Fix vscode example config for debug builds - please run `./fbt vscode_dist` again if you had issues with debug builds size +* OFW PR 2316: NFC V support (by @g3gg0 & @nvx) +* OFW PR 2669: nfc: Fix MFUL tearing flags read (by @GMMan) +* OFW PR 2666: BadUSB: Add fr-FR-mac key layout (by @FelixLgr) +* OFW: api: added lib/nfc/protocols/nfc_util.h +* OFW: fix PIN retry count reset on reboot +* OFW: fbt: allow strings for fap_version field in app manifests +* OFW: Rpc: add desktop service. Desktop: refactor locking routine. **Now PIN lock is actually cannot be bypassed by reboot!** / **Desktop settings will be reset, please set your PIN and favourite apps again!** +* OFW: Part 2 of hooking C2 IPC * OFW: ble: attempt to handle hardfaulted c2 * OFW: Add Mfkey32 application * OFW: Added DigitalSequence and PulseReader diff --git a/ReadMe.md b/ReadMe.md index 91ca8235d..596891d6e 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -43,7 +43,8 @@ Our Discord Community: * Picopass/iClass plugin included in releases * Recompiled IR TV Universal Remote for ALL buttons * Universal remote for Projectors, Fans, A/Cs and Audio(soundbars, etc.) -* Customizable Flipper name +* Customizable Flipper name **Update! Now can be changed in Settings->Desktop** (by @xMasterX and @Willy-JL) +* Text Input UI element -> Cursor feature (by @Willy-JL) - BadUSB -> Keyboard layouts [(by rien > dummy-decoy)](https://github.com/dummy-decoy/flipperzero-firmware/tree/dummy_decoy/bad_usb_keyboard_layout) - Sub-GHz -> External CC1101 module support - [(by quen0n)](https://github.com/DarkFlippers/unleashed-firmware/pull/307) - Sub-GHz -> `Add manually` menu extended with new protocols @@ -58,8 +59,8 @@ Our Discord Community: * SubGHz -> Read mode UI improvements (scrolling text, + shows time when signal was received) (by @wosk) * Sub-GHz -> External CC1101 module support (Hardware SPI used) * SubGHz -> **Hold right in received signal list to delete selected signal** -* SubGHz -> **Custom buttons for Keeloq / Alutech AT4N / Nice Flor S / Somfy Telis / Security+ 2.0** - now you can use arrow buttons to send signal with different button code -* SubGHz -> BFT Mitto / Somfy Telis / Nice Flor S manual creation with programming new remote into receiver (use button 0xF for BFT Mitto, 0x8 (Prog) on Somfy Telis) +* SubGHz -> **Custom buttons for Keeloq / Alutech AT4N / Nice Flor S / Somfy Telis / Security+ 2.0 / CAME Atomo** - now you can use arrow buttons to send signal with different button code +* SubGHz -> BFT Mitto / Somfy Telis / Nice Flor S / CAME Atomo, etc.. manual creation with programming new remote into receiver (use button 0xF for BFT Mitto, 0x8 (Prog) on Somfy Telis) * SubGHz -> Debug mode counter increase settings (+1 -> +5, +10, default: +1) * SubGHz -> Debug PIN output settings for protocol development * Infrared -> Debug TX PIN output settings @@ -75,6 +76,7 @@ Keeloq [Not ALL systems supported for decode or emulation yet!] - [Supported man Encoders or sending made by @xMasterX: - Nero Radio 57bit (+ 56bit encoder improvements) +- CAME 12bit/24bit encoder fixes (already merged in OFW) - Keeloq: HCS101 - Keeloq: AN-Motors - Keeloq: JCM Tech @@ -89,20 +91,20 @@ Encoders or sending made by @xMasterX: - Keeloq: CAME Space - Keeloq: Aprimatic (model TR and similar) -Encoders/sending made by @Eng1n33r & @xMasterX: -- CAME Atomo -- Nice Flor S +Encoders or sending made by @Eng1n33r(first implementation in Q2 2022) & @xMasterX (current version): +- CAME Atomo -> Update! check out new [instructions](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/SubGHzRemoteProg.md) +- Nice Flor S -> How to create new remote - [instructions](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/SubGHzRemoteProg.md) - FAAC SLH (Spa) [External seed calculation required (For info contact me in Discord: Nano#8998)] - Keeloq: BFT Mitto [External seed calculation required (For info contact me in Discord: Nano#8998)] -> Update! check out new [instructions](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/SubGHzRemoteProg.md) - Star Line -- Security+ v1 & v2 +- Security+ v1 & v2 (encoders was made in OFW) Encoders made by @assasinfil & @xMasterX: -- Somfy Telis +- Somfy Telis -> How to create new remote - [instructions](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/SubGHzRemoteProg.md) - Somfy Keytis - KingGates Stylo 4k -- Alutech AT-4N -- Nice ON2E (Nice One) +- Alutech AT-4N -> How to create new remote - [instructions](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/SubGHzRemoteProg.md) +- Nice ON2E (Nice One) -> How to create new remote - [instructions](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/SubGHzRemoteProg.md) ## Please support development of the project The majority of this project is developed and maintained by me, @xMasterX. @@ -162,6 +164,7 @@ You can support us by using links or addresses below: - **ProtoView** [(by antirez)](https://github.com/antirez/protoview) - **SWD Probe** [(by g3gg0)](https://github.com/g3gg0/flipper-swd_probe) - IR Scope [(by kallanreed)](https://github.com/DarkFlippers/unleashed-firmware/pull/407) +- BadBT plugin (BT version of BadKB) [(by Willy-JL, ClaraCrazy, XFW contributors)](https://github.com/ClaraCrazy/Flipper-Xtreme/tree/dev/applications/main/bad_kb) Games: - DOOM (fixed) [(by p4nic4ttack)](https://github.com/p4nic4ttack/doom-flipper-zero/) diff --git a/applications/external/bad_bt/application.fam b/applications/external/bad_bt/application.fam new file mode 100644 index 000000000..ddd8eee19 --- /dev/null +++ b/applications/external/bad_bt/application.fam @@ -0,0 +1,17 @@ +App( + appid="bad_bt", + name="Bad BT", + apptype=FlipperAppType.EXTERNAL, + entry_point="bad_bt_app", + requires=[ + "gui", + "dialogs", + ], + stack_size=2 * 1024, + order=70, + fap_libs=["assets"], + fap_category="Tools", + fap_icon="images/badbt_10px.png", + fap_icon_assets="images", + fap_icon_assets_symbol="bad_bt", +) diff --git a/applications/external/bad_bt/bad_bt_app.c b/applications/external/bad_bt/bad_bt_app.c new file mode 100644 index 000000000..6ac7d4fa4 --- /dev/null +++ b/applications/external/bad_bt/bad_bt_app.c @@ -0,0 +1,333 @@ +#include "bad_bt_app.h" +#include +#include +#include +#include +#include + +#include +#include + +#define BAD_BT_SETTINGS_FILE_NAME ".badbt.settings" +#define BAD_BT_APP_PATH_BOUND_KEYS_FOLDER EXT_PATH("badbt") +#define BAD_BT_APP_PATH_BOUND_KEYS_FILE BAD_BT_APP_PATH_BOUND_KEYS_FOLDER "/.badbt.keys" + +#define BAD_BT_SETTINGS_PATH BAD_BT_APP_BASE_CONFIG_FOLDER "/" BAD_BT_SETTINGS_FILE_NAME + +static bool bad_bt_app_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + BadBtApp* app = context; + return scene_manager_handle_custom_event(app->scene_manager, event); +} + +static bool bad_bt_app_back_event_callback(void* context) { + furi_assert(context); + BadBtApp* app = context; + return scene_manager_handle_back_event(app->scene_manager); +} + +static void bad_bt_app_tick_event_callback(void* context) { + furi_assert(context); + BadBtApp* app = context; + scene_manager_handle_tick_event(app->scene_manager); +} + +static void bad_bt_load_settings(BadBtApp* app) { + furi_string_reset(app->keyboard_layout); + strcpy(app->config.bt_name, ""); + memcpy( + app->config.bt_mac, + furi_hal_bt_get_profile_mac_addr(FuriHalBtProfileHidKeyboard), + BAD_BT_MAC_ADDRESS_LEN); + + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* file = flipper_format_file_alloc(storage); + if(flipper_format_file_open_existing(file, BAD_BT_SETTINGS_PATH)) { + FuriString* tmp_str = furi_string_alloc(); + if(!flipper_format_read_string(file, "Keyboard_Layout", app->keyboard_layout)) { + furi_string_reset(app->keyboard_layout); + } + if(!flipper_format_read_bool(file, "BT_Remember", &(app->bt_remember), 1)) { + app->bt_remember = false; + } + if(flipper_format_read_string(file, "Bt_Name", tmp_str) && !furi_string_empty(tmp_str)) { + strcpy(app->config.bt_name, furi_string_get_cstr(tmp_str)); + } else { + strcpy(app->config.bt_name, ""); + } + if(!flipper_format_read_hex( + file, "Bt_Mac", (uint8_t*)&app->config.bt_mac, BAD_BT_MAC_ADDRESS_LEN)) { + memcpy( + app->config.bt_mac, + furi_hal_bt_get_profile_mac_addr(FuriHalBtProfileHidKeyboard), + BAD_BT_MAC_ADDRESS_LEN); + } + furi_string_free(tmp_str); + flipper_format_file_close(file); + } + flipper_format_free(file); + + if(!furi_string_empty(app->keyboard_layout)) { + FileInfo layout_file_info; + FS_Error file_check_err = storage_common_stat( + storage, furi_string_get_cstr(app->keyboard_layout), &layout_file_info); + if(file_check_err != FSE_OK) { + furi_string_reset(app->keyboard_layout); + return; + } + if(layout_file_info.size != 256) { + furi_string_reset(app->keyboard_layout); + } + } + + furi_record_close(RECORD_STORAGE); +} + +static void bad_bt_save_settings(BadBtApp* app) { + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* file = flipper_format_file_alloc(storage); + if(flipper_format_file_open_always(file, BAD_BT_SETTINGS_PATH)) { + flipper_format_write_string(file, "Keyboard_Layout", app->keyboard_layout); + flipper_format_write_bool(file, "BT_Remember", &(app->bt_remember), 1); + flipper_format_write_string_cstr(file, "Bt_Name", app->config.bt_name); + flipper_format_write_hex( + file, "Bt_Mac", (uint8_t*)&app->config.bt_mac, BAD_BT_MAC_ADDRESS_LEN); + flipper_format_file_close(file); + } + flipper_format_free(file); + furi_record_close(RECORD_STORAGE); +} + +void bad_bt_reload_worker(BadBtApp* app) { + bad_bt_script_close(app->bad_bt_script); + app->bad_bt_script = bad_bt_script_open(app->file_path, app->bt, app); + bad_bt_script_set_keyboard_layout(app->bad_bt_script, app->keyboard_layout); +} + +void bad_kb_config_refresh_menu(BadBtApp* app) { + scene_manager_next_scene(app->scene_manager, BadBtSceneConfig); + scene_manager_previous_scene(app->scene_manager); +} + +int32_t bad_bt_config_switch_mode(BadBtApp* app) { + bad_bt_reload_worker(app); + furi_hal_bt_start_advertising(); + bad_kb_config_refresh_menu(app); + return 0; +} + +void bad_bt_config_switch_remember_mode(BadBtApp* app) { + if(app->bt_remember) { + furi_hal_bt_set_profile_pairing_method( + FuriHalBtProfileHidKeyboard, GapPairingPinCodeVerifyYesNo); + bt_set_profile_mac_address(app->bt, (uint8_t*)&BAD_BT_BOUND_MAC_ADDRESS); + bt_enable_peer_key_update(app->bt); + } else { + furi_hal_bt_set_profile_pairing_method(FuriHalBtProfileHidKeyboard, GapPairingNone); + bt_set_profile_mac_address(app->bt, app->config.bt_mac); + bt_disable_peer_key_update(app->bt); + } + bad_bt_reload_worker(app); +} + +int32_t bad_bt_connection_init(BadBtApp* app) { + // Set original name and mac address in prev config + strcpy( + app->prev_config.bt_name, furi_hal_bt_get_profile_adv_name(FuriHalBtProfileHidKeyboard)); + + memcpy(app->prev_config.bt_mac, furi_hal_version_get_ble_mac(), BAD_BT_MAC_ADDRESS_LEN); + + bt_timeout = bt_hid_delays[LevelRssi39_0]; + bt_disconnect(app->bt); + // Wait 2nd core to update nvm storage + furi_delay_ms(200); + bt_keys_storage_set_storage_path(app->bt, BAD_BT_APP_PATH_BOUND_KEYS_FILE); + if(strcmp(app->config.bt_name, "") != 0) { + furi_hal_bt_set_profile_adv_name(FuriHalBtProfileHidKeyboard, app->config.bt_name); + } + if(app->bt_remember) { + furi_hal_bt_set_profile_mac_addr( + FuriHalBtProfileHidKeyboard, (uint8_t*)&BAD_BT_BOUND_MAC_ADDRESS); + furi_hal_bt_set_profile_pairing_method( + FuriHalBtProfileHidKeyboard, GapPairingPinCodeVerifyYesNo); + } else { + if(memcmp( + app->config.bt_mac, (uint8_t*)&BAD_BT_EMPTY_MAC_ADDRESS, BAD_BT_MAC_ADDRESS_LEN) != + 0) { + furi_hal_bt_set_profile_mac_addr(FuriHalBtProfileHidKeyboard, app->config.bt_mac); + } + furi_hal_bt_set_profile_pairing_method(FuriHalBtProfileHidKeyboard, GapPairingNone); + } + bt_set_profile(app->bt, BtProfileHidKeyboard); + if(strcmp(app->config.bt_name, "") == 0) { + strcpy(app->config.bt_name, furi_hal_bt_get_profile_adv_name(FuriHalBtProfileHidKeyboard)); + } + if(memcmp(app->config.bt_mac, (uint8_t*)&BAD_BT_EMPTY_MAC_ADDRESS, BAD_BT_MAC_ADDRESS_LEN) == + 0) { + memcpy( + app->config.bt_mac, + furi_hal_bt_get_profile_mac_addr(FuriHalBtProfileHidKeyboard), + BAD_BT_MAC_ADDRESS_LEN); + } + + furi_hal_bt_start_advertising(); + if(app->bt_remember) { + bt_enable_peer_key_update(app->bt); + } else { + bt_disable_peer_key_update(app->bt); + } + + return 0; +} + +void bad_bt_connection_deinit(BadBtApp* app) { + bt_disconnect(app->bt); + // Wait 2nd core to update nvm storage + furi_delay_ms(200); + bt_keys_storage_set_default_path(app->bt); + furi_hal_bt_set_profile_adv_name(FuriHalBtProfileHidKeyboard, app->prev_config.bt_name); + furi_hal_bt_set_profile_mac_addr(FuriHalBtProfileHidKeyboard, app->prev_config.bt_mac); + furi_hal_bt_set_profile_pairing_method( + FuriHalBtProfileHidKeyboard, GapPairingPinCodeVerifyYesNo); + bt_set_profile(app->bt, BtProfileSerial); + bt_enable_peer_key_update(app->bt); +} + +BadBtApp* bad_bt_app_alloc(char* arg) { + BadBtApp* app = malloc(sizeof(BadBtApp)); + + app->bad_bt_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); + } + + Storage* storage = furi_record_open(RECORD_STORAGE); + storage_simply_mkdir(storage, BAD_BT_APP_BASE_CONFIG_FOLDER); + furi_record_close(RECORD_STORAGE); + + bad_bt_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(); + view_dispatcher_enable_queue(app->view_dispatcher); + + app->scene_manager = scene_manager_alloc(&bad_bt_scene_handlers, app); + + view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + view_dispatcher_set_tick_event_callback( + app->view_dispatcher, bad_bt_app_tick_event_callback, 500); + view_dispatcher_set_custom_event_callback( + app->view_dispatcher, bad_bt_app_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + app->view_dispatcher, bad_bt_app_back_event_callback); + + Bt* bt = furi_record_open(RECORD_BT); + app->bt = bt; + app->bt->suppress_pin_screen = true; + + // Custom Widget + app->widget = widget_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, BadBtAppViewError, widget_get_view(app->widget)); + + app->var_item_list = variable_item_list_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, BadBtAppViewConfig, variable_item_list_get_view(app->var_item_list)); + + app->bad_bt_view = bad_bt_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, BadBtAppViewWork, bad_bt_get_view(app->bad_bt_view)); + + app->text_input = text_input_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, BadBtAppViewConfigName, text_input_get_view(app->text_input)); + + app->byte_input = byte_input_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, BadBtAppViewConfigMac, byte_input_get_view(app->byte_input)); + + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + + app->conn_init_thread = furi_thread_alloc_ex( + "BadBtConnInit", 1024, (FuriThreadCallback)bad_bt_connection_init, app); + furi_thread_start(app->conn_init_thread); + if(!furi_string_empty(app->file_path)) { + app->bad_bt_script = bad_bt_script_open(app->file_path, app->bt, app); + bad_bt_script_set_keyboard_layout(app->bad_bt_script, app->keyboard_layout); + scene_manager_next_scene(app->scene_manager, BadBtSceneWork); + } else { + furi_string_set(app->file_path, BAD_BT_APP_BASE_FOLDER); + scene_manager_next_scene(app->scene_manager, BadBtSceneFileSelect); + } + + return app; +} + +void bad_bt_app_free(BadBtApp* app) { + furi_assert(app); + + if(app->bad_bt_script) { + bad_bt_script_close(app->bad_bt_script); + app->bad_bt_script = NULL; + } + + // Views + view_dispatcher_remove_view(app->view_dispatcher, BadBtAppViewWork); + bad_bt_free(app->bad_bt_view); + + // Custom Widget + view_dispatcher_remove_view(app->view_dispatcher, BadBtAppViewError); + widget_free(app->widget); + + // Variable item list + view_dispatcher_remove_view(app->view_dispatcher, BadBtAppViewConfig); + variable_item_list_free(app->var_item_list); + + // Text Input + view_dispatcher_remove_view(app->view_dispatcher, BadBtAppViewConfigName); + text_input_free(app->text_input); + + // Byte Input + view_dispatcher_remove_view(app->view_dispatcher, BadBtAppViewConfigMac); + byte_input_free(app->byte_input); + + // View dispatcher + view_dispatcher_free(app->view_dispatcher); + scene_manager_free(app->scene_manager); + + // Restore bt config + app->bt->suppress_pin_screen = false; + if(app->conn_init_thread) { + furi_thread_join(app->conn_init_thread); + furi_thread_free(app->conn_init_thread); + bad_bt_connection_deinit(app); + } + + // Close records + furi_record_close(RECORD_GUI); + furi_record_close(RECORD_NOTIFICATION); + furi_record_close(RECORD_DIALOGS); + furi_record_close(RECORD_BT); + + bad_bt_save_settings(app); + + furi_string_free(app->file_path); + furi_string_free(app->keyboard_layout); + + free(app); +} + +int32_t bad_bt_app(void* p) { + BadBtApp* bad_bt_app = bad_bt_app_alloc((char*)p); + + view_dispatcher_run(bad_bt_app->view_dispatcher); + + bad_bt_app_free(bad_bt_app); + return 0; +} diff --git a/applications/external/bad_bt/bad_bt_app.h b/applications/external/bad_bt/bad_bt_app.h new file mode 100644 index 000000000..13b0844b0 --- /dev/null +++ b/applications/external/bad_bt/bad_bt_app.h @@ -0,0 +1,39 @@ +#pragma once + +#include "scenes/bad_bt_scene.h" +#include "helpers/ducky_script.h" + +#include +#include +#include +#include +#include +#include "bad_bt_icons.h" + +#define BAD_BT_APP_BASE_FOLDER EXT_PATH("badusb") +#define BAD_BT_APP_BASE_CONFIG_FOLDER EXT_PATH("badbt") +#define BAD_BT_APP_PATH_LAYOUT_FOLDER BAD_BT_APP_BASE_FOLDER "/assets/layouts" +#define BAD_BT_APP_SCRIPT_EXTENSION ".txt" +#define BAD_BT_APP_LAYOUT_EXTENSION ".kl" + +typedef enum BadBtCustomEvent { + BadBtAppCustomEventTextEditResult, + BadBtAppCustomEventByteInputDone, + BadBtCustomEventErrorBack +} BadBtCustomEvent; + +typedef enum { + BadBtAppViewError, + BadBtAppViewWork, + BadBtAppViewConfig, + BadBtAppViewConfigMac, + BadBtAppViewConfigName +} BadBtAppView; + +void bad_bt_config_switch_remember_mode(BadBtApp* app); + +int32_t bad_bt_connection_init(BadBtApp* app); + +void bad_bt_connection_deinit(BadBtApp* app); + +void bad_kb_config_refresh_menu(BadBtApp* app); \ No newline at end of file diff --git a/applications/external/bad_bt/helpers/ducky_script.c b/applications/external/bad_bt/helpers/ducky_script.c new file mode 100644 index 000000000..a59d377f2 --- /dev/null +++ b/applications/external/bad_bt/helpers/ducky_script.c @@ -0,0 +1,777 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "ducky_script.h" +#include "ducky_script_i.h" +#include +#include +#include "../bad_bt_app.h" + +const uint8_t BAD_BT_BOUND_MAC_ADDRESS[BAD_BT_MAC_ADDRESS_LEN] = + {0x41, 0x4a, 0xef, 0xb6, 0xa9, 0xd4}; +const uint8_t BAD_BT_EMPTY_MAC_ADDRESS[BAD_BT_MAC_ADDRESS_LEN] = + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +#define TAG "BadBT" +#define WORKER_TAG TAG "Worker" + +#define BADBT_ASCII_TO_KEY(script, x) \ + (((uint8_t)x < 128) ? (script->layout[(uint8_t)x]) : HID_KEYBOARD_NONE) + +/** + * Delays for waiting between HID key press and key release +*/ +const uint8_t bt_hid_delays[LevelRssiNum] = { + 30, // LevelRssi122_100 + 25, // LevelRssi99_80 + 20, // LevelRssi79_60 + 17, // LevelRssi59_40 + 14, // LevelRssi39_0 +}; + +uint8_t bt_timeout = 0; + +static LevelRssiRange bt_remote_rssi_range(Bt* bt) { + uint8_t rssi; + + if(!bt_remote_rssi(bt, &rssi)) return LevelRssiError; + + if(rssi <= 39) + return LevelRssi39_0; + else if(rssi <= 59) + return LevelRssi59_40; + else if(rssi <= 79) + return LevelRssi79_60; + else if(rssi <= 99) + return LevelRssi99_80; + else if(rssi <= 122) + return LevelRssi122_100; + + return LevelRssiError; +} + +static inline void update_bt_timeout(Bt* bt) { + LevelRssiRange r = bt_remote_rssi_range(bt); + if(r < LevelRssiNum) { + bt_timeout = bt_hid_delays[r]; + FURI_LOG_D(WORKER_TAG, "BLE Key timeout : %u", bt_timeout); + } +} + +typedef enum { + WorkerEvtToggle = (1 << 0), + WorkerEvtEnd = (1 << 1), + WorkerEvtConnect = (1 << 2), + WorkerEvtDisconnect = (1 << 3), +} WorkerEvtFlags; + +static const char ducky_cmd_id[] = {"ID"}; +static const char ducky_cmd_bt_id[] = {"BT_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(BadBtScript* bad_bt, 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 (BADBT_ASCII_TO_KEY(bad_bt, param[0]) & 0xFF); + } + return 0; +} + +bool ducky_get_number(const char* param, uint32_t* val) { + uint32_t value = 0; + if(sscanf(param, "%lu", &value) == 1) { + *val = value; + return true; + } + return false; +} + +void ducky_numlock_on(BadBtScript* bad_bt) { + UNUSED(bad_bt); + if((furi_hal_bt_hid_get_led_state() & HID_KB_LED_NUM) == 0) { + furi_hal_bt_hid_kb_press(HID_KEYBOARD_LOCK_NUM_LOCK); + furi_delay_ms(bt_timeout); + furi_hal_bt_hid_kb_release(HID_KEYBOARD_LOCK_NUM_LOCK); + } +} + +bool ducky_numpad_press(BadBtScript* bad_bt, const char num) { + UNUSED(bad_bt); + if((num < '0') || (num > '9')) return false; + + uint16_t key = numpad_keys[num - '0']; + furi_hal_bt_hid_kb_press(key); + furi_delay_ms(bt_timeout); + furi_hal_bt_hid_kb_release(key); + + return true; +} + +bool ducky_altchar(BadBtScript* bad_bt, const char* charcode) { + uint8_t i = 0; + bool state = false; + + furi_hal_bt_hid_kb_press(KEY_MOD_LEFT_ALT); + + while(!ducky_is_line_end(charcode[i])) { + state = ducky_numpad_press(bad_bt, charcode[i]); + if(state == false) break; + i++; + } + + furi_hal_bt_hid_kb_release(KEY_MOD_LEFT_ALT); + + return state; +} + +bool ducky_altstring(BadBtScript* bad_bt, 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_bt, temp_str); + if(state == false) break; + i++; + } + return state; +} + +int32_t ducky_error(BadBtScript* bad_bt, const char* text, ...) { + va_list args; + va_start(args, text); + + vsnprintf(bad_bt->st.error, sizeof(bad_bt->st.error), text, args); + + va_end(args); + return SCRIPT_STATE_ERROR; +} + +bool ducky_string(BadBtScript* bad_bt, const char* param) { + uint32_t i = 0; + + while(param[i] != '\0') { + if(param[i] != '\n') { + uint16_t keycode = BADBT_ASCII_TO_KEY(bad_bt, param[i]); + if(keycode != HID_KEYBOARD_NONE) { + furi_hal_bt_hid_kb_press(keycode); + furi_delay_ms(bt_timeout); + furi_hal_bt_hid_kb_release(keycode); + } + } else { + furi_hal_bt_hid_kb_press(HID_KEYBOARD_RETURN); + furi_delay_ms(bt_timeout); + furi_hal_bt_hid_kb_release(HID_KEYBOARD_RETURN); + } + i++; + } + bad_bt->stringdelay = 0; + return true; +} + +static bool ducky_string_next(BadBtScript* bad_bt) { + if(bad_bt->string_print_pos >= furi_string_size(bad_bt->string_print)) { + return true; + } + + char print_char = furi_string_get_char(bad_bt->string_print, bad_bt->string_print_pos); + + if(print_char != '\n') { + uint16_t keycode = BADBT_ASCII_TO_KEY(bad_bt, print_char); + if(keycode != HID_KEYBOARD_NONE) { + furi_hal_bt_hid_kb_press(keycode); + furi_delay_ms(bt_timeout); + furi_hal_bt_hid_kb_release(keycode); + } + } else { + furi_hal_bt_hid_kb_press(HID_KEYBOARD_RETURN); + furi_delay_ms(bt_timeout); + furi_hal_bt_hid_kb_release(HID_KEYBOARD_RETURN); + } + + bad_bt->string_print_pos++; + + return false; +} + +static int32_t ducky_parse_line(BadBtScript* bad_bt, 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_bt, line_tmp); + if(cmd_result != SCRIPT_STATE_CMD_UNKNOWN) { + return cmd_result; + } + + // Special keys + modifiers + uint16_t key = ducky_get_keycode(bad_bt, line_tmp, false); + if(key == HID_KEYBOARD_NONE) { + return ducky_error(bad_bt, "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_bt, line_tmp, true); + } + furi_hal_bt_hid_kb_press(key); + furi_delay_ms(bt_timeout); + furi_hal_bt_hid_kb_release(key); + + return 0; +} + +static bool ducky_set_bt_id(BadBtScript* bad_bt, const char* line) { + size_t line_len = strlen(line); + size_t mac_len = BAD_BT_MAC_ADDRESS_LEN * 3; + if(line_len < mac_len + 1) return false; // MAC + at least 1 char for name + + uint8_t mac[BAD_BT_MAC_ADDRESS_LEN]; + for(size_t i = 0; i < BAD_BT_MAC_ADDRESS_LEN; i++) { + char a = line[i * 3]; + char b = line[i * 3 + 1]; + if((a < 'A' && a > 'F') || (a < '0' && a > '9') || (b < 'A' && b > 'F') || + (b < '0' && b > '9') || !hex_char_to_uint8(a, b, &mac[i])) { + return false; + } + } + + furi_hal_bt_set_profile_adv_name(FuriHalBtProfileHidKeyboard, line + mac_len); + bt_set_profile_mac_address(bad_bt->bt, mac); + return true; +} + +static bool ducky_script_preload(BadBtScript* bad_bt, File* script_file) { + uint8_t ret = 0; + uint32_t line_len = 0; + + furi_string_reset(bad_bt->line); + + do { + ret = storage_file_read(script_file, bad_bt->file_buf, FILE_BUFFER_LEN); + for(uint16_t i = 0; i < ret; i++) { + if(bad_bt->file_buf[i] == '\n' && line_len > 0) { + bad_bt->st.line_nb++; + line_len = 0; + } else { + if(bad_bt->st.line_nb == 0) { // Save first line + furi_string_push_back(bad_bt->line, bad_bt->file_buf[i]); + } + line_len++; + } + } + if(storage_file_eof(script_file)) { + if(line_len > 0) { + bad_bt->st.line_nb++; + break; + } + } + } while(ret > 0); + + const char* line_tmp = furi_string_get_cstr(bad_bt->line); + if(bad_bt->app->switch_mode_thread) { + furi_thread_join(bad_bt->app->switch_mode_thread); + furi_thread_free(bad_bt->app->switch_mode_thread); + bad_bt->app->switch_mode_thread = NULL; + } + // Looking for ID or BT_ID command at first line + bad_bt->set_usb_id = false; + bad_bt->set_bt_id = false; + bad_bt->has_usb_id = strncmp(line_tmp, ducky_cmd_id, strlen(ducky_cmd_id)) == 0; + // TODO: We setting has_usb_id to its value but ignoring it for now and not using anywhere here, may be used in a future to detect script type + bad_bt->has_bt_id = strncmp(line_tmp, ducky_cmd_bt_id, strlen(ducky_cmd_bt_id)) == 0; + if(bad_bt->has_bt_id) { + if(!bad_bt->app->bt_remember) { + bad_bt->set_bt_id = ducky_set_bt_id(bad_bt, &line_tmp[strlen(ducky_cmd_bt_id) + 1]); + } + } + + bad_kb_config_refresh_menu(bad_bt->app); + + if(!bad_bt->set_bt_id) { + const char* bt_name = bad_bt->app->config.bt_name; + const uint8_t* bt_mac = bad_bt->app->bt_remember ? (uint8_t*)&BAD_BT_BOUND_MAC_ADDRESS : + bad_bt->app->config.bt_mac; + bool reset_name = strncmp( + bt_name, + furi_hal_bt_get_profile_adv_name(FuriHalBtProfileHidKeyboard), + BAD_BT_ADV_NAME_MAX_LEN); + bool reset_mac = memcmp( + bt_mac, + furi_hal_bt_get_profile_mac_addr(FuriHalBtProfileHidKeyboard), + BAD_BT_MAC_ADDRESS_LEN); + if(reset_name && reset_mac) { + furi_hal_bt_set_profile_adv_name(FuriHalBtProfileHidKeyboard, bt_name); + } else if(reset_name) { + bt_set_profile_adv_name(bad_bt->bt, bt_name); + } + if(reset_mac) { + bt_set_profile_mac_address(bad_bt->bt, bt_mac); + } + } + + storage_file_seek(script_file, 0, true); + furi_string_reset(bad_bt->line); + + return true; +} + +static int32_t ducky_script_execute_next(BadBtScript* bad_bt, File* script_file) { + int32_t delay_val = 0; + + if(bad_bt->repeat_cnt > 0) { + bad_bt->repeat_cnt--; + delay_val = ducky_parse_line(bad_bt, bad_bt->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_bt->st.error_line = bad_bt->st.line_cur - 1; + FURI_LOG_E(WORKER_TAG, "Unknown command at line %u", bad_bt->st.line_cur - 1U); + return SCRIPT_STATE_ERROR; + } else { + return (delay_val + bad_bt->defdelay); + } + } + + furi_string_set(bad_bt->line_prev, bad_bt->line); + furi_string_reset(bad_bt->line); + + while(1) { + if(bad_bt->buf_len == 0) { + bad_bt->buf_len = storage_file_read(script_file, bad_bt->file_buf, FILE_BUFFER_LEN); + if(storage_file_eof(script_file)) { + if((bad_bt->buf_len < FILE_BUFFER_LEN) && (bad_bt->file_end == false)) { + bad_bt->file_buf[bad_bt->buf_len] = '\n'; + bad_bt->buf_len++; + bad_bt->file_end = true; + } + } + + bad_bt->buf_start = 0; + if(bad_bt->buf_len == 0) return SCRIPT_STATE_END; + } + for(uint8_t i = bad_bt->buf_start; i < (bad_bt->buf_start + bad_bt->buf_len); i++) { + if(bad_bt->file_buf[i] == '\n' && furi_string_size(bad_bt->line) > 0) { + bad_bt->st.line_cur++; + bad_bt->buf_len = bad_bt->buf_len + bad_bt->buf_start - (i + 1); + bad_bt->buf_start = i + 1; + furi_string_trim(bad_bt->line); + delay_val = ducky_parse_line(bad_bt, bad_bt->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_bt->st.error_line = bad_bt->st.line_cur; + FURI_LOG_E(WORKER_TAG, "Unknown command at line %u", bad_bt->st.line_cur); + return SCRIPT_STATE_ERROR; + } else { + return (delay_val + bad_bt->defdelay); + } + } else { + furi_string_push_back(bad_bt->line, bad_bt->file_buf[i]); + } + } + bad_bt->buf_len = 0; + if(bad_bt->file_end) return SCRIPT_STATE_END; + } + + return 0; +} + +static void bad_bt_bt_hid_state_callback(BtStatus status, void* context) { + furi_assert(context); + BadBtScript* bad_bt = context; + bool state = (status == BtStatusConnected); + + if(state == true) { + LevelRssiRange r = bt_remote_rssi_range(bad_bt->bt); + if(r != LevelRssiError) { + bt_timeout = bt_hid_delays[r]; + } + furi_thread_flags_set(furi_thread_get_id(bad_bt->thread), WorkerEvtConnect); + } else { + furi_thread_flags_set(furi_thread_get_id(bad_bt->thread), WorkerEvtDisconnect); + } +} + +static uint32_t bad_bt_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_bt_worker(void* context) { + BadBtScript* bad_bt = context; + + BadBtWorkerState worker_state = BadBtStateInit; + int32_t delay_val = 0; + + FURI_LOG_I(WORKER_TAG, "Init"); + File* script_file = storage_file_alloc(furi_record_open(RECORD_STORAGE)); + bad_bt->line = furi_string_alloc(); + bad_bt->line_prev = furi_string_alloc(); + bad_bt->string_print = furi_string_alloc(); + + bt_set_status_changed_callback(bad_bt->bt, bad_bt_bt_hid_state_callback, bad_bt); + + while(1) { + if(worker_state == BadBtStateInit) { // State: initialization + if(storage_file_open( + script_file, + furi_string_get_cstr(bad_bt->file_path), + FSAM_READ, + FSOM_OPEN_EXISTING)) { + if((ducky_script_preload(bad_bt, script_file)) && (bad_bt->st.line_nb > 0)) { + if(furi_hal_bt_is_connected()) { + worker_state = BadBtStateIdle; // Ready to run + } else { + worker_state = BadBtStateNotConnected; // Not connected + } + + } else { + worker_state = BadBtStateScriptError; // Script preload error + } + } else { + FURI_LOG_E(WORKER_TAG, "File open error"); + worker_state = BadBtStateFileError; // File open error + } + bad_bt->st.state = worker_state; + + } else if(worker_state == BadBtStateNotConnected) { // State: Not connected + uint32_t flags = bad_bt_flags_get( + WorkerEvtEnd | WorkerEvtConnect | WorkerEvtToggle, FuriWaitForever); + + if(flags & WorkerEvtEnd) { + break; + } else if(flags & WorkerEvtConnect) { + worker_state = BadBtStateIdle; // Ready to run + } else if(flags & WorkerEvtToggle) { + worker_state = BadBtStateWillRun; // Will run when connected + } + bad_bt->st.state = worker_state; + + } else if(worker_state == BadBtStateIdle) { // State: ready to start + uint32_t flags = bad_bt_flags_get( + WorkerEvtEnd | WorkerEvtToggle | WorkerEvtDisconnect, FuriWaitForever); + + if(flags & WorkerEvtEnd) { + break; + } else if(flags & WorkerEvtToggle) { // Start executing script + delay_val = 0; + bad_bt->buf_len = 0; + bad_bt->st.line_cur = 0; + bad_bt->defdelay = 0; + bad_bt->stringdelay = 0; + bad_bt->repeat_cnt = 0; + bad_bt->key_hold_nb = 0; + bad_bt->file_end = false; + storage_file_seek(script_file, 0, true); + bad_bt_script_set_keyboard_layout(bad_bt, bad_bt->keyboard_layout); + worker_state = BadBtStateRunning; + } else if(flags & WorkerEvtDisconnect) { + worker_state = BadBtStateNotConnected; // Disconnected + } + bad_bt->st.state = worker_state; + + } else if(worker_state == BadBtStateWillRun) { // State: start on connection + uint32_t flags = bad_bt_flags_get( + WorkerEvtEnd | WorkerEvtConnect | WorkerEvtToggle, FuriWaitForever); + + if(flags & WorkerEvtEnd) { + break; + } else if(flags & WorkerEvtConnect) { // Start executing script + delay_val = 0; + bad_bt->buf_len = 0; + bad_bt->st.line_cur = 0; + bad_bt->defdelay = 0; + bad_bt->stringdelay = 0; + bad_bt->repeat_cnt = 0; + bad_bt->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 | WorkerEvtToggle, + FuriFlagWaitAny | FuriFlagNoClear, + 1500); + if(flags == (unsigned)FuriFlagErrorTimeout) { + // If nothing happened - start script execution + worker_state = BadBtStateRunning; + } else if(flags & WorkerEvtToggle) { + worker_state = BadBtStateIdle; + furi_thread_flags_clear(WorkerEvtToggle); + } + + update_bt_timeout(bad_bt->bt); + + bad_bt_script_set_keyboard_layout(bad_bt, bad_bt->keyboard_layout); + } else if(flags & WorkerEvtToggle) { // Cancel scheduled execution + worker_state = BadBtStateNotConnected; + } + bad_bt->st.state = worker_state; + + } else if(worker_state == BadBtStateRunning) { // State: running + uint16_t delay_cur = (delay_val > 1000) ? (1000) : (delay_val); + uint32_t flags = furi_thread_flags_wait( + WorkerEvtEnd | WorkerEvtToggle | WorkerEvtDisconnect, FuriFlagWaitAny, delay_cur); + + delay_val -= delay_cur; + if(!(flags & FuriFlagError)) { + if(flags & WorkerEvtEnd) { + break; + } else if(flags & WorkerEvtToggle) { + worker_state = BadBtStateIdle; // Stop executing script + + furi_hal_bt_hid_kb_release_all(); + + } else if(flags & WorkerEvtDisconnect) { + worker_state = BadBtStateNotConnected; // Disconnected + + furi_hal_bt_hid_kb_release_all(); + } + bad_bt->st.state = worker_state; + continue; + } else if( + (flags == (unsigned)FuriFlagErrorTimeout) || + (flags == (unsigned)FuriFlagErrorResource)) { + if(delay_val > 0) { + bad_bt->st.delay_remain--; + continue; + } + bad_bt->st.state = BadBtStateRunning; + delay_val = ducky_script_execute_next(bad_bt, script_file); + if(delay_val == SCRIPT_STATE_ERROR) { // Script error + delay_val = 0; + worker_state = BadBtStateScriptError; + bad_bt->st.state = worker_state; + + furi_hal_bt_hid_kb_release_all(); + + } else if(delay_val == SCRIPT_STATE_END) { // End of script + delay_val = 0; + worker_state = BadBtStateIdle; + bad_bt->st.state = BadBtStateDone; + + furi_hal_bt_hid_kb_release_all(); + + continue; + } else if(delay_val == SCRIPT_STATE_STRING_START) { // Start printing string with delays + delay_val = bad_bt->defdelay; + bad_bt->string_print_pos = 0; + worker_state = BadBtStateStringDelay; + } else if(delay_val == SCRIPT_STATE_WAIT_FOR_BTN) { // set state to wait for user input + worker_state = BadBtStateWaitForBtn; + bad_bt->st.state = BadBtStateWaitForBtn; // Show long delays + } else if(delay_val > 1000) { + bad_bt->st.state = BadBtStateDelay; // Show long delays + bad_bt->st.delay_remain = delay_val / 1000; + } + } else { + furi_check((flags & FuriFlagError) == 0); + } + } else if(worker_state == BadBtStateWaitForBtn) { // State: Wait for button Press + uint16_t delay_cur = (delay_val > 1000) ? (1000) : (delay_val); + uint32_t flags = furi_thread_flags_wait( + WorkerEvtEnd | WorkerEvtToggle | WorkerEvtDisconnect, FuriFlagWaitAny, delay_cur); + if(!(flags & FuriFlagError)) { + if(flags & WorkerEvtEnd) { + break; + } else if(flags & WorkerEvtToggle) { + delay_val = 0; + worker_state = BadBtStateRunning; + } else if(flags & WorkerEvtDisconnect) { + worker_state = BadBtStateNotConnected; // Disconnected + furi_hal_hid_kb_release_all(); + } + bad_bt->st.state = worker_state; + continue; + } + } else if(worker_state == BadBtStateStringDelay) { // State: print string with delays + uint32_t flags = furi_thread_flags_wait( + WorkerEvtEnd | WorkerEvtToggle | WorkerEvtDisconnect, + FuriFlagWaitAny, + bad_bt->stringdelay); + + if(!(flags & FuriFlagError)) { + if(flags & WorkerEvtEnd) { + break; + } else if(flags & WorkerEvtToggle) { + worker_state = BadBtStateIdle; // Stop executing script + + furi_hal_bt_hid_kb_release_all(); + + } else if(flags & WorkerEvtDisconnect) { + worker_state = BadBtStateNotConnected; // Disconnected + + furi_hal_bt_hid_kb_release_all(); + } + bad_bt->st.state = worker_state; + continue; + } else if( + (flags == (unsigned)FuriFlagErrorTimeout) || + (flags == (unsigned)FuriFlagErrorResource)) { + bool string_end = ducky_string_next(bad_bt); + if(string_end) { + bad_bt->stringdelay = 0; + worker_state = BadBtStateRunning; + } + } else { + furi_check((flags & FuriFlagError) == 0); + } + } else if( + (worker_state == BadBtStateFileError) || + (worker_state == BadBtStateScriptError)) { // State: error + uint32_t flags = + bad_bt_flags_get(WorkerEvtEnd, FuriWaitForever); // Waiting for exit command + + if(flags & WorkerEvtEnd) { + break; + } + } + + update_bt_timeout(bad_bt->bt); + } + + bt_set_status_changed_callback(bad_bt->bt, NULL, NULL); + + storage_file_close(script_file); + storage_file_free(script_file); + furi_string_free(bad_bt->line); + furi_string_free(bad_bt->line_prev); + furi_string_free(bad_bt->string_print); + + FURI_LOG_I(WORKER_TAG, "End"); + + return 0; +} + +static void bad_bt_script_set_default_keyboard_layout(BadBtScript* bad_bt) { + furi_assert(bad_bt); + furi_string_set_str(bad_bt->keyboard_layout, ""); + memset(bad_bt->layout, HID_KEYBOARD_NONE, sizeof(bad_bt->layout)); + memcpy(bad_bt->layout, hid_asciimap, MIN(sizeof(hid_asciimap), sizeof(bad_bt->layout))); +} + +BadBtScript* bad_bt_script_open(FuriString* file_path, Bt* bt, BadBtApp* app) { + furi_assert(file_path); + + BadBtScript* bad_bt = malloc(sizeof(BadBtScript)); + bad_bt->app = app; + bad_bt->file_path = furi_string_alloc(); + furi_string_set(bad_bt->file_path, file_path); + bad_bt->keyboard_layout = furi_string_alloc(); + bad_bt_script_set_default_keyboard_layout(bad_bt); + + bad_bt->st.state = BadBtStateInit; + bad_bt->st.error[0] = '\0'; + + bad_bt->bt = bt; + + bad_bt->thread = furi_thread_alloc_ex("BadBtWorker", 2048, bad_bt_worker, bad_bt); + furi_thread_start(bad_bt->thread); + return bad_bt; +} + +void bad_bt_script_close(BadBtScript* bad_bt) { + furi_assert(bad_bt); + furi_record_close(RECORD_STORAGE); + furi_thread_flags_set(furi_thread_get_id(bad_bt->thread), WorkerEvtEnd); + furi_thread_join(bad_bt->thread); + furi_thread_free(bad_bt->thread); + furi_string_free(bad_bt->file_path); + furi_string_free(bad_bt->keyboard_layout); + free(bad_bt); +} + +void bad_bt_script_set_keyboard_layout(BadBtScript* bad_bt, FuriString* layout_path) { + furi_assert(bad_bt); + + if((bad_bt->st.state == BadBtStateRunning) || (bad_bt->st.state == BadBtStateDelay)) { + // 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 + furi_string_set(bad_bt->keyboard_layout, layout_path); + 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_bt->layout, layout, sizeof(layout)); + } + } + storage_file_close(layout_file); + } else { + bad_bt_script_set_default_keyboard_layout(bad_bt); + } + storage_file_free(layout_file); +} + +void bad_bt_script_toggle(BadBtScript* bad_bt) { + furi_assert(bad_bt); + furi_thread_flags_set(furi_thread_get_id(bad_bt->thread), WorkerEvtToggle); +} + +BadBtState* bad_bt_script_get_state(BadBtScript* bad_bt) { + furi_assert(bad_bt); + return &(bad_bt->st); +} \ No newline at end of file diff --git a/applications/external/bad_bt/helpers/ducky_script.h b/applications/external/bad_bt/helpers/ducky_script.h new file mode 100644 index 000000000..ea0f91040 --- /dev/null +++ b/applications/external/bad_bt/helpers/ducky_script.h @@ -0,0 +1,154 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +#include +#include +#include +#include +#include +#include "../views/bad_bt_view.h" + +#define FILE_BUFFER_LEN 16 + +typedef enum { + LevelRssi122_100, + LevelRssi99_80, + LevelRssi79_60, + LevelRssi59_40, + LevelRssi39_0, + LevelRssiNum, + LevelRssiError = 0xFF, +} LevelRssiRange; + +extern const uint8_t bt_hid_delays[LevelRssiNum]; + +extern uint8_t bt_timeout; + +typedef enum { + BadBtStateInit, + BadBtStateNotConnected, + BadBtStateIdle, + BadBtStateWillRun, + BadBtStateRunning, + BadBtStateDelay, + BadBtStateStringDelay, + BadBtStateWaitForBtn, + BadBtStateDone, + BadBtStateScriptError, + BadBtStateFileError, +} BadBtWorkerState; + +struct BadBtState { + BadBtWorkerState state; + uint32_t pin; + uint16_t line_cur; + uint16_t line_nb; + uint32_t delay_remain; + uint16_t error_line; + char error[64]; +}; + +typedef struct BadBtApp BadBtApp; + +typedef struct { + FuriHalUsbHidConfig hid_cfg; + FuriThread* thread; + BadBtState st; + + FuriString* file_path; + FuriString* keyboard_layout; + uint8_t file_buf[FILE_BUFFER_LEN + 1]; + uint8_t buf_start; + uint8_t buf_len; + bool file_end; + + uint32_t defdelay; + uint32_t stringdelay; + uint16_t layout[128]; + + FuriString* line; + FuriString* line_prev; + uint32_t repeat_cnt; + uint8_t key_hold_nb; + + bool set_usb_id; + bool set_bt_id; + bool has_usb_id; + bool has_bt_id; + + FuriString* string_print; + size_t string_print_pos; + + Bt* bt; + BadBtApp* app; +} BadBtScript; + +BadBtScript* bad_bt_script_open(FuriString* file_path, Bt* bt, BadBtApp* app); + +void bad_bt_script_close(BadBtScript* bad_bt); + +void bad_bt_script_set_keyboard_layout(BadBtScript* bad_bt, FuriString* layout_path); + +void bad_bt_script_start(BadBtScript* bad_bt); + +void bad_bt_script_stop(BadBtScript* bad_bt); + +void bad_bt_script_toggle(BadBtScript* bad_bt); + +BadBtState* bad_bt_script_get_state(BadBtScript* bad_bt); + +#define BAD_BT_ADV_NAME_MAX_LEN FURI_HAL_BT_ADV_NAME_LENGTH +#define BAD_BT_MAC_ADDRESS_LEN GAP_MAC_ADDR_SIZE + +// this is the MAC address used when we do not forget paired device (BOUND STATE) +extern const uint8_t BAD_BT_BOUND_MAC_ADDRESS[BAD_BT_MAC_ADDRESS_LEN]; +extern const uint8_t BAD_BT_EMPTY_MAC_ADDRESS[BAD_BT_MAC_ADDRESS_LEN]; + +typedef enum { + BadBtAppErrorNoFiles, + BadBtAppErrorCloseRpc, +} BadBtAppError; + +typedef struct { + char bt_name[BAD_BT_ADV_NAME_MAX_LEN]; + uint8_t bt_mac[BAD_BT_MAC_ADDRESS_LEN]; + GapPairing bt_mode; +} BadBtConfig; + +struct BadBtApp { + Gui* gui; + ViewDispatcher* view_dispatcher; + SceneManager* scene_manager; + NotificationApp* notifications; + DialogsApp* dialogs; + Widget* widget; + VariableItemList* var_item_list; + TextInput* text_input; + ByteInput* byte_input; + + BadBtAppError error; + FuriString* file_path; + FuriString* keyboard_layout; + BadBt* bad_bt_view; + BadBtScript* bad_bt_script; + + Bt* bt; + bool bt_remember; + BadBtConfig config; + BadBtConfig prev_config; + FuriThread* conn_init_thread; + FuriThread* switch_mode_thread; +}; + +int32_t bad_bt_config_switch_mode(BadBtApp* app); + +#ifdef __cplusplus +} +#endif diff --git a/applications/external/bad_bt/helpers/ducky_script_commands.c b/applications/external/bad_bt/helpers/ducky_script_commands.c new file mode 100644 index 000000000..eddab96ac --- /dev/null +++ b/applications/external/bad_bt/helpers/ducky_script_commands.c @@ -0,0 +1,201 @@ +#include +#include +#include "ducky_script.h" +#include "ducky_script_i.h" + +typedef int32_t (*DuckyCmdCallback)(BadBtScript* bad_bt, const char* line, int32_t param); + +typedef struct { + char* name; + DuckyCmdCallback callback; + int32_t param; +} DuckyCmd; + +static int32_t ducky_fnc_delay(BadBtScript* bad_bt, 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_bt, "Invalid number %s", line); +} + +static int32_t ducky_fnc_defdelay(BadBtScript* bad_bt, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + bool state = ducky_get_number(line, &bad_bt->defdelay); + if(!state) { + return ducky_error(bad_bt, "Invalid number %s", line); + } + return 0; +} + +static int32_t ducky_fnc_strdelay(BadBtScript* bad_bt, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + bool state = ducky_get_number(line, &bad_bt->stringdelay); + if(!state) { + return ducky_error(bad_bt, "Invalid number %s", line); + } + return 0; +} + +static int32_t ducky_fnc_string(BadBtScript* bad_bt, const char* line, int32_t param) { + line = &line[ducky_get_command_len(line) + 1]; + furi_string_set_str(bad_bt->string_print, line); + if(param == 1) { + furi_string_cat(bad_bt->string_print, "\n"); + } + + if(bad_bt->stringdelay == 0) { // stringdelay not set - run command immidiately + bool state = ducky_string(bad_bt, furi_string_get_cstr(bad_bt->string_print)); + if(!state) { + return ducky_error(bad_bt, "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(BadBtScript* bad_bt, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + bool state = ducky_get_number(line, &bad_bt->repeat_cnt); + if((!state) || (bad_bt->repeat_cnt == 0)) { + return ducky_error(bad_bt, "Invalid number %s", line); + } + return 0; +} + +static int32_t ducky_fnc_sysrq(BadBtScript* bad_bt, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + uint16_t key = ducky_get_keycode(bad_bt, line, true); + + furi_hal_bt_hid_kb_press(KEY_MOD_LEFT_ALT | HID_KEYBOARD_PRINT_SCREEN); + furi_hal_bt_hid_kb_press(key); + furi_delay_ms(bt_timeout); + furi_hal_bt_hid_kb_release(key); + furi_hal_bt_hid_kb_release(KEY_MOD_LEFT_ALT | HID_KEYBOARD_PRINT_SCREEN); + return 0; +} + +static int32_t ducky_fnc_altchar(BadBtScript* bad_bt, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + ducky_numlock_on(bad_bt); + bool state = ducky_altchar(bad_bt, line); + if(!state) { + return ducky_error(bad_bt, "Invalid altchar %s", line); + } + return 0; +} + +static int32_t ducky_fnc_altstring(BadBtScript* bad_bt, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + ducky_numlock_on(bad_bt); + bool state = ducky_altstring(bad_bt, line); + if(!state) { + return ducky_error(bad_bt, "Invalid altstring %s", line); + } + return 0; +} + +static int32_t ducky_fnc_hold(BadBtScript* bad_bt, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + uint16_t key = ducky_get_keycode(bad_bt, line, true); + if(key == HID_KEYBOARD_NONE) { + return ducky_error(bad_bt, "No keycode defined for %s", line); + } + bad_bt->key_hold_nb++; + if(bad_bt->key_hold_nb > (HID_KB_MAX_KEYS - 1)) { + return ducky_error(bad_bt, "Too many keys are hold"); + } + furi_hal_bt_hid_kb_press(key); + + return 0; +} + +static int32_t ducky_fnc_release(BadBtScript* bad_bt, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + uint16_t key = ducky_get_keycode(bad_bt, line, true); + if(key == HID_KEYBOARD_NONE) { + return ducky_error(bad_bt, "No keycode defined for %s", line); + } + if(bad_bt->key_hold_nb == 0) { + return ducky_error(bad_bt, "No keys are hold"); + } + bad_bt->key_hold_nb--; + furi_hal_bt_hid_kb_release(key); + return 0; +} + +static int32_t ducky_fnc_waitforbutton(BadBtScript* bad_bt, const char* line, int32_t param) { + UNUSED(param); + UNUSED(bad_bt); + UNUSED(line); + + return SCRIPT_STATE_WAIT_FOR_BTN; +} + +static const DuckyCmd ducky_commands[] = { + {"REM", NULL, -1}, + {"ID", NULL, -1}, + {"BT_ID", NULL, -1}, + {"DELAY", ducky_fnc_delay, -1}, + {"STRING", ducky_fnc_string, 0}, + {"STRINGLN", ducky_fnc_string, 1}, + {"DEFAULT_DELAY", ducky_fnc_defdelay, -1}, + {"DEFAULTDELAY", ducky_fnc_defdelay, -1}, + {"STRINGDELAY", ducky_fnc_strdelay, -1}, + {"STRING_DELAY", ducky_fnc_strdelay, -1}, + {"REPEAT", ducky_fnc_repeat, -1}, + {"SYSRQ", ducky_fnc_sysrq, -1}, + {"ALTCHAR", ducky_fnc_altchar, -1}, + {"ALTSTRING", ducky_fnc_altstring, -1}, + {"ALTCODE", ducky_fnc_altstring, -1}, + {"HOLD", ducky_fnc_hold, -1}, + {"RELEASE", ducky_fnc_release, -1}, + {"WAIT_FOR_BUTTON_PRESS", ducky_fnc_waitforbutton, -1}, +}; + +#define TAG "BadBT" +#define WORKER_TAG TAG "Worker" + +int32_t ducky_execute_cmd(BadBtScript* bad_bt, 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_bt, line, ducky_commands[i].param)); + } + } + } + + return SCRIPT_STATE_CMD_UNKNOWN; +} diff --git a/applications/external/bad_bt/helpers/ducky_script_i.h b/applications/external/bad_bt/helpers/ducky_script_i.h new file mode 100644 index 000000000..08afa65a4 --- /dev/null +++ b/applications/external/bad_bt/helpers/ducky_script_i.h @@ -0,0 +1,44 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include "ducky_script.h" + +#define SCRIPT_STATE_ERROR (-1) +#define SCRIPT_STATE_END (-2) +#define SCRIPT_STATE_NEXT_LINE (-3) +#define SCRIPT_STATE_CMD_UNKNOWN (-4) +#define SCRIPT_STATE_STRING_START (-5) +#define SCRIPT_STATE_WAIT_FOR_BTN (-6) + +uint16_t ducky_get_keycode(BadBtScript* bad_bt, const char* param, bool accept_chars); + +uint32_t ducky_get_command_len(const char* line); + +bool ducky_is_line_end(const char chr); + +uint16_t ducky_get_keycode_by_name(const char* param); + +bool ducky_get_number(const char* param, uint32_t* val); + +void ducky_numlock_on(BadBtScript* bad_bt); + +bool ducky_numpad_press(BadBtScript* bad_bt, const char num); + +bool ducky_altchar(BadBtScript* bad_bt, const char* charcode); + +bool ducky_altstring(BadBtScript* bad_bt, const char* param); + +bool ducky_string(BadBtScript* bad_bt, const char* param); + +int32_t ducky_execute_cmd(BadBtScript* bad_bt, const char* line); + +int32_t ducky_error(BadBtScript* bad_bt, const char* text, ...); + +#ifdef __cplusplus +} +#endif diff --git a/applications/external/bad_bt/helpers/ducky_script_keycodes.c b/applications/external/bad_bt/helpers/ducky_script_keycodes.c new file mode 100644 index 000000000..55c52810f --- /dev/null +++ b/applications/external/bad_bt/helpers/ducky_script_keycodes.c @@ -0,0 +1,78 @@ +#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}, +}; + +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; +} diff --git a/applications/external/bad_bt/images/badbt_10px.png b/applications/external/bad_bt/images/badbt_10px.png new file mode 100644 index 000000000..037474aa3 Binary files /dev/null and b/applications/external/bad_bt/images/badbt_10px.png differ diff --git a/applications/external/bad_bt/scenes/bad_bt_scene.c b/applications/external/bad_bt/scenes/bad_bt_scene.c new file mode 100644 index 000000000..c207ae44b --- /dev/null +++ b/applications/external/bad_bt/scenes/bad_bt_scene.c @@ -0,0 +1,30 @@ +#include "bad_bt_scene.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const bad_bt_scene_on_enter_handlers[])(void*) = { +#include "bad_bt_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_bt_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = { +#include "bad_bt_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_bt_scene_on_exit_handlers[])(void* context) = { +#include "bad_bt_scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers bad_bt_scene_handlers = { + .on_enter_handlers = bad_bt_scene_on_enter_handlers, + .on_event_handlers = bad_bt_scene_on_event_handlers, + .on_exit_handlers = bad_bt_scene_on_exit_handlers, + .scene_num = BadBtSceneNum, +}; diff --git a/applications/external/bad_bt/scenes/bad_bt_scene.h b/applications/external/bad_bt/scenes/bad_bt_scene.h new file mode 100644 index 000000000..a316034ef --- /dev/null +++ b/applications/external/bad_bt/scenes/bad_bt_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) BadBtScene##id, +typedef enum { +#include "bad_bt_scene_config.h" + BadBtSceneNum, +} BadBtScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers bad_bt_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "bad_bt_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_bt_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_bt_scene_config.h" +#undef ADD_SCENE diff --git a/applications/external/bad_bt/scenes/bad_bt_scene_config.c b/applications/external/bad_bt/scenes/bad_bt_scene_config.c new file mode 100644 index 000000000..5fc9c0012 --- /dev/null +++ b/applications/external/bad_bt/scenes/bad_bt_scene_config.c @@ -0,0 +1,104 @@ +#include "../bad_bt_app.h" +#include "../helpers/ducky_script.h" +#include "furi_hal_power.h" + +enum VarItemListIndex { + VarItemListIndexKeyboardLayout, + VarItemListIndexBtRemember, + VarItemListIndexBtDeviceName, + VarItemListIndexBtMacAddress, + VarItemListIndexRandomizeBtMac, +}; + +void bad_bt_scene_config_bt_remember_callback(VariableItem* item) { + BadBtApp* bad_bt = variable_item_get_context(item); + bad_bt->bt_remember = variable_item_get_current_value_index(item); + variable_item_set_current_value_text(item, bad_bt->bt_remember ? "ON" : "OFF"); + view_dispatcher_send_custom_event(bad_bt->view_dispatcher, VarItemListIndexBtRemember); +} + +void bad_bt_scene_config_var_item_list_callback(void* context, uint32_t index) { + BadBtApp* bad_bt = context; + view_dispatcher_send_custom_event(bad_bt->view_dispatcher, index); +} + +void bad_bt_scene_config_on_enter(void* context) { + BadBtApp* bad_bt = context; + VariableItemList* var_item_list = bad_bt->var_item_list; + VariableItem* item; + + item = variable_item_list_add(var_item_list, "Keyboard layout", 0, NULL, bad_bt); + + item = variable_item_list_add( + var_item_list, "BT Remember", 2, bad_bt_scene_config_bt_remember_callback, bad_bt); + variable_item_set_current_value_index(item, bad_bt->bt_remember); + variable_item_set_current_value_text(item, bad_bt->bt_remember ? "ON" : "OFF"); + + item = variable_item_list_add(var_item_list, "BT Device Name", 0, NULL, bad_bt); + if(bad_bt->bad_bt_script->set_bt_id) { + variable_item_set_locked(item, true, "Script has\nBT_ID cmd!\nLocked to\nset Name!"); + } + + item = variable_item_list_add(var_item_list, "BT MAC Address", 0, NULL, bad_bt); + if(bad_bt->bt_remember) { + variable_item_set_locked(item, true, "Remember\nmust be Off!"); + } else if(bad_bt->bad_bt_script->set_bt_id) { + variable_item_set_locked(item, true, "Script has\nBT_ID cmd!\nLocked to\nset MAC!"); + } + + item = variable_item_list_add(var_item_list, "Randomize BT MAC", 0, NULL, bad_bt); + if(bad_bt->bt_remember) { + variable_item_set_locked(item, true, "Remember\nmust be Off!"); + } else if(bad_bt->bad_bt_script->set_bt_id) { + variable_item_set_locked(item, true, "Script has\nBT_ID cmd!\nLocked to\nset MAC!"); + } + + variable_item_list_set_enter_callback( + var_item_list, bad_bt_scene_config_var_item_list_callback, bad_bt); + + variable_item_list_set_selected_item( + var_item_list, scene_manager_get_scene_state(bad_bt->scene_manager, BadBtSceneConfig)); + + view_dispatcher_switch_to_view(bad_bt->view_dispatcher, BadBtAppViewConfig); +} + +bool bad_bt_scene_config_on_event(void* context, SceneManagerEvent event) { + BadBtApp* bad_bt = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_set_scene_state(bad_bt->scene_manager, BadBtSceneConfig, event.event); + consumed = true; + switch(event.event) { + case VarItemListIndexKeyboardLayout: + scene_manager_next_scene(bad_bt->scene_manager, BadBtSceneConfigLayout); + break; + case VarItemListIndexBtRemember: + bad_bt_config_switch_remember_mode(bad_bt); + scene_manager_previous_scene(bad_bt->scene_manager); + scene_manager_next_scene(bad_bt->scene_manager, BadBtSceneConfig); + break; + case VarItemListIndexBtDeviceName: + scene_manager_next_scene(bad_bt->scene_manager, BadBtSceneConfigName); + break; + case VarItemListIndexBtMacAddress: + scene_manager_next_scene(bad_bt->scene_manager, BadBtSceneConfigMac); + break; + case VarItemListIndexRandomizeBtMac: + furi_hal_random_fill_buf(bad_bt->config.bt_mac, BAD_BT_MAC_ADDRESS_LEN); + bt_set_profile_mac_address(bad_bt->bt, bad_bt->config.bt_mac); + break; + default: + break; + } + } + + return consumed; +} + +void bad_bt_scene_config_on_exit(void* context) { + BadBtApp* bad_bt = context; + VariableItemList* var_item_list = bad_bt->var_item_list; + + variable_item_list_reset(var_item_list); +} diff --git a/applications/external/bad_bt/scenes/bad_bt_scene_config.h b/applications/external/bad_bt/scenes/bad_bt_scene_config.h new file mode 100644 index 000000000..f7914e6dd --- /dev/null +++ b/applications/external/bad_bt/scenes/bad_bt_scene_config.h @@ -0,0 +1,7 @@ +ADD_SCENE(bad_bt, file_select, FileSelect) +ADD_SCENE(bad_bt, work, Work) +ADD_SCENE(bad_bt, error, Error) +ADD_SCENE(bad_bt, config, Config) +ADD_SCENE(bad_bt, config_layout, ConfigLayout) +ADD_SCENE(bad_bt, config_name, ConfigName) +ADD_SCENE(bad_bt, config_mac, ConfigMac) diff --git a/applications/external/bad_bt/scenes/bad_bt_scene_config_layout.c b/applications/external/bad_bt/scenes/bad_bt_scene_config_layout.c new file mode 100644 index 000000000..b0ce2d084 --- /dev/null +++ b/applications/external/bad_bt/scenes/bad_bt_scene_config_layout.c @@ -0,0 +1,47 @@ +#include "../bad_bt_app.h" +#include "furi_hal_power.h" +#include + +static bool bad_bt_layout_select(BadBtApp* bad_bt) { + furi_assert(bad_bt); + + FuriString* predefined_path; + predefined_path = furi_string_alloc(); + if(!furi_string_empty(bad_bt->keyboard_layout)) { + furi_string_set(predefined_path, bad_bt->keyboard_layout); + } else { + furi_string_set(predefined_path, BAD_BT_APP_PATH_LAYOUT_FOLDER); + } + + DialogsFileBrowserOptions browser_options; + dialog_file_browser_set_basic_options( + &browser_options, BAD_BT_APP_LAYOUT_EXTENSION, &I_keyboard_10px); + browser_options.base_path = BAD_BT_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_bt->dialogs, bad_bt->keyboard_layout, predefined_path, &browser_options); + + furi_string_free(predefined_path); + return res; +} + +void bad_bt_scene_config_layout_on_enter(void* context) { + BadBtApp* bad_bt = context; + + if(bad_bt_layout_select(bad_bt)) { + bad_bt_script_set_keyboard_layout(bad_bt->bad_bt_script, bad_bt->keyboard_layout); + } + scene_manager_previous_scene(bad_bt->scene_manager); +} + +bool bad_bt_scene_config_layout_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + return false; +} + +void bad_bt_scene_config_layout_on_exit(void* context) { + UNUSED(context); +} diff --git a/applications/external/bad_bt/scenes/bad_bt_scene_config_mac.c b/applications/external/bad_bt/scenes/bad_bt_scene_config_mac.c new file mode 100644 index 000000000..47f63e08c --- /dev/null +++ b/applications/external/bad_bt/scenes/bad_bt_scene_config_mac.c @@ -0,0 +1,47 @@ +#include "../bad_bt_app.h" + +#define TAG "BadBtConfigMac" + +void bad_bt_scene_config_mac_byte_input_callback(void* context) { + BadBtApp* bad_bt = context; + + view_dispatcher_send_custom_event(bad_bt->view_dispatcher, BadBtAppCustomEventByteInputDone); +} + +void bad_bt_scene_config_mac_on_enter(void* context) { + BadBtApp* bad_bt = context; + + // Setup view + ByteInput* byte_input = bad_bt->byte_input; + byte_input_set_header_text(byte_input, "Set BT MAC address"); + byte_input_set_result_callback( + byte_input, + bad_bt_scene_config_mac_byte_input_callback, + NULL, + bad_bt, + bad_bt->config.bt_mac, + GAP_MAC_ADDR_SIZE); + view_dispatcher_switch_to_view(bad_bt->view_dispatcher, BadBtAppViewConfigMac); +} + +bool bad_bt_scene_config_mac_on_event(void* context, SceneManagerEvent event) { + BadBtApp* bad_bt = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == BadBtAppCustomEventByteInputDone) { + bt_set_profile_mac_address(bad_bt->bt, bad_bt->config.bt_mac); + scene_manager_previous_scene(bad_bt->scene_manager); + consumed = true; + } + } + return consumed; +} + +void bad_bt_scene_config_mac_on_exit(void* context) { + BadBtApp* bad_bt = context; + + // Clear view + byte_input_set_result_callback(bad_bt->byte_input, NULL, NULL, NULL, NULL, 0); + byte_input_set_header_text(bad_bt->byte_input, ""); +} diff --git a/applications/external/bad_bt/scenes/bad_bt_scene_config_name.c b/applications/external/bad_bt/scenes/bad_bt_scene_config_name.c new file mode 100644 index 000000000..61d198b8c --- /dev/null +++ b/applications/external/bad_bt/scenes/bad_bt_scene_config_name.c @@ -0,0 +1,45 @@ +#include "../bad_bt_app.h" + +static void bad_bt_scene_config_name_text_input_callback(void* context) { + BadBtApp* bad_bt = context; + + view_dispatcher_send_custom_event(bad_bt->view_dispatcher, BadBtAppCustomEventTextEditResult); +} + +void bad_bt_scene_config_name_on_enter(void* context) { + BadBtApp* bad_bt = context; + TextInput* text_input = bad_bt->text_input; + + text_input_set_header_text(text_input, "Set BT device name"); + + text_input_set_result_callback( + text_input, + bad_bt_scene_config_name_text_input_callback, + bad_bt, + bad_bt->config.bt_name, + BAD_BT_ADV_NAME_MAX_LEN, + true); + + view_dispatcher_switch_to_view(bad_bt->view_dispatcher, BadBtAppViewConfigName); +} + +bool bad_bt_scene_config_name_on_event(void* context, SceneManagerEvent event) { + BadBtApp* bad_bt = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + if(event.event == BadBtAppCustomEventTextEditResult) { + bt_set_profile_adv_name(bad_bt->bt, bad_bt->config.bt_name); + } + scene_manager_previous_scene(bad_bt->scene_manager); + } + return consumed; +} + +void bad_bt_scene_config_name_on_exit(void* context) { + BadBtApp* bad_bt = context; + TextInput* text_input = bad_bt->text_input; + + text_input_reset(text_input); +} diff --git a/applications/external/bad_bt/scenes/bad_bt_scene_error.c b/applications/external/bad_bt/scenes/bad_bt_scene_error.c new file mode 100644 index 000000000..e25703e7d --- /dev/null +++ b/applications/external/bad_bt/scenes/bad_bt_scene_error.c @@ -0,0 +1,61 @@ +#include "../bad_bt_app.h" + +static void + bad_bt_scene_error_event_callback(GuiButtonType result, InputType type, void* context) { + furi_assert(context); + BadBtApp* app = context; + + if((result == GuiButtonTypeLeft) && (type == InputTypeShort)) { + view_dispatcher_send_custom_event(app->view_dispatcher, BadBtCustomEventErrorBack); + } +} + +void bad_bt_scene_error_on_enter(void* context) { + BadBtApp* app = context; + + if(app->error == BadBtAppErrorNoFiles) { + 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_bt_scene_error_event_callback, app); + } else if(app->error == BadBtAppErrorCloseRpc) { + 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, BadBtAppViewError); +} + +bool bad_bt_scene_error_on_event(void* context, SceneManagerEvent event) { + BadBtApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == BadBtCustomEventErrorBack) { + view_dispatcher_stop(app->view_dispatcher); + consumed = true; + } + } + return consumed; +} + +void bad_bt_scene_error_on_exit(void* context) { + BadBtApp* app = context; + widget_reset(app->widget); +} diff --git a/applications/external/bad_bt/scenes/bad_bt_scene_file_select.c b/applications/external/bad_bt/scenes/bad_bt_scene_file_select.c new file mode 100644 index 000000000..b86dc6d71 --- /dev/null +++ b/applications/external/bad_bt/scenes/bad_bt_scene_file_select.c @@ -0,0 +1,49 @@ +#include "../bad_bt_app.h" +#include +#include + +static bool bad_bt_file_select(BadBtApp* bad_bt) { + furi_assert(bad_bt); + + DialogsFileBrowserOptions browser_options; + dialog_file_browser_set_basic_options( + &browser_options, BAD_BT_APP_SCRIPT_EXTENSION, &I_badbt_10px); + browser_options.base_path = BAD_BT_APP_BASE_FOLDER; + browser_options.skip_assets = true; + + // Input events and views are managed by file_browser + bool res = dialog_file_browser_show( + bad_bt->dialogs, bad_bt->file_path, bad_bt->file_path, &browser_options); + + return res; +} + +void bad_bt_scene_file_select_on_enter(void* context) { + BadBtApp* bad_bt = context; + + if(bad_bt->bad_bt_script) { + bad_bt_script_close(bad_bt->bad_bt_script); + bad_bt->bad_bt_script = NULL; + } + + if(bad_bt_file_select(bad_bt)) { + bad_bt->bad_bt_script = bad_bt_script_open(bad_bt->file_path, bad_bt->bt, bad_bt); + bad_bt_script_set_keyboard_layout(bad_bt->bad_bt_script, bad_bt->keyboard_layout); + + scene_manager_next_scene(bad_bt->scene_manager, BadBtSceneWork); + } else { + view_dispatcher_stop(bad_bt->view_dispatcher); + } +} + +bool bad_bt_scene_file_select_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + // BadBtApp* bad_bt = context; + return false; +} + +void bad_bt_scene_file_select_on_exit(void* context) { + UNUSED(context); + // BadBtApp* bad_bt = context; +} diff --git a/applications/external/bad_bt/scenes/bad_bt_scene_work.c b/applications/external/bad_bt/scenes/bad_bt_scene_work.c new file mode 100644 index 000000000..684bb8b74 --- /dev/null +++ b/applications/external/bad_bt/scenes/bad_bt_scene_work.c @@ -0,0 +1,56 @@ +#include "../helpers/ducky_script.h" +#include "../bad_bt_app.h" +#include "../views/bad_bt_view.h" +#include +#include "toolbox/path.h" + +void bad_bt_scene_work_button_callback(InputKey key, void* context) { + furi_assert(context); + BadBtApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, key); +} + +bool bad_bt_scene_work_on_event(void* context, SceneManagerEvent event) { + BadBtApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == InputKeyLeft) { + if(bad_bt_is_idle_state(app->bad_bt_view)) { + scene_manager_next_scene(app->scene_manager, BadBtSceneConfig); + } + consumed = true; + } else if(event.event == InputKeyOk) { + bad_bt_script_toggle(app->bad_bt_script); + consumed = true; + } + } else if(event.type == SceneManagerEventTypeTick) { + bad_bt_set_state(app->bad_bt_view, bad_bt_script_get_state(app->bad_bt_script)); + } + return consumed; +} + +void bad_bt_scene_work_on_enter(void* context) { + BadBtApp* app = context; + + FuriString* file_name; + file_name = furi_string_alloc(); + path_extract_filename(app->file_path, file_name, true); + bad_bt_set_file_name(app->bad_bt_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_bt_set_layout(app->bad_bt_view, furi_string_get_cstr(layout)); + furi_string_free(layout); + + bad_bt_set_state(app->bad_bt_view, bad_bt_script_get_state(app->bad_bt_script)); + + bad_bt_set_button_callback(app->bad_bt_view, bad_bt_scene_work_button_callback, app); + view_dispatcher_switch_to_view(app->view_dispatcher, BadBtAppViewWork); +} + +void bad_bt_scene_work_on_exit(void* context) { + UNUSED(context); +} diff --git a/applications/external/bad_bt/views/bad_bt_view.c b/applications/external/bad_bt/views/bad_bt_view.c new file mode 100644 index 000000000..4a9bf589c --- /dev/null +++ b/applications/external/bad_bt/views/bad_bt_view.c @@ -0,0 +1,233 @@ +#include "bad_bt_view.h" +#include "../helpers/ducky_script.h" +#include "../bad_bt_app.h" +#include +#include +#include + +#define MAX_NAME_LEN 64 + +typedef struct { + char file_name[MAX_NAME_LEN]; + char layout[MAX_NAME_LEN]; + BadBtState state; + uint8_t anim_frame; +} BadBtModel; + +static void bad_bt_draw_callback(Canvas* canvas, void* _model) { + BadBtModel* model = _model; + + FuriString* disp_str; + disp_str = furi_string_alloc_set("(BT) "); + furi_string_cat_str(disp_str, 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_reset(disp_str); + furi_string_push_back(disp_str, '('); + for(size_t i = 0; i < strlen(model->layout); i++) + furi_string_push_back(disp_str, model->layout[i]); + furi_string_push_back(disp_str, ')'); + } + if(model->state.pin) { + furi_string_cat_printf(disp_str, " PIN: %ld", model->state.pin); + } + 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); + + if((model->state.state == BadBtStateIdle) || (model->state.state == BadBtStateDone) || + (model->state.state == BadBtStateNotConnected)) { + elements_button_center(canvas, "Run"); + elements_button_left(canvas, "Config"); + } else if((model->state.state == BadBtStateRunning) || (model->state.state == BadBtStateDelay)) { + elements_button_center(canvas, "Stop"); + } else if(model->state.state == BadBtStateWaitForBtn) { + elements_button_center(canvas, "Press to continue"); + } else if(model->state.state == BadBtStateWillRun) { + elements_button_center(canvas, "Cancel"); + } + + if(model->state.state == BadBtStateNotConnected) { + canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Connect to"); + canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "a device"); + } else if(model->state.state == BadBtStateWillRun) { + 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(model->state.state == BadBtStateFileError) { + 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(model->state.state == BadBtStateScriptError) { + 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 %u", 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(model->state.state == BadBtStateIdle) { + 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(model->state.state == BadBtStateRunning) { + 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, "%u", ((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(model->state.state == BadBtStateDone) { + 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(model->state.state == BadBtStateDelay) { + 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, "%u", ((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 { + canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18); + } + + furi_string_free(disp_str); +} + +static bool bad_bt_input_callback(InputEvent* event, void* context) { + furi_assert(context); + BadBt* bad_bt = context; + bool consumed = false; + + if(event->type == InputTypeShort) { + if((event->key == InputKeyLeft) || (event->key == InputKeyOk)) { + consumed = true; + furi_assert(bad_bt->callback); + bad_bt->callback(event->key, bad_bt->context); + } + } + + return consumed; +} + +BadBt* bad_bt_alloc() { + BadBt* bad_bt = malloc(sizeof(BadBt)); + + bad_bt->view = view_alloc(); + view_allocate_model(bad_bt->view, ViewModelTypeLocking, sizeof(BadBtModel)); + view_set_context(bad_bt->view, bad_bt); + view_set_draw_callback(bad_bt->view, bad_bt_draw_callback); + view_set_input_callback(bad_bt->view, bad_bt_input_callback); + + return bad_bt; +} + +void bad_bt_free(BadBt* bad_bt) { + furi_assert(bad_bt); + view_free(bad_bt->view); + free(bad_bt); +} + +View* bad_bt_get_view(BadBt* bad_bt) { + furi_assert(bad_bt); + return bad_bt->view; +} + +void bad_bt_set_button_callback(BadBt* bad_bt, BadBtButtonCallback callback, void* context) { + furi_assert(bad_bt); + furi_assert(callback); + with_view_model( + bad_bt->view, + BadBtModel * model, + { + UNUSED(model); + bad_bt->callback = callback; + bad_bt->context = context; + }, + true); +} + +void bad_bt_set_file_name(BadBt* bad_bt, const char* name) { + furi_assert(name); + with_view_model( + bad_bt->view, BadBtModel * model, { strlcpy(model->file_name, name, MAX_NAME_LEN); }, true); +} + +void bad_bt_set_layout(BadBt* bad_bt, const char* layout) { + furi_assert(layout); + with_view_model( + bad_bt->view, BadBtModel * model, { strlcpy(model->layout, layout, MAX_NAME_LEN); }, true); +} + +void bad_bt_set_state(BadBt* bad_bt, BadBtState* st) { + furi_assert(st); + uint32_t pin = 0; + if(bad_bt->context != NULL) { + BadBtApp* app = bad_bt->context; + if(app->bt != NULL) { + pin = app->bt->pin; + } + } + st->pin = pin; + with_view_model( + bad_bt->view, + BadBtModel * model, + { + memcpy(&(model->state), st, sizeof(BadBtState)); + model->anim_frame ^= 1; + }, + true); +} + +bool bad_bt_is_idle_state(BadBt* bad_bt) { + bool is_idle = false; + with_view_model( + bad_bt->view, + BadBtModel * model, + { + if((model->state.state == BadBtStateIdle) || (model->state.state == BadBtStateDone) || + (model->state.state == BadBtStateNotConnected)) { + is_idle = true; + } + }, + false); + return is_idle; +} diff --git a/applications/external/bad_bt/views/bad_bt_view.h b/applications/external/bad_bt/views/bad_bt_view.h new file mode 100644 index 000000000..850a71057 --- /dev/null +++ b/applications/external/bad_bt/views/bad_bt_view.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +typedef void (*BadBtButtonCallback)(InputKey key, void* context); + +typedef struct { + View* view; + BadBtButtonCallback callback; + void* context; +} BadBt; + +typedef struct BadBtState BadBtState; + +BadBt* bad_bt_alloc(); + +void bad_bt_free(BadBt* bad_bt); + +View* bad_bt_get_view(BadBt* bad_bt); + +void bad_bt_set_button_callback(BadBt* bad_bt, BadBtButtonCallback callback, void* context); + +void bad_bt_set_file_name(BadBt* bad_bt, const char* name); + +void bad_bt_set_layout(BadBt* bad_bt, const char* layout); + +void bad_bt_set_state(BadBt* bad_bt, BadBtState* st); + +bool bad_bt_is_idle_state(BadBt* bad_bt); diff --git a/applications/external/subbrute b/applications/external/subbrute index c94efdf88..d1317392a 160000 --- a/applications/external/subbrute +++ b/applications/external/subbrute @@ -1 +1 @@ -Subproject commit c94efdf88ac2ed51b88caf288ab3c50afaaec9bb +Subproject commit d1317392a4a7dadd34157e66c73fee6990fd2100 diff --git a/applications/external/totp/features_config.h b/applications/external/totp/features_config.h index 845ec1d35..683eff2fc 100644 --- a/applications/external/totp/features_config.h +++ b/applications/external/totp/features_config.h @@ -11,4 +11,4 @@ // End of list // Target firmware to build for -#define TOTP_TARGET_FIRMWARE TOTP_FIRMWARE_OFFICIAL_DEV \ No newline at end of file +#define TOTP_TARGET_FIRMWARE TOTP_FIRMWARE_XTREME \ No newline at end of file diff --git a/applications/external/uart_terminal/uart_terminal_uart.c b/applications/external/uart_terminal/uart_terminal_uart.c index 136bd5d38..e906c9e8e 100644 --- a/applications/external/uart_terminal/uart_terminal_uart.c +++ b/applications/external/uart_terminal/uart_terminal_uart.c @@ -38,8 +38,6 @@ void uart_terminal_uart_on_irq_cb(UartIrqEvent ev, uint8_t data, void* context) static int32_t uart_worker(void* context) { UART_TerminalUart* uart = (void*)context; - uart->rx_stream = furi_stream_buffer_alloc(RX_BUF_SIZE, 1); - while(1) { uint32_t events = furi_thread_flags_wait(WORKER_ALL_RX_EVENTS, FuriFlagWaitAny, FuriWaitForever); @@ -64,6 +62,16 @@ void uart_terminal_uart_tx(uint8_t* data, size_t len) { UART_TerminalUart* uart_terminal_uart_init(UART_TerminalApp* app) { UART_TerminalUart* uart = malloc(sizeof(UART_TerminalUart)); + uart->app = app; + // Init all rx stream and thread early to avoid crashes + uart->rx_stream = furi_stream_buffer_alloc(RX_BUF_SIZE, 1); + uart->rx_thread = furi_thread_alloc(); + furi_thread_set_name(uart->rx_thread, "UART_TerminalUartRxThread"); + furi_thread_set_stack_size(uart->rx_thread, 1024); + furi_thread_set_context(uart->rx_thread, uart); + furi_thread_set_callback(uart->rx_thread, uart_worker); + + furi_thread_start(uart->rx_thread); furi_hal_console_disable(); if(app->BAUDRATE == 0) { @@ -72,14 +80,6 @@ UART_TerminalUart* uart_terminal_uart_init(UART_TerminalApp* app) { furi_hal_uart_set_br(UART_CH, app->BAUDRATE); furi_hal_uart_set_irq_cb(UART_CH, uart_terminal_uart_on_irq_cb, uart); - uart->app = app; - uart->rx_thread = furi_thread_alloc(); - furi_thread_set_name(uart->rx_thread, "UART_TerminalUartRxThread"); - furi_thread_set_stack_size(uart->rx_thread, 1024); - furi_thread_set_context(uart->rx_thread, uart); - furi_thread_set_callback(uart->rx_thread, uart_worker); - - furi_thread_start(uart->rx_thread); return uart; } diff --git a/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_start.c b/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_start.c index c77543fd0..6f22d0adb 100644 --- a/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_start.c +++ b/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_start.c @@ -97,11 +97,11 @@ const WifiMarauderItem items[NUM_MENU_ITEMS] = { NO_ARGS, FOCUS_CONSOLE_END, SHOW_STOPSCAN_TIP}, - {"Sniff PMKID on channel", - {""}, - 1, - {"sniffpmkid -c"}, - INPUT_ARGS, + {"Sniff PMKID", + {"ap", "channel"}, + 2, + {"sniffpmkid -d -l", "sniffpmkid -c"}, + TOGGLE_ARGS, FOCUS_CONSOLE_END, SHOW_STOPSCAN_TIP}, {"Channel", diff --git a/applications/external/wifi_marauder_companion/wifi_marauder_app.h b/applications/external/wifi_marauder_companion/wifi_marauder_app.h index 2f8b2f9a6..303fb59b4 100644 --- a/applications/external/wifi_marauder_companion/wifi_marauder_app.h +++ b/applications/external/wifi_marauder_companion/wifi_marauder_app.h @@ -4,7 +4,7 @@ extern "C" { #endif -#define WIFI_MARAUDER_APP_VERSION "v0.3.4" +#define WIFI_MARAUDER_APP_VERSION "v0.3.5" typedef struct WifiMarauderApp WifiMarauderApp; diff --git a/applications/main/application.fam b/applications/main/application.fam index 35a63ddab..4ac735e92 100644 --- a/applications/main/application.fam +++ b/applications/main/application.fam @@ -24,9 +24,9 @@ App( name="Basic applications for main menu", apptype=FlipperAppType.METAPACKAGE, provides=[ - "gpio", + #"gpio", #"ibutton", - "infrared", + #"infrared", "lfrfid", "nfc", "subghz", diff --git a/applications/main/lfrfid/lfrfid.c b/applications/main/lfrfid/lfrfid.c index a2bcdcf52..a82d2dd03 100644 --- a/applications/main/lfrfid/lfrfid.c +++ b/applications/main/lfrfid/lfrfid.c @@ -186,11 +186,16 @@ int32_t lfrfid_app(void* p) { DOLPHIN_DEED(DolphinDeedRfidEmulate); } else { furi_string_set(app->file_path, args); - lfrfid_load_key_data(app, app->file_path, true); - view_dispatcher_attach_to_gui( - app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); - scene_manager_next_scene(app->scene_manager, LfRfidSceneEmulate); - DOLPHIN_DEED(DolphinDeedRfidEmulate); + if(lfrfid_load_key_data(app, app->file_path, true)) { + view_dispatcher_attach_to_gui( + app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + scene_manager_next_scene(app->scene_manager, LfRfidSceneEmulate); + DOLPHIN_DEED(DolphinDeedRfidEmulate); + } else { + // TODO: exit properly + lfrfid_free(app); + return 0; + } } } else { diff --git a/applications/main/nfc/helpers/nfc_custom_event.h b/applications/main/nfc/helpers/nfc_custom_event.h index 4227a5b14..aa932a3d8 100644 --- a/applications/main/nfc/helpers/nfc_custom_event.h +++ b/applications/main/nfc/helpers/nfc_custom_event.h @@ -12,4 +12,6 @@ enum NfcCustomEvent { NfcCustomEventDictAttackSkip, NfcCustomEventRpcLoad, NfcCustomEventRpcSessionClose, + NfcCustomEventUpdateLog, + NfcCustomEventSaveShadow, }; diff --git a/applications/main/nfc/nfc.c b/applications/main/nfc/nfc.c index 16d5837ea..59110940c 100644 --- a/applications/main/nfc/nfc.c +++ b/applications/main/nfc/nfc.c @@ -290,6 +290,9 @@ int32_t nfc_app(void* p) { } else if(nfc->dev->format == NfcDeviceSaveFormatMifareClassic) { scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicEmulate); DOLPHIN_DEED(DolphinDeedNfcEmulate); + } else if(nfc->dev->format == NfcDeviceSaveFormatNfcV) { + scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcVEmulate); + DOLPHIN_DEED(DolphinDeedNfcEmulate); } else { scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateUid); DOLPHIN_DEED(DolphinDeedNfcEmulate); diff --git a/applications/main/nfc/scenes/nfc_scene_config.h b/applications/main/nfc/scenes/nfc_scene_config.h index a9da07dfd..445c4436a 100644 --- a/applications/main/nfc/scenes/nfc_scene_config.h +++ b/applications/main/nfc/scenes/nfc_scene_config.h @@ -14,6 +14,12 @@ ADD_SCENE(nfc, file_select, FileSelect) ADD_SCENE(nfc, emulate_uid, EmulateUid) ADD_SCENE(nfc, nfca_read_success, NfcaReadSuccess) ADD_SCENE(nfc, nfca_menu, NfcaMenu) +ADD_SCENE(nfc, nfcv_menu, NfcVMenu) +ADD_SCENE(nfc, nfcv_unlock_menu, NfcVUnlockMenu) +ADD_SCENE(nfc, nfcv_key_input, NfcVKeyInput) +ADD_SCENE(nfc, nfcv_unlock, NfcVUnlock) +ADD_SCENE(nfc, nfcv_emulate, NfcVEmulate) +ADD_SCENE(nfc, nfcv_sniff, NfcVSniff) ADD_SCENE(nfc, mf_ultralight_read_success, MfUltralightReadSuccess) ADD_SCENE(nfc, mf_ultralight_data, MfUltralightData) ADD_SCENE(nfc, mf_ultralight_menu, MfUltralightMenu) @@ -46,14 +52,18 @@ ADD_SCENE(nfc, mf_classic_update_success, MfClassicUpdateSuccess) ADD_SCENE(nfc, mf_classic_wrong_card, MfClassicWrongCard) ADD_SCENE(nfc, emv_read_success, EmvReadSuccess) ADD_SCENE(nfc, emv_menu, EmvMenu) +#if FURI_DEBUG ADD_SCENE(nfc, emulate_apdu_sequence, EmulateApduSequence) +#endif ADD_SCENE(nfc, device_info, DeviceInfo) ADD_SCENE(nfc, delete, Delete) ADD_SCENE(nfc, delete_success, DeleteSuccess) ADD_SCENE(nfc, restore_original_confirm, RestoreOriginalConfirm) ADD_SCENE(nfc, restore_original, RestoreOriginal) +#if FURI_DEBUG ADD_SCENE(nfc, debug, Debug) ADD_SCENE(nfc, field, Field) +#endif ADD_SCENE(nfc, dict_not_found, DictNotFound) ADD_SCENE(nfc, rpc, Rpc) ADD_SCENE(nfc, exit_confirm, ExitConfirm) diff --git a/applications/main/nfc/scenes/nfc_scene_debug.c b/applications/main/nfc/scenes/nfc_scene_debug.c index ed079c2ed..5dc29c96a 100644 --- a/applications/main/nfc/scenes/nfc_scene_debug.c +++ b/applications/main/nfc/scenes/nfc_scene_debug.c @@ -1,3 +1,4 @@ +#if FURI_DEBUG #include "../nfc_i.h" enum SubmenuDebugIndex { @@ -52,3 +53,4 @@ void nfc_scene_debug_on_exit(void* context) { submenu_reset(nfc->submenu); } +#endif \ No newline at end of file diff --git a/applications/main/nfc/scenes/nfc_scene_delete.c b/applications/main/nfc/scenes/nfc_scene_delete.c index cbb52bfd0..0808db45a 100644 --- a/applications/main/nfc/scenes/nfc_scene_delete.c +++ b/applications/main/nfc/scenes/nfc_scene_delete.c @@ -31,6 +31,8 @@ void nfc_scene_delete_on_enter(void* context) { nfc->widget, 64, 24, AlignCenter, AlignTop, FontSecondary, furi_string_get_cstr(temp_str)); NfcProtocol protocol = nfc->dev->dev_data.protocol; + const char* nfc_type = "NFC-A"; + if(protocol == NfcDeviceProtocolEMV) { furi_string_set(temp_str, "EMV bank card"); } else if(protocol == NfcDeviceProtocolMifareUl) { @@ -39,12 +41,15 @@ void nfc_scene_delete_on_enter(void* context) { furi_string_set(temp_str, nfc_mf_classic_type(nfc->dev->dev_data.mf_classic_data.type)); } else if(protocol == NfcDeviceProtocolMifareDesfire) { furi_string_set(temp_str, "MIFARE DESFire"); + } else if(protocol == NfcDeviceProtocolNfcV) { + furi_string_set(temp_str, "ISO15693 tag"); + nfc_type = "NFC-V"; } else { furi_string_set(temp_str, "Unknown ISO tag"); } widget_add_string_element( nfc->widget, 64, 34, AlignCenter, AlignTop, FontSecondary, furi_string_get_cstr(temp_str)); - widget_add_string_element(nfc->widget, 64, 44, AlignCenter, AlignTop, FontSecondary, "NFC-A"); + widget_add_string_element(nfc->widget, 64, 44, AlignCenter, AlignTop, FontSecondary, nfc_type); furi_string_free(temp_str); view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); diff --git a/applications/main/nfc/scenes/nfc_scene_emulate_apdu_sequence.c b/applications/main/nfc/scenes/nfc_scene_emulate_apdu_sequence.c index 358ad2ab6..bc3f0f42d 100644 --- a/applications/main/nfc/scenes/nfc_scene_emulate_apdu_sequence.c +++ b/applications/main/nfc/scenes/nfc_scene_emulate_apdu_sequence.c @@ -1,3 +1,4 @@ +#if FURI_DEBUG #include "../nfc_i.h" #include @@ -32,3 +33,4 @@ void nfc_scene_emulate_apdu_sequence_on_exit(void* context) { nfc_blink_stop(nfc); } +#endif \ No newline at end of file diff --git a/applications/main/nfc/scenes/nfc_scene_extra_actions.c b/applications/main/nfc/scenes/nfc_scene_extra_actions.c index 66aaf5a26..7f5bc7e75 100644 --- a/applications/main/nfc/scenes/nfc_scene_extra_actions.c +++ b/applications/main/nfc/scenes/nfc_scene_extra_actions.c @@ -4,6 +4,8 @@ enum SubmenuIndex { SubmenuIndexReadCardType, SubmenuIndexMfClassicKeys, SubmenuIndexMfUltralightUnlock, + SubmenuIndexNfcVUnlock, + SubmenuIndexNfcVSniff, }; void nfc_scene_extra_actions_submenu_callback(void* context, uint32_t index) { @@ -34,6 +36,18 @@ void nfc_scene_extra_actions_on_enter(void* context) { SubmenuIndexMfUltralightUnlock, nfc_scene_extra_actions_submenu_callback, nfc); + submenu_add_item( + submenu, + "Unlock SLIX-L", + SubmenuIndexNfcVUnlock, + nfc_scene_extra_actions_submenu_callback, + nfc); + submenu_add_item( + submenu, + "Listen NfcV Reader", + SubmenuIndexNfcVSniff, + nfc_scene_extra_actions_submenu_callback, + nfc); submenu_set_selected_item( submenu, scene_manager_get_scene_state(nfc->scene_manager, NfcSceneExtraActions)); view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); @@ -58,6 +72,12 @@ bool nfc_scene_extra_actions_on_event(void* context, SceneManagerEvent event) { scene_manager_set_scene_state(nfc->scene_manager, NfcSceneReadCardType, 0); scene_manager_next_scene(nfc->scene_manager, NfcSceneReadCardType); consumed = true; + } else if(event.event == SubmenuIndexNfcVUnlock) { + scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcVUnlockMenu); + consumed = true; + } else if(event.event == SubmenuIndexNfcVSniff) { + scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcVSniff); + consumed = true; } scene_manager_set_scene_state(nfc->scene_manager, NfcSceneExtraActions, event.event); } diff --git a/applications/main/nfc/scenes/nfc_scene_field.c b/applications/main/nfc/scenes/nfc_scene_field.c index e3eb6a708..f287e8796 100644 --- a/applications/main/nfc/scenes/nfc_scene_field.c +++ b/applications/main/nfc/scenes/nfc_scene_field.c @@ -1,3 +1,4 @@ +#if FURI_DEBUG #include "../nfc_i.h" void nfc_scene_field_on_enter(void* context) { @@ -31,3 +32,4 @@ void nfc_scene_field_on_exit(void* context) { notification_internal_message(nfc->notifications, &sequence_reset_blue); popup_reset(nfc->popup); } +#endif \ No newline at end of file diff --git a/applications/main/nfc/scenes/nfc_scene_nfc_data_info.c b/applications/main/nfc/scenes/nfc_scene_nfc_data_info.c index 92ad7b56e..38f3e75c7 100644 --- a/applications/main/nfc/scenes/nfc_scene_nfc_data_info.c +++ b/applications/main/nfc/scenes/nfc_scene_nfc_data_info.c @@ -7,6 +7,17 @@ void nfc_scene_nfc_data_info_widget_callback(GuiButtonType result, InputType typ } } +uint32_t nfc_scene_nfc_data_info_get_key(uint8_t* data) { + uint32_t value = 0; + + for(uint32_t pos = 0; pos < 4; pos++) { + value <<= 8; + value |= data[pos]; + } + + return value; +} + void nfc_scene_nfc_data_info_on_enter(void* context) { Nfc* nfc = context; Widget* widget = nfc->widget; @@ -15,7 +26,7 @@ void nfc_scene_nfc_data_info_on_enter(void* context) { NfcProtocol protocol = dev_data->protocol; uint8_t text_scroll_height = 0; if((protocol == NfcDeviceProtocolMifareDesfire) || (protocol == NfcDeviceProtocolMifareUl) || - (protocol == NfcDeviceProtocolMifareClassic)) { + (protocol == NfcDeviceProtocolMifareClassic) || (protocol == NfcDeviceProtocolNfcV)) { widget_add_button_element( widget, GuiButtonTypeRight, "More", nfc_scene_nfc_data_info_widget_callback, nfc); text_scroll_height = 52; @@ -41,19 +52,165 @@ void nfc_scene_nfc_data_info_on_enter(void* context) { temp_str, "\e#%s\n", nfc_mf_classic_type(dev_data->mf_classic_data.type)); } else if(protocol == NfcDeviceProtocolMifareDesfire) { furi_string_cat_printf(temp_str, "\e#MIFARE DESfire\n"); + } else if(protocol == NfcDeviceProtocolNfcV) { + switch(dev_data->nfcv_data.sub_type) { + case NfcVTypePlain: + furi_string_cat_printf(temp_str, "\e#ISO15693\n"); + break; + case NfcVTypeSlix: + furi_string_cat_printf(temp_str, "\e#ISO15693 SLIX\n"); + break; + case NfcVTypeSlixS: + furi_string_cat_printf(temp_str, "\e#ISO15693 SLIX-S\n"); + break; + case NfcVTypeSlixL: + furi_string_cat_printf(temp_str, "\e#ISO15693 SLIX-L\n"); + break; + case NfcVTypeSlix2: + furi_string_cat_printf(temp_str, "\e#ISO15693 SLIX2\n"); + break; + default: + furi_string_cat_printf(temp_str, "\e#ISO15693 (unknown)\n"); + break; + } } else { furi_string_cat_printf(temp_str, "\e#Unknown ISO tag\n"); } // Set tag iso data - char iso_type = FURI_BIT(nfc_data->sak, 5) ? '4' : '3'; - furi_string_cat_printf(temp_str, "ISO 14443-%c (NFC-A)\n", iso_type); - furi_string_cat_printf(temp_str, "UID:"); - for(size_t i = 0; i < nfc_data->uid_len; i++) { - furi_string_cat_printf(temp_str, " %02X", nfc_data->uid[i]); + if(protocol == NfcDeviceProtocolNfcV) { + NfcVData* nfcv_data = &nfc->dev->dev_data.nfcv_data; + + furi_string_cat_printf(temp_str, "UID:\n"); + for(size_t i = 0; i < nfc_data->uid_len; i++) { + furi_string_cat_printf(temp_str, " %02X", nfc_data->uid[i]); + } + furi_string_cat_printf(temp_str, "\n"); + + furi_string_cat_printf( + temp_str, + "DSFID: %02X %s\n", + nfcv_data->dsfid, + (nfcv_data->security_status[0] & NfcVLockBitDsfid) ? "(locked)" : ""); + furi_string_cat_printf( + temp_str, + "AFI: %02X %s\n", + nfcv_data->afi, + (nfcv_data->security_status[0] & NfcVLockBitAfi) ? "(locked)" : ""); + furi_string_cat_printf(temp_str, "IC Ref: %02X\n", nfcv_data->ic_ref); + furi_string_cat_printf(temp_str, "Blocks: %02X\n", nfcv_data->block_num); + furi_string_cat_printf(temp_str, "Blocksize: %02X\n", nfcv_data->block_size); + + furi_string_cat_printf( + temp_str, "Data (%d byte)\n", nfcv_data->block_num * nfcv_data->block_size); + + int maxBlocks = nfcv_data->block_num; + if(maxBlocks > 32) { + maxBlocks = 32; + furi_string_cat_printf(temp_str, "(truncated to %d blocks)\n", maxBlocks); + } + + for(int block = 0; block < maxBlocks; block++) { + const char* status = (nfcv_data->security_status[block] & 0x01) ? "(lck)" : ""; + for(int pos = 0; pos < nfcv_data->block_size; pos++) { + furi_string_cat_printf( + temp_str, " %02X", nfcv_data->data[block * nfcv_data->block_size + pos]); + } + furi_string_cat_printf(temp_str, " %s\n", status); + } + furi_string_cat_printf(temp_str, "\n"); + + switch(dev_data->nfcv_data.sub_type) { + case NfcVTypePlain: + furi_string_cat_printf(temp_str, "Type: Plain\n"); + break; + case NfcVTypeSlix: + furi_string_cat_printf(temp_str, "Type: SLIX\n"); + furi_string_cat_printf(temp_str, "Keys:\n"); + furi_string_cat_printf( + temp_str, + " EAS %08lX\n", + nfc_scene_nfc_data_info_get_key(nfcv_data->sub_data.slix.key_eas)); + break; + case NfcVTypeSlixS: + furi_string_cat_printf(temp_str, "Type: SLIX-S\n"); + furi_string_cat_printf(temp_str, "Keys:\n"); + furi_string_cat_printf( + temp_str, + " Read %08lX\n", + nfc_scene_nfc_data_info_get_key(nfcv_data->sub_data.slix.key_read)); + furi_string_cat_printf( + temp_str, + " Write %08lX\n", + nfc_scene_nfc_data_info_get_key(nfcv_data->sub_data.slix.key_write)); + furi_string_cat_printf( + temp_str, + " Privacy %08lX\n", + nfc_scene_nfc_data_info_get_key(nfcv_data->sub_data.slix.key_privacy)); + furi_string_cat_printf( + temp_str, + " Destroy %08lX\n", + nfc_scene_nfc_data_info_get_key(nfcv_data->sub_data.slix.key_destroy)); + furi_string_cat_printf( + temp_str, + " EAS %08lX\n", + nfc_scene_nfc_data_info_get_key(nfcv_data->sub_data.slix.key_eas)); + break; + case NfcVTypeSlixL: + furi_string_cat_printf(temp_str, "Type: SLIX-L\n"); + furi_string_cat_printf(temp_str, "Keys:\n"); + furi_string_cat_printf( + temp_str, + " Privacy %08lX\n", + nfc_scene_nfc_data_info_get_key(nfcv_data->sub_data.slix.key_privacy)); + furi_string_cat_printf( + temp_str, + " Destroy %08lX\n", + nfc_scene_nfc_data_info_get_key(nfcv_data->sub_data.slix.key_destroy)); + furi_string_cat_printf( + temp_str, + " EAS %08lX\n", + nfc_scene_nfc_data_info_get_key(nfcv_data->sub_data.slix.key_eas)); + break; + case NfcVTypeSlix2: + furi_string_cat_printf(temp_str, "Type: SLIX2\n"); + furi_string_cat_printf(temp_str, "Keys:\n"); + furi_string_cat_printf( + temp_str, + " Read %08lX\n", + nfc_scene_nfc_data_info_get_key(nfcv_data->sub_data.slix.key_read)); + furi_string_cat_printf( + temp_str, + " Write %08lX\n", + nfc_scene_nfc_data_info_get_key(nfcv_data->sub_data.slix.key_write)); + furi_string_cat_printf( + temp_str, + " Privacy %08lX\n", + nfc_scene_nfc_data_info_get_key(nfcv_data->sub_data.slix.key_privacy)); + furi_string_cat_printf( + temp_str, + " Destroy %08lX\n", + nfc_scene_nfc_data_info_get_key(nfcv_data->sub_data.slix.key_destroy)); + furi_string_cat_printf( + temp_str, + " EAS %08lX\n", + nfc_scene_nfc_data_info_get_key(nfcv_data->sub_data.slix.key_eas)); + break; + default: + furi_string_cat_printf(temp_str, "\e#ISO15693 (unknown)\n"); + break; + } + } else { + char iso_type = FURI_BIT(nfc_data->sak, 5) ? '4' : '3'; + furi_string_cat_printf(temp_str, "ISO 14443-%c (NFC-A)\n", iso_type); + furi_string_cat_printf(temp_str, "UID:"); + for(size_t i = 0; i < nfc_data->uid_len; i++) { + furi_string_cat_printf(temp_str, " %02X", nfc_data->uid[i]); + } + furi_string_cat_printf( + temp_str, "\nATQA: %02X %02X ", nfc_data->atqa[1], nfc_data->atqa[0]); + furi_string_cat_printf(temp_str, " SAK: %02X", nfc_data->sak); } - furi_string_cat_printf(temp_str, "\nATQA: %02X %02X ", nfc_data->atqa[1], nfc_data->atqa[0]); - furi_string_cat_printf(temp_str, " SAK: %02X", nfc_data->sak); // Set application specific data if(protocol == NfcDeviceProtocolMifareDesfire) { @@ -139,6 +296,8 @@ bool nfc_scene_nfc_data_info_on_event(void* context, SceneManagerEvent event) { consumed = true; } else if(protocol == NfcDeviceProtocolMifareClassic) { scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicData); + } else if(protocol == NfcDeviceProtocolNfcV) { + scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcVMenu); consumed = true; } } diff --git a/applications/main/nfc/scenes/nfc_scene_nfcv_emulate.c b/applications/main/nfc/scenes/nfc_scene_nfcv_emulate.c new file mode 100644 index 000000000..ca10f5d6e --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_nfcv_emulate.c @@ -0,0 +1,164 @@ +#include "../nfc_i.h" + +#define NFC_SCENE_EMULATE_NFCV_LOG_SIZE_MAX (200) + +enum { + NfcSceneNfcVEmulateStateWidget, + NfcSceneNfcVEmulateStateTextBox, +}; + +bool nfc_scene_nfcv_emulate_worker_callback(NfcWorkerEvent event, void* context) { + UNUSED(event); + furi_assert(context); + Nfc* nfc = context; + + switch(event) { + case NfcWorkerEventNfcVCommandExecuted: + view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventUpdateLog); + break; + case NfcWorkerEventNfcVContentChanged: + view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventSaveShadow); + break; + default: + break; + } + return true; +} + +void nfc_scene_nfcv_emulate_widget_callback(GuiButtonType result, InputType type, void* context) { + furi_assert(context); + Nfc* nfc = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(nfc->view_dispatcher, result); + } +} + +void nfc_scene_nfcv_emulate_textbox_callback(void* context) { + furi_assert(context); + Nfc* nfc = context; + view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit); +} + +static void nfc_scene_nfcv_emulate_widget_config(Nfc* nfc, bool data_received) { + FuriHalNfcDevData* data = &nfc->dev->dev_data.nfc_data; + Widget* widget = nfc->widget; + widget_reset(widget); + FuriString* info_str; + info_str = furi_string_alloc(); + + widget_add_icon_element(widget, 0, 3, &I_RFIDDolphinSend_97x61); + widget_add_string_element( + widget, 89, 32, AlignCenter, AlignTop, FontPrimary, "Emulating NfcV"); + if(strcmp(nfc->dev->dev_name, "")) { + furi_string_printf(info_str, "%s", nfc->dev->dev_name); + } else { + for(uint8_t i = 0; i < data->uid_len; i++) { + furi_string_cat_printf(info_str, "%02X ", data->uid[i]); + } + } + furi_string_trim(info_str); + widget_add_text_box_element( + widget, 56, 43, 70, 21, AlignCenter, AlignTop, furi_string_get_cstr(info_str), true); + furi_string_free(info_str); + if(data_received) { + widget_add_button_element( + widget, GuiButtonTypeCenter, "Log", nfc_scene_nfcv_emulate_widget_callback, nfc); + } +} + +void nfc_scene_nfcv_emulate_on_enter(void* context) { + Nfc* nfc = context; + + // Setup Widget + nfc_scene_nfcv_emulate_widget_config(nfc, false); + // Setup TextBox + TextBox* text_box = nfc->text_box; + text_box_set_font(text_box, TextBoxFontHex); + text_box_set_focus(text_box, TextBoxFocusEnd); + text_box_set_text(text_box, ""); + furi_string_reset(nfc->text_box_store); + + // Set Widget state and view + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneNfcVEmulate, NfcSceneNfcVEmulateStateWidget); + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); + // Start worker + memset(&nfc->dev->dev_data.reader_data, 0, sizeof(NfcReaderRequestData)); + nfc_worker_start( + nfc->worker, + NfcWorkerStateNfcVEmulate, + &nfc->dev->dev_data, + nfc_scene_nfcv_emulate_worker_callback, + nfc); + + nfc_blink_emulate_start(nfc); +} + +bool nfc_scene_nfcv_emulate_on_event(void* context, SceneManagerEvent event) { + Nfc* nfc = context; + NfcVData* nfcv_data = &nfc->dev->dev_data.nfcv_data; + uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneNfcVEmulate); + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventUpdateLog) { + // Add data button to widget if data is received for the first time + if(strlen(nfcv_data->last_command) > 0) { + if(!furi_string_size(nfc->text_box_store)) { + nfc_scene_nfcv_emulate_widget_config(nfc, true); + } + /* use the last n bytes from the log so there's enough space for the new log entry */ + size_t maxSize = + NFC_SCENE_EMULATE_NFCV_LOG_SIZE_MAX - (strlen(nfcv_data->last_command) + 1); + if(furi_string_size(nfc->text_box_store) >= maxSize) { + furi_string_right(nfc->text_box_store, (strlen(nfcv_data->last_command) + 1)); + } + furi_string_cat_printf(nfc->text_box_store, "%s", nfcv_data->last_command); + furi_string_push_back(nfc->text_box_store, '\n'); + text_box_set_text(nfc->text_box, furi_string_get_cstr(nfc->text_box_store)); + + /* clear previously logged command */ + strcpy(nfcv_data->last_command, ""); + } + consumed = true; + } else if(event.event == NfcCustomEventSaveShadow) { + if(furi_string_size(nfc->dev->load_path)) { + nfc_device_save_shadow(nfc->dev, furi_string_get_cstr(nfc->dev->load_path)); + } + consumed = true; + } else if(event.event == GuiButtonTypeCenter && state == NfcSceneNfcVEmulateStateWidget) { + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextBox); + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneNfcVEmulate, NfcSceneNfcVEmulateStateTextBox); + consumed = true; + } else if(event.event == NfcCustomEventViewExit && state == NfcSceneNfcVEmulateStateTextBox) { + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneNfcVEmulate, NfcSceneNfcVEmulateStateWidget); + consumed = true; + } + } else if(event.type == SceneManagerEventTypeBack) { + if(state == NfcSceneNfcVEmulateStateTextBox) { + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneNfcVEmulate, NfcSceneNfcVEmulateStateWidget); + consumed = true; + } + } + + return consumed; +} + +void nfc_scene_nfcv_emulate_on_exit(void* context) { + Nfc* nfc = context; + + // Stop worker + nfc_worker_stop(nfc->worker); + + // Clear view + widget_reset(nfc->widget); + text_box_reset(nfc->text_box); + furi_string_reset(nfc->text_box_store); + + nfc_blink_stop(nfc); +} diff --git a/applications/main/nfc/scenes/nfc_scene_nfcv_key_input.c b/applications/main/nfc/scenes/nfc_scene_nfcv_key_input.c new file mode 100644 index 000000000..cc53c4dcb --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_nfcv_key_input.c @@ -0,0 +1,48 @@ +#include "../nfc_i.h" +#include + +void nfc_scene_nfcv_key_input_byte_input_callback(void* context) { + Nfc* nfc = context; + NfcVSlixData* data = &nfc->dev->dev_data.nfcv_data.sub_data.slix; + + memcpy(data->key_privacy, nfc->byte_input_store, 4); + view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventByteInputDone); +} + +void nfc_scene_nfcv_key_input_on_enter(void* context) { + Nfc* nfc = context; + + // Setup view + ByteInput* byte_input = nfc->byte_input; + byte_input_set_header_text(byte_input, "Enter The Password In Hex"); + byte_input_set_result_callback( + byte_input, + nfc_scene_nfcv_key_input_byte_input_callback, + NULL, + nfc, + nfc->byte_input_store, + 4); + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewByteInput); +} + +bool nfc_scene_nfcv_key_input_on_event(void* context, SceneManagerEvent event) { + Nfc* nfc = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventByteInputDone) { + scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcVUnlock); + DOLPHIN_DEED(DolphinDeedNfcRead); + consumed = true; + } + } + return consumed; +} + +void nfc_scene_nfcv_key_input_on_exit(void* context) { + Nfc* nfc = context; + + // Clear view + byte_input_set_result_callback(nfc->byte_input, NULL, NULL, NULL, NULL, 0); + byte_input_set_header_text(nfc->byte_input, ""); +} diff --git a/applications/main/nfc/scenes/nfc_scene_nfcv_menu.c b/applications/main/nfc/scenes/nfc_scene_nfcv_menu.c new file mode 100644 index 000000000..44d677513 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_nfcv_menu.c @@ -0,0 +1,63 @@ +#include "../nfc_i.h" +#include + +enum SubmenuIndex { + SubmenuIndexSave, + SubmenuIndexEmulate, +}; + +void nfc_scene_nfcv_menu_submenu_callback(void* context, uint32_t index) { + Nfc* nfc = context; + + view_dispatcher_send_custom_event(nfc->view_dispatcher, index); +} + +void nfc_scene_nfcv_menu_on_enter(void* context) { + Nfc* nfc = context; + Submenu* submenu = nfc->submenu; + + submenu_add_item( + submenu, "Emulate", SubmenuIndexEmulate, nfc_scene_nfcv_menu_submenu_callback, nfc); + submenu_add_item(submenu, "Save", SubmenuIndexSave, nfc_scene_nfcv_menu_submenu_callback, nfc); + + submenu_set_selected_item( + nfc->submenu, scene_manager_get_scene_state(nfc->scene_manager, NfcSceneNfcVMenu)); + + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); +} + +bool nfc_scene_nfcv_menu_on_event(void* context, SceneManagerEvent event) { + Nfc* nfc = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexSave) { + nfc->dev->format = NfcDeviceSaveFormatNfcV; + // Clear device name + nfc_device_set_name(nfc->dev, ""); + scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveName); + consumed = true; + } else if(event.event == SubmenuIndexEmulate) { + scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcVEmulate); + if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSetType)) { + DOLPHIN_DEED(DolphinDeedNfcAddEmulate); + } else { + DOLPHIN_DEED(DolphinDeedNfcEmulate); + } + consumed = true; + } + scene_manager_set_scene_state(nfc->scene_manager, NfcSceneNfcVMenu, event.event); + + } else if(event.type == SceneManagerEventTypeBack) { + consumed = scene_manager_previous_scene(nfc->scene_manager); + } + + return consumed; +} + +void nfc_scene_nfcv_menu_on_exit(void* context) { + Nfc* nfc = context; + + // Clear view + submenu_reset(nfc->submenu); +} diff --git a/applications/main/nfc/scenes/nfc_scene_nfcv_sniff.c b/applications/main/nfc/scenes/nfc_scene_nfcv_sniff.c new file mode 100644 index 000000000..2c0f17981 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_nfcv_sniff.c @@ -0,0 +1,155 @@ +#include "../nfc_i.h" + +#define NFC_SCENE_EMULATE_NFCV_LOG_SIZE_MAX (800) + +enum { + NfcSceneNfcVSniffStateWidget, + NfcSceneNfcVSniffStateTextBox, +}; + +bool nfc_scene_nfcv_sniff_worker_callback(NfcWorkerEvent event, void* context) { + UNUSED(event); + furi_assert(context); + Nfc* nfc = context; + + switch(event) { + case NfcWorkerEventNfcVCommandExecuted: + view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventUpdateLog); + break; + case NfcWorkerEventNfcVContentChanged: + view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventSaveShadow); + break; + default: + break; + } + return true; +} + +void nfc_scene_nfcv_sniff_widget_callback(GuiButtonType result, InputType type, void* context) { + furi_assert(context); + Nfc* nfc = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(nfc->view_dispatcher, result); + } +} + +void nfc_scene_nfcv_sniff_textbox_callback(void* context) { + furi_assert(context); + Nfc* nfc = context; + view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit); +} + +static void nfc_scene_nfcv_sniff_widget_config(Nfc* nfc, bool data_received) { + Widget* widget = nfc->widget; + widget_reset(widget); + FuriString* info_str; + info_str = furi_string_alloc(); + + widget_add_icon_element(widget, 0, 3, &I_RFIDDolphinSend_97x61); + widget_add_string_element(widget, 89, 32, AlignCenter, AlignTop, FontPrimary, "Listen NfcV"); + furi_string_trim(info_str); + widget_add_text_box_element( + widget, 56, 43, 70, 21, AlignCenter, AlignTop, furi_string_get_cstr(info_str), true); + furi_string_free(info_str); + if(data_received) { + widget_add_button_element( + widget, GuiButtonTypeCenter, "Log", nfc_scene_nfcv_sniff_widget_callback, nfc); + } +} + +void nfc_scene_nfcv_sniff_on_enter(void* context) { + Nfc* nfc = context; + + // Setup Widget + nfc_scene_nfcv_sniff_widget_config(nfc, false); + // Setup TextBox + TextBox* text_box = nfc->text_box; + text_box_set_font(text_box, TextBoxFontHex); + text_box_set_focus(text_box, TextBoxFocusEnd); + text_box_set_text(text_box, ""); + furi_string_reset(nfc->text_box_store); + + // Set Widget state and view + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneNfcVSniff, NfcSceneNfcVSniffStateWidget); + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); + // Start worker + memset(&nfc->dev->dev_data.reader_data, 0, sizeof(NfcReaderRequestData)); + nfc_worker_start( + nfc->worker, + NfcWorkerStateNfcVSniff, + &nfc->dev->dev_data, + nfc_scene_nfcv_sniff_worker_callback, + nfc); + + nfc_blink_emulate_start(nfc); +} + +bool nfc_scene_nfcv_sniff_on_event(void* context, SceneManagerEvent event) { + Nfc* nfc = context; + NfcVData* nfcv_data = &nfc->dev->dev_data.nfcv_data; + uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneNfcVSniff); + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventUpdateLog) { + // Add data button to widget if data is received for the first time + if(strlen(nfcv_data->last_command) > 0) { + if(!furi_string_size(nfc->text_box_store)) { + nfc_scene_nfcv_sniff_widget_config(nfc, true); + } + /* use the last n bytes from the log so there's enough space for the new log entry */ + size_t maxSize = + NFC_SCENE_EMULATE_NFCV_LOG_SIZE_MAX - (strlen(nfcv_data->last_command) + 1); + if(furi_string_size(nfc->text_box_store) >= maxSize) { + furi_string_right(nfc->text_box_store, (strlen(nfcv_data->last_command) + 1)); + } + furi_string_cat_printf(nfc->text_box_store, "%s", nfcv_data->last_command); + furi_string_push_back(nfc->text_box_store, '\n'); + text_box_set_text(nfc->text_box, furi_string_get_cstr(nfc->text_box_store)); + + /* clear previously logged command */ + strcpy(nfcv_data->last_command, ""); + } + consumed = true; + } else if(event.event == NfcCustomEventSaveShadow) { + if(furi_string_size(nfc->dev->load_path)) { + nfc_device_save_shadow(nfc->dev, furi_string_get_cstr(nfc->dev->load_path)); + } + consumed = true; + } else if(event.event == GuiButtonTypeCenter && state == NfcSceneNfcVSniffStateWidget) { + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextBox); + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneNfcVSniff, NfcSceneNfcVSniffStateTextBox); + consumed = true; + } else if(event.event == NfcCustomEventViewExit && state == NfcSceneNfcVSniffStateTextBox) { + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneNfcVSniff, NfcSceneNfcVSniffStateWidget); + consumed = true; + } + } else if(event.type == SceneManagerEventTypeBack) { + if(state == NfcSceneNfcVSniffStateTextBox) { + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneNfcVSniff, NfcSceneNfcVSniffStateWidget); + consumed = true; + } + } + + return consumed; +} + +void nfc_scene_nfcv_sniff_on_exit(void* context) { + Nfc* nfc = context; + + // Stop worker + nfc_worker_stop(nfc->worker); + + // Clear view + widget_reset(nfc->widget); + text_box_reset(nfc->text_box); + furi_string_reset(nfc->text_box_store); + + nfc_blink_stop(nfc); +} diff --git a/applications/main/nfc/scenes/nfc_scene_nfcv_unlock.c b/applications/main/nfc/scenes/nfc_scene_nfcv_unlock.c new file mode 100644 index 000000000..26de304de --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_nfcv_unlock.c @@ -0,0 +1,154 @@ +#include "../nfc_i.h" +#include + +typedef enum { + NfcSceneNfcVUnlockStateIdle, + NfcSceneNfcVUnlockStateDetecting, + NfcSceneNfcVUnlockStateUnlocked, + NfcSceneNfcVUnlockStateAlreadyUnlocked, + NfcSceneNfcVUnlockStateNotSupportedCard, +} NfcSceneNfcVUnlockState; + +static bool nfc_scene_nfcv_unlock_worker_callback(NfcWorkerEvent event, void* context) { + Nfc* nfc = context; + NfcVSlixData* data = &nfc->dev->dev_data.nfcv_data.sub_data.slix; + + if(event == NfcWorkerEventNfcVPassKey) { + memcpy(data->key_privacy, nfc->byte_input_store, 4); + } else { + view_dispatcher_send_custom_event(nfc->view_dispatcher, event); + } + return true; +} + +void nfc_scene_nfcv_unlock_popup_callback(void* context) { + Nfc* nfc = context; + view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit); +} + +void nfc_scene_nfcv_unlock_set_state(Nfc* nfc, NfcSceneNfcVUnlockState state) { + FuriHalNfcDevData* nfc_data = &(nfc->dev->dev_data.nfc_data); + NfcVData* nfcv_data = &(nfc->dev->dev_data.nfcv_data); + + uint32_t curr_state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneNfcVUnlock); + if(curr_state != state) { + Popup* popup = nfc->popup; + if(state == NfcSceneNfcVUnlockStateDetecting) { + popup_reset(popup); + popup_set_text( + popup, "Put figurine on\nFlipper's back", 97, 24, AlignCenter, AlignTop); + popup_set_icon(popup, 0, 8, &I_NFC_manual_60x50); + } else if(state == NfcSceneNfcVUnlockStateUnlocked) { + popup_reset(popup); + + if(nfc_worker_get_state(nfc->worker) == NfcWorkerStateNfcVUnlockAndSave) { + snprintf( + nfc->dev->dev_name, + sizeof(nfc->dev->dev_name), + "SLIX_%02X%02X%02X%02X%02X%02X%02X%02X", + nfc_data->uid[0], + nfc_data->uid[1], + nfc_data->uid[2], + nfc_data->uid[3], + nfc_data->uid[4], + nfc_data->uid[5], + nfc_data->uid[6], + nfc_data->uid[7]); + + nfc->dev->format = NfcDeviceSaveFormatNfcV; + + if(nfc_save_file(nfc)) { + popup_set_header(popup, "Successfully\nsaved", 94, 3, AlignCenter, AlignTop); + } else { + popup_set_header( + popup, "Unlocked but\nsave failed!", 94, 3, AlignCenter, AlignTop); + } + } else { + popup_set_header(popup, "Successfully\nunlocked", 94, 3, AlignCenter, AlignTop); + } + + notification_message(nfc->notifications, &sequence_single_vibro); + //notification_message(nfc->notifications, &sequence_success); + + popup_set_icon(popup, 0, 6, &I_RFIDDolphinSuccess_108x57); + popup_set_context(popup, nfc); + popup_set_callback(popup, nfc_scene_nfcv_unlock_popup_callback); + popup_set_timeout(popup, 1500); + + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); + DOLPHIN_DEED(DolphinDeedNfcReadSuccess); + + } else if(state == NfcSceneNfcVUnlockStateAlreadyUnlocked) { + popup_reset(popup); + + popup_set_header(popup, "Already\nUnlocked!", 94, 3, AlignCenter, AlignTop); + popup_set_icon(popup, 0, 6, &I_RFIDDolphinSuccess_108x57); + popup_set_context(popup, nfc); + popup_set_callback(popup, nfc_scene_nfcv_unlock_popup_callback); + popup_set_timeout(popup, 1500); + + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); + } else if(state == NfcSceneNfcVUnlockStateNotSupportedCard) { + popup_reset(popup); + popup_set_header(popup, "Wrong Type Of Card!", 64, 3, AlignCenter, AlignTop); + popup_set_text(popup, nfcv_data->error, 4, 22, AlignLeft, AlignTop); + popup_set_icon(popup, 73, 20, &I_DolphinCommon_56x48); + } + scene_manager_set_scene_state(nfc->scene_manager, NfcSceneNfcVUnlock, state); + } +} + +void nfc_scene_nfcv_unlock_on_enter(void* context) { + Nfc* nfc = context; + + nfc_device_clear(nfc->dev); + // Setup view + nfc_scene_nfcv_unlock_set_state(nfc, NfcSceneNfcVUnlockStateDetecting); + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); + + // Start worker + nfc_worker_start( + nfc->worker, + NfcWorkerStateNfcVUnlockAndSave, + &nfc->dev->dev_data, + nfc_scene_nfcv_unlock_worker_callback, + nfc); + + nfc_blink_read_start(nfc); +} + +bool nfc_scene_nfcv_unlock_on_event(void* context, SceneManagerEvent event) { + Nfc* nfc = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcWorkerEventCardDetected) { + nfc_scene_nfcv_unlock_set_state(nfc, NfcSceneNfcVUnlockStateUnlocked); + consumed = true; + } else if(event.event == NfcWorkerEventAborted) { + nfc_scene_nfcv_unlock_set_state(nfc, NfcSceneNfcVUnlockStateAlreadyUnlocked); + consumed = true; + } else if(event.event == NfcWorkerEventNoCardDetected) { + nfc_scene_nfcv_unlock_set_state(nfc, NfcSceneNfcVUnlockStateDetecting); + consumed = true; + } else if(event.event == NfcWorkerEventWrongCardDetected) { + nfc_scene_nfcv_unlock_set_state(nfc, NfcSceneNfcVUnlockStateNotSupportedCard); + } + } else if(event.type == SceneManagerEventTypeBack) { + consumed = scene_manager_search_and_switch_to_previous_scene( + nfc->scene_manager, NfcSceneNfcVUnlockMenu); + } + return consumed; +} + +void nfc_scene_nfcv_unlock_on_exit(void* context) { + Nfc* nfc = context; + + // Stop worker + nfc_worker_stop(nfc->worker); + // Clear view + popup_reset(nfc->popup); + nfc_blink_stop(nfc); + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneNfcVUnlock, NfcSceneNfcVUnlockStateIdle); +} diff --git a/applications/main/nfc/scenes/nfc_scene_nfcv_unlock_menu.c b/applications/main/nfc/scenes/nfc_scene_nfcv_unlock_menu.c new file mode 100644 index 000000000..9c4c81fbd --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_nfcv_unlock_menu.c @@ -0,0 +1,60 @@ +#include "../nfc_i.h" +#include + +enum SubmenuIndex { + SubmenuIndexNfcVUnlockMenuManual, + SubmenuIndexNfcVUnlockMenuTonieBox, +}; + +void nfc_scene_nfcv_unlock_menu_submenu_callback(void* context, uint32_t index) { + Nfc* nfc = context; + + view_dispatcher_send_custom_event(nfc->view_dispatcher, index); +} + +void nfc_scene_nfcv_unlock_menu_on_enter(void* context) { + Nfc* nfc = context; + Submenu* submenu = nfc->submenu; + + uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneNfcVUnlockMenu); + submenu_add_item( + submenu, + "Enter PWD Manually", + SubmenuIndexNfcVUnlockMenuManual, + nfc_scene_nfcv_unlock_menu_submenu_callback, + nfc); + submenu_add_item( + submenu, + "Auth As TonieBox", + SubmenuIndexNfcVUnlockMenuTonieBox, + nfc_scene_nfcv_unlock_menu_submenu_callback, + nfc); + submenu_set_selected_item(submenu, state); + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); +} + +bool nfc_scene_nfcv_unlock_menu_on_event(void* context, SceneManagerEvent event) { + Nfc* nfc = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexNfcVUnlockMenuManual) { + nfc->dev->dev_data.nfcv_data.auth_method = NfcVAuthMethodManual; + scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcVKeyInput); + consumed = true; + } else if(event.event == SubmenuIndexNfcVUnlockMenuTonieBox) { + nfc->dev->dev_data.nfcv_data.auth_method = NfcVAuthMethodTonieBox; + scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcVUnlock); + DOLPHIN_DEED(DolphinDeedNfcRead); + consumed = true; + } + scene_manager_set_scene_state(nfc->scene_manager, NfcSceneNfcVUnlockMenu, event.event); + } + return consumed; +} + +void nfc_scene_nfcv_unlock_menu_on_exit(void* context) { + Nfc* nfc = context; + + submenu_reset(nfc->submenu); +} diff --git a/applications/main/nfc/scenes/nfc_scene_read.c b/applications/main/nfc/scenes/nfc_scene_read.c index 4252883b2..d30706c5b 100644 --- a/applications/main/nfc/scenes/nfc_scene_read.c +++ b/applications/main/nfc/scenes/nfc_scene_read.c @@ -68,6 +68,11 @@ bool nfc_scene_read_on_event(void* context, SceneManagerEvent event) { scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcaReadSuccess); DOLPHIN_DEED(DolphinDeedNfcReadSuccess); consumed = true; + } else if(event.event == NfcWorkerEventReadNfcV) { + notification_message(nfc->notifications, &sequence_success); + scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcDataInfo); + DOLPHIN_DEED(DolphinDeedNfcReadSuccess); + consumed = true; } else if(event.event == NfcWorkerEventReadMfUltralight) { notification_message(nfc->notifications, &sequence_success); // Set unlock password input to 0xFFFFFFFF only on fresh read diff --git a/applications/main/nfc/scenes/nfc_scene_rpc.c b/applications/main/nfc/scenes/nfc_scene_rpc.c index 60d01a30d..d06ee7564 100644 --- a/applications/main/nfc/scenes/nfc_scene_rpc.c +++ b/applications/main/nfc/scenes/nfc_scene_rpc.c @@ -55,6 +55,13 @@ bool nfc_scene_rpc_on_event(void* context, SceneManagerEvent event) { &nfc->dev->dev_data, nfc_scene_rpc_emulate_callback, nfc); + } else if(nfc->dev->format == NfcDeviceSaveFormatNfcV) { + nfc_worker_start( + nfc->worker, + NfcWorkerStateNfcVEmulate, + &nfc->dev->dev_data, + nfc_scene_rpc_emulate_callback, + nfc); } else { nfc_worker_start( nfc->worker, NfcWorkerStateUidEmulate, &nfc->dev->dev_data, NULL, nfc); diff --git a/applications/main/nfc/scenes/nfc_scene_saved_menu.c b/applications/main/nfc/scenes/nfc_scene_saved_menu.c index e52493f2a..15771bcc3 100644 --- a/applications/main/nfc/scenes/nfc_scene_saved_menu.c +++ b/applications/main/nfc/scenes/nfc_scene_saved_menu.c @@ -44,6 +44,7 @@ void nfc_scene_saved_menu_on_enter(void* context) { } } else if( nfc->dev->format == NfcDeviceSaveFormatMifareUl || + nfc->dev->format == NfcDeviceSaveFormatNfcV || nfc->dev->format == NfcDeviceSaveFormatMifareClassic) { submenu_add_item( submenu, "Emulate", SubmenuIndexEmulate, nfc_scene_saved_menu_submenu_callback, nfc); @@ -117,6 +118,8 @@ bool nfc_scene_saved_menu_on_event(void* context, SceneManagerEvent event) { scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightEmulate); } else if(nfc->dev->format == NfcDeviceSaveFormatMifareClassic) { scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicEmulate); + } else if(nfc->dev->format == NfcDeviceSaveFormatNfcV) { + scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcVEmulate); } else { scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateUid); } diff --git a/applications/main/nfc/scenes/nfc_scene_start.c b/applications/main/nfc/scenes/nfc_scene_start.c index a01f871ab..858d519fc 100644 --- a/applications/main/nfc/scenes/nfc_scene_start.c +++ b/applications/main/nfc/scenes/nfc_scene_start.c @@ -8,7 +8,9 @@ enum SubmenuIndex { SubmenuIndexSaved, SubmenuIndexExtraAction, SubmenuIndexAddManually, +#if FURI_DEBUG SubmenuIndexDebug, +#endif }; void nfc_scene_start_submenu_callback(void* context, uint32_t index) { @@ -29,12 +31,12 @@ void nfc_scene_start_on_enter(void* context) { submenu, "Extra Actions", SubmenuIndexExtraAction, nfc_scene_start_submenu_callback, nfc); submenu_add_item( submenu, "Add Manually", SubmenuIndexAddManually, nfc_scene_start_submenu_callback, nfc); - +#if FURI_DEBUG if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { submenu_add_item( submenu, "Debug", SubmenuIndexDebug, nfc_scene_start_submenu_callback, nfc); } - +#endif submenu_set_selected_item( submenu, scene_manager_get_scene_state(nfc->scene_manager, NfcSceneStart)); @@ -82,11 +84,14 @@ bool nfc_scene_start_on_event(void* context, SceneManagerEvent event) { nfc->scene_manager, NfcSceneStart, SubmenuIndexAddManually); scene_manager_next_scene(nfc->scene_manager, NfcSceneSetType); consumed = true; - } else if(event.event == SubmenuIndexDebug) { + } +#if FURI_DEBUG + else if(event.event == SubmenuIndexDebug) { scene_manager_set_scene_state(nfc->scene_manager, NfcSceneStart, SubmenuIndexDebug); scene_manager_next_scene(nfc->scene_manager, NfcSceneDebug); consumed = true; } +#endif } return consumed; } diff --git a/applications/main/subghz/application.fam b/applications/main/subghz/application.fam index f0dc66e89..0b403feda 100644 --- a/applications/main/subghz/application.fam +++ b/applications/main/subghz/application.fam @@ -10,7 +10,10 @@ App( "cli", "dialogs", ], - provides=["subghz_start"], + provides=[ + "subghz_start", + "subghz_load_dangerous_settings", + ], icon="A_Sub1ghz_14", stack_size=3 * 1024, order=10, @@ -23,3 +26,11 @@ App( requires=["subghz"], order=40, ) + +App( + appid="subghz_load_dangerous_settings", + apptype=FlipperAppType.STARTUP, + entry_point="subghz_dangerous_freq", + requires=["storage", "subghz"], + order=650, +) diff --git a/applications/main/subghz/helpers/subghz_custom_event.h b/applications/main/subghz/helpers/subghz_custom_event.h index cd163a653..bf3c71d3a 100644 --- a/applications/main/subghz/helpers/subghz_custom_event.h +++ b/applications/main/subghz/helpers/subghz_custom_event.h @@ -36,6 +36,8 @@ typedef enum { SubmenuIndexCAME24bit868, SubmenuIndexCAMETwee, SubmenuIndexCAMESpace, + SubmenuIndexCameAtomo433, + SubmenuIndexCameAtomo868, SubmenuIndexPricenton433, SubmenuIndexPricenton315, SubmenuIndexBETT_433, diff --git a/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.c b/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.c index 0631c7c15..4854bc882 100644 --- a/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.c +++ b/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.c @@ -266,6 +266,34 @@ bool subghz_txrx_gen_alutech_at_4n_protocol( return res; } +bool subghz_txrx_gen_came_atomo_protocol( + void* context, + const char* preset_name, + uint32_t frequency, + uint32_t serial, + uint16_t cnt) { + SubGhzTxRx* txrx = context; + + bool res = false; + + txrx->transmitter = + subghz_transmitter_alloc_init(txrx->environment, SUBGHZ_PROTOCOL_CAME_ATOMO_NAME); + subghz_txrx_set_preset(txrx, preset_name, frequency, NULL, 0); + + if(txrx->transmitter && subghz_protocol_came_atomo_create_data( + subghz_transmitter_get_protocol_instance(txrx->transmitter), + txrx->fff_data, + serial, + cnt, + txrx->preset)) { + res = true; + } + + subghz_transmitter_free(txrx->transmitter); + + return res; +} + bool subghz_txrx_gen_somfy_telis_protocol( void* context, const char* preset_name, diff --git a/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.h b/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.h index a5fa2a802..dc7dfbe7e 100644 --- a/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.h +++ b/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.h @@ -108,6 +108,13 @@ bool subghz_txrx_gen_somfy_telis_protocol( uint8_t btn, uint16_t cnt); +bool subghz_txrx_gen_came_atomo_protocol( + void* context, + const char* preset_name, + uint32_t frequency, + uint32_t serial, + uint16_t cnt); + /** * Generate data SecPlus v2 protocol * diff --git a/applications/main/subghz/helpers/subghz_types.h b/applications/main/subghz/helpers/subghz_types.h index 700049237..1cddfc8d5 100644 --- a/applications/main/subghz/helpers/subghz_types.h +++ b/applications/main/subghz/helpers/subghz_types.h @@ -91,3 +91,9 @@ typedef enum { SubGhzViewReceiverModeLive, SubGhzViewReceiverModeFile, } SubGhzViewReceiverMode; + +typedef enum { + SubGhzDecodeRawStateStart, + SubGhzDecodeRawStateLoading, + SubGhzDecodeRawStateLoaded, +} SubGhzDecodeRawState; \ No newline at end of file diff --git a/applications/main/subghz/scenes/subghz_scene_decode_raw.c b/applications/main/subghz/scenes/subghz_scene_decode_raw.c index 54c729cc3..e88da3749 100644 --- a/applications/main/subghz/scenes/subghz_scene_decode_raw.c +++ b/applications/main/subghz/scenes/subghz_scene_decode_raw.c @@ -120,7 +120,8 @@ bool subghz_scene_decode_raw_next(SubGhz* subghz) { uint32_t duration = level_duration_get_duration(level_duration); subghz_receiver_decode(receiver, level, duration); } else { - subghz->decode_raw_state = SubGhzDecodeRawStateLoaded; + scene_manager_set_scene_state( + subghz->scene_manager, SubGhzSceneDecodeRAW, SubGhzDecodeRawStateLoaded); subghz->state_notifications = SubGhzNotificationStateIDLE; subghz_view_receiver_add_data_progress(subghz->subghz_receiver, "Done!"); @@ -155,11 +156,13 @@ void subghz_scene_decode_raw_on_enter(void* context) { subghz_txrx_receiver_set_filter(subghz->txrx, SubGhzProtocolFlag_Decodable); - if(subghz->decode_raw_state == SubGhzDecodeRawStateStart) { + if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneDecodeRAW) == + SubGhzDecodeRawStateStart) { //Decode RAW to history subghz_history_reset(subghz->history); if(subghz_scene_decode_raw_start(subghz)) { - subghz->decode_raw_state = SubGhzDecodeRawStateLoading; + scene_manager_set_scene_state( + subghz->scene_manager, SubGhzSceneDecodeRAW, SubGhzDecodeRawStateLoading); subghz->state_notifications = SubGhzNotificationStateRx; } } else { @@ -192,10 +195,9 @@ bool subghz_scene_decode_raw_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { switch(event.event) { case SubGhzCustomEventViewReceiverBack: - subghz->decode_raw_state = SubGhzDecodeRawStateStart; + scene_manager_set_scene_state( + subghz->scene_manager, SubGhzSceneDecodeRAW, SubGhzDecodeRawStateStart); subghz->idx_menu_chosen = 0; - subghz->in_decoder_scene = false; - subghz->in_decoder_scene_skip = false; subghz_txrx_set_rx_calback(subghz->txrx, NULL, subghz); @@ -214,7 +216,6 @@ bool subghz_scene_decode_raw_on_event(void* context, SceneManagerEvent event) { case SubGhzCustomEventViewReceiverOK: subghz->idx_menu_chosen = subghz_view_receiver_get_idx_menu(subghz->subghz_receiver); subghz->state_notifications = SubGhzNotificationStateIDLE; - subghz->in_decoder_scene = true; scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReceiverInfo); consumed = true; break; @@ -242,7 +243,7 @@ bool subghz_scene_decode_raw_on_event(void* context, SceneManagerEvent event) { break; } - switch(subghz->decode_raw_state) { + switch(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneDecodeRAW)) { case SubGhzDecodeRawStateLoading: subghz_scene_decode_raw_next(subghz); break; diff --git a/applications/main/subghz/scenes/subghz_scene_radio_settings.c b/applications/main/subghz/scenes/subghz_scene_radio_settings.c index 8e8d05019..b60ea0b2d 100644 --- a/applications/main/subghz/scenes/subghz_scene_radio_settings.c +++ b/applications/main/subghz/scenes/subghz_scene_radio_settings.c @@ -25,11 +25,14 @@ const char* const debug_pin_text[DEBUG_P_COUNT] = { "17(1W)", }; -#define DEBUG_COUNTER_COUNT 3 +#define DEBUG_COUNTER_COUNT 6 const char* const debug_counter_text[DEBUG_COUNTER_COUNT] = { "+1", "+2", "+3", + "+4", + "+5", + "+10", }; static void subghz_scene_ext_module_changed(VariableItem* item) { @@ -71,6 +74,15 @@ static void subghz_scene_receiver_config_set_debug_counter(VariableItem* item) { case 2: furi_hal_subghz_set_rolling_counter_mult(3); break; + case 3: + furi_hal_subghz_set_rolling_counter_mult(4); + break; + case 4: + furi_hal_subghz_set_rolling_counter_mult(5); + break; + case 5: + furi_hal_subghz_set_rolling_counter_mult(10); + break; default: break; } @@ -140,24 +152,58 @@ void subghz_scene_radio_settings_on_enter(void* context) { variable_item_set_current_value_index(item, value_index); variable_item_set_current_value_text(item, timestamp_names_text[value_index]); - item = variable_item_list_add( - subghz->variable_item_list, - "Counter incr.", - DEBUG_COUNTER_COUNT, - subghz_scene_receiver_config_set_debug_counter, - subghz); - switch(furi_hal_subghz_get_rolling_counter_mult()) { - case 1: - value_index = 0; - break; - case 2: - value_index = 1; - break; - case 3: - value_index = 2; - break; - default: - break; + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + item = variable_item_list_add( + subghz->variable_item_list, + "Counter incr.", + DEBUG_COUNTER_COUNT, + subghz_scene_receiver_config_set_debug_counter, + subghz); + switch(furi_hal_subghz_get_rolling_counter_mult()) { + case 1: + value_index = 0; + break; + case 2: + value_index = 1; + break; + case 3: + value_index = 2; + break; + case 4: + value_index = 3; + break; + case 5: + value_index = 4; + break; + case 10: + value_index = 5; + break; + default: + break; + } + } else { + item = variable_item_list_add( + subghz->variable_item_list, + "Counter incr.", + 3, + subghz_scene_receiver_config_set_debug_counter, + subghz); + switch(furi_hal_subghz_get_rolling_counter_mult()) { + case 1: + value_index = 0; + break; + case 2: + value_index = 1; + break; + case 3: + value_index = 2; + break; + default: + // Reset to default value + value_index = 0; + furi_hal_subghz_set_rolling_counter_mult(1); + break; + } } variable_item_set_current_value_index(item, value_index); variable_item_set_current_value_text(item, debug_counter_text[value_index]); diff --git a/applications/main/subghz/scenes/subghz_scene_receiver.c b/applications/main/subghz/scenes/subghz_scene_receiver.c index 9987449cd..26e92e983 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver.c @@ -227,12 +227,20 @@ bool subghz_scene_receiver_on_event(void* context, SceneManagerEvent event) { consumed = true; break; case SubGhzCustomEventViewReceiverDeleteItem: + subghz->state_notifications = SubGhzNotificationStateRx; subghz->idx_menu_chosen = subghz_view_receiver_get_idx_menu(subghz->subghz_receiver); + subghz_view_receiver_disable_draw_callback(subghz->subghz_receiver); + subghz_history_delete_item(subghz->history, subghz->idx_menu_chosen); subghz_view_receiver_delete_element_callback(subghz->subghz_receiver); + subghz_view_receiver_enable_draw_callback(subghz->subghz_receiver); + subghz_scene_receiver_update_statusbar(subghz); + if(subghz_history_get_last_index(subghz->history) == 0) { + subghz_rx_key_state_set(subghz, SubGhzRxKeyStateStart); + } consumed = true; break; case SubGhzCustomEventViewReceiverConfig: diff --git a/applications/main/subghz/scenes/subghz_scene_receiver_info.c b/applications/main/subghz/scenes/subghz_scene_receiver_info.c index 370638931..2d89de731 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver_info.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver_info.c @@ -141,7 +141,7 @@ bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event) subghz_scene_receiver_info_draw_widget(subghz); subghz_txrx_stop(subghz->txrx); - if(!subghz->in_decoder_scene) { + if(!scene_manager_has_previous_scene(subghz->scene_manager, SubGhzSceneDecodeRAW)) { subghz_txrx_rx_start(subghz->txrx); subghz_txrx_hopper_unpause(subghz->txrx); @@ -161,9 +161,6 @@ bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event) if(subghz_txrx_protocol_is_serializable(subghz->txrx)) { subghz_file_name_clear(subghz); - if(subghz->in_decoder_scene) { - subghz->in_decoder_scene_skip = true; - } scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveName); } return true; @@ -192,9 +189,7 @@ bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event) void subghz_scene_receiver_info_on_exit(void* context) { SubGhz* subghz = context; - if(subghz->in_decoder_scene && !subghz->in_decoder_scene_skip) { - subghz->in_decoder_scene = false; - } + widget_reset(subghz->widget); keeloq_reset_mfname(); keeloq_reset_kl_type(); diff --git a/applications/main/subghz/scenes/subghz_scene_save_name.c b/applications/main/subghz/scenes/subghz_scene_save_name.c index b0c3a1b12..74a861efb 100644 --- a/applications/main/subghz/scenes/subghz_scene_save_name.c +++ b/applications/main/subghz/scenes/subghz_scene_save_name.c @@ -127,7 +127,7 @@ bool subghz_scene_save_name_on_event(void* context, SceneManagerEvent event) { if(!(strcmp(subghz->file_name_tmp, "") == 0) || scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) != SubGhzCustomEventManagerNoSet) { - if(!subghz->in_decoder_scene) { + if(!scene_manager_has_previous_scene(subghz->scene_manager, SubGhzSceneDecodeRAW)) { furi_string_set(subghz->file_path, subghz->file_path_tmp); } } diff --git a/applications/main/subghz/scenes/subghz_scene_save_success.c b/applications/main/subghz/scenes/subghz_scene_save_success.c index 0d0f9059e..6a5346492 100644 --- a/applications/main/subghz/scenes/subghz_scene_save_success.c +++ b/applications/main/subghz/scenes/subghz_scene_save_success.c @@ -24,7 +24,7 @@ bool subghz_scene_save_success_on_event(void* context, SceneManagerEvent event) SubGhz* subghz = context; if(event.type == SceneManagerEventTypeCustom) { if(event.event == SubGhzCustomEventSceneSaveSuccess) { - if(!subghz->in_decoder_scene) { + if(!scene_manager_has_previous_scene(subghz->scene_manager, SubGhzSceneDecodeRAW)) { if(!scene_manager_search_and_switch_to_previous_scene( subghz->scene_manager, SubGhzSceneReceiver)) { subghz_rx_key_state_set(subghz, SubGhzRxKeyStateRAWSave); @@ -38,7 +38,9 @@ bool subghz_scene_save_success_on_event(void* context, SceneManagerEvent event) } } } else { - subghz->decode_raw_state = SubGhzDecodeRawStateStart; + scene_manager_set_scene_state( + subghz->scene_manager, SubGhzSceneDecodeRAW, SubGhzDecodeRawStateStart); + subghz->idx_menu_chosen = 0; subghz_txrx_set_rx_calback(subghz->txrx, NULL, subghz); @@ -64,11 +66,6 @@ bool subghz_scene_save_success_on_event(void* context, SceneManagerEvent event) void subghz_scene_save_success_on_exit(void* context) { SubGhz* subghz = context; - if(subghz->in_decoder_scene) { - subghz->in_decoder_scene = false; - subghz->in_decoder_scene_skip = false; - } - // Clear view Popup* popup = subghz->popup; diff --git a/applications/main/subghz/scenes/subghz_scene_set_type.c b/applications/main/subghz/scenes/subghz_scene_set_type.c index ef53006da..14d8ba0d8 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_type.c +++ b/applications/main/subghz/scenes/subghz_scene_set_type.c @@ -199,6 +199,18 @@ void subghz_scene_set_type_on_enter(void* context) { SubmenuIndexCAMETwee, subghz_scene_set_type_submenu_callback, subghz); + submenu_add_item( + subghz->submenu, + "CAME Atomo 433MHz", + SubmenuIndexCameAtomo433, + subghz_scene_set_type_submenu_callback, + subghz); + submenu_add_item( + subghz->submenu, + "CAME Atomo 868MHz", + SubmenuIndexCameAtomo868, + subghz_scene_set_type_submenu_callback, + subghz); submenu_add_item( subghz->submenu, "KL: CAME Space 433MHz", @@ -534,6 +546,14 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError); } break; + case SubmenuIndexCameAtomo433: + generated_protocol = subghz_txrx_gen_came_atomo_protocol( + subghz->txrx, "AM650", 433920000, (key & 0x0FFFFFFF) | 0x10000000, 0x0003); + break; + case SubmenuIndexCameAtomo868: + generated_protocol = subghz_txrx_gen_came_atomo_protocol( + subghz->txrx, "AM650", 868350000, (key & 0x0FFFFFFF) | 0x10000000, 0x0003); + break; case SubmenuIndexBFTMitto: generated_protocol = subghz_txrx_gen_keeloq_bft_protocol( subghz->txrx, diff --git a/applications/main/subghz/scenes/subghz_scene_transmitter.c b/applications/main/subghz/scenes/subghz_scene_transmitter.c index b07785c06..15a53603d 100644 --- a/applications/main/subghz/scenes/subghz_scene_transmitter.c +++ b/applications/main/subghz/scenes/subghz_scene_transmitter.c @@ -76,8 +76,8 @@ bool subghz_scene_transmitter_on_event(void* context, SceneManagerEvent event) { } else if(event.event == SubGhzCustomEventViewTransmitterSendStop) { subghz->state_notifications = SubGhzNotificationStateIDLE; subghz_txrx_stop(subghz->txrx); - if(subghz_custom_btn_get() != 0) { - subghz_custom_btn_set(0); + if(subghz_custom_btn_get() != SUBGHZ_CUSTOM_BTN_OK) { + subghz_custom_btn_set(SUBGHZ_CUSTOM_BTN_OK); uint8_t tmp_counter = furi_hal_subghz_get_rolling_counter_mult(); furi_hal_subghz_set_rolling_counter_mult(0); // Calling restore! diff --git a/applications/main/subghz/subghz.c b/applications/main/subghz/subghz.c index 4f4393efb..d30bd79f2 100644 --- a/applications/main/subghz/subghz.c +++ b/applications/main/subghz/subghz.c @@ -60,9 +60,6 @@ SubGhz* subghz_alloc(bool alloc_for_tx_only) { // GUI subghz->gui = furi_record_open(RECORD_GUI); - subghz->in_decoder_scene = false; - subghz->in_decoder_scene_skip = false; - // View Dispatcher subghz->view_dispatcher = view_dispatcher_alloc(); view_dispatcher_enable_queue(subghz->view_dispatcher); diff --git a/applications/main/subghz/subghz_dangerous_freq.c b/applications/main/subghz/subghz_dangerous_freq.c new file mode 100644 index 000000000..69a54f04b --- /dev/null +++ b/applications/main/subghz/subghz_dangerous_freq.c @@ -0,0 +1,23 @@ +#include +#include + +#include + +#include + +void subghz_dangerous_freq() { + bool is_extended_i = false; + + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* fff_data_file = flipper_format_file_alloc(storage); + + if(flipper_format_file_open_existing(fff_data_file, "/ext/subghz/assets/dangerous_settings")) { + flipper_format_read_bool( + fff_data_file, "yes_i_want_to_destroy_my_flipper", &is_extended_i, 1); + } + + furi_hal_subghz_set_dangerous_frequency(is_extended_i); + + flipper_format_free(fff_data_file); + furi_record_close(RECORD_STORAGE); +} diff --git a/applications/main/subghz/subghz_i.h b/applications/main/subghz/subghz_i.h index 785a6708f..3d1c2db9c 100644 --- a/applications/main/subghz/subghz_i.h +++ b/applications/main/subghz/subghz_i.h @@ -54,12 +54,6 @@ typedef struct { uint8_t seed[4]; } SecureData; -typedef enum { - SubGhzDecodeRawStateStart, - SubGhzDecodeRawStateLoading, - SubGhzDecodeRawStateLoaded, -} SubGhzDecodeRawState; - struct SubGhz { Gui* gui; NotificationApp* notifications; @@ -98,16 +92,12 @@ struct SubGhz { FuriString* error_str; SubGhzLock lock; - bool in_decoder_scene; - bool in_decoder_scene_skip; - bool ignore_starline; bool ignore_auto_alarms; bool ignore_magellan; SecureData* secure_data; - SubGhzDecodeRawState decode_raw_state; SubGhzFileEncoderWorker* decode_raw_file_worker_encoder; SubGhzThresholdRssi* threshold_rssi; diff --git a/applications/main/subghz/views/receiver.c b/applications/main/subghz/views/receiver.c index 6f272b6a6..53921f866 100644 --- a/applications/main/subghz/views/receiver.c +++ b/applications/main/subghz/views/receiver.c @@ -71,6 +71,7 @@ typedef struct { SubGhzViewReceiverMode mode; uint8_t u_rssi; size_t scroll_counter; + bool nodraw; } SubGhzViewReceiverModel; void subghz_view_receiver_set_mode( @@ -241,39 +242,47 @@ void subghz_view_receiver_draw(Canvas* canvas, SubGhzViewReceiverModel* model) { bool scrollbar = model->history_item > 4; FuriString* str_buff = furi_string_alloc(); - SubGhzReceiverMenuItem* item_menu; + if(!model->nodraw) { + SubGhzReceiverMenuItem* item_menu; - for(size_t i = 0; i < MIN(model->history_item, MENU_ITEMS); ++i) { - size_t idx = CLAMP((uint16_t)(i + model->list_offset), model->history_item, 0); - item_menu = SubGhzReceiverMenuItemArray_get(model->history->data, idx); - furi_string_set(str_buff, item_menu->item_str); - size_t scroll_counter = model->scroll_counter; - if(model->idx == idx) { - subghz_view_receiver_draw_frame(canvas, i, scrollbar); - if(scroll_counter < SCROLL_DELAY) { - // Show time of signal one moment - furi_string_set(str_buff, item_menu->time); - scroll_counter = 0; - } else { - scroll_counter -= SCROLL_DELAY; + for(size_t i = 0; i < MIN(model->history_item, MENU_ITEMS); ++i) { + size_t idx = CLAMP((uint16_t)(i + model->list_offset), model->history_item, 0); + item_menu = SubGhzReceiverMenuItemArray_get(model->history->data, idx); + if(item_menu == NULL) { + break; } - } else { - canvas_set_color(canvas, ColorBlack); - scroll_counter = 0; + if(item_menu->type == 0) { + break; + } + furi_string_set(str_buff, item_menu->item_str); + size_t scroll_counter = model->scroll_counter; + if(model->idx == idx) { + subghz_view_receiver_draw_frame(canvas, i, scrollbar); + if(scroll_counter < SCROLL_DELAY) { + // Show time of signal one moment + furi_string_set(str_buff, item_menu->time); + scroll_counter = 0; + } else { + scroll_counter -= SCROLL_DELAY; + } + } else { + canvas_set_color(canvas, ColorBlack); + scroll_counter = 0; + } + canvas_draw_icon(canvas, 4, 2 + i * FRAME_HEIGHT, ReceiverItemIcons[item_menu->type]); + elements_scrollable_text_line( + canvas, + 15, + 9 + i * FRAME_HEIGHT, + (scrollbar ? MAX_LEN_PX - 6 : MAX_LEN_PX), + str_buff, + scroll_counter, + (model->idx != idx)); + furi_string_reset(str_buff); + } + if(scrollbar) { + elements_scrollbar_pos(canvas, 128, 0, 49, model->idx, model->history_item); } - canvas_draw_icon(canvas, 4, 2 + i * FRAME_HEIGHT, ReceiverItemIcons[item_menu->type]); - elements_scrollable_text_line( - canvas, - 15, - 9 + i * FRAME_HEIGHT, - (scrollbar ? MAX_LEN_PX - 6 : MAX_LEN_PX), - str_buff, - scroll_counter, - (model->idx != idx)); - furi_string_reset(str_buff); - } - if(scrollbar) { - elements_scrollbar_pos(canvas, 128, 0, 49, model->idx, model->history_item); } furi_string_free(str_buff); @@ -464,29 +473,12 @@ bool subghz_view_receiver_input(InputEvent* event, void* context) { SubGhzViewReceiverModel * model, { if(model->history_item != 0) { - SubGhzReceiverMenuItemArray_it_t it; - // SubGhzReceiverMenuItem* target_item = - // SubGhzReceiverMenuItemArray_get(model->history->data, model->idx); - SubGhzReceiverMenuItemArray_it_last(it, model->history->data); - while(!SubGhzReceiverMenuItemArray_end_p(it)) { - SubGhzReceiverMenuItem* item = SubGhzReceiverMenuItemArray_ref(it); - - if(it->index == (size_t)(model->idx)) { - furi_string_free(item->item_str); - furi_string_free(item->time); - item->type = 0; - SubGhzReceiverMenuItemArray_remove(model->history->data, it); - } - - SubGhzReceiverMenuItemArray_previous(it); - } - // Callback subghz_receiver->callback( SubGhzCustomEventViewReceiverDeleteItem, subghz_receiver->context); } }, - true); + false); } else if(event->key == InputKeyOk && event->type == InputTypeShort) { with_view_model( subghz_receiver->view, @@ -537,6 +529,7 @@ void subghz_view_receiver_exit(void* context) { model->idx = 0; model->list_offset = 0; model->history_item = 0; + model->nodraw = false; }, false); furi_timer_stop(subghz_receiver->timer); @@ -571,6 +564,7 @@ SubGhzViewReceiver* subghz_view_receiver_alloc() { model->history_stat_str = furi_string_alloc(); model->progress_str = furi_string_alloc(); model->bar_show = SubGhzViewReceiverBarShowDefault; + model->nodraw = false; model->history = malloc(sizeof(SubGhzReceiverHistory)); SubGhzReceiverMenuItemArray_init(model->history->data); }, @@ -628,6 +622,23 @@ void subghz_view_receiver_delete_element_callback(SubGhzViewReceiver* subghz_rec subghz_receiver->view, SubGhzViewReceiverModel * model, { + SubGhzReceiverMenuItemArray_it_t it; + // SubGhzReceiverMenuItem* target_item = + // SubGhzReceiverMenuItemArray_get(model->history->data, model->idx); + SubGhzReceiverMenuItemArray_it_last(it, model->history->data); + while(!SubGhzReceiverMenuItemArray_end_p(it)) { + SubGhzReceiverMenuItem* item = SubGhzReceiverMenuItemArray_ref(it); + + if(it->index == (size_t)(model->idx)) { + furi_string_free(item->item_str); + furi_string_free(item->time); + item->type = 0; + SubGhzReceiverMenuItemArray_remove(model->history->data, it); + } + + SubGhzReceiverMenuItemArray_previous(it); + } + if(model->history_item == 5) { if(model->idx >= 2) { model->idx = model->history_item - 1; @@ -640,7 +651,20 @@ void subghz_view_receiver_delete_element_callback(SubGhzViewReceiver* subghz_rec } }, true); - furi_delay_ms(200); +} + +void subghz_view_receiver_enable_draw_callback(SubGhzViewReceiver* subghz_receiver) { + furi_assert(subghz_receiver); + + with_view_model( + subghz_receiver->view, SubGhzViewReceiverModel * model, { model->nodraw = false; }, true); +} + +void subghz_view_receiver_disable_draw_callback(SubGhzViewReceiver* subghz_receiver) { + furi_assert(subghz_receiver); + + with_view_model( + subghz_receiver->view, SubGhzViewReceiverModel * model, { model->nodraw = true; }, true); } void subghz_view_receiver_set_idx_menu(SubGhzViewReceiver* subghz_receiver, uint16_t idx) { diff --git a/applications/main/subghz/views/receiver.h b/applications/main/subghz/views/receiver.h index 54c1b3e7d..f239331d5 100644 --- a/applications/main/subghz/views/receiver.h +++ b/applications/main/subghz/views/receiver.h @@ -49,4 +49,8 @@ void subghz_view_receiver_set_idx_menu(SubGhzViewReceiver* subghz_receiver, uint void subghz_view_receiver_delete_element_callback(SubGhzViewReceiver* subghz_receiver); +void subghz_view_receiver_enable_draw_callback(SubGhzViewReceiver* subghz_receiver); + +void subghz_view_receiver_disable_draw_callback(SubGhzViewReceiver* subghz_receiver); + void subghz_view_receiver_exit(void* context); diff --git a/applications/main/subghz/views/transmitter.c b/applications/main/subghz/views/transmitter.c index 5ae1a6e2c..7ae865064 100644 --- a/applications/main/subghz/views/transmitter.c +++ b/applications/main/subghz/views/transmitter.c @@ -124,7 +124,7 @@ bool subghz_view_transmitter_input(InputEvent* event, void* context) { }, false); return false; - } + } // Finish "Back" key processing with_view_model( subghz_transmitter->view, @@ -136,124 +136,67 @@ bool subghz_view_transmitter_input(InputEvent* event, void* context) { }, true); - if(can_be_sent && event->key == InputKeyOk && event->type == InputTypePress) { - subghz_custom_btn_set(0); - with_view_model( - subghz_transmitter->view, - SubGhzViewTransmitterModel * model, - { - furi_string_reset(model->temp_button_id); - model->draw_temp_button = false; - }, - true); - subghz_transmitter->callback( - SubGhzCustomEventViewTransmitterSendStart, subghz_transmitter->context); - return true; - } else if(can_be_sent && event->key == InputKeyOk && event->type == InputTypeRelease) { - subghz_transmitter->callback( - SubGhzCustomEventViewTransmitterSendStop, subghz_transmitter->context); - return true; - } + if(can_be_sent) { + if(event->key == InputKeyOk && event->type == InputTypePress) { + subghz_custom_btn_set(SUBGHZ_CUSTOM_BTN_OK); + with_view_model( + subghz_transmitter->view, + SubGhzViewTransmitterModel * model, + { + furi_string_reset(model->temp_button_id); + model->draw_temp_button = false; + }, + true); + subghz_transmitter->callback( + SubGhzCustomEventViewTransmitterSendStart, subghz_transmitter->context); + return true; + } else if(event->key == InputKeyOk && event->type == InputTypeRelease) { + subghz_transmitter->callback( + SubGhzCustomEventViewTransmitterSendStop, subghz_transmitter->context); + return true; + } // Finish "OK" key processing - // Temp Buttons (UP) - if(can_be_sent && event->key == InputKeyUp && event->type == InputTypePress) { - subghz_custom_btn_set(1); - with_view_model( - subghz_transmitter->view, - SubGhzViewTransmitterModel * model, - { - furi_string_reset(model->temp_button_id); - if(subghz_custom_btn_get_original() != 0) { - if(subghz_custom_btn_get() == 1) { - furi_string_printf( - model->temp_button_id, "%01X", subghz_custom_btn_get_original()); - model->draw_temp_button = true; - } - } - }, - true); - subghz_transmitter->callback( - SubGhzCustomEventViewTransmitterSendStart, subghz_transmitter->context); - return true; - } else if(can_be_sent && event->key == InputKeyUp && event->type == InputTypeRelease) { - subghz_transmitter->callback( - SubGhzCustomEventViewTransmitterSendStop, subghz_transmitter->context); - return true; - } - // Down - if(can_be_sent && event->key == InputKeyDown && event->type == InputTypePress) { - subghz_custom_btn_set(2); - with_view_model( - subghz_transmitter->view, - SubGhzViewTransmitterModel * model, - { - furi_string_reset(model->temp_button_id); - if(subghz_custom_btn_get_original() != 0) { - if(subghz_custom_btn_get() == 2) { - furi_string_printf( - model->temp_button_id, "%01X", subghz_custom_btn_get_original()); - model->draw_temp_button = true; - } - } - }, - true); - subghz_transmitter->callback( - SubGhzCustomEventViewTransmitterSendStart, subghz_transmitter->context); - return true; - } else if(can_be_sent && event->key == InputKeyDown && event->type == InputTypeRelease) { - subghz_transmitter->callback( - SubGhzCustomEventViewTransmitterSendStop, subghz_transmitter->context); - return true; - } - // Left - if(can_be_sent && event->key == InputKeyLeft && event->type == InputTypePress) { - subghz_custom_btn_set(3); - with_view_model( - subghz_transmitter->view, - SubGhzViewTransmitterModel * model, - { - furi_string_reset(model->temp_button_id); - if(subghz_custom_btn_get_original() != 0) { - if(subghz_custom_btn_get() == 3) { - furi_string_printf( - model->temp_button_id, "%01X", subghz_custom_btn_get_original()); - model->draw_temp_button = true; - } - } - }, - true); - subghz_transmitter->callback( - SubGhzCustomEventViewTransmitterSendStart, subghz_transmitter->context); - return true; - } else if(can_be_sent && event->key == InputKeyLeft && event->type == InputTypeRelease) { - subghz_transmitter->callback( - SubGhzCustomEventViewTransmitterSendStop, subghz_transmitter->context); - return true; - } - // Right - if(can_be_sent && event->key == InputKeyRight && event->type == InputTypePress) { - subghz_custom_btn_set(4); - with_view_model( - subghz_transmitter->view, - SubGhzViewTransmitterModel * model, - { - furi_string_reset(model->temp_button_id); - if(subghz_custom_btn_get_original() != 0) { - if(subghz_custom_btn_get() == 4) { - furi_string_printf( - model->temp_button_id, "%01X", subghz_custom_btn_get_original()); - model->draw_temp_button = true; - } - } - }, - true); - subghz_transmitter->callback( - SubGhzCustomEventViewTransmitterSendStart, subghz_transmitter->context); - return true; - } else if(can_be_sent && event->key == InputKeyRight && event->type == InputTypeRelease) { - subghz_transmitter->callback( - SubGhzCustomEventViewTransmitterSendStop, subghz_transmitter->context); - return true; + if(subghz_custom_btn_is_allowed()) { + uint8_t temp_btn_id; + if(event->key == InputKeyUp) { + temp_btn_id = SUBGHZ_CUSTOM_BTN_UP; + } else if(event->key == InputKeyDown) { + temp_btn_id = SUBGHZ_CUSTOM_BTN_DOWN; + } else if(event->key == InputKeyLeft) { + temp_btn_id = SUBGHZ_CUSTOM_BTN_LEFT; + } else if(event->key == InputKeyRight) { + temp_btn_id = SUBGHZ_CUSTOM_BTN_RIGHT; + } else { + // Finish processing if the button is different + return true; + } + + if(event->type == InputTypePress) { + with_view_model( + subghz_transmitter->view, + SubGhzViewTransmitterModel * model, + { + furi_string_reset(model->temp_button_id); + if(subghz_custom_btn_get_original() != 0) { + if(subghz_custom_btn_set(temp_btn_id)) { + furi_string_printf( + model->temp_button_id, + "%01X", + subghz_custom_btn_get_original()); + model->draw_temp_button = true; + } + } + }, + true); + subghz_transmitter->callback( + SubGhzCustomEventViewTransmitterSendStart, subghz_transmitter->context); + return true; + } else if(event->type == InputTypeRelease) { + subghz_transmitter->callback( + SubGhzCustomEventViewTransmitterSendStop, subghz_transmitter->context); + return true; + } + } } return true; diff --git a/applications/main/subghz_remote/application.fam b/applications/main/subghz_remote/application.fam index e09f8500f..e785043e6 100644 --- a/applications/main/subghz_remote/application.fam +++ b/applications/main/subghz_remote/application.fam @@ -3,7 +3,10 @@ App( name="Sub-GHz Remote", apptype=FlipperAppType.APP, entry_point="subghz_remote_app", - cdefines=["APP_SUBGHZREMOTE"], + cdefines=[ + "APP_SUBGHZREMOTE", + "SUBREM_LIGHT", + ], requires=[ "gui", "dialogs", @@ -11,4 +14,4 @@ App( icon="A_SubGHzRemote_14", stack_size=4 * 1024, order=11, -) +) \ No newline at end of file diff --git a/applications/main/subghz_remote/helpers/subrem_custom_event.h b/applications/main/subghz_remote/helpers/subrem_custom_event.h new file mode 100644 index 000000000..46ab8ad54 --- /dev/null +++ b/applications/main/subghz_remote/helpers/subrem_custom_event.h @@ -0,0 +1,18 @@ +#pragma once + +typedef enum { + //SubmenuIndex + SubmenuIndexSubRemOpenMapFile, + SubmenuIndexSubRemRemoteView, + SubmenuIndexSubRemAbout, + + //SubRemCustomEvent + SubRemCustomEventViewRemoteStartUP = 100, + SubRemCustomEventViewRemoteStartDOWN, + SubRemCustomEventViewRemoteStartLEFT, + SubRemCustomEventViewRemoteStartRIGHT, + SubRemCustomEventViewRemoteStartOK, + SubRemCustomEventViewRemoteBack, + SubRemCustomEventViewRemoteStop, + SubRemCustomEventViewRemoteForcedStop, +} SubRemCustomEvent; \ No newline at end of file diff --git a/applications/main/subghz_remote/helpers/subrem_types.h b/applications/main/subghz_remote/helpers/subrem_types.h new file mode 100644 index 000000000..1b99aac6d --- /dev/null +++ b/applications/main/subghz_remote/helpers/subrem_types.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include + +// TODO: File version/type logic +// #define SUBREM_APP_APP_FILE_VERSION 1 +// #define SUBREM_APP_APP_FILE_TYPE "Flipper SubRem Map file" +#define SUBREM_APP_EXTENSION ".txt" + +typedef enum { + SubRemSubKeyNameUp = (0U), + SubRemSubKeyNameDown, + SubRemSubKeyNameLeft, + SubRemSubKeyNameRight, + SubRemSubKeyNameOk, + SubRemSubKeyNameMaxCount, +} SubRemSubKeyName; + +typedef enum { + SubRemViewSubmenu, + SubRemViewWidget, + SubRemViewPopup, + SubRemViewTextInput, + SubRemViewIDRemote, +} SubRemViewID; + +typedef enum { + SubRemLoadMapStateBack = 0, + SubRemLoadMapStateError, + SubRemLoadMapStateOK, +} SubRemLoadMapState; \ No newline at end of file diff --git a/applications/main/subghz_remote/scenes/subrem_scene.c b/applications/main/subghz_remote/scenes/subrem_scene.c new file mode 100644 index 000000000..c45285b96 --- /dev/null +++ b/applications/main/subghz_remote/scenes/subrem_scene.c @@ -0,0 +1,30 @@ +#include "../subghz_remote_app_i.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const subrem_scene_on_enter_handlers[])(void*) = { +#include "subrem_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 subrem_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = { +#include "subrem_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 subrem_scene_on_exit_handlers[])(void* context) = { +#include "subrem_scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers subrem_scene_handlers = { + .on_enter_handlers = subrem_scene_on_enter_handlers, + .on_event_handlers = subrem_scene_on_event_handlers, + .on_exit_handlers = subrem_scene_on_exit_handlers, + .scene_num = SubRemSceneNum, +}; diff --git a/applications/main/subghz_remote/scenes/subrem_scene.h b/applications/main/subghz_remote/scenes/subrem_scene.h new file mode 100644 index 000000000..5c01f8ca5 --- /dev/null +++ b/applications/main/subghz_remote/scenes/subrem_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) SubRemScene##id, +typedef enum { +#include "subrem_scene_config.h" + SubRemSceneNum, +} SubRemScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers subrem_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "subrem_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 "subrem_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 "subrem_scene_config.h" +#undef ADD_SCENE diff --git a/applications/main/subghz_remote/scenes/subrem_scene_config.h b/applications/main/subghz_remote/scenes/subrem_scene_config.h new file mode 100644 index 000000000..93d4de642 --- /dev/null +++ b/applications/main/subghz_remote/scenes/subrem_scene_config.h @@ -0,0 +1,3 @@ +ADD_SCENE(subrem, start, Start) +ADD_SCENE(subrem, openmapfile, OpenMapFile) +ADD_SCENE(subrem, remote, Remote) \ No newline at end of file diff --git a/applications/main/subghz_remote/scenes/subrem_scene_openmapfile.c b/applications/main/subghz_remote/scenes/subrem_scene_openmapfile.c new file mode 100644 index 000000000..3391845e1 --- /dev/null +++ b/applications/main/subghz_remote/scenes/subrem_scene_openmapfile.c @@ -0,0 +1,41 @@ +#include "../subghz_remote_app_i.h" + +void subrem_scene_openmapfile_on_enter(void* context) { + SubGhzRemoteApp* app = context; + SubRemLoadMapState load_state = subrem_load_from_file(app); + + if(load_state == SubRemLoadMapStateError) { +#ifdef SUBREM_LIGHT + dialog_message_show_storage_error(app->dialogs, "Can't load\nMap file"); +#else + DialogMessage* message = dialog_message_alloc(); + + dialog_message_set_header(message, "Map File Error", 64, 8, AlignCenter, AlignCenter); + dialog_message_set_text(message, "Can't load\nMap file", 64, 32, AlignCenter, AlignCenter); + dialog_message_set_buttons(message, "Back", NULL, NULL); + dialog_message_show(app->dialogs, message); + + dialog_message_free(message); +#endif + } + if(load_state == SubRemLoadMapStateOK) { + scene_manager_next_scene(app->scene_manager, SubRemSceneRemote); + } else { + // TODO: Map Preset Reset + if(!scene_manager_search_and_switch_to_previous_scene( + app->scene_manager, SubRemSceneStart)) { + scene_manager_stop(app->scene_manager); + view_dispatcher_stop(app->view_dispatcher); + } + } +} + +bool subrem_scene_openmapfile_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + return false; +} + +void subrem_scene_openmapfile_on_exit(void* context) { + UNUSED(context); +} diff --git a/applications/main/subghz_remote/scenes/subrem_scene_remote.c b/applications/main/subghz_remote/scenes/subrem_scene_remote.c new file mode 100644 index 000000000..c24583233 --- /dev/null +++ b/applications/main/subghz_remote/scenes/subrem_scene_remote.c @@ -0,0 +1,130 @@ +#include "../subghz_remote_app_i.h" +#include "../views/remote.h" + +#include + +#define TAG "SubRemScenRemote" + +void subrem_scene_remote_callback(SubRemCustomEvent event, void* context) { + furi_assert(context); + SubGhzRemoteApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, event); +} + +void subrem_scene_remote_raw_callback_end_tx(void* context) { + furi_assert(context); + SubGhzRemoteApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, SubRemCustomEventViewRemoteForcedStop); +} + +static uint8_t subrem_scene_remote_event_to_index(SubRemCustomEvent event_id) { + uint8_t ret = 0; + + if(event_id == SubRemCustomEventViewRemoteStartUP) { + ret = SubRemSubKeyNameUp; + } else if(event_id == SubRemCustomEventViewRemoteStartDOWN) { + ret = SubRemSubKeyNameDown; + } else if(event_id == SubRemCustomEventViewRemoteStartLEFT) { + ret = SubRemSubKeyNameLeft; + } else if(event_id == SubRemCustomEventViewRemoteStartRIGHT) { + ret = SubRemSubKeyNameRight; + } else if(event_id == SubRemCustomEventViewRemoteStartOK) { + ret = SubRemSubKeyNameOk; + } + + return ret; +} + +static bool subrem_scene_remote_update_data_show(void* context) { + SubGhzRemoteApp* app = context; + bool ret = false; + + subrem_view_remote_add_data_to_show( + app->subrem_remote_view, + furi_string_get_cstr(app->subs_preset[0]->label), + furi_string_get_cstr(app->subs_preset[1]->label), + furi_string_get_cstr(app->subs_preset[2]->label), + furi_string_get_cstr(app->subs_preset[3]->label), + furi_string_get_cstr(app->subs_preset[4]->label)); + + return ret; +} + +void subrem_scene_remote_on_enter(void* context) { + SubGhzRemoteApp* app = context; + + subrem_scene_remote_update_data_show(app); + + subrem_view_remote_set_callback(app->subrem_remote_view, subrem_scene_remote_callback, app); + + view_dispatcher_switch_to_view(app->view_dispatcher, SubRemViewIDRemote); +} + +bool subrem_scene_remote_on_event(void* context, SceneManagerEvent event) { + SubGhzRemoteApp* app = context; + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubRemCustomEventViewRemoteBack) { + if(!scene_manager_search_and_switch_to_previous_scene( + app->scene_manager, SubRemSceneOpenMapFile)) { + if(!scene_manager_search_and_switch_to_previous_scene( + app->scene_manager, SubRemSceneStart)) { + scene_manager_stop(app->scene_manager); + view_dispatcher_stop(app->view_dispatcher); + } + } + return true; + } else if( + event.event == SubRemCustomEventViewRemoteStartUP || + event.event == SubRemCustomEventViewRemoteStartDOWN || + event.event == SubRemCustomEventViewRemoteStartLEFT || + event.event == SubRemCustomEventViewRemoteStartRIGHT || + event.event == SubRemCustomEventViewRemoteStartOK) { + // Start sending sub + subrem_tx_stop_sub(app, true); + app->chusen_sub = subrem_scene_remote_event_to_index(event.event); + subrem_view_remote_set_state(app->subrem_remote_view, SubRemViewRemoteStateLoading); + if(subrem_tx_start_sub( + app, + app->subs_preset[app->chusen_sub], + subrem_scene_remote_raw_callback_end_tx)) { + subrem_view_remote_set_presed_btn(app->subrem_remote_view, app->chusen_sub); + subrem_view_remote_set_state( + app->subrem_remote_view, SubRemViewRemoteStateSending); + notification_message(app->notifications, &sequence_blink_start_magenta); + } else { + subrem_view_remote_set_state(app->subrem_remote_view, SubRemViewRemoteStateIdle); + notification_message(app->notifications, &sequence_blink_stop); + } + return true; + } else if(event.event == SubRemCustomEventViewRemoteForcedStop) { + subrem_tx_stop_sub(app, true); + subrem_view_remote_set_presed_btn(app->subrem_remote_view, 0); + subrem_view_remote_set_state(app->subrem_remote_view, SubRemViewRemoteStateIdle); + + notification_message(app->notifications, &sequence_blink_stop); + return true; + } else if(event.event == SubRemCustomEventViewRemoteStop) { + if(subrem_tx_stop_sub(app, false)) { + subrem_view_remote_set_presed_btn(app->subrem_remote_view, 0); + subrem_view_remote_set_state(app->subrem_remote_view, SubRemViewRemoteStateIdle); + + notification_message(app->notifications, &sequence_blink_stop); + } + return true; + } + } + // } else if(event.type == SceneManagerEventTypeTick) { + // } + return false; +} + +void subrem_scene_remote_on_exit(void* context) { + SubGhzRemoteApp* app = context; + + subrem_tx_stop_sub(app, true); + + subrem_view_remote_set_presed_btn(app->subrem_remote_view, 0); + subrem_view_remote_set_state(app->subrem_remote_view, SubRemViewRemoteStateIdle); + + notification_message(app->notifications, &sequence_blink_stop); +} diff --git a/applications/main/subghz_remote/scenes/subrem_scene_start.c b/applications/main/subghz_remote/scenes/subrem_scene_start.c new file mode 100644 index 000000000..962eda54c --- /dev/null +++ b/applications/main/subghz_remote/scenes/subrem_scene_start.c @@ -0,0 +1,74 @@ +#include "../subghz_remote_app_i.h" +#include "../helpers/subrem_custom_event.h" + +void subrem_scene_start_submenu_callback(void* context, uint32_t index) { + furi_assert(context); + SubGhzRemoteApp* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, index); +} + +void subrem_scene_start_on_enter(void* context) { + furi_assert(context); + + SubGhzRemoteApp* app = context; + Submenu* submenu = app->submenu; + submenu_add_item( + submenu, + "Open Map File", + SubmenuIndexSubRemOpenMapFile, + subrem_scene_start_submenu_callback, + app); +#if FURI_DEBUG + submenu_add_item( + submenu, + "Remote_Debug", + SubmenuIndexSubRemRemoteView, + subrem_scene_start_submenu_callback, + app); +#endif + // submenu_add_item( + // submenu, + // "About", + // SubmenuIndexSubGhzRemoteAbout, + // subrem_scene_start_submenu_callback, + // app); + + submenu_set_selected_item( + submenu, scene_manager_get_scene_state(app->scene_manager, SubRemSceneStart)); + + view_dispatcher_switch_to_view(app->view_dispatcher, SubRemViewSubmenu); +} + +bool subrem_scene_start_on_event(void* context, SceneManagerEvent event) { + furi_assert(context); + + SubGhzRemoteApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexSubRemOpenMapFile) { + scene_manager_next_scene(app->scene_manager, SubRemSceneOpenMapFile); + consumed = true; + } + // } else if(event.event == SubmenuIndexSubRemAbout) { + // scene_manager_next_scene(app->scene_manager, SubRemSceneAbout); + // consumed = true; + // } +#if FURI_DEBUG + else if(event.event == SubmenuIndexSubRemRemoteView) { + scene_manager_next_scene(app->scene_manager, SubRemSceneRemote); + consumed = true; + } +#endif + } + + return consumed; +} + +void subrem_scene_start_on_exit(void* context) { + furi_assert(context); + + SubGhzRemoteApp* app = context; + submenu_reset(app->submenu); +} diff --git a/applications/main/subghz_remote/subghz_remote_app.c b/applications/main/subghz_remote/subghz_remote_app.c index d215a1d0e..0b3f645ab 100644 --- a/applications/main/subghz_remote/subghz_remote_app.c +++ b/applications/main/subghz_remote/subghz_remote_app.c @@ -1,764 +1,27 @@ -#include +#include "subghz_remote_app_i.h" -#include -#include -#include -#include - -#include -#include - -#include - -#include -#include -#include - -#include #include -#include -#include -#include -#include - -#define SUBREMOTEMAP_FOLDER "/ext/subghz_remote" -#define SUBREMOTEMAP_EXTENSION ".txt" - -#define TAG "SubGHzRemote" - -typedef struct { - uint32_t frequency; - FuriString* name; - - FuriString* protocol; - uint32_t repeat; - - uint8_t* data; - size_t data_size; - - SubGhzProtocolDecoderBase* decoder; -} SubRemotePreset; - -typedef struct { - FuriMutex* model_mutex; - - FuriMessageQueue* input_queue; - - ViewPort* view_port; - Gui* gui; - - SubGhzSetting* setting; - SubGhzEnvironment* environment; - SubGhzReceiver* subghz_receiver; - NotificationApp* notification; - SubRemotePreset* txpreset; - - FuriString* up_file; - FuriString* down_file; - FuriString* left_file; - FuriString* right_file; - FuriString* ok_file; - - FuriString* file_path; - - char* up_label; - char* down_label; - char* left_label; - char* right_label; - char* ok_label; - - int up_enabled; - int down_enabled; - int left_enabled; - int right_enabled; - int ok_enabled; - - char* send_status; - int send_status_c; - int processing; - - SubGhzTransmitter* tx_transmitter; - FlipperFormat* tx_fff_data; - const char* tx_file_path; - int button; - - int file_result; - bool tx_not_allowed; - - FuriString* signal; -} SubGHzRemote; - -SubRemotePreset* subghz_remote_preset_alloc(void) { - SubRemotePreset* preset = malloc(sizeof(SubRemotePreset)); - preset->name = furi_string_alloc(); - preset->protocol = furi_string_alloc(); - preset->repeat = 200; - return preset; +static bool subghz_remote_app_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + SubGhzRemoteApp* app = context; + return scene_manager_handle_custom_event(app->scene_manager, event); } -void subghz_remote_preset_free(SubRemotePreset* preset) { - furi_string_free(preset->name); - furi_string_free(preset->protocol); - free(preset); +static bool subghz_remote_app_back_event_callback(void* context) { + furi_assert(context); + SubGhzRemoteApp* app = context; + return scene_manager_handle_back_event(app->scene_manager); } -static char* char_to_str(char* str, int i) { - char* converted = malloc(sizeof(char) * i + 1); - memcpy(converted, str, i); - - converted[i] = '\0'; - - return converted; +static void subghz_remote_app_tick_event_callback(void* context) { + furi_assert(context); + SubGhzRemoteApp* app = context; + scene_manager_handle_tick_event(app->scene_manager); } -//get filename without path -static char* extract_filename(const char* name, int len) { - FuriString* tmp; - tmp = furi_string_alloc(); - - //remove path - path_extract_filename_no_ext(name, tmp); - - return char_to_str((char*)furi_string_get_cstr(tmp), len); -} - -static void cfg_read_file_path( - FlipperFormat* fff_file, - FuriString* text_file_path, - char** text_file_label, - const char* read_key, - int* is_enabled) { - if(!flipper_format_read_string(fff_file, read_key, text_file_path)) { - FURI_LOG_W(TAG, "Could not read %s string", read_key); - *text_file_label = "N/A"; - *is_enabled = 0; - } else { - *text_file_label = extract_filename(furi_string_get_cstr(text_file_path), 16); - //FURI_LOG_D(TAG, "%s file: %s", read_key, furi_string_get_cstr(text_file_path)); - *is_enabled = 1; - } - flipper_format_rewind(fff_file); -} - -static void cfg_read_file_label( - FlipperFormat* fff_file, - char** text_file_label, - const char* read_key, - bool is_enabled) { - FuriString* temp_label = furi_string_alloc(); - - if(!flipper_format_read_string(fff_file, read_key, temp_label)) { - FURI_LOG_W(TAG, "Could not read %s string", read_key); - } else { - if(is_enabled == 1) { - *text_file_label = char_to_str((char*)furi_string_get_cstr(temp_label), 16); - } - //FURI_LOG_D(TAG, "%s label: %s", read_key, *text_file_label); - } - flipper_format_rewind(fff_file); - furi_string_free(temp_label); -} - -/* - * check that map file exists - * assign variables to values within map file - * set missing filenames to N/A - * set filename as label if label definitions are missing - * set error flag if all buttons are N/A - * set error flag if missing map file - */ - -void subghz_remote_cfg_set_check(SubGHzRemote* app, FuriString* file_name) { - Storage* storage = furi_record_open(RECORD_STORAGE); - FlipperFormat* fff_data_file = flipper_format_file_alloc(storage); - - app->file_result = 1; - - app->up_enabled = 0; - app->down_enabled = 0; - app->left_enabled = 0; - app->right_enabled = 0; - app->ok_enabled = 0; - - //check that map file exists - if(!flipper_format_file_open_existing(fff_data_file, furi_string_get_cstr(file_name))) { - FURI_LOG_E(TAG, "Could not open MAP file %s", furi_string_get_cstr(file_name)); - } else { - //Filename Assignment/Check Start - - //assign variables to values within map file - //set missing filenames to N/A - cfg_read_file_path(fff_data_file, app->up_file, &app->up_label, "UP", &app->up_enabled); - - cfg_read_file_path( - fff_data_file, app->down_file, &app->down_label, "DOWN", &app->down_enabled); - - cfg_read_file_path( - fff_data_file, app->left_file, &app->left_label, "LEFT", &app->left_enabled); - - cfg_read_file_path( - fff_data_file, app->right_file, &app->right_label, "RIGHT", &app->right_enabled); - - cfg_read_file_path(fff_data_file, app->ok_file, &app->ok_label, "OK", &app->ok_enabled); - - //File definitions are done. - //File checks will follow after label assignment in order to close the universal_rf_map file without the need to reopen it again. - - //Label Assignment/Check Start - - cfg_read_file_label(fff_data_file, &app->up_label, "ULABEL", app->up_enabled); - cfg_read_file_label(fff_data_file, &app->down_label, "DLABEL", app->down_enabled); - cfg_read_file_label(fff_data_file, &app->left_label, "LLABEL", app->left_enabled); - cfg_read_file_label(fff_data_file, &app->right_label, "RLABEL", app->right_enabled); - cfg_read_file_label(fff_data_file, &app->ok_label, "OKLABEL", app->ok_enabled); - } - - flipper_format_file_close(fff_data_file); - flipper_format_free(fff_data_file); - - //File Existence Check - //Check each file definition if not already set to "N/A" - - //determine if files exist. - //determine whether or not to continue to launch app with missing variables - //if 5 files are missing, throw error - - //if button is still enabled, check that file exists - if(app->up_enabled == 1) { - furi_string_set(file_name, app->up_file); - fff_data_file = flipper_format_file_alloc(storage); - - if(!flipper_format_file_open_existing(fff_data_file, furi_string_get_cstr(file_name))) { - FURI_LOG_W(TAG, "Could not open UP file %s", furi_string_get_cstr(file_name)); - - //disable button, and set label to "N/A" - app->up_enabled = 0; - app->up_label = "N/A"; - } - - //close the file - flipper_format_file_close(fff_data_file); - flipper_format_free(fff_data_file); - } - - if(app->down_enabled == 1) { - furi_string_set(file_name, app->down_file); - fff_data_file = flipper_format_file_alloc(storage); - - if(!flipper_format_file_open_existing(fff_data_file, furi_string_get_cstr(file_name))) { - FURI_LOG_W(TAG, "Could not open DOWN file %s", furi_string_get_cstr(file_name)); - - app->down_enabled = 0; - app->down_label = "N/A"; - } - - flipper_format_file_close(fff_data_file); - flipper_format_free(fff_data_file); - } - - if(app->left_enabled == 1) { - furi_string_set(file_name, app->left_file); - fff_data_file = flipper_format_file_alloc(storage); - - if(!flipper_format_file_open_existing(fff_data_file, furi_string_get_cstr(file_name))) { - FURI_LOG_W(TAG, "Could not open LEFT file %s", furi_string_get_cstr(file_name)); - - app->left_enabled = 0; - app->left_label = "N/A"; - } - - flipper_format_file_close(fff_data_file); - flipper_format_free(fff_data_file); - } - - if(app->right_enabled == 1) { - furi_string_set(file_name, app->right_file); - fff_data_file = flipper_format_file_alloc(storage); - - if(!flipper_format_file_open_existing(fff_data_file, furi_string_get_cstr(file_name))) { - FURI_LOG_W(TAG, "Could not open RIGHT file %s", furi_string_get_cstr(file_name)); - - app->right_enabled = 0; - app->right_label = "N/A"; - } - - flipper_format_file_close(fff_data_file); - flipper_format_free(fff_data_file); - } - - if(app->ok_enabled == 1) { - furi_string_set(file_name, app->ok_file); - fff_data_file = flipper_format_file_alloc(storage); - - if(!flipper_format_file_open_existing(fff_data_file, furi_string_get_cstr(file_name))) { - FURI_LOG_W(TAG, "Could not open OK file %s", furi_string_get_cstr(file_name)); - - app->ok_enabled = 0; - app->ok_label = "N/A"; - } - - flipper_format_file_close(fff_data_file); - flipper_format_free(fff_data_file); - } - - furi_record_close(RECORD_STORAGE); - - if(app->up_enabled == 0 && app->down_enabled == 0 && app->left_enabled == 0 && - app->right_enabled == 0 && app->ok_enabled == 0) { - app->file_result = 1; - } else { - app->file_result = 2; - } -} - -static void subghz_remote_end_send(SubGHzRemote* app) { - app->processing = 0; -} - -bool subghz_remote_set_preset(SubRemotePreset* p, const char* preset) { - if(!strcmp(preset, "FuriHalSubGhzPresetOok270Async")) { - furi_string_set(p->name, "AM270"); - } else if(!strcmp(preset, "FuriHalSubGhzPresetOok650Async")) { - furi_string_set(p->name, "AM650"); - } else if(!strcmp(preset, "FuriHalSubGhzPreset2FSKDev238Async")) { - furi_string_set(p->name, "FM238"); - } else if(!strcmp(preset, "FuriHalSubGhzPreset2FSKDev476Async")) { - furi_string_set(p->name, "FM476"); - } else if(!strcmp(preset, "FuriHalSubGhzPresetCustom")) { - FURI_LOG_E(TAG, "Custom preset unsupported now"); - return false; - // furi_string_set(p->name, "CUSTOM"); - } else { - FURI_LOG_E(TAG, "Unsupported preset"); - return false; - } - return true; -} - -bool subghz_remote_key_load( - SubRemotePreset* preset, - FlipperFormat* fff_file, - FlipperFormat* fff_data, - SubGhzSetting* setting, - SubGhzReceiver* receiver, - const char* path) { - // - if(!flipper_format_rewind(fff_file)) { - FURI_LOG_E(TAG, "Rewind error"); - return false; - } - - FuriString* temp_str; - temp_str = furi_string_alloc(); - - bool res = false; - - subghz_custom_btn_set(0); - keeloq_reset_original_btn(); - subghz_custom_btns_reset(); - - do { - // load frequency from file - if(!flipper_format_read_uint32(fff_file, "Frequency", &preset->frequency, 1)) { - FURI_LOG_W(TAG, "Cannot read frequency. Defaulting to 433.92 MHz"); - preset->frequency = 433920000; - } - - // load preset from file - if(!flipper_format_read_string(fff_file, "Preset", temp_str)) { - FURI_LOG_W(TAG, "Could not read Preset. Defaulting to Ook650Async"); - furi_string_set(temp_str, "FuriHalSubGhzPresetOok650Async"); - } - if(!subghz_remote_set_preset(preset, furi_string_get_cstr(temp_str))) { - FURI_LOG_E(TAG, "Could not set preset"); - break; - } - if(!strcmp(furi_string_get_cstr(temp_str), "FuriHalSubGhzPresetCustom")) { - // TODO: check if preset is custom - FURI_LOG_E(TAG, "Could not use custom preset"); - break; - } - size_t preset_index = - subghz_setting_get_inx_preset_by_name(setting, furi_string_get_cstr(preset->name)); - preset->data = subghz_setting_get_preset_data(setting, preset_index); - preset->data_size = subghz_setting_get_preset_data_size(setting, preset_index); - - // load protocol from file - if(!flipper_format_read_string(fff_file, "Protocol", preset->protocol)) { - FURI_LOG_E(TAG, "Could not read Protocol."); - break; - } - if(!flipper_format_rewind(fff_data)) { - FURI_LOG_E(TAG, "Rewind error"); - return false; - } - - if(!furi_string_cmp_str(preset->protocol, "RAW")) { - subghz_protocol_raw_gen_fff_data(fff_data, path); - // repeat - if(!flipper_format_insert_or_update_uint32(fff_data, "Repeat", &preset->repeat, 1)) { - FURI_LOG_E(TAG, "Unable to insert or update Repeat"); - break; - } - - } else { - stream_copy_full( - flipper_format_get_raw_stream(fff_file), flipper_format_get_raw_stream(fff_data)); - // repeat - if(!flipper_format_insert_or_update_uint32(fff_data, "Repeat", &preset->repeat, 1)) { - FURI_LOG_E(TAG, "Unable to insert or update Repeat"); - break; - } - } - - if(!flipper_format_rewind(fff_file)) { - FURI_LOG_E(TAG, "Rewind error"); - return false; - } - - preset->decoder = subghz_receiver_search_decoder_base_by_name( - receiver, furi_string_get_cstr(preset->protocol)); - if(preset->decoder) { - SubGhzProtocolStatus status = - subghz_protocol_decoder_base_deserialize(preset->decoder, fff_data); - if(status != SubGhzProtocolStatusOk) { - FURI_LOG_E(TAG, "Protocol deserialize failed, status = %d", status); - break; - } - } else { - FURI_LOG_E(TAG, "Protocol %s not found", furi_string_get_cstr(temp_str)); - } - - res = true; - } while(0); - - furi_string_free(temp_str); - - return res; -} - -// method modified from subghz_i.c -// https://github.com/flipperdevices/flipperzero-firmware/blob/b0daa601ad5b87427a45f9089c8b403a01f72c2a/applications/subghz/subghz_i.c#L417-L456 -bool subghz_remote_save_protocol_to_file(FlipperFormat* fff_file, const char* dev_file_name) { - furi_assert(fff_file); - furi_assert(dev_file_name); - - Storage* storage = furi_record_open(RECORD_STORAGE); - Stream* flipper_format_stream = flipper_format_get_raw_stream(fff_file); - - bool saved = false; - FuriString* file_dir; - file_dir = furi_string_alloc(); - - path_extract_dirname(dev_file_name, file_dir); - do { - if(!storage_simply_mkdir(storage, furi_string_get_cstr(file_dir))) { - FURI_LOG_E(TAG, "(save) Cannot mkdir"); - break; - } - - if(!storage_simply_remove(storage, dev_file_name)) { - FURI_LOG_E(TAG, "(save) Cannot remove"); - break; - } - - stream_seek(flipper_format_stream, 0, StreamOffsetFromStart); - stream_save_to_file(flipper_format_stream, storage, dev_file_name, FSOM_CREATE_ALWAYS); - - saved = true; - //FURI_LOG_D(TAG, "(save) OK Save"); - } while(0); - furi_string_free(file_dir); - furi_record_close(RECORD_STORAGE); - return saved; -} - -void subghz_remote_tx_stop(SubGHzRemote* app) { - if(app->processing == 0) { - return; - } - - if(!furi_string_cmp_str(app->txpreset->protocol, "RAW")) { - while(!furi_hal_subghz_is_async_tx_complete()) { - furi_delay_ms(15); - } - } - - //Stop TX - furi_hal_subghz_stop_async_tx(); - //FURI_LOG_I(TAG, "TX Done!"); - subghz_transmitter_stop(app->tx_transmitter); - - //FURI_LOG_D(TAG, "Checking if protocol is dynamic"); - const SubGhzProtocolRegistry* protocol_registry_items = - subghz_environment_get_protocol_registry(app->environment); - const SubGhzProtocol* proto = subghz_protocol_registry_get_by_name( - protocol_registry_items, furi_string_get_cstr(app->txpreset->protocol)); - //FURI_LOG_D(TAG, "Protocol-TYPE %d", proto->type); - - if(proto && proto->type == SubGhzProtocolTypeDynamic) { - //FURI_LOG_D(TAG, "Protocol is dynamic. Saving key"); - // Remove repeat if it was present - flipper_format_delete_key(app->tx_fff_data, "Repeat"); - - subghz_remote_save_protocol_to_file(app->tx_fff_data, app->tx_file_path); - - keeloq_reset_mfname(); - keeloq_reset_kl_type(); - keeloq_reset_original_btn(); - subghz_custom_btns_reset(); - star_line_reset_mfname(); - star_line_reset_kl_type(); - } - - subghz_transmitter_free(app->tx_transmitter); - furi_hal_subghz_idle(); - - notification_message(app->notification, &sequence_blink_stop); - - subghz_remote_preset_free(app->txpreset); - flipper_format_free(app->tx_fff_data); - subghz_remote_end_send(app); -} - -static bool subghz_remote_send_sub(SubGHzRemote* app, FlipperFormat* fff_data) { - // - bool res = false; - do { - if(!furi_hal_subghz_is_tx_allowed(app->txpreset->frequency)) { - FURI_LOG_E( - TAG, - "In your settings, only reception on this frequency (%lu) is allowed,\r\n" - "the actual operation of the subghz remote app is not possible\r\n ", - app->txpreset->frequency); - app->tx_not_allowed = true; - subghz_remote_end_send(app); - break; - } else { - app->tx_not_allowed = false; - } - - app->tx_transmitter = subghz_transmitter_alloc_init( - app->environment, furi_string_get_cstr(app->txpreset->protocol)); - if(!app->tx_transmitter) { - break; - } - - if(subghz_transmitter_deserialize(app->tx_transmitter, fff_data) != - SubGhzProtocolStatusOk) { - FURI_LOG_E(TAG, "Deserialize error!"); - break; - } - - furi_hal_subghz_reset(); - furi_hal_subghz_idle(); - furi_hal_subghz_load_custom_preset(app->txpreset->data); - furi_hal_gpio_init(furi_hal_subghz.cc1101_g0_pin, GpioModeInput, GpioPullNo, GpioSpeedLow); - - furi_hal_subghz_idle(); - - furi_hal_subghz_set_frequency_and_path(app->txpreset->frequency); - furi_hal_gpio_write(furi_hal_subghz.cc1101_g0_pin, false); - furi_hal_gpio_init( - furi_hal_subghz.cc1101_g0_pin, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); - - if(!furi_hal_subghz_tx()) { - FURI_LOG_E(TAG, "Sending not allowed"); - break; - } - - //FURI_LOG_I(TAG, "Sending..."); - notification_message(app->notification, &sequence_blink_start_magenta); - - furi_hal_subghz_start_async_tx(subghz_transmitter_yield, app->tx_transmitter); - - res = true; - } while(0); - - return res; -} - -static void subghz_remote_send_signal(SubGHzRemote* app, Storage* storage, const char* path) { - //FURI_LOG_D(TAG, "Sending: %s", path); - - app->tx_file_path = path; - - app->tx_fff_data = flipper_format_string_alloc(); - - app->txpreset = subghz_remote_preset_alloc(); - - // load settings/stream from .sub file - FlipperFormat* fff_file = flipper_format_file_alloc(storage); - bool open_ok = false; - do { - if(!flipper_format_file_open_existing(fff_file, path)) { - FURI_LOG_E(TAG, "Could not open file %s", path); - break; - } - if(!subghz_remote_key_load( - app->txpreset, - fff_file, - app->tx_fff_data, - app->setting, - app->subghz_receiver, - path)) { - FURI_LOG_E(TAG, "Could not load key"); - break; - } - open_ok = true; - } while(0); - flipper_format_free(fff_file); - if(!open_ok) { - FURI_LOG_E(TAG, "Could not load file!"); - return; - } - - subghz_remote_send_sub(app, app->tx_fff_data); -} - -static void subghz_remote_process_signal(SubGHzRemote* app, FuriString* signal) { - view_port_update(app->view_port); - - //FURI_LOG_D(TAG, "signal = %s", furi_string_get_cstr(signal)); - - if(strlen(furi_string_get_cstr(signal)) > 12) { - Storage* storage = furi_record_open(RECORD_STORAGE); - subghz_remote_send_signal(app, storage, furi_string_get_cstr(signal)); - furi_record_close(RECORD_STORAGE); - } else if(strlen(furi_string_get_cstr(signal)) < 10) { - subghz_remote_end_send(app); - } -} - -static void render_callback(Canvas* canvas, void* ctx) { - SubGHzRemote* app = ctx; - furi_check(furi_mutex_acquire(app->model_mutex, FuriWaitForever) == FuriStatusOk); - - //setup different canvas settings - if(app->file_result == 1) { - //if map has no valid filenames defined - canvas_clear(canvas); - canvas_set_font(canvas, FontPrimary); - canvas_draw_str_aligned(canvas, 62, 5, AlignCenter, AlignTop, "Config is incorrect."); - canvas_set_font(canvas, FontSecondary); - canvas_draw_str_aligned(canvas, 62, 30, AlignCenter, AlignTop, "Please configure map."); - canvas_draw_str_aligned(canvas, 62, 60, AlignCenter, AlignBottom, "Press Back to Exit."); - } else if(app->file_result == 3) { - //if map has no valid filenames defined - canvas_clear(canvas); - canvas_set_font(canvas, FontPrimary); - canvas_draw_str_aligned(canvas, 62, 5, AlignCenter, AlignTop, "Checking config."); - canvas_set_font(canvas, FontSecondary); - canvas_draw_str_aligned(canvas, 62, 30, AlignCenter, AlignTop, "If app is stuck..."); - canvas_draw_str_aligned(canvas, 62, 60, AlignCenter, AlignBottom, "Press Back to Exit."); - } else if(app->tx_not_allowed) { - canvas_clear(canvas); - canvas_set_font(canvas, FontPrimary); - canvas_draw_str_aligned(canvas, 62, 5, AlignCenter, AlignTop, "Transmission is blocked."); - canvas_set_font(canvas, FontSecondary); - canvas_draw_str_aligned(canvas, 62, 15, AlignCenter, AlignTop, "Frequency is outside of"); - canvas_draw_str_aligned(canvas, 62, 25, AlignCenter, AlignTop, "default range."); - canvas_draw_str_aligned(canvas, 62, 35, AlignCenter, AlignTop, "Check docs."); - canvas_draw_str_aligned(canvas, 62, 60, AlignCenter, AlignBottom, "Press Back to Exit."); - } else { - //map found, draw all the things - canvas_clear(canvas); - - //canvas_set_font(canvas, FontPrimary); - //canvas_draw_str(canvas, 0, 10, "U: "); - //canvas_draw_str(canvas, 0, 20, "L: "); - //canvas_draw_str(canvas, 0, 30, "R: "); - //canvas_draw_str(canvas, 0, 40, "D: "); - //canvas_draw_str(canvas, 0, 50, "Ok: "); - - //PNGs are located in assets/icons/SubGHzRemote before compilation - - //Icons for Labels - //canvas_draw_icon(canvas, 0, 0, &I_SubGHzRemote_LeftAlignedButtons_9x64); - canvas_draw_icon(canvas, 1, 5, &I_ButtonUp_7x4); - canvas_draw_icon(canvas, 1, 15, &I_ButtonDown_7x4); - canvas_draw_icon(canvas, 2, 23, &I_ButtonLeft_4x7); - canvas_draw_icon(canvas, 2, 33, &I_ButtonRight_4x7); - canvas_draw_icon(canvas, 0, 42, &I_Ok_btn_9x9); - canvas_draw_icon(canvas, 0, 53, &I_back_10px); - - //Labels - canvas_set_font(canvas, FontSecondary); - canvas_draw_str(canvas, 10, 10, app->up_label); - canvas_draw_str(canvas, 10, 20, app->down_label); - canvas_draw_str(canvas, 10, 30, app->left_label); - canvas_draw_str(canvas, 10, 40, app->right_label); - canvas_draw_str(canvas, 10, 50, app->ok_label); - - canvas_draw_str_aligned(canvas, 11, 62, AlignLeft, AlignBottom, "Press=Exit."); - - //Status text and indicator - canvas_draw_str_aligned(canvas, 126, 10, AlignRight, AlignBottom, app->send_status); - - switch(app->send_status_c) { - case 0: - canvas_draw_icon(canvas, 113, 15, &I_Pin_cell_13x13); - break; - case 1: - canvas_draw_icon(canvas, 113, 15, &I_Pin_cell_13x13); - canvas_draw_icon(canvas, 116, 17, &I_Pin_arrow_up_7x9); - break; - case 2: - canvas_draw_icon(canvas, 113, 15, &I_Pin_cell_13x13); - canvas_draw_icon_ex(canvas, 116, 17, &I_Pin_arrow_up_7x9, IconRotation180); - break; - case 3: - canvas_draw_icon(canvas, 113, 15, &I_Pin_cell_13x13); - canvas_draw_icon_ex(canvas, 115, 18, &I_Pin_arrow_up_7x9, IconRotation90); - break; - case 4: - canvas_draw_icon(canvas, 113, 15, &I_Pin_cell_13x13); - canvas_draw_icon_ex(canvas, 115, 18, &I_Pin_arrow_up_7x9, IconRotation270); - break; - case 5: - canvas_draw_icon(canvas, 113, 15, &I_Pin_cell_13x13); - canvas_draw_icon(canvas, 116, 18, &I_Pin_star_7x7); - break; - } - - //Repeat indicator - //canvas_draw_str_aligned(canvas, 125, 40, AlignRight, AlignBottom, "Repeat:"); - //canvas_draw_icon(canvas, 115, 39, &I_SubGHzRemote_Repeat_12x14); - //canvas_draw_str_aligned(canvas, 125, 62, AlignRight, AlignBottom, int_to_char(app->repeat)); - } - - furi_mutex_release(app->model_mutex); -} - -static void input_callback(InputEvent* input_event, void* ctx) { - SubGHzRemote* app = ctx; - furi_message_queue_put(app->input_queue, input_event, 0); -} - -void subghz_remote_subghz_alloc(SubGHzRemote* app) { - // load subghz presets - app->setting = subghz_setting_alloc(); - subghz_setting_load(app->setting, EXT_PATH("subghz/assets/setting_user")); - - // load mfcodes - app->environment = subghz_environment_alloc(); - subghz_environment_load_keystore(app->environment, EXT_PATH("subghz/assets/keeloq_mfcodes")); - subghz_environment_load_keystore( - app->environment, EXT_PATH("subghz/assets/keeloq_mfcodes_user")); - subghz_environment_set_came_atomo_rainbow_table_file_name( - app->environment, EXT_PATH("subghz/assets/came_atomo")); - subghz_environment_set_nice_flor_s_rainbow_table_file_name( - app->environment, EXT_PATH("subghz/assets/nice_flor_s")); - subghz_environment_set_alutech_at_4n_rainbow_table_file_name( - app->environment, EXT_PATH("subghz/assets/alutech_at_4n")); - subghz_environment_set_protocol_registry(app->environment, (void*)&subghz_protocol_registry); - - app->subghz_receiver = subghz_receiver_alloc_init(app->environment); -} - -SubGHzRemote* subghz_remote_alloc(void) { - SubGHzRemote* app = malloc(sizeof(SubGHzRemote)); +SubGhzRemoteApp* subghz_remote_app_alloc() { + SubGhzRemoteApp* app = malloc(sizeof(SubGhzRemoteApp)); // Enable power for External CC1101 if it is connected furi_hal_subghz_enable_ext_power(); @@ -771,24 +34,81 @@ SubGHzRemote* subghz_remote_alloc(void) { furi_hal_power_suppress_charge_enter(); - app->model_mutex = furi_mutex_alloc(FuriMutexTypeNormal); + app->file_path = furi_string_alloc(); + furi_string_set(app->file_path, SUBREM_APP_FOLDER); - app->input_queue = furi_message_queue_alloc(32, sizeof(InputEvent)); - - app->view_port = view_port_alloc(); - view_port_draw_callback_set(app->view_port, render_callback, app); - view_port_input_callback_set(app->view_port, input_callback, app); - - // Open GUI and register view_port + // GUI app->gui = furi_record_open(RECORD_GUI); - gui_add_view_port(app->gui, app->view_port, GuiLayerFullscreen); - app->notification = furi_record_open(RECORD_NOTIFICATION); + // View Dispatcher + app->view_dispatcher = view_dispatcher_alloc(); + app->scene_manager = scene_manager_alloc(&subrem_scene_handlers, app); + view_dispatcher_enable_queue(app->view_dispatcher); + + view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + view_dispatcher_set_custom_event_callback( + app->view_dispatcher, subghz_remote_app_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + app->view_dispatcher, subghz_remote_app_back_event_callback); + view_dispatcher_set_tick_event_callback( + app->view_dispatcher, subghz_remote_app_tick_event_callback, 100); + + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + + // Open Notification record + app->notifications = furi_record_open(RECORD_NOTIFICATION); + + // SubMenu + app->submenu = submenu_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, SubRemViewSubmenu, submenu_get_view(app->submenu)); + + //Dialog + app->dialogs = furi_record_open(RECORD_DIALOGS); + + // Remote view + app->subrem_remote_view = subrem_view_remote_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + SubRemViewIDRemote, + subrem_view_remote_get_view(app->subrem_remote_view)); + + for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { + app->subs_preset[i] = subrem_sub_file_preset_alloc(); + } + + app->setting = subghz_setting_alloc(); + subghz_setting_load(app->setting, EXT_PATH("subghz/assets/setting_user")); + + app->environment = subghz_environment_alloc(); + + subghz_environment_load_keystore(app->environment, EXT_PATH("subghz/assets/keeloq_mfcodes")); + subghz_environment_load_keystore( + app->environment, EXT_PATH("subghz/assets/keeloq_mfcodes_user")); + subghz_environment_set_came_atomo_rainbow_table_file_name( + app->environment, EXT_PATH("subghz/assets/came_atomo")); + subghz_environment_set_alutech_at_4n_rainbow_table_file_name( + app->environment, EXT_PATH("subghz/assets/alutech_at_4n")); + subghz_environment_set_nice_flor_s_rainbow_table_file_name( + app->environment, EXT_PATH("subghz/assets/nice_flor_s")); + subghz_environment_set_protocol_registry(app->environment, (void*)&subghz_protocol_registry); + + app->receiver = subghz_receiver_alloc_init(app->environment); + + app->tx_running = false; + +#ifdef SUBREM_LIGHT + scene_manager_next_scene(app->scene_manager, SubRemSceneOpenMapFile); +#else + scene_manager_next_scene(app->scene_manager, SubRemSceneStart); +#endif return app; } -void subghz_remote_free(SubGHzRemote* app, bool with_subghz) { +void subghz_remote_app_free(SubGhzRemoteApp* app) { + furi_assert(app); + furi_hal_power_suppress_charge_exit(); // Disable power for External CC1101 if it was enabled and module is connected @@ -796,304 +116,47 @@ void subghz_remote_free(SubGHzRemote* app, bool with_subghz) { // Reinit SPI handles for internal radio / nfc furi_hal_subghz_init_radio_type(SubGhzRadioInternal); - furi_string_free(app->up_file); - furi_string_free(app->down_file); - furi_string_free(app->left_file); - furi_string_free(app->right_file); - furi_string_free(app->ok_file); + // Submenu + view_dispatcher_remove_view(app->view_dispatcher, SubRemViewSubmenu); + submenu_free(app->submenu); - furi_string_free(app->file_path); - furi_string_free(app->signal); + //Dialog + furi_record_close(RECORD_DIALOGS); - gui_remove_view_port(app->gui, app->view_port); - furi_record_close(RECORD_GUI); - view_port_free(app->view_port); - app->gui = NULL; + // Remote view + view_dispatcher_remove_view(app->view_dispatcher, SubRemViewIDRemote); + subrem_view_remote_free(app->subrem_remote_view); - furi_message_queue_free(app->input_queue); + subghz_receiver_free(app->receiver); + subghz_environment_free(app->environment); + subghz_setting_free(app->setting); - furi_mutex_free(app->model_mutex); - - if(with_subghz) { - furi_hal_subghz_sleep(); - subghz_setting_free(app->setting); - subghz_receiver_free(app->subghz_receiver); - subghz_environment_free(app->environment); + for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { + subrem_sub_file_preset_free(app->subs_preset[i]); } + // Notifications furi_record_close(RECORD_NOTIFICATION); - app->notification = NULL; + app->notifications = NULL; + + // Close records + furi_record_close(RECORD_GUI); + + // Path strings + furi_string_free(app->file_path); free(app); } int32_t subghz_remote_app(void* p) { UNUSED(p); - SubGHzRemote* app = subghz_remote_alloc(); + SubGhzRemoteApp* subghz_remote_app = subghz_remote_app_alloc(); - app->file_path = furi_string_alloc(); - app->signal = furi_string_alloc(); + furi_string_set(subghz_remote_app->file_path, SUBREM_APP_FOLDER); - //setup variables before population - app->up_file = furi_string_alloc(); - app->down_file = furi_string_alloc(); - app->left_file = furi_string_alloc(); - app->right_file = furi_string_alloc(); - app->ok_file = furi_string_alloc(); + view_dispatcher_run(subghz_remote_app->view_dispatcher); - app->file_result = 3; - - Storage* storage = furi_record_open(RECORD_STORAGE); - storage_common_migrate(storage, EXT_PATH("unirf"), SUBREMOTEMAP_FOLDER); - - if(!storage_simply_mkdir(storage, SUBREMOTEMAP_FOLDER)) { - FURI_LOG_E(TAG, "Could not create folder %s", SUBREMOTEMAP_FOLDER); - } - furi_record_close(RECORD_STORAGE); - - furi_string_set(app->file_path, SUBREMOTEMAP_FOLDER); - - DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); - - DialogsFileBrowserOptions browser_options; - dialog_file_browser_set_basic_options(&browser_options, SUBREMOTEMAP_EXTENSION, &I_sub1_10px); - browser_options.base_path = SUBREMOTEMAP_FOLDER; - - bool res = dialog_file_browser_show(dialogs, app->file_path, app->file_path, &browser_options); - - furi_record_close(RECORD_DIALOGS); - if(!res) { - FURI_LOG_E(TAG, "No file selected"); - subghz_remote_free(app, false); - return 255; - } else { - //check map and population variables - subghz_remote_cfg_set_check(app, app->file_path); - } - - // init subghz stuff - subghz_remote_subghz_alloc(app); - - bool exit_loop = false; - - if(app->file_result == 2) { - //FURI_LOG_D( - //TAG, - //"U: %s - D: %s - L: %s - R: %s - O: %s ", - //furi_string_get_cstr(app->up_file), - //furi_string_get_cstr(app->down_file), - //furi_string_get_cstr(app->left_file), - //furi_string_get_cstr(app->right_file), - //furi_string_get_cstr(app->ok_file)); - - //variables to control multiple button presses and status updates - app->send_status = "Idle"; - app->send_status_c = 0; - app->processing = 0; - //app->repeat = 1; - app->button = 0; - - //refresh screen to update variables before processing main screen or error screens - furi_mutex_release(app->model_mutex); - view_port_update(app->view_port); - - //input detect loop start - InputEvent input; - while(1) { - furi_check( - furi_message_queue_get(app->input_queue, &input, FuriWaitForever) == FuriStatusOk); - //FURI_LOG_D( - //TAG, - //"key: %s type: %s", - //input_get_key_name(input.key), - //input_get_type_name(input.type)); - - switch(input.key) { - case InputKeyUp: - if(input.type == InputTypePress) { - if(app->up_enabled) { - if(app->processing == 0) { - furi_string_reset(app->signal); - furi_string_set(app->signal, app->up_file); - app->button = 1; - app->processing = 1; - } - } - } - if(input.type == InputTypeRelease) { - if(app->up_enabled) { - subghz_remote_tx_stop(app); - } - } - break; - - case InputKeyDown: - if(input.type == InputTypePress) { - if(app->down_enabled) { - if(app->processing == 0) { - furi_string_reset(app->signal); - furi_string_set(app->signal, app->down_file); - app->button = 2; - app->processing = 1; - } - } - } - if(input.type == InputTypeRelease) { - if(app->down_enabled) { - subghz_remote_tx_stop(app); - } - } - break; - - case InputKeyRight: - if(input.type == InputTypePress) { - if(app->right_enabled) { - if(app->processing == 0) { - furi_string_reset(app->signal); - furi_string_set(app->signal, app->right_file); - app->button = 3; - app->processing = 1; - } - } - } - if(input.type == InputTypeRelease) { - if(app->right_enabled) { - subghz_remote_tx_stop(app); - } - } - break; - - case InputKeyLeft: - if(input.type == InputTypePress) { - if(app->left_enabled) { - if(app->processing == 0) { - furi_string_reset(app->signal); - furi_string_set(app->signal, app->left_file); - app->button = 4; - app->processing = 1; - } - } - } - if(input.type == InputTypeRelease) { - if(app->left_enabled) { - subghz_remote_tx_stop(app); - } - } - break; - - case InputKeyOk: - if(input.type == InputTypePress) { - if(app->ok_enabled) { - if(app->processing == 0) { - furi_string_reset(app->signal); - furi_string_set(app->signal, app->ok_file); - app->button = 5; - app->processing = 1; - } - } - } - if(input.type == InputTypeRelease) { - if(app->ok_enabled) { - subghz_remote_tx_stop(app); - } - } - break; - - case InputKeyBack: - subghz_remote_tx_stop(app); - exit_loop = true; - break; - default: - break; - } - - if(app->processing == 0) { - //FURI_LOG_D(TAG, "processing 0"); - app->send_status = "Idle"; - app->send_status_c = 0; - app->button = 0; - } else if(app->processing == 1) { - //FURI_LOG_D(TAG, "processing 1"); - - app->send_status = "Send"; - - switch(app->button) { - case 1: - app->send_status_c = 1; - break; - case 2: - app->send_status_c = 2; - break; - case 3: - app->send_status_c = 3; - break; - case 4: - app->send_status_c = 4; - break; - case 5: - app->send_status_c = 5; - break; - } - - app->processing = 2; - - subghz_remote_process_signal(app, app->signal); - } - - if(exit_loop == true) { - furi_mutex_release(app->model_mutex); - break; - } - - furi_mutex_release(app->model_mutex); - view_port_update(app->view_port); - } - } else if(app->file_result == 1 || app->file_result == 3) { - //refresh screen to update variables before processing main screen or error screens - view_port_update(app->view_port); - - InputEvent input; - while(1) { - furi_check( - furi_message_queue_get(app->input_queue, &input, FuriWaitForever) == FuriStatusOk); - //FURI_LOG_D( - //TAG, - //"key: %s type: %s", - //input_get_key_name(input.key), - //input_get_type_name(input.type)); - - switch(input.key) { - case InputKeyRight: - break; - case InputKeyLeft: - break; - case InputKeyUp: - break; - case InputKeyDown: - break; - case InputKeyOk: - break; - case InputKeyBack: - exit_loop = true; - break; - default: - break; - } - - if(exit_loop == true) { - furi_mutex_release(app->model_mutex); - break; - } - - furi_mutex_release(app->model_mutex); - view_port_update(app->view_port); - } - } else { - furi_mutex_release(app->model_mutex); - } - - // remove & free all stuff created by app - subghz_remote_free(app, true); + subghz_remote_app_free(subghz_remote_app); return 0; } diff --git a/applications/main/subghz_remote/subghz_remote_app_i.c b/applications/main/subghz_remote/subghz_remote_app_i.c new file mode 100644 index 000000000..9b0f77d17 --- /dev/null +++ b/applications/main/subghz_remote/subghz_remote_app_i.c @@ -0,0 +1,448 @@ +#include "subghz_remote_app_i.h" +#include +#include + +#include + +// #include +// #include + +#include + +#define TAG "SubGhzRemote" + +static const char* map_file_labels[SubRemSubKeyNameMaxCount][2] = { + [SubRemSubKeyNameUp] = {"UP", "ULABEL"}, + [SubRemSubKeyNameDown] = {"DOWN", "DLABEL"}, + [SubRemSubKeyNameLeft] = {"LEFT", "LLABEL"}, + [SubRemSubKeyNameRight] = {"RIGHT", "RLABEL"}, + [SubRemSubKeyNameOk] = {"OK", "OKLABEL"}, +}; + +static bool + subrem_set_preset_data(SubGhzSetting* setting, FreqPreset* freq_preset, const char* preset) { + const char* preset_name = ""; + if(!strcmp(preset, "FuriHalSubGhzPresetOok270Async")) { + preset_name = "AM270"; + } else if(!strcmp(preset, "FuriHalSubGhzPresetOok650Async")) { + preset_name = "AM650"; + } else if(!strcmp(preset, "FuriHalSubGhzPreset2FSKDev238Async")) { + preset_name = "FM238"; + } else if(!strcmp(preset, "FuriHalSubGhzPreset2FSKDev476Async")) { + preset_name = "FM476"; + } else if(!strcmp(preset, "FuriHalSubGhzPresetCustom")) { + // preset_name = "CUSTOM"; + return false; + } else { + FURI_LOG_E(TAG, "Unknown preset"); + return false; + } + size_t preset_index = subghz_setting_get_inx_preset_by_name(setting, preset_name); + freq_preset->data = subghz_setting_get_preset_data(setting, preset_index); + return true; +} + +SubRemSubFilePreset* subrem_sub_file_preset_alloc() { + SubRemSubFilePreset* sub_preset = malloc(sizeof(SubRemSubFilePreset)); + + sub_preset->fff_data = flipper_format_string_alloc(); + sub_preset->file_path = furi_string_alloc(); + sub_preset->protocaol_name = furi_string_alloc(); + sub_preset->label = furi_string_alloc_set_str("N/A"); + + sub_preset->type = SubGhzProtocolTypeUnknown; + + return sub_preset; +} + +void subrem_sub_file_preset_free(SubRemSubFilePreset* sub_preset) { + furi_assert(sub_preset); + + furi_string_free(sub_preset->label); + furi_string_free(sub_preset->protocaol_name); + furi_string_free(sub_preset->file_path); + flipper_format_free(sub_preset->fff_data); + + free(sub_preset); +} + +static void subrem_sub_file_preset_reset(SubRemSubFilePreset* sub_preset) { + furi_assert(sub_preset); + + furi_string_set_str(sub_preset->label, "N/A"); + furi_string_reset(sub_preset->protocaol_name); + furi_string_reset(sub_preset->file_path); + + Stream* fff_data_stream = flipper_format_get_raw_stream(sub_preset->fff_data); + stream_clean(fff_data_stream); + + sub_preset->type = SubGhzProtocolTypeUnknown; +} + +void subrem_map_preset_reset(SubGhzRemoteApp* app) { + furi_assert(app); + + for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { + subrem_sub_file_preset_reset(app->subs_preset[i]); + } +} + +static bool subrem_map_preset_load(SubGhzRemoteApp* app, FlipperFormat* fff_data_file) { + furi_assert(app); + bool ret = false; + SubRemSubFilePreset* sub_preset; + for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { + sub_preset = app->subs_preset[i]; + if(!flipper_format_read_string( + fff_data_file, map_file_labels[i][0], sub_preset->file_path)) { +#if FURI_DEBUG + FURI_LOG_W(TAG, "No file patch for %s", map_file_labels[i][0]); +#endif + sub_preset->type = SubGhzProtocolTypeUnknown; + } else if(!flipper_format_rewind(fff_data_file)) { + // Rewind error + } else if(!flipper_format_read_string( + fff_data_file, map_file_labels[i][1], sub_preset->label)) { +#if FURI_DEBUG + FURI_LOG_W(TAG, "No Label for %s", map_file_labels[i][0]); +#endif + path_extract_filename(sub_preset->file_path, sub_preset->label, true); + } else { + FURI_LOG_I( + TAG, + "%-5s: %s %s", + map_file_labels[i][0], + furi_string_get_cstr(sub_preset->label), + furi_string_get_cstr(sub_preset->file_path)); + ret = true; + } + flipper_format_rewind(fff_data_file); + } + return ret; +} + +bool subrem_save_protocol_to_file(FlipperFormat* flipper_format, const char* dev_file_name) { + furi_assert(flipper_format); + furi_assert(dev_file_name); + + Storage* storage = furi_record_open(RECORD_STORAGE); + Stream* flipper_format_stream = flipper_format_get_raw_stream(flipper_format); + + bool saved = false; + FuriString* file_dir = furi_string_alloc(); + + path_extract_dirname(dev_file_name, file_dir); + do { + //removing additional fields + flipper_format_delete_key(flipper_format, "Repeat"); + //flipper_format_delete_key(flipper_format, "Manufacture"); + + if(!storage_simply_remove(storage, dev_file_name)) { + break; + } + //ToDo check Write + stream_seek(flipper_format_stream, 0, StreamOffsetFromStart); + stream_save_to_file(flipper_format_stream, storage, dev_file_name, FSOM_CREATE_ALWAYS); + + saved = true; + } while(0); + furi_string_free(file_dir); + furi_record_close(RECORD_STORAGE); + return saved; +} + +bool subrem_tx_start_sub( + SubGhzRemoteApp* app, + SubRemSubFilePreset* sub_preset, + SubGhzProtocolEncoderRAWCallbackEnd callback) { + furi_assert(app); + furi_assert(sub_preset); + bool ret = false; + + subrem_tx_stop_sub(app, true); + + if(sub_preset->type == SubGhzProtocolTypeUnknown) { + return false; + } + + FURI_LOG_I(TAG, "Send %s", furi_string_get_cstr(sub_preset->label)); + + subghz_custom_btn_set(SUBGHZ_CUSTOM_BTN_OK); + keeloq_reset_original_btn(); + subghz_custom_btns_reset(); + + do { + flipper_format_rewind(sub_preset->fff_data); // + + app->transmitter = subghz_transmitter_alloc_init( + app->environment, furi_string_get_cstr(sub_preset->protocaol_name)); + + if(app->transmitter) { + if(subghz_transmitter_deserialize(app->transmitter, sub_preset->fff_data) != + SubGhzProtocolStatusOk) { + FURI_LOG_E(TAG, "Deserialize error!"); + break; + } + furi_hal_subghz_reset(); + furi_hal_subghz_idle(); + furi_hal_subghz_load_custom_preset(sub_preset->freq_preset.data); + furi_hal_gpio_init( + furi_hal_subghz.cc1101_g0_pin, GpioModeInput, GpioPullNo, GpioSpeedLow); + + furi_hal_subghz_idle(); + + furi_hal_subghz_set_frequency_and_path(sub_preset->freq_preset.frequency); + furi_hal_gpio_write(furi_hal_subghz.cc1101_g0_pin, false); + furi_hal_gpio_init( + furi_hal_subghz.cc1101_g0_pin, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); + + if(!furi_hal_subghz_tx()) { + FURI_LOG_E(TAG, "Sending not allowed"); + break; + } + + if(sub_preset->type == SubGhzProtocolTypeRAW) { + subghz_protocol_raw_file_encoder_worker_set_callback_end( + (SubGhzProtocolEncoderRAW*)subghz_transmitter_get_protocol_instance( + app->transmitter), + callback, + app); + } + + furi_hal_subghz_start_async_tx(subghz_transmitter_yield, app->transmitter); + + ret = true; + } + } while(false); + + app->tx_running = ret; + + return ret; +} + +static void subghz_tx_stop(SubGhzRemoteApp* app) { + furi_assert(app); + + //Stop TX + furi_hal_subghz_stop_async_tx(); + + subghz_transmitter_stop(app->transmitter); + subghz_transmitter_free(app->transmitter); + furi_hal_subghz_idle(); +} + +bool subrem_tx_stop_sub(SubGhzRemoteApp* app, bool forced) { + furi_assert(app); + SubRemSubFilePreset* sub_preset = app->subs_preset[app->chusen_sub]; + + if(forced || (sub_preset->type != SubGhzProtocolTypeRAW)) { + // SubRemSubKeyTypeRawKey)) { + if(app->tx_running) { + subghz_tx_stop(app); + + if(sub_preset->type == SubGhzProtocolTypeDynamic) { + subrem_save_protocol_to_file( + sub_preset->fff_data, furi_string_get_cstr(sub_preset->file_path)); + + keeloq_reset_mfname(); + keeloq_reset_kl_type(); + keeloq_reset_original_btn(); + subghz_custom_btns_reset(); + star_line_reset_mfname(); + star_line_reset_kl_type(); + } + + app->tx_running = false; + return true; + } + } + + return false; +} + +static bool subrem_map_preset_check(SubGhzRemoteApp* app, FlipperFormat* fff_data_file) { + furi_assert(app); + FuriString* temp_str = furi_string_alloc(); + uint32_t temp_data32; + bool ret = false; + bool sub_preset_loaded = false; + SubRemSubFilePreset* sub_preset; + + for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { + sub_preset = app->subs_preset[i]; + sub_preset_loaded = false; + if(furi_string_empty(sub_preset->file_path)) { + // FURI_LOG_I(TAG, "Empty file path"); + continue; + } + do { + if(!flipper_format_file_open_existing( + fff_data_file, furi_string_get_cstr(sub_preset->file_path))) { + FURI_LOG_W(TAG, "Error open file %s", furi_string_get_cstr(sub_preset->file_path)); + break; + } + + if(!flipper_format_read_header(fff_data_file, temp_str, &temp_data32)) { + FURI_LOG_E(TAG, "Missing or incorrect header"); + break; + } + + if(((!strcmp(furi_string_get_cstr(temp_str), SUBGHZ_KEY_FILE_TYPE)) || + (!strcmp(furi_string_get_cstr(temp_str), SUBGHZ_RAW_FILE_TYPE))) && + temp_data32 == SUBGHZ_KEY_FILE_VERSION) { + } else { + FURI_LOG_E(TAG, "Type or version mismatch"); + break; + } + + //Load frequency + if(!flipper_format_read_uint32(fff_data_file, "Frequency", &temp_data32, 1)) { + FURI_LOG_W(TAG, "Cannot read frequency. Set default frequency"); + sub_preset->freq_preset.frequency = + subghz_setting_get_default_frequency(app->setting); + } else if(!furi_hal_subghz_is_tx_allowed(temp_data32)) { + FURI_LOG_E(TAG, "This frequency can only be used for RX"); + break; + } else { + sub_preset->freq_preset.frequency = temp_data32; + } + + //Load preset + if(!flipper_format_read_string(fff_data_file, "Preset", temp_str)) { + FURI_LOG_E(TAG, "Missing Preset"); + break; + } + + if(!subrem_set_preset_data( + app->setting, &sub_preset->freq_preset, furi_string_get_cstr(temp_str))) { + FURI_LOG_E(TAG, "Cannot load preset."); + break; + } + + //Load protocol + if(!flipper_format_read_string(fff_data_file, "Protocol", temp_str)) { + FURI_LOG_E(TAG, "Missing Protocol"); + break; + } + + FlipperFormat* fff_data = sub_preset->fff_data; + if(!strcmp(furi_string_get_cstr(temp_str), "RAW")) { + //if RAW + subghz_protocol_raw_gen_fff_data( + fff_data, furi_string_get_cstr(sub_preset->file_path)); + } else { + stream_copy_full( + flipper_format_get_raw_stream(fff_data_file), + flipper_format_get_raw_stream(fff_data)); + } + + const SubGhzProtocolRegistry* protocol_registry_items = + subghz_environment_get_protocol_registry(app->environment); + + const SubGhzProtocol* protocol = subghz_protocol_registry_get_by_name( + protocol_registry_items, furi_string_get_cstr(temp_str)); + + if(!protocol) { + FURI_LOG_E(TAG, "Protocol not found"); + break; + } else if(protocol->flag & SubGhzProtocolFlag_Send) { + if((protocol->type == SubGhzProtocolTypeStatic) || + (protocol->type == SubGhzProtocolTypeDynamic) || + // (protocol->type == SubGhzProtocolTypeBinRAW) || // TODO: BINRAW + (protocol->type == SubGhzProtocolTypeRAW)) { + sub_preset->type = protocol->type; + } else { + FURI_LOG_E(TAG, "Unsuported Protocol"); + break; + } + + furi_string_set(sub_preset->protocaol_name, temp_str); + } else { + FURI_LOG_E(TAG, "Protocol does not support transmission"); + } + + sub_preset_loaded = true; + ret |= true; +#if FURI_DEBUG + FURI_LOG_I(TAG, "%-16s - protocol Loaded", furi_string_get_cstr(sub_preset->label)); +#endif + } while(false); + + // TODO: + // Load file state logic + // Label depending on the state + + if(!sub_preset_loaded) { + furi_string_set_str(sub_preset->label, "N/A"); + } + + flipper_format_file_close(fff_data_file); + } + furi_string_free(temp_str); + + return ret; +} + +bool subrem_map_file_load(SubGhzRemoteApp* app, const char* file_path) { + furi_assert(app); + furi_assert(file_path); +#if FURI_DEBUG + FURI_LOG_I(TAG, "Load Map File Start"); +#endif + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* fff_data_file = flipper_format_file_alloc(storage); + bool ret = false; +#if FURI_DEBUG + FURI_LOG_I(TAG, "Open Map File.."); +#endif + subrem_map_preset_reset(app); + + if(!flipper_format_file_open_existing(fff_data_file, file_path)) { + FURI_LOG_E(TAG, "Could not open MAP file %s", file_path); + } else { + if(!subrem_map_preset_load(app, fff_data_file)) { + FURI_LOG_E(TAG, "Could no Sub file path in MAP file"); + // ret = // error for popup + } else if( + (flipper_format_file_close(fff_data_file)) && + (subrem_map_preset_check(app, fff_data_file))) { + FURI_LOG_I(TAG, "Load Map File Seccesful"); + ret = true; + } + } + + // TODO: Popup for error or return error type + if(!ret) { + FURI_LOG_E(TAG, "Broken Map File"); + } + + flipper_format_file_close(fff_data_file); + flipper_format_free(fff_data_file); + + furi_record_close(RECORD_STORAGE); + + return ret; +} + +SubRemLoadMapState subrem_load_from_file(SubGhzRemoteApp* app) { + furi_assert(app); + + FuriString* file_path = furi_string_alloc(); + SubRemLoadMapState ret = SubRemLoadMapStateBack; + + DialogsFileBrowserOptions browser_options; + dialog_file_browser_set_basic_options(&browser_options, SUBREM_APP_EXTENSION, &I_sub1_10px); + browser_options.base_path = SUBREM_APP_FOLDER; + + // Input events and views are managed by file_select + if(!dialog_file_browser_show(app->dialogs, app->file_path, app->file_path, &browser_options)) { + } else if(subrem_map_file_load(app, furi_string_get_cstr(app->file_path))) { + ret = SubRemLoadMapStateOK; + } else { + ret = SubRemLoadMapStateError; + } + + furi_string_free(file_path); + + return ret; +} diff --git a/applications/main/subghz_remote/subghz_remote_app_i.h b/applications/main/subghz_remote/subghz_remote_app_i.h new file mode 100644 index 000000000..1cbdbd5cd --- /dev/null +++ b/applications/main/subghz_remote/subghz_remote_app_i.h @@ -0,0 +1,84 @@ +#pragma once + +#include "helpers/subrem_types.h" +#include + +#include "views/remote.h" + +#include "scenes/subrem_scene.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include + +#define SUBREM_APP_FOLDER EXT_PATH("subghz_remote") +#define SUBREM_MAX_LEN_NAME 64 + +typedef struct { + uint32_t frequency; + uint8_t* data; +} FreqPreset; + +// Sub File preset +typedef struct { + FlipperFormat* fff_data; + FreqPreset freq_preset; + FuriString* file_path; + FuriString* protocaol_name; + FuriString* label; + SubGhzProtocolType type; +} SubRemSubFilePreset; + +SubRemSubFilePreset* subrem_sub_file_preset_alloc(); + +void subrem_sub_file_preset_free(SubRemSubFilePreset* sub_preset); + +typedef struct { + Gui* gui; + ViewDispatcher* view_dispatcher; + SceneManager* scene_manager; + NotificationApp* notifications; + DialogsApp* dialogs; + Submenu* submenu; + FuriString* file_path; + char file_name_tmp[SUBREM_MAX_LEN_NAME]; + + SubRemViewRemote* subrem_remote_view; + + SubRemSubFilePreset* subs_preset[SubRemSubKeyNameMaxCount]; + + SubGhzSetting* setting; + SubGhzEnvironment* environment; + SubGhzReceiver* receiver; + SubGhzTransmitter* transmitter; + + bool tx_running; + + uint8_t chusen_sub; + + // TODO: LoadFileError +} SubGhzRemoteApp; + +SubRemLoadMapState subrem_load_from_file(SubGhzRemoteApp* app); + +bool subrem_tx_start_sub( + SubGhzRemoteApp* app, + SubRemSubFilePreset* sub_preset, + SubGhzProtocolEncoderRAWCallbackEnd callback); + +bool subrem_tx_stop_sub(SubGhzRemoteApp* app, bool forced); \ No newline at end of file diff --git a/applications/main/subghz_remote/views/remote.c b/applications/main/subghz_remote/views/remote.c new file mode 100644 index 000000000..85c63568e --- /dev/null +++ b/applications/main/subghz_remote/views/remote.c @@ -0,0 +1,302 @@ +#include "remote.h" +#include "../subghz_remote_app_i.h" + +#include +#include + +#define SUBREM_VIEW_REMOTE_MAX_LABEL_LENGTH 16 + +struct SubRemViewRemote { + View* view; + SubRemViewRemoteCallback callback; + void* context; +}; + +// TODO: model +typedef struct { + // FuriString* up_label; + // FuriString* down_label; + // FuriString* left_label; + // FuriString* right_label; + // FuriString* ok_label; + + char* up_label; + char* down_label; + char* left_label; + char* right_label; + char* ok_label; + + SubRemViewRemoteState state; + + uint8_t pressed_btn; +} SubRemViewRemoteModel; + +void subrem_view_remote_set_callback( + SubRemViewRemote* subrem_view_remote, + SubRemViewRemoteCallback callback, + void* context) { + furi_assert(subrem_view_remote); + + subrem_view_remote->callback = callback; + subrem_view_remote->context = context; +} + +void subrem_view_remote_add_data_to_show( + SubRemViewRemote* subrem_view_remote, + const char* up_label, + const char* down_label, + const char* left_label, + const char* right_label, + const char* ok_label) { + furi_assert(subrem_view_remote); + + with_view_model( + subrem_view_remote->view, + SubRemViewRemoteModel * model, + { + strncpy(model->up_label, up_label, SUBREM_VIEW_REMOTE_MAX_LABEL_LENGTH); + strncpy(model->down_label, down_label, SUBREM_VIEW_REMOTE_MAX_LABEL_LENGTH); + strncpy(model->left_label, left_label, SUBREM_VIEW_REMOTE_MAX_LABEL_LENGTH); + strncpy(model->right_label, right_label, SUBREM_VIEW_REMOTE_MAX_LABEL_LENGTH); + strncpy(model->ok_label, ok_label, SUBREM_VIEW_REMOTE_MAX_LABEL_LENGTH); + + // furi_string_set(model->up_label, up_label); + // furi_string_set(model->down_label, down_label); + // furi_string_set(model->left_label, left_label); + // furi_string_set(model->right_label, right_label); + // furi_string_set(model->ok_label, ok_label); + }, + true); +} + +void subrem_view_remote_set_presed_btn(SubRemViewRemote* subrem_view_remote, uint8_t presed_btn) { + furi_assert(subrem_view_remote); + with_view_model( + subrem_view_remote->view, + SubRemViewRemoteModel * model, + { model->pressed_btn = presed_btn; }, + true); +} + +void subrem_view_remote_set_state( + SubRemViewRemote* subrem_view_remote, + SubRemViewRemoteState state) { + furi_assert(subrem_view_remote); + with_view_model( + subrem_view_remote->view, SubRemViewRemoteModel * model, { model->state = state; }, true); +} + +void subrem_view_remote_draw(Canvas* canvas, SubRemViewRemoteModel* model) { + canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); + + canvas_clear(canvas); + + //Icons for Labels + //canvas_draw_icon(canvas, 0, 0, &I_SubGHzRemote_LeftAlignedButtons_9x64); + canvas_draw_icon(canvas, 1, 5, &I_ButtonUp_7x4); + canvas_draw_icon(canvas, 1, 15, &I_ButtonDown_7x4); + canvas_draw_icon(canvas, 2, 23, &I_ButtonLeft_4x7); + canvas_draw_icon(canvas, 2, 33, &I_ButtonRight_4x7); + canvas_draw_icon(canvas, 0, 42, &I_Ok_btn_9x9); + canvas_draw_icon(canvas, 0, 53, &I_back_10px); + + //Labels + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 10, 10, model->up_label); + canvas_draw_str(canvas, 10, 20, model->down_label); + canvas_draw_str(canvas, 10, 30, model->left_label); + canvas_draw_str(canvas, 10, 40, model->right_label); + canvas_draw_str(canvas, 10, 50, model->ok_label); + + // canvas_draw_str(canvas, 10, 10, furi_string_get_cstr(model->up_label)); + // canvas_draw_str(canvas, 10, 10, furi_string_get_cstr(model->up_label)); + // canvas_draw_str(canvas, 10, 10, furi_string_get_cstr(model->up_label)); + // canvas_draw_str(canvas, 10, 10, furi_string_get_cstr(model->up_label)); + // canvas_draw_str(canvas, 10, 10, furi_string_get_cstr(model->up_label)); + + canvas_draw_str_aligned(canvas, 11, 62, AlignLeft, AlignBottom, "Hold=Exit."); + + //Status text and indicator + canvas_draw_icon(canvas, 113, 15, &I_Pin_cell_13x13); + + if(model->state == SubRemViewRemoteStateIdle) { + canvas_draw_str_aligned(canvas, 126, 10, AlignRight, AlignBottom, "Idle"); + } else { + switch(model->state) { + case SubRemViewRemoteStateSending: + canvas_draw_str_aligned(canvas, 126, 10, AlignRight, AlignBottom, "Send"); + break; + case SubRemViewRemoteStateLoading: + canvas_draw_str_aligned(canvas, 126, 10, AlignRight, AlignBottom, "Load"); + break; + default: +#if FURI_DEBUG + canvas_draw_str_aligned(canvas, 126, 10, AlignRight, AlignBottom, "Wrong_state"); +#endif + break; + } + + switch(model->pressed_btn) { + case SubRemSubKeyNameUp: + canvas_draw_icon(canvas, 116, 17, &I_Pin_arrow_up_7x9); + break; + case SubRemSubKeyNameDown: + canvas_draw_icon_ex(canvas, 116, 17, &I_Pin_arrow_up_7x9, IconRotation180); + break; + case SubRemSubKeyNameLeft: + canvas_draw_icon_ex(canvas, 115, 18, &I_Pin_arrow_up_7x9, IconRotation270); + break; + case SubRemSubKeyNameRight: + canvas_draw_icon_ex(canvas, 115, 18, &I_Pin_arrow_up_7x9, IconRotation90); + break; + case SubRemSubKeyNameOk: + canvas_draw_icon(canvas, 116, 18, &I_Pin_star_7x7); + break; + } + } + //Repeat indicator + //canvas_draw_str_aligned(canvas, 125, 40, AlignRight, AlignBottom, "Repeat:"); + //canvas_draw_icon(canvas, 115, 39, &I_SubGHzRemote_Repeat_12x14); + //canvas_draw_str_aligned(canvas, 125, 62, AlignRight, AlignBottom, int_to_char(app->repeat)); +} + +bool subrem_view_remote_input(InputEvent* event, void* context) { + furi_assert(context); + SubRemViewRemote* subrem_view_remote = context; + + if(event->key == InputKeyBack && event->type == InputTypeLong) { + with_view_model( + subrem_view_remote->view, + SubRemViewRemoteModel * model, + { + strcpy(model->up_label, "N/A"); + strcpy(model->down_label, "N/A"); + strcpy(model->left_label, "N/A"); + strcpy(model->right_label, "N/A"); + strcpy(model->ok_label, "N/A"); + }, + false); + subrem_view_remote->callback(SubRemCustomEventViewRemoteBack, subrem_view_remote->context); + return true; + } else if(event->key == InputKeyBack && event->type == InputTypeShort) { + with_view_model( + subrem_view_remote->view, + SubRemViewRemoteModel * model, + { model->pressed_btn = 0; }, + true); + subrem_view_remote->callback( + SubRemCustomEventViewRemoteForcedStop, subrem_view_remote->context); + return true; + } else if(event->key == InputKeyBack) { + return true; + } + // BACK button processing end + + if(event->key == InputKeyUp && event->type == InputTypePress) { + subrem_view_remote->callback( + SubRemCustomEventViewRemoteStartUP, subrem_view_remote->context); + return true; + } else if(event->key == InputKeyDown && event->type == InputTypePress) { + subrem_view_remote->callback( + SubRemCustomEventViewRemoteStartDOWN, subrem_view_remote->context); + return true; + } else if(event->key == InputKeyLeft && event->type == InputTypePress) { + subrem_view_remote->callback( + SubRemCustomEventViewRemoteStartLEFT, subrem_view_remote->context); + return true; + } else if(event->key == InputKeyRight && event->type == InputTypePress) { + subrem_view_remote->callback( + SubRemCustomEventViewRemoteStartRIGHT, subrem_view_remote->context); + return true; + } else if(event->key == InputKeyOk && event->type == InputTypePress) { + subrem_view_remote->callback( + SubRemCustomEventViewRemoteStartOK, subrem_view_remote->context); + return true; + } else if(event->type == InputTypeRelease) { + subrem_view_remote->callback(SubRemCustomEventViewRemoteStop, subrem_view_remote->context); + return true; + } + + return true; +} + +void subrem_view_remote_enter(void* context) { + furi_assert(context); +} + +void subrem_view_remote_exit(void* context) { + furi_assert(context); +} + +SubRemViewRemote* subrem_view_remote_alloc() { + SubRemViewRemote* subrem_view_remote = malloc(sizeof(SubRemViewRemote)); + + // View allocation and configuration + subrem_view_remote->view = view_alloc(); + view_allocate_model( + subrem_view_remote->view, ViewModelTypeLocking, sizeof(SubRemViewRemoteModel)); + view_set_context(subrem_view_remote->view, subrem_view_remote); + view_set_draw_callback(subrem_view_remote->view, (ViewDrawCallback)subrem_view_remote_draw); + view_set_input_callback(subrem_view_remote->view, subrem_view_remote_input); + view_set_enter_callback(subrem_view_remote->view, subrem_view_remote_enter); + view_set_exit_callback(subrem_view_remote->view, subrem_view_remote_exit); + + with_view_model( + subrem_view_remote->view, + SubRemViewRemoteModel * model, + { + model->state = SubRemViewRemoteStateIdle; + + model->up_label = malloc(sizeof(char) * SUBREM_VIEW_REMOTE_MAX_LABEL_LENGTH + 1); + model->down_label = malloc(sizeof(char) * SUBREM_VIEW_REMOTE_MAX_LABEL_LENGTH + 1); + model->left_label = malloc(sizeof(char) * SUBREM_VIEW_REMOTE_MAX_LABEL_LENGTH + 1); + model->right_label = malloc(sizeof(char) * SUBREM_VIEW_REMOTE_MAX_LABEL_LENGTH + 1); + model->ok_label = malloc(sizeof(char) * SUBREM_VIEW_REMOTE_MAX_LABEL_LENGTH + 1); + + strcpy(model->up_label, "N/A"); + strcpy(model->down_label, "N/A"); + strcpy(model->left_label, "N/A"); + strcpy(model->right_label, "N/A"); + strcpy(model->ok_label, "N/A"); + + // model->up_label = furi_string_alloc_set_str("N/A"); + // model->down_label = furi_string_alloc_set_str("N/A"); + // model->left_label = furi_string_alloc_set_str("N/A"); + // model->right_label = furi_string_alloc_set_str("N/A"); + // model->ok_label = furi_string_alloc_set_str("N/A"); + + model->pressed_btn = 0; + }, + true); + return subrem_view_remote; +} + +void subrem_view_remote_free(SubRemViewRemote* subghz_remote) { + furi_assert(subghz_remote); + + with_view_model( + subghz_remote->view, + SubRemViewRemoteModel * model, + { + free(model->up_label); + free(model->down_label); + free(model->left_label); + free(model->right_label); + free(model->ok_label); + + // furi_string_free(model->up_label); + // furi_string_free(model->down_label); + // furi_string_free(model->left_label); + // furi_string_free(model->right_label); + // furi_string_free(model->ok_label); + }, + true); + view_free(subghz_remote->view); + free(subghz_remote); +} + +View* subrem_view_remote_get_view(SubRemViewRemote* subrem_view_remote) { + furi_assert(subrem_view_remote); + return subrem_view_remote->view; +} \ No newline at end of file diff --git a/applications/main/subghz_remote/views/remote.h b/applications/main/subghz_remote/views/remote.h new file mode 100644 index 000000000..76121cf8a --- /dev/null +++ b/applications/main/subghz_remote/views/remote.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include "../helpers/subrem_custom_event.h" + +typedef enum { + SubRemViewRemoteStateIdle, + SubRemViewRemoteStateLoading, + SubRemViewRemoteStateSending, +} SubRemViewRemoteState; + +typedef struct SubRemViewRemote SubRemViewRemote; + +typedef void (*SubRemViewRemoteCallback)(SubRemCustomEvent event, void* context); + +void subrem_view_remote_set_callback( + SubRemViewRemote* subrem_view_remote, + SubRemViewRemoteCallback callback, + void* context); + +SubRemViewRemote* subrem_view_remote_alloc(); + +void subrem_view_remote_free(SubRemViewRemote* subrem_view_remote); + +View* subrem_view_remote_get_view(SubRemViewRemote* subrem_view_remote); + +void subrem_view_remote_add_data_to_show( + SubRemViewRemote* subrem_view_remote, + const char* up_label, + const char* down_label, + const char* left_label, + const char* right_label, + const char* ok_label); + +void subrem_view_remote_set_presed_btn(SubRemViewRemote* subrem_view_remote, uint8_t presed_btn); +void subrem_view_remote_set_state( + SubRemViewRemote* subrem_view_remote, + SubRemViewRemoteState state); \ No newline at end of file diff --git a/applications/services/application.fam b/applications/services/application.fam index aec49b231..0b5009096 100644 --- a/applications/services/application.fam +++ b/applications/services/application.fam @@ -9,5 +9,6 @@ App( "desktop", "loader", "power", + "namechanger_srv", ], ) diff --git a/applications/services/bt/bt_service/bt.c b/applications/services/bt/bt_service/bt.c index 2dcea3485..a842aea45 100644 --- a/applications/services/bt/bt_service/bt.c +++ b/applications/services/bt/bt_service/bt.c @@ -61,6 +61,7 @@ static ViewPort* bt_pin_code_view_port_alloc(Bt* bt) { static void bt_pin_code_show(Bt* bt, uint32_t pin_code) { bt->pin_code = pin_code; + if(bt->suppress_pin_screen) return; notification_message(bt->notification, &sequence_display_backlight_on); gui_view_port_send_to_front(bt->gui, bt->pin_code_view_port); view_port_enabled_set(bt->pin_code_view_port, true); @@ -75,6 +76,8 @@ static void bt_pin_code_hide(Bt* bt) { static bool bt_pin_code_verify_event_handler(Bt* bt, uint32_t pin) { furi_assert(bt); + bt->pin_code = pin; + if(bt->suppress_pin_screen) return true; notification_message(bt->notification, &sequence_display_backlight_on); FuriString* pin_str; dialog_message_set_icon(bt->dialog_message, &I_BLE_Pairing_128x64, 0, 0); @@ -149,6 +152,8 @@ Bt* bt_alloc() { // API evnent bt->api_event = furi_event_flag_alloc(); + bt->pin = 0; + return bt; } @@ -214,6 +219,7 @@ static bool bt_on_gap_event_callback(GapEvent event, void* context) { furi_assert(context); Bt* bt = context; bool ret = false; + bt->pin = 0; if(event.type == GapEventTypeConnected) { // Update status bar @@ -270,12 +276,14 @@ static bool bt_on_gap_event_callback(GapEvent event, void* context) { furi_message_queue_put(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk); ret = true; } else if(event.type == GapEventTypePinCodeShow) { + bt->pin = event.data.pin_code; BtMessage message = { .type = BtMessageTypePinCodeShow, .data.pin_code = event.data.pin_code}; furi_check( furi_message_queue_put(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk); ret = true; } else if(event.type == GapEventTypePinCodeVerify) { + bt->pin = event.data.pin_code; ret = bt_pin_code_verify_event_handler(bt, event.data.pin_code); } else if(event.type == GapEventTypeUpdateMTU) { bt->max_packet_size = event.data.max_packet_size; @@ -368,6 +376,86 @@ static void bt_close_connection(Bt* bt) { furi_event_flag_set(bt->api_event, BT_API_UNLOCK_EVENT); } +static inline FuriHalBtProfile get_hal_bt_profile(BtProfile profile) { + if(profile == BtProfileHidKeyboard) { + return FuriHalBtProfileHidKeyboard; + } else { + return FuriHalBtProfileSerial; + } +} + +void bt_restart(Bt* bt) { + furi_hal_bt_change_app(get_hal_bt_profile(bt->profile), bt_on_gap_event_callback, bt); + furi_hal_bt_start_advertising(); +} + +void bt_set_profile_adv_name(Bt* bt, const char* fmt, ...) { + furi_assert(bt); + furi_assert(fmt); + + char name[FURI_HAL_BT_ADV_NAME_LENGTH]; + va_list args; + va_start(args, fmt); + vsnprintf(name, sizeof(name), fmt, args); + va_end(args); + furi_hal_bt_set_profile_adv_name(get_hal_bt_profile(bt->profile), name); + + bt_restart(bt); +} + +const char* bt_get_profile_adv_name(Bt* bt) { + furi_assert(bt); + return furi_hal_bt_get_profile_adv_name(get_hal_bt_profile(bt->profile)); +} + +void bt_set_profile_mac_address(Bt* bt, const uint8_t mac[6]) { + furi_assert(bt); + furi_assert(mac); + + furi_hal_bt_set_profile_mac_addr(get_hal_bt_profile(bt->profile), mac); + + bt_restart(bt); +} + +const uint8_t* bt_get_profile_mac_address(Bt* bt) { + furi_assert(bt); + return furi_hal_bt_get_profile_mac_addr(get_hal_bt_profile(bt->profile)); +} + +bool bt_remote_rssi(Bt* bt, uint8_t* rssi) { + furi_assert(bt); + + uint8_t rssi_val; + uint32_t since = furi_hal_bt_get_conn_rssi(&rssi_val); + + if(since == 0) return false; + + *rssi = rssi_val; + + return true; +} + +void bt_set_profile_pairing_method(Bt* bt, GapPairing pairing_method) { + furi_assert(bt); + furi_hal_bt_set_profile_pairing_method(get_hal_bt_profile(bt->profile), pairing_method); + bt_restart(bt); +} + +GapPairing bt_get_profile_pairing_method(Bt* bt) { + furi_assert(bt); + return furi_hal_bt_get_profile_pairing_method(get_hal_bt_profile(bt->profile)); +} + +void bt_disable_peer_key_update(Bt* bt) { + UNUSED(bt); + furi_hal_bt_set_key_storage_change_callback(NULL, NULL); +} + +void bt_enable_peer_key_update(Bt* bt) { + furi_assert(bt); + furi_hal_bt_set_key_storage_change_callback(bt_on_key_storage_change_callback, bt); +} + int32_t bt_srv(void* p) { UNUSED(p); Bt* bt = bt_alloc(); diff --git a/applications/services/bt/bt_service/bt.h b/applications/services/bt/bt_service/bt.h index ca47936db..3d96ce603 100644 --- a/applications/services/bt/bt_service/bt.h +++ b/applications/services/bt/bt_service/bt.h @@ -3,6 +3,8 @@ #include #include +#include + #ifdef __cplusplus extern "C" { #endif @@ -23,6 +25,11 @@ typedef enum { BtProfileHidKeyboard, } BtProfile; +typedef struct { + uint8_t rssi; + uint32_t since; +} BtRssi; + typedef void (*BtStatusChangedCallback)(BtStatus status, void* context); /** Change BLE Profile @@ -69,6 +76,32 @@ void bt_keys_storage_set_storage_path(Bt* bt, const char* keys_storage_path); */ void bt_keys_storage_set_default_path(Bt* bt); +// New methods + +void bt_set_profile_adv_name(Bt* bt, const char* fmt, ...); + +const char* bt_get_profile_adv_name(Bt* bt); + +void bt_set_profile_mac_address(Bt* bt, const uint8_t mac[6]); + +const uint8_t* bt_get_profile_mac_address(Bt* bt); + +bool bt_remote_rssi(Bt* bt, uint8_t* rssi); + +void bt_set_profile_pairing_method(Bt* bt, GapPairing pairing_method); +GapPairing bt_get_profile_pairing_method(Bt* bt); + +/** Stop saving new peer key to flash (in .bt.keys file) + * +*/ +void bt_disable_peer_key_update(Bt* bt); + +/** Enable saving peer key to internal flash (enable by default) + * + * @note This function should be called if bt_disable_peer_key_update was called before +*/ +void bt_enable_peer_key_update(Bt* bt); + #ifdef __cplusplus } #endif diff --git a/applications/services/bt/bt_service/bt_i.h b/applications/services/bt/bt_service/bt_i.h index c8a0e9965..41e5bcd8c 100644 --- a/applications/services/bt/bt_service/bt_i.h +++ b/applications/services/bt/bt_service/bt_i.h @@ -76,4 +76,6 @@ struct Bt { FuriEventFlag* api_event; BtStatusChangedCallback status_changed_cb; void* status_changed_ctx; + uint32_t pin; + bool suppress_pin_screen; }; diff --git a/applications/services/desktop/desktop.c b/applications/services/desktop/desktop.c index c82ecac87..77999dfcc 100644 --- a/applications/services/desktop/desktop.c +++ b/applications/services/desktop/desktop.c @@ -6,6 +6,8 @@ #include #include #include +#include +#include #include "animations/animation_manager.h" #include "desktop/scenes/desktop_scene.h" @@ -14,7 +16,7 @@ #include "desktop/views/desktop_view_pin_input.h" #include "desktop/views/desktop_view_pin_timeout.h" #include "desktop_i.h" -#include "helpers/pin_lock.h" +#include "helpers/pin.h" #include "helpers/slideshow_filename.h" #define TAG "Desktop" @@ -70,9 +72,6 @@ static bool desktop_custom_event_callback(void* context, uint32_t event) { return true; case DesktopGlobalAutoLock: if(!loader_is_locked(desktop->loader)) { - if(desktop->settings.auto_lock_with_pin && desktop->settings.pin_code.length > 0) { - desktop_pin_lock(&desktop->settings); - } desktop_lock(desktop); } return true; @@ -135,6 +134,14 @@ static void desktop_auto_lock_inhibit(Desktop* desktop) { } void desktop_lock(Desktop* desktop) { + furi_hal_rtc_set_flag(FuriHalRtcFlagLock); + + if(desktop->settings.pin_code.length) { + Cli* cli = furi_record_open(RECORD_CLI); + cli_session_close(cli); + furi_record_close(RECORD_CLI); + } + desktop_auto_lock_inhibit(desktop); scene_manager_set_scene_state( desktop->scene_manager, DesktopSceneLocked, SCENE_LOCKED_FIRST_ENTER); @@ -150,6 +157,14 @@ void desktop_unlock(Desktop* desktop) { desktop_view_locked_unlock(desktop->locked_view); scene_manager_search_and_switch_to_previous_scene(desktop->scene_manager, DesktopSceneMain); desktop_auto_lock_arm(desktop); + furi_hal_rtc_reset_flag(FuriHalRtcFlagLock); + furi_hal_rtc_set_pin_fails(0); + + if(desktop->settings.pin_code.length) { + Cli* cli = furi_record_open(RECORD_CLI); + cli_session_open(cli, &cli_vcp); + furi_record_close(RECORD_CLI); + } } void desktop_set_dummy_mode_state(Desktop* desktop, bool enabled) { @@ -293,11 +308,14 @@ Desktop* desktop_alloc() { desktop->auto_lock_timer = furi_timer_alloc(desktop_auto_lock_timer_callback, FuriTimerTypeOnce, desktop); + furi_record_create(RECORD_DESKTOP, desktop); + return desktop; } void desktop_free(Desktop* desktop) { furi_assert(desktop); + furi_check(furi_record_destroy(RECORD_DESKTOP)); furi_pubsub_unsubscribe( loader_get_pubsub(desktop->loader), desktop->app_start_stop_subscription); @@ -355,6 +373,16 @@ static bool desktop_check_file_flag(const char* flag_path) { return exists; } +bool desktop_api_is_locked(Desktop* instance) { + furi_assert(instance); + return furi_hal_rtc_is_flag_set(FuriHalRtcFlagLock); +} + +void desktop_api_unlock(Desktop* instance) { + furi_assert(instance); + view_dispatcher_send_custom_event(instance->view_dispatcher, DesktopLockedEventUnlocked); +} + int32_t desktop_srv(void* p) { UNUSED(p); @@ -378,14 +406,12 @@ int32_t desktop_srv(void* p) { scene_manager_next_scene(desktop->scene_manager, DesktopSceneMain); - desktop_pin_lock_init(&desktop->settings); - - if(!desktop_pin_lock_is_locked()) { + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagLock)) { + desktop_lock(desktop); + } else { if(!loader_is_locked(desktop->loader)) { desktop_auto_lock_arm(desktop); } - } else { - desktop_lock(desktop); } if(desktop_check_file_flag(SLIDESHOW_FS_PATH)) { diff --git a/applications/services/desktop/desktop.h b/applications/services/desktop/desktop.h index f5608207d..5b12647b8 100644 --- a/applications/services/desktop/desktop.h +++ b/applications/services/desktop/desktop.h @@ -1,3 +1,9 @@ #pragma once typedef struct Desktop Desktop; + +#define RECORD_DESKTOP "desktop" + +bool desktop_api_is_locked(Desktop* instance); + +void desktop_api_unlock(Desktop* instance); diff --git a/applications/services/desktop/desktop_settings.h b/applications/services/desktop/desktop_settings.h index 4b4bca000..e750c696c 100644 --- a/applications/services/desktop/desktop_settings.h +++ b/applications/services/desktop/desktop_settings.h @@ -8,7 +8,7 @@ #include #include -#define DESKTOP_SETTINGS_VER (8) +#define DESKTOP_SETTINGS_VER (9) #define DESKTOP_SETTINGS_PATH INT_PATH(DESKTOP_SETTINGS_FILE_NAME) #define DESKTOP_SETTINGS_MAGIC (0x17) @@ -60,9 +60,7 @@ typedef struct { FavoriteApp favorite_secondary; FavoriteApp favorite_tertiary; PinCode pin_code; - uint8_t is_locked; uint32_t auto_lock_delay_ms; uint8_t displayBatteryPercentage; uint8_t dummy_mode; - bool auto_lock_with_pin; } DesktopSettings; diff --git a/applications/services/desktop/helpers/pin.c b/applications/services/desktop/helpers/pin.c new file mode 100644 index 000000000..8a79a1fb8 --- /dev/null +++ b/applications/services/desktop/helpers/pin.c @@ -0,0 +1,74 @@ +#include "pin.h" + +#include +#include +#include +#include +#include +#include + +#include "../desktop_i.h" + +static const NotificationSequence sequence_pin_fail = { + &message_display_backlight_on, + + &message_red_255, + &message_vibro_on, + &message_delay_100, + &message_vibro_off, + &message_red_0, + + &message_delay_250, + + &message_red_255, + &message_vibro_on, + &message_delay_100, + &message_vibro_off, + &message_red_0, + NULL, +}; + +static const uint8_t desktop_helpers_fails_timeout[] = { + 0, + 0, + 0, + 0, + 30, + 60, + 90, + 120, + 150, + 180, + /* +60 for every next fail */ +}; + +void desktop_pin_lock_error_notify() { + NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); + notification_message(notification, &sequence_pin_fail); + furi_record_close(RECORD_NOTIFICATION); +} + +uint32_t desktop_pin_lock_get_fail_timeout() { + uint32_t pin_fails = furi_hal_rtc_get_pin_fails(); + uint32_t pin_timeout = 0; + uint32_t max_index = COUNT_OF(desktop_helpers_fails_timeout) - 1; + if(pin_fails <= max_index) { + pin_timeout = desktop_helpers_fails_timeout[pin_fails]; + } else { + pin_timeout = desktop_helpers_fails_timeout[max_index] + (pin_fails - max_index) * 60; + } + + return pin_timeout; +} + +bool desktop_pin_compare(const PinCode* pin_code1, const PinCode* pin_code2) { + furi_assert(pin_code1); + furi_assert(pin_code2); + bool result = false; + + if(pin_code1->length == pin_code2->length) { + result = !memcmp(pin_code1->data, pin_code2->data, pin_code1->length); + } + + return result; +} diff --git a/applications/services/desktop/helpers/pin.h b/applications/services/desktop/helpers/pin.h new file mode 100644 index 000000000..e5410723e --- /dev/null +++ b/applications/services/desktop/helpers/pin.h @@ -0,0 +1,11 @@ +#pragma once +#include +#include +#include "../desktop.h" +#include + +void desktop_pin_lock_error_notify(); + +uint32_t desktop_pin_lock_get_fail_timeout(); + +bool desktop_pin_compare(const PinCode* pin_code1, const PinCode* pin_code2); diff --git a/applications/services/desktop/helpers/pin_lock.c b/applications/services/desktop/helpers/pin_lock.c deleted file mode 100644 index 22fcabe7d..000000000 --- a/applications/services/desktop/helpers/pin_lock.c +++ /dev/null @@ -1,140 +0,0 @@ - -#include -#include -#include -#include -#include -#include - -#include "../helpers/pin_lock.h" -#include "../desktop_i.h" -#include -#include - -static const NotificationSequence sequence_pin_fail = { - &message_display_backlight_on, - - &message_red_255, - &message_vibro_on, - &message_delay_100, - &message_vibro_off, - &message_red_0, - - &message_delay_250, - - &message_red_255, - &message_vibro_on, - &message_delay_100, - &message_vibro_off, - &message_red_0, - NULL, -}; - -static const uint8_t desktop_helpers_fails_timeout[] = { - 0, - 0, - 0, - 0, - 30, - 60, - 90, - 120, - 150, - 180, - /* +60 for every next fail */ -}; - -void desktop_pin_lock_error_notify() { - NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); - notification_message(notification, &sequence_pin_fail); - furi_record_close(RECORD_NOTIFICATION); -} - -uint32_t desktop_pin_lock_get_fail_timeout() { - uint32_t pin_fails = furi_hal_rtc_get_pin_fails(); - uint32_t pin_timeout = 0; - uint32_t max_index = COUNT_OF(desktop_helpers_fails_timeout) - 1; - if(pin_fails <= max_index) { - pin_timeout = desktop_helpers_fails_timeout[pin_fails]; - } else { - pin_timeout = desktop_helpers_fails_timeout[max_index] + (pin_fails - max_index) * 60; - } - - return pin_timeout; -} - -void desktop_pin_lock(DesktopSettings* settings) { - furi_assert(settings); - - furi_hal_rtc_set_pin_fails(0); - furi_hal_rtc_set_flag(FuriHalRtcFlagLock); - Cli* cli = furi_record_open(RECORD_CLI); - cli_session_close(cli); - furi_record_close(RECORD_CLI); - settings->is_locked = 1; - DESKTOP_SETTINGS_SAVE(settings); -} - -void desktop_pin_unlock(DesktopSettings* settings) { - furi_assert(settings); - - furi_hal_rtc_reset_flag(FuriHalRtcFlagLock); - Cli* cli = furi_record_open(RECORD_CLI); - cli_session_open(cli, &cli_vcp); - furi_record_close(RECORD_CLI); - settings->is_locked = 0; - DESKTOP_SETTINGS_SAVE(settings); -} - -void desktop_pin_lock_init(DesktopSettings* settings) { - furi_assert(settings); - - if(settings->pin_code.length > 0) { - if(settings->is_locked == 1) { - furi_hal_rtc_set_flag(FuriHalRtcFlagLock); - } else { - if(desktop_pin_lock_is_locked()) { - settings->is_locked = 1; - DESKTOP_SETTINGS_SAVE(settings); - } - } - } else { - furi_hal_rtc_set_pin_fails(0); - furi_hal_rtc_reset_flag(FuriHalRtcFlagLock); - } - - if(desktop_pin_lock_is_locked()) { - Cli* cli = furi_record_open(RECORD_CLI); - cli_session_close(cli); - furi_record_close(RECORD_CLI); - } -} - -bool desktop_pin_lock_verify(const PinCode* pin_set, const PinCode* pin_entered) { - bool result = false; - if(desktop_pins_are_equal(pin_set, pin_entered)) { - furi_hal_rtc_set_pin_fails(0); - result = true; - } else { - uint32_t pin_fails = furi_hal_rtc_get_pin_fails(); - furi_hal_rtc_set_pin_fails(pin_fails + 1); - result = false; - } - return result; -} - -bool desktop_pin_lock_is_locked() { - return furi_hal_rtc_is_flag_set(FuriHalRtcFlagLock); -} - -bool desktop_pins_are_equal(const PinCode* pin_code1, const PinCode* pin_code2) { - furi_assert(pin_code1); - furi_assert(pin_code2); - bool result = false; - - if(pin_code1->length == pin_code2->length) { - result = !memcmp(pin_code1->data, pin_code2->data, pin_code1->length); - } - - return result; -} diff --git a/applications/services/desktop/helpers/pin_lock.h b/applications/services/desktop/helpers/pin_lock.h deleted file mode 100644 index 028ae6d22..000000000 --- a/applications/services/desktop/helpers/pin_lock.h +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once -#include -#include -#include "../desktop.h" -#include - -void desktop_pin_lock_error_notify(); - -uint32_t desktop_pin_lock_get_fail_timeout(); - -void desktop_pin_lock(DesktopSettings* settings); - -void desktop_pin_unlock(DesktopSettings* settings); - -bool desktop_pin_lock_is_locked(); - -void desktop_pin_lock_init(DesktopSettings* settings); - -bool desktop_pin_lock_verify(const PinCode* pin_set, const PinCode* pin_entered); - -bool desktop_pins_are_equal(const PinCode* pin_code1, const PinCode* pin_code2); diff --git a/applications/services/desktop/scenes/desktop_scene_lock_menu.c b/applications/services/desktop/scenes/desktop_scene_lock_menu.c index bfaa8a036..105b2b37a 100644 --- a/applications/services/desktop/scenes/desktop_scene_lock_menu.c +++ b/applications/services/desktop/scenes/desktop_scene_lock_menu.c @@ -10,7 +10,7 @@ #include "../views/desktop_view_lock_menu.h" #include "desktop_scene_i.h" #include "desktop_scene.h" -#include "../helpers/pin_lock.h" +#include "../helpers/pin.h" #define TAG "DesktopSceneLock" @@ -25,7 +25,6 @@ void desktop_scene_lock_menu_on_enter(void* context) { DESKTOP_SETTINGS_LOAD(&desktop->settings); scene_manager_set_scene_state(desktop->scene_manager, DesktopSceneLockMenu, 0); desktop_lock_menu_set_callback(desktop->lock_menu, desktop_scene_lock_menu_callback, desktop); - desktop_lock_menu_set_pin_state(desktop->lock_menu, desktop->settings.pin_code.length > 0); desktop_lock_menu_set_dummy_mode_state(desktop->lock_menu, desktop->settings.dummy_mode); desktop_lock_menu_set_stealth_mode_state( desktop->lock_menu, furi_hal_rtc_is_flag_set(FuriHalRtcFlagStealthMode)); @@ -44,7 +43,6 @@ bool desktop_scene_lock_menu_on_event(void* context, SceneManagerEvent event) { if(check_pin_changed) { DESKTOP_SETTINGS_LOAD(&desktop->settings); if(desktop->settings.pin_code.length > 0) { - desktop_lock_menu_set_pin_state(desktop->lock_menu, true); scene_manager_set_scene_state(desktop->scene_manager, DesktopSceneLockMenu, 0); } } @@ -55,21 +53,6 @@ bool desktop_scene_lock_menu_on_event(void* context, SceneManagerEvent event) { desktop_lock(desktop); consumed = true; break; - case DesktopLockMenuEventPinLock: - if(desktop->settings.pin_code.length > 0) { - desktop_pin_lock(&desktop->settings); - desktop_lock(desktop); - } else { - LoaderStatus status = - loader_start(desktop->loader, "Desktop", DESKTOP_SETTINGS_RUN_PIN_SETUP_ARG); - if(status == LoaderStatusOk) { - scene_manager_set_scene_state(desktop->scene_manager, DesktopSceneLockMenu, 1); - } else { - FURI_LOG_E(TAG, "Unable to start desktop settings"); - } - } - consumed = true; - break; case DesktopLockMenuEventDummyModeOn: desktop_set_dummy_mode_state(desktop, true); scene_manager_search_and_switch_to_previous_scene( diff --git a/applications/services/desktop/scenes/desktop_scene_locked.c b/applications/services/desktop/scenes/desktop_scene_locked.c index af19efc74..f64ef8371 100644 --- a/applications/services/desktop/scenes/desktop_scene_locked.c +++ b/applications/services/desktop/scenes/desktop_scene_locked.c @@ -7,7 +7,7 @@ #include "../desktop.h" #include "../desktop_i.h" -#include "../helpers/pin_lock.h" +#include "../helpers/pin.h" #include "../animations/animation_manager.h" #include "../views/desktop_events.h" #include "../views/desktop_view_pin_input.h" @@ -45,7 +45,7 @@ void desktop_scene_locked_on_enter(void* context) { bool switch_to_timeout_scene = false; uint32_t state = scene_manager_get_scene_state(desktop->scene_manager, DesktopSceneLocked); if(state == SCENE_LOCKED_FIRST_ENTER) { - bool pin_locked = desktop_pin_lock_is_locked(); + bool pin_locked = desktop->settings.pin_code.length > 0; view_port_enabled_set(desktop->lock_icon_viewport, true); Gui* gui = furi_record_open(RECORD_GUI); gui_set_lockdown(gui, true); diff --git a/applications/services/desktop/scenes/desktop_scene_main.c b/applications/services/desktop/scenes/desktop_scene_main.c index 9109ef309..f504646bf 100644 --- a/applications/services/desktop/scenes/desktop_scene_main.c +++ b/applications/services/desktop/scenes/desktop_scene_main.c @@ -9,7 +9,6 @@ #include "../views/desktop_view_main.h" #include "desktop_scene.h" #include "desktop_scene_i.h" -#include "../helpers/pin_lock.h" #define TAG "DesktopSrv" @@ -108,13 +107,8 @@ bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) { break; case DesktopMainEventLock: - if(desktop->settings.pin_code.length > 0) { - desktop_pin_lock(&desktop->settings); - desktop_lock(desktop); - } else { - scene_manager_set_scene_state(desktop->scene_manager, DesktopSceneLockMenu, 0); - desktop_lock(desktop); - } + scene_manager_set_scene_state(desktop->scene_manager, DesktopSceneLockMenu, 0); + desktop_lock(desktop); consumed = true; break; diff --git a/applications/services/desktop/scenes/desktop_scene_pin_input.c b/applications/services/desktop/scenes/desktop_scene_pin_input.c index 9392309e6..e062c1b97 100644 --- a/applications/services/desktop/scenes/desktop_scene_pin_input.c +++ b/applications/services/desktop/scenes/desktop_scene_pin_input.c @@ -12,7 +12,7 @@ #include "../animations/animation_manager.h" #include "../views/desktop_events.h" #include "../views/desktop_view_pin_input.h" -#include "../helpers/pin_lock.h" +#include "../helpers/pin.h" #include "desktop_scene.h" #include "desktop_scene_i.h" @@ -54,9 +54,11 @@ static void desktop_scene_pin_input_back_callback(void* context) { static void desktop_scene_pin_input_done_callback(const PinCode* pin_code, void* context) { Desktop* desktop = (Desktop*)context; - if(desktop_pin_lock_verify(&desktop->settings.pin_code, pin_code)) { + if(desktop_pin_compare(&desktop->settings.pin_code, pin_code)) { view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopPinInputEventUnlocked); } else { + uint32_t pin_fails = furi_hal_rtc_get_pin_fails(); + furi_hal_rtc_set_pin_fails(pin_fails + 1); view_dispatcher_send_custom_event( desktop->view_dispatcher, DesktopPinInputEventUnlockFailed); } @@ -126,7 +128,6 @@ bool desktop_scene_pin_input_on_event(void* context, SceneManagerEvent event) { consumed = true; break; case DesktopPinInputEventUnlocked: - desktop_pin_unlock(&desktop->settings); desktop_unlock(desktop); consumed = true; break; diff --git a/applications/services/desktop/views/desktop_events.h b/applications/services/desktop/views/desktop_events.h index cdabe5b33..fd72457b0 100644 --- a/applications/services/desktop/views/desktop_events.h +++ b/applications/services/desktop/views/desktop_events.h @@ -36,7 +36,6 @@ typedef enum { DesktopDebugEventExit, DesktopLockMenuEventLock, - DesktopLockMenuEventPinLock, DesktopLockMenuEventDummyModeOn, DesktopLockMenuEventDummyModeOff, DesktopLockMenuEventStealthModeOn, diff --git a/applications/services/desktop/views/desktop_view_lock_menu.c b/applications/services/desktop/views/desktop_view_lock_menu.c index 8bf868ebf..8b0eb3760 100644 --- a/applications/services/desktop/views/desktop_view_lock_menu.c +++ b/applications/services/desktop/views/desktop_view_lock_menu.c @@ -23,14 +23,6 @@ void desktop_lock_menu_set_callback( lock_menu->context = context; } -void desktop_lock_menu_set_pin_state(DesktopLockMenuView* lock_menu, bool pin_is_set) { - with_view_model( - lock_menu->view, - DesktopLockMenuViewModel * model, - { model->pin_is_set = pin_is_set; }, - true); -} - void desktop_lock_menu_set_dummy_mode_state(DesktopLockMenuView* lock_menu, bool dummy_mode) { with_view_model( lock_menu->view, @@ -102,7 +94,6 @@ bool desktop_lock_menu_input_callback(InputEvent* event, void* context) { bool consumed = false; bool dummy_mode = false; bool stealth_mode = false; - bool pin_is_set = false; bool update = false; with_view_model( @@ -131,15 +122,12 @@ bool desktop_lock_menu_input_callback(InputEvent* event, void* context) { idx = model->idx; dummy_mode = model->dummy_mode; stealth_mode = model->stealth_mode; - pin_is_set = model->pin_is_set; }, update); if(event->key == InputKeyOk) { if((idx == DesktopLockMenuIndexLock)) { - if((pin_is_set) && (event->type == InputTypeLong)) { - lock_menu->callback(DesktopLockMenuEventPinLock, lock_menu->context); - } else if(event->type == InputTypeShort) { + if(event->type == InputTypeShort) { lock_menu->callback(DesktopLockMenuEventLock, lock_menu->context); } } else if(idx == DesktopLockMenuIndexStealth) { diff --git a/applications/services/desktop/views/desktop_view_lock_menu.h b/applications/services/desktop/views/desktop_view_lock_menu.h index 03ce6fa80..8ac3a7273 100644 --- a/applications/services/desktop/views/desktop_view_lock_menu.h +++ b/applications/services/desktop/views/desktop_view_lock_menu.h @@ -17,7 +17,6 @@ struct DesktopLockMenuView { typedef struct { uint8_t idx; - bool pin_is_set; bool dummy_mode; bool stealth_mode; } DesktopLockMenuViewModel; @@ -28,7 +27,6 @@ void desktop_lock_menu_set_callback( void* context); View* desktop_lock_menu_get_view(DesktopLockMenuView* lock_menu); -void desktop_lock_menu_set_pin_state(DesktopLockMenuView* lock_menu, bool pin_is_set); void desktop_lock_menu_set_dummy_mode_state(DesktopLockMenuView* lock_menu, bool dummy_mode); void desktop_lock_menu_set_stealth_mode_state(DesktopLockMenuView* lock_menu, bool stealth_mode); void desktop_lock_menu_set_idx(DesktopLockMenuView* lock_menu, uint8_t idx); diff --git a/applications/services/desktop/views/desktop_view_pin_setup_done.c b/applications/services/desktop/views/desktop_view_pin_setup_done.c deleted file mode 100644 index 561b12861..000000000 --- a/applications/services/desktop/views/desktop_view_pin_setup_done.c +++ /dev/null @@ -1,79 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../desktop_i.h" -#include "desktop_view_pin_setup_done.h" - -struct DesktopViewPinSetupDone { - View* view; - DesktopViewPinSetupDoneDoneCallback callback; - void* context; -}; - -static void desktop_view_pin_done_draw(Canvas* canvas, void* model) { - furi_assert(canvas); - UNUSED(model); - - canvas_set_font(canvas, FontPrimary); - elements_multiline_text_aligned( - canvas, 64, 0, AlignCenter, AlignTop, "Prepare to use\narrows as\nPIN symbols"); - - canvas_set_font(canvas, FontSecondary); - elements_multiline_text(canvas, 58, 24, "Prepare to use\narrows as\nPIN symbols"); - - canvas_draw_icon(canvas, 16, 18, &I_Pin_attention_dpad_29x29); - elements_button_right(canvas, "Next"); -} - -static bool desktop_view_pin_done_input(InputEvent* event, void* context) { - furi_assert(event); - furi_assert(context); - - DesktopViewPinSetupDone* instance = context; - bool consumed = false; - - if((event->key == InputKeyRight) && (event->type == InputTypeShort)) { - instance->callback(instance->context); - consumed = true; - } - - return consumed; -} - -void desktop_view_pin_done_set_callback( - DesktopViewPinSetupDone* instance, - DesktopViewPinSetupDoneDoneCallback callback, - void* context) { - furi_assert(instance); - furi_assert(callback); - instance->callback = callback; - instance->context = context; -} - -DesktopViewPinSetupDone* desktop_view_pin_done_alloc() { - DesktopViewPinSetupDone* view = malloc(sizeof(DesktopViewPinSetupDone)); - view->view = view_alloc(); - view_set_context(view->view, view); - view_set_draw_callback(view->view, desktop_view_pin_done_draw); - view_set_input_callback(view->view, desktop_view_pin_done_input); - - return view; -} - -void desktop_view_pin_done_free(DesktopViewPinSetupDone* instance) { - furi_assert(instance); - - view_free(instance->view); - free(instance); -} - -View* desktop_view_pin_done_get_view(DesktopViewPinSetupDone* instance) { - furi_assert(instance); - return instance->view; -} diff --git a/applications/services/desktop/views/desktop_view_pin_setup_done.h b/applications/services/desktop/views/desktop_view_pin_setup_done.h deleted file mode 100644 index b55677dc5..000000000 --- a/applications/services/desktop/views/desktop_view_pin_setup_done.h +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once - -#include - -typedef struct DesktopViewPinSetupDone DesktopViewPinSetupDone; - -typedef void (*DesktopViewPinSetupDoneDoneCallback)(void*); - -void desktop_view_pin_done_set_callback( - DesktopViewPinSetupDone* instance, - DesktopViewPinSetupDoneDoneCallback callback, - void* context); -DesktopViewPinSetupDone* desktop_view_pin_done_alloc(); -void desktop_view_pin_done_free(DesktopViewPinSetupDone* instance); -View* desktop_view_pin_done_get_view(DesktopViewPinSetupDone* instance); diff --git a/applications/services/gui/elements.c b/applications/services/gui/elements.c index 90682d68e..4d990c14e 100644 --- a/applications/services/gui/elements.c +++ b/applications/services/gui/elements.c @@ -573,6 +573,64 @@ void elements_string_fit_width(Canvas* canvas, FuriString* string, uint8_t width } } +void elements_scrollable_text_line_str( + Canvas* canvas, + uint8_t x, + uint8_t y, + uint8_t width, + const char* string, + size_t scroll, + bool ellipsis, + bool centered) { + FuriString* line = furi_string_alloc_set_str(string); + + size_t len_px = canvas_string_width(canvas, furi_string_get_cstr(line)); + if(len_px > width) { + if(centered) { + centered = false; + x -= width / 2; + } + + if(ellipsis) { + width -= canvas_string_width(canvas, "..."); + } + + // Calculate scroll size + size_t scroll_size = furi_string_size(line); + size_t right_width = 0; + for(size_t i = scroll_size - 1; i > 0; i--) { + right_width += canvas_glyph_width(canvas, furi_string_get_char(line, i)); + if(right_width > width) break; + scroll_size--; + if(!scroll_size) break; + } + // Ensure that we have something to scroll + if(scroll_size) { + scroll_size += 3; + scroll = scroll % scroll_size; + furi_string_right(line, scroll); + } + + len_px = canvas_string_width(canvas, furi_string_get_cstr(line)); + while(len_px > width) { + furi_string_left(line, furi_string_size(line) - 1); + len_px = canvas_string_width(canvas, furi_string_get_cstr(line)); + } + + if(ellipsis) { + furi_string_cat(line, "..."); + } + } + + if(centered) { + canvas_draw_str_aligned( + canvas, x, y, AlignCenter, AlignBottom, furi_string_get_cstr(line)); + } else { + canvas_draw_str(canvas, x, y, furi_string_get_cstr(line)); + } + furi_string_free(line); +} + void elements_scrollable_text_line( Canvas* canvas, uint8_t x, diff --git a/applications/services/gui/elements.h b/applications/services/gui/elements.h index 04ca357b8..bdc43d09a 100644 --- a/applications/services/gui/elements.h +++ b/applications/services/gui/elements.h @@ -228,6 +228,16 @@ void elements_scrollable_text_line( size_t scroll, bool ellipsis); +void elements_scrollable_text_line_str( + Canvas* canvas, + uint8_t x, + uint8_t y, + uint8_t width, + const char* string, + size_t scroll, + bool ellipsis, + bool centered); + /** Draw text box element * * @param canvas Canvas instance diff --git a/applications/services/gui/modules/popup.h b/applications/services/gui/modules/popup.h index 13371a05d..ca783fdb7 100644 --- a/applications/services/gui/modules/popup.h +++ b/applications/services/gui/modules/popup.h @@ -41,7 +41,7 @@ void popup_free(Popup* popup); */ View* popup_get_view(Popup* popup); -/** Set popup header text +/** Set popup callback function * * @param popup Popup instance * @param callback PopupCallback diff --git a/applications/services/gui/modules/submenu.c b/applications/services/gui/modules/submenu.c index 66b264458..5f47ac179 100644 --- a/applications/services/gui/modules/submenu.c +++ b/applications/services/gui/modules/submenu.c @@ -1,11 +1,14 @@ #include "submenu.h" +#include +#include #include #include #include struct Submenu { View* view; + FuriTimer* locked_timer; }; typedef struct { @@ -13,6 +16,8 @@ typedef struct { uint32_t index; SubmenuItemCallback callback; void* callback_context; + bool locked; + FuriString* locked_message; } SubmenuItem; static void SubmenuItem_init(SubmenuItem* item) { @@ -20,6 +25,8 @@ static void SubmenuItem_init(SubmenuItem* item) { item->index = 0; item->callback = NULL; item->callback_context = NULL; + item->locked = false; + item->locked_message = furi_string_alloc(); } static void SubmenuItem_init_set(SubmenuItem* item, const SubmenuItem* src) { @@ -27,6 +34,8 @@ static void SubmenuItem_init_set(SubmenuItem* item, const SubmenuItem* src) { item->index = src->index; item->callback = src->callback; item->callback_context = src->callback_context; + item->locked = src->locked; + item->locked_message = furi_string_alloc_set(src->locked_message); } static void SubmenuItem_set(SubmenuItem* item, const SubmenuItem* src) { @@ -34,10 +43,13 @@ static void SubmenuItem_set(SubmenuItem* item, const SubmenuItem* src) { item->index = src->index; item->callback = src->callback; item->callback_context = src->callback_context; + item->locked = src->locked; + furi_string_set(item->locked_message, src->locked_message); } static void SubmenuItem_clear(SubmenuItem* item) { furi_string_free(item->label); + furi_string_free(item->locked_message); } ARRAY_DEF( @@ -53,6 +65,7 @@ typedef struct { FuriString* header; size_t position; size_t window_position; + bool locked_message_visible; } SubmenuModel; static void submenu_process_up(Submenu* submenu); @@ -63,7 +76,12 @@ static void submenu_view_draw_callback(Canvas* canvas, void* _model) { SubmenuModel* model = _model; const uint8_t item_height = 16; - const uint8_t item_width = 123; + uint8_t item_width = 123; + + if(canvas->orientation == CanvasOrientationVertical || + canvas->orientation == CanvasOrientationVerticalFlip) { + item_width = 60; + } canvas_clear(canvas); @@ -96,9 +114,18 @@ static void submenu_view_draw_callback(Canvas* canvas, void* _model) { canvas_set_color(canvas, ColorBlack); } + if(SubmenuItemArray_cref(it)->locked) { + canvas_draw_icon( + canvas, + 110, + y_offset + (item_position * item_height) + item_height - 12, + &I_Lock_7x8); + } + FuriString* disp_str; disp_str = furi_string_alloc_set(SubmenuItemArray_cref(it)->label); - elements_string_fit_width(canvas, disp_str, item_width - 11); + elements_string_fit_width( + canvas, disp_str, item_width - (SubmenuItemArray_cref(it)->locked ? 25 : 11)); canvas_draw_str( canvas, @@ -113,6 +140,23 @@ static void submenu_view_draw_callback(Canvas* canvas, void* _model) { } elements_scrollbar(canvas, model->position, SubmenuItemArray_size(model->items)); + + if(model->locked_message_visible) { + canvas_set_color(canvas, ColorWhite); + canvas_draw_box(canvas, 8, 10, 110, 48); + canvas_set_color(canvas, ColorBlack); + canvas_draw_icon(canvas, 10, 14, &I_WarningDolphin_45x42); + canvas_draw_rframe(canvas, 8, 8, 112, 50, 3); + canvas_draw_rframe(canvas, 9, 9, 110, 48, 2); + elements_multiline_text_aligned( + canvas, + 84, + 32, + AlignCenter, + AlignCenter, + furi_string_get_cstr( + SubmenuItemArray_get(model->items, model->position)->locked_message)); + } } static bool submenu_view_input_callback(InputEvent* event, void* context) { @@ -120,7 +164,19 @@ static bool submenu_view_input_callback(InputEvent* event, void* context) { furi_assert(submenu); bool consumed = false; - if(event->type == InputTypeShort) { + bool locked_message_visible = false; + with_view_model( + submenu->view, + SubmenuModel * model, + { locked_message_visible = model->locked_message_visible; }, + false); + + if((!(event->type == InputTypePress) && !(event->type == InputTypeRelease)) && + locked_message_visible) { + with_view_model( + submenu->view, SubmenuModel * model, { model->locked_message_visible = false; }, true); + consumed = true; + } else if(event->type == InputTypeShort) { switch(event->key) { case InputKeyUp: consumed = true; @@ -150,6 +206,14 @@ static bool submenu_view_input_callback(InputEvent* event, void* context) { return consumed; } +void submenu_timer_callback(void* context) { + furi_assert(context); + Submenu* submenu = context; + + with_view_model( + submenu->view, SubmenuModel * model, { model->locked_message_visible = false; }, true); +} + Submenu* submenu_alloc() { Submenu* submenu = malloc(sizeof(Submenu)); submenu->view = view_alloc(); @@ -158,6 +222,8 @@ Submenu* submenu_alloc() { view_set_draw_callback(submenu->view, submenu_view_draw_callback); view_set_input_callback(submenu->view, submenu_view_input_callback); + submenu->locked_timer = furi_timer_alloc(submenu_timer_callback, FuriTimerTypeOnce, submenu); + with_view_model( submenu->view, SubmenuModel * model, @@ -183,6 +249,8 @@ void submenu_free(Submenu* submenu) { SubmenuItemArray_clear(model->items); }, true); + furi_timer_stop(submenu->locked_timer); + furi_timer_free(submenu->locked_timer); view_free(submenu->view); free(submenu); } @@ -198,9 +266,23 @@ void submenu_add_item( uint32_t index, SubmenuItemCallback callback, void* callback_context) { + submenu_add_lockable_item(submenu, label, index, callback, callback_context, false, NULL); +} + +void submenu_add_lockable_item( + Submenu* submenu, + const char* label, + uint32_t index, + SubmenuItemCallback callback, + void* callback_context, + bool locked, + const char* locked_message) { SubmenuItem* item = NULL; furi_assert(label); furi_assert(submenu); + if(locked) { + furi_assert(locked_message); + } with_view_model( submenu->view, @@ -211,6 +293,10 @@ void submenu_add_item( item->index = index; item->callback = callback; item->callback_context = callback_context; + item->locked = locked; + if(locked) { + furi_string_set_str(item->locked_message, locked_message); + } }, true); } @@ -328,10 +414,14 @@ void submenu_process_ok(Submenu* submenu) { if(model->position < items_size) { item = SubmenuItemArray_get(model->items, model->position); } + if(item && item->locked) { + model->locked_message_visible = true; + furi_timer_start(submenu->locked_timer, furi_kernel_get_tick_frequency() * 3); + } }, true); - if(item && item->callback) { + if(item && !item->locked && item->callback) { item->callback(item->callback_context, item->index); } } diff --git a/applications/services/gui/modules/submenu.h b/applications/services/gui/modules/submenu.h index f68abe83a..e7252eb33 100644 --- a/applications/services/gui/modules/submenu.h +++ b/applications/services/gui/modules/submenu.h @@ -53,6 +53,26 @@ void submenu_add_item( SubmenuItemCallback callback, void* callback_context); +/** Add lockable item to submenu + * + * @param submenu Submenu instance + * @param label menu item label + * @param index menu item index, used for callback, may be + * the same with other items + * @param callback menu item callback + * @param callback_context menu item callback context + * @param locked menu item locked status + * @param locked_message menu item locked message + */ +void submenu_add_lockable_item( + Submenu* submenu, + const char* label, + uint32_t index, + SubmenuItemCallback callback, + void* callback_context, + bool locked, + const char* locked_message); + /** Remove all items from submenu * * @param submenu Submenu instance diff --git a/applications/services/gui/modules/text_input.c b/applications/services/gui/modules/text_input.c index 5fd306aa6..3e521c356 100644 --- a/applications/services/gui/modules/text_input.c +++ b/applications/services/gui/modules/text_input.c @@ -18,8 +18,12 @@ typedef struct { const char* header; char* text_buffer; size_t text_buffer_size; + size_t minimum_length; bool clear_default_text; + bool cursor_select; + size_t cursor_pos; + TextInputCallback callback; void* callback_context; @@ -146,9 +150,13 @@ static char char_to_uppercase(const char letter) { } static void text_input_backspace_cb(TextInputModel* model) { - uint8_t text_length = model->clear_default_text ? 1 : strlen(model->text_buffer); - if(text_length > 0) { - model->text_buffer[text_length - 1] = 0; + if(model->clear_default_text) { + model->text_buffer[0] = 0; + model->cursor_pos = 0; + } else if(model->cursor_pos > 0) { + char* move = model->text_buffer + model->cursor_pos; + memmove(move - 1, move, strlen(move) + 1); + model->cursor_pos--; } } @@ -158,7 +166,8 @@ static void text_input_view_draw_callback(Canvas* canvas, void* _model) { uint8_t needed_string_width = canvas_width(canvas) - 8; uint8_t start_pos = 4; - const char* text = model->text_buffer; + model->cursor_pos = model->cursor_pos > text_length ? text_length : model->cursor_pos; + size_t cursor_pos = model->cursor_pos; canvas_clear(canvas); canvas_set_color(canvas, ColorBlack); @@ -166,25 +175,44 @@ static void text_input_view_draw_callback(Canvas* canvas, void* _model) { canvas_draw_str(canvas, 2, 8, model->header); elements_slightly_rounded_frame(canvas, 1, 12, 126, 15); - if(canvas_string_width(canvas, text) > needed_string_width) { - canvas_draw_str(canvas, start_pos, 22, "..."); - start_pos += 6; - needed_string_width -= 8; - } - - while(text != 0 && canvas_string_width(canvas, text) > needed_string_width) { - text++; + char buf[model->text_buffer_size + 1]; + if(model->text_buffer) { + strlcpy(buf, model->text_buffer, sizeof(buf)); } + char* str = buf; if(model->clear_default_text) { elements_slightly_rounded_box( - canvas, start_pos - 1, 14, canvas_string_width(canvas, text) + 2, 10); + canvas, start_pos - 1, 14, canvas_string_width(canvas, str) + 2, 10); canvas_set_color(canvas, ColorWhite); } else { - canvas_draw_str(canvas, start_pos + canvas_string_width(canvas, text) + 1, 22, "|"); - canvas_draw_str(canvas, start_pos + canvas_string_width(canvas, text) + 2, 22, "|"); + char* move = str + cursor_pos; + memmove(move + 1, move, strlen(move) + 1); + str[cursor_pos] = '|'; } - canvas_draw_str(canvas, start_pos, 22, text); + + if(cursor_pos > 0 && canvas_string_width(canvas, str) > needed_string_width) { + canvas_draw_str(canvas, start_pos, 22, "..."); + start_pos += 6; + needed_string_width -= 8; + for(uint32_t off = 0; + strlen(str) && canvas_string_width(canvas, str) > needed_string_width && + off < cursor_pos; + off++) { + str++; + } + } + + if(canvas_string_width(canvas, str) > needed_string_width) { + needed_string_width -= 4; + size_t len = strlen(str); + while(len && canvas_string_width(canvas, str) > needed_string_width) { + str[len--] = '\0'; + } + strcat(str, "..."); + } + + canvas_draw_str(canvas, start_pos, 22, str); canvas_set_font(canvas, FontKeyboard); @@ -193,9 +221,12 @@ static void text_input_view_draw_callback(Canvas* canvas, void* _model) { const TextInputKey* keys = get_row(row); for(size_t column = 0; column < column_count; column++) { + bool selected = !model->cursor_select && model->selected_row == row && + model->selected_column == column; + if(keys[column].text == ENTER_KEY) { canvas_set_color(canvas, ColorBlack); - if(model->selected_row == row && model->selected_column == column) { + if(selected) { canvas_draw_icon( canvas, keyboard_origin_x + keys[column].x, @@ -210,7 +241,7 @@ static void text_input_view_draw_callback(Canvas* canvas, void* _model) { } } else if(keys[column].text == BACKSPACE_KEY) { canvas_set_color(canvas, ColorBlack); - if(model->selected_row == row && model->selected_column == column) { + if(selected) { canvas_draw_icon( canvas, keyboard_origin_x + keys[column].x, @@ -224,7 +255,7 @@ static void text_input_view_draw_callback(Canvas* canvas, void* _model) { &I_KeyBackspace_16x9); } } else { - if(model->selected_row == row && model->selected_column == column) { + if(selected) { canvas_set_color(canvas, ColorBlack); canvas_draw_box( canvas, @@ -273,12 +304,17 @@ static void text_input_handle_up(TextInput* text_input, TextInputModel* model) { if(model->selected_column > get_row_size(model->selected_row) - 6) { model->selected_column = model->selected_column + 1; } + } else { + model->cursor_select = true; + model->clear_default_text = false; } } static void text_input_handle_down(TextInput* text_input, TextInputModel* model) { UNUSED(text_input); - if(model->selected_row < keyboard_row_count - 1) { + if(model->cursor_select) { + model->cursor_select = false; + } else if(model->selected_row < keyboard_row_count - 1) { model->selected_row++; if(model->selected_column > get_row_size(model->selected_row) - 4) { model->selected_column = model->selected_column - 1; @@ -288,7 +324,11 @@ static void text_input_handle_down(TextInput* text_input, TextInputModel* model) static void text_input_handle_left(TextInput* text_input, TextInputModel* model) { UNUSED(text_input); - if(model->selected_column > 0) { + if(model->cursor_select) { + if(model->cursor_pos > 0) { + model->cursor_pos = CLAMP(model->cursor_pos - 1, strlen(model->text_buffer), 0u); + } + } else if(model->selected_column > 0) { model->selected_column--; } else { model->selected_column = get_row_size(model->selected_row) - 1; @@ -297,14 +337,19 @@ static void text_input_handle_left(TextInput* text_input, TextInputModel* model) static void text_input_handle_right(TextInput* text_input, TextInputModel* model) { UNUSED(text_input); - if(model->selected_column < get_row_size(model->selected_row) - 1) { + if(model->cursor_select) { + model->cursor_pos = CLAMP(model->cursor_pos + 1, strlen(model->text_buffer), 0u); + } else if(model->selected_column < get_row_size(model->selected_row) - 1) { model->selected_column++; } else { model->selected_column = 0; } } -static void text_input_handle_ok(TextInput* text_input, TextInputModel* model, bool shift) { +static void text_input_handle_ok(TextInput* text_input, TextInputModel* model, InputType type) { + if(model->cursor_select) return; + bool shift = type == InputTypeLong; + bool repeat = type == InputTypeRepeat; char selected = get_selected_char(model); size_t text_length = strlen(model->text_buffer); @@ -314,24 +359,34 @@ static void text_input_handle_ok(TextInput* text_input, TextInputModel* model, b model->text_buffer, model->validator_text, model->validator_callback_context))) { model->validator_message_visible = true; furi_timer_start(text_input->timer, furi_kernel_get_tick_frequency() * 4); - } else if(model->callback != 0 && text_length > 0) { + } else if(model->callback != 0 && text_length >= model->minimum_length) { model->callback(model->callback_context); } - } else if(selected == BACKSPACE_KEY) { - text_input_backspace_cb(model); } else { - if(model->clear_default_text) { - text_length = 0; - } - if(text_length < (model->text_buffer_size - 1)) { - if(shift != (text_length == 0)) { - selected = char_to_uppercase(selected); + if(selected == BACKSPACE_KEY) { + text_input_backspace_cb(model); + } else if(!repeat) { + if(model->clear_default_text) { + text_length = 0; + } + if(text_length < (model->text_buffer_size - 1)) { + if(shift != (text_length == 0)) { + selected = char_to_uppercase(selected); + } + if(model->clear_default_text) { + model->text_buffer[0] = selected; + model->text_buffer[1] = '\0'; + model->cursor_pos = 1; + } else { + char* move = model->text_buffer + model->cursor_pos; + memmove(move + 1, move, strlen(move) + 1); + model->text_buffer[model->cursor_pos] = selected; + model->cursor_pos++; + } } - model->text_buffer[text_length] = selected; - model->text_buffer[text_length + 1] = 0; } + model->clear_default_text = false; } - model->clear_default_text = false; } static bool text_input_view_input_callback(InputEvent* event, void* context) { @@ -363,7 +418,7 @@ static bool text_input_view_input_callback(InputEvent* event, void* context) { text_input_handle_right(text_input, model); break; case InputKeyOk: - text_input_handle_ok(text_input, model, false); + text_input_handle_ok(text_input, model, event->type); break; default: consumed = false; @@ -385,7 +440,7 @@ static bool text_input_view_input_callback(InputEvent* event, void* context) { text_input_handle_right(text_input, model); break; case InputKeyOk: - text_input_handle_ok(text_input, model, true); + text_input_handle_ok(text_input, model, event->type); break; case InputKeyBack: text_input_backspace_cb(model); @@ -409,6 +464,9 @@ static bool text_input_view_input_callback(InputEvent* event, void* context) { case InputKeyRight: text_input_handle_right(text_input, model); break; + case InputKeyOk: + text_input_handle_ok(text_input, model, event->type); + break; case InputKeyBack: text_input_backspace_cb(model); break; @@ -448,7 +506,12 @@ TextInput* text_input_alloc() { with_view_model( text_input->view, TextInputModel * model, - { model->validator_text = furi_string_alloc(); }, + { + model->validator_text = furi_string_alloc(); + model->minimum_length = 1; + model->cursor_pos = 0; + model->cursor_select = false; + }, false); text_input_reset(text_input); @@ -483,7 +546,10 @@ void text_input_reset(TextInput* text_input) { model->header = ""; model->selected_row = 0; model->selected_column = 0; + model->minimum_length = 1; model->clear_default_text = false; + model->cursor_pos = 0; + model->cursor_select = false; model->text_buffer = NULL; model->text_buffer_size = 0; model->callback = NULL; @@ -517,15 +583,27 @@ void text_input_set_result_callback( model->text_buffer = text_buffer; model->text_buffer_size = text_buffer_size; model->clear_default_text = clear_default_text; + model->cursor_select = false; if(text_buffer && text_buffer[0] != '\0') { + model->cursor_pos = strlen(text_buffer); // Set focus on Save model->selected_row = 2; model->selected_column = 8; + } else { + model->cursor_pos = 0; } }, true); } +void text_input_set_minimum_length(TextInput* text_input, size_t minimum_length) { + with_view_model( + text_input->view, + TextInputModel * model, + { model->minimum_length = minimum_length; }, + true); +} + void text_input_set_validator( TextInput* text_input, TextInputValidatorCallback callback, diff --git a/applications/services/gui/modules/text_input.h b/applications/services/gui/modules/text_input.h index 218df3141..65207532e 100644 --- a/applications/services/gui/modules/text_input.h +++ b/applications/services/gui/modules/text_input.h @@ -70,6 +70,8 @@ void text_input_set_validator( TextInputValidatorCallback callback, void* callback_context); +void text_input_set_minimum_length(TextInput* text_input, size_t minimum_length); + TextInputValidatorCallback text_input_get_validator_callback(TextInput* text_input); void* text_input_get_validator_callback_context(TextInput* text_input); diff --git a/applications/services/gui/modules/variable_item_list.c b/applications/services/gui/modules/variable_item_list.c index cf7f64ca3..93948f7a4 100644 --- a/applications/services/gui/modules/variable_item_list.c +++ b/applications/services/gui/modules/variable_item_list.c @@ -2,6 +2,7 @@ #include #include #include +#include #include #include @@ -11,6 +12,8 @@ struct VariableItem { FuriString* current_value_text; uint8_t values_count; VariableItemChangeCallback change_callback; + bool locked; + FuriString* locked_message; void* context; }; @@ -20,12 +23,16 @@ struct VariableItemList { View* view; VariableItemListEnterCallback callback; void* context; + FuriTimer* scroll_timer; + FuriTimer* locked_timer; }; typedef struct { VariableItemArray_t items; uint8_t position; uint8_t window_position; + size_t scroll_counter; + bool locked_message_visible; } VariableItemListModel; static void variable_item_list_process_up(VariableItemList* variable_item_list); @@ -56,31 +63,50 @@ static void variable_item_list_draw_callback(Canvas* canvas, void* _model) { const VariableItem* item = VariableItemArray_cref(it); uint8_t item_y = y_offset + (item_position * item_height); uint8_t item_text_y = item_y + item_height - 4; + size_t scroll_counter = 0; if(position == model->position) { canvas_set_color(canvas, ColorBlack); elements_slightly_rounded_box(canvas, 0, item_y + 1, item_width, item_height - 2); canvas_set_color(canvas, ColorWhite); + scroll_counter = model->scroll_counter; + if(scroll_counter < 1) { + scroll_counter = 0; + } else { + scroll_counter -= 1; + } } else { canvas_set_color(canvas, ColorBlack); } - canvas_draw_str(canvas, 6, item_text_y, item->label); - - if(item->current_value_index > 0) { - canvas_draw_str(canvas, 73, item_text_y, "<"); + if(item->current_value_index == 0 && furi_string_empty(item->current_value_text)) { + // Only left text, no right text + canvas_draw_str(canvas, 6, item_text_y, item->label); + } else { + elements_scrollable_text_line_str( + canvas, 6, item_text_y, 66, item->label, scroll_counter, false, false); } - canvas_draw_str_aligned( - canvas, - (115 + 73) / 2 + 1, - item_text_y, - AlignCenter, - AlignBottom, - furi_string_get_cstr(item->current_value_text)); + if(item->locked) { + canvas_draw_icon(canvas, 110, item_text_y - 8, &I_Lock_7x8); + } else { + if(item->current_value_index > 0) { + canvas_draw_str(canvas, 73, item_text_y, "<"); + } - if(item->current_value_index < (item->values_count - 1)) { - canvas_draw_str(canvas, 115, item_text_y, ">"); + elements_scrollable_text_line_str( + canvas, + (115 + 73) / 2 + 1, + item_text_y, + 37, + furi_string_get_cstr(item->current_value_text), + scroll_counter, + false, + true); + + if(item->current_value_index < (item->values_count - 1)) { + canvas_draw_str(canvas, 115, item_text_y, ">"); + } } } @@ -88,6 +114,23 @@ static void variable_item_list_draw_callback(Canvas* canvas, void* _model) { } elements_scrollbar(canvas, model->position, VariableItemArray_size(model->items)); + + if(model->locked_message_visible) { + canvas_set_color(canvas, ColorWhite); + canvas_draw_box(canvas, 8, 10, 110, 48); + canvas_set_color(canvas, ColorBlack); + canvas_draw_icon(canvas, 10, 14, &I_WarningDolphin_45x42); + canvas_draw_rframe(canvas, 8, 8, 112, 50, 3); + canvas_draw_rframe(canvas, 9, 9, 110, 48, 2); + elements_multiline_text_aligned( + canvas, + 84, + 32, + AlignCenter, + AlignCenter, + furi_string_get_cstr( + VariableItemArray_get(model->items, model->position)->locked_message)); + } } void variable_item_list_set_selected_item(VariableItemList* variable_item_list, uint8_t index) { @@ -130,7 +173,22 @@ static bool variable_item_list_input_callback(InputEvent* event, void* context) furi_assert(variable_item_list); bool consumed = false; - if(event->type == InputTypeShort) { + bool locked_message_visible = false; + with_view_model( + variable_item_list->view, + VariableItemListModel * model, + { locked_message_visible = model->locked_message_visible; }, + false); + + if((!(event->type == InputTypePress) && !(event->type == InputTypeRelease)) && + locked_message_visible) { + with_view_model( + variable_item_list->view, + VariableItemListModel * model, + { model->locked_message_visible = false; }, + true); + consumed = true; + } else if(event->type == InputTypeShort) { switch(event->key) { case InputKeyUp: consumed = true; @@ -198,6 +256,7 @@ void variable_item_list_process_up(VariableItemList* variable_item_list) { model->window_position = model->position - (items_on_screen - 1); } } + model->scroll_counter = 0; }, true); } @@ -219,6 +278,7 @@ void variable_item_list_process_down(VariableItemList* variable_item_list) { model->position = 0; model->window_position = 0; } + model->scroll_counter = 0; }, true); } @@ -248,8 +308,13 @@ void variable_item_list_process_left(VariableItemList* variable_item_list) { VariableItemListModel * model, { VariableItem* item = variable_item_list_get_selected_item(model); - if(item->current_value_index > 0) { + if(item->locked) { + model->locked_message_visible = true; + furi_timer_start( + variable_item_list->locked_timer, furi_kernel_get_tick_frequency() * 3); + } else if(item->current_value_index > 0) { item->current_value_index--; + model->scroll_counter = 0; if(item->change_callback) { item->change_callback(item); } @@ -264,8 +329,13 @@ void variable_item_list_process_right(VariableItemList* variable_item_list) { VariableItemListModel * model, { VariableItem* item = variable_item_list_get_selected_item(model); - if(item->current_value_index < (item->values_count - 1)) { + if(item->locked) { + model->locked_message_visible = true; + furi_timer_start( + variable_item_list->locked_timer, furi_kernel_get_tick_frequency() * 3); + } else if(item->current_value_index < (item->values_count - 1)) { item->current_value_index++; + model->scroll_counter = 0; if(item->change_callback) { item->change_callback(item); } @@ -279,11 +349,36 @@ void variable_item_list_process_ok(VariableItemList* variable_item_list) { variable_item_list->view, VariableItemListModel * model, { - if(variable_item_list->callback) { + VariableItem* item = variable_item_list_get_selected_item(model); + if(item->locked) { + model->locked_message_visible = true; + furi_timer_start( + variable_item_list->locked_timer, furi_kernel_get_tick_frequency() * 3); + } else if(variable_item_list->callback) { variable_item_list->callback(variable_item_list->context, model->position); } }, - false); + true); +} + +static void variable_item_list_scroll_timer_callback(void* context) { + VariableItemList* variable_item_list = context; + with_view_model( + variable_item_list->view, + VariableItemListModel * model, + { model->scroll_counter++; }, + true); +} + +void variable_item_list_locked_timer_callback(void* context) { + furi_assert(context); + VariableItemList* variable_item_list = context; + + with_view_model( + variable_item_list->view, + VariableItemListModel * model, + { model->locked_message_visible = false; }, + true); } VariableItemList* variable_item_list_alloc() { @@ -295,6 +390,9 @@ VariableItemList* variable_item_list_alloc() { view_set_draw_callback(variable_item_list->view, variable_item_list_draw_callback); view_set_input_callback(variable_item_list->view, variable_item_list_input_callback); + variable_item_list->locked_timer = furi_timer_alloc( + variable_item_list_locked_timer_callback, FuriTimerTypeOnce, variable_item_list); + with_view_model( variable_item_list->view, VariableItemListModel * model, @@ -302,8 +400,12 @@ VariableItemList* variable_item_list_alloc() { VariableItemArray_init(model->items); model->position = 0; model->window_position = 0; + model->scroll_counter = 0; }, true); + variable_item_list->scroll_timer = furi_timer_alloc( + variable_item_list_scroll_timer_callback, FuriTimerTypePeriodic, variable_item_list); + furi_timer_start(variable_item_list->scroll_timer, 333); return variable_item_list; } @@ -319,10 +421,15 @@ void variable_item_list_free(VariableItemList* variable_item_list) { for(VariableItemArray_it(it, model->items); !VariableItemArray_end_p(it); VariableItemArray_next(it)) { furi_string_free(VariableItemArray_ref(it)->current_value_text); + furi_string_free(VariableItemArray_ref(it)->locked_message); } VariableItemArray_clear(model->items); }, false); + furi_timer_stop(variable_item_list->scroll_timer); + furi_timer_free(variable_item_list->scroll_timer); + furi_timer_stop(variable_item_list->locked_timer); + furi_timer_free(variable_item_list->locked_timer); view_free(variable_item_list->view); free(variable_item_list); } @@ -338,6 +445,7 @@ void variable_item_list_reset(VariableItemList* variable_item_list) { for(VariableItemArray_it(it, model->items); !VariableItemArray_end_p(it); VariableItemArray_next(it)) { furi_string_free(VariableItemArray_ref(it)->current_value_text); + furi_string_free(VariableItemArray_ref(it)->locked_message); } VariableItemArray_reset(model->items); }, @@ -370,6 +478,8 @@ VariableItem* variable_item_list_add( item->context = context; item->current_value_index = 0; item->current_value_text = furi_string_alloc(); + item->locked = false; + item->locked_message = furi_string_alloc(); }, true); @@ -404,6 +514,14 @@ void variable_item_set_current_value_text(VariableItem* item, const char* curren furi_string_set(item->current_value_text, current_value_text); } +void variable_item_set_locked(VariableItem* item, bool locked, const char* locked_message) { + item->locked = locked; + if(locked) { + furi_assert(locked_message); + furi_string_set(item->locked_message, locked_message); + } +} + uint8_t variable_item_get_current_value_index(VariableItem* item) { return item->current_value_index; } diff --git a/applications/services/gui/modules/variable_item_list.h b/applications/services/gui/modules/variable_item_list.h index db2a58993..db8b1788f 100644 --- a/applications/services/gui/modules/variable_item_list.h +++ b/applications/services/gui/modules/variable_item_list.h @@ -95,6 +95,14 @@ void variable_item_set_values_count(VariableItem* item, uint8_t values_count); */ void variable_item_set_current_value_text(VariableItem* item, const char* current_value_text); +/** Set item locked state and text + * + * @param item VariableItem* instance + * @param locked Is item locked boolean + * @param locked_message The locked message text + */ +void variable_item_set_locked(VariableItem* item, bool locked, const char* locked_message); + /** Get item current selected index * * @param item VariableItem* instance diff --git a/applications/services/namechanger/application.fam b/applications/services/namechanger/application.fam new file mode 100644 index 000000000..0eaeab987 --- /dev/null +++ b/applications/services/namechanger/application.fam @@ -0,0 +1,8 @@ +App( + appid="namechanger_srv", + apptype=FlipperAppType.STARTUP, + entry_point="namechanger_on_system_start", + requires=["storage", "cli", "bt"], + conflicts=["updater"], + order=600, +) \ No newline at end of file diff --git a/applications/services/namechanger/namechanger.c b/applications/services/namechanger/namechanger.c new file mode 100644 index 000000000..796dd2d3b --- /dev/null +++ b/applications/services/namechanger/namechanger.c @@ -0,0 +1,111 @@ +#include "namechanger.h" +#include +#include +#include +#include +#include +#include +#include + +#define TAG "NameChanger" + +static bool namechanger_init() { + Storage* storage = furi_record_open(RECORD_STORAGE); + + // Kostil + velosiped = top ficha + uint8_t timeout = 0; + while(timeout < 11) { + if(storage_sd_status(storage) == FSE_OK) break; + furi_delay_ms(250); + timeout++; + /*if(timeout == 10) { + // Failed to init namechanger, SD card not ready + furi_record_close(RECORD_STORAGE); + return false; + }*/ + } + + FuriString* str = furi_string_alloc(); + FlipperFormat* file = flipper_format_file_alloc(storage); + + bool res = false; + + do { + uint32_t version; + if(!flipper_format_file_open_existing(file, NAMECHANGER_PATH)) break; + if(!flipper_format_read_header(file, str, &version)) break; + if(furi_string_cmp_str(str, NAMECHANGER_HEADER)) break; + if(version != NAMECHANGER_VERSION) break; + + if(!flipper_format_read_string(file, "Name", str)) break; + // Check for size + size_t temp_string_size = furi_string_size(str); + if(temp_string_size > (size_t)8) break; + if(temp_string_size < (size_t)2) break; + + // Check for forbidden characters + const char* name_ptr = furi_string_get_cstr(str); + bool chars_check_failed = false; + + for(; *name_ptr; ++name_ptr) { + const char c = *name_ptr; + if((c < '0' || c > '9') && (c < 'A' || c > 'Z') && (c < 'a' || c > 'z')) { + chars_check_failed = true; + break; + } + } + + if(chars_check_failed) break; + + // If all checks was good we can set the name + version_set_custom_name(NULL, strdup(furi_string_get_cstr(str))); + furi_hal_version_set_name(version_get_custom_name(NULL)); + + res = true; + } while(false); + + flipper_format_free(file); + furi_record_close(RECORD_STORAGE); + furi_string_free(str); + + return res; +} + +int32_t namechanger_on_system_start(void* p) { + UNUSED(p); + if(furi_hal_rtc_get_boot_mode() != FuriHalRtcBootModeNormal) { + return 0; + } + + // Wait for all required services to start and create their records + uint8_t timeout = 0; + while(!furi_record_exists(RECORD_CLI) || !furi_record_exists(RECORD_BT) || + !furi_record_exists(RECORD_STORAGE)) { + timeout++; + if(timeout > 250) { + return 0; + } + furi_delay_ms(5); + } + + // Hehe bad code now here, bad bad bad, very bad, bad example, dont take it, make it better + + if(namechanger_init()) { + Cli* cli = furi_record_open(RECORD_CLI); + cli_session_close(cli); + furi_delay_ms(2); // why i added delays here + cli_session_open(cli, &cli_vcp); + furi_record_close(RECORD_CLI); + + furi_delay_ms(3); + Bt* bt = furi_record_open(RECORD_BT); + if(!bt_set_profile(bt, BtProfileSerial)) { + //FURI_LOG_D(TAG, "Failed to touch bluetooth to name change"); + } + furi_record_close(RECORD_BT); + bt = NULL; + furi_delay_ms(3); + } + + return 0; +} \ No newline at end of file diff --git a/applications/services/namechanger/namechanger.h b/applications/services/namechanger/namechanger.h new file mode 100644 index 000000000..dac1b7952 --- /dev/null +++ b/applications/services/namechanger/namechanger.h @@ -0,0 +1,5 @@ +#pragma once + +#define NAMECHANGER_HEADER "Flipper Name File" +#define NAMECHANGER_VERSION 1 +#define NAMECHANGER_PATH EXT_PATH("dolphin/name.settings") diff --git a/applications/services/rpc/rpc.c b/applications/services/rpc/rpc.c index 879754fd5..3b8cd5f23 100644 --- a/applications/services/rpc/rpc.c +++ b/applications/services/rpc/rpc.c @@ -57,6 +57,10 @@ static RpcSystemCallbacks rpc_systems[] = { .alloc = rpc_system_property_alloc, .free = NULL, }, + { + .alloc = rpc_desktop_alloc, + .free = rpc_desktop_free, + }, }; struct RpcSession { diff --git a/applications/services/rpc/rpc_desktop.c b/applications/services/rpc/rpc_desktop.c new file mode 100644 index 000000000..dbf9796ec --- /dev/null +++ b/applications/services/rpc/rpc_desktop.c @@ -0,0 +1,73 @@ +#include "flipper.pb.h" +#include "rpc_i.h" +#include +#include "desktop.pb.h" + +#define TAG "RpcDesktop" + +typedef struct { + RpcSession* session; + Desktop* desktop; +} RpcDesktop; + +static void rpc_desktop_on_is_locked_request(const PB_Main* request, void* context) { + furi_assert(request); + furi_assert(context); + furi_assert(request->which_content == PB_Main_desktop_is_locked_request_tag); + + FURI_LOG_D(TAG, "IsLockedRequest"); + RpcDesktop* rpc_desktop = context; + RpcSession* session = rpc_desktop->session; + + PB_CommandStatus ret = desktop_api_is_locked(rpc_desktop->desktop) ? PB_CommandStatus_OK : + PB_CommandStatus_ERROR; + + rpc_send_and_release_empty(session, request->command_id, ret); +} + +static void rpc_desktop_on_unlock_request(const PB_Main* request, void* context) { + furi_assert(request); + furi_assert(context); + furi_assert(request->which_content == PB_Main_desktop_unlock_request_tag); + + FURI_LOG_D(TAG, "UnlockRequest"); + RpcDesktop* rpc_desktop = context; + RpcSession* session = rpc_desktop->session; + + desktop_api_unlock(rpc_desktop->desktop); + + rpc_send_and_release_empty(session, request->command_id, PB_CommandStatus_OK); +} + +void* rpc_desktop_alloc(RpcSession* session) { + furi_assert(session); + + RpcDesktop* rpc_desktop = malloc(sizeof(RpcDesktop)); + rpc_desktop->desktop = furi_record_open(RECORD_DESKTOP); + rpc_desktop->session = session; + + RpcHandler rpc_handler = { + .message_handler = NULL, + .decode_submessage = NULL, + .context = rpc_desktop, + }; + + rpc_handler.message_handler = rpc_desktop_on_is_locked_request; + rpc_add_handler(session, PB_Main_desktop_is_locked_request_tag, &rpc_handler); + + rpc_handler.message_handler = rpc_desktop_on_unlock_request; + rpc_add_handler(session, PB_Main_desktop_unlock_request_tag, &rpc_handler); + + return rpc_desktop; +} + +void rpc_desktop_free(void* context) { + furi_assert(context); + RpcDesktop* rpc_desktop = context; + + furi_assert(rpc_desktop->desktop); + furi_record_close(RECORD_DESKTOP); + + rpc_desktop->session = NULL; + free(rpc_desktop); +} \ No newline at end of file diff --git a/applications/services/rpc/rpc_i.h b/applications/services/rpc/rpc_i.h index 91a176da8..16e5e594d 100644 --- a/applications/services/rpc/rpc_i.h +++ b/applications/services/rpc/rpc_i.h @@ -36,6 +36,9 @@ void* rpc_system_gpio_alloc(RpcSession* session); void rpc_system_gpio_free(void* ctx); void* rpc_system_property_alloc(RpcSession* session); +void* rpc_desktop_alloc(RpcSession* session); +void rpc_desktop_free(void* ctx); + void rpc_debug_print_message(const PB_Main* message); void rpc_debug_print_data(const char* prefix, uint8_t* buffer, size_t size); diff --git a/applications/settings/bt_settings_app/scenes/bt_settings_scene_forget_dev_confirm.c b/applications/settings/bt_settings_app/scenes/bt_settings_scene_forget_dev_confirm.c index 31921b9f3..150c3ef9f 100644 --- a/applications/settings/bt_settings_app/scenes/bt_settings_scene_forget_dev_confirm.c +++ b/applications/settings/bt_settings_app/scenes/bt_settings_scene_forget_dev_confirm.c @@ -1,5 +1,6 @@ #include "../bt_settings_app.h" #include +#include void bt_settings_scene_forget_dev_confirm_dialog_callback(DialogExResult result, void* context) { furi_assert(context); @@ -29,7 +30,16 @@ bool bt_settings_scene_forget_dev_confirm_on_event(void* context, SceneManagerEv if(event.event == DialogExResultLeft) { consumed = scene_manager_previous_scene(app->scene_manager); } else if(event.event == DialogExResultRight) { + bt_keys_storage_set_default_path(app->bt); bt_forget_bonded_devices(app->bt); + + // Also remove keys of BadBT, Bluetooth Remote, TOTP Authenticator + Storage* storage = furi_record_open(RECORD_STORAGE); + storage_simply_remove(storage, EXT_PATH("badbt/.badbt.keys")); + storage_simply_remove(storage, EXT_PATH("apps_data/hid_ble/.bt_hid.keys")); + storage_simply_remove(storage, EXT_PATH("authenticator/.bt_hid.keys")); + furi_record_close(RECORD_STORAGE); + scene_manager_next_scene(app->scene_manager, BtSettingsAppSceneForgetDevSuccess); consumed = true; } diff --git a/applications/settings/desktop_settings/desktop_settings_app.c b/applications/settings/desktop_settings/desktop_settings_app.c index afb5d59ec..7e04271f2 100644 --- a/applications/settings/desktop_settings/desktop_settings_app.c +++ b/applications/settings/desktop_settings/desktop_settings_app.c @@ -1,6 +1,9 @@ #include #include #include +#include +#include +#include #include "desktop_settings_app.h" #include "scenes/desktop_settings_scene.h" @@ -62,11 +65,41 @@ DesktopSettingsApp* desktop_settings_app_alloc() { app->view_dispatcher, DesktopSettingsAppViewIdPinSetupHowto2, desktop_settings_view_pin_setup_howto2_get_view(app->pin_setup_howto2_view)); + + // Text Input + app->text_input = text_input_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + DesktopSettingsAppViewTextInput, + text_input_get_view(app->text_input)); + return app; } void desktop_settings_app_free(DesktopSettingsApp* app) { furi_assert(app); + + bool temp_save_name = app->save_name; + // Save name if set or remove file + if(temp_save_name) { + Storage* storage = furi_record_open(RECORD_STORAGE); + if(strcmp(app->device_name, "") == 0) { + storage_simply_remove(storage, NAMECHANGER_PATH); + } else { + FlipperFormat* file = flipper_format_file_alloc(storage); + + do { + if(!flipper_format_file_open_always(file, NAMECHANGER_PATH)) break; + if(!flipper_format_write_header_cstr(file, NAMECHANGER_HEADER, NAMECHANGER_VERSION)) + break; + if(!flipper_format_write_string_cstr(file, "Name", app->device_name)) break; + } while(0); + + flipper_format_free(file); + } + furi_record_close(RECORD_STORAGE); + } + // Variable item list view_dispatcher_remove_view(app->view_dispatcher, DesktopSettingsAppViewMenu); view_dispatcher_remove_view(app->view_dispatcher, DesktopSettingsAppViewVarItemList); @@ -74,6 +107,10 @@ void desktop_settings_app_free(DesktopSettingsApp* app) { view_dispatcher_remove_view(app->view_dispatcher, DesktopSettingsAppViewIdPinInput); view_dispatcher_remove_view(app->view_dispatcher, DesktopSettingsAppViewIdPinSetupHowto); view_dispatcher_remove_view(app->view_dispatcher, DesktopSettingsAppViewIdPinSetupHowto2); + // TextInput + view_dispatcher_remove_view(app->view_dispatcher, DesktopSettingsAppViewTextInput); + text_input_free(app->text_input); + variable_item_list_free(app->variable_item_list); submenu_free(app->submenu); popup_free(app->popup); @@ -87,6 +124,10 @@ void desktop_settings_app_free(DesktopSettingsApp* app) { furi_record_close(RECORD_DIALOGS); furi_record_close(RECORD_GUI); free(app); + + if(temp_save_name) { + power_reboot(PowerBootModeNormal); + } } extern int32_t desktop_settings_app(void* p) { diff --git a/applications/settings/desktop_settings/desktop_settings_app.h b/applications/settings/desktop_settings/desktop_settings_app.h index 6f97564c9..76f23e22f 100644 --- a/applications/settings/desktop_settings/desktop_settings_app.h +++ b/applications/settings/desktop_settings/desktop_settings_app.h @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -21,6 +22,7 @@ typedef enum { DesktopSettingsAppViewIdPinInput, DesktopSettingsAppViewIdPinSetupHowto, DesktopSettingsAppViewIdPinSetupHowto2, + DesktopSettingsAppViewTextInput, } DesktopSettingsAppView; typedef struct { @@ -32,6 +34,7 @@ typedef struct { ViewDispatcher* view_dispatcher; VariableItemList* variable_item_list; Submenu* submenu; + TextInput* text_input; Popup* popup; DesktopViewPinInput* pin_input_view; DesktopSettingsViewPinSetupHowto* pin_setup_howto_view; @@ -40,5 +43,8 @@ typedef struct { PinCode pincode_buffer; bool pincode_buffer_filled; + bool save_name; + char device_name[FURI_HAL_VERSION_ARRAY_NAME_LENGTH]; + uint8_t menu_idx; } DesktopSettingsApp; diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_change_name.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_change_name.c new file mode 100644 index 000000000..11080b2ec --- /dev/null +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_change_name.c @@ -0,0 +1,75 @@ +#include "../desktop_settings_app.h" +#include +#include "desktop_settings_scene.h" + +enum TextInputIndex { + TextInputResultOk, +}; + +static void desktop_settings_scene_change_name_text_input_callback(void* context) { + DesktopSettingsApp* app = context; + + app->save_name = true; + view_dispatcher_send_custom_event(app->view_dispatcher, TextInputResultOk); +} + +static bool desktop_settings_scene_change_name_validator( + const char* text, + FuriString* error, + void* context) { + UNUSED(context); + + for(; *text; ++text) { + const char c = *text; + if((c < '0' || c > '9') && (c < 'A' || c > 'Z') && (c < 'a' || c > 'z')) { + furi_string_printf(error, "Please only\nenter letters\nand numbers!"); + return false; + } + } + + return true; +} + +void desktop_settings_scene_change_name_on_enter(void* context) { + DesktopSettingsApp* app = context; + TextInput* text_input = app->text_input; + + text_input_set_header_text(text_input, "Leave empty for default"); + + text_input_set_validator(text_input, desktop_settings_scene_change_name_validator, NULL); + + text_input_set_minimum_length(text_input, 0); + + text_input_set_result_callback( + text_input, + desktop_settings_scene_change_name_text_input_callback, + app, + app->device_name, + FURI_HAL_VERSION_ARRAY_NAME_LENGTH, + true); + + view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewTextInput); +} + +bool desktop_settings_scene_change_name_on_event(void* context, SceneManagerEvent event) { + DesktopSettingsApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + switch(event.event) { + case TextInputResultOk: + scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneNamePopup); + break; + default: + break; + } + } + + return consumed; +} + +void desktop_settings_scene_change_name_on_exit(void* context) { + DesktopSettingsApp* app = context; + text_input_reset(app->text_input); +} \ No newline at end of file diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_config.h b/applications/settings/desktop_settings/scenes/desktop_settings_scene_config.h index 5bc52172b..b85ab36af 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_config.h +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_config.h @@ -9,3 +9,6 @@ ADD_SCENE(desktop_settings, pin_setup, PinSetup) ADD_SCENE(desktop_settings, pin_setup_howto, PinSetupHowto) ADD_SCENE(desktop_settings, pin_setup_howto2, PinSetupHowto2) ADD_SCENE(desktop_settings, pin_setup_done, PinSetupDone) + +ADD_SCENE(desktop_settings, change_name, ChangeName) +ADD_SCENE(desktop_settings, name_popup, NamePopup) diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_name_popup.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_name_popup.c new file mode 100644 index 000000000..2f03a9bfd --- /dev/null +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_name_popup.c @@ -0,0 +1,54 @@ +#include +#include +#include +#include + +#include "../desktop_settings_app.h" +#include +#include "desktop_settings_scene.h" + +#define SCENE_EVENT_EXIT (0U) + +static void name_popup_back_callback(void* context) { + furi_assert(context); + DesktopSettingsApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EVENT_EXIT); +} + +void desktop_settings_scene_name_popup_on_enter(void* context) { + furi_assert(context); + DesktopSettingsApp* app = context; + + popup_set_context(app->popup, app); + popup_set_callback(app->popup, name_popup_back_callback); + popup_set_header(app->popup, "Name is set!", 64, 20, AlignCenter, AlignCenter); + popup_set_text( + app->popup, "Your Flipper will be\nrestarted on exit.", 64, 40, AlignCenter, AlignCenter); + popup_set_timeout(app->popup, 2100); + popup_enable_timeout(app->popup); + view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewIdPopup); +} + +bool desktop_settings_scene_name_popup_on_event(void* context, SceneManagerEvent event) { + DesktopSettingsApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + switch(event.event) { + case SCENE_EVENT_EXIT: + scene_manager_search_and_switch_to_previous_scene( + app->scene_manager, DesktopSettingsAppSceneStart); + consumed = true; + break; + + default: + consumed = true; + break; + } + } + return consumed; +} + +void desktop_settings_scene_name_popup_on_exit(void* context) { + UNUSED(context); +} diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_auth.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_auth.c index 5fed235ce..be2ee4825 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_auth.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_auth.c @@ -1,7 +1,7 @@ #include #include #include -#include +#include #include "../desktop_settings_app.h" #include #include @@ -18,7 +18,7 @@ static void pin_auth_done_callback(const PinCode* pin_code, void* context) { DesktopSettingsApp* app = context; app->pincode_buffer = *pin_code; - if(desktop_pins_are_equal(&app->settings.pin_code, pin_code)) { + if(desktop_pin_compare(&app->settings.pin_code, pin_code)) { view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EVENT_PINS_EQUAL); } else { view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EVENT_PINS_DIFFERENT); diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_error.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_error.c index dd1e85795..508992cee 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_error.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_error.c @@ -6,7 +6,7 @@ #include #include "desktop_settings_scene.h" #include "desktop_settings_scene_i.h" -#include +#include #include "../desktop_settings_app.h" #define SCENE_EVENT_EXIT (0U) diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup.c index bf0f48ae6..1603aa337 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup.c @@ -7,7 +7,7 @@ #include #include "desktop_settings_scene.h" #include "desktop_settings_scene_i.h" -#include +#include #define SCENE_EVENT_EXIT (0U) #define SCENE_EVENT_1ST_PIN_ENTERED (1U) @@ -25,7 +25,7 @@ static void pin_setup_done_callback(const PinCode* pin_code, void* context) { view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EVENT_1ST_PIN_ENTERED); } else { app->pincode_buffer_filled = false; - if(desktop_pins_are_equal(&app->pincode_buffer, pin_code)) { + if(desktop_pin_compare(&app->pincode_buffer, pin_code)) { view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EVENT_PINS_EQUAL); } else { view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EVENT_PINS_DIFFERENT); diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_start.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_start.c index b28d95d16..9d63f6628 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_start.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_start.c @@ -10,8 +10,8 @@ #define SCENE_EVENT_SELECT_FAVORITE_TERTIARY 2 #define SCENE_EVENT_SELECT_PIN_SETUP 3 #define SCENE_EVENT_SELECT_AUTO_LOCK_DELAY 4 -#define SCENE_EVENT_SELECT_AUTO_LOCK_PIN 5 -#define SCENE_EVENT_SELECT_BATTERY_DISPLAY 6 +#define SCENE_EVENT_SELECT_BATTERY_DISPLAY 5 +#define SCENE_EVENT_SELECT_CHANGE_NAME 6 #define AUTO_LOCK_DELAY_COUNT 9 const char* const auto_lock_delay_text[AUTO_LOCK_DELAY_COUNT] = { @@ -63,14 +63,6 @@ static void desktop_settings_scene_start_auto_lock_delay_changed(VariableItem* i app->settings.auto_lock_delay_ms = auto_lock_delay_value[index]; } -static void desktop_settings_scene_start_auto_lock_pin_changed(VariableItem* item) { - DesktopSettingsApp* app = variable_item_get_context(item); - uint8_t value = variable_item_get_current_value_index(item); - - variable_item_set_current_value_text(item, value ? "ON" : "OFF"); - app->settings.auto_lock_with_pin = value; -} - void desktop_settings_scene_start_on_enter(void* context) { DesktopSettingsApp* app = context; VariableItemList* variable_item_list = app->variable_item_list; @@ -100,16 +92,6 @@ void desktop_settings_scene_start_on_enter(void* context) { variable_item_set_current_value_index(item, value_index); variable_item_set_current_value_text(item, auto_lock_delay_text[value_index]); - item = variable_item_list_add( - variable_item_list, - "Auto Lock Pin", - 2, - desktop_settings_scene_start_auto_lock_pin_changed, - app); - - variable_item_set_current_value_index(item, app->settings.auto_lock_with_pin); - variable_item_set_current_value_text(item, app->settings.auto_lock_with_pin ? "ON" : "OFF"); - item = variable_item_list_add( variable_item_list, "Battery View", @@ -124,6 +106,8 @@ void desktop_settings_scene_start_on_enter(void* context) { variable_item_set_current_value_index(item, value_index); variable_item_set_current_value_text(item, battery_view_count_text[value_index]); + variable_item_list_add(variable_item_list, "Change Flipper Name", 0, NULL, app); + view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewVarItemList); } @@ -155,10 +139,11 @@ bool desktop_settings_scene_start_on_event(void* context, SceneManagerEvent even case SCENE_EVENT_SELECT_AUTO_LOCK_DELAY: consumed = true; break; - case SCENE_EVENT_SELECT_AUTO_LOCK_PIN: + case SCENE_EVENT_SELECT_BATTERY_DISPLAY: consumed = true; break; - case SCENE_EVENT_SELECT_BATTERY_DISPLAY: + case SCENE_EVENT_SELECT_CHANGE_NAME: + scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneChangeName); consumed = true; break; } diff --git a/applications/system/storage_move_to_sd/storage_move_to_sd.c b/applications/system/storage_move_to_sd/storage_move_to_sd.c index 9c91b9266..2f33eec33 100644 --- a/applications/system/storage_move_to_sd/storage_move_to_sd.c +++ b/applications/system/storage_move_to_sd/storage_move_to_sd.c @@ -5,6 +5,7 @@ #include #include #include +#include #define TAG "MoveToSd" @@ -20,6 +21,17 @@ static bool storage_move_to_sd_check_entry(const char* name, FileInfo* fileinfo, return (name && (*name != '.')); } +static void storage_move_to_sd_remove_region() { + if(furi_hal_rtc_get_boot_mode() != FuriHalRtcBootModeNormal) return; + Storage* storage = furi_record_open(RECORD_STORAGE); + + if(storage_common_exists(storage, INT_PATH(".region_data"))) { + storage_common_remove(storage, INT_PATH(".region_data")); + } + + furi_record_close(RECORD_STORAGE); +} + bool storage_move_to_sd_perform(void) { Storage* storage = furi_record_open(RECORD_STORAGE); @@ -162,6 +174,9 @@ int32_t storage_move_to_sd_app(void* p) { FURI_LOG_I(TAG, "Nothing to move"); } + // Remove unused region file from int memory + storage_move_to_sd_remove_region(); + return 0; } diff --git a/assets/protobuf b/assets/protobuf index 1f6b4a08c..a13c5ddd0 160000 --- a/assets/protobuf +++ b/assets/protobuf @@ -1 +1 @@ -Subproject commit 1f6b4a08c5d05c2b17926a3ba79f60109638932f +Subproject commit a13c5ddd0397511bd4c6de4afdd1031a5b6f5bca diff --git a/assets/resources/badusb/assets/layouts/fr-FR-mac.kl b/assets/resources/badusb/assets/layouts/fr-FR-mac.kl new file mode 100644 index 000000000..090693654 Binary files /dev/null and b/assets/resources/badusb/assets/layouts/fr-FR-mac.kl differ diff --git a/assets/resources/infrared/assets/ac.ir b/assets/resources/infrared/assets/ac.ir index 777d5399c..44f685bce 100644 --- a/assets/resources/infrared/assets/ac.ir +++ b/assets/resources/infrared/assets/ac.ir @@ -1,7 +1,7 @@ Filetype: IR library file Version: 1 -# Last Updated 2nd May, 2023 -# Last Checked 2nd May, 2023 +# Last Updated 17th May, 2023 +# Last Checked 17th May, 2023 # name: POWER type: raw @@ -1770,3 +1770,51 @@ type: raw frequency: 38000 duty_cycle: 0.330000 data: 645 17766 3059 8884 533 461 557 1427 560 435 584 433 534 458 534 458 534 458 588 404 587 405 586 1399 586 407 584 408 558 1429 555 1457 502 491 525 1460 528 1458 554 1431 554 1431 555 1431 554 438 555 438 554 439 553 440 553 464 528 464 528 464 529 465 528 465 526 467 526 467 527 465 553 439 554 439 554 438 554 439 554 439 553 439 554 439 553 439 553 439 554 440 552 464 528 464 528 465 528 466 527 466 501 492 525 468 526 466 527 466 553 439 554 439 553 440 552 1433 553 1433 553 2936 3024 8893 552 1458 527 465 528 465 527 467 526 467 500 493 524 468 526 467 526 467 552 1434 552 441 552 441 552 1435 550 465 528 1458 527 1458 528 1459 527 1485 501 1485 500 1486 501 491 527 466 528 465 528 465 527 465 528 465 527 465 528 465 528 465 527 465 528 465 527 466 527 466 527 492 500 492 501 492 500 493 500 493 500 492 526 466 527 465 527 465 527 465 527 466 527 466 527 466 527 465 527 466 526 466 527 466 526 467 526 493 499 493 474 518 499 494 500 493 526 2936 3025 8918 526 1459 527 466 527 466 527 466 526 466 527 466 526 467 526 467 525 467 526 1460 525 493 499 493 498 495 498 495 499 493 500 493 525 1460 527 1460 525 1460 526 1460 525 1460 526 1460 526 468 525 1487 499 1487 497 495 498 495 499 493 500 1486 525 1460 525 1460 525 467 525 467 526 467 525 467 526 468 524 1462 524 468 524 494 498 1488 497 1488 499 494 499 493 525 468 525 1461 524 468 525 468 524 468 525 468 525 468 524 468 525 469 524 494 498 494 499 1488 473 1514 524 +# +name: POWER +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 46 00 00 00 +# +name: TEMP+ +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 07 00 00 00 +# +name: TEMP- +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 0C 00 00 00 +# +name: TIMER +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 42 00 00 00 +# +name: MODE +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 09 00 00 00 +# +name: POWER +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9464 4442 637 583 601 584 601 584 601 584 600 582 603 580 604 580 630 555 629 1637 627 1665 598 1668 571 1696 569 1696 594 1672 594 1672 592 1674 593 1673 593 591 594 1673 593 1672 593 591 591 593 569 616 568 616 593 593 591 1674 593 592 593 593 592 1675 592 1675 592 1700 568 1700 568 39559 9409 2272 567 96244 9461 2246 594 +# +name: SWING +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9505 4415 688 531 602 584 602 585 601 584 601 581 604 580 605 580 605 581 628 1636 628 1665 599 1667 597 1670 595 1671 595 1671 596 1672 595 1673 595 1673 595 592 594 591 595 1672 595 590 595 590 595 591 594 591 594 590 595 1672 595 1672 595 591 594 1672 595 1672 595 1672 595 1672 595 39565 9410 2244 592 96225 9442 2244 592 +# +name: TIMER +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9515 4477 575 611 576 551 636 610 576 611 575 610 576 581 606 580 606 580 606 1661 631 1639 628 1666 600 1667 599 1671 596 1673 595 1673 595 1673 595 593 594 617 570 594 593 1674 594 617 570 617 570 617 570 617 570 1699 569 1699 569 1699 569 618 569 1699 569 1698 570 1698 570 1699 569 diff --git a/assets/resources/infrared/assets/audio.ir b/assets/resources/infrared/assets/audio.ir index ce6d0a7aa..14c18c04c 100644 --- a/assets/resources/infrared/assets/audio.ir +++ b/assets/resources/infrared/assets/audio.ir @@ -1,7 +1,7 @@ Filetype: IR library file Version: 1 # Last Updated 14th Apr, 2023 -# Last Checked 2nd May, 2023 +# Last Checked 17th May, 2023 # name: POWER type: parsed diff --git a/assets/resources/infrared/assets/fans.ir b/assets/resources/infrared/assets/fans.ir index e422bdce4..a741daf8e 100644 --- a/assets/resources/infrared/assets/fans.ir +++ b/assets/resources/infrared/assets/fans.ir @@ -1,7 +1,7 @@ Filetype: IR library file Version: 1 -# Last Updated 2nd May, 2023 -# Last Checked 2nd May, 2023 +# Last Updated 17th May, 2023 +# Last Checked 17th May, 2023 # name: POWER type: raw @@ -1503,3 +1503,33 @@ type: parsed protocol: NEC address: 80 00 00 00 command: 1A 00 00 00 +# +name: POWER +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1370 314 1375 320 519 1167 1370 322 1339 350 465 1221 467 1222 467 1222 466 1221 467 1221 467 1221 1317 7067 1317 372 1341 349 491 1198 1340 352 1337 353 486 1202 486 1226 462 1226 462 1227 461 1227 462 1227 1311 7073 1312 378 1311 378 462 1227 1311 378 1312 378 462 1227 462 1227 461 1227 462 1227 461 1227 461 1227 1311 7074 1311 378 1311 378 462 1227 1311 379 1310 379 461 1228 461 1228 460 1228 460 1228 460 1229 459 1228 1310 7076 1309 381 1309 380 460 1229 1310 381 1309 381 458 1230 458 1230 459 1230 459 1230 459 1230 458 1230 1309 7077 1309 380 1310 380 460 1229 1310 380 1310 380 459 1229 460 1229 459 1229 460 1229 459 1229 459 1229 1310 7075 1310 379 1310 380 459 1229 1310 380 1310 379 460 1229 460 1229 459 1229 460 1228 460 1229 459 1229 1310 7074 1310 380 1310 379 460 1229 1310 379 1310 379 460 1229 459 1229 459 1229 460 1229 460 1229 460 1228 1311 +# +name: SPEED+ +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1394 321 1369 321 520 1141 1397 321 1368 321 519 1142 495 1194 495 1194 494 1195 494 1195 1366 351 491 7891 1341 350 1337 353 487 1201 1337 354 1336 354 487 1202 487 1202 487 1202 487 1202 487 1202 1336 354 487 7900 1336 354 1337 354 486 1202 1337 354 1336 354 487 1202 487 1202 487 1203 486 1202 487 1202 1336 354 487 7901 1335 354 1336 354 487 1203 1335 354 1336 355 486 1203 486 1203 486 1203 486 1203 486 1203 1335 355 486 7900 1335 355 1335 355 486 1203 1335 355 1335 355 486 1203 486 1204 485 1204 485 1204 485 1203 1335 356 485 7900 1334 356 1334 356 485 1204 1334 356 1334 356 485 1204 485 1204 484 1204 485 1204 485 1204 1333 357 484 7901 1333 357 1333 380 460 1228 1310 380 1310 380 460 1228 460 1228 460 1228 460 1228 460 1228 1310 380 460 7924 1309 380 1310 380 460 1229 1309 380 1310 380 460 1229 459 1229 460 1229 459 1230 459 1230 1309 381 458 +# +name: ROTATE +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1343 372 1318 372 467 1194 1346 372 1318 372 467 1193 525 1164 1400 319 495 1166 523 1166 523 1167 521 7890 1344 347 1340 350 489 1200 1339 351 1339 351 488 1201 488 1201 1338 351 488 1201 488 1201 488 1201 488 7899 1338 352 1338 352 487 1201 1339 352 1338 352 488 1201 488 1201 1339 352 487 1202 487 1201 488 1201 488 7899 1338 352 1338 352 487 1201 1338 352 1338 352 487 1202 487 1202 1337 353 486 1202 487 1202 487 1202 487 7900 1337 353 1337 353 486 1202 1338 353 1337 353 486 1202 487 1202 1338 353 486 1202 487 1202 487 1202 487 7900 1337 353 1337 353 486 1203 1336 354 1336 354 485 1203 486 1204 1335 354 485 1203 485 1203 486 1203 486 7901 1336 378 1312 355 484 1205 1335 378 1312 378 461 1227 462 1227 1313 378 461 1228 461 1228 461 1228 461 7925 1312 378 1312 378 461 1228 1311 378 1312 378 461 1228 461 1228 1312 378 461 1228 461 1228 461 1228 461 +# +name: TIMER +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1370 319 1371 321 519 1167 1371 322 1366 324 488 1198 466 1222 466 1222 1317 375 464 1221 467 1220 468 7915 1317 372 1341 350 490 1197 1340 376 1313 377 462 1226 462 1226 462 1226 1312 377 462 1226 462 1226 462 7921 1312 377 1313 377 462 1226 1312 377 1312 377 462 1226 462 1226 462 1226 1312 378 462 1226 462 1226 462 7922 1312 377 1313 377 462 1226 1313 377 1312 377 462 1226 462 1226 462 1226 1313 377 462 1226 462 1226 462 7921 1312 377 1312 377 462 1226 1312 377 1313 377 462 1226 462 1226 462 1226 1312 377 462 1226 462 1226 462 7921 1312 377 1313 377 462 1226 1313 377 1312 354 485 1202 486 1202 486 1202 1337 352 487 1202 487 1201 487 7897 1337 352 1337 351 488 1200 1339 351 1339 352 487 1201 487 1201 488 1201 1338 352 487 1201 487 1201 488 7896 1338 352 1337 352 487 1201 1337 352 1337 352 487 1201 487 1201 487 1201 1338 352 487 1201 487 1201 487 +# +name: MODE +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1369 349 1370 319 521 1167 1371 321 1340 349 466 1221 467 1222 467 1221 468 1221 1318 373 466 1220 468 7917 1344 347 1341 349 489 1198 1340 351 1338 352 487 1202 486 1202 486 1202 486 1202 1337 353 486 1202 486 7922 1312 377 1313 377 462 1227 1312 377 1313 378 461 1227 461 1227 462 1227 462 1227 1312 377 462 1227 461 7923 1312 378 1311 378 461 1227 1312 378 1311 377 462 1227 461 1227 461 1227 461 1227 1312 377 462 1227 462 7922 1312 378 1312 377 462 1227 1312 354 1336 354 485 1227 461 1227 461 1227 461 1203 1336 353 486 1203 486 7922 1312 353 1336 354 485 1203 1336 354 1336 354 485 1203 485 1227 461 1227 461 1227 1312 378 461 1227 461 7923 1312 378 1312 378 461 1227 1312 378 1312 378 461 1228 461 1228 461 1228 460 1228 1311 378 461 1227 461 7923 1311 378 1311 378 461 1227 1312 378 1312 378 461 1227 462 1227 461 1227 461 1227 1312 378 461 1228 461 6641 1312 378 1312 355 484 1228 1311 355 1335 378 461 1228 461 1228 460 1228 460 1228 1311 378 461 1228 460 7924 1311 379 1310 379 460 1228 1311 379 1311 379 460 1229 459 1229 459 1229 460 1229 1310 380 459 1229 459 7925 1310 380 1309 381 458 1230 1309 381 1309 381 458 1230 458 1231 457 1231 458 1231 1308 381 458 1231 457 7952 1283 407 1283 407 432 1256 1283 407 1283 407 432 1257 431 1257 432 1257 431 1257 1283 408 431 1257 431 diff --git a/assets/resources/infrared/assets/projectors.ir b/assets/resources/infrared/assets/projectors.ir index 8e81a25bd..43c3723ef 100644 --- a/assets/resources/infrared/assets/projectors.ir +++ b/assets/resources/infrared/assets/projectors.ir @@ -1,7 +1,7 @@ Filetype: IR library file Version: 1 # Last Updated 2nd May, 2023 -# Last Checked 2nd May, 2023 +# Last Checked 17th May, 2023 # # ON name: POWER diff --git a/assets/resources/infrared/assets/tv.ir b/assets/resources/infrared/assets/tv.ir index 8c781eea9..a0fe73bd9 100755 --- a/assets/resources/infrared/assets/tv.ir +++ b/assets/resources/infrared/assets/tv.ir @@ -1,7 +1,7 @@ Filetype: IR library file Version: 1 -# Last Updated 2nd May, 2023 -# Last Checked 2nd May, 2023 +# Last Updated 17th May, 2023 +# Last Checked 17th May, 2023 # name: POWER type: parsed @@ -1973,3 +1973,77 @@ type: raw frequency: 38000 duty_cycle: 0.330000 data: 273 1816 272 767 277 789 255 785 249 791 253 787 246 1818 281 785 248 765 279 1812 276 789 255 759 275 791 253 1812 276 789 255 46372 281 1808 280 785 249 791 253 787 247 793 251 1814 274 791 253 1812 276 1814 274 791 253 1812 276 1814 274 1815 273 792 252 1813 275 42172 272 1819 280 785 249 765 279 761 273 768 276 764 280 1811 277 788 246 768 276 1815 273 792 252 788 246 794 250 1815 273 791 253 +# +# Brandt TV +# +name: POWER +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9346 4516 657 502 659 502 688 473 687 475 634 527 635 527 634 1662 633 528 662 1634 662 1658 635 1660 608 1687 609 1687 633 1662 634 527 634 1662 634 528 633 1663 632 529 632 530 631 1665 630 531 630 531 631 531 630 1666 630 531 630 1666 630 1666 630 532 630 1666 630 1666 630 1666 630 +# +name: VOL+ +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9348 4542 631 529 632 503 659 502 688 474 634 527 634 527 634 1661 635 528 661 1636 660 1659 634 1637 632 1687 608 1687 609 1687 609 552 632 1664 633 528 633 1663 633 529 632 1664 631 1665 631 531 630 531 631 531 631 1665 631 531 631 1666 630 531 631 531 630 1666 630 1666 630 1666 631 +# +name: VOL- +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9376 4512 660 475 684 475 635 527 634 528 633 527 634 527 634 1662 662 525 636 1658 637 1658 637 1658 636 1659 636 1660 635 1660 635 526 635 1661 634 527 634 1662 633 1663 632 1664 631 1665 630 531 631 531 630 531 630 1666 630 531 630 531 631 531 630 531 631 1666 630 1666 630 1667 630 +# +name: CH+ +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9315 4513 657 501 660 501 659 500 661 502 688 499 661 474 634 1660 634 527 634 1660 634 1686 608 1687 607 1686 608 1686 608 1686 608 552 608 1686 608 1687 607 1687 632 530 631 1664 630 1665 630 531 630 531 630 531 630 531 630 531 630 1665 630 531 630 531 630 1665 630 1665 630 1665 630 +# +name: CH- +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9345 4540 631 503 658 529 661 475 683 476 635 526 635 527 634 1661 634 527 662 1658 636 1659 634 1660 608 1686 609 1686 634 1661 634 527 634 1661 634 1662 633 1662 633 1663 632 1664 630 1665 630 531 630 531 630 531 630 531 631 531 630 531 630 531 630 532 629 1666 630 1666 629 1666 629 +# +name: MUTE +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9348 4516 657 501 660 502 659 502 659 502 688 474 634 527 634 1662 634 528 633 1687 634 1660 608 1664 631 1687 608 1687 609 1686 609 552 609 1687 608 553 632 529 633 529 632 530 631 1665 630 531 630 531 631 531 630 1666 630 1666 630 1666 630 1666 630 531 631 1666 630 1666 630 1666 630 +# +name: POWER +type: parsed +protocol: Samsung32 +address: 3E 00 00 00 +command: 0C 00 00 00 +# +name: VOL+ +type: parsed +protocol: Samsung32 +address: 3E 00 00 00 +command: 14 00 00 00 +# +name: VOL- +type: parsed +protocol: Samsung32 +address: 3E 00 00 00 +command: 15 00 00 00 +# +name: CH+ +type: parsed +protocol: Samsung32 +address: 3E 00 00 00 +command: 12 00 00 00 +# +name: CH- +type: parsed +protocol: Samsung32 +address: 3E 00 00 00 +command: 13 00 00 00 +# +name: MUTE +type: parsed +protocol: Samsung32 +address: 3E 00 00 00 +command: 0D 00 00 00 diff --git a/documentation/AppManifests.md b/documentation/AppManifests.md index 99f6386b2..b48a6b8ed 100644 --- a/documentation/AppManifests.md +++ b/documentation/AppManifests.md @@ -47,7 +47,7 @@ Only two parameters are mandatory: **_appid_** and **_apptype_**. Others are opt The following parameters are used only for [FAPs](./AppsOnSDCard.md): - **sources**: list of strings, file name masks used for gathering sources within the app folder. The default value of `["*.c*"]` includes C and C++ source files. Applications cannot use the `"lib"` folder for their own source code, as it is reserved for **fap_private_libs**. -- **fap_version**: tuple, 2 numbers in the form of (x,y): application version to be embedded within .fap file. The default value is (0,1), meaning version "0.1". +- **fap_version**: string, application version. The default value is "0.1". You can also use a tuple of 2 numbers in the form of (x,y) to specify the version. It is also possible to add more dot-separated parts to the version, like patch number, but only major and minor version numbers are stored in the built .fap. - **fap_icon**: name of a `.png` file, 1-bit color depth, 10x10px, to be embedded within `.fap` file. - **fap_libs**: list of extra libraries to link the application against. Provides access to extra functions that are not exported as a part of main firmware at the expense of increased `.fap` file size and RAM consumption. - **fap_category**: string, may be empty. App subcategory, also determines the path of the FAP within the apps folder in the file system. diff --git a/documentation/CustomFlipperName.md b/documentation/CustomFlipperName.md index d16c35b58..54a6f7ad8 100644 --- a/documentation/CustomFlipperName.md +++ b/documentation/CustomFlipperName.md @@ -1,35 +1,10 @@ # How to change Flipper name: ## Instruction -1. Read [How to build](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/HowToBuild.md) and [How to install](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/HowToInstall.md) to know how to build and install firmware -2. Follow how to build instructions to prepare all things before continuing -3. Run release build to verify all is ok - `./fbt COMPACT=1 DEBUG=0 updater_package` -4. Clear build files - `./fbt COMPACT=1 DEBUG=0 updater_package -c` -5. Run command with extra environment var before `./fbt` that variable should contain your custom name in alphanumeric characters - max length 8 chars - `CUSTOM_FLIPPER_NAME=Name ./fbt COMPACT=1 DEBUG=0 updater_package` - where `Name` write your custom name -6. Copy `dist/f7-C/f7-update-local` folder to microSD `update/myfw/` and run `update` file on flipper from file manager app (Archive) -7. Flash from microSD card only!!!! .dfu update from qFlipper will not work properly since name and serial number will be changed -8. Done, you will have custom name, serial number and bluetooth mac address +1. Go to Settings -> Desktop -> Change Flipper Name +2. Enter your new custom name for your flipper and click `Save`, **name will be saved on microSD card, and will stay same after firmware updates** +3. You will see a message `Name is set!` and when you exit from settings -> flipper will automatically reboot! +4. After reboot you will see your new custom name in device info and right screen `passport` +5. Done! -9. Also you can skip 5-7 and flash with `CUSTOM_FLIPPER_NAME=Name ./fbt COMPACT=1 DEBUG=0 FORCE=1 flash_usb_full` - - -## Troubleshooting -### I'm using Windows and name changing / building firmware doesn't work -- Use PowerShell or VSCode terminal(powershell by default) -- Clear build files - `.\fbt.cmd COMPACT=1 DEBUG=0 updater_package -c` -- Enter this in same terminal `$Env:CUSTOM_FLIPPER_NAME="Name"` -- Run release build - `.\fbt.cmd COMPACT=1 DEBUG=0 updater_package` -- Flash as described before (see 6.) -- If something still not work - Run powershell or VSCode as Admin -### Name stays same for every new build -- Clear build files - `./fbt COMPACT=1 DEBUG=0 updater_package -c` -- Try again -### I want to return my original name and serial number -- Flash stock FW or any CFW using microSD card offline update method - -Or -- Clear build files - `./fbt COMPACT=1 DEBUG=0 updater_package -c` -- Run release build - `./fbt COMPACT=1 DEBUG=0 updater_package` -- Copy `dist/f7-C/f7-update-local` folder to microSD `update/myfw/` and run `update` file on flipper from file manager app (Archive) -- Flash from microSD card only, .dfu update from qFlipper will not work properly since name and serial number will be changed \ No newline at end of file +**To reset device name to default - do same steps but do not enter any characters, leave it empty and click** `Save` \ No newline at end of file diff --git a/documentation/FuriHalDebuging.md b/documentation/FuriHalDebuging.md index 8ff770163..e7f2d8f2a 100644 --- a/documentation/FuriHalDebuging.md +++ b/documentation/FuriHalDebuging.md @@ -1,6 +1,6 @@ # Furi HAL Debugging -Some Furi subsystem got additional debugging features that can be enabled by adding additional defines to firmware compilation. +Some Furi subsystems got additional debugging features that can be enabled by adding additional defines to firmware compilation. Usually they are used for low level tracing and profiling or signal redirection/duplication. @@ -23,4 +23,4 @@ There are 3 signals that will be exposed to external GPIO pins: There are 2 signals that will be exposed to external GPIO pins: - `WFI` - `PB2` - Light sleep (wait for interrupt) used. Basically this is lightest and most non-breaking things power save mode. All function and debug should work correctly in this mode. -- `STOP` - `PC3` - STOP mode used. Platform deep sleep mode. Extremely fragile mode where most of the silicon is disabled or in unusable state. Debugging MCU in this mode is nearly impossible. \ No newline at end of file +- `STOP` - `PC3` - STOP mode used. Platform deep sleep mode. Extremely fragile mode where most of the silicon is disabled or in unusable state. Debugging MCU in this mode is nearly impossible. diff --git a/documentation/SubGHzRemoteProg.md b/documentation/SubGHzRemoteProg.md index a5357fe18..7cd8ea68a 100644 --- a/documentation/SubGHzRemoteProg.md +++ b/documentation/SubGHzRemoteProg.md @@ -68,6 +68,11 @@ Watch this videos to learn more (videos in Russian language): https://www.youtub 6. Long press (Right Arrow) - (0xF button - Btn:F) on Flipper for like 3-5 sec 7. Done? +## CAME Atomo + +1. Use google to find instructions - `how to program new CAME Atomo remote into receiver` +2. Watch this video to learn more (video in Russian language): https://www.youtube.com/watch?v=XeHUwfcSS30 + ## Nice Flor S - Create new remote with randomly generated serial: Go to SubGHz -> Add Manually -> Nice FloR-S 433Mhz diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index 828c77166..ee1ae1154 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,26.2,, +Version,+,26.3,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 96006c43c..84d0beda8 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,26.2,, +Version,+,26.3,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -145,6 +145,7 @@ Header,+,lib/mlib/m-rbtree.h,, Header,+,lib/mlib/m-tuple.h,, Header,+,lib/mlib/m-variant.h,, Header,+,lib/nfc/nfc_device.h,, +Header,+,lib/nfc/protocols/nfc_util.h,, Header,+,lib/one_wire/maxim_crc.h,, Header,+,lib/one_wire/one_wire_host.h,, Header,+,lib/one_wire/one_wire_slave.h,, @@ -586,11 +587,20 @@ Function,+,ble_glue_start,_Bool, Function,+,ble_glue_thread_stop,void, Function,+,ble_glue_wait_for_c2_start,_Bool,int32_t Function,-,bsearch,void*,"const void*, const void*, size_t, size_t, __compar_fn_t" +Function,+,bt_disable_peer_key_update,void,Bt* Function,+,bt_disconnect,void,Bt* +Function,+,bt_enable_peer_key_update,void,Bt* Function,+,bt_forget_bonded_devices,void,Bt* +Function,+,bt_get_profile_adv_name,const char*,Bt* +Function,+,bt_get_profile_mac_address,const uint8_t*,Bt* +Function,+,bt_get_profile_pairing_method,GapPairing,Bt* Function,+,bt_keys_storage_set_default_path,void,Bt* Function,+,bt_keys_storage_set_storage_path,void,"Bt*, const char*" +Function,+,bt_remote_rssi,_Bool,"Bt*, uint8_t*" Function,+,bt_set_profile,_Bool,"Bt*, BtProfile" +Function,+,bt_set_profile_adv_name,void,"Bt*, const char*, ..." +Function,+,bt_set_profile_mac_address,void,"Bt*, const uint8_t[6]" +Function,+,bt_set_profile_pairing_method,void,"Bt*, GapPairing" Function,+,bt_set_status_changed_callback,void,"Bt*, BtStatusChangedCallback, void*" Function,+,buffered_file_stream_alloc,Stream*,Storage* Function,+,buffered_file_stream_close,_Bool,Stream* @@ -816,6 +826,7 @@ Function,+,elements_multiline_text_framed,void,"Canvas*, uint8_t, uint8_t, const Function,+,elements_progress_bar,void,"Canvas*, uint8_t, uint8_t, uint8_t, float" Function,+,elements_progress_bar_with_text,void,"Canvas*, uint8_t, uint8_t, uint8_t, float, const char*" Function,+,elements_scrollable_text_line,void,"Canvas*, uint8_t, uint8_t, uint8_t, FuriString*, size_t, _Bool" +Function,+,elements_scrollable_text_line_str,void,"Canvas*, uint8_t, uint8_t, uint8_t, const char*, size_t, _Bool, _Bool" Function,+,elements_scrollbar,void,"Canvas*, uint16_t, uint16_t" Function,+,elements_scrollbar_pos,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint16_t, uint16_t" Function,+,elements_slightly_rounded_box,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" @@ -1037,14 +1048,19 @@ Function,+,furi_hal_bt_change_app,_Bool,"FuriHalBtProfile, GapEventCallback, voi Function,+,furi_hal_bt_clear_white_list,_Bool, Function,+,furi_hal_bt_dump_state,void,FuriString* Function,+,furi_hal_bt_ensure_c2_mode,_Bool,BleGlueC2Mode +Function,+,furi_hal_bt_get_conn_rssi,uint32_t,uint8_t* Function,-,furi_hal_bt_get_hardfault_info,const FuriHalBtHardfaultInfo*, Function,+,furi_hal_bt_get_key_storage_buff,void,"uint8_t**, uint16_t*" +Function,+,furi_hal_bt_get_profile_adv_name,const char*,FuriHalBtProfile +Function,+,furi_hal_bt_get_profile_mac_addr,const uint8_t*,FuriHalBtProfile +Function,+,furi_hal_bt_get_profile_pairing_method,GapPairing,FuriHalBtProfile Function,+,furi_hal_bt_get_radio_stack,FuriHalBtStack, Function,+,furi_hal_bt_get_rssi,float, Function,+,furi_hal_bt_get_transmitted_packets,uint32_t, Function,+,furi_hal_bt_hid_consumer_key_press,_Bool,uint16_t Function,+,furi_hal_bt_hid_consumer_key_release,_Bool,uint16_t Function,+,furi_hal_bt_hid_consumer_key_release_all,_Bool, +Function,+,furi_hal_bt_hid_get_led_state,uint8_t, Function,+,furi_hal_bt_hid_kb_press,_Bool,uint16_t Function,+,furi_hal_bt_hid_kb_release,_Bool,uint16_t Function,+,furi_hal_bt_hid_kb_release_all,_Bool, @@ -1059,6 +1075,7 @@ Function,-,furi_hal_bt_init,void, Function,+,furi_hal_bt_is_active,_Bool, Function,+,furi_hal_bt_is_alive,_Bool, Function,+,furi_hal_bt_is_ble_gatt_gap_supported,_Bool, +Function,+,furi_hal_bt_is_connected,_Bool, Function,+,furi_hal_bt_is_testing_supported,_Bool, Function,+,furi_hal_bt_lock_core2,void, Function,+,furi_hal_bt_nvm_sram_sem_acquire,void, @@ -1071,6 +1088,9 @@ Function,+,furi_hal_bt_serial_start,void, Function,+,furi_hal_bt_serial_stop,void, Function,+,furi_hal_bt_serial_tx,_Bool,"uint8_t*, uint16_t" Function,+,furi_hal_bt_set_key_storage_change_callback,void,"BleGlueKeyStorageChangedCallback, void*" +Function,+,furi_hal_bt_set_profile_adv_name,void,"FuriHalBtProfile, const char[( 18 + 1 )]" +Function,+,furi_hal_bt_set_profile_mac_addr,void,"FuriHalBtProfile, const uint8_t[( 6 )]" +Function,+,furi_hal_bt_set_profile_pairing_method,void,"FuriHalBtProfile, GapPairing" Function,+,furi_hal_bt_start_advertising,void, Function,+,furi_hal_bt_start_app,_Bool,"FuriHalBtProfile, GapEventCallback, void*" Function,+,furi_hal_bt_start_packet_rx,void,"uint8_t, uint8_t" @@ -1467,6 +1487,7 @@ Function,+,furi_hal_version_get_model_name,const char*, Function,+,furi_hal_version_get_name_ptr,const char*, Function,+,furi_hal_version_get_otp_version,FuriHalVersionOtpVersion, Function,-,furi_hal_version_init,void, +Function,-,furi_hal_version_set_name,void,const char* Function,+,furi_hal_version_uid,const uint8_t*, Function,+,furi_hal_version_uid_size,size_t, Function,-,furi_hal_vibro_init,void, @@ -1633,6 +1654,7 @@ Function,-,gamma,double,double Function,-,gamma_r,double,"double, int*" Function,-,gammaf,float,float Function,-,gammaf_r,float,"float, int*" +Function,+,gap_get_remote_conn_rssi,uint32_t,int8_t* Function,-,gap_get_state,GapState, Function,-,gap_init,_Bool,"GapConfig*, GapEventCallback, void*" Function,-,gap_start_advertising,void, @@ -2106,12 +2128,25 @@ Function,+,nfc_device_save_shadow,_Bool,"NfcDevice*, const char*" Function,+,nfc_device_set_loading_callback,void,"NfcDevice*, NfcLoadingCallback, void*" Function,+,nfc_device_set_name,void,"NfcDevice*, const char*" Function,+,nfc_file_select,_Bool,NfcDevice* +Function,+,nfc_util_bytes2num,uint64_t,"const uint8_t*, uint8_t" +Function,+,nfc_util_even_parity32,uint8_t,uint32_t +Function,+,nfc_util_num2bytes,void,"uint64_t, uint8_t, uint8_t*" +Function,+,nfc_util_odd_parity,void,"const uint8_t*, uint8_t*, uint8_t" +Function,+,nfc_util_odd_parity8,uint8_t,uint8_t Function,-,nfca_append_crc16,void,"uint8_t*, uint16_t" Function,-,nfca_emulation_handler,_Bool,"uint8_t*, uint16_t, uint8_t*, uint16_t*" Function,-,nfca_get_crc16,uint16_t,"uint8_t*, uint16_t" Function,-,nfca_signal_alloc,NfcaSignal*, Function,-,nfca_signal_encode,void,"NfcaSignal*, uint8_t*, uint16_t, uint8_t*" Function,-,nfca_signal_free,void,NfcaSignal* +Function,+,nfcv_emu_deinit,void,NfcVData* +Function,+,nfcv_emu_init,void,"FuriHalNfcDevData*, NfcVData*" +Function,+,nfcv_emu_loop,_Bool,"FuriHalNfcTxRxContext*, FuriHalNfcDevData*, NfcVData*, uint32_t" +Function,+,nfcv_emu_send,void,"FuriHalNfcTxRxContext*, NfcVData*, uint8_t*, uint8_t, NfcVSendFlags, uint32_t" +Function,-,nfcv_inventory,ReturnCode,uint8_t* +Function,-,nfcv_read_blocks,ReturnCode,"NfcVReader*, NfcVData*" +Function,-,nfcv_read_card,_Bool,"NfcVReader*, FuriHalNfcDevData*, NfcVData*" +Function,-,nfcv_read_sysinfo,ReturnCode,"FuriHalNfcDevData*, NfcVData*" Function,+,notification_internal_message,void,"NotificationApp*, const NotificationSequence*" Function,+,notification_internal_message_block,void,"NotificationApp*, const NotificationSequence*" Function,+,notification_message,void,"NotificationApp*, const NotificationSequence*" @@ -2683,7 +2718,8 @@ Function,+,subghz_block_generic_get_preset_name,void,"const char*, FuriString*" Function,+,subghz_block_generic_serialize,SubGhzProtocolStatus,"SubGhzBlockGeneric*, FlipperFormat*, SubGhzRadioPreset*" Function,-,subghz_custom_btn_get,uint8_t, Function,-,subghz_custom_btn_get_original,uint8_t, -Function,-,subghz_custom_btn_set,void,uint8_t +Function,-,subghz_custom_btn_is_allowed,_Bool, +Function,-,subghz_custom_btn_set,_Bool,uint8_t Function,-,subghz_custom_btn_set_max,void,uint8_t Function,-,subghz_custom_btn_set_original,void,uint8_t Function,-,subghz_custom_btns_reset,void, @@ -2729,6 +2765,7 @@ Function,+,subghz_protocol_blocks_parity_bytes,uint8_t,"const uint8_t[], size_t" Function,+,subghz_protocol_blocks_reverse_key,uint64_t,"uint64_t, uint8_t" Function,+,subghz_protocol_blocks_set_bit_array,void,"_Bool, uint8_t[], size_t, size_t" Function,+,subghz_protocol_blocks_xor_bytes,uint8_t,"const uint8_t[], size_t" +Function,-,subghz_protocol_came_atomo_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint16_t, SubGhzRadioPreset*" Function,-,subghz_protocol_decoder_alutech_at_4n_alloc,void*,SubGhzEnvironment* Function,-,subghz_protocol_decoder_alutech_at_4n_deserialize,SubGhzProtocolStatus,"void*, FlipperFormat*" Function,-,subghz_protocol_decoder_alutech_at_4n_feed,void,"void*, _Bool, uint32_t" @@ -3334,6 +3371,7 @@ Function,+,subghz_worker_set_pair_callback,void,"SubGhzWorker*, SubGhzWorkerPair Function,+,subghz_worker_start,void,SubGhzWorker* Function,+,subghz_worker_stop,void,SubGhzWorker* Function,+,submenu_add_item,void,"Submenu*, const char*, uint32_t, SubmenuItemCallback, void*" +Function,+,submenu_add_lockable_item,void,"Submenu*, const char*, uint32_t, SubmenuItemCallback, void*, _Bool, const char*" Function,+,submenu_alloc,Submenu*, Function,+,submenu_free,void,Submenu* Function,+,submenu_get_view,View*,Submenu* @@ -3378,6 +3416,7 @@ Function,+,text_input_get_validator_callback_context,void*,TextInput* Function,+,text_input_get_view,View*,TextInput* Function,+,text_input_reset,void,TextInput* Function,+,text_input_set_header_text,void,"TextInput*, const char*" +Function,+,text_input_set_minimum_length,void,"TextInput*, size_t" Function,+,text_input_set_result_callback,void,"TextInput*, TextInputCallback, void*, char*, size_t, _Bool" Function,+,text_input_set_validator,void,"TextInput*, TextInputValidatorCallback, void*" Function,-,tga_save,void,const char* @@ -4557,6 +4596,7 @@ Function,+,variable_item_list_set_enter_callback,void,"VariableItemList*, Variab Function,+,variable_item_list_set_selected_item,void,"VariableItemList*, uint8_t" Function,+,variable_item_set_current_value_index,void,"VariableItem*, uint8_t" Function,+,variable_item_set_current_value_text,void,"VariableItem*, const char*" +Function,+,variable_item_set_locked,void,"VariableItem*, _Bool, const char*" Function,+,variable_item_set_values_count,void,"VariableItem*, uint8_t" Function,-,vasiprintf,int,"char**, const char*, __gnuc_va_list" Function,-,vasniprintf,char*,"char*, size_t*, const char*, __gnuc_va_list" @@ -4573,6 +4613,7 @@ Function,+,version_get_gitbranchnum,const char*,const Version* Function,+,version_get_githash,const char*,const Version* Function,+,version_get_target,uint8_t,const Version* Function,+,version_get_version,const char*,const Version* +Function,-,version_set_custom_name,void,"Version*, const char*" Function,-,vfiprintf,int,"FILE*, const char*, __gnuc_va_list" Function,-,vfiscanf,int,"FILE*, const char*, __gnuc_va_list" Function,-,vfprintf,int,"FILE*, const char*, __gnuc_va_list" diff --git a/firmware/targets/f7/ble_glue/ble_glue.c b/firmware/targets/f7/ble_glue/ble_glue.c index 6b527cfca..1cb5501d9 100644 --- a/firmware/targets/f7/ble_glue/ble_glue.c +++ b/firmware/targets/f7/ble_glue/ble_glue.c @@ -2,6 +2,7 @@ #include "app_common.h" #include "ble_app.h" #include +#include #include #include @@ -72,6 +73,20 @@ void shci_register_io_bus(tSHciIO* fops) { fops->Send = ble_glue_TL_SYS_SendCmd; } +static int32_t ble_glue_TL_BLE_SendCmd(uint8_t* buffer, uint16_t size) { + if(furi_hal_bt_get_hardfault_info()) { + furi_crash("ST(R) Copro(R) HardFault"); + } + + return TL_BLE_SendCmd(buffer, size); +} + +void hci_register_io_bus(tHciIO* fops) { + /* Register IO bus services */ + fops->Init = TL_BLE_Init; + fops->Send = ble_glue_TL_BLE_SendCmd; +} + /////////////////////////////////////////////////////////////////////////////// void ble_glue_init() { diff --git a/firmware/targets/f7/ble_glue/gap.c b/firmware/targets/f7/ble_glue/gap.c index f0a9ced3c..f2fb3e388 100644 --- a/firmware/targets/f7/ble_glue/gap.c +++ b/firmware/targets/f7/ble_glue/gap.c @@ -28,6 +28,8 @@ typedef struct { GapConfig* config; GapConnectionParams connection_params; GapState state; + int8_t conn_rssi; + uint32_t time_rssi_sample; FuriMutex* state_mutex; GapEventCallback on_event_cb; void* context; @@ -56,6 +58,19 @@ static Gap* gap = NULL; static void gap_advertise_start(GapState new_state); static int32_t gap_app(void* context); +/** function for updating rssi informations in global Gap object + * +*/ +static inline void fetch_rssi() { + uint8_t ret_rssi = 127; + if(hci_read_rssi(gap->service.connection_handle, &ret_rssi) == BLE_STATUS_SUCCESS) { + gap->conn_rssi = (int8_t)ret_rssi; + gap->time_rssi_sample = furi_get_tick(); + return; + } + FURI_LOG_D(TAG, "Failed to read RSSI"); +} + static void gap_verify_connection_parameters(Gap* gap) { furi_assert(gap); @@ -128,6 +143,9 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification(void* pckt) { gap->connection_params.supervisor_timeout = event->Supervision_Timeout; FURI_LOG_I(TAG, "Connection parameters event complete"); gap_verify_connection_parameters(gap); + + // Save rssi for current connection + fetch_rssi(); break; } @@ -162,6 +180,9 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification(void* pckt) { gap->service.connection_handle = event->Connection_Handle; gap_verify_connection_parameters(gap); + + // Save rssi for current connection + fetch_rssi(); // Start pairing by sending security request aci_gap_slave_security_req(event->Connection_Handle); } break; @@ -242,6 +263,9 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification(void* pckt) { pairing_complete->Status); aci_gap_terminate(gap->service.connection_handle, 5); } else { + // Save RSSI + fetch_rssi(); + FURI_LOG_I(TAG, "Pairing complete"); GapEvent event = {.type = GapEventTypeConnected}; gap->on_event_cb(event, gap->context); //-V595 @@ -310,7 +334,7 @@ static void gap_init_svc(Gap* gap) { // Initialize GATT interface aci_gatt_init(); // Initialize GAP interface - // Skip fist symbol AD_TYPE_COMPLETE_LOCAL_NAME + // Skip first symbol AD_TYPE_COMPLETE_LOCAL_NAME char* name = gap->service.adv_name + 1; aci_gap_init( GAP_PERIPHERAL_ROLE, @@ -345,21 +369,34 @@ static void gap_init_svc(Gap* gap) { hci_le_set_default_phy(ALL_PHYS_PREFERENCE, TX_2M_PREFERRED, RX_2M_PREFERRED); // Set I/O capability bool keypress_supported = false; + // New things below + uint8_t conf_mitm = CFG_MITM_PROTECTION; + uint8_t conf_used_fixed_pin = CFG_USED_FIXED_PIN; + bool conf_bonding = gap->config->bonding_mode; + if(gap->config->pairing_method == GapPairingPinCodeShow) { aci_gap_set_io_capability(IO_CAP_DISPLAY_ONLY); } else if(gap->config->pairing_method == GapPairingPinCodeVerifyYesNo) { aci_gap_set_io_capability(IO_CAP_DISPLAY_YES_NO); keypress_supported = true; + } else if(gap->config->pairing_method == GapPairingNone) { + // Just works pairing method (IOS accept it, it seems android and linux doesn't) + conf_mitm = 0; + conf_used_fixed_pin = 0; + conf_bonding = false; + // if just works isn't supported, we want the numeric comparaison method + aci_gap_set_io_capability(IO_CAP_DISPLAY_YES_NO); + keypress_supported = true; } // Setup authentication aci_gap_set_authentication_requirement( - gap->config->bonding_mode, - CFG_MITM_PROTECTION, + conf_bonding, + conf_mitm, CFG_SC_SUPPORT, keypress_supported, CFG_ENCRYPTION_KEY_SIZE_MIN, CFG_ENCRYPTION_KEY_SIZE_MAX, - CFG_USED_FIXED_PIN, + conf_used_fixed_pin, // 0x0 for no pin 0, CFG_IDENTITY_ADDRESS); // Configure whitelist @@ -488,6 +525,9 @@ bool gap_init(GapConfig* config, GapEventCallback on_event_cb, void* context) { gap->service.connection_handle = 0xFFFF; gap->enable_adv = true; + gap->conn_rssi = 127; + gap->time_rssi_sample = 0; + // Thread configuration gap->thread = furi_thread_alloc_ex("BleGapDriver", 1024, gap_app, gap); furi_thread_start(gap->thread); @@ -507,6 +547,17 @@ bool gap_init(GapConfig* config, GapEventCallback on_event_cb, void* context) { return true; } +// Get RSSI +uint32_t gap_get_remote_conn_rssi(int8_t* rssi) { + if(gap && gap->state == GapStateConnected) { + fetch_rssi(); + *rssi = gap->conn_rssi; + + if(gap->time_rssi_sample) return furi_get_tick() - gap->time_rssi_sample; + } + return 0; +} + GapState gap_get_state() { GapState state; if(gap) { diff --git a/firmware/targets/f7/ble_glue/gap.h b/firmware/targets/f7/ble_glue/gap.h index 1e207299f..7b317e06c 100644 --- a/firmware/targets/f7/ble_glue/gap.h +++ b/firmware/targets/f7/ble_glue/gap.h @@ -81,6 +81,8 @@ GapState gap_get_state(); void gap_thread_stop(); +uint32_t gap_get_remote_conn_rssi(int8_t* rssi); + #ifdef __cplusplus } #endif diff --git a/firmware/targets/f7/ble_glue/hid_service.c b/firmware/targets/f7/ble_glue/hid_service.c index 47d242d4d..a31d6015f 100644 --- a/firmware/targets/f7/ble_glue/hid_service.c +++ b/firmware/targets/f7/ble_glue/hid_service.c @@ -14,6 +14,11 @@ typedef struct { uint16_t report_map_char_handle; uint16_t info_char_handle; uint16_t ctrl_point_char_handle; + // led state + uint16_t led_state_char_handle; + uint16_t led_state_desc_handle; + HidLedStateEventCallback led_state_event_callback; + void* led_state_ctx; } HIDSvc; static HIDSvc* hid_svc = NULL; @@ -30,6 +35,32 @@ static SVCCTL_EvtAckStatus_t hid_svc_event_handler(void* event) { } else if(blecore_evt->ecode == ACI_GATT_SERVER_CONFIRMATION_VSEVT_CODE) { // Process notification confirmation ret = SVCCTL_EvtAckFlowEnable; + } else if(blecore_evt->ecode == ACI_GATT_WRITE_PERMIT_REQ_VSEVT_CODE) { + // Process write request + aci_gatt_write_permit_req_event_rp0* req = + (aci_gatt_write_permit_req_event_rp0*)blecore_evt->data; + + furi_check(hid_svc->led_state_event_callback && hid_svc->led_state_ctx); + + // this check is likely to be incorrect, it will actually work in our case + // but we need to investigate gatt api to see what is the rules + // that specify attibute handle value from char handle (or the reverse) + if(req->Attribute_Handle == (hid_svc->led_state_char_handle + 1)) { + hid_svc->led_state_event_callback(req->Data[0], hid_svc->led_state_ctx); + aci_gatt_write_resp( + req->Connection_Handle, + req->Attribute_Handle, + 0x00, /* write_status = 0 (no error))*/ + 0x00, /* err_code */ + req->Data_Length, + req->Data); + aci_gatt_write_char_value( + req->Connection_Handle, + hid_svc->led_state_char_handle, + req->Data_Length, + req->Data); + ret = SVCCTL_EvtAckFlowEnable; + } } } return ret; @@ -55,8 +86,8 @@ void hid_svc_start() { PRIMARY_SERVICE, 2 + /* protocol mode */ (4 * HID_SVC_INPUT_REPORT_COUNT) + (3 * HID_SVC_OUTPUT_REPORT_COUNT) + - (3 * HID_SVC_FEATURE_REPORT_COUNT) + 1 + 2 + 2 + - 2, /* Service + Report Map + HID Information + HID Control Point */ + (3 * HID_SVC_FEATURE_REPORT_COUNT) + 1 + 2 + 2 + 2 + + 4, /* Service + Report Map + HID Information + HID Control Point + LED state */ &hid_svc->svc_handle); if(status) { FURI_LOG_E(TAG, "Failed to add HID service: %d", status); @@ -198,6 +229,43 @@ void hid_svc_start() { } } #endif + // Add led state output report + char_uuid.Char_UUID_16 = REPORT_CHAR_UUID; + status = aci_gatt_add_char( + hid_svc->svc_handle, + UUID_TYPE_16, + &char_uuid, + 1, + CHAR_PROP_READ | CHAR_PROP_WRITE_WITHOUT_RESP | CHAR_PROP_WRITE, + ATTR_PERMISSION_NONE, + GATT_NOTIFY_ATTRIBUTE_WRITE | GATT_NOTIFY_WRITE_REQ_AND_WAIT_FOR_APPL_RESP, + 10, + CHAR_VALUE_LEN_CONSTANT, + &(hid_svc->led_state_char_handle)); + if(status) { + FURI_LOG_E(TAG, "Failed to add led state characteristic: %d", status); + } + + // Add led state char descriptor specifying it is an output report + uint8_t buf[2] = {HID_SVC_REPORT_COUNT + 1, 2}; + desc_uuid.Char_UUID_16 = REPORT_REFERENCE_DESCRIPTOR_UUID; + status = aci_gatt_add_char_desc( + hid_svc->svc_handle, + hid_svc->led_state_char_handle, + UUID_TYPE_16, + &desc_uuid, + HID_SVC_REPORT_REF_LEN, + HID_SVC_REPORT_REF_LEN, + buf, + ATTR_PERMISSION_NONE, + ATTR_ACCESS_READ_WRITE, + GATT_DONT_NOTIFY_EVENTS, + MIN_ENCRY_KEY_SIZE, + CHAR_VALUE_LEN_CONSTANT, + &(hid_svc->led_state_desc_handle)); + if(status) { + FURI_LOG_E(TAG, "Failed to add led state descriptor: %d", status); + } // Add Report Map characteristic char_uuid.Char_UUID_16 = REPORT_MAP_CHAR_UUID; status = aci_gatt_add_char( @@ -247,6 +315,9 @@ void hid_svc_start() { if(status) { FURI_LOG_E(TAG, "Failed to add control point characteristic: %d", status); } + + hid_svc->led_state_event_callback = NULL; + hid_svc->led_state_ctx = NULL; } bool hid_svc_update_report_map(const uint8_t* data, uint16_t len) { @@ -288,6 +359,15 @@ bool hid_svc_update_info(uint8_t* data, uint16_t len) { return true; } +void hid_svc_register_led_state_callback(HidLedStateEventCallback callback, void* context) { + furi_assert(hid_svc); + furi_assert(callback); + furi_assert(context); + + hid_svc->led_state_event_callback = callback; + hid_svc->led_state_ctx = context; +} + bool hid_svc_is_started() { return hid_svc != NULL; } @@ -320,6 +400,10 @@ void hid_svc_stop() { if(status) { FURI_LOG_E(TAG, "Failed to delete Control Point characteristic: %d", status); } + status = aci_gatt_del_char(hid_svc->svc_handle, hid_svc->led_state_char_handle); + if(status) { + FURI_LOG_E(TAG, "Failed to delete led state characteristic: %d", status); + } // Delete service status = aci_gatt_del_service(hid_svc->svc_handle); if(status) { diff --git a/firmware/targets/f7/ble_glue/hid_service.h b/firmware/targets/f7/ble_glue/hid_service.h index 723460d49..b8f6b244d 100644 --- a/firmware/targets/f7/ble_glue/hid_service.h +++ b/firmware/targets/f7/ble_glue/hid_service.h @@ -15,6 +15,8 @@ #define HID_SVC_REPORT_COUNT \ (HID_SVC_INPUT_REPORT_COUNT + HID_SVC_OUTPUT_REPORT_COUNT + HID_SVC_FEATURE_REPORT_COUNT) +typedef uint16_t (*HidLedStateEventCallback)(uint8_t state, void* ctx); + void hid_svc_start(); void hid_svc_stop(); @@ -26,3 +28,5 @@ bool hid_svc_update_report_map(const uint8_t* data, uint16_t len); bool hid_svc_update_input_report(uint8_t input_report_num, uint8_t* data, uint16_t len); bool hid_svc_update_info(uint8_t* data, uint16_t len); + +void hid_svc_register_led_state_callback(HidLedStateEventCallback callback, void* context); diff --git a/firmware/targets/f7/furi_hal/furi_hal_bt.c b/firmware/targets/f7/furi_hal/furi_hal_bt.c index 048a8b309..4e8b3091f 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_bt.c +++ b/firmware/targets/f7/furi_hal/furi_hal_bt.c @@ -199,26 +199,39 @@ bool furi_hal_bt_start_app(FuriHalBtProfile profile, GapEventCallback event_cb, FURI_LOG_E(TAG, "Can't start Ble App - unsupported radio stack"); break; } - // Set mac address - memcpy( - profile_config[profile].config.mac_address, - furi_hal_version_get_ble_mac(), - sizeof(profile_config[profile].config.mac_address)); - // Set advertise name - strlcpy( - profile_config[profile].config.adv_name, - furi_hal_version_get_ble_local_device_name_ptr(), - FURI_HAL_VERSION_DEVICE_NAME_LENGTH); - // Configure GAP GapConfig* config = &profile_config[profile].config; + // Configure GAP if(profile == FuriHalBtProfileSerial) { + // Set mac address + memcpy( + config->mac_address, furi_hal_version_get_ble_mac(), sizeof(config->mac_address)); + // Set advertise name + strlcpy( + config->adv_name, + furi_hal_version_get_ble_local_device_name_ptr(), + FURI_HAL_VERSION_DEVICE_NAME_LENGTH); + config->adv_service_uuid |= furi_hal_version_get_hw_color(); } else if(profile == FuriHalBtProfileHidKeyboard) { // Change MAC address for HID profile - config->mac_address[2]++; + uint8_t default_mac[sizeof(config->mac_address)] = FURI_HAL_BT_DEFAULT_MAC_ADDR; + const uint8_t* normal_mac = furi_hal_version_get_ble_mac(); + if(memcmp(config->mac_address, default_mac, sizeof(config->mac_address)) == 0) { + memcpy(config->mac_address, normal_mac, sizeof(config->mac_address)); + } + if(memcmp(config->mac_address, normal_mac, sizeof(config->mac_address)) == 0) { + config->mac_address[2]++; + } // Change name Flipper -> Control - const char* clicker_str = "Control"; - memcpy(&config->adv_name[1], clicker_str, strlen(clicker_str)); + if(strnlen(config->adv_name, FURI_HAL_VERSION_DEVICE_NAME_LENGTH) < 2 || + strnlen(config->adv_name + 1, FURI_HAL_VERSION_DEVICE_NAME_LENGTH) < 1) { + snprintf( + config->adv_name, + FURI_HAL_VERSION_DEVICE_NAME_LENGTH, + "%cControl %s", + *furi_hal_version_get_ble_local_device_name_ptr(), + furi_hal_version_get_ble_local_device_name_ptr() + 9); + } } if(!gap_init(config, event_cb, context)) { gap_thread_stop(); @@ -280,6 +293,10 @@ bool furi_hal_bt_is_active() { return gap_get_state() > GapStateIdle; } +bool furi_hal_bt_is_connected() { + return gap_get_state() == GapStateConnected; +} + void furi_hal_bt_start_advertising() { if(gap_get_state() == GapStateIdle) { gap_start_advertising(); @@ -418,6 +435,67 @@ float furi_hal_bt_get_rssi() { return val; } +/** fill the RSSI of the remote host of the bt connection and returns the last + * time the RSSI was updated + * +*/ +uint32_t furi_hal_bt_get_conn_rssi(uint8_t* rssi) { + int8_t ret_rssi = 0; + uint32_t since = gap_get_remote_conn_rssi(&ret_rssi); + + if(ret_rssi == 127 || since == 0) return 0; + + *rssi = (uint8_t)abs(ret_rssi); + + return since; +} + +void furi_hal_bt_set_profile_adv_name( + FuriHalBtProfile profile, + const char name[FURI_HAL_BT_ADV_NAME_LENGTH]) { + furi_assert(profile < FuriHalBtProfileNumber); + furi_assert(name); + + if(strlen(name) == 0) { + memset( + &(profile_config[profile].config.adv_name[1]), + 0, + strlen(&(profile_config[profile].config.adv_name[1]))); + } else { + profile_config[profile].config.adv_name[0] = AD_TYPE_COMPLETE_LOCAL_NAME; + memcpy(&(profile_config[profile].config.adv_name[1]), name, FURI_HAL_BT_ADV_NAME_LENGTH); + } +} + +const char* furi_hal_bt_get_profile_adv_name(FuriHalBtProfile profile) { + furi_assert(profile < FuriHalBtProfileNumber); + return &(profile_config[profile].config.adv_name[1]); +} + +void furi_hal_bt_set_profile_mac_addr( + FuriHalBtProfile profile, + const uint8_t mac_addr[GAP_MAC_ADDR_SIZE]) { + furi_assert(profile < FuriHalBtProfileNumber); + furi_assert(mac_addr); + + memcpy(profile_config[profile].config.mac_address, mac_addr, GAP_MAC_ADDR_SIZE); +} + +const uint8_t* furi_hal_bt_get_profile_mac_addr(FuriHalBtProfile profile) { + furi_assert(profile < FuriHalBtProfileNumber); + return profile_config[profile].config.mac_address; +} + +void furi_hal_bt_set_profile_pairing_method(FuriHalBtProfile profile, GapPairing pairing_method) { + furi_assert(profile < FuriHalBtProfileNumber); + profile_config[profile].config.pairing_method = pairing_method; +} + +GapPairing furi_hal_bt_get_profile_pairing_method(FuriHalBtProfile profile) { + furi_assert(profile < FuriHalBtProfileNumber); + return profile_config[profile].config.pairing_method; +} + uint32_t furi_hal_bt_get_transmitted_packets() { uint32_t packets = 0; aci_hal_le_tx_test_packet_number(&packets); diff --git a/firmware/targets/f7/furi_hal/furi_hal_bt_hid.c b/firmware/targets/f7/furi_hal/furi_hal_bt_hid.c index 8259be2f6..860edfcd4 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_bt_hid.c +++ b/firmware/targets/f7/furi_hal/furi_hal_bt_hid.c @@ -20,6 +20,7 @@ enum HidReportId { ReportIdKeyboard = 1, ReportIdMouse = 2, ReportIdConsumer = 3, + ReportIdLEDState = 4, }; // Report numbers corresponded to the report id with an offset of 1 enum HidInputNumber { @@ -77,6 +78,13 @@ static const uint8_t furi_hal_bt_hid_report_map_data[] = { HID_USAGE_MINIMUM(0), HID_USAGE_MAXIMUM(101), HID_INPUT(HID_IOF_DATA | HID_IOF_ARRAY | HID_IOF_ABSOLUTE), + HID_REPORT_ID(ReportIdLEDState), + HID_USAGE_PAGE(HID_PAGE_LED), + HID_REPORT_COUNT(8), + HID_REPORT_SIZE(1), + HID_USAGE_MINIMUM(1), + HID_USAGE_MAXIMUM(8), + HID_OUTPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), HID_END_COLLECTION, // Mouse Report HID_USAGE_PAGE(HID_PAGE_DESKTOP), @@ -125,6 +133,63 @@ FuriHalBtHidKbReport* kb_report = NULL; FuriHalBtHidMouseReport* mouse_report = NULL; FuriHalBtHidConsumerReport* consumer_report = NULL; +typedef struct { +// shortcuts +#define s_undefined data.bits.b_undefined +#define s_num_lock data.bits.b_num_lock +#define s_caps_lock data.bits.b_caps_lock +#define s_scroll_lock data.bits.b_scroll_lock +#define s_compose data.bits.b_compose +#define s_kana data.bits.b_kana +#define s_power data.bits.b_power +#define s_shift data.bits.b_shift +#define s_value data.value + union { + struct { + uint8_t b_undefined : 1; + uint8_t b_num_lock : 1; + uint8_t b_caps_lock : 1; + uint8_t b_scroll_lock : 1; + uint8_t b_compose : 1; + uint8_t b_kana : 1; + uint8_t b_power : 1; + uint8_t b_shift : 1; + } bits; + uint8_t value; + } data; +} __attribute__((__packed__)) FuriHalBtHidLedState; + +FuriHalBtHidLedState hid_host_led_state = {.s_value = 0}; + +uint16_t furi_hal_bt_hid_led_state_cb(uint8_t state, void* ctx) { + FuriHalBtHidLedState* led_state = (FuriHalBtHidLedState*)ctx; + + //FURI_LOG_D("HalBtHid", "LED state updated !"); + + led_state->s_value = state; + + return 0; +} + +uint8_t furi_hal_bt_hid_get_led_state(void) { + /*FURI_LOG_D( + "HalBtHid", + "LED state: RFU=%d NUMLOCK=%d CAPSLOCK=%d SCROLLLOCK=%d COMPOSE=%d KANA=%d POWER=%d SHIFT=%d", + hid_host_led_state.s_undefined, + hid_host_led_state.s_num_lock, + hid_host_led_state.s_caps_lock, + hid_host_led_state.s_scroll_lock, + hid_host_led_state.s_compose, + hid_host_led_state.s_kana, + hid_host_led_state.s_power, + hid_host_led_state.s_shift); + */ + + return (hid_host_led_state.s_value >> 1); // bit 0 is undefined (after shift bit location + // match with HID led state bits defines) + // see bad_bt_script.c (ducky_numlock_on function) +} + void furi_hal_bt_hid_start() { // Start device info if(!dev_info_svc_is_started()) { @@ -139,6 +204,8 @@ void furi_hal_bt_hid_start() { hid_svc_start(); } // Configure HID Keyboard + hid_svc_register_led_state_callback(furi_hal_bt_hid_led_state_cb, &hid_host_led_state); + kb_report = malloc(sizeof(FuriHalBtHidKbReport)); mouse_report = malloc(sizeof(FuriHalBtHidMouseReport)); consumer_report = malloc(sizeof(FuriHalBtHidConsumerReport)); @@ -160,6 +227,8 @@ void furi_hal_bt_hid_stop() { furi_assert(kb_report); furi_assert(mouse_report); furi_assert(consumer_report); + + hid_svc_register_led_state_callback(NULL, NULL); // Stop all services if(dev_info_svc_is_started()) { dev_info_svc_stop(); @@ -180,12 +249,16 @@ void furi_hal_bt_hid_stop() { bool furi_hal_bt_hid_kb_press(uint16_t button) { furi_assert(kb_report); - for(uint8_t i = 0; i < FURI_HAL_BT_HID_KB_MAX_KEYS; i++) { + uint8_t i; + for(i = 0; i < FURI_HAL_BT_HID_KB_MAX_KEYS; i++) { if(kb_report->key[i] == 0) { kb_report->key[i] = button & 0xFF; break; } } + if(i == FURI_HAL_BT_HID_KB_MAX_KEYS) { + return false; + } kb_report->mods |= (button >> 8); return hid_svc_update_input_report( ReportNumberKeyboard, (uint8_t*)kb_report, sizeof(FuriHalBtHidKbReport)); diff --git a/firmware/targets/f7/furi_hal/furi_hal_subghz.c b/firmware/targets/f7/furi_hal/furi_hal_subghz.c index 6a566d471..06a7990fb 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_subghz.c +++ b/firmware/targets/f7/furi_hal/furi_hal_subghz.c @@ -41,6 +41,7 @@ volatile FuriHalSubGhz furi_hal_subghz = { .rolling_counter_mult = 1, .ext_module_power_disabled = false, .timestamp_file_names = false, + .dangerous_frequency_i = false, }; void furi_hal_subghz_select_radio_type(SubGhzRadioType state) { @@ -96,6 +97,10 @@ bool furi_hal_subghz_get_timestamp_file_names(void) { return furi_hal_subghz.timestamp_file_names; } +void furi_hal_subghz_set_dangerous_frequency(bool state_i) { + furi_hal_subghz.dangerous_frequency_i = state_i; +} + void furi_hal_subghz_set_async_mirror_pin(const GpioPin* pin) { furi_hal_subghz.async_mirror_pin = pin; } @@ -448,29 +453,19 @@ uint32_t furi_hal_subghz_set_frequency_and_path(uint32_t value) { } bool furi_hal_subghz_is_tx_allowed(uint32_t value) { - bool is_extended = false; + bool allow_extended_for_int = furi_hal_subghz.dangerous_frequency_i; - // TODO: !!! Move file check to another place - Storage* storage = furi_record_open(RECORD_STORAGE); - FlipperFormat* fff_data_file = flipper_format_file_alloc(storage); - - if(flipper_format_file_open_existing(fff_data_file, "/ext/subghz/assets/dangerous_settings")) { - flipper_format_read_bool( - fff_data_file, "yes_i_want_to_destroy_my_flipper", &is_extended, 1); - } - - flipper_format_free(fff_data_file); - furi_record_close(RECORD_STORAGE); - - if(!(value >= 299999755 && value <= 350000335) && // was increased from 348 to 350 + if(!(allow_extended_for_int) && + !(value >= 299999755 && value <= 350000335) && // was increased from 348 to 350 !(value >= 386999938 && value <= 467750000) && // was increased from 464 to 467.75 - !(value >= 778999847 && value <= 928000000) && !(is_extended)) { + !(value >= 778999847 && value <= 928000000)) { FURI_LOG_I(TAG, "Frequency blocked - outside default range"); return false; } else if( + (allow_extended_for_int) && // !(value >= 281000000 && value <= 361000000) && !(value >= 378000000 && value <= 481000000) && - !(value >= 749000000 && value <= 962000000) && is_extended) { + !(value >= 749000000 && value <= 962000000)) { FURI_LOG_I(TAG, "Frequency blocked - outside dangerous range"); return false; } diff --git a/firmware/targets/f7/furi_hal/furi_hal_subghz.h b/firmware/targets/f7/furi_hal/furi_hal_subghz.h index 35eab4faf..0ef7bd90a 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_subghz.h +++ b/firmware/targets/f7/furi_hal/furi_hal_subghz.h @@ -78,8 +78,9 @@ typedef struct { FuriHalSpiBusHandle* spi_bus_handle; const GpioPin* cc1101_g0_pin; uint8_t rolling_counter_mult; - bool ext_module_power_disabled; - bool timestamp_file_names; + bool ext_module_power_disabled : 1; + bool timestamp_file_names : 1; + bool dangerous_frequency_i : 1; } FuriHalSubGhz; extern volatile FuriHalSubGhz furi_hal_subghz; diff --git a/firmware/targets/f7/furi_hal/furi_hal_subghz_i.h b/firmware/targets/f7/furi_hal/furi_hal_subghz_i.h new file mode 100644 index 000000000..e7fe2602f --- /dev/null +++ b/firmware/targets/f7/furi_hal/furi_hal_subghz_i.h @@ -0,0 +1,3 @@ +#pragma once + +void furi_hal_subghz_set_dangerous_frequency(bool state_i); \ No newline at end of file diff --git a/firmware/targets/f7/furi_hal/furi_hal_version.c b/firmware/targets/f7/furi_hal/furi_hal_version.c index 7e0046e52..d0857f288 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_version.c +++ b/firmware/targets/f7/furi_hal/furi_hal_version.c @@ -90,7 +90,7 @@ typedef struct { static FuriHalVersion furi_hal_version = {0}; -static void furi_hal_version_set_name(const char* name) { +void furi_hal_version_set_name(const char* name) { if(name != NULL) { strlcpy(furi_hal_version.name, name, FURI_HAL_VERSION_ARRAY_NAME_LENGTH); snprintf( diff --git a/firmware/targets/furi_hal_include/furi_hal_bt.h b/firmware/targets/furi_hal_include/furi_hal_bt.h index 6ba38cb5e..f128b1064 100644 --- a/firmware/targets/furi_hal_include/furi_hal_bt.h +++ b/firmware/targets/furi_hal_include/furi_hal_bt.h @@ -218,6 +218,35 @@ float furi_hal_bt_get_rssi(); */ uint32_t furi_hal_bt_get_transmitted_packets(); +// BadBT stuff +/** Modify profile advertisement name and restart bluetooth + * @param[in] profile profile type + * @param[in] name new adv name +*/ +void furi_hal_bt_set_profile_adv_name( + FuriHalBtProfile profile, + const char name[FURI_HAL_BT_ADV_NAME_LENGTH]); + +const char* furi_hal_bt_get_profile_adv_name(FuriHalBtProfile profile); + +/** Modify profile mac address and restart bluetooth + * @param[in] profile profile type + * @param[in] mac new mac address +*/ +void furi_hal_bt_set_profile_mac_addr( + FuriHalBtProfile profile, + const uint8_t mac_addr[GAP_MAC_ADDR_SIZE]); + +const uint8_t* furi_hal_bt_get_profile_mac_addr(FuriHalBtProfile profile); + +uint32_t furi_hal_bt_get_conn_rssi(uint8_t* rssi); + +void furi_hal_bt_set_profile_pairing_method(FuriHalBtProfile profile, GapPairing pairing_method); + +GapPairing furi_hal_bt_get_profile_pairing_method(FuriHalBtProfile profile); + +bool furi_hal_bt_is_connected(void); + /** Check & switch C2 to given mode * * @param[in] mode mode to switch into diff --git a/firmware/targets/furi_hal_include/furi_hal_bt_hid.h b/firmware/targets/furi_hal_include/furi_hal_bt_hid.h index 4e74bbda7..56a8b4e48 100644 --- a/firmware/targets/furi_hal_include/furi_hal_bt_hid.h +++ b/firmware/targets/furi_hal_include/furi_hal_bt_hid.h @@ -86,6 +86,13 @@ bool furi_hal_bt_hid_consumer_key_release(uint16_t button); */ bool furi_hal_bt_hid_consumer_key_release_all(); +/** Retrieves LED state from remote BT HID host + * + * @return (look at HID usage page to know what each bit of the returned byte means) + * NB: RFU bit has been shifted out in the returned octet so USB defines should work +*/ +uint8_t furi_hal_bt_hid_get_led_state(void); + #ifdef __cplusplus } #endif diff --git a/firmware/targets/furi_hal_include/furi_hal_version.h b/firmware/targets/furi_hal_include/furi_hal_version.h index bfe691aeb..f5e5ca49a 100644 --- a/firmware/targets/furi_hal_include/furi_hal_version.h +++ b/firmware/targets/furi_hal_include/furi_hal_version.h @@ -16,6 +16,7 @@ extern "C" { #define FURI_HAL_VERSION_NAME_LENGTH 8 #define FURI_HAL_VERSION_ARRAY_NAME_LENGTH (FURI_HAL_VERSION_NAME_LENGTH + 1) +#define FURI_HAL_BT_ADV_NAME_LENGTH (18 + 1) // 18 characters + null terminator /** BLE symbol + "Flipper " + name */ #define FURI_HAL_VERSION_DEVICE_NAME_LENGTH (1 + 8 + FURI_HAL_VERSION_ARRAY_NAME_LENGTH) @@ -175,6 +176,10 @@ const char* furi_hal_version_get_device_name_ptr(); */ const char* furi_hal_version_get_ble_local_device_name_ptr(); +/** Set flipper name + */ +void furi_hal_version_set_name(const char* name); + /** Get BLE MAC address * * @return pointer to BLE MAC address diff --git a/lib/nfc/SConscript b/lib/nfc/SConscript index b086298de..b8551db84 100644 --- a/lib/nfc/SConscript +++ b/lib/nfc/SConscript @@ -6,6 +6,7 @@ env.Append( ], SDK_HEADERS=[ File("nfc_device.h"), + File("protocols/nfc_util.h"), ], ) diff --git a/lib/nfc/helpers/reader_analyzer.c b/lib/nfc/helpers/reader_analyzer.c index 0063b13e7..9bf37a60d 100644 --- a/lib/nfc/helpers/reader_analyzer.c +++ b/lib/nfc/helpers/reader_analyzer.c @@ -2,7 +2,6 @@ #include #include #include -#include #include "mfkey32.h" #include "nfc_debug_pcap.h" @@ -38,8 +37,7 @@ struct ReaderAnalyzer { NfcDebugPcap* pcap; }; -static FuriHalNfcDevData reader_analyzer_nfc_data[] = { - //XXX +const FuriHalNfcDevData reader_analyzer_nfc_data[] = { [ReaderAnalyzerNfcDataMfClassic] = {.sak = 0x08, .atqa = {0x44, 0x00}, @@ -100,9 +98,7 @@ int32_t reader_analyzer_thread(void* context) { ReaderAnalyzer* reader_analyzer_alloc() { ReaderAnalyzer* instance = malloc(sizeof(ReaderAnalyzer)); - reader_analyzer_nfc_data[ReaderAnalyzerNfcDataMfClassic].cuid = rand(); //XXX - furi_hal_random_fill_buf( - (uint8_t*)&reader_analyzer_nfc_data[ReaderAnalyzerNfcDataMfClassic].uid, 7); + instance->nfc_data = reader_analyzer_nfc_data[ReaderAnalyzerNfcDataMfClassic]; instance->alive = false; instance->stream = diff --git a/lib/nfc/nfc_device.c b/lib/nfc/nfc_device.c index 3c9cf408a..bdbc62e50 100644 --- a/lib/nfc/nfc_device.c +++ b/lib/nfc/nfc_device.c @@ -58,6 +58,8 @@ static void nfc_device_prepare_format_string(NfcDevice* dev, FuriString* format_ furi_string_set(format_string, "Mifare Classic"); } else if(dev->format == NfcDeviceSaveFormatMifareDesfire) { furi_string_set(format_string, "Mifare DESFire"); + } else if(dev->format == NfcDeviceSaveFormatNfcV) { + furi_string_set(format_string, "ISO15693"); } else { furi_string_set(format_string, "Unknown"); } @@ -93,6 +95,11 @@ static bool nfc_device_parse_format_string(NfcDevice* dev, FuriString* format_st dev->dev_data.protocol = NfcDeviceProtocolMifareDesfire; return true; } + if(furi_string_start_with_str(format_string, "ISO15693")) { + dev->format = NfcDeviceSaveFormatNfcV; + dev->dev_data.protocol = NfcDeviceProtocolNfcV; + return true; + } return false; } @@ -650,6 +657,298 @@ bool nfc_device_load_mifare_df_data(FlipperFormat* file, NfcDevice* dev) { return parsed; } +static bool nfc_device_save_slix_data(FlipperFormat* file, NfcDevice* dev) { + bool saved = false; + NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix; + + do { + if(!flipper_format_write_comment_cstr(file, "SLIX specific data")) break; + if(!flipper_format_write_hex(file, "Password EAS", data->key_eas, sizeof(data->key_eas))) + break; + saved = true; + } while(false); + + return saved; +} + +bool nfc_device_load_slix_data(FlipperFormat* file, NfcDevice* dev) { + bool parsed = false; + NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix; + memset(data, 0, sizeof(NfcVSlixData)); + + do { + if(!flipper_format_read_hex(file, "Password EAS", data->key_eas, sizeof(data->key_eas))) + break; + + parsed = true; + } while(false); + + return parsed; +} + +static bool nfc_device_save_slix_s_data(FlipperFormat* file, NfcDevice* dev) { + bool saved = false; + NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix; + + do { + if(!flipper_format_write_comment_cstr(file, "SLIX-S specific data")) break; + if(!flipper_format_write_hex(file, "Password Read", data->key_read, sizeof(data->key_read))) + break; + if(!flipper_format_write_hex( + file, "Password Write", data->key_write, sizeof(data->key_write))) + break; + if(!flipper_format_write_hex( + file, "Password Privacy", data->key_privacy, sizeof(data->key_privacy))) + break; + if(!flipper_format_write_hex( + file, "Password Destroy", data->key_destroy, sizeof(data->key_destroy))) + break; + if(!flipper_format_write_hex(file, "Password EAS", data->key_eas, sizeof(data->key_eas))) + break; + if(!flipper_format_write_bool(file, "Privacy Mode", &data->privacy, 1)) break; + saved = true; + } while(false); + + return saved; +} + +bool nfc_device_load_slix_s_data(FlipperFormat* file, NfcDevice* dev) { + bool parsed = false; + NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix; + memset(data, 0, sizeof(NfcVSlixData)); + + do { + if(!flipper_format_read_hex(file, "Password Read", data->key_read, sizeof(data->key_read))) + break; + if(!flipper_format_read_hex( + file, "Password Write", data->key_write, sizeof(data->key_write))) + break; + if(!flipper_format_read_hex( + file, "Password Privacy", data->key_privacy, sizeof(data->key_privacy))) + break; + if(!flipper_format_read_hex( + file, "Password Destroy", data->key_destroy, sizeof(data->key_destroy))) + break; + if(!flipper_format_read_hex(file, "Password EAS", data->key_eas, sizeof(data->key_eas))) + break; + if(!flipper_format_read_bool(file, "Privacy Mode", &data->privacy, 1)) break; + + parsed = true; + } while(false); + + return parsed; +} + +static bool nfc_device_save_slix_l_data(FlipperFormat* file, NfcDevice* dev) { + bool saved = false; + NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix; + + do { + if(!flipper_format_write_comment_cstr(file, "SLIX-L specific data")) break; + if(!flipper_format_write_hex( + file, "Password Privacy", data->key_privacy, sizeof(data->key_privacy))) + break; + if(!flipper_format_write_hex( + file, "Password Destroy", data->key_destroy, sizeof(data->key_destroy))) + break; + if(!flipper_format_write_hex(file, "Password EAS", data->key_eas, sizeof(data->key_eas))) + break; + if(!flipper_format_write_bool(file, "Privacy Mode", &data->privacy, 1)) break; + saved = true; + } while(false); + + return saved; +} + +bool nfc_device_load_slix_l_data(FlipperFormat* file, NfcDevice* dev) { + bool parsed = false; + NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix; + memset(data, 0, sizeof(NfcVSlixData)); + + do { + if(!flipper_format_read_hex( + file, "Password Privacy", data->key_privacy, sizeof(data->key_privacy))) + break; + if(!flipper_format_read_hex( + file, "Password Destroy", data->key_destroy, sizeof(data->key_destroy))) + break; + if(!flipper_format_read_hex(file, "Password EAS", data->key_eas, sizeof(data->key_eas))) + break; + if(!flipper_format_read_bool(file, "Privacy Mode", &data->privacy, 1)) break; + + parsed = true; + } while(false); + + return parsed; +} + +static bool nfc_device_save_slix2_data(FlipperFormat* file, NfcDevice* dev) { + bool saved = false; + NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix; + + do { + if(!flipper_format_write_comment_cstr(file, "SLIX2 specific data")) break; + if(!flipper_format_write_hex(file, "Password Read", data->key_read, sizeof(data->key_read))) + break; + if(!flipper_format_write_hex( + file, "Password Write", data->key_write, sizeof(data->key_write))) + break; + if(!flipper_format_write_hex( + file, "Password Privacy", data->key_privacy, sizeof(data->key_privacy))) + break; + if(!flipper_format_write_hex( + file, "Password Destroy", data->key_destroy, sizeof(data->key_destroy))) + break; + if(!flipper_format_write_hex(file, "Password EAS", data->key_eas, sizeof(data->key_eas))) + break; + if(!flipper_format_write_bool(file, "Privacy Mode", &data->privacy, 1)) break; + saved = true; + } while(false); + + return saved; +} + +bool nfc_device_load_slix2_data(FlipperFormat* file, NfcDevice* dev) { + bool parsed = false; + NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix; + memset(data, 0, sizeof(NfcVSlixData)); + + do { + if(!flipper_format_read_hex(file, "Password Read", data->key_read, sizeof(data->key_read))) + break; + if(!flipper_format_read_hex( + file, "Password Write", data->key_write, sizeof(data->key_write))) + break; + if(!flipper_format_read_hex( + file, "Password Privacy", data->key_privacy, sizeof(data->key_privacy))) + break; + if(!flipper_format_read_hex( + file, "Password Destroy", data->key_destroy, sizeof(data->key_destroy))) + break; + if(!flipper_format_read_hex(file, "Password EAS", data->key_eas, sizeof(data->key_eas))) + break; + if(!flipper_format_read_bool(file, "Privacy Mode", &data->privacy, 1)) break; + + parsed = true; + } while(false); + + return parsed; +} + +static bool nfc_device_save_nfcv_data(FlipperFormat* file, NfcDevice* dev) { + bool saved = false; + NfcVData* data = &dev->dev_data.nfcv_data; + + do { + uint32_t temp_uint32 = 0; + uint8_t temp_uint8 = 0; + + if(!flipper_format_write_comment_cstr(file, "Data Storage Format Identifier")) break; + if(!flipper_format_write_hex(file, "DSFID", &(data->dsfid), 1)) break; + if(!flipper_format_write_comment_cstr(file, "Application Family Identifier")) break; + if(!flipper_format_write_hex(file, "AFI", &(data->afi), 1)) break; + if(!flipper_format_write_hex(file, "IC Reference", &(data->ic_ref), 1)) break; + temp_uint32 = data->block_num; + if(!flipper_format_write_comment_cstr(file, "Number of memory blocks, usually 0 to 256")) + break; + if(!flipper_format_write_uint32(file, "Block Count", &temp_uint32, 1)) break; + if(!flipper_format_write_comment_cstr(file, "Size of a single memory block, usually 4")) + break; + if(!flipper_format_write_hex(file, "Block Size", &(data->block_size), 1)) break; + if(!flipper_format_write_hex( + file, "Data Content", data->data, data->block_num * data->block_size)) + break; + if(!flipper_format_write_comment_cstr( + file, "First byte: DSFID (0x01) / AFI (0x02) lock info, others: block lock info")) + break; + if(!flipper_format_write_hex( + file, "Security Status", data->security_status, 1 + data->block_num)) + break; + if(!flipper_format_write_comment_cstr( + file, + "Subtype of this card (0 = ISO15693, 1 = SLIX, 2 = SLIX-S, 3 = SLIX-L, 4 = SLIX2)")) + break; + temp_uint8 = (uint8_t)data->sub_type; + if(!flipper_format_write_hex(file, "Subtype", &temp_uint8, 1)) break; + + switch(data->sub_type) { + case NfcVTypePlain: + if(!flipper_format_write_comment_cstr(file, "End of ISO15693 parameters")) break; + saved = true; + break; + case NfcVTypeSlix: + saved = nfc_device_save_slix_data(file, dev); + break; + case NfcVTypeSlixS: + saved = nfc_device_save_slix_s_data(file, dev); + break; + case NfcVTypeSlixL: + saved = nfc_device_save_slix_l_data(file, dev); + break; + case NfcVTypeSlix2: + saved = nfc_device_save_slix2_data(file, dev); + break; + default: + break; + } + } while(false); + + return saved; +} + +bool nfc_device_load_nfcv_data(FlipperFormat* file, NfcDevice* dev) { + bool parsed = false; + NfcVData* data = &dev->dev_data.nfcv_data; + + memset(data, 0x00, sizeof(NfcVData)); + + do { + uint32_t temp_uint32 = 0; + uint8_t temp_value = 0; + + if(!flipper_format_read_hex(file, "DSFID", &(data->dsfid), 1)) break; + if(!flipper_format_read_hex(file, "AFI", &(data->afi), 1)) break; + if(!flipper_format_read_hex(file, "IC Reference", &(data->ic_ref), 1)) break; + if(!flipper_format_read_uint32(file, "Block Count", &temp_uint32, 1)) break; + data->block_num = temp_uint32; + if(!flipper_format_read_hex(file, "Block Size", &(data->block_size), 1)) break; + if(!flipper_format_read_hex( + file, "Data Content", data->data, data->block_num * data->block_size)) + break; + + /* optional, as added later */ + if(flipper_format_key_exist(file, "Security Status")) { + if(!flipper_format_read_hex( + file, "Security Status", data->security_status, 1 + data->block_num)) + break; + } + if(!flipper_format_read_hex(file, "Subtype", &temp_value, 1)) break; + data->sub_type = temp_value; + + switch(data->sub_type) { + case NfcVTypePlain: + parsed = true; + break; + case NfcVTypeSlix: + parsed = nfc_device_load_slix_data(file, dev); + break; + case NfcVTypeSlixS: + parsed = nfc_device_load_slix_s_data(file, dev); + break; + case NfcVTypeSlixL: + parsed = nfc_device_load_slix_l_data(file, dev); + break; + case NfcVTypeSlix2: + parsed = nfc_device_load_slix2_data(file, dev); + break; + default: + break; + } + } while(false); + + return parsed; +} + static bool nfc_device_save_bank_card_data(FlipperFormat* file, NfcDevice* dev) { bool saved = false; EmvData* data = &dev->dev_data.emv_data; @@ -692,11 +991,17 @@ bool nfc_device_load_bank_card_data(FlipperFormat* file, NfcDevice* dev) { if(!flipper_format_get_value_count(file, "AID", &data_cnt)) break; data->aid_len = data_cnt; if(!flipper_format_read_hex(file, "AID", data->aid, data->aid_len)) break; - if(!flipper_format_read_string(file, "Name", temp_str)) break; + if(!flipper_format_read_string(file, "Name", temp_str)) { + furi_string_set_str(temp_str, "Unknown"); + } strlcpy(data->name, furi_string_get_cstr(temp_str), sizeof(data->name)); - if(!flipper_format_get_value_count(file, "Number", &data_cnt)) break; + if(!flipper_format_get_value_count(file, "Number", &data_cnt)) { + data_cnt = 0; + } data->number_len = data_cnt; - if(!flipper_format_read_hex(file, "Number", data->number, data->number_len)) break; + if(!flipper_format_read_hex(file, "Number", data->number, data->number_len)) { + memset(data->number, 0, sizeof(data->number)); + }; parsed = true; // Load optional data uint8_t exp_data[2] = {}; @@ -1097,23 +1402,30 @@ bool nfc_device_save(NfcDevice* dev, const char* dev_name) { if(!flipper_format_write_header_cstr(file, nfc_file_header, nfc_file_version)) break; // Write nfc device type if(!flipper_format_write_comment_cstr( - file, "Nfc device type can be UID, Mifare Ultralight, Mifare Classic, Bank card")) + file, "Nfc device type can be UID, Mifare Ultralight, Mifare Classic or ISO15693")) break; nfc_device_prepare_format_string(dev, temp_str); if(!flipper_format_write_string(file, "Device type", temp_str)) break; - // Write UID, ATQA, SAK - if(!flipper_format_write_comment_cstr(file, "UID, ATQA and SAK are common for all formats")) - break; + // Write UID + if(!flipper_format_write_comment_cstr(file, "UID is common for all formats")) break; if(!flipper_format_write_hex(file, "UID", data->uid, data->uid_len)) break; - // Save ATQA in MSB order for correct companion apps display - uint8_t atqa[2] = {data->atqa[1], data->atqa[0]}; - if(!flipper_format_write_hex(file, "ATQA", atqa, 2)) break; - if(!flipper_format_write_hex(file, "SAK", &data->sak, 1)) break; + + if(dev->format != NfcDeviceSaveFormatNfcV) { + // Write ATQA, SAK + if(!flipper_format_write_comment_cstr(file, "ISO14443 specific fields")) break; + // Save ATQA in MSB order for correct companion apps display + uint8_t atqa[2] = {data->atqa[1], data->atqa[0]}; + if(!flipper_format_write_hex(file, "ATQA", atqa, 2)) break; + if(!flipper_format_write_hex(file, "SAK", &data->sak, 1)) break; + } + // Save more data if necessary if(dev->format == NfcDeviceSaveFormatMifareUl) { if(!nfc_device_save_mifare_ul_data(file, dev)) break; } else if(dev->format == NfcDeviceSaveFormatMifareDesfire) { if(!nfc_device_save_mifare_df_data(file, dev)) break; + } else if(dev->format == NfcDeviceSaveFormatNfcV) { + if(!nfc_device_save_nfcv_data(file, dev)) break; } else if(dev->format == NfcDeviceSaveFormatBankCard) { if(!nfc_device_save_bank_card_data(file, dev)) break; } else if(dev->format == NfcDeviceSaveFormatMifareClassic) { @@ -1190,18 +1502,20 @@ static bool nfc_device_load_data(NfcDevice* dev, FuriString* path, bool show_dia if(!nfc_device_parse_format_string(dev, temp_str)) break; // Read and parse UID, ATQA and SAK if(!flipper_format_get_value_count(file, "UID", &data_cnt)) break; - if(!(data_cnt == 4 || data_cnt == 7)) break; + if(!(data_cnt == 4 || data_cnt == 7 || data_cnt == 8)) break; data->uid_len = data_cnt; if(!flipper_format_read_hex(file, "UID", data->uid, data->uid_len)) break; - if(version == version_with_lsb_atqa) { - if(!flipper_format_read_hex(file, "ATQA", data->atqa, 2)) break; - } else { - uint8_t atqa[2] = {}; - if(!flipper_format_read_hex(file, "ATQA", atqa, 2)) break; - data->atqa[0] = atqa[1]; - data->atqa[1] = atqa[0]; + if(dev->format != NfcDeviceSaveFormatNfcV) { + if(version == version_with_lsb_atqa) { + if(!flipper_format_read_hex(file, "ATQA", data->atqa, 2)) break; + } else { + uint8_t atqa[2] = {}; + if(!flipper_format_read_hex(file, "ATQA", atqa, 2)) break; + data->atqa[0] = atqa[1]; + data->atqa[1] = atqa[0]; + } + if(!flipper_format_read_hex(file, "SAK", &data->sak, 1)) break; } - if(!flipper_format_read_hex(file, "SAK", &data->sak, 1)) break; // Load CUID uint8_t* cuid_start = data->uid; if(data->uid_len == 7) { @@ -1216,6 +1530,8 @@ static bool nfc_device_load_data(NfcDevice* dev, FuriString* path, bool show_dia if(!nfc_device_load_mifare_classic_data(file, dev)) break; } else if(dev->format == NfcDeviceSaveFormatMifareDesfire) { if(!nfc_device_load_mifare_df_data(file, dev)) break; + } else if(dev->format == NfcDeviceSaveFormatNfcV) { + if(!nfc_device_load_nfcv_data(file, dev)) break; } else if(dev->format == NfcDeviceSaveFormatBankCard) { if(!nfc_device_load_bank_card_data(file, dev)) break; } diff --git a/lib/nfc/nfc_device.h b/lib/nfc/nfc_device.h index 8b2e6e5ba..d5a9e57fb 100644 --- a/lib/nfc/nfc_device.h +++ b/lib/nfc/nfc_device.h @@ -11,6 +11,7 @@ #include #include #include +#include #ifdef __cplusplus extern "C" { @@ -31,6 +32,7 @@ typedef enum { NfcDeviceProtocolMifareUl, NfcDeviceProtocolMifareClassic, NfcDeviceProtocolMifareDesfire, + NfcDeviceProtocolNfcV } NfcProtocol; typedef enum { @@ -39,6 +41,7 @@ typedef enum { NfcDeviceSaveFormatMifareUl, NfcDeviceSaveFormatMifareClassic, NfcDeviceSaveFormatMifareDesfire, + NfcDeviceSaveFormatNfcV, } NfcDeviceSaveFormat; typedef struct { @@ -74,6 +77,7 @@ typedef struct { MfUltralightData mf_ul_data; MfClassicData mf_classic_data; MifareDesfireData mf_df_data; + NfcVData nfcv_data; }; FuriString* parsed_data; } NfcDeviceData; diff --git a/lib/nfc/nfc_worker.c b/lib/nfc/nfc_worker.c index ae6c504b0..8f7a1b5e4 100644 --- a/lib/nfc/nfc_worker.c +++ b/lib/nfc/nfc_worker.c @@ -95,9 +95,13 @@ int32_t nfc_worker_task(void* context) { } } else if(nfc_worker->state == NfcWorkerStateUidEmulate) { nfc_worker_emulate_uid(nfc_worker); - } else if(nfc_worker->state == NfcWorkerStateEmulateApdu) { + } +#if FURI_DEBUG + else if(nfc_worker->state == NfcWorkerStateEmulateApdu) { nfc_worker_emulate_apdu(nfc_worker); - } else if(nfc_worker->state == NfcWorkerStateMfUltralightEmulate) { + } +#endif + else if(nfc_worker->state == NfcWorkerStateMfUltralightEmulate) { nfc_worker_emulate_mf_ultralight(nfc_worker); } else if(nfc_worker->state == NfcWorkerStateMfClassicEmulate) { nfc_worker_emulate_mf_classic(nfc_worker); @@ -111,6 +115,14 @@ int32_t nfc_worker_task(void* context) { nfc_worker_mf_classic_dict_attack(nfc_worker); } else if(nfc_worker->state == NfcWorkerStateAnalyzeReader) { nfc_worker_analyze_reader(nfc_worker); + } else if(nfc_worker->state == NfcWorkerStateNfcVEmulate) { + nfc_worker_nfcv_emulate(nfc_worker); + } else if(nfc_worker->state == NfcWorkerStateNfcVSniff) { + nfc_worker_nfcv_sniff(nfc_worker); + } else if(nfc_worker->state == NfcWorkerStateNfcVUnlock) { + nfc_worker_nfcv_unlock(nfc_worker); + } else if(nfc_worker->state == NfcWorkerStateNfcVUnlockAndSave) { + nfc_worker_nfcv_unlock(nfc_worker); } furi_hal_nfc_sleep(); nfc_worker_change_state(nfc_worker, NfcWorkerStateReady); @@ -118,6 +130,239 @@ int32_t nfc_worker_task(void* context) { return 0; } +static bool nfc_worker_read_nfcv(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { + bool read_success = false; + NfcVReader reader = {}; + + FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; + NfcVData* nfcv_data = &nfc_worker->dev_data->nfcv_data; + + furi_hal_nfc_sleep(); + + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + reader_analyzer_prepare_tx_rx(nfc_worker->reader_analyzer, tx_rx, false); + reader_analyzer_start(nfc_worker->reader_analyzer, ReaderAnalyzerModeDebugLog); + } + + do { + if(!furi_hal_nfc_detect(&nfc_worker->dev_data->nfc_data, 200)) break; + if(!nfcv_read_card(&reader, nfc_data, nfcv_data)) break; + + read_success = true; + } while(false); + + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + reader_analyzer_stop(nfc_worker->reader_analyzer); + } + + return read_success; +} + +void nfc_worker_nfcv_emulate(NfcWorker* nfc_worker) { + FuriHalNfcTxRxContext tx_rx = {}; + FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; + NfcVData* nfcv_data = &nfc_worker->dev_data->nfcv_data; + + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + reader_analyzer_prepare_tx_rx(nfc_worker->reader_analyzer, &tx_rx, true); + reader_analyzer_start(nfc_worker->reader_analyzer, ReaderAnalyzerModeDebugLog); + } + + nfcv_emu_init(nfc_data, nfcv_data); + while(nfc_worker->state == NfcWorkerStateNfcVEmulate) { + if(nfcv_emu_loop(&tx_rx, nfc_data, nfcv_data, 100)) { + if(nfc_worker->callback) { + nfc_worker->callback(NfcWorkerEventNfcVCommandExecuted, nfc_worker->context); + if(nfcv_data->modified) { + nfc_worker->callback(NfcWorkerEventNfcVContentChanged, nfc_worker->context); + nfcv_data->modified = false; + } + } + } + furi_delay_ms(10); + } + nfcv_emu_deinit(nfcv_data); + + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + reader_analyzer_stop(nfc_worker->reader_analyzer); + } +} + +void nfc_worker_nfcv_sniff(NfcWorker* nfc_worker) { + FuriHalNfcTxRxContext tx_rx = {}; + FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; + NfcVData* nfcv_data = &nfc_worker->dev_data->nfcv_data; + + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + reader_analyzer_prepare_tx_rx(nfc_worker->reader_analyzer, &tx_rx, true); + reader_analyzer_start(nfc_worker->reader_analyzer, ReaderAnalyzerModeDebugLog); + } + + nfcv_data->sub_type = NfcVTypeSniff; + nfcv_emu_init(nfc_data, nfcv_data); + + while(nfc_worker->state == NfcWorkerStateNfcVSniff) { + if(nfcv_emu_loop(&tx_rx, nfc_data, nfcv_data, 100)) { + if(nfc_worker->callback) { + nfc_worker->callback(NfcWorkerEventNfcVCommandExecuted, nfc_worker->context); + } + } + furi_delay_ms(10); + } + nfcv_emu_deinit(nfcv_data); + + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + reader_analyzer_stop(nfc_worker->reader_analyzer); + } +} + +void nfc_worker_nfcv_unlock(NfcWorker* nfc_worker) { + furi_assert(nfc_worker); + furi_assert(nfc_worker->callback); + + NfcVData* nfcv_data = &nfc_worker->dev_data->nfcv_data; + FuriHalNfcTxRxContext tx_rx = {}; + uint8_t* key_data = nfcv_data->sub_data.slix.key_privacy; + uint32_t key = 0; + + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + reader_analyzer_prepare_tx_rx(nfc_worker->reader_analyzer, &tx_rx, true); + reader_analyzer_start(nfc_worker->reader_analyzer, ReaderAnalyzerModeDebugLog); + } + + furi_hal_nfc_sleep(); + + while((nfc_worker->state == NfcWorkerStateNfcVUnlock) || + (nfc_worker->state == NfcWorkerStateNfcVUnlockAndSave)) { + furi_hal_nfc_exit_sleep(); + furi_hal_nfc_ll_txrx_on(); + furi_hal_nfc_ll_poll(); + if(furi_hal_nfc_ll_set_mode( + FuriHalNfcModePollNfcv, FuriHalNfcBitrate26p48, FuriHalNfcBitrate26p48) != + FuriHalNfcReturnOk) { + break; + } + + furi_hal_nfc_ll_set_fdt_listen(FURI_HAL_NFC_LL_FDT_LISTEN_NFCV_POLLER); + furi_hal_nfc_ll_set_fdt_poll(FURI_HAL_NFC_LL_FDT_POLL_NFCV_POLLER); + furi_hal_nfc_ll_set_error_handling(FuriHalNfcErrorHandlingNfc); + furi_hal_nfc_ll_set_guard_time(FURI_HAL_NFC_LL_GT_NFCV); + + furi_hal_console_printf("Detect presence\r\n"); + ReturnCode ret = slix_get_random(nfcv_data); + + if(ret == ERR_NONE) { + /* there is some chip, responding with a RAND */ + nfc_worker->dev_data->protocol = NfcDeviceProtocolNfcV; + furi_hal_console_printf(" Chip detected. In privacy?\r\n"); + ret = nfcv_inventory(NULL); + + if(ret == ERR_NONE) { + /* chip is also visible, so no action required, just save */ + if(nfc_worker->state == NfcWorkerStateNfcVUnlockAndSave) { + NfcVReader reader = {}; + + if(!nfcv_read_card(&reader, &nfc_worker->dev_data->nfc_data, nfcv_data)) { + furi_hal_console_printf(" => failed, wait for chip to disappear.\r\n"); + snprintf(nfcv_data->error, sizeof(nfcv_data->error), "Read card\nfailed"); + nfc_worker->callback(NfcWorkerEventWrongCardDetected, nfc_worker->context); + } else { + furi_hal_console_printf(" => success, wait for chip to disappear.\r\n"); + nfc_worker->callback(NfcWorkerEventCardDetected, nfc_worker->context); + } + } else { + furi_hal_console_printf(" => success, wait for chip to disappear.\r\n"); + nfc_worker->callback(NfcWorkerEventCardDetected, nfc_worker->context); + } + + while(slix_get_random(NULL) == ERR_NONE) { + furi_delay_ms(100); + } + + furi_hal_console_printf( + " => chip is already visible, wait for chip to disappear.\r\n"); + nfc_worker->callback(NfcWorkerEventAborted, nfc_worker->context); + while(slix_get_random(NULL) == ERR_NONE) { + furi_delay_ms(100); + } + + key_data[0] = 0; + key_data[1] = 0; + key_data[2] = 0; + key_data[3] = 0; + + } else { + /* chip is invisible, try to unlock */ + furi_hal_console_printf(" chip is invisible, unlocking\r\n"); + + if(nfcv_data->auth_method == NfcVAuthMethodManual) { + key |= key_data[0] << 24; + key |= key_data[1] << 16; + key |= key_data[2] << 8; + key |= key_data[3] << 0; + + ret = slix_unlock(nfcv_data, 4); + } else { + key = 0x7FFD6E5B; + key_data[0] = key >> 24; + key_data[1] = key >> 16; + key_data[2] = key >> 8; + key_data[3] = key >> 0; + ret = slix_unlock(nfcv_data, 4); + + if(ret != ERR_NONE) { + /* main key failed, trying second one */ + furi_hal_console_printf(" trying second key after resetting\r\n"); + + /* reset chip */ + furi_hal_nfc_ll_txrx_off(); + furi_delay_ms(20); + furi_hal_nfc_ll_txrx_on(); + + if(slix_get_random(nfcv_data) != ERR_NONE) { + furi_hal_console_printf(" reset failed\r\n"); + } + + key = 0x0F0F0F0F; + key_data[0] = key >> 24; + key_data[1] = key >> 16; + key_data[2] = key >> 8; + key_data[3] = key >> 0; + ret = slix_unlock(nfcv_data, 4); + } + } + if(ret != ERR_NONE) { + /* unlock failed */ + furi_hal_console_printf(" => failed, wait for chip to disappear.\r\n"); + snprintf( + nfcv_data->error, sizeof(nfcv_data->error), "Passwords not\naccepted"); + nfc_worker->callback(NfcWorkerEventWrongCardDetected, nfc_worker->context); + + /* reset chip */ + furi_hal_nfc_ll_txrx_off(); + furi_delay_ms(20); + furi_hal_nfc_ll_txrx_on(); + + /* wait for disappearing */ + while(slix_get_random(NULL) == ERR_NONE) { + furi_delay_ms(100); + } + } + } + } else { + nfc_worker->callback(NfcWorkerEventNoCardDetected, nfc_worker->context); + } + + furi_hal_nfc_ll_txrx_off(); + furi_hal_nfc_sleep(); + furi_delay_ms(100); + } + + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + reader_analyzer_stop(nfc_worker->reader_analyzer); + } +} + static bool nfc_worker_read_mf_ultralight(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { bool read_success = false; MfUltralightReader reader = {}; @@ -378,7 +623,12 @@ void nfc_worker_read(NfcWorker* nfc_worker) { event = NfcWorkerEventReadUidNfcF; break; } else if(nfc_data->type == FuriHalNfcTypeV) { - event = NfcWorkerEventReadUidNfcV; + FURI_LOG_I(TAG, "NfcV detected"); + nfc_worker->dev_data->protocol = NfcDeviceProtocolNfcV; + if(nfc_worker_read_nfcv(nfc_worker, &tx_rx)) { + FURI_LOG_I(TAG, "nfc_worker_read_nfcv success"); + } + event = NfcWorkerEventReadNfcV; break; } } else { @@ -495,7 +745,7 @@ void nfc_worker_emulate_uid(NfcWorker* nfc_worker) { } } } - +#if FURI_DEBUG void nfc_worker_emulate_apdu(NfcWorker* nfc_worker) { FuriHalNfcTxRxContext tx_rx = {}; FuriHalNfcDevData params = { @@ -528,6 +778,7 @@ void nfc_worker_emulate_apdu(NfcWorker* nfc_worker) { reader_analyzer_stop(nfc_worker->reader_analyzer); } } +#endif void nfc_worker_mf_ultralight_auth_received_callback(MfUltralightAuth auth, void* context) { furi_assert(context); diff --git a/lib/nfc/nfc_worker.h b/lib/nfc/nfc_worker.h index ce542828a..8918d8e8b 100644 --- a/lib/nfc/nfc_worker.h +++ b/lib/nfc/nfc_worker.h @@ -18,6 +18,10 @@ typedef enum { NfcWorkerStateReadMfUltralightReadAuth, NfcWorkerStateMfClassicDictAttack, NfcWorkerStateAnalyzeReader, + NfcWorkerStateNfcVEmulate, + NfcWorkerStateNfcVUnlock, + NfcWorkerStateNfcVUnlockAndSave, + NfcWorkerStateNfcVSniff, // Debug NfcWorkerStateEmulateApdu, NfcWorkerStateField, @@ -40,6 +44,7 @@ typedef enum { NfcWorkerEventReadMfClassicLoadKeyCache, NfcWorkerEventReadMfClassicDictAttackRequired, NfcWorkerEventReadBankCard, + NfcWorkerEventReadNfcV, // Nfc worker common events NfcWorkerEventSuccess, @@ -70,6 +75,9 @@ typedef enum { // Mifare Ultralight events NfcWorkerEventMfUltralightPassKey, // NFC worker requesting manual key NfcWorkerEventMfUltralightPwdAuth, // Reader sent auth command + NfcWorkerEventNfcVPassKey, // NFC worker requesting manual key + NfcWorkerEventNfcVCommandExecuted, + NfcWorkerEventNfcVContentChanged, } NfcWorkerEvent; typedef bool (*NfcWorkerCallback)(NfcWorkerEvent event, void* context); @@ -88,3 +96,6 @@ void nfc_worker_start( void* context); void nfc_worker_stop(NfcWorker* nfc_worker); +void nfc_worker_nfcv_unlock(NfcWorker* nfc_worker); +void nfc_worker_nfcv_emulate(NfcWorker* nfc_worker); +void nfc_worker_nfcv_sniff(NfcWorker* nfc_worker); \ No newline at end of file diff --git a/lib/nfc/nfc_worker_i.h b/lib/nfc/nfc_worker_i.h index 9733426ab..d13d3c5c8 100644 --- a/lib/nfc/nfc_worker_i.h +++ b/lib/nfc/nfc_worker_i.h @@ -12,6 +12,8 @@ #include #include #include +#include +#include #include struct NfcWorker { diff --git a/lib/nfc/protocols/emv.c b/lib/nfc/protocols/emv.c index 4c4ac856b..8cc5fc44e 100644 --- a/lib/nfc/protocols/emv.c +++ b/lib/nfc/protocols/emv.c @@ -44,7 +44,7 @@ const PDOLValue* const pdol_values[] = { &pdol_transaction_cert, &pdol_unpredict_number, }; - +#if FURI_DEBUG static const uint8_t select_ppse_ans[] = {0x6F, 0x29, 0x84, 0x0E, 0x32, 0x50, 0x41, 0x59, 0x2E, 0x53, 0x59, 0x53, 0x2E, 0x44, 0x44, 0x46, 0x30, 0x31, 0xA5, 0x17, 0xBF, 0x0C, 0x14, 0x61, 0x12, 0x4F, 0x07, @@ -61,7 +61,7 @@ static const uint8_t pdol_ans[] = {0x77, 0x40, 0x82, 0x02, 0x20, 0x00, 0x57, 0x1 0x00, 0x9F, 0x26, 0x08, 0x7A, 0x65, 0x7F, 0xD3, 0x52, 0x96, 0xC9, 0x85, 0x9F, 0x27, 0x01, 0x00, 0x9F, 0x36, 0x02, 0x06, 0x0C, 0x9F, 0x6C, 0x02, 0x10, 0x00, 0x90, 0x00}; - +#endif static void emv_trace(FuriHalNfcTxRxContext* tx_rx, const char* message) { if(furi_log_get_level() == FuriLogLevelTrace) { FURI_LOG_T(TAG, "%s", message); @@ -409,6 +409,7 @@ bool emv_read_bank_card(FuriHalNfcTxRxContext* tx_rx, EmvApplication* emv_app) { return card_num_read; } +#if FURI_DEBUG bool emv_card_emulation(FuriHalNfcTxRxContext* tx_rx) { furi_assert(tx_rx); bool emulation_complete = false; @@ -442,3 +443,4 @@ bool emv_card_emulation(FuriHalNfcTxRxContext* tx_rx) { return emulation_complete; } +#endif \ No newline at end of file diff --git a/lib/nfc/protocols/mifare_classic.c b/lib/nfc/protocols/mifare_classic.c index d2d7467dc..e9d43baf5 100644 --- a/lib/nfc/protocols/mifare_classic.c +++ b/lib/nfc/protocols/mifare_classic.c @@ -376,7 +376,8 @@ bool mf_classic_is_value_block(MfClassicData* data, uint8_t block_num) { bool mf_classic_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK) { UNUSED(ATQA1); - if((ATQA0 == 0x44 || ATQA0 == 0x04) && (SAK == 0x08 || SAK == 0x88 || SAK == 0x09)) { + if((ATQA0 == 0x44 || ATQA0 == 0x04) && + (SAK == 0x08 || SAK == 0x88 || SAK == 0x09 || SAK == 0x89)) { return true; } else if((ATQA0 == 0x01) && (ATQA1 == 0x0F) && (SAK == 0x01)) { //skylanders support @@ -393,7 +394,7 @@ MfClassicType mf_classic_get_classic_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t if((ATQA0 == 0x44 || ATQA0 == 0x04)) { if((SAK == 0x08 || SAK == 0x88)) { return MfClassicType1k; - } else if(SAK == 0x09) { + } else if((SAK == 0x09 || SAK == 0x89)) { return MfClassicTypeMini; } } else if((ATQA0 == 0x01) && (ATQA1 == 0x0F) && (SAK == 0x01)) { diff --git a/lib/nfc/protocols/mifare_common.c b/lib/nfc/protocols/mifare_common.c index 90b57e1f0..5277cc8cd 100644 --- a/lib/nfc/protocols/mifare_common.c +++ b/lib/nfc/protocols/mifare_common.c @@ -6,7 +6,8 @@ MifareType mifare_common_get_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK) { if((ATQA0 == 0x44) && (ATQA1 == 0x00) && (SAK == 0x00)) { type = MifareTypeUltralight; } else if( - ((ATQA0 == 0x44 || ATQA0 == 0x04) && (SAK == 0x08 || SAK == 0x88 || SAK == 0x09)) || + ((ATQA0 == 0x44 || ATQA0 == 0x04) && + (SAK == 0x08 || SAK == 0x88 || SAK == 0x09 || SAK == 0x89)) || ((ATQA0 == 0x42 || ATQA0 == 0x02) && (SAK == 0x18)) || ((ATQA0 == 0x01) && (ATQA1 == 0x0F) && (SAK == 0x01))) { type = MifareTypeClassic; diff --git a/lib/nfc/protocols/mifare_ultralight.c b/lib/nfc/protocols/mifare_ultralight.c index 0e28c0074..6960bd2a1 100644 --- a/lib/nfc/protocols/mifare_ultralight.c +++ b/lib/nfc/protocols/mifare_ultralight.c @@ -702,7 +702,7 @@ bool mf_ultralight_read_tearing_flags(FuriHalNfcTxRxContext* tx_rx, MfUltralight FURI_LOG_D(TAG, "Reading tearing flags"); for(size_t i = 0; i < 3; i++) { tx_rx->tx_data[0] = MF_UL_CHECK_TEARING; - tx_rx->rx_data[1] = i; + tx_rx->tx_data[1] = i; tx_rx->tx_bits = 16; tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; if(!furi_hal_nfc_tx_rx(tx_rx, 50)) { diff --git a/lib/nfc/protocols/nfc_util.h b/lib/nfc/protocols/nfc_util.h index 04fa7622b..a9d5a3f8a 100644 --- a/lib/nfc/protocols/nfc_util.h +++ b/lib/nfc/protocols/nfc_util.h @@ -2,6 +2,10 @@ #include +#ifdef __cplusplus +extern "C" { +#endif + void nfc_util_num2bytes(uint64_t src, uint8_t len, uint8_t* dest); uint64_t nfc_util_bytes2num(const uint8_t* src, uint8_t len); @@ -11,3 +15,7 @@ uint8_t nfc_util_even_parity32(uint32_t data); uint8_t nfc_util_odd_parity8(uint8_t data); void nfc_util_odd_parity(const uint8_t* src, uint8_t* dst, uint8_t len); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/nfcv.c b/lib/nfc/protocols/nfcv.c new file mode 100644 index 000000000..471993b63 --- /dev/null +++ b/lib/nfc/protocols/nfcv.c @@ -0,0 +1,1393 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "nfcv.h" +#include "nfc_util.h" +#include "slix.h" + +#define TAG "NfcV" + +/* macros to map "modulate field" flag to GPIO level */ +#define GPIO_LEVEL_MODULATED NFCV_LOAD_MODULATION_POLARITY +#define GPIO_LEVEL_UNMODULATED (!GPIO_LEVEL_MODULATED) + +/* timing macros */ +#define DIGITAL_SIGNAL_UNIT_S (100000000000.0f) +#define DIGITAL_SIGNAL_UNIT_US (100000.0f) + +ReturnCode nfcv_inventory(uint8_t* uid) { + uint16_t received = 0; + rfalNfcvInventoryRes res; + ReturnCode ret = ERR_NONE; + + for(int tries = 0; tries < NFCV_COMMAND_RETRIES; tries++) { + /* TODO: needs proper abstraction via fury_hal(_ll)_* */ + ret = rfalNfcvPollerInventory(RFAL_NFCV_NUM_SLOTS_1, 0, NULL, &res, &received); + + if(ret == ERR_NONE) { + break; + } + } + + if(ret == ERR_NONE) { + if(uid != NULL) { + memcpy(uid, res.UID, NFCV_UID_LENGTH); + } + } + + return ret; +} + +ReturnCode nfcv_read_blocks(NfcVReader* reader, NfcVData* nfcv_data) { + UNUSED(reader); + + uint16_t received = 0; + for(size_t block = 0; block < nfcv_data->block_num; block++) { + uint8_t rxBuf[32]; + FURI_LOG_D(TAG, "Reading block %d/%d", block, (nfcv_data->block_num - 1)); + + ReturnCode ret = ERR_NONE; + for(int tries = 0; tries < NFCV_COMMAND_RETRIES; tries++) { + ret = rfalNfcvPollerReadSingleBlock( + RFAL_NFCV_REQ_FLAG_DEFAULT, NULL, block, rxBuf, sizeof(rxBuf), &received); + + if(ret == ERR_NONE) { + break; + } + } + if(ret != ERR_NONE) { + FURI_LOG_D(TAG, "failed to read: %d", ret); + return ret; + } + memcpy( + &(nfcv_data->data[block * nfcv_data->block_size]), &rxBuf[1], nfcv_data->block_size); + FURI_LOG_D( + TAG, + " %02X %02X %02X %02X", + nfcv_data->data[block * nfcv_data->block_size + 0], + nfcv_data->data[block * nfcv_data->block_size + 1], + nfcv_data->data[block * nfcv_data->block_size + 2], + nfcv_data->data[block * nfcv_data->block_size + 3]); + } + + return ERR_NONE; +} + +ReturnCode nfcv_read_sysinfo(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data) { + uint8_t rxBuf[32]; + uint16_t received = 0; + ReturnCode ret = ERR_NONE; + + FURI_LOG_D(TAG, "Read SYSTEM INFORMATION..."); + + for(int tries = 0; tries < NFCV_COMMAND_RETRIES; tries++) { + /* TODO: needs proper abstraction via fury_hal(_ll)_* */ + ret = rfalNfcvPollerGetSystemInformation( + RFAL_NFCV_REQ_FLAG_DEFAULT, NULL, rxBuf, sizeof(rxBuf), &received); + + if(ret == ERR_NONE) { + break; + } + } + + if(ret == ERR_NONE) { + nfc_data->type = FuriHalNfcTypeV; + nfc_data->uid_len = NFCV_UID_LENGTH; + /* UID is stored reversed in this response */ + for(int pos = 0; pos < nfc_data->uid_len; pos++) { + nfc_data->uid[pos] = rxBuf[2 + (NFCV_UID_LENGTH - 1 - pos)]; + } + nfcv_data->dsfid = rxBuf[NFCV_UID_LENGTH + 2]; + nfcv_data->afi = rxBuf[NFCV_UID_LENGTH + 3]; + nfcv_data->block_num = rxBuf[NFCV_UID_LENGTH + 4] + 1; + nfcv_data->block_size = rxBuf[NFCV_UID_LENGTH + 5] + 1; + nfcv_data->ic_ref = rxBuf[NFCV_UID_LENGTH + 6]; + FURI_LOG_D( + TAG, + " UID: %02X %02X %02X %02X %02X %02X %02X %02X", + nfc_data->uid[0], + nfc_data->uid[1], + nfc_data->uid[2], + nfc_data->uid[3], + nfc_data->uid[4], + nfc_data->uid[5], + nfc_data->uid[6], + nfc_data->uid[7]); + FURI_LOG_D( + TAG, + " DSFID %d, AFI %d, Blocks %d, Size %d, IC Ref %d", + nfcv_data->dsfid, + nfcv_data->afi, + nfcv_data->block_num, + nfcv_data->block_size, + nfcv_data->ic_ref); + return ret; + } + FURI_LOG_D(TAG, "Failed: %d", ret); + + return ret; +} + +bool nfcv_read_card(NfcVReader* reader, FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data) { + furi_assert(reader); + furi_assert(nfc_data); + furi_assert(nfcv_data); + + if(nfcv_read_sysinfo(nfc_data, nfcv_data) != ERR_NONE) { + return false; + } + + if(nfcv_read_blocks(reader, nfcv_data) != ERR_NONE) { + return false; + } + + if(slix_check_card_type(nfc_data)) { + FURI_LOG_I(TAG, "NXP SLIX detected"); + nfcv_data->sub_type = NfcVTypeSlix; + } else if(slix2_check_card_type(nfc_data)) { + FURI_LOG_I(TAG, "NXP SLIX2 detected"); + nfcv_data->sub_type = NfcVTypeSlix2; + } else if(slix_s_check_card_type(nfc_data)) { + FURI_LOG_I(TAG, "NXP SLIX-S detected"); + nfcv_data->sub_type = NfcVTypeSlixS; + } else if(slix_l_check_card_type(nfc_data)) { + FURI_LOG_I(TAG, "NXP SLIX-L detected"); + nfcv_data->sub_type = NfcVTypeSlixL; + } else { + nfcv_data->sub_type = NfcVTypePlain; + } + + return true; +} + +void nfcv_crc(uint8_t* data, uint32_t length) { + uint32_t reg = 0xFFFF; + + for(size_t i = 0; i < length; i++) { + reg = reg ^ ((uint32_t)data[i]); + for(size_t j = 0; j < 8; j++) { + if(reg & 0x0001) { + reg = (reg >> 1) ^ 0x8408; + } else { + reg = (reg >> 1); + } + } + } + + uint16_t crc = ~(uint16_t)(reg & 0xffff); + + data[length + 0] = crc & 0xFF; + data[length + 1] = crc >> 8; +} + +void nfcv_emu_free_signals(NfcVEmuAirSignals* signals) { + furi_assert(signals); + + if(signals->nfcv_resp_one) { + digital_signal_free(signals->nfcv_resp_one); + } + if(signals->nfcv_resp_zero) { + digital_signal_free(signals->nfcv_resp_zero); + } + if(signals->nfcv_resp_sof) { + digital_signal_free(signals->nfcv_resp_sof); + } + if(signals->nfcv_resp_eof) { + digital_signal_free(signals->nfcv_resp_eof); + } + signals->nfcv_resp_one = NULL; + signals->nfcv_resp_zero = NULL; + signals->nfcv_resp_sof = NULL; + signals->nfcv_resp_eof = NULL; +} + +bool nfcv_emu_alloc_signals(NfcVEmuAir* air, NfcVEmuAirSignals* signals, uint32_t slowdown) { + furi_assert(air); + furi_assert(signals); + + bool success = true; + + if(!signals->nfcv_resp_one) { + /* logical one: unmodulated then 8 pulses */ + signals->nfcv_resp_one = digital_signal_alloc( + slowdown * (air->nfcv_resp_unmod->edge_cnt + 8 * air->nfcv_resp_pulse->edge_cnt)); + if(!signals->nfcv_resp_one) { + return false; + } + for(size_t i = 0; i < slowdown; i++) { + success &= digital_signal_append(signals->nfcv_resp_one, air->nfcv_resp_unmod); + } + for(size_t i = 0; i < slowdown * 8; i++) { + success &= digital_signal_append(signals->nfcv_resp_one, air->nfcv_resp_pulse); + } + if(!success) { + return false; + } + } + if(!signals->nfcv_resp_zero) { + /* logical zero: 8 pulses then unmodulated */ + signals->nfcv_resp_zero = digital_signal_alloc( + slowdown * (8 * air->nfcv_resp_pulse->edge_cnt + air->nfcv_resp_unmod->edge_cnt)); + if(!signals->nfcv_resp_zero) { + return false; + } + for(size_t i = 0; i < slowdown * 8; i++) { + success &= digital_signal_append(signals->nfcv_resp_zero, air->nfcv_resp_pulse); + } + for(size_t i = 0; i < slowdown; i++) { + success &= digital_signal_append(signals->nfcv_resp_zero, air->nfcv_resp_unmod); + } + if(!success) { + return false; + } + } + if(!signals->nfcv_resp_sof) { + /* SOF: unmodulated, 24 pulses, logic 1 */ + signals->nfcv_resp_sof = digital_signal_alloc( + slowdown * (3 * air->nfcv_resp_unmod->edge_cnt + 24 * air->nfcv_resp_pulse->edge_cnt) + + signals->nfcv_resp_one->edge_cnt); + if(!signals->nfcv_resp_sof) { + return false; + } + for(size_t i = 0; i < slowdown * 3; i++) { + success &= digital_signal_append(signals->nfcv_resp_sof, air->nfcv_resp_unmod); + } + for(size_t i = 0; i < slowdown * 24; i++) { + success &= digital_signal_append(signals->nfcv_resp_sof, air->nfcv_resp_pulse); + } + success &= digital_signal_append(signals->nfcv_resp_sof, signals->nfcv_resp_one); + if(!success) { + return false; + } + } + if(!signals->nfcv_resp_eof) { + /* EOF: logic 0, 24 pulses, unmodulated */ + signals->nfcv_resp_eof = digital_signal_alloc( + signals->nfcv_resp_zero->edge_cnt + + slowdown * (24 * air->nfcv_resp_pulse->edge_cnt + 3 * air->nfcv_resp_unmod->edge_cnt) + + air->nfcv_resp_unmod->edge_cnt); + if(!signals->nfcv_resp_eof) { + return false; + } + success &= digital_signal_append(signals->nfcv_resp_eof, signals->nfcv_resp_zero); + for(size_t i = 0; i < slowdown * 23; i++) { + success &= digital_signal_append(signals->nfcv_resp_eof, air->nfcv_resp_pulse); + } + /* we don't want to add the last level as we just want a transition to "unmodulated" again */ + for(size_t i = 0; i < slowdown; i++) { + success &= digital_signal_append(signals->nfcv_resp_eof, air->nfcv_resp_half_pulse); + } + } + return success; +} + +bool nfcv_emu_alloc(NfcVData* nfcv_data) { + furi_assert(nfcv_data); + + if(!nfcv_data->frame) { + nfcv_data->frame = malloc(NFCV_FRAMESIZE_MAX); + if(!nfcv_data->frame) { + return false; + } + } + + if(!nfcv_data->emu_air.nfcv_signal) { + /* assuming max frame length is 255 bytes */ + nfcv_data->emu_air.nfcv_signal = digital_sequence_alloc(8 * 255 + 2, &gpio_spi_r_mosi); + if(!nfcv_data->emu_air.nfcv_signal) { + return false; + } + } + if(!nfcv_data->emu_air.nfcv_resp_unmod) { + /* unmodulated 256/fc or 1024/fc signal as building block */ + nfcv_data->emu_air.nfcv_resp_unmod = digital_signal_alloc(4); + if(!nfcv_data->emu_air.nfcv_resp_unmod) { + return false; + } + nfcv_data->emu_air.nfcv_resp_unmod->start_level = GPIO_LEVEL_UNMODULATED; + nfcv_data->emu_air.nfcv_resp_unmod->edge_timings[0] = + (uint32_t)(NFCV_RESP_SUBC1_UNMOD_256 * DIGITAL_SIGNAL_UNIT_S); + nfcv_data->emu_air.nfcv_resp_unmod->edge_cnt = 1; + } + if(!nfcv_data->emu_air.nfcv_resp_pulse) { + /* modulated fc/32 or fc/8 pulse as building block */ + nfcv_data->emu_air.nfcv_resp_pulse = digital_signal_alloc(4); + if(!nfcv_data->emu_air.nfcv_resp_pulse) { + return false; + } + nfcv_data->emu_air.nfcv_resp_pulse->start_level = GPIO_LEVEL_MODULATED; + nfcv_data->emu_air.nfcv_resp_pulse->edge_timings[0] = + (uint32_t)(NFCV_RESP_SUBC1_PULSE_32 * DIGITAL_SIGNAL_UNIT_S); + nfcv_data->emu_air.nfcv_resp_pulse->edge_timings[1] = + (uint32_t)(NFCV_RESP_SUBC1_PULSE_32 * DIGITAL_SIGNAL_UNIT_S); + nfcv_data->emu_air.nfcv_resp_pulse->edge_cnt = 2; + } + + if(!nfcv_data->emu_air.nfcv_resp_half_pulse) { + /* modulated fc/32 or fc/8 pulse as building block */ + nfcv_data->emu_air.nfcv_resp_half_pulse = digital_signal_alloc(4); + if(!nfcv_data->emu_air.nfcv_resp_half_pulse) { + return false; + } + nfcv_data->emu_air.nfcv_resp_half_pulse->start_level = GPIO_LEVEL_MODULATED; + nfcv_data->emu_air.nfcv_resp_half_pulse->edge_timings[0] = + (uint32_t)(NFCV_RESP_SUBC1_PULSE_32 * DIGITAL_SIGNAL_UNIT_S); + nfcv_data->emu_air.nfcv_resp_half_pulse->edge_cnt = 1; + } + + bool success = true; + success &= nfcv_emu_alloc_signals(&nfcv_data->emu_air, &nfcv_data->emu_air.signals_high, 1); + success &= nfcv_emu_alloc_signals(&nfcv_data->emu_air, &nfcv_data->emu_air.signals_low, 4); + + if(!success) { + FURI_LOG_E(TAG, "Failed to allocate signals"); + return false; + } + + digital_sequence_set_signal( + nfcv_data->emu_air.nfcv_signal, + NFCV_SIG_SOF, + nfcv_data->emu_air.signals_high.nfcv_resp_sof); + digital_sequence_set_signal( + nfcv_data->emu_air.nfcv_signal, + NFCV_SIG_BIT0, + nfcv_data->emu_air.signals_high.nfcv_resp_zero); + digital_sequence_set_signal( + nfcv_data->emu_air.nfcv_signal, + NFCV_SIG_BIT1, + nfcv_data->emu_air.signals_high.nfcv_resp_one); + digital_sequence_set_signal( + nfcv_data->emu_air.nfcv_signal, + NFCV_SIG_EOF, + nfcv_data->emu_air.signals_high.nfcv_resp_eof); + digital_sequence_set_signal( + nfcv_data->emu_air.nfcv_signal, + NFCV_SIG_LOW_SOF, + nfcv_data->emu_air.signals_low.nfcv_resp_sof); + digital_sequence_set_signal( + nfcv_data->emu_air.nfcv_signal, + NFCV_SIG_LOW_BIT0, + nfcv_data->emu_air.signals_low.nfcv_resp_zero); + digital_sequence_set_signal( + nfcv_data->emu_air.nfcv_signal, + NFCV_SIG_LOW_BIT1, + nfcv_data->emu_air.signals_low.nfcv_resp_one); + digital_sequence_set_signal( + nfcv_data->emu_air.nfcv_signal, + NFCV_SIG_LOW_EOF, + nfcv_data->emu_air.signals_low.nfcv_resp_eof); + + return true; +} + +void nfcv_emu_free(NfcVData* nfcv_data) { + furi_assert(nfcv_data); + + if(nfcv_data->frame) { + free(nfcv_data->frame); + } + if(nfcv_data->emu_protocol_ctx) { + free(nfcv_data->emu_protocol_ctx); + } + if(nfcv_data->emu_air.nfcv_resp_unmod) { + digital_signal_free(nfcv_data->emu_air.nfcv_resp_unmod); + } + if(nfcv_data->emu_air.nfcv_resp_pulse) { + digital_signal_free(nfcv_data->emu_air.nfcv_resp_pulse); + } + if(nfcv_data->emu_air.nfcv_resp_half_pulse) { + digital_signal_free(nfcv_data->emu_air.nfcv_resp_half_pulse); + } + if(nfcv_data->emu_air.nfcv_signal) { + digital_sequence_free(nfcv_data->emu_air.nfcv_signal); + } + if(nfcv_data->emu_air.reader_signal) { + pulse_reader_free(nfcv_data->emu_air.reader_signal); + } + + nfcv_data->frame = NULL; + nfcv_data->emu_air.nfcv_resp_unmod = NULL; + nfcv_data->emu_air.nfcv_resp_pulse = NULL; + nfcv_data->emu_air.nfcv_resp_half_pulse = NULL; + nfcv_data->emu_air.nfcv_signal = NULL; + nfcv_data->emu_air.reader_signal = NULL; + + nfcv_emu_free_signals(&nfcv_data->emu_air.signals_high); + nfcv_emu_free_signals(&nfcv_data->emu_air.signals_low); +} + +void nfcv_emu_send( + FuriHalNfcTxRxContext* tx_rx, + NfcVData* nfcv, + uint8_t* data, + uint8_t length, + NfcVSendFlags flags, + uint32_t send_time) { + furi_assert(tx_rx); + furi_assert(nfcv); + + /* picked default value (0) to match the most common format */ + if(!flags) { + flags = NfcVSendFlagsSof | NfcVSendFlagsCrc | NfcVSendFlagsEof | + NfcVSendFlagsOneSubcarrier | NfcVSendFlagsHighRate; + } + + if(flags & NfcVSendFlagsCrc) { + nfcv_crc(data, length); + length += 2; + } + + /* depending on the request flags, send with high or low rate */ + uint32_t bit0 = (flags & NfcVSendFlagsHighRate) ? NFCV_SIG_BIT0 : NFCV_SIG_LOW_BIT0; + uint32_t bit1 = (flags & NfcVSendFlagsHighRate) ? NFCV_SIG_BIT1 : NFCV_SIG_LOW_BIT1; + uint32_t sof = (flags & NfcVSendFlagsHighRate) ? NFCV_SIG_SOF : NFCV_SIG_LOW_SOF; + uint32_t eof = (flags & NfcVSendFlagsHighRate) ? NFCV_SIG_EOF : NFCV_SIG_LOW_EOF; + + digital_sequence_clear(nfcv->emu_air.nfcv_signal); + + if(flags & NfcVSendFlagsSof) { + digital_sequence_add(nfcv->emu_air.nfcv_signal, sof); + } + + for(int bit_total = 0; bit_total < length * 8; bit_total++) { + uint32_t byte_pos = bit_total / 8; + uint32_t bit_pos = bit_total % 8; + uint8_t bit_val = 0x01 << bit_pos; + + digital_sequence_add(nfcv->emu_air.nfcv_signal, (data[byte_pos] & bit_val) ? bit1 : bit0); + } + + if(flags & NfcVSendFlagsEof) { + digital_sequence_add(nfcv->emu_air.nfcv_signal, eof); + } + + furi_hal_gpio_write(&gpio_spi_r_mosi, GPIO_LEVEL_UNMODULATED); + digital_sequence_set_sendtime(nfcv->emu_air.nfcv_signal, send_time); + digital_sequence_send(nfcv->emu_air.nfcv_signal); + furi_hal_gpio_write(&gpio_spi_r_mosi, GPIO_LEVEL_UNMODULATED); + + if(tx_rx->sniff_tx) { + tx_rx->sniff_tx(data, length * 8, false, tx_rx->sniff_context); + } +} + +static void nfcv_revuidcpy(uint8_t* dst, uint8_t* src) { + for(int pos = 0; pos < NFCV_UID_LENGTH; pos++) { + dst[pos] = src[NFCV_UID_LENGTH - 1 - pos]; + } +} + +static int nfcv_revuidcmp(uint8_t* dst, uint8_t* src) { + for(int pos = 0; pos < NFCV_UID_LENGTH; pos++) { + if(dst[pos] != src[NFCV_UID_LENGTH - 1 - pos]) { + return 1; + } + } + return 0; +} + +void nfcv_emu_handle_packet( + FuriHalNfcTxRxContext* tx_rx, + FuriHalNfcDevData* nfc_data, + void* nfcv_data_in) { + furi_assert(tx_rx); + furi_assert(nfc_data); + furi_assert(nfcv_data_in); + + NfcVData* nfcv_data = (NfcVData*)nfcv_data_in; + NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx; + + if(nfcv_data->frame_length < 2) { + return; + } + + if(nfcv_data->echo_mode) { + nfcv_emu_send( + tx_rx, + nfcv_data, + nfcv_data->frame, + nfcv_data->frame_length, + NfcVSendFlagsSof | NfcVSendFlagsHighRate | NfcVSendFlagsEof, + ctx->send_time); + snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "ECHO data"); + return; + } + + /* parse the frame data for the upcoming part 3 handling */ + ctx->flags = nfcv_data->frame[0]; + ctx->command = nfcv_data->frame[1]; + ctx->selected = !(ctx->flags & NFCV_REQ_FLAG_INVENTORY) && (ctx->flags & NFCV_REQ_FLAG_SELECT); + ctx->addressed = !(ctx->flags & NFCV_REQ_FLAG_INVENTORY) && + (ctx->flags & NFCV_REQ_FLAG_ADDRESS); + ctx->advanced = (ctx->command >= NFCV_CMD_ADVANCED); + ctx->address_offset = 2 + (ctx->advanced ? 1 : 0); + ctx->payload_offset = ctx->address_offset + (ctx->addressed ? NFCV_UID_LENGTH : 0); + ctx->response_flags = NfcVSendFlagsSof | NfcVSendFlagsCrc | NfcVSendFlagsEof; + ctx->send_time = nfcv_data->eof_timestamp + NFCV_FDT_FC(4380); + + if(ctx->flags & NFCV_REQ_FLAG_DATA_RATE) { + ctx->response_flags |= NfcVSendFlagsHighRate; + } + if(ctx->flags & NFCV_REQ_FLAG_SUB_CARRIER) { + ctx->response_flags |= NfcVSendFlagsTwoSubcarrier; + } + + if(ctx->payload_offset + 2 > nfcv_data->frame_length) { +#ifdef NFCV_VERBOSE + FURI_LOG_D(TAG, "command 0x%02X, but packet is too short", ctx->command); +#endif + return; + } + + /* standard behavior is implemented */ + if(ctx->addressed) { + uint8_t* address = &nfcv_data->frame[ctx->address_offset]; + if(nfcv_revuidcmp(address, nfc_data->uid)) { +#ifdef NFCV_VERBOSE + FURI_LOG_D(TAG, "addressed command 0x%02X, but not for us:", ctx->command); + FURI_LOG_D( + TAG, + " dest: %02X%02X%02X%02X%02X%02X%02X%02X", + address[7], + address[6], + address[5], + address[4], + address[3], + address[2], + address[1], + address[0]); + FURI_LOG_D( + TAG, + " our UID: %02X%02X%02X%02X%02X%02X%02X%02X", + nfc_data->uid[0], + nfc_data->uid[1], + nfc_data->uid[2], + nfc_data->uid[3], + nfc_data->uid[4], + nfc_data->uid[5], + nfc_data->uid[6], + nfc_data->uid[7]); +#endif + return; + } + } + + if(ctx->selected && !nfcv_data->selected) { +#ifdef NFCV_VERBOSE + FURI_LOG_D( + TAG, + "selected card shall execute command 0x%02X, but we were not selected", + ctx->command); +#endif + return; + } + + /* then give control to the card subtype specific protocol filter */ + if(ctx->emu_protocol_filter != NULL) { + if(ctx->emu_protocol_filter(tx_rx, nfc_data, nfcv_data)) { + if(strlen(nfcv_data->last_command) > 0) { +#ifdef NFCV_VERBOSE + FURI_LOG_D( + TAG, "Received command %s (handled by filter)", nfcv_data->last_command); +#endif + } + return; + } + } + + switch(ctx->command) { + case NFCV_CMD_INVENTORY: { + bool respond = false; + + if(ctx->flags & NFCV_REQ_FLAG_AFI) { + uint8_t afi = nfcv_data->frame[ctx->payload_offset]; + if(afi == nfcv_data->afi) { + respond = true; + } + } else { + respond = true; + } + + if(!nfcv_data->quiet && respond) { + int buffer_pos = 0; + ctx->response_buffer[buffer_pos++] = NFCV_NOERROR; + ctx->response_buffer[buffer_pos++] = nfcv_data->dsfid; + nfcv_revuidcpy(&ctx->response_buffer[buffer_pos], nfc_data->uid); + buffer_pos += NFCV_UID_LENGTH; + + nfcv_emu_send( + tx_rx, + nfcv_data, + ctx->response_buffer, + buffer_pos, + ctx->response_flags, + ctx->send_time); + snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "INVENTORY"); + } else { + snprintf( + nfcv_data->last_command, sizeof(nfcv_data->last_command), "INVENTORY (quiet)"); + } + break; + } + + case NFCV_CMD_STAY_QUIET: { + snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "STAYQUIET"); + nfcv_data->quiet = true; + break; + } + + case NFCV_CMD_LOCK_BLOCK: { + uint8_t block = nfcv_data->frame[ctx->payload_offset]; + nfcv_data->security_status[block] |= 0x01; + nfcv_data->modified = true; + + ctx->response_buffer[0] = NFCV_NOERROR; + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); + + snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "LOCK BLOCK %d", block); + break; + } + + case NFCV_CMD_WRITE_DSFID: { + uint8_t id = nfcv_data->frame[ctx->payload_offset]; + + if(!(nfcv_data->security_status[0] & NfcVLockBitDsfid)) { + nfcv_data->dsfid = id; + nfcv_data->modified = true; + ctx->response_buffer[0] = NFCV_NOERROR; + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); + } + + snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "WRITE DSFID %02X", id); + break; + } + + case NFCV_CMD_WRITE_AFI: { + uint8_t id = nfcv_data->frame[ctx->payload_offset]; + + if(!(nfcv_data->security_status[0] & NfcVLockBitAfi)) { + nfcv_data->afi = id; + nfcv_data->modified = true; + ctx->response_buffer[0] = NFCV_NOERROR; + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); + } + + snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "WRITE AFI %02X", id); + break; + } + + case NFCV_CMD_LOCK_DSFID: { + if(!(nfcv_data->security_status[0] & NfcVLockBitDsfid)) { + nfcv_data->security_status[0] |= NfcVLockBitDsfid; + nfcv_data->modified = true; + + ctx->response_buffer[0] = NFCV_NOERROR; + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); + } + + snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "LOCK DSFID"); + break; + } + + case NFCV_CMD_LOCK_AFI: { + if(!(nfcv_data->security_status[0] & NfcVLockBitAfi)) { + nfcv_data->security_status[0] |= NfcVLockBitAfi; + nfcv_data->modified = true; + + ctx->response_buffer[0] = NFCV_NOERROR; + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); + } + + snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "LOCK AFI"); + break; + } + + case NFCV_CMD_SELECT: { + ctx->response_buffer[0] = NFCV_NOERROR; + nfcv_data->selected = true; + nfcv_data->quiet = false; + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); + snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "SELECT"); + break; + } + + case NFCV_CMD_RESET_TO_READY: { + ctx->response_buffer[0] = NFCV_NOERROR; + nfcv_data->quiet = false; + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); + snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "RESET_TO_READY"); + break; + } + + case NFCV_CMD_READ_MULTI_BLOCK: + case NFCV_CMD_READ_BLOCK: { + uint8_t block = nfcv_data->frame[ctx->payload_offset]; + uint8_t blocks = 1; + + if(ctx->command == NFCV_CMD_READ_MULTI_BLOCK) { + blocks = nfcv_data->frame[ctx->payload_offset + 1] + 1; + } + + if(block + blocks <= nfcv_data->block_num) { + uint8_t buffer_pos = 0; + + ctx->response_buffer[buffer_pos++] = NFCV_NOERROR; + + for(int block_index = 0; block_index < blocks; block_index++) { + int block_current = block + block_index; + /* prepend security status */ + if(ctx->flags & NFCV_REQ_FLAG_OPTION) { + ctx->response_buffer[buffer_pos++] = + nfcv_data->security_status[1 + block_current]; + } + /* then the data block */ + memcpy( + &ctx->response_buffer[buffer_pos], + &nfcv_data->data[nfcv_data->block_size * block_current], + nfcv_data->block_size); + buffer_pos += nfcv_data->block_size; + } + nfcv_emu_send( + tx_rx, + nfcv_data, + ctx->response_buffer, + buffer_pos, + ctx->response_flags, + ctx->send_time); + } else { + ctx->response_buffer[0] = NFCV_RES_FLAG_ERROR; + ctx->response_buffer[1] = NFCV_ERROR_GENERIC; + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, 2, ctx->response_flags, ctx->send_time); + } + snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "READ BLOCK %d", block); + + break; + } + + case NFCV_CMD_WRITE_MULTI_BLOCK: + case NFCV_CMD_WRITE_BLOCK: { + uint8_t blocks = 1; + uint8_t block = nfcv_data->frame[ctx->payload_offset]; + uint8_t data_pos = ctx->payload_offset + 1; + + if(ctx->command == NFCV_CMD_WRITE_MULTI_BLOCK) { + blocks = nfcv_data->frame[data_pos] + 1; + data_pos++; + } + + uint8_t* data = &nfcv_data->frame[data_pos]; + uint32_t data_len = nfcv_data->block_size * blocks; + + if((block + blocks) <= nfcv_data->block_num && + (data_pos + data_len + 2) == nfcv_data->frame_length) { + ctx->response_buffer[0] = NFCV_NOERROR; + memcpy( + &nfcv_data->data[nfcv_data->block_size * block], + &nfcv_data->frame[data_pos], + data_len); + nfcv_data->modified = true; + + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); + } else { + ctx->response_buffer[0] = NFCV_RES_FLAG_ERROR; + ctx->response_buffer[1] = NFCV_ERROR_GENERIC; + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, 2, ctx->response_flags, ctx->send_time); + } + + if(ctx->command == NFCV_CMD_WRITE_MULTI_BLOCK) { + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "WRITE MULTI BLOCK %d, %d blocks", + block, + blocks); + } else { + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "WRITE BLOCK %d <- %02X %02X %02X %02X", + block, + data[0], + data[1], + data[2], + data[3]); + } + break; + } + + case NFCV_CMD_GET_SYSTEM_INFO: { + int buffer_pos = 0; + ctx->response_buffer[buffer_pos++] = NFCV_NOERROR; + ctx->response_buffer[buffer_pos++] = NFCV_SYSINFO_FLAG_DSFID | NFCV_SYSINFO_FLAG_AFI | + NFCV_SYSINFO_FLAG_MEMSIZE | NFCV_SYSINFO_FLAG_ICREF; + nfcv_revuidcpy(&ctx->response_buffer[buffer_pos], nfc_data->uid); + buffer_pos += NFCV_UID_LENGTH; + ctx->response_buffer[buffer_pos++] = nfcv_data->dsfid; /* DSFID */ + ctx->response_buffer[buffer_pos++] = nfcv_data->afi; /* AFI */ + ctx->response_buffer[buffer_pos++] = nfcv_data->block_num - 1; /* number of blocks */ + ctx->response_buffer[buffer_pos++] = nfcv_data->block_size - 1; /* block size */ + ctx->response_buffer[buffer_pos++] = nfcv_data->ic_ref; /* IC reference */ + + nfcv_emu_send( + tx_rx, + nfcv_data, + ctx->response_buffer, + buffer_pos, + ctx->response_flags, + ctx->send_time); + snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "SYSTEMINFO"); + break; + } + + case NFCV_CMD_CUST_ECHO_MODE: { + ctx->response_buffer[0] = NFCV_NOERROR; + nfcv_data->echo_mode = true; + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); + snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "ECHO mode"); + break; + } + + case NFCV_CMD_CUST_ECHO_DATA: { + nfcv_emu_send( + tx_rx, + nfcv_data, + &nfcv_data->frame[ctx->payload_offset], + nfcv_data->frame_length - ctx->payload_offset - 2, + NfcVSendFlagsSof | NfcVSendFlagsHighRate | NfcVSendFlagsEof, + ctx->send_time); + snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "ECHO data"); + break; + } + + default: + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "unsupported: %02X", + ctx->command); + break; + } + + if(strlen(nfcv_data->last_command) > 0) { +#ifdef NFCV_VERBOSE + FURI_LOG_D(TAG, "Received command %s", nfcv_data->last_command); +#endif + } +} + +void nfcv_emu_sniff_packet( + FuriHalNfcTxRxContext* tx_rx, + FuriHalNfcDevData* nfc_data, + void* nfcv_data_in) { + furi_assert(tx_rx); + furi_assert(nfc_data); + furi_assert(nfcv_data_in); + + NfcVData* nfcv_data = (NfcVData*)nfcv_data_in; + NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx; + + if(nfcv_data->frame_length < 2) { + return; + } + + /* parse the frame data for the upcoming part 3 handling */ + ctx->flags = nfcv_data->frame[0]; + ctx->command = nfcv_data->frame[1]; + ctx->selected = (ctx->flags & NFCV_REQ_FLAG_SELECT); + ctx->addressed = !(ctx->flags & NFCV_REQ_FLAG_INVENTORY) && + (ctx->flags & NFCV_REQ_FLAG_ADDRESS); + ctx->advanced = (ctx->command >= NFCV_CMD_ADVANCED); + ctx->address_offset = 2 + (ctx->advanced ? 1 : 0); + ctx->payload_offset = ctx->address_offset + (ctx->addressed ? NFCV_UID_LENGTH : 0); + + char flags_string[5]; + + snprintf( + flags_string, + 5, + "%c%c%c%d", + (ctx->flags & NFCV_REQ_FLAG_INVENTORY) ? + 'I' : + (ctx->addressed ? 'A' : (ctx->selected ? 'S' : '*')), + ctx->advanced ? 'X' : ' ', + (ctx->flags & NFCV_REQ_FLAG_DATA_RATE) ? 'h' : 'l', + (ctx->flags & NFCV_REQ_FLAG_SUB_CARRIER) ? 2 : 1); + + switch(ctx->command) { + case NFCV_CMD_INVENTORY: { + snprintf( + nfcv_data->last_command, sizeof(nfcv_data->last_command), "%s INVENTORY", flags_string); + break; + } + + case NFCV_CMD_STAY_QUIET: { + snprintf( + nfcv_data->last_command, sizeof(nfcv_data->last_command), "%s STAYQUIET", flags_string); + nfcv_data->quiet = true; + break; + } + + case NFCV_CMD_LOCK_BLOCK: { + uint8_t block = nfcv_data->frame[ctx->payload_offset]; + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "%s LOCK %d", + flags_string, + block); + break; + } + + case NFCV_CMD_WRITE_DSFID: { + uint8_t id = nfcv_data->frame[ctx->payload_offset]; + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "%s WR DSFID %d", + flags_string, + id); + break; + } + + case NFCV_CMD_WRITE_AFI: { + uint8_t id = nfcv_data->frame[ctx->payload_offset]; + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "%s WR AFI %d", + flags_string, + id); + break; + } + + case NFCV_CMD_LOCK_DSFID: { + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "%s LOCK DSFID", + flags_string); + break; + } + + case NFCV_CMD_LOCK_AFI: { + snprintf( + nfcv_data->last_command, sizeof(nfcv_data->last_command), "%s LOCK AFI", flags_string); + break; + } + + case NFCV_CMD_SELECT: { + snprintf( + nfcv_data->last_command, sizeof(nfcv_data->last_command), "%s SELECT", flags_string); + break; + } + + case NFCV_CMD_RESET_TO_READY: { + snprintf( + nfcv_data->last_command, sizeof(nfcv_data->last_command), "%s RESET", flags_string); + break; + } + + case NFCV_CMD_READ_MULTI_BLOCK: + case NFCV_CMD_READ_BLOCK: { + uint8_t block = nfcv_data->frame[ctx->payload_offset]; + uint8_t blocks = 1; + + if(ctx->command == NFCV_CMD_READ_MULTI_BLOCK) { + blocks = nfcv_data->frame[ctx->payload_offset + 1] + 1; + } + + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "%s READ %d cnt: %d", + flags_string, + block, + blocks); + + break; + } + + case NFCV_CMD_WRITE_MULTI_BLOCK: + case NFCV_CMD_WRITE_BLOCK: { + uint8_t block = nfcv_data->frame[ctx->payload_offset]; + uint8_t blocks = 1; + uint8_t data_pos = 1; + + if(ctx->command == NFCV_CMD_WRITE_MULTI_BLOCK) { + blocks = nfcv_data->frame[ctx->payload_offset + 1] + 1; + data_pos++; + } + + uint8_t* data = &nfcv_data->frame[ctx->payload_offset + data_pos]; + + if(ctx->command == NFCV_CMD_WRITE_MULTI_BLOCK) { + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "%s WRITE %d, cnd %d", + flags_string, + block, + blocks); + } else { + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "%s WRITE %d %02X %02X %02X %02X", + flags_string, + block, + data[0], + data[1], + data[2], + data[3]); + } + break; + } + + case NFCV_CMD_GET_SYSTEM_INFO: { + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "%s SYSTEMINFO", + flags_string); + break; + } + + default: + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "%s unsupported: %02X", + flags_string, + ctx->command); + break; + } + + if(strlen(nfcv_data->last_command) > 0) { + FURI_LOG_D(TAG, "Received command %s", nfcv_data->last_command); + } +} + +void nfcv_emu_init(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data) { + furi_assert(nfc_data); + furi_assert(nfcv_data); + + if(!nfcv_emu_alloc(nfcv_data)) { + FURI_LOG_E(TAG, "Failed to allocate structures"); + nfcv_data->ready = false; + return; + } + + strcpy(nfcv_data->last_command, ""); + nfcv_data->quiet = false; + nfcv_data->selected = false; + nfcv_data->modified = false; + + /* everything is initialized */ + nfcv_data->ready = true; + + /* ensure the GPIO is already in unmodulated state */ + furi_hal_gpio_init(&gpio_spi_r_mosi, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); + furi_hal_gpio_write(&gpio_spi_r_mosi, GPIO_LEVEL_UNMODULATED); + + rfal_platform_spi_acquire(); + /* stop operation to configure for transparent and passive mode */ + st25r3916ExecuteCommand(ST25R3916_CMD_STOP); + /* set enable, rx_enable and field detector enable */ + st25r3916WriteRegister( + ST25R3916_REG_OP_CONTROL, + ST25R3916_REG_OP_CONTROL_en | ST25R3916_REG_OP_CONTROL_rx_en | + ST25R3916_REG_OP_CONTROL_en_fd_auto_efd); + /* explicitely set the modulation resistor in case system config changes for some reason */ + st25r3916WriteRegister( + ST25R3916_REG_PT_MOD, + (0 << ST25R3916_REG_PT_MOD_ptm_res_shift) | (15 << ST25R3916_REG_PT_MOD_pt_res_shift)); + /* target mode: target, other fields do not have any effect as we use transparent mode */ + st25r3916WriteRegister(ST25R3916_REG_MODE, ST25R3916_REG_MODE_targ); + /* let us modulate the field using MOSI, read ASK modulation using IRQ */ + st25r3916ExecuteCommand(ST25R3916_CMD_TRANSPARENT_MODE); + + furi_hal_spi_bus_handle_deinit(&furi_hal_spi_bus_handle_nfc); + + /* if not set already, initialize the default protocol handler */ + if(!nfcv_data->emu_protocol_ctx) { + nfcv_data->emu_protocol_ctx = malloc(sizeof(NfcVEmuProtocolCtx)); + if(nfcv_data->sub_type == NfcVTypeSniff) { + nfcv_data->emu_protocol_handler = &nfcv_emu_sniff_packet; + } else { + nfcv_data->emu_protocol_handler = &nfcv_emu_handle_packet; + } + } + + FURI_LOG_D(TAG, "Starting NfcV emulation"); + FURI_LOG_D( + TAG, + " UID: %02X %02X %02X %02X %02X %02X %02X %02X", + nfc_data->uid[0], + nfc_data->uid[1], + nfc_data->uid[2], + nfc_data->uid[3], + nfc_data->uid[4], + nfc_data->uid[5], + nfc_data->uid[6], + nfc_data->uid[7]); + + switch(nfcv_data->sub_type) { + case NfcVTypeSlixL: + FURI_LOG_D(TAG, " Card type: SLIX-L"); + slix_l_prepare(nfcv_data); + break; + case NfcVTypeSlixS: + FURI_LOG_D(TAG, " Card type: SLIX-S"); + slix_s_prepare(nfcv_data); + break; + case NfcVTypeSlix2: + FURI_LOG_D(TAG, " Card type: SLIX2"); + slix2_prepare(nfcv_data); + break; + case NfcVTypeSlix: + FURI_LOG_D(TAG, " Card type: SLIX"); + slix_prepare(nfcv_data); + break; + case NfcVTypePlain: + FURI_LOG_D(TAG, " Card type: Plain"); + break; + case NfcVTypeSniff: + FURI_LOG_D(TAG, " Card type: Sniffing"); + break; + } + + /* allocate a 512 edge buffer, more than enough */ + nfcv_data->emu_air.reader_signal = + pulse_reader_alloc(&gpio_nfc_irq_rfid_pull, NFCV_PULSE_BUFFER); + /* timebase shall be 1 ns */ + pulse_reader_set_timebase(nfcv_data->emu_air.reader_signal, PulseReaderUnitNanosecond); + /* and configure to already calculate the number of bits */ + pulse_reader_set_bittime(nfcv_data->emu_air.reader_signal, NFCV_PULSE_DURATION_NS); + /* this IO is fed into the µC via a diode, so we need a pulldown */ + pulse_reader_set_pull(nfcv_data->emu_air.reader_signal, GpioPullDown); + + /* start sampling */ + pulse_reader_start(nfcv_data->emu_air.reader_signal); +} + +void nfcv_emu_deinit(NfcVData* nfcv_data) { + furi_assert(nfcv_data); + + furi_hal_spi_bus_handle_init(&furi_hal_spi_bus_handle_nfc); + nfcv_emu_free(nfcv_data); + + if(nfcv_data->emu_protocol_ctx) { + free(nfcv_data->emu_protocol_ctx); + nfcv_data->emu_protocol_ctx = NULL; + } + + /* set registers back to how we found them */ + st25r3916WriteRegister(ST25R3916_REG_OP_CONTROL, 0x00); + st25r3916WriteRegister(ST25R3916_REG_MODE, 0x08); + rfal_platform_spi_release(); +} + +bool nfcv_emu_loop( + FuriHalNfcTxRxContext* tx_rx, + FuriHalNfcDevData* nfc_data, + NfcVData* nfcv_data, + uint32_t timeout_ms) { + furi_assert(tx_rx); + furi_assert(nfc_data); + furi_assert(nfcv_data); + + bool ret = false; + uint32_t frame_state = NFCV_FRAME_STATE_SOF1; + uint32_t periods_previous = 0; + uint32_t frame_pos = 0; + uint32_t byte_value = 0; + uint32_t bits_received = 0; + uint32_t timeout = timeout_ms * 1000; + bool wait_for_pulse = false; + + if(!nfcv_data->ready) { + return false; + } + +#ifdef NFCV_DIAGNOSTIC_DUMPS + uint8_t period_buffer[NFCV_DIAGNOSTIC_DUMP_SIZE]; + uint32_t period_buffer_pos = 0; +#endif + + while(true) { + uint32_t periods = pulse_reader_receive(nfcv_data->emu_air.reader_signal, timeout); + uint32_t timestamp = DWT->CYCCNT; + + /* when timed out, reset to SOF state */ + if(periods == PULSE_READER_NO_EDGE || periods == PULSE_READER_LOST_EDGE) { + break; + } + +#ifdef NFCV_DIAGNOSTIC_DUMPS + if(period_buffer_pos < sizeof(period_buffer)) { + period_buffer[period_buffer_pos++] = periods; + } +#endif + + /* short helper for detecting a pulse position */ + if(wait_for_pulse) { + wait_for_pulse = false; + if(periods != 1) { + frame_state = NFCV_FRAME_STATE_RESET; + } + continue; + } + + switch(frame_state) { + case NFCV_FRAME_STATE_SOF1: + if(periods == 1) { + frame_state = NFCV_FRAME_STATE_SOF2; + } else { + frame_state = NFCV_FRAME_STATE_SOF1; + break; + } + break; + + case NFCV_FRAME_STATE_SOF2: + /* waiting for the second low period, telling us about coding */ + if(periods == 6) { + frame_state = NFCV_FRAME_STATE_CODING_256; + periods_previous = 0; + wait_for_pulse = true; + } else if(periods == 4) { + frame_state = NFCV_FRAME_STATE_CODING_4; + periods_previous = 2; + wait_for_pulse = true; + } else { + frame_state = NFCV_FRAME_STATE_RESET; + } + break; + + case NFCV_FRAME_STATE_CODING_256: + if(periods_previous > periods) { + frame_state = NFCV_FRAME_STATE_RESET; + break; + } + + /* previous symbol left us with some pulse periods */ + periods -= periods_previous; + + if(periods > 512) { + frame_state = NFCV_FRAME_STATE_RESET; + break; + } else if(periods == 2) { + frame_state = NFCV_FRAME_STATE_EOF; + break; + } + + periods_previous = 512 - (periods + 1); + byte_value = (periods - 1) / 2; + if(frame_pos < NFCV_FRAMESIZE_MAX) { + nfcv_data->frame[frame_pos++] = (uint8_t)byte_value; + } + + wait_for_pulse = true; + + break; + + case NFCV_FRAME_STATE_CODING_4: + if(periods_previous > periods) { + frame_state = NFCV_FRAME_STATE_RESET; + break; + } + + /* previous symbol left us with some pulse periods */ + periods -= periods_previous; + periods_previous = 0; + + byte_value >>= 2; + bits_received += 2; + + if(periods == 1) { + byte_value |= 0x00 << 6; + periods_previous = 6; + } else if(periods == 3) { + byte_value |= 0x01 << 6; + periods_previous = 4; + } else if(periods == 5) { + byte_value |= 0x02 << 6; + periods_previous = 2; + } else if(periods == 7) { + byte_value |= 0x03 << 6; + periods_previous = 0; + } else if(periods == 2) { + frame_state = NFCV_FRAME_STATE_EOF; + break; + } else { + frame_state = NFCV_FRAME_STATE_RESET; + break; + } + + if(bits_received >= 8) { + if(frame_pos < NFCV_FRAMESIZE_MAX) { + nfcv_data->frame[frame_pos++] = (uint8_t)byte_value; + } + bits_received = 0; + } + wait_for_pulse = true; + break; + } + + /* post-state-machine cleanup and reset */ + if(frame_state == NFCV_FRAME_STATE_RESET) { + frame_state = NFCV_FRAME_STATE_SOF1; + } else if(frame_state == NFCV_FRAME_STATE_EOF) { + nfcv_data->frame_length = frame_pos; + nfcv_data->eof_timestamp = timestamp; + break; + } + } + + if(frame_state == NFCV_FRAME_STATE_EOF) { + /* we know that this code uses TIM2, so stop pulse reader */ + pulse_reader_stop(nfcv_data->emu_air.reader_signal); + if(tx_rx->sniff_rx) { + tx_rx->sniff_rx(nfcv_data->frame, frame_pos * 8, false, tx_rx->sniff_context); + } + nfcv_data->emu_protocol_handler(tx_rx, nfc_data, nfcv_data); + + pulse_reader_start(nfcv_data->emu_air.reader_signal); + ret = true; + } else { + if(frame_state != NFCV_FRAME_STATE_SOF1) { +#ifdef NFCV_VERBOSE + FURI_LOG_T(TAG, "leaving while in state: %lu", frame_state); +#endif + } + } + +#ifdef NFCV_DIAGNOSTIC_DUMPS + if(period_buffer_pos) { + FURI_LOG_T(TAG, "pulses:"); + for(uint32_t pos = 0; pos < period_buffer_pos; pos++) { + FURI_LOG_T(TAG, " #%lu: %u", pos, period_buffer[pos]); + } + } +#endif + + return ret; +} diff --git a/lib/nfc/protocols/nfcv.h b/lib/nfc/protocols/nfcv.h new file mode 100644 index 000000000..8ca6955d1 --- /dev/null +++ b/lib/nfc/protocols/nfcv.h @@ -0,0 +1,291 @@ +#pragma once + +#include +#include + +#include +#include +#include "nfc_util.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* true: modulating releases load, false: modulating adds load resistor to field coil */ +#define NFCV_LOAD_MODULATION_POLARITY (false) + +#define NFCV_FC (13560000.0f / 0.9998f) /* MHz */ +#define NFCV_RESP_SUBC1_PULSE_32 (1.0f / (NFCV_FC / 32) / 2.0f) /* 1.1799 µs */ +#define NFCV_RESP_SUBC1_UNMOD_256 (256.0f / NFCV_FC) /* 18.8791 µs */ +#define NFCV_PULSE_DURATION_NS (128.0f * 1000000000.0f / NFCV_FC) + +/* ISO/IEC 15693-3:2019(E) 10.4.12: maximum number of blocks is defined as 256 */ +#define NFCV_BLOCKS_MAX 256 +/* ISO/IEC 15693-3:2019(E) 10.4.12: maximum size of blocks is defined as 32 */ +#define NFCV_BLOCKSIZE_MAX 32 +/* the resulting memory size a card can have */ +#define NFCV_MEMSIZE_MAX (NFCV_BLOCKS_MAX * NFCV_BLOCKSIZE_MAX) +/* ISO/IEC 15693-3:2019(E) 7.1b: standard allows up to 8192, the maxium frame length that we are expected to receive/send is less */ +#define NFCV_FRAMESIZE_MAX (1 + NFCV_MEMSIZE_MAX + NFCV_BLOCKS_MAX) + +/* maximum string length for log messages */ +#define NFCV_LOG_STR_LEN 128 +/* maximum of pulses to be buffered by pulse reader */ +#define NFCV_PULSE_BUFFER 512 + +//#define NFCV_DIAGNOSTIC_DUMPS +//#define NFCV_DIAGNOSTIC_DUMP_SIZE 256 +//#define NFCV_VERBOSE + +/* helpers to calculate the send time based on DWT->CYCCNT */ +#define NFCV_FDT_USEC(usec) ((usec)*64) +#define NFCV_FDT_FC(ticks) ((ticks)*6400 / 1356) + +/* state machine when receiving frame bits */ +#define NFCV_FRAME_STATE_SOF1 0 +#define NFCV_FRAME_STATE_SOF2 1 +#define NFCV_FRAME_STATE_CODING_4 2 +#define NFCV_FRAME_STATE_CODING_256 3 +#define NFCV_FRAME_STATE_EOF 4 +#define NFCV_FRAME_STATE_RESET 5 + +/* sequences for every section of a frame */ +#define NFCV_SIG_SOF 0 +#define NFCV_SIG_BIT0 1 +#define NFCV_SIG_BIT1 2 +#define NFCV_SIG_EOF 3 +#define NFCV_SIG_LOW_SOF 4 +#define NFCV_SIG_LOW_BIT0 5 +#define NFCV_SIG_LOW_BIT1 6 +#define NFCV_SIG_LOW_EOF 7 + +/* various constants */ +#define NFCV_COMMAND_RETRIES 5 +#define NFCV_UID_LENGTH 8 + +/* ISO15693 protocol flags */ +typedef enum { + /* ISO15693 protocol flags when INVENTORY is NOT set */ + NFCV_REQ_FLAG_SUB_CARRIER = (1 << 0), + NFCV_REQ_FLAG_DATA_RATE = (1 << 1), + NFCV_REQ_FLAG_INVENTORY = (1 << 2), + NFCV_REQ_FLAG_PROTOCOL_EXT = (1 << 3), + NFCV_REQ_FLAG_SELECT = (1 << 4), + NFCV_REQ_FLAG_ADDRESS = (1 << 5), + NFCV_REQ_FLAG_OPTION = (1 << 6), + /* ISO15693 protocol flags when INVENTORY flag is set */ + NFCV_REQ_FLAG_AFI = (1 << 4), + NFCV_REQ_FLAG_NB_SLOTS = (1 << 5) +} NfcVRequestFlags; + +/* ISO15693 protocol flags */ +typedef enum { + NFCV_RES_FLAG_ERROR = (1 << 0), + NFCV_RES_FLAG_VALIDITY = (1 << 1), + NFCV_RES_FLAG_FINAL = (1 << 2), + NFCV_RES_FLAG_PROTOCOL_EXT = (1 << 3), + NFCV_RES_FLAG_SEC_LEN1 = (1 << 4), + NFCV_RES_FLAG_SEC_LEN2 = (1 << 5), + NFCV_RES_FLAG_WAIT_EXT = (1 << 6), +} NfcVRsponseFlags; + +/* flags for SYSINFO response */ +typedef enum { + NFCV_SYSINFO_FLAG_DSFID = (1 << 0), + NFCV_SYSINFO_FLAG_AFI = (1 << 1), + NFCV_SYSINFO_FLAG_MEMSIZE = (1 << 2), + NFCV_SYSINFO_FLAG_ICREF = (1 << 3) +} NfcVSysinfoFlags; + +/* ISO15693 command codes */ +typedef enum { + /* mandatory command codes */ + NFCV_CMD_INVENTORY = 0x01, + NFCV_CMD_STAY_QUIET = 0x02, + /* optional command codes */ + NFCV_CMD_READ_BLOCK = 0x20, + NFCV_CMD_WRITE_BLOCK = 0x21, + NFCV_CMD_LOCK_BLOCK = 0x22, + NFCV_CMD_READ_MULTI_BLOCK = 0x23, + NFCV_CMD_WRITE_MULTI_BLOCK = 0x24, + NFCV_CMD_SELECT = 0x25, + NFCV_CMD_RESET_TO_READY = 0x26, + NFCV_CMD_WRITE_AFI = 0x27, + NFCV_CMD_LOCK_AFI = 0x28, + NFCV_CMD_WRITE_DSFID = 0x29, + NFCV_CMD_LOCK_DSFID = 0x2A, + NFCV_CMD_GET_SYSTEM_INFO = 0x2B, + NFCV_CMD_READ_MULTI_SECSTATUS = 0x2C, + /* advanced command codes */ + NFCV_CMD_ADVANCED = 0xA0, + /* flipper zero custom command codes */ + NFCV_CMD_CUST_ECHO_MODE = 0xDE, + NFCV_CMD_CUST_ECHO_DATA = 0xDF +} NfcVCommands; + +/* ISO15693 Response error codes */ +typedef enum { + NFCV_NOERROR = 0x00, + NFCV_ERROR_CMD_NOT_SUP = 0x01, // Command not supported + NFCV_ERROR_CMD_NOT_REC = 0x02, // Command not recognized (eg. parameter error) + NFCV_ERROR_CMD_OPTION = 0x03, // Command option not supported + NFCV_ERROR_GENERIC = 0x0F, // No additional Info about this error + NFCV_ERROR_BLOCK_UNAVAILABLE = 0x10, + NFCV_ERROR_BLOCK_LOCKED_ALREADY = 0x11, // cannot lock again + NFCV_ERROR_BLOCK_LOCKED = 0x12, // cannot be changed + NFCV_ERROR_BLOCK_WRITE = 0x13, // Writing was unsuccessful + NFCV_ERROR_BLOCL_WRITELOCK = 0x14 // Locking was unsuccessful +} NfcVErrorcodes; + +typedef enum { + NfcVLockBitDsfid = 1, + NfcVLockBitAfi = 2, +} NfcVLockBits; + +typedef enum { + NfcVAuthMethodManual, + NfcVAuthMethodTonieBox, +} NfcVAuthMethod; + +typedef enum { + NfcVTypePlain = 0, + NfcVTypeSlix = 1, + NfcVTypeSlixS = 2, + NfcVTypeSlixL = 3, + NfcVTypeSlix2 = 4, + NfcVTypeSniff = 255, +} NfcVSubtype; + +typedef enum { + NfcVSendFlagsNormal = 0, + NfcVSendFlagsSof = 1 << 0, + NfcVSendFlagsCrc = 1 << 1, + NfcVSendFlagsEof = 1 << 2, + NfcVSendFlagsOneSubcarrier = 0, + NfcVSendFlagsTwoSubcarrier = 1 << 3, + NfcVSendFlagsLowRate = 0, + NfcVSendFlagsHighRate = 1 << 4 +} NfcVSendFlags; + +typedef struct { + uint8_t key_read[4]; + uint8_t key_write[4]; + uint8_t key_privacy[4]; + uint8_t key_destroy[4]; + uint8_t key_eas[4]; + uint8_t rand[2]; + bool privacy; +} NfcVSlixData; + +typedef union { + NfcVSlixData slix; +} NfcVSubtypeData; + +typedef struct { + DigitalSignal* nfcv_resp_sof; + DigitalSignal* nfcv_resp_one; + DigitalSignal* nfcv_resp_zero; + DigitalSignal* nfcv_resp_eof; +} NfcVEmuAirSignals; + +typedef struct { + PulseReader* reader_signal; + DigitalSignal* nfcv_resp_pulse; /* pulse length, fc/32 */ + DigitalSignal* nfcv_resp_half_pulse; /* half pulse length, fc/32 */ + DigitalSignal* nfcv_resp_unmod; /* unmodulated length 256/fc */ + NfcVEmuAirSignals signals_high; + NfcVEmuAirSignals signals_low; + DigitalSequence* nfcv_signal; +} NfcVEmuAir; + +typedef void (*NfcVEmuProtocolHandler)( + FuriHalNfcTxRxContext* tx_rx, + FuriHalNfcDevData* nfc_data, + void* nfcv_data); +typedef bool (*NfcVEmuProtocolFilter)( + FuriHalNfcTxRxContext* tx_rx, + FuriHalNfcDevData* nfc_data, + void* nfcv_data); + +/* the default ISO15693 handler context */ +typedef struct { + uint8_t flags; /* ISO15693-3 flags of the header as specified */ + uint8_t command; /* ISO15693-3 command at offset 1 as specified */ + bool selected; /* ISO15693-3 flags: selected frame */ + bool addressed; /* ISO15693-3 flags: addressed frame */ + bool advanced; /* ISO15693-3 command: advanced command */ + uint8_t address_offset; /* ISO15693-3 offset of the address in frame, if addressed is set */ + uint8_t payload_offset; /* ISO15693-3 offset of the payload in frame */ + + uint8_t response_buffer[NFCV_FRAMESIZE_MAX]; /* pre-allocated response buffer */ + NfcVSendFlags response_flags; /* flags to use when sending response */ + uint32_t send_time; /* timestamp when to send the response */ + + NfcVEmuProtocolFilter emu_protocol_filter; +} NfcVEmuProtocolCtx; + +typedef struct { + /* common ISO15693 fields, being specified in ISO15693-3 */ + uint8_t dsfid; + uint8_t afi; + uint8_t ic_ref; + uint16_t block_num; + uint8_t block_size; + uint8_t data[NFCV_MEMSIZE_MAX]; + uint8_t security_status[1 + NFCV_BLOCKS_MAX]; + bool selected; + bool quiet; + + bool modified; + bool ready; + bool echo_mode; + + /* specfic variant infos */ + NfcVSubtype sub_type; + NfcVSubtypeData sub_data; + NfcVAuthMethod auth_method; + + /* precalced air level data */ + NfcVEmuAir emu_air; + + uint8_t* frame; /* [NFCV_FRAMESIZE_MAX] ISO15693-2 incoming raw data from air layer */ + uint8_t frame_length; /* ISO15693-2 length of incoming data */ + uint32_t eof_timestamp; /* ISO15693-2 EOF timestamp, read from DWT->CYCCNT */ + + /* handler for the protocol layer as specified in ISO15693-3 */ + NfcVEmuProtocolHandler emu_protocol_handler; + void* emu_protocol_ctx; + /* runtime data */ + char last_command[NFCV_LOG_STR_LEN]; + char error[NFCV_LOG_STR_LEN]; +} NfcVData; + +typedef struct { + uint16_t blocks_to_read; + int16_t blocks_read; +} NfcVReader; + +ReturnCode nfcv_read_blocks(NfcVReader* reader, NfcVData* data); +ReturnCode nfcv_read_sysinfo(FuriHalNfcDevData* nfc_data, NfcVData* data); +ReturnCode nfcv_inventory(uint8_t* uid); +bool nfcv_read_card(NfcVReader* reader, FuriHalNfcDevData* nfc_data, NfcVData* data); + +void nfcv_emu_init(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data); +void nfcv_emu_deinit(NfcVData* nfcv_data); +bool nfcv_emu_loop( + FuriHalNfcTxRxContext* tx_rx, + FuriHalNfcDevData* nfc_data, + NfcVData* nfcv_data, + uint32_t timeout_ms); +void nfcv_emu_send( + FuriHalNfcTxRxContext* tx_rx, + NfcVData* nfcv, + uint8_t* data, + uint8_t length, + NfcVSendFlags flags, + uint32_t send_time); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/slix.c b/lib/nfc/protocols/slix.c new file mode 100644 index 000000000..ec3afc248 --- /dev/null +++ b/lib/nfc/protocols/slix.c @@ -0,0 +1,412 @@ + +#include +#include "nfcv.h" +#include "slix.h" +#include "nfc_util.h" +#include +#include "furi_hal_nfc.h" +#include + +#define TAG "SLIX" + +static uint32_t slix_read_be(uint8_t* data, uint32_t length) { + uint32_t value = 0; + + for(uint32_t pos = 0; pos < length; pos++) { + value <<= 8; + value |= data[pos]; + } + + return value; +} + +uint8_t slix_get_ti(FuriHalNfcDevData* nfc_data) { + return (nfc_data->uid[3] >> 3) & 3; +} + +bool slix_check_card_type(FuriHalNfcDevData* nfc_data) { + if((nfc_data->uid[0] == 0xE0) && (nfc_data->uid[1] == 0x04) && (nfc_data->uid[2] == 0x01) && + slix_get_ti(nfc_data) == 2) { + return true; + } + return false; +} + +bool slix2_check_card_type(FuriHalNfcDevData* nfc_data) { + if((nfc_data->uid[0] == 0xE0) && (nfc_data->uid[1] == 0x04) && (nfc_data->uid[2] == 0x01) && + slix_get_ti(nfc_data) == 1) { + return true; + } + return false; +} + +bool slix_s_check_card_type(FuriHalNfcDevData* nfc_data) { + if((nfc_data->uid[0] == 0xE0) && (nfc_data->uid[1] == 0x04) && (nfc_data->uid[2] == 0x02)) { + return true; + } + return false; +} + +bool slix_l_check_card_type(FuriHalNfcDevData* nfc_data) { + if((nfc_data->uid[0] == 0xE0) && (nfc_data->uid[1] == 0x04) && (nfc_data->uid[2] == 0x03)) { + return true; + } + return false; +} + +ReturnCode slix_get_random(NfcVData* data) { + uint16_t received = 0; + uint8_t rxBuf[32]; + + ReturnCode ret = rfalNfcvPollerTransceiveReq( + NFCV_CMD_NXP_GET_RANDOM_NUMBER, + RFAL_NFCV_REQ_FLAG_DEFAULT, + NFCV_MANUFACTURER_NXP, + NULL, + NULL, + 0, + rxBuf, + sizeof(rxBuf), + &received); + + if(ret == ERR_NONE) { + if(received != 3) { + return ERR_PROTO; + } + if(data != NULL) { + data->sub_data.slix.rand[0] = rxBuf[2]; + data->sub_data.slix.rand[1] = rxBuf[1]; + } + } + + return ret; +} + +ReturnCode slix_unlock(NfcVData* data, uint32_t password_id) { + furi_assert(rand); + + uint16_t received = 0; + uint8_t rxBuf[32]; + uint8_t cmd_set_pass[] = { + password_id, + data->sub_data.slix.rand[1], + data->sub_data.slix.rand[0], + data->sub_data.slix.rand[1], + data->sub_data.slix.rand[0]}; + uint8_t* password = NULL; + + switch(password_id) { + case SLIX_PASS_READ: + password = data->sub_data.slix.key_read; + break; + case SLIX_PASS_WRITE: + password = data->sub_data.slix.key_write; + break; + case SLIX_PASS_PRIVACY: + password = data->sub_data.slix.key_privacy; + break; + case SLIX_PASS_DESTROY: + password = data->sub_data.slix.key_destroy; + break; + case SLIX_PASS_EASAFI: + password = data->sub_data.slix.key_eas; + break; + default: + break; + } + + if(!password) { + return ERR_NOTSUPP; + } + + for(int pos = 0; pos < 4; pos++) { + cmd_set_pass[1 + pos] ^= password[3 - pos]; + } + + ReturnCode ret = rfalNfcvPollerTransceiveReq( + NFCV_CMD_NXP_SET_PASSWORD, + RFAL_NFCV_REQ_FLAG_DATA_RATE, + NFCV_MANUFACTURER_NXP, + NULL, + cmd_set_pass, + sizeof(cmd_set_pass), + rxBuf, + sizeof(rxBuf), + &received); + + return ret; +} + +bool slix_generic_protocol_filter( + FuriHalNfcTxRxContext* tx_rx, + FuriHalNfcDevData* nfc_data, + void* nfcv_data_in, + uint32_t password_supported) { + furi_assert(tx_rx); + furi_assert(nfc_data); + furi_assert(nfcv_data_in); + + NfcVData* nfcv_data = (NfcVData*)nfcv_data_in; + NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx; + NfcVSlixData* slix = &nfcv_data->sub_data.slix; + + if(slix->privacy && ctx->command != NFCV_CMD_NXP_GET_RANDOM_NUMBER && + ctx->command != NFCV_CMD_NXP_SET_PASSWORD) { + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "command 0x%02X ignored, privacy mode", + ctx->command); + FURI_LOG_D(TAG, "%s", nfcv_data->last_command); + return true; + } + + bool handled = false; + + switch(ctx->command) { + case NFCV_CMD_NXP_GET_RANDOM_NUMBER: { + slix->rand[0] = furi_hal_random_get(); + slix->rand[1] = furi_hal_random_get(); + + ctx->response_buffer[0] = NFCV_NOERROR; + ctx->response_buffer[1] = slix->rand[1]; + ctx->response_buffer[2] = slix->rand[0]; + + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, 3, ctx->response_flags, ctx->send_time); + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "GET_RANDOM_NUMBER -> 0x%02X%02X", + slix->rand[0], + slix->rand[1]); + + handled = true; + break; + } + + case NFCV_CMD_NXP_SET_PASSWORD: { + uint8_t password_id = nfcv_data->frame[ctx->payload_offset]; + + if(!(password_id & password_supported)) { + break; + } + + uint8_t* password_xored = &nfcv_data->frame[ctx->payload_offset + 1]; + uint8_t* rand = slix->rand; + uint8_t* password = NULL; + uint8_t password_rcv[4]; + + switch(password_id) { + case SLIX_PASS_READ: + password = slix->key_read; + break; + case SLIX_PASS_WRITE: + password = slix->key_write; + break; + case SLIX_PASS_PRIVACY: + password = slix->key_privacy; + break; + case SLIX_PASS_DESTROY: + password = slix->key_destroy; + break; + case SLIX_PASS_EASAFI: + password = slix->key_eas; + break; + default: + break; + } + + if(!password) { + break; + } + + for(int pos = 0; pos < 4; pos++) { + password_rcv[pos] = password_xored[3 - pos] ^ rand[pos % 2]; + } + uint32_t pass_expect = slix_read_be(password, 4); + uint32_t pass_received = slix_read_be(password_rcv, 4); + + /* if the password is all-zeroes, just accept any password*/ + if(!pass_expect || pass_expect == pass_received) { + switch(password_id) { + case SLIX_PASS_READ: + break; + case SLIX_PASS_WRITE: + break; + case SLIX_PASS_PRIVACY: + slix->privacy = false; + nfcv_data->modified = true; + break; + case SLIX_PASS_DESTROY: + FURI_LOG_D(TAG, "Pooof! Got destroyed"); + break; + case SLIX_PASS_EASAFI: + break; + default: + break; + } + ctx->response_buffer[0] = NFCV_NOERROR; + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "SET_PASSWORD #%02X 0x%08lX OK", + password_id, + pass_received); + } else { + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "SET_PASSWORD #%02X 0x%08lX/%08lX FAIL", + password_id, + pass_received, + pass_expect); + } + handled = true; + break; + } + + case NFCV_CMD_NXP_ENABLE_PRIVACY: { + ctx->response_buffer[0] = NFCV_NOERROR; + + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "NFCV_CMD_NXP_ENABLE_PRIVACY"); + + slix->privacy = true; + handled = true; + break; + } + } + + return handled; +} + +bool slix_l_protocol_filter( + FuriHalNfcTxRxContext* tx_rx, + FuriHalNfcDevData* nfc_data, + void* nfcv_data_in) { + furi_assert(tx_rx); + furi_assert(nfc_data); + furi_assert(nfcv_data_in); + + bool handled = false; + + /* many SLIX share some of the functions, place that in a generic handler */ + if(slix_generic_protocol_filter( + tx_rx, + nfc_data, + nfcv_data_in, + SLIX_PASS_PRIVACY | SLIX_PASS_DESTROY | SLIX_PASS_EASAFI)) { + return true; + } + + return handled; +} + +void slix_l_prepare(NfcVData* nfcv_data) { + FURI_LOG_D( + TAG, " Privacy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_privacy, 4)); + FURI_LOG_D( + TAG, " Destroy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_destroy, 4)); + FURI_LOG_D(TAG, " EAS pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_eas, 4)); + FURI_LOG_D(TAG, " Privacy mode: %s", nfcv_data->sub_data.slix.privacy ? "ON" : "OFF"); + + NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx; + ctx->emu_protocol_filter = &slix_l_protocol_filter; +} + +bool slix_s_protocol_filter( + FuriHalNfcTxRxContext* tx_rx, + FuriHalNfcDevData* nfc_data, + void* nfcv_data_in) { + furi_assert(tx_rx); + furi_assert(nfc_data); + furi_assert(nfcv_data_in); + + bool handled = false; + + /* many SLIX share some of the functions, place that in a generic handler */ + if(slix_generic_protocol_filter(tx_rx, nfc_data, nfcv_data_in, SLIX_PASS_ALL)) { + return true; + } + + return handled; +} + +void slix_s_prepare(NfcVData* nfcv_data) { + FURI_LOG_D( + TAG, " Privacy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_privacy, 4)); + FURI_LOG_D( + TAG, " Destroy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_destroy, 4)); + FURI_LOG_D(TAG, " EAS pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_eas, 4)); + FURI_LOG_D(TAG, " Privacy mode: %s", nfcv_data->sub_data.slix.privacy ? "ON" : "OFF"); + + NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx; + ctx->emu_protocol_filter = &slix_s_protocol_filter; +} + +bool slix_protocol_filter( + FuriHalNfcTxRxContext* tx_rx, + FuriHalNfcDevData* nfc_data, + void* nfcv_data_in) { + furi_assert(tx_rx); + furi_assert(nfc_data); + furi_assert(nfcv_data_in); + + bool handled = false; + + /* many SLIX share some of the functions, place that in a generic handler */ + if(slix_generic_protocol_filter(tx_rx, nfc_data, nfcv_data_in, SLIX_PASS_EASAFI)) { + return true; + } + + return handled; +} + +void slix_prepare(NfcVData* nfcv_data) { + FURI_LOG_D( + TAG, " Privacy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_privacy, 4)); + FURI_LOG_D( + TAG, " Destroy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_destroy, 4)); + FURI_LOG_D(TAG, " EAS pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_eas, 4)); + FURI_LOG_D(TAG, " Privacy mode: %s", nfcv_data->sub_data.slix.privacy ? "ON" : "OFF"); + + NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx; + ctx->emu_protocol_filter = &slix_protocol_filter; +} + +bool slix2_protocol_filter( + FuriHalNfcTxRxContext* tx_rx, + FuriHalNfcDevData* nfc_data, + void* nfcv_data_in) { + furi_assert(tx_rx); + furi_assert(nfc_data); + furi_assert(nfcv_data_in); + + bool handled = false; + + /* many SLIX share some of the functions, place that in a generic handler */ + if(slix_generic_protocol_filter(tx_rx, nfc_data, nfcv_data_in, SLIX_PASS_ALL)) { + return true; + } + + return handled; +} + +void slix2_prepare(NfcVData* nfcv_data) { + FURI_LOG_D( + TAG, " Privacy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_privacy, 4)); + FURI_LOG_D( + TAG, " Destroy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_destroy, 4)); + FURI_LOG_D(TAG, " EAS pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_eas, 4)); + FURI_LOG_D(TAG, " Privacy mode: %s", nfcv_data->sub_data.slix.privacy ? "ON" : "OFF"); + + NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx; + ctx->emu_protocol_filter = &slix2_protocol_filter; +} diff --git a/lib/nfc/protocols/slix.h b/lib/nfc/protocols/slix.h new file mode 100644 index 000000000..701fa2f82 --- /dev/null +++ b/lib/nfc/protocols/slix.h @@ -0,0 +1,46 @@ +#pragma once + +#include +#include +#include "nfc_util.h" +#include + +#define NFCV_MANUFACTURER_NXP 0x04 + +/* ISO15693-3 CUSTOM NXP COMMANDS */ +#define NFCV_CMD_NXP_SET_EAS 0xA2 +#define NFCV_CMD_NXP_RESET_EAS 0xA3 +#define NFCV_CMD_NXP_LOCK_EAS 0xA4 +#define NFCV_CMD_NXP_EAS_ALARM 0xA5 +#define NFCV_CMD_NXP_PASSWORD_PROTECT_EAS_AFI 0xA6 +#define NFCV_CMD_NXP_WRITE_EAS_ID 0xA7 +#define NFCV_CMD_NXP_INVENTORY_PAGE_READ 0xB0 +#define NFCV_CMD_NXP_INVENTORY_PAGE_READ_FAST 0xB1 +#define NFCV_CMD_NXP_GET_RANDOM_NUMBER 0xB2 +#define NFCV_CMD_NXP_SET_PASSWORD 0xB3 +#define NFCV_CMD_NXP_WRITE_PASSWORD 0xB4 +#define NFCV_CMD_NXP_DESTROY 0xB9 +#define NFCV_CMD_NXP_ENABLE_PRIVACY 0xBA + +/* available passwords */ +#define SLIX_PASS_READ 0x01 +#define SLIX_PASS_WRITE 0x02 +#define SLIX_PASS_PRIVACY 0x04 +#define SLIX_PASS_DESTROY 0x08 +#define SLIX_PASS_EASAFI 0x10 + +#define SLIX_PASS_ALL \ + (SLIX_PASS_READ | SLIX_PASS_WRITE | SLIX_PASS_PRIVACY | SLIX_PASS_DESTROY | SLIX_PASS_EASAFI) + +bool slix_check_card_type(FuriHalNfcDevData* nfc_data); +bool slix2_check_card_type(FuriHalNfcDevData* nfc_data); +bool slix_s_check_card_type(FuriHalNfcDevData* nfc_data); +bool slix_l_check_card_type(FuriHalNfcDevData* nfc_data); + +ReturnCode slix_get_random(NfcVData* data); +ReturnCode slix_unlock(NfcVData* data, uint32_t password_id); + +void slix_prepare(NfcVData* nfcv_data); +void slix_s_prepare(NfcVData* nfcv_data); +void slix_l_prepare(NfcVData* nfcv_data); +void slix2_prepare(NfcVData* nfcv_data); diff --git a/lib/stm32wb.scons b/lib/stm32wb.scons index 7cb2fa8bd..94a1c7075 100644 --- a/lib/stm32wb.scons +++ b/lib/stm32wb.scons @@ -48,7 +48,10 @@ sources += Glob( ) sources += Glob( "stm32wb_copro/wpan/interface/patterns/ble_thread/tl/*_tl*.c", - exclude="stm32wb_copro/wpan/interface/patterns/ble_thread/tl/shci_tl_if.c", + exclude=[ + "stm32wb_copro/wpan/interface/patterns/ble_thread/tl/hci_tl_if.c", + "stm32wb_copro/wpan/interface/patterns/ble_thread/tl/shci_tl_if.c", + ], source=True, ) sources += [ diff --git a/lib/subghz/blocks/custom_btn.c b/lib/subghz/blocks/custom_btn.c index 3023e09ea..275a4c930 100644 --- a/lib/subghz/blocks/custom_btn.c +++ b/lib/subghz/blocks/custom_btn.c @@ -1,14 +1,16 @@ #include "custom_btn.h" -static uint8_t custom_btn_id = 0; +static uint8_t custom_btn_id = SUBGHZ_CUSTOM_BTN_OK; static uint8_t custom_btn_original = 0; static uint8_t custom_btn_max_btns = 0; -void subghz_custom_btn_set(uint8_t b) { - if(b > custom_btn_max_btns) { - custom_btn_id = 0; +bool subghz_custom_btn_set(uint8_t btn_id) { + if(btn_id > custom_btn_max_btns) { + custom_btn_id = SUBGHZ_CUSTOM_BTN_OK; + return false; } else { - custom_btn_id = b; + custom_btn_id = btn_id; + return true; } } @@ -16,8 +18,8 @@ uint8_t subghz_custom_btn_get() { return custom_btn_id; } -void subghz_custom_btn_set_original(uint8_t b) { - custom_btn_original = b; +void subghz_custom_btn_set_original(uint8_t btn_code) { + custom_btn_original = btn_code; } uint8_t subghz_custom_btn_get_original() { @@ -31,4 +33,8 @@ void subghz_custom_btn_set_max(uint8_t b) { void subghz_custom_btns_reset() { custom_btn_original = 0; custom_btn_max_btns = 0; +} + +bool subghz_custom_btn_is_allowed() { + return custom_btn_max_btns != 0; } \ No newline at end of file diff --git a/lib/subghz/blocks/custom_btn.h b/lib/subghz/blocks/custom_btn.h index 0ea5ee654..c13842fa8 100644 --- a/lib/subghz/blocks/custom_btn.h +++ b/lib/subghz/blocks/custom_btn.h @@ -4,14 +4,23 @@ #include #include -void subghz_custom_btn_set(uint8_t b); +// Default btn ID +#define SUBGHZ_CUSTOM_BTN_OK (0U) +#define SUBGHZ_CUSTOM_BTN_UP (1U) +#define SUBGHZ_CUSTOM_BTN_DOWN (2U) +#define SUBGHZ_CUSTOM_BTN_LEFT (3U) +#define SUBGHZ_CUSTOM_BTN_RIGHT (4U) + +bool subghz_custom_btn_set(uint8_t btn_id); uint8_t subghz_custom_btn_get(); -void subghz_custom_btn_set_original(uint8_t b); +void subghz_custom_btn_set_original(uint8_t btn_code); uint8_t subghz_custom_btn_get_original(); void subghz_custom_btn_set_max(uint8_t b); -void subghz_custom_btns_reset(); \ No newline at end of file +void subghz_custom_btns_reset(); + +bool subghz_custom_btn_is_allowed(); \ No newline at end of file diff --git a/lib/subghz/protocols/alutech_at_4n.c b/lib/subghz/protocols/alutech_at_4n.c index 47cf37d6b..281e820f0 100644 --- a/lib/subghz/protocols/alutech_at_4n.c +++ b/lib/subghz/protocols/alutech_at_4n.c @@ -308,8 +308,8 @@ bool subghz_protocol_alutech_at_4n_create_data( instance->generic.data_count_bit = 72; bool res = subghz_protocol_alutech_at_4n_gen_data(instance, btn); if(res) { - res = subghz_block_generic_serialize(&instance->generic, flipper_format, preset); - if((res == SubGhzProtocolStatusOk) && + if((subghz_block_generic_serialize(&instance->generic, flipper_format, preset) != + SubGhzProtocolStatusOk) || !flipper_format_write_uint32(flipper_format, "CRC", &instance->crc, 1)) { FURI_LOG_E(TAG, "Unable to add CRC"); res = false; @@ -318,6 +318,13 @@ bool subghz_protocol_alutech_at_4n_create_data( return res; } +/** + * Defines the button value for the current btn_id + * Basic set | 0x11 | 0x22 | 0xFF | 0x44 | 0x33 | + * @return Button code + */ +static uint8_t subghz_protocol_alutech_at_4n_get_btn_code(); + /** * Generating an upload from data. * @param instance Pointer to a SubGhzProtocolEncoderAlutech instance @@ -333,102 +340,8 @@ static bool subghz_protocol_encoder_alutech_at_4n_get_upload( subghz_custom_btn_set_original(btn); } - uint8_t custom_btn_id = subghz_custom_btn_get(); - uint8_t original_btn_num = subghz_custom_btn_get_original(); + btn = subghz_protocol_alutech_at_4n_get_btn_code(); - // Set custom button - if(custom_btn_id == 1) { - switch(original_btn_num) { - case 0x11: - btn = 0x22; - break; - case 0x22: - btn = 0x11; - break; - case 0xFF: - btn = 0x11; - break; - case 0x44: - btn = 0x11; - break; - case 0x33: - btn = 0x11; - break; - - default: - break; - } - } - if(custom_btn_id == 2) { - switch(original_btn_num) { - case 0x11: - btn = 0x44; - break; - case 0x22: - btn = 0x44; - break; - case 0xFF: - btn = 0x44; - break; - case 0x44: - btn = 0xFF; - break; - case 0x33: - btn = 0x44; - break; - - default: - break; - } - } - if(custom_btn_id == 3) { - switch(original_btn_num) { - case 0x11: - btn = 0x33; - break; - case 0x22: - btn = 0x33; - break; - case 0xFF: - btn = 0x33; - break; - case 0x44: - btn = 0x33; - break; - case 0x33: - btn = 0x22; - break; - - default: - break; - } - } - if(custom_btn_id == 4) { - switch(original_btn_num) { - case 0x11: - btn = 0xFF; - break; - case 0x22: - btn = 0xFF; - break; - case 0xFF: - btn = 0x22; - break; - case 0x44: - btn = 0x22; - break; - case 0x33: - btn = 0xFF; - break; - - default: - break; - } - } - - if((custom_btn_id == 0) && (original_btn_num != 0)) { - btn = original_btn_num; - } // Gen new key if(!subghz_protocol_alutech_at_4n_gen_data(instance, btn)) { return false; @@ -788,6 +701,104 @@ SubGhzProtocolStatus subghz_protocol_decoder_alutech_at_4n_deserialize( return ret; } +static uint8_t subghz_protocol_alutech_at_4n_get_btn_code() { + uint8_t custom_btn_id = subghz_custom_btn_get(); + uint8_t original_btn_code = subghz_custom_btn_get_original(); + uint8_t btn = original_btn_code; + + // Set custom button + if((custom_btn_id == SUBGHZ_CUSTOM_BTN_OK) && (original_btn_code != 0)) { + // Restore original button code + btn = original_btn_code; + } else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_UP) { + switch(original_btn_code) { + case 0x11: + btn = 0x22; + break; + case 0x22: + btn = 0x11; + break; + case 0xFF: + btn = 0x11; + break; + case 0x44: + btn = 0x11; + break; + case 0x33: + btn = 0x11; + break; + + default: + break; + } + } else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_DOWN) { + switch(original_btn_code) { + case 0x11: + btn = 0x44; + break; + case 0x22: + btn = 0x44; + break; + case 0xFF: + btn = 0x44; + break; + case 0x44: + btn = 0xFF; + break; + case 0x33: + btn = 0x44; + break; + + default: + break; + } + } else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_LEFT) { + switch(original_btn_code) { + case 0x11: + btn = 0x33; + break; + case 0x22: + btn = 0x33; + break; + case 0xFF: + btn = 0x33; + break; + case 0x44: + btn = 0x33; + break; + case 0x33: + btn = 0x22; + break; + + default: + break; + } + } else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_RIGHT) { + switch(original_btn_code) { + case 0x11: + btn = 0xFF; + break; + case 0x22: + btn = 0xFF; + break; + case 0xFF: + btn = 0x22; + break; + case 0x44: + btn = 0x22; + break; + case 0x33: + btn = 0xFF; + break; + + default: + break; + } + } + + return btn; +} + void subghz_protocol_decoder_alutech_at_4n_get_string(void* context, FuriString* output) { furi_assert(context); SubGhzProtocolDecoderAlutech_at_4n* instance = context; diff --git a/lib/subghz/protocols/came_atomo.c b/lib/subghz/protocols/came_atomo.c index 870afede3..4723ec99f 100644 --- a/lib/subghz/protocols/came_atomo.c +++ b/lib/subghz/protocols/came_atomo.c @@ -7,6 +7,8 @@ #include "../blocks/generic.h" #include "../blocks/math.h" +#include "../blocks/custom_btn.h" + #define TAG "SubGhzProtocoCameAtomo" static const SubGhzBlockConst subghz_protocol_came_atomo_const = { @@ -71,6 +73,13 @@ const SubGhzProtocol subghz_protocol_came_atomo = { static void subghz_protocol_came_atomo_remote_controller(SubGhzBlockGeneric* instance); +/** + * Defines the button value for the current btn_id + * Basic set | 0x0 | 0x2 | 0x4 | 0x6 | + * @return Button code + */ +static uint8_t subghz_protocol_came_atomo_get_btn_code(); + void* subghz_protocol_encoder_came_atomo_alloc(SubGhzEnvironment* environment) { UNUSED(environment); SubGhzProtocolEncoderCameAtomo* instance = malloc(sizeof(SubGhzProtocolEncoderCameAtomo)); @@ -120,12 +129,53 @@ static LevelDuration return level_duration_make(data.level, data.duration); } +bool subghz_protocol_came_atomo_create_data( + void* context, + FlipperFormat* flipper_format, + uint32_t serial, + uint16_t cnt, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolEncoderCameAtomo* instance = context; + instance->generic.btn = 0x1; + instance->generic.serial = serial; + instance->generic.cnt = cnt; + instance->generic.cnt_2 = 0x7e; + instance->generic.data_count_bit = 62; + instance->generic.data_2 = + ((uint64_t)0x7e << 56 | (uint64_t)cnt << 40 | (uint64_t)serial << 8); + + uint8_t pack[8] = {}; + + pack[0] = (instance->generic.cnt_2); + pack[1] = (instance->generic.cnt >> 8); + pack[2] = (instance->generic.cnt & 0xFF); + pack[3] = ((instance->generic.data_2 >> 32) & 0xFF); + pack[4] = ((instance->generic.data_2 >> 24) & 0xFF); + pack[5] = ((instance->generic.data_2 >> 16) & 0xFF); + pack[6] = ((instance->generic.data_2 >> 8) & 0xFF); + pack[7] = (instance->generic.data_2 & 0xFF); + + atomo_encrypt(pack); + uint32_t hi = pack[0] << 24 | pack[1] << 16 | pack[2] << 8 | pack[3]; + uint32_t lo = pack[4] << 24 | pack[5] << 16 | pack[6] << 8 | pack[7]; + instance->generic.data = (uint64_t)hi << 32 | lo; + + instance->generic.data ^= 0xFFFFFFFFFFFFFFFF; + instance->generic.data >>= 4; + instance->generic.data &= 0xFFFFFFFFFFFFFFF; + + return SubGhzProtocolStatusOk == + subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + /** * Generating an upload from data. * @param instance Pointer to a SubGhzProtocolEncoderCameAtomo instance */ -static void - subghz_protocol_encoder_came_atomo_get_upload(SubGhzProtocolEncoderCameAtomo* instance) { +static void subghz_protocol_encoder_came_atomo_get_upload( + SubGhzProtocolEncoderCameAtomo* instance, + uint8_t btn) { furi_assert(instance); size_t index = 0; @@ -145,6 +195,23 @@ static void instance->generic.cnt = 0; } + // Save original button for later use + if(subghz_custom_btn_get_original() == 0) { + subghz_custom_btn_set_original(btn); + } + + btn = subghz_protocol_came_atomo_get_btn_code(); + + if(btn == 0x1) { + btn = 0x0; + } else if(btn == 0x2) { + btn = 0x2; + } else if(btn == 0x3) { + btn = 0x4; + } else if(btn == 0x4) { + btn = 0x6; + } + //Send header instance->encoder.upload[index++] = level_duration_make(true, (uint32_t)subghz_protocol_came_atomo_const.te_long * 15); @@ -159,7 +226,7 @@ static void pack[4] = ((instance->generic.data_2 >> 24) & 0xFF); pack[5] = ((instance->generic.data_2 >> 16) & 0xFF); pack[6] = ((instance->generic.data_2 >> 8) & 0xFF); - pack[7] = (instance->generic.data_2 & 0xFF); + pack[7] = (btn << 4); if(pack[0] == 0x7F) { pack[0] = 0; @@ -211,7 +278,7 @@ static void pack[4] = ((instance->generic.data_2 >> 24) & 0xFF); pack[5] = ((instance->generic.data_2 >> 16) & 0xFF); pack[6] = ((instance->generic.data_2 >> 8) & 0xFF); - pack[7] = (instance->generic.data_2 & 0xFF); + pack[7] = (btn << 4); atomo_encrypt(pack); uint32_t hi = pack[0] << 24 | pack[1] << 16 | pack[2] << 8 | pack[3]; @@ -240,7 +307,7 @@ SubGhzProtocolStatus flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); subghz_protocol_came_atomo_remote_controller(&instance->generic); - subghz_protocol_encoder_came_atomo_get_upload(instance); + subghz_protocol_encoder_came_atomo_get_upload(instance, instance->generic.btn); if(!flipper_format_rewind(flipper_format)) { FURI_LOG_E(TAG, "Rewind error"); @@ -487,20 +554,23 @@ static void subghz_protocol_came_atomo_remote_controller(SubGhzBlockGeneric* ins uint8_t btn_decode = (pack[7] >> 4); if(btn_decode == 0x0) { instance->btn = 0x1; - } - if(btn_decode == 0x2) { + } else if(btn_decode == 0x2) { instance->btn = 0x2; - } - if(btn_decode == 0x4) { + } else if(btn_decode == 0x4) { instance->btn = 0x3; - } - if(btn_decode == 0x6) { + } else if(btn_decode == 0x6) { instance->btn = 0x4; } uint32_t hi = pack[0] << 24 | pack[1] << 16 | pack[2] << 8 | pack[3]; uint32_t lo = pack[4] << 24 | pack[5] << 16 | pack[6] << 8 | pack[7]; instance->data_2 = (uint64_t)hi << 32 | lo; + + // Save original button for later use + if(subghz_custom_btn_get_original() == 0) { + subghz_custom_btn_set_original(instance->btn); + } + subghz_custom_btn_set_max(3); } void atomo_encrypt(uint8_t* buff) { @@ -544,6 +614,74 @@ void atomo_decrypt(uint8_t* buff) { } } +static uint8_t subghz_protocol_came_atomo_get_btn_code() { + uint8_t custom_btn_id = subghz_custom_btn_get(); + uint8_t original_btn_code = subghz_custom_btn_get_original(); + uint8_t btn = original_btn_code; + + // Set custom button + if((custom_btn_id == SUBGHZ_CUSTOM_BTN_OK) && (original_btn_code != 0)) { + // Restore original button code + btn = original_btn_code; + } else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_UP) { + switch(original_btn_code) { + case 0x1: + btn = 0x2; + break; + case 0x2: + btn = 0x1; + break; + case 0x3: + btn = 0x1; + break; + case 0x4: + btn = 0x1; + break; + + default: + break; + } + } else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_DOWN) { + switch(original_btn_code) { + case 0x1: + btn = 0x3; + break; + case 0x2: + btn = 0x3; + break; + case 0x3: + btn = 0x2; + break; + case 0x4: + btn = 0x2; + break; + + default: + break; + } + } else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_LEFT) { + switch(original_btn_code) { + case 0x1: + btn = 0x4; + break; + case 0x2: + btn = 0x4; + break; + case 0x3: + btn = 0x4; + break; + case 0x4: + btn = 0x3; + break; + + default: + break; + } + } + + return btn; +} + uint8_t subghz_protocol_decoder_came_atomo_get_hash_data(void* context) { furi_assert(context); SubGhzProtocolDecoderCameAtomo* instance = context; @@ -581,7 +719,7 @@ void subghz_protocol_decoder_came_atomo_get_string(void* context, FuriString* ou output, "%s %db\r\n" "Key:0x%08lX%08lX\r\n" - "Sn:0x%08lX Btn:0x%01X\r\n" + "Sn:0x%08lX Btn:%01X\r\n" "Pcl_Cnt:0x%04lX\r\n" "Btn_Cnt:0x%02X", diff --git a/lib/subghz/protocols/came_atomo.h b/lib/subghz/protocols/came_atomo.h index c5e45a68d..8183877b0 100644 --- a/lib/subghz/protocols/came_atomo.h +++ b/lib/subghz/protocols/came_atomo.h @@ -14,6 +14,22 @@ void atomo_decrypt(uint8_t* buff); void atomo_encrypt(uint8_t* buff); +/** + * Key generation from simple data. + * @param context Pointer to a SubGhzProtocolEncoderCameAtomo instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param serial Serial number, 24 bit + * @param cnt Counter value, 16 bit + * @param preset Modulation, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_came_atomo_create_data( + void* context, + FlipperFormat* flipper_format, + uint32_t serial, + uint16_t cnt, + SubGhzRadioPreset* preset); + /** * Allocate SubGhzProtocolEncoderCameAtomo. * @param environment Pointer to a SubGhzEnvironment instance diff --git a/lib/subghz/protocols/dooya.c b/lib/subghz/protocols/dooya.c index 47e95209e..816847840 100644 --- a/lib/subghz/protocols/dooya.c +++ b/lib/subghz/protocols/dooya.c @@ -308,7 +308,7 @@ void subghz_protocol_decoder_dooya_feed(void* context, bool level, uint32_t dura * Analysis of received data * @param instance Pointer to a SubGhzBlockGeneric* instance */ -static void subghz_protocol_somfy_telis_check_remote_controller(SubGhzBlockGeneric* instance) { +static void subghz_protocol_dooya_check_remote_controller(SubGhzBlockGeneric* instance) { /* * serial s/m ch key * long press down X * E1DC030533, 40b 111000011101110000000011 0000 0101 0011 0011 @@ -416,7 +416,7 @@ void subghz_protocol_decoder_dooya_get_string(void* context, FuriString* output) furi_assert(context); SubGhzProtocolDecoderDooya* instance = context; - subghz_protocol_somfy_telis_check_remote_controller(&instance->generic); + subghz_protocol_dooya_check_remote_controller(&instance->generic); furi_string_cat_printf( output, diff --git a/lib/subghz/protocols/keeloq.c b/lib/subghz/protocols/keeloq.c index 4900e7e49..26b5a20a9 100644 --- a/lib/subghz/protocols/keeloq.c +++ b/lib/subghz/protocols/keeloq.c @@ -14,6 +14,10 @@ #define TAG "SubGhzProtocolKeeloq" +#define KEELOQ_PROG_MODE_OFF (0U) +#define KEELOQ_PROG_MODE_BFT (1U) +#define KEELOQ_PROG_MODE_APRIMATIC (2U) + static const SubGhzBlockConst subghz_protocol_keeloq_const = { .te_short = 400, .te_long = 800, @@ -92,10 +96,9 @@ static uint8_t klq_prog_mode; static uint16_t temp_counter; void keeloq_reset_original_btn() { - subghz_custom_btn_set_original(0); - subghz_custom_btn_set_max(0); + subghz_custom_btns_reset(); temp_counter = 0; - klq_prog_mode = 0; + klq_prog_mode = KEELOQ_PROG_MODE_OFF; } void keeloq_reset_mfname() { @@ -146,6 +149,7 @@ void subghz_protocol_encoder_keeloq_free(void* context) { * Key generation from simple data * @param instance Pointer to a SubGhzProtocolEncoderKeeloq* instance * @param btn Button number, 4 bit + * @param counter_up increasing the counter if the value is true */ static bool subghz_protocol_keeloq_gen_data( SubGhzProtocolEncoderKeeloq* instance, @@ -160,29 +164,30 @@ static bool subghz_protocol_keeloq_gen_data( instance->manufacture_name = ""; } - // BFT programming mode on / off conditions - if((strcmp(instance->manufacture_name, "BFT") == 0) && (btn == 0xF)) { - klq_prog_mode = 1; - } - if((strcmp(instance->manufacture_name, "BFT") == 0) && (btn != 0xF) && (klq_prog_mode == 1)) { - klq_prog_mode = 0; - } - // Aprimatic programming mode on / off conditions - if((strcmp(instance->manufacture_name, "Aprimatic") == 0) && (btn == 0xF)) { - klq_prog_mode = 2; - } - if((strcmp(instance->manufacture_name, "Aprimatic") == 0) && (btn != 0xF) && - (klq_prog_mode == 2)) { - klq_prog_mode = 0; + // programming mode on / off conditions + if(strcmp(instance->manufacture_name, "BFT") == 0) { + // BFT programming mode on / off conditions + if(btn == 0xF) { + klq_prog_mode = KEELOQ_PROG_MODE_BFT; + } else if(klq_prog_mode == KEELOQ_PROG_MODE_BFT) { + klq_prog_mode = KEELOQ_PROG_MODE_OFF; + } + } else if(strcmp(instance->manufacture_name, "Aprimatic") == 0) { + // Aprimatic programming mode on / off conditions + if(btn == 0xF) { + klq_prog_mode = KEELOQ_PROG_MODE_APRIMATIC; + } else if(klq_prog_mode == KEELOQ_PROG_MODE_APRIMATIC) { + klq_prog_mode = KEELOQ_PROG_MODE_OFF; + } } // If we using BFT programming mode we will trasmit its seed in hop part like original remote - if(klq_prog_mode == 1) { + if(klq_prog_mode == KEELOQ_PROG_MODE_BFT) { hop = instance->generic.seed; - } else if(klq_prog_mode == 2) { + } else if(klq_prog_mode == KEELOQ_PROG_MODE_APRIMATIC) { // If we using Aprimatic programming mode we will trasmit some strange looking hop value, why? cuz manufacturer did it this way :) hop = 0x1A2B3C4D; } - if(counter_up && klq_prog_mode == 0) { + if(counter_up && klq_prog_mode == KEELOQ_PROG_MODE_OFF) { if(instance->generic.cnt < 0xFFFF) { if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) >= 0xFFFF) { instance->generic.cnt = 0; @@ -193,50 +198,8 @@ static bool subghz_protocol_keeloq_gen_data( instance->generic.cnt = 0; } } - if(klq_prog_mode == 0) { - uint32_t decrypt = (uint32_t)btn << 28 | - (instance->generic.serial & 0x3FF) - << 16 | //ToDo in some protocols the discriminator is 0 - instance->generic.cnt; - - if(strcmp(instance->manufacture_name, "Aprimatic") == 0) { - // Aprimatic uses 12bit serial number + 2bit APR1 "parity" bit in front of it replacing first 2 bits of serial - // Thats in theory! We need to check if this is true for all Aprimatic remotes but we got only 3 recordings to test - // For now lets assume that this is true for all Aprimatic remotes, if not we will need to add some more code here - uint32_t apri_serial = instance->generic.serial; - uint8_t apr1 = 0; - for(uint16_t i = 1; i != 0b10000000000; i <<= 1) { - if(apri_serial & i) apr1++; - } - apri_serial &= 0b00001111111111; - if(apr1 % 2 == 0) { - apri_serial |= 0b110000000000; - } - decrypt = btn << 28 | (apri_serial & 0xFFF) << 16 | instance->generic.cnt; - } - - // DTM Neo, Came_Space uses 12bit -> simple learning -- FAAC_RC,XT , Mutanco_Mutancode, Stilmatic(Schellenberg) -> 12bit normal learning - if((strcmp(instance->manufacture_name, "DTM_Neo") == 0) || - (strcmp(instance->manufacture_name, "FAAC_RC,XT") == 0) || - (strcmp(instance->manufacture_name, "Mutanco_Mutancode") == 0) || - (strcmp(instance->manufacture_name, "Stilmatic") == 0) || - (strcmp(instance->manufacture_name, "Came_Space") == 0)) { - decrypt = btn << 28 | (instance->generic.serial & 0xFFF) << 16 | instance->generic.cnt; - } - - // Nice Smilo, MHouse, JCM, Normstahl -> 8bit serial - simple learning - if((strcmp(instance->manufacture_name, "NICE_Smilo") == 0) || - (strcmp(instance->manufacture_name, "NICE_MHOUSE") == 0) || - (strcmp(instance->manufacture_name, "JCM_Tech") == 0) || - (strcmp(instance->manufacture_name, "Normstahl") == 0)) { - decrypt = btn << 28 | (instance->generic.serial & 0xFF) << 16 | instance->generic.cnt; - } - - // Beninca / Allmatic -> no serial - simple XOR - if(strcmp(instance->manufacture_name, "Beninca") == 0) { - decrypt = btn << 28 | (0x000) << 16 | instance->generic.cnt; - } - + if(klq_prog_mode == KEELOQ_PROG_MODE_OFF) { + // Protocols that do not use encryption if(strcmp(instance->manufacture_name, "Unknown") == 0) { code_found_reverse = subghz_protocol_blocks_reverse_key( instance->generic.data, instance->generic.data_count_bit); @@ -247,72 +210,120 @@ static bool subghz_protocol_keeloq_gen_data( } else if(strcmp(instance->manufacture_name, "HCS101") == 0) { hop = instance->generic.cnt << 16 | (btn & 0xF) << 12 | 0x000; } else { - for - M_EACH(manufacture_code, *subghz_keystore_get_data(instance->keystore), SubGhzKeyArray_t) { - res = strcmp(furi_string_get_cstr(manufacture_code->name), instance->manufacture_name); - if(res == 0) { - switch(manufacture_code->type) { - case KEELOQ_LEARNING_SIMPLE: - //Simple Learning - hop = subghz_protocol_keeloq_common_encrypt(decrypt, manufacture_code->key); - break; - case KEELOQ_LEARNING_NORMAL: - //Simple Learning - man = - subghz_protocol_keeloq_common_normal_learning(fix, manufacture_code->key); - hop = subghz_protocol_keeloq_common_encrypt(decrypt, man); - break; - case KEELOQ_LEARNING_SECURE: - //Secure Learning - man = subghz_protocol_keeloq_common_secure_learning( - fix, instance->generic.seed, manufacture_code->key); - hop = subghz_protocol_keeloq_common_encrypt(decrypt, man); - break; - case KEELOQ_LEARNING_MAGIC_XOR_TYPE_1: - //Magic XOR type-1 Learning - man = subghz_protocol_keeloq_common_magic_xor_type1_learning( - instance->generic.serial, manufacture_code->key); - hop = subghz_protocol_keeloq_common_encrypt(decrypt, man); - break; - case KEELOQ_LEARNING_MAGIC_SERIAL_TYPE_1: - //Magic Serial Type 1 learning - man = subghz_protocol_keeloq_common_magic_serial_type1_learning( - fix, manufacture_code->key); - hop = subghz_protocol_keeloq_common_encrypt(decrypt, man); - break; - case KEELOQ_LEARNING_UNKNOWN: - if(kl_type == 1) { - hop = - subghz_protocol_keeloq_common_encrypt(decrypt, manufacture_code->key); - } - if(kl_type == 2) { - man = subghz_protocol_keeloq_common_normal_learning( - fix, manufacture_code->key); - hop = subghz_protocol_keeloq_common_encrypt(decrypt, man); - } - if(kl_type == 3) { - man = subghz_protocol_keeloq_common_secure_learning( - fix, instance->generic.seed, manufacture_code->key); - hop = subghz_protocol_keeloq_common_encrypt(decrypt, man); - } - if(kl_type == 4) { - man = subghz_protocol_keeloq_common_magic_xor_type1_learning( - instance->generic.serial, manufacture_code->key); - hop = subghz_protocol_keeloq_common_encrypt(decrypt, man); - } - break; + // Protocols that use encryption + uint32_t decrypt = (uint32_t)btn << 28 | + (instance->generic.serial & 0x3FF) + << 16 | //ToDo in some protocols the discriminator is 0 + instance->generic.cnt; + + if(strcmp(instance->manufacture_name, "Aprimatic") == 0) { + // Aprimatic uses 12bit serial number + 2bit APR1 "parity" bit in front of it replacing first 2 bits of serial + // Thats in theory! We need to check if this is true for all Aprimatic remotes but we got only 3 recordings to test + // For now lets assume that this is true for all Aprimatic remotes, if not we will need to add some more code here + uint32_t apri_serial = instance->generic.serial; + uint8_t apr1 = 0; + for(uint16_t i = 1; i != 0b10000000000; i <<= 1) { + if(apri_serial & i) apr1++; } - break; + apri_serial &= 0b00001111111111; + if(apr1 % 2 == 0) { + apri_serial |= 0b110000000000; + } + decrypt = btn << 28 | (apri_serial & 0xFFF) << 16 | instance->generic.cnt; + } else if( + (strcmp(instance->manufacture_name, "DTM_Neo") == 0) || + (strcmp(instance->manufacture_name, "FAAC_RC,XT") == 0) || + (strcmp(instance->manufacture_name, "Mutanco_Mutancode") == 0) || + (strcmp(instance->manufacture_name, "Stilmatic") == 0) || + (strcmp(instance->manufacture_name, "Came_Space") == 0)) { + // DTM Neo, Came_Space uses 12bit serial -> simple learning + // FAAC_RC,XT , Mutanco_Mutancode, Stilmatic(Schellenberg) 12bit serial -> normal learning + decrypt = btn << 28 | (instance->generic.serial & 0xFFF) << 16 | + instance->generic.cnt; + } else if( + (strcmp(instance->manufacture_name, "NICE_Smilo") == 0) || + (strcmp(instance->manufacture_name, "NICE_MHOUSE") == 0) || + (strcmp(instance->manufacture_name, "JCM_Tech") == 0) || + (strcmp(instance->manufacture_name, "Normstahl") == 0)) { + // Nice Smilo, MHouse, JCM, Normstahl -> 8bit serial - simple learning + decrypt = btn << 28 | (instance->generic.serial & 0xFF) << 16 | + instance->generic.cnt; + } else if(strcmp(instance->manufacture_name, "Beninca") == 0) { + decrypt = btn << 28 | (0x000) << 16 | instance->generic.cnt; + // Beninca / Allmatic -> no serial - simple XOR } - } + + for + M_EACH( + manufacture_code, + *subghz_keystore_get_data(instance->keystore), + SubGhzKeyArray_t) { + res = strcmp( + furi_string_get_cstr(manufacture_code->name), instance->manufacture_name); + if(res == 0) { + switch(manufacture_code->type) { + case KEELOQ_LEARNING_SIMPLE: + //Simple Learning + hop = subghz_protocol_keeloq_common_encrypt( + decrypt, manufacture_code->key); + break; + case KEELOQ_LEARNING_NORMAL: + //Simple Learning + man = subghz_protocol_keeloq_common_normal_learning( + fix, manufacture_code->key); + hop = subghz_protocol_keeloq_common_encrypt(decrypt, man); + break; + case KEELOQ_LEARNING_SECURE: + //Secure Learning + man = subghz_protocol_keeloq_common_secure_learning( + fix, instance->generic.seed, manufacture_code->key); + hop = subghz_protocol_keeloq_common_encrypt(decrypt, man); + break; + case KEELOQ_LEARNING_MAGIC_XOR_TYPE_1: + //Magic XOR type-1 Learning + man = subghz_protocol_keeloq_common_magic_xor_type1_learning( + instance->generic.serial, manufacture_code->key); + hop = subghz_protocol_keeloq_common_encrypt(decrypt, man); + break; + case KEELOQ_LEARNING_MAGIC_SERIAL_TYPE_1: + //Magic Serial Type 1 learning + man = subghz_protocol_keeloq_common_magic_serial_type1_learning( + fix, manufacture_code->key); + hop = subghz_protocol_keeloq_common_encrypt(decrypt, man); + break; + case KEELOQ_LEARNING_UNKNOWN: + if(kl_type == 1) { + hop = subghz_protocol_keeloq_common_encrypt( + decrypt, manufacture_code->key); + } + if(kl_type == 2) { + man = subghz_protocol_keeloq_common_normal_learning( + fix, manufacture_code->key); + hop = subghz_protocol_keeloq_common_encrypt(decrypt, man); + } + if(kl_type == 3) { + man = subghz_protocol_keeloq_common_secure_learning( + fix, instance->generic.seed, manufacture_code->key); + hop = subghz_protocol_keeloq_common_encrypt(decrypt, man); + } + if(kl_type == 4) { + man = subghz_protocol_keeloq_common_magic_xor_type1_learning( + instance->generic.serial, manufacture_code->key); + hop = subghz_protocol_keeloq_common_encrypt(decrypt, man); + } + break; + } + break; + } + } } } if(hop) { uint64_t yek = (uint64_t)fix << 32 | hop; instance->generic.data = subghz_protocol_blocks_reverse_key(yek, instance->generic.data_count_bit); - } - return true; + } // What should happen if seed = 0 in bft programming mode + return true; // Always return true } bool subghz_protocol_keeloq_create_data( @@ -363,6 +374,14 @@ bool subghz_protocol_keeloq_bft_create_data( return res; } +/** + * Defines the button value for the current btn_id + * Basic set | 0x1 | 0x2 | 0x4 | 0x8 | 0xA or Special Learning Code | + * @param last_btn_code Candidate for the last button + * @return Button code + */ +static uint8_t subghz_protocol_keeloq_get_btn_code(uint8_t last_btn_code); + /** * Generating an upload from data. * @param instance Pointer to a SubGhzProtocolEncoderKeeloq instance @@ -380,9 +399,9 @@ static bool if(instance->manufacture_name == 0x0) { instance->manufacture_name = ""; } - if(klq_prog_mode == 1) { + if(klq_prog_mode == KEELOQ_PROG_MODE_BFT) { instance->manufacture_name = "BFT"; - } else if(klq_prog_mode == 2) { + } else if(klq_prog_mode == KEELOQ_PROG_MODE_APRIMATIC) { instance->manufacture_name = "Aprimatic"; } uint8_t klq_last_custom_btn = 0xA; @@ -391,118 +410,9 @@ static bool klq_last_custom_btn = 0xF; } - uint8_t custom_btn_id = subghz_custom_btn_get(); - uint8_t original_btn_num = subghz_custom_btn_get_original(); - // Set custom button - if(custom_btn_id == 1) { - switch(original_btn_num) { - case 0x1: - btn = 0x2; - break; - case 0x2: - btn = 0x1; - break; - case 0xA: - btn = 0x1; - break; - case 0x4: - btn = 0x1; - break; - case 0x8: - btn = 0x1; - break; - case 0xF: - btn = 0x1; - break; + btn = subghz_protocol_keeloq_get_btn_code(klq_last_custom_btn); - default: - btn = 0x1; - break; - } - } - if(custom_btn_id == 2) { - switch(original_btn_num) { - case 0x1: - btn = 0x4; - break; - case 0x2: - btn = 0x4; - break; - case 0xA: - btn = 0x4; - break; - case 0x4: - btn = klq_last_custom_btn; - break; - case 0x8: - btn = 0x4; - break; - case 0xF: - btn = 0x4; - break; - - default: - btn = 0x4; - break; - } - } - if(custom_btn_id == 3) { - switch(original_btn_num) { - case 0x1: - btn = 0x8; - break; - case 0x2: - btn = 0x8; - break; - case 0xA: - btn = 0x8; - break; - case 0x4: - btn = 0x8; - break; - case 0x8: - btn = 0x2; - break; - case 0xF: - btn = 0x8; - break; - - default: - btn = 0x8; - break; - } - } - if(custom_btn_id == 4) { - switch(original_btn_num) { - case 0x1: - btn = klq_last_custom_btn; - break; - case 0x2: - btn = klq_last_custom_btn; - break; - case 0xA: - btn = 0x2; - break; - case 0x4: - btn = 0x2; - break; - case 0x8: - btn = klq_last_custom_btn; - break; - case 0xF: - btn = 0x2; - break; - - default: - btn = 0x2; - break; - } - } - if((custom_btn_id == 0) && (original_btn_num != 0)) { - btn = original_btn_num; - } // Generate new key - if(subghz_protocol_keeloq_gen_data(instance, btn, true)) { // OK } else { @@ -669,7 +579,7 @@ void* subghz_protocol_decoder_keeloq_alloc(SubGhzEnvironment* environment) { instance->keystore = subghz_environment_get_keystore(environment); instance->manufacture_from_file = furi_string_alloc(); - klq_prog_mode = 0; + klq_prog_mode = KEELOQ_PROG_MODE_OFF; return instance; } @@ -829,185 +739,19 @@ static uint8_t subghz_protocol_keeloq_check_remote_controller_selector( uint8_t btn = (uint8_t)(fix >> 28); uint32_t decrypt = 0; uint64_t man; - int res = 0; + bool mf_not_set = false; if(mfname == 0x0) { mfname = ""; } - if(strcmp(mfname, "") == 0) { - for - M_EACH(manufacture_code, *subghz_keystore_get_data(keystore), SubGhzKeyArray_t) { - switch(manufacture_code->type) { - case KEELOQ_LEARNING_SIMPLE: - // Simple Learning - decrypt = subghz_protocol_keeloq_common_decrypt(hop, manufacture_code->key); - if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { - *manufacture_name = furi_string_get_cstr(manufacture_code->name); - mfname = *manufacture_name; - return 1; - } - break; - case KEELOQ_LEARNING_NORMAL: - // Normal Learning - // https://phreakerclub.com/forum/showpost.php?p=43557&postcount=37 - man = subghz_protocol_keeloq_common_normal_learning(fix, manufacture_code->key); - decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); - if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { - *manufacture_name = furi_string_get_cstr(manufacture_code->name); - mfname = *manufacture_name; - return 1; - } - break; - case KEELOQ_LEARNING_SECURE: - man = subghz_protocol_keeloq_common_secure_learning( - fix, instance->seed, manufacture_code->key); - decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); - if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { - *manufacture_name = furi_string_get_cstr(manufacture_code->name); - mfname = *manufacture_name; - return 1; - } - break; - case KEELOQ_LEARNING_MAGIC_XOR_TYPE_1: - man = subghz_protocol_keeloq_common_magic_xor_type1_learning( - fix, manufacture_code->key); - decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); - if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { - *manufacture_name = furi_string_get_cstr(manufacture_code->name); - mfname = *manufacture_name; - return 1; - } - break; - case KEELOQ_LEARNING_MAGIC_SERIAL_TYPE_1: - man = subghz_protocol_keeloq_common_magic_serial_type1_learning( - fix, manufacture_code->key); - decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); - if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { - *manufacture_name = furi_string_get_cstr(manufacture_code->name); - mfname = *manufacture_name; - return 1; - } - break; - case KEELOQ_LEARNING_MAGIC_SERIAL_TYPE_2: - man = subghz_protocol_keeloq_common_magic_serial_type2_learning( - fix, manufacture_code->key); - decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); - if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { - *manufacture_name = furi_string_get_cstr(manufacture_code->name); - mfname = *manufacture_name; - return 1; - } - break; - case KEELOQ_LEARNING_MAGIC_SERIAL_TYPE_3: - man = subghz_protocol_keeloq_common_magic_serial_type3_learning( - fix, manufacture_code->key); - decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); - if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { - *manufacture_name = furi_string_get_cstr(manufacture_code->name); - mfname = *manufacture_name; - return 1; - } - break; - case KEELOQ_LEARNING_UNKNOWN: - // Simple Learning - decrypt = subghz_protocol_keeloq_common_decrypt(hop, manufacture_code->key); - if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { - *manufacture_name = furi_string_get_cstr(manufacture_code->name); - mfname = *manufacture_name; - kl_type = 1; - return 1; - } - - // Check for mirrored man - uint64_t man_rev = 0; - uint64_t man_rev_byte = 0; - for(uint8_t i = 0; i < 64; i += 8) { - man_rev_byte = (uint8_t)(manufacture_code->key >> i); - man_rev = man_rev | man_rev_byte << (56 - i); - } - - decrypt = subghz_protocol_keeloq_common_decrypt(hop, man_rev); - if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { - *manufacture_name = furi_string_get_cstr(manufacture_code->name); - mfname = *manufacture_name; - kl_type = 1; - return 1; - } - - //########################### - // Normal Learning - // https://phreakerclub.com/forum/showpost.php?p=43557&postcount=37 - man = subghz_protocol_keeloq_common_normal_learning(fix, manufacture_code->key); - decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); - if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { - *manufacture_name = furi_string_get_cstr(manufacture_code->name); - mfname = *manufacture_name; - kl_type = 2; - return 1; - } - - // Check for mirrored man - man = subghz_protocol_keeloq_common_normal_learning(fix, man_rev); - decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); - if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { - *manufacture_name = furi_string_get_cstr(manufacture_code->name); - mfname = *manufacture_name; - kl_type = 2; - return 1; - } - - // Secure Learning - man = subghz_protocol_keeloq_common_secure_learning( - fix, instance->seed, manufacture_code->key); - decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); - if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { - *manufacture_name = furi_string_get_cstr(manufacture_code->name); - mfname = *manufacture_name; - kl_type = 3; - return 1; - } - - // Check for mirrored man - man = subghz_protocol_keeloq_common_secure_learning(fix, instance->seed, man_rev); - decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); - if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { - *manufacture_name = furi_string_get_cstr(manufacture_code->name); - mfname = *manufacture_name; - kl_type = 3; - return 1; - } - - // Magic xor type1 learning - man = subghz_protocol_keeloq_common_magic_xor_type1_learning( - fix, manufacture_code->key); - decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); - if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { - *manufacture_name = furi_string_get_cstr(manufacture_code->name); - mfname = *manufacture_name; - kl_type = 4; - return 1; - } - - // Check for mirrored man - man = subghz_protocol_keeloq_common_magic_xor_type1_learning(fix, man_rev); - decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); - if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { - *manufacture_name = furi_string_get_cstr(manufacture_code->name); - mfname = *manufacture_name; - kl_type = 4; - return 1; - } - - break; - } - } - } else if(strcmp(mfname, "Unknown") == 0) { + if(strcmp(mfname, "Unknown") == 0) { return 1; - } else { + } else if(strcmp(mfname, "") == 0) { + mf_not_set = true; + } for M_EACH(manufacture_code, *subghz_keystore_get_data(keystore), SubGhzKeyArray_t) { - res = strcmp(furi_string_get_cstr(manufacture_code->name), mfname); - if(res == 0) { + if(mf_not_set || (strcmp(furi_string_get_cstr(manufacture_code->name), mfname) == 0)) { switch(manufacture_code->type) { case KEELOQ_LEARNING_SIMPLE: // Simple Learning @@ -1060,6 +804,26 @@ static uint8_t subghz_protocol_keeloq_check_remote_controller_selector( return 1; } break; + case KEELOQ_LEARNING_MAGIC_SERIAL_TYPE_2: + man = subghz_protocol_keeloq_common_magic_serial_type2_learning( + fix, manufacture_code->key); + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); + if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + mfname = *manufacture_name; + return 1; + } + break; + case KEELOQ_LEARNING_MAGIC_SERIAL_TYPE_3: + man = subghz_protocol_keeloq_common_magic_serial_type3_learning( + fix, manufacture_code->key); + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); + if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + mfname = *manufacture_name; + return 1; + } + break; case KEELOQ_LEARNING_UNKNOWN: // Simple Learning decrypt = subghz_protocol_keeloq_common_decrypt(hop, manufacture_code->key); @@ -1069,6 +833,7 @@ static uint8_t subghz_protocol_keeloq_check_remote_controller_selector( kl_type = 1; return 1; } + // Check for mirrored man uint64_t man_rev = 0; uint64_t man_rev_byte = 0; @@ -1076,6 +841,7 @@ static uint8_t subghz_protocol_keeloq_check_remote_controller_selector( man_rev_byte = (uint8_t)(manufacture_code->key >> i); man_rev = man_rev | man_rev_byte << (56 - i); } + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man_rev); if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { *manufacture_name = furi_string_get_cstr(manufacture_code->name); @@ -1083,6 +849,7 @@ static uint8_t subghz_protocol_keeloq_check_remote_controller_selector( kl_type = 1; return 1; } + //########################### // Normal Learning // https://phreakerclub.com/forum/showpost.php?p=43557&postcount=37 @@ -1153,7 +920,6 @@ static uint8_t subghz_protocol_keeloq_check_remote_controller_selector( } } } - } *manufacture_name = "Unknown"; mfname = "Unknown"; @@ -1170,8 +936,8 @@ static void subghz_protocol_keeloq_check_remote_controller( uint32_t key_fix = key >> 32; uint32_t key_hop = key & 0x00000000ffffffff; - // If we are in BFT programming mode we will set previous remembered counter and skip mf keys check - if(klq_prog_mode == 0) { + // If we are in BFT / Aprimatic programming mode we will set previous remembered counter and skip mf keys check + if(klq_prog_mode == KEELOQ_PROG_MODE_OFF) { // Check key AN-Motors if((key_hop >> 24) == ((key_hop >> 16) & 0x00ff) && (key_fix >> 28) == ((key_hop >> 12) & 0x0f) && (key_hop & 0xFFF) == 0x404) { @@ -1188,11 +954,11 @@ static void subghz_protocol_keeloq_check_remote_controller( } temp_counter = instance->cnt; - } else if(klq_prog_mode == 1) { + } else if(klq_prog_mode == KEELOQ_PROG_MODE_BFT) { *manufacture_name = "BFT"; mfname = *manufacture_name; instance->cnt = temp_counter; - } else if(klq_prog_mode == 2) { + } else if(klq_prog_mode == KEELOQ_PROG_MODE_APRIMATIC) { *manufacture_name = "Aprimatic"; mfname = *manufacture_name; instance->cnt = temp_counter; @@ -1302,6 +1068,125 @@ SubGhzProtocolStatus return res; } +static uint8_t subghz_protocol_keeloq_get_btn_code(uint8_t last_btn_code) { + uint8_t custom_btn_id = subghz_custom_btn_get(); + uint8_t original_btn_code = subghz_custom_btn_get_original(); + uint8_t btn = original_btn_code; + + if(last_btn_code == 0) { + last_btn_code = 0xA; + } + + // Set custom button + // Basic set | 0x1 | 0x2 | 0x4 | 0x8 | 0xA or Special Learning Code | + if((custom_btn_id == SUBGHZ_CUSTOM_BTN_OK) && (original_btn_code != 0)) { + // Restore original button code + btn = original_btn_code; + } else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_UP) { + switch(original_btn_code) { + case 0x1: + btn = 0x2; + break; + case 0x2: + btn = 0x1; + break; + case 0xA: + btn = 0x1; + break; + case 0x4: + btn = 0x1; + break; + case 0x8: + btn = 0x1; + break; + case 0xF: + btn = 0x1; + break; + + default: + btn = 0x1; + break; + } + } else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_DOWN) { + switch(original_btn_code) { + case 0x1: + btn = 0x4; + break; + case 0x2: + btn = 0x4; + break; + case 0xA: + btn = 0x4; + break; + case 0x4: + btn = last_btn_code; + break; + case 0x8: + btn = 0x4; + break; + case 0xF: + btn = 0x4; + break; + + default: + btn = 0x4; + break; + } + } else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_LEFT) { + switch(original_btn_code) { + case 0x1: + btn = 0x8; + break; + case 0x2: + btn = 0x8; + break; + case 0xA: + btn = 0x8; + break; + case 0x4: + btn = 0x8; + break; + case 0x8: + btn = 0x2; + break; + case 0xF: + btn = 0x8; + break; + + default: + btn = 0x8; + break; + } + } else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_RIGHT) { + switch(original_btn_code) { + case 0x1: + btn = last_btn_code; + break; + case 0x2: + btn = last_btn_code; + break; + case 0xA: + btn = 0x2; + break; + case 0x4: + btn = 0x2; + break; + case 0x8: + btn = last_btn_code; + break; + case 0xF: + btn = 0x2; + break; + + default: + btn = 0x2; + break; + } + } + + return btn; +} + void subghz_protocol_decoder_keeloq_get_string(void* context, FuriString* output) { furi_assert(context); SubGhzProtocolDecoderKeeloq* instance = context; diff --git a/lib/subghz/protocols/nice_flor_s.c b/lib/subghz/protocols/nice_flor_s.c index 5d8e7f587..5eaa21274 100644 --- a/lib/subghz/protocols/nice_flor_s.c +++ b/lib/subghz/protocols/nice_flor_s.c @@ -117,6 +117,13 @@ void subghz_protocol_encoder_nice_flor_s_free(void* context) { static void subghz_protocol_nice_one_get_data(uint8_t* p, uint8_t num_parcel, uint8_t hold_bit); +/** + * Defines the button value for the current btn_id + * Basic set | 0x1 | 0x2 | 0x4 | 0x8 | + * @return Button code + */ +static uint8_t subghz_protocol_nice_flor_s_get_btn_code(); + /** * Generating an upload from data. * @param instance Pointer to a SubGhzProtocolEncoderNiceFlorS instance @@ -135,71 +142,7 @@ static void subghz_protocol_encoder_nice_flor_s_get_upload( subghz_custom_btn_set_original(btn); } - uint8_t custom_btn_id = subghz_custom_btn_get(); - uint8_t original_btn_num = subghz_custom_btn_get_original(); - - // Set custom button - if(custom_btn_id == 1) { - switch(original_btn_num) { - case 0x1: - btn = 0x2; - break; - case 0x2: - btn = 0x1; - break; - case 0x4: - btn = 0x1; - break; - case 0x8: - btn = 0x1; - break; - - default: - break; - } - } - if(custom_btn_id == 2) { - switch(original_btn_num) { - case 0x1: - btn = 0x4; - break; - case 0x2: - btn = 0x4; - break; - case 0x4: - btn = 0x2; - break; - case 0x8: - btn = 0x4; - break; - - default: - break; - } - } - if(custom_btn_id == 3) { - switch(original_btn_num) { - case 0x1: - btn = 0x8; - break; - case 0x2: - btn = 0x8; - break; - case 0x4: - btn = 0x8; - break; - case 0x8: - btn = 0x2; - break; - - default: - break; - } - } - - if((custom_btn_id == 0) && (original_btn_num != 0)) { - btn = original_btn_num; - } + btn = subghz_protocol_nice_flor_s_get_btn_code(); size_t size_upload = ((instance->generic.data_count_bit * 2) + ((37 + 2 + 2) * 2) * 16); if(size_upload > instance->encoder.size_upload) { @@ -808,6 +751,74 @@ SubGhzProtocolStatus return ret; } +static uint8_t subghz_protocol_nice_flor_s_get_btn_code() { + uint8_t custom_btn_id = subghz_custom_btn_get(); + uint8_t original_btn_code = subghz_custom_btn_get_original(); + uint8_t btn = original_btn_code; + + // Set custom button + if((custom_btn_id == SUBGHZ_CUSTOM_BTN_OK) && (original_btn_code != 0)) { + // Restore original button code + btn = original_btn_code; + } else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_UP) { + switch(original_btn_code) { + case 0x1: + btn = 0x2; + break; + case 0x2: + btn = 0x1; + break; + case 0x4: + btn = 0x1; + break; + case 0x8: + btn = 0x1; + break; + + default: + break; + } + } else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_DOWN) { + switch(original_btn_code) { + case 0x1: + btn = 0x4; + break; + case 0x2: + btn = 0x4; + break; + case 0x4: + btn = 0x2; + break; + case 0x8: + btn = 0x4; + break; + + default: + break; + } + } else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_LEFT) { + switch(original_btn_code) { + case 0x1: + btn = 0x8; + break; + case 0x2: + btn = 0x8; + break; + case 0x4: + btn = 0x8; + break; + case 0x8: + btn = 0x2; + break; + + default: + break; + } + } + + return btn; +} + void subghz_protocol_decoder_nice_flor_s_get_string(void* context, FuriString* output) { furi_assert(context); SubGhzProtocolDecoderNiceFlorS* instance = context; diff --git a/lib/subghz/protocols/secplus_v2.c b/lib/subghz/protocols/secplus_v2.c index 5d0ecb0dd..d39f794ab 100644 --- a/lib/subghz/protocols/secplus_v2.c +++ b/lib/subghz/protocols/secplus_v2.c @@ -375,6 +375,13 @@ static uint64_t subghz_protocol_secplus_v2_encode_half(uint8_t roll_array[], uin return data; } +/** + * Defines the button value for the current btn_id + * Basic set | 0x68 | 0x80 | 0x81 | 0xE2 | + * @return Button code + */ +static uint8_t subghz_protocol_secplus_v2_get_btn_code(); + /** * Security+ 2.0 message encoding * @param instance SubGhzProtocolEncoderSecPlus_v2* @@ -386,70 +393,8 @@ static void subghz_protocol_secplus_v2_encode(SubGhzProtocolEncoderSecPlus_v2* i subghz_custom_btn_set_original(instance->generic.btn); } - uint8_t custom_btn_id = subghz_custom_btn_get(); - uint8_t original_btn_num = subghz_custom_btn_get_original(); + instance->generic.btn = subghz_protocol_secplus_v2_get_btn_code(); - // Set custom button - if(custom_btn_id == 1) { - switch(original_btn_num) { - case 0x68: - instance->generic.btn = 0x80; - break; - case 0x80: - instance->generic.btn = 0x68; - break; - case 0x81: - instance->generic.btn = 0x80; - break; - case 0xE2: - instance->generic.btn = 0x80; - break; - - default: - break; - } - } - if(custom_btn_id == 2) { - switch(original_btn_num) { - case 0x68: - instance->generic.btn = 0x81; - break; - case 0x80: - instance->generic.btn = 0x81; - break; - case 0x81: - instance->generic.btn = 0x68; - break; - case 0xE2: - instance->generic.btn = 0x81; - break; - - default: - break; - } - } - if(custom_btn_id == 3) { - switch(original_btn_num) { - case 0x68: - instance->generic.btn = 0xE2; - break; - case 0x80: - instance->generic.btn = 0xE2; - break; - case 0x81: - instance->generic.btn = 0xE2; - break; - case 0xE2: - instance->generic.btn = 0x68; - break; - - default: - break; - } - } - if((custom_btn_id == 0) && (original_btn_num != 0)) { - instance->generic.btn = original_btn_num; - } uint32_t fixed_1[1] = {instance->generic.btn << 12 | instance->generic.serial >> 20}; uint32_t fixed_2[1] = {instance->generic.serial & 0xFFFFF}; uint8_t rolling_digits[18] = {0}; @@ -888,6 +833,74 @@ SubGhzProtocolStatus return ret; } +static uint8_t subghz_protocol_secplus_v2_get_btn_code() { + uint8_t custom_btn_id = subghz_custom_btn_get(); + uint8_t original_btn_code = subghz_custom_btn_get_original(); + uint8_t btn = original_btn_code; + + // Set custom button + if((custom_btn_id == SUBGHZ_CUSTOM_BTN_OK) && (original_btn_code != 0)) { + // Restore original button code + btn = original_btn_code; + } else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_UP) { + switch(original_btn_code) { + case 0x68: + btn = 0x80; + break; + case 0x80: + btn = 0x68; + break; + case 0x81: + btn = 0x80; + break; + case 0xE2: + btn = 0x80; + break; + + default: + break; + } + } else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_DOWN) { + switch(original_btn_code) { + case 0x68: + btn = 0x81; + break; + case 0x80: + btn = 0x81; + break; + case 0x81: + btn = 0x68; + break; + case 0xE2: + btn = 0x81; + break; + + default: + break; + } + } else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_LEFT) { + switch(original_btn_code) { + case 0x68: + btn = 0xE2; + break; + case 0x80: + btn = 0xE2; + break; + case 0x81: + btn = 0xE2; + break; + case 0xE2: + btn = 0x68; + break; + + default: + break; + } + } + + return btn; +} + void subghz_protocol_decoder_secplus_v2_get_string(void* context, FuriString* output) { furi_assert(context); SubGhzProtocolDecoderSecPlus_v2* instance = context; diff --git a/lib/subghz/protocols/somfy_telis.c b/lib/subghz/protocols/somfy_telis.c index 57c6c510b..6a159a7ea 100644 --- a/lib/subghz/protocols/somfy_telis.c +++ b/lib/subghz/protocols/somfy_telis.c @@ -97,6 +97,13 @@ void subghz_protocol_encoder_somfy_telis_free(void* context) { free(instance); } +/** + * Defines the button value for the current btn_id + * Basic set | 0x1 | 0x2 | 0x4 | 0x8 | + * @return Button code + */ +static uint8_t subghz_protocol_somfy_telis_get_btn_code(); + static bool subghz_protocol_somfy_telis_gen_data( SubGhzProtocolEncoderSomfyTelis* instance, uint8_t btn, @@ -115,71 +122,7 @@ static bool subghz_protocol_somfy_telis_gen_data( subghz_custom_btn_set_original(btn); } - uint8_t custom_btn_id = subghz_custom_btn_get(); - uint8_t original_btn_num = subghz_custom_btn_get_original(); - - // Set custom button - if(custom_btn_id == 1) { - switch(original_btn_num) { - case 0x1: - btn = 0x2; - break; - case 0x2: - btn = 0x1; - break; - case 0x4: - btn = 0x1; - break; - case 0x8: - btn = 0x1; - break; - - default: - break; - } - } - if(custom_btn_id == 2) { - switch(original_btn_num) { - case 0x1: - btn = 0x4; - break; - case 0x2: - btn = 0x4; - break; - case 0x4: - btn = 0x2; - break; - case 0x8: - btn = 0x4; - break; - - default: - break; - } - } - if(custom_btn_id == 3) { - switch(original_btn_num) { - case 0x1: - btn = 0x8; - break; - case 0x2: - btn = 0x8; - break; - case 0x4: - btn = 0x8; - break; - case 0x8: - btn = 0x2; - break; - - default: - break; - } - } - - if((custom_btn_id == 0) && (original_btn_num != 0)) { - btn = original_btn_num; - } + btn = subghz_protocol_somfy_telis_get_btn_code(); if(instance->generic.cnt < 0xFFFF) { if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) >= 0xFFFF) { @@ -725,6 +668,74 @@ SubGhzProtocolStatus subghz_protocol_somfy_telis_const.min_count_bit_for_found); } +static uint8_t subghz_protocol_somfy_telis_get_btn_code() { + uint8_t custom_btn_id = subghz_custom_btn_get(); + uint8_t original_btn_code = subghz_custom_btn_get_original(); + uint8_t btn = original_btn_code; + + // Set custom button + if((custom_btn_id == SUBGHZ_CUSTOM_BTN_OK) && (original_btn_code != 0)) { + // Restore original button code + btn = original_btn_code; + } else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_UP) { + switch(original_btn_code) { + case 0x1: + btn = 0x2; + break; + case 0x2: + btn = 0x1; + break; + case 0x4: + btn = 0x1; + break; + case 0x8: + btn = 0x1; + break; + + default: + break; + } + } else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_DOWN) { + switch(original_btn_code) { + case 0x1: + btn = 0x4; + break; + case 0x2: + btn = 0x4; + break; + case 0x4: + btn = 0x2; + break; + case 0x8: + btn = 0x4; + break; + + default: + break; + } + } else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_LEFT) { + switch(original_btn_code) { + case 0x1: + btn = 0x8; + break; + case 0x2: + btn = 0x8; + break; + case 0x4: + btn = 0x8; + break; + case 0x8: + btn = 0x2; + break; + + default: + break; + } + } + + return btn; +} + void subghz_protocol_decoder_somfy_telis_get_string(void* context, FuriString* output) { furi_assert(context); SubGhzProtocolDecoderSomfyTelis* instance = context; diff --git a/lib/subghz/protocols/star_line.c b/lib/subghz/protocols/star_line.c index cb9b26760..362e34f3c 100644 --- a/lib/subghz/protocols/star_line.c +++ b/lib/subghz/protocols/star_line.c @@ -163,36 +163,41 @@ static bool instance->generic.data, instance->generic.data_count_bit); hop = code_found_reverse & 0x00000000ffffffff; } else { - for - M_EACH(manufacture_code, *subghz_keystore_get_data(instance->keystore), SubGhzKeyArray_t) { - res = strcmp(furi_string_get_cstr(manufacture_code->name), instance->manufacture_name); - if(res == 0) { - switch(manufacture_code->type) { - case KEELOQ_LEARNING_SIMPLE: - //Simple Learning - hop = subghz_protocol_keeloq_common_encrypt(decrypt, manufacture_code->key); - break; - case KEELOQ_LEARNING_NORMAL: - //Normal Learning - man = - subghz_protocol_keeloq_common_normal_learning(fix, manufacture_code->key); - hop = subghz_protocol_keeloq_common_encrypt(decrypt, man); - break; - case KEELOQ_LEARNING_UNKNOWN: - if(kl_type == 1) { + for + M_EACH( + manufacture_code, + *subghz_keystore_get_data(instance->keystore), + SubGhzKeyArray_t) { + res = strcmp( + furi_string_get_cstr(manufacture_code->name), instance->manufacture_name); + if(res == 0) { + switch(manufacture_code->type) { + case KEELOQ_LEARNING_SIMPLE: + //Simple Learning hop = subghz_protocol_keeloq_common_encrypt(decrypt, manufacture_code->key); - } - if(kl_type == 2) { + break; + case KEELOQ_LEARNING_NORMAL: + //Normal Learning man = subghz_protocol_keeloq_common_normal_learning( fix, manufacture_code->key); hop = subghz_protocol_keeloq_common_encrypt(decrypt, man); + break; + case KEELOQ_LEARNING_UNKNOWN: + if(kl_type == 1) { + hop = subghz_protocol_keeloq_common_encrypt( + decrypt, manufacture_code->key); + } + if(kl_type == 2) { + man = subghz_protocol_keeloq_common_normal_learning( + fix, manufacture_code->key); + hop = subghz_protocol_keeloq_common_encrypt(decrypt, man); + } + break; } break; } - break; } - } } if(hop) { uint64_t yek = (uint64_t)fix << 32 | hop; @@ -517,90 +522,19 @@ static uint8_t subghz_protocol_star_line_check_remote_controller_selector( uint8_t btn = (uint8_t)(fix >> 24); uint32_t decrypt = 0; uint64_t man_normal_learning; - int res = 0; + bool mf_not_set = false; if(mfname == 0x0) { mfname = ""; } - if(strcmp(mfname, "") == 0) { - for - M_EACH(manufacture_code, *subghz_keystore_get_data(keystore), SubGhzKeyArray_t) { - switch(manufacture_code->type) { - case KEELOQ_LEARNING_SIMPLE: - // Simple Learning - decrypt = subghz_protocol_keeloq_common_decrypt(hop, manufacture_code->key); - if(subghz_protocol_star_line_check_decrypt(instance, decrypt, btn, end_serial)) { - *manufacture_name = furi_string_get_cstr(manufacture_code->name); - mfname = *manufacture_name; - return 1; - } - break; - case KEELOQ_LEARNING_NORMAL: - // Normal Learning - // https://phreakerclub.com/forum/showpost.php?p=43557&postcount=37 - man_normal_learning = - subghz_protocol_keeloq_common_normal_learning(fix, manufacture_code->key); - decrypt = subghz_protocol_keeloq_common_decrypt(hop, man_normal_learning); - if(subghz_protocol_star_line_check_decrypt(instance, decrypt, btn, end_serial)) { - *manufacture_name = furi_string_get_cstr(manufacture_code->name); - mfname = *manufacture_name; - return 1; - } - break; - case KEELOQ_LEARNING_UNKNOWN: - // Simple Learning - decrypt = subghz_protocol_keeloq_common_decrypt(hop, manufacture_code->key); - if(subghz_protocol_star_line_check_decrypt(instance, decrypt, btn, end_serial)) { - *manufacture_name = furi_string_get_cstr(manufacture_code->name); - mfname = *manufacture_name; - kl_type = 1; - return 1; - } - // Check for mirrored man - uint64_t man_rev = 0; - uint64_t man_rev_byte = 0; - for(uint8_t i = 0; i < 64; i += 8) { - man_rev_byte = (uint8_t)(manufacture_code->key >> i); - man_rev = man_rev | man_rev_byte << (56 - i); - } - decrypt = subghz_protocol_keeloq_common_decrypt(hop, man_rev); - if(subghz_protocol_star_line_check_decrypt(instance, decrypt, btn, end_serial)) { - *manufacture_name = furi_string_get_cstr(manufacture_code->name); - mfname = *manufacture_name; - kl_type = 1; - return 1; - } - //########################### - // Normal Learning - // https://phreakerclub.com/forum/showpost.php?p=43557&postcount=37 - man_normal_learning = - subghz_protocol_keeloq_common_normal_learning(fix, manufacture_code->key); - decrypt = subghz_protocol_keeloq_common_decrypt(hop, man_normal_learning); - if(subghz_protocol_star_line_check_decrypt(instance, decrypt, btn, end_serial)) { - *manufacture_name = furi_string_get_cstr(manufacture_code->name); - mfname = *manufacture_name; - kl_type = 2; - return 1; - } - // Check for mirrored man - man_normal_learning = subghz_protocol_keeloq_common_normal_learning(fix, man_rev); - decrypt = subghz_protocol_keeloq_common_decrypt(hop, man_normal_learning); - if(subghz_protocol_star_line_check_decrypt(instance, decrypt, btn, end_serial)) { - *manufacture_name = furi_string_get_cstr(manufacture_code->name); - mfname = *manufacture_name; - kl_type = 2; - return 1; - } - break; - } - } - } else if(strcmp(mfname, "Unknown") == 0) { + if(strcmp(mfname, "Unknown") == 0) { return 1; - } else { + } else if(strcmp(mfname, "") == 0) { + mf_not_set = true; + } for M_EACH(manufacture_code, *subghz_keystore_get_data(keystore), SubGhzKeyArray_t) { - res = strcmp(furi_string_get_cstr(manufacture_code->name), mfname); - if(res == 0) { + if(mf_not_set || (strcmp(furi_string_get_cstr(manufacture_code->name), mfname) == 0)) { switch(manufacture_code->type) { case KEELOQ_LEARNING_SIMPLE: // Simple Learning @@ -663,7 +597,6 @@ static uint8_t subghz_protocol_star_line_check_remote_controller_selector( kl_type = 2; return 1; } - // Check for mirrored man man_normal_learning = subghz_protocol_keeloq_common_normal_learning(fix, man_rev); @@ -679,7 +612,6 @@ static uint8_t subghz_protocol_star_line_check_remote_controller_selector( } } } - } *manufacture_name = "Unknown"; mfname = "Unknown"; diff --git a/lib/toolbox/version.c b/lib/toolbox/version.c index e0a6e6197..70386d96d 100644 --- a/lib/toolbox/version.c +++ b/lib/toolbox/version.c @@ -16,26 +16,21 @@ struct Version { const char* git_hash; const char* git_branch; const char* build_date; - const char* custom_flipper_name; const char* version; // Payload bits and pieces const uint8_t target; const bool build_is_dirty; + const char* custom_flipper_name; }; /* version of current running firmware (bootloader/flipper) */ -static const Version version = { +static Version version = { .magic = VERSION_MAGIC, .major = VERSION_MAJOR, .minor = VERSION_MINOR, .git_hash = GIT_COMMIT, .git_branch = GIT_BRANCH, .build_date = BUILD_DATE, -#ifdef FURI_CUSTOM_FLIPPER_NAME - .custom_flipper_name = FURI_CUSTOM_FLIPPER_NAME, -#else - .custom_flipper_name = NULL, -#endif .version = VERSION #ifdef FURI_RAM_EXEC " (RAM)" @@ -43,6 +38,7 @@ static const Version version = { , .target = TARGET, .build_is_dirty = BUILD_DIRTY, + .custom_flipper_name = NULL, }; const Version* version_get(void) { @@ -74,6 +70,12 @@ const char* version_get_custom_name(const Version* v) { return v ? v->custom_flipper_name : version.custom_flipper_name; } +void version_set_custom_name(Version* v, const char* name) { + Version* ver = v ? v : &version; + ver->custom_flipper_name = name; + return; +} + uint8_t version_get_target(const Version* v) { return v ? v->target : version.target; } diff --git a/lib/toolbox/version.h b/lib/toolbox/version.h index ed54631b3..2aa72bc28 100644 --- a/lib/toolbox/version.h +++ b/lib/toolbox/version.h @@ -65,7 +65,7 @@ const char* version_get_builddate(const Version* v); */ const char* version_get_version(const Version* v); -/** Get custom flipper name if set in ENV +/** Get custom flipper name if set in version_set_custom_name * * @param v pointer to Version data. NULL for currently running * software. @@ -74,6 +74,14 @@ const char* version_get_version(const Version* v); */ const char* version_get_custom_name(const Version* v); +/** Set custom flipper name + * + * @param v pointer to Version data. NULL for currently running + * software. + * @param name Custom name or NULL + */ +void version_set_custom_name(Version* v, const char* name); + /** Get hardware target this firmware was built for * * @param v pointer to Version data. NULL for currently running diff --git a/scripts/debug/flipperapps.py b/scripts/debug/flipperapps.py index 90582c1e4..608c30412 100644 --- a/scripts/debug/flipperapps.py +++ b/scripts/debug/flipperapps.py @@ -196,7 +196,10 @@ class FlipperAppStateHelper: self.set_debug_mode(False) def set_debug_mode(self, mode: bool) -> None: - gdb.execute(f"set variable furi_hal_debug_gdb_session_active = {int(mode)}") + try: + gdb.execute(f"set variable furi_hal_debug_gdb_session_active = {int(mode)}") + except gdb.error as e: + print(f"Failed to set debug mode: {e}") # Init additional 'fap-set-debug-elf-root' command and set up hooks diff --git a/scripts/fbt/appmanifest.py b/scripts/fbt/appmanifest.py index 5e41f4c0e..ed1654e36 100644 --- a/scripts/fbt/appmanifest.py +++ b/scripts/fbt/appmanifest.py @@ -56,7 +56,7 @@ class FlipperApplication: # .fap-specific sources: List[str] = field(default_factory=lambda: ["*.c*"]) - fap_version: Tuple[int] = field(default_factory=lambda: (0, 1)) + fap_version: str | Tuple[int] = "0.1" fap_icon: Optional[str] = None fap_libs: List[str] = field(default_factory=list) fap_category: str = "" @@ -84,6 +84,13 @@ class FlipperApplication: def __post_init__(self): if self.apptype == FlipperAppType.PLUGIN: self.stack_size = 0 + if isinstance(self.fap_version, str): + try: + self.fap_version = tuple(int(v) for v in self.fap_version.split(".")) + except ValueError: + raise FlipperManifestException( + f"Invalid version string '{self.fap_version}'. Must be in the form 'major.minor'" + ) class AppManager: diff --git a/scripts/sconsdist.py b/scripts/sconsdist.py index 0f0b22082..a3b466898 100644 --- a/scripts/sconsdist.py +++ b/scripts/sconsdist.py @@ -250,13 +250,6 @@ class Main(App): ) bundle_args.extend(self.other_args) - log_custom_fz_name = ( - environ.get("CUSTOM_FLIPPER_NAME", None) - or "" - ) - if (log_custom_fz_name != "") and (len(log_custom_fz_name) <= 8) and (log_custom_fz_name.isalnum()) and (log_custom_fz_name.isascii()): - self.logger.info(f"Flipper Custom Name is set:\n\tName: {log_custom_fz_name} : length - {len(log_custom_fz_name)} chars") - if (bundle_result := UpdateMain(no_exit=True)(bundle_args)) == 0: self.note_dist_component("update", "dir", bundle_dir) self.logger.info( diff --git a/scripts/ufbt/project_template/app_template/application.fam b/scripts/ufbt/project_template/app_template/application.fam index 37a4ce665..a2d23ef46 100644 --- a/scripts/ufbt/project_template/app_template/application.fam +++ b/scripts/ufbt/project_template/app_template/application.fam @@ -8,7 +8,7 @@ App( stack_size=2 * 1024, fap_category="Examples", # Optional values - # fap_version=(0, 1), # (major, minor) + # fap_version="0.1", fap_icon="@FBT_APPID@.png", # 10x10 1-bit PNG # fap_description="A simple app", # fap_author="J. Doe", diff --git a/scripts/version.py b/scripts/version.py index 27772e9c2..149c5d99e 100644 --- a/scripts/version.py +++ b/scripts/version.py @@ -40,11 +40,6 @@ class GitVersion: or "unknown" ) - custom_fz_name = ( - os.environ.get("CUSTOM_FLIPPER_NAME", None) - or "" - ) - force_no_dirty = ( os.environ.get("FORCE_NO_DIRTY", None) or "" @@ -52,21 +47,12 @@ class GitVersion: if (force_no_dirty != ""): dirty = False - if (custom_fz_name != "") and (len(custom_fz_name) <= 8) and (custom_fz_name.isalnum()) and (custom_fz_name.isascii()): - return { - "GIT_COMMIT": commit, - "GIT_BRANCH": branch, - "FURI_CUSTOM_FLIPPER_NAME": custom_fz_name, - "VERSION": version, - "BUILD_DIRTY": dirty and 1 or 0, - } - else: - return { - "GIT_COMMIT": commit, - "GIT_BRANCH": branch, - "VERSION": version, - "BUILD_DIRTY": dirty and 1 or 0, - } + return { + "GIT_COMMIT": commit, + "GIT_BRANCH": branch, + "VERSION": version, + "BUILD_DIRTY": dirty and 1 or 0, + } def _exec_git(self, args): cmd = ["git"] diff --git a/site_scons/commandline.scons b/site_scons/commandline.scons index 84ef6ce19..54bcbc568 100644 --- a/site_scons/commandline.scons +++ b/site_scons/commandline.scons @@ -100,11 +100,6 @@ vars.AddVariables( "Version string for updater package", "${DIST_SUFFIX}", ), - ( - "CUSTOM_FLIPPER_NAME", - "Replaces OTP flipper name with custom string of 8 chars", - "", - ), ( "FORCE_NO_DIRTY", "Force disable dirty status of the build", diff --git a/site_scons/environ.scons b/site_scons/environ.scons index 0ffc8a6a2..0cdc45557 100644 --- a/site_scons/environ.scons +++ b/site_scons/environ.scons @@ -20,7 +20,6 @@ variables_to_forward = [ # CI/CD variables "WORKFLOW_BRANCH_OR_TAG", "DIST_SUFFIX", - "CUSTOM_FLIPPER_NAME", "FORCE_NO_DIRTY", # Python & other tools "HOME",