From 53435579b3c357a1f915dd5e3f0822da65ba59cd Mon Sep 17 00:00:00 2001 From: hedger Date: Tue, 14 Mar 2023 18:29:28 +0400 Subject: [PATCH 01/32] [FL-3097] fbt, faploader: minimal app module implementation (#2420) * fbt, faploader: minimal app module implementation * faploader, libs: moved API hashtable core to flipper_application * example: compound api * lib: flipper_application: naming fixes, doxygen comments * fbt: changed `requires` manifest field behavior for app extensions * examples: refactored plugin apps; faploader: changed new API naming; fbt: changed PLUGIN app type meaning * loader: dropped support for debug apps & plugin menus * moved applications/plugins -> applications/external * Restored x bit on chiplist_convert.py * git: fixed free-dap submodule path * pvs: updated submodule paths * examples: example_advanced_plugins.c: removed potential memory leak on errors * examples: example_plugins: refined requires * fbt: not deploying app modules for debug/sample apps; extra validation for .PLUGIN-type apps * apps: removed cdefines for external apps * fbt: moved ext app path definition * fbt: reworked fap_dist handling; f18: synced api_symbols.csv * fbt: removed resources_paths for extapps * scripts: reworked storage * scripts: reworked runfap.py & selfupdate.py to use new api * wip: fal runner * fbt: moved file packaging into separate module * scripts: storage: fixes * scripts: storage: minor fixes for new api * fbt: changed internal artifact storage details for external apps * scripts: storage: additional fixes and better error reporting; examples: using APP_DATA_PATH() * fbt, scripts: reworked launch_app to deploy plugins; moved old runfap.py to distfap.py * fbt: extra check for plugins descriptors * fbt: additional checks in emitter * fbt: better info message on SDK rebuild * scripts: removed requirements.txt * loader: removed remnants of plugins & debug menus * post-review fixes --- .github/CODEOWNERS | 4 +- .gitmodules | 4 +- .pvsoptions | 2 +- SConstruct | 27 +- applications/examples/application.fam | 1 + .../examples/example_plugins/application.fam | 31 ++ .../example_plugins/example_plugins.c | 70 +++ .../example_plugins/example_plugins_multi.c | 43 ++ .../examples/example_plugins/plugin1.c | 32 ++ .../examples/example_plugins/plugin2.c | 32 ++ .../example_plugins/plugin_interface.h | 12 + .../example_plugins_advanced/app_api.c | 25 ++ .../example_plugins_advanced/app_api.h | 25 ++ .../app_api_interface.h | 9 + .../app_api_table.cpp | 27 ++ .../app_api_table_i.h | 13 + .../example_plugins_advanced/application.fam | 24 + .../example_advanced_plugins.c | 48 ++ .../example_plugins_advanced/plugin1.c | 40 ++ .../example_plugins_advanced/plugin2.c | 40 ++ .../plugin_interface.h | 12 + applications/external/application.fam | 6 + .../clock/application.fam | 2 +- .../{plugins => external}/clock/clock.png | Bin .../{plugins => external}/clock/clock_app.c | 0 .../{plugins => external}/dap_link/README.md | 0 .../dap_link/application.fam | 2 +- .../dap_link/dap_config.h | 0 .../{plugins => external}/dap_link/dap_link.c | 0 .../{plugins => external}/dap_link/dap_link.h | 0 .../dap_link/dap_link.png | Bin .../dap_link/gui/dap_gui.c | 0 .../dap_link/gui/dap_gui.h | 0 .../dap_link/gui/dap_gui_custom_event.h | 0 .../dap_link/gui/dap_gui_i.h | 0 .../dap_link/gui/scenes/config/dap_scene.c | 0 .../dap_link/gui/scenes/config/dap_scene.h | 0 .../gui/scenes/config/dap_scene_config.h | 0 .../dap_link/gui/scenes/dap_scene_about.c | 0 .../dap_link/gui/scenes/dap_scene_config.c | 0 .../dap_link/gui/scenes/dap_scene_help.c | 0 .../dap_link/gui/scenes/dap_scene_main.c | 0 .../dap_link/gui/views/dap_main_view.c | 0 .../dap_link/gui/views/dap_main_view.h | 0 .../dap_link/icons/ActiveConnection_50x64.png | Bin .../dap_link/icons/ArrowDownEmpty_12x18.png | Bin .../dap_link/icons/ArrowDownFilled_12x18.png | Bin .../dap_link/icons/ArrowUpEmpty_12x18.png | Bin .../dap_link/icons/ArrowUpFilled_12x18.png | Bin .../dap_link/lib/free-dap | 0 .../dap_link/usb/dap_v2_usb.c | 0 .../dap_link/usb/dap_v2_usb.h | 0 .../dap_link/usb/usb_winusb.h | 0 .../hid_app/application.fam | 4 +- .../hid_app/assets/Arr_dwn_7x9.png | Bin .../hid_app/assets/Arr_up_7x9.png | Bin .../hid_app/assets/Ble_connected_15x15.png | Bin .../hid_app/assets/Ble_disconnected_15x15.png | Bin .../hid_app/assets/ButtonDown_7x4.png | Bin .../hid_app/assets/ButtonF10_5x8.png | Bin .../hid_app/assets/ButtonF11_5x8.png | Bin .../hid_app/assets/ButtonF12_5x8.png | Bin .../hid_app/assets/ButtonF1_5x8.png | Bin .../hid_app/assets/ButtonF2_5x8.png | Bin .../hid_app/assets/ButtonF3_5x8.png | Bin .../hid_app/assets/ButtonF4_5x8.png | Bin .../hid_app/assets/ButtonF5_5x8.png | Bin .../hid_app/assets/ButtonF6_5x8.png | Bin .../hid_app/assets/ButtonF7_5x8.png | Bin .../hid_app/assets/ButtonF8_5x8.png | Bin .../hid_app/assets/ButtonF9_5x8.png | Bin .../hid_app/assets/ButtonLeft_4x7.png | Bin .../hid_app/assets/ButtonRight_4x7.png | Bin .../hid_app/assets/ButtonUp_7x4.png | Bin .../hid_app/assets/Button_18x18.png | Bin .../hid_app/assets/Circles_47x47.png | Bin .../hid_app/assets/Left_mouse_icon_9x9.png | Bin .../hid_app/assets/Like_def_11x9.png | Bin .../hid_app/assets/Like_pressed_17x17.png | Bin .../hid_app/assets/Ok_btn_9x9.png | Bin .../hid_app/assets/Ok_btn_pressed_13x13.png | Bin .../hid_app/assets/Pin_arrow_down_7x9.png | Bin .../hid_app/assets/Pin_arrow_left_9x7.png | Bin .../hid_app/assets/Pin_arrow_right_9x7.png | Bin .../hid_app/assets/Pin_arrow_up_7x9.png | Bin .../hid_app/assets/Pin_back_arrow_10x8.png | Bin .../hid_app/assets/Pressed_Button_13x13.png | Bin .../hid_app/assets/Right_mouse_icon_9x9.png | Bin .../hid_app/assets/Space_65x18.png | Bin .../hid_app/assets/Voldwn_6x6.png | Bin .../hid_app/assets/Volup_8x6.png | Bin .../{plugins => external}/hid_app/hid.c | 0 .../{plugins => external}/hid_app/hid.h | 0 .../hid_app/hid_ble_10px.png | Bin .../hid_app/hid_usb_10px.png | Bin .../{plugins => external}/hid_app/views.h | 0 .../hid_app/views/hid_keyboard.c | 0 .../hid_app/views/hid_keyboard.h | 0 .../hid_app/views/hid_keynote.c | 0 .../hid_app/views/hid_keynote.h | 0 .../hid_app/views/hid_media.c | 0 .../hid_app/views/hid_media.h | 0 .../hid_app/views/hid_mouse.c | 0 .../hid_app/views/hid_mouse.h | 0 .../hid_app/views/hid_mouse_jiggler.c | 0 .../hid_app/views/hid_mouse_jiggler.h | 0 .../hid_app/views/hid_tiktok.c | 0 .../hid_app/views/hid_tiktok.h | 0 .../music_player/application.fam | 3 +- .../music_player/icons/music_10px.png | Bin .../music_player/music_player.c | 0 .../music_player/music_player_cli.c | 0 .../music_player/music_player_worker.c | 0 .../music_player/music_player_worker.h | 0 .../nfc_magic/application.fam | 0 .../nfc_magic/assets/DolphinCommon_56x48.png | Bin .../nfc_magic/assets/DolphinNice_96x59.png | Bin .../nfc_magic/assets/Loading_24.png | Bin .../nfc_magic/assets/NFC_manual_60x50.png | Bin .../nfc_magic/lib/magic/magic.c | 0 .../nfc_magic/lib/magic/magic.h | 0 .../nfc_magic/nfc_magic.c | 0 .../nfc_magic/nfc_magic.h | 0 .../nfc_magic/nfc_magic_i.h | 0 .../nfc_magic/nfc_magic_worker.c | 0 .../nfc_magic/nfc_magic_worker.h | 0 .../nfc_magic/nfc_magic_worker_i.h | 0 .../nfc_magic/scenes/nfc_magic_scene.c | 0 .../nfc_magic/scenes/nfc_magic_scene.h | 0 .../nfc_magic/scenes/nfc_magic_scene_check.c | 0 .../nfc_magic/scenes/nfc_magic_scene_config.h | 0 .../scenes/nfc_magic_scene_file_select.c | 0 .../scenes/nfc_magic_scene_magic_info.c | 0 .../scenes/nfc_magic_scene_not_magic.c | 0 .../nfc_magic/scenes/nfc_magic_scene_start.c | 0 .../scenes/nfc_magic_scene_success.c | 0 .../nfc_magic/scenes/nfc_magic_scene_wipe.c | 0 .../scenes/nfc_magic_scene_wipe_fail.c | 0 .../nfc_magic/scenes/nfc_magic_scene_write.c | 0 .../scenes/nfc_magic_scene_write_confirm.c | 0 .../scenes/nfc_magic_scene_write_fail.c | 0 .../scenes/nfc_magic_scene_wrong_card.c | 0 .../picopass/125_10px.png | Bin .../picopass/application.fam | 0 .../picopass/helpers/iclass_elite_dict.c | 0 .../picopass/helpers/iclass_elite_dict.h | 0 .../picopass/icons/DolphinMafia_115x62.png | Bin .../picopass/icons/DolphinNice_96x59.png | Bin .../picopass/icons/Nfc_10px.png | Bin .../icons/RFIDDolphinReceive_97x61.png | Bin .../picopass/icons/RFIDDolphinSend_97x61.png | Bin .../picopass/lib/loclass/optimized_cipher.c | 0 .../picopass/lib/loclass/optimized_cipher.h | 0 .../lib/loclass/optimized_cipherutils.c | 0 .../lib/loclass/optimized_cipherutils.h | 0 .../picopass/lib/loclass/optimized_elite.c | 0 .../picopass/lib/loclass/optimized_elite.h | 0 .../picopass/lib/loclass/optimized_ikeys.c | 0 .../picopass/lib/loclass/optimized_ikeys.h | 0 .../{plugins => external}/picopass/picopass.c | 0 .../{plugins => external}/picopass/picopass.h | 0 .../picopass/picopass_device.c | 0 .../picopass/picopass_device.h | 0 .../picopass/picopass_i.h | 0 .../picopass/picopass_keys.c | 0 .../picopass/picopass_keys.h | 0 .../picopass/picopass_worker.c | 0 .../picopass/picopass_worker.h | 0 .../picopass/picopass_worker_i.h | 0 .../picopass/rfal_picopass.c | 0 .../picopass/rfal_picopass.h | 0 .../picopass/scenes/picopass_scene.c | 0 .../picopass/scenes/picopass_scene.h | 0 .../scenes/picopass_scene_card_menu.c | 0 .../picopass/scenes/picopass_scene_config.h | 0 .../picopass/scenes/picopass_scene_delete.c | 0 .../scenes/picopass_scene_delete_success.c | 0 .../scenes/picopass_scene_device_info.c | 0 .../scenes/picopass_scene_file_select.c | 0 .../picopass/scenes/picopass_scene_key_menu.c | 0 .../scenes/picopass_scene_read_card.c | 0 .../scenes/picopass_scene_read_card_success.c | 0 .../picopass_scene_read_factory_success.c | 0 .../scenes/picopass_scene_save_name.c | 0 .../scenes/picopass_scene_save_success.c | 0 .../scenes/picopass_scene_saved_menu.c | 0 .../picopass/scenes/picopass_scene_start.c | 0 .../scenes/picopass_scene_write_card.c | 0 .../picopass_scene_write_card_success.c | 0 .../scenes/picopass_scene_write_key.c | 0 .../signal_generator/application.fam | 3 +- .../icons/SmallArrowDown_3x5.png | Bin .../icons/SmallArrowUp_3x5.png | Bin .../scenes/signal_gen_scene.c | 0 .../scenes/signal_gen_scene.h | 0 .../scenes/signal_gen_scene_config.h | 0 .../scenes/signal_gen_scene_mco.c | 0 .../scenes/signal_gen_scene_pwm.c | 0 .../scenes/signal_gen_scene_start.c | 0 .../signal_generator/signal_gen_10px.png | Bin .../signal_generator/signal_gen_app.c | 0 .../signal_generator/signal_gen_app_i.h | 0 .../signal_generator/views/signal_gen_pwm.c | 0 .../signal_generator/views/signal_gen_pwm.h | 0 .../snake_game/application.fam | 3 +- .../snake_game/snake_10px.png | Bin .../snake_game/snake_game.c | 0 .../spi_mem_manager/application.fam | 0 .../images/ChipLooking_64x64/frame_01.png | Bin .../images/ChipLooking_64x64/frame_02.png | Bin .../images/ChipLooking_64x64/frame_03.png | Bin .../images/ChipLooking_64x64/frame_rate | 0 .../spi_mem_manager/images/Dip8_10px.png | Bin .../spi_mem_manager/images/Dip8_32x36.png | Bin .../images/DolphinMafia_115x62.png | Bin .../images/DolphinNice_96x59.png | Bin .../images/SDQuestion_35x43.png | Bin .../images/Wiring_SPI_128x64.png | Bin .../spi_mem_manager/lib/spi/spi_mem_chip.c | 0 .../spi_mem_manager/lib/spi/spi_mem_chip.h | 0 .../lib/spi/spi_mem_chip_arr.c | 0 .../spi_mem_manager/lib/spi/spi_mem_chip_i.h | 0 .../spi_mem_manager/lib/spi/spi_mem_tools.c | 0 .../spi_mem_manager/lib/spi/spi_mem_tools.h | 0 .../spi_mem_manager/lib/spi/spi_mem_worker.c | 0 .../spi_mem_manager/lib/spi/spi_mem_worker.h | 0 .../lib/spi/spi_mem_worker_i.h | 0 .../lib/spi/spi_mem_worker_modes.c | 0 .../spi_mem_manager/scenes/spi_mem_scene.c | 0 .../spi_mem_manager/scenes/spi_mem_scene.h | 0 .../scenes/spi_mem_scene_about.c | 0 .../scenes/spi_mem_scene_chip_detect.c | 0 .../scenes/spi_mem_scene_chip_detect_fail.c | 0 .../scenes/spi_mem_scene_chip_detected.c | 0 .../scenes/spi_mem_scene_chip_error.c | 0 .../scenes/spi_mem_scene_config.h | 0 .../scenes/spi_mem_scene_delete_confirm.c | 0 .../scenes/spi_mem_scene_erase.c | 0 .../scenes/spi_mem_scene_file_info.c | 0 .../scenes/spi_mem_scene_read.c | 0 .../scenes/spi_mem_scene_read_filename.c | 0 .../scenes/spi_mem_scene_saved_file_menu.c | 0 .../scenes/spi_mem_scene_select_file.c | 0 .../scenes/spi_mem_scene_select_model.c | 0 .../scenes/spi_mem_scene_select_vendor.c | 0 .../scenes/spi_mem_scene_start.c | 0 .../scenes/spi_mem_scene_storage_error.c | 0 .../scenes/spi_mem_scene_success.c | 0 .../scenes/spi_mem_scene_verify.c | 0 .../scenes/spi_mem_scene_verify_error.c | 0 .../scenes/spi_mem_scene_wiring.c | 0 .../scenes/spi_mem_scene_write.c | 0 .../spi_mem_manager/spi_mem_app.c | 0 .../spi_mem_manager/spi_mem_app.h | 0 .../spi_mem_manager/spi_mem_app_i.h | 0 .../spi_mem_manager/spi_mem_files.c | 0 .../spi_mem_manager/spi_mem_files.h | 0 .../spi_mem_manager/tools/README.md | 0 .../spi_mem_manager/tools/chiplist/LICENSE | 0 .../tools/chiplist/chiplist.xml | 0 .../spi_mem_manager/tools/chiplist_convert.py | 0 .../views/spi_mem_view_detect.c | 0 .../views/spi_mem_view_detect.h | 0 .../views/spi_mem_view_progress.c | 0 .../views/spi_mem_view_progress.h | 0 .../weather_station/application.fam | 3 +- .../helpers/weather_station_event.h | 0 .../helpers/weather_station_types.h | 0 .../weather_station/images/Humid_10x15.png | Bin .../weather_station/images/Humid_8x13.png | Bin .../weather_station/images/Lock_7x8.png | Bin .../images/Pin_back_arrow_10x8.png | Bin .../weather_station/images/Quest_7x8.png | Bin .../images/Scanning_123x52.png | Bin .../weather_station/images/Therm_7x16.png | Bin .../weather_station/images/Timer_11x11.png | Bin .../weather_station/images/Unlock_7x8.png | Bin .../images/WarningDolphin_45x42.png | Bin .../weather_station/images/station_icon.png | Bin .../protocols/acurite_592txr.c | 0 .../protocols/acurite_592txr.h | 0 .../weather_station/protocols/acurite_606tx.c | 0 .../weather_station/protocols/acurite_606tx.h | 0 .../protocols/acurite_609txc.c | 0 .../protocols/acurite_609txc.h | 0 .../protocols/ambient_weather.c | 0 .../protocols/ambient_weather.h | 0 .../protocols/auriol_hg0601a.c | 0 .../protocols/auriol_hg0601a.h | 0 .../weather_station/protocols/gt_wt_02.c | 0 .../weather_station/protocols/gt_wt_02.h | 0 .../weather_station/protocols/gt_wt_03.c | 0 .../weather_station/protocols/gt_wt_03.h | 0 .../weather_station/protocols/infactory.c | 0 .../weather_station/protocols/infactory.h | 0 .../weather_station/protocols/lacrosse_tx.c | 0 .../weather_station/protocols/lacrosse_tx.h | 0 .../protocols/lacrosse_tx141thbv2.c | 0 .../protocols/lacrosse_tx141thbv2.h | 0 .../weather_station/protocols/nexus_th.c | 0 .../weather_station/protocols/nexus_th.h | 0 .../weather_station/protocols/oregon2.c | 0 .../weather_station/protocols/oregon2.h | 0 .../weather_station/protocols/oregon_v1.c | 0 .../weather_station/protocols/oregon_v1.h | 0 .../protocols/protocol_items.c | 0 .../protocols/protocol_items.h | 0 .../weather_station/protocols/thermopro_tx4.c | 0 .../weather_station/protocols/thermopro_tx4.h | 0 .../weather_station/protocols/tx_8300.c | 0 .../weather_station/protocols/tx_8300.h | 0 .../weather_station/protocols/ws_generic.c | 0 .../weather_station/protocols/ws_generic.h | 0 .../scenes/weather_station_receiver.c | 0 .../scenes/weather_station_scene.c | 0 .../scenes/weather_station_scene.h | 0 .../scenes/weather_station_scene_about.c | 0 .../scenes/weather_station_scene_config.h | 0 .../weather_station_scene_receiver_config.c | 0 .../weather_station_scene_receiver_info.c | 0 .../scenes/weather_station_scene_start.c | 0 .../views/weather_station_receiver.c | 0 .../views/weather_station_receiver.h | 0 .../views/weather_station_receiver_info.c | 0 .../views/weather_station_receiver_info.h | 0 .../weather_station/weather_station_10px.png | Bin .../weather_station/weather_station_app.c | 0 .../weather_station/weather_station_app_i.c | 0 .../weather_station/weather_station_app_i.h | 0 .../weather_station/weather_station_history.c | 0 .../weather_station/weather_station_history.h | 0 applications/main/fap_loader/application.fam | 1 + .../main/fap_loader/elf_cpp/elf_hashtable.cpp | 48 -- .../main/fap_loader/elf_cpp/elf_hashtable.h | 14 - .../elf_cpp/elf_hashtable_checks.hpp | 18 - .../fap_loader/elf_cpp/elf_hashtable_entry.h | 41 -- applications/main/fap_loader/fap_loader_app.c | 6 +- applications/plugins/application.fam | 9 - applications/services/applications.h | 12 - applications/services/loader/application.fam | 5 +- .../loader/firmware_api/firmware_api.cpp | 21 + .../loader/firmware_api/firmware_api.h | 5 + applications/services/loader/loader.c | 81 +--- applications/services/loader/loader_i.h | 4 - assets/.gitignore | 1 + documentation/Doxyfile | 2 +- fbt_options.py | 5 - firmware/targets/f18/api_symbols.csv | 24 +- firmware/targets/f7/api_symbols.csv | 24 +- furi/flipper.c | 2 +- lib/flipper_application/SConscript | 5 + .../api_hashtable/api_hashtable.cpp | 38 ++ .../api_hashtable/api_hashtable.h | 85 ++++ .../api_hashtable}/compilesort.hpp | 4 + .../elf/elf_api_interface.h | 12 +- lib/flipper_application/elf/elf_file.c | 33 +- lib/flipper_application/elf/elf_file.h | 22 +- lib/flipper_application/elf/elf_file_i.h | 2 + lib/flipper_application/flipper_application.c | 64 ++- lib/flipper_application/flipper_application.h | 36 +- .../plugins/composite_resolver.c | 52 +++ .../plugins/composite_resolver.h | 46 ++ .../plugins/plugin_manager.c | 153 +++++++ .../plugins/plugin_manager.h | 82 ++++ scripts/distfap.py | 71 +++ scripts/fbt/appmanifest.py | 46 +- scripts/fbt/fapassets.py | 108 +++++ scripts/fbt_tools/fbt_extapps.py | 425 ++++++++---------- scripts/fbt_tools/fbt_sdk.py | 2 +- scripts/flipper/storage.py | 348 +++++++++----- scripts/requirements.txt | 9 - scripts/runfap.py | 132 +++--- scripts/selfupdate.py | 94 ++-- scripts/storage.py | 267 +++-------- site_scons/commandline.scons | 6 +- site_scons/extapps.scons | 65 ++- 376 files changed, 2041 insertions(+), 1036 deletions(-) create mode 100644 applications/examples/example_plugins/application.fam create mode 100644 applications/examples/example_plugins/example_plugins.c create mode 100644 applications/examples/example_plugins/example_plugins_multi.c create mode 100644 applications/examples/example_plugins/plugin1.c create mode 100644 applications/examples/example_plugins/plugin2.c create mode 100644 applications/examples/example_plugins/plugin_interface.h create mode 100644 applications/examples/example_plugins_advanced/app_api.c create mode 100644 applications/examples/example_plugins_advanced/app_api.h create mode 100644 applications/examples/example_plugins_advanced/app_api_interface.h create mode 100644 applications/examples/example_plugins_advanced/app_api_table.cpp create mode 100644 applications/examples/example_plugins_advanced/app_api_table_i.h create mode 100644 applications/examples/example_plugins_advanced/application.fam create mode 100644 applications/examples/example_plugins_advanced/example_advanced_plugins.c create mode 100644 applications/examples/example_plugins_advanced/plugin1.c create mode 100644 applications/examples/example_plugins_advanced/plugin2.c create mode 100644 applications/examples/example_plugins_advanced/plugin_interface.h create mode 100644 applications/external/application.fam rename applications/{plugins => external}/clock/application.fam (82%) rename applications/{plugins => external}/clock/clock.png (100%) rename applications/{plugins => external}/clock/clock_app.c (100%) rename applications/{plugins => external}/dap_link/README.md (100%) rename applications/{plugins => external}/dap_link/application.fam (92%) rename applications/{plugins => external}/dap_link/dap_config.h (100%) rename applications/{plugins => external}/dap_link/dap_link.c (100%) rename applications/{plugins => external}/dap_link/dap_link.h (100%) rename applications/{plugins => external}/dap_link/dap_link.png (100%) rename applications/{plugins => external}/dap_link/gui/dap_gui.c (100%) rename applications/{plugins => external}/dap_link/gui/dap_gui.h (100%) rename applications/{plugins => external}/dap_link/gui/dap_gui_custom_event.h (100%) rename applications/{plugins => external}/dap_link/gui/dap_gui_i.h (100%) rename applications/{plugins => external}/dap_link/gui/scenes/config/dap_scene.c (100%) rename applications/{plugins => external}/dap_link/gui/scenes/config/dap_scene.h (100%) rename applications/{plugins => external}/dap_link/gui/scenes/config/dap_scene_config.h (100%) rename applications/{plugins => external}/dap_link/gui/scenes/dap_scene_about.c (100%) rename applications/{plugins => external}/dap_link/gui/scenes/dap_scene_config.c (100%) rename applications/{plugins => external}/dap_link/gui/scenes/dap_scene_help.c (100%) rename applications/{plugins => external}/dap_link/gui/scenes/dap_scene_main.c (100%) rename applications/{plugins => external}/dap_link/gui/views/dap_main_view.c (100%) rename applications/{plugins => external}/dap_link/gui/views/dap_main_view.h (100%) rename applications/{plugins => external}/dap_link/icons/ActiveConnection_50x64.png (100%) rename applications/{plugins => external}/dap_link/icons/ArrowDownEmpty_12x18.png (100%) rename applications/{plugins => external}/dap_link/icons/ArrowDownFilled_12x18.png (100%) rename applications/{plugins => external}/dap_link/icons/ArrowUpEmpty_12x18.png (100%) rename applications/{plugins => external}/dap_link/icons/ArrowUpFilled_12x18.png (100%) rename applications/{plugins => external}/dap_link/lib/free-dap (100%) rename applications/{plugins => external}/dap_link/usb/dap_v2_usb.c (100%) rename applications/{plugins => external}/dap_link/usb/dap_v2_usb.h (100%) rename applications/{plugins => external}/dap_link/usb/usb_winusb.h (100%) rename applications/{plugins => external}/hid_app/application.fam (86%) rename applications/{plugins => external}/hid_app/assets/Arr_dwn_7x9.png (100%) rename applications/{plugins => external}/hid_app/assets/Arr_up_7x9.png (100%) rename applications/{plugins => external}/hid_app/assets/Ble_connected_15x15.png (100%) rename applications/{plugins => external}/hid_app/assets/Ble_disconnected_15x15.png (100%) rename applications/{plugins => external}/hid_app/assets/ButtonDown_7x4.png (100%) rename applications/{plugins => external}/hid_app/assets/ButtonF10_5x8.png (100%) rename applications/{plugins => external}/hid_app/assets/ButtonF11_5x8.png (100%) rename applications/{plugins => external}/hid_app/assets/ButtonF12_5x8.png (100%) rename applications/{plugins => external}/hid_app/assets/ButtonF1_5x8.png (100%) rename applications/{plugins => external}/hid_app/assets/ButtonF2_5x8.png (100%) rename applications/{plugins => external}/hid_app/assets/ButtonF3_5x8.png (100%) rename applications/{plugins => external}/hid_app/assets/ButtonF4_5x8.png (100%) rename applications/{plugins => external}/hid_app/assets/ButtonF5_5x8.png (100%) rename applications/{plugins => external}/hid_app/assets/ButtonF6_5x8.png (100%) rename applications/{plugins => external}/hid_app/assets/ButtonF7_5x8.png (100%) rename applications/{plugins => external}/hid_app/assets/ButtonF8_5x8.png (100%) rename applications/{plugins => external}/hid_app/assets/ButtonF9_5x8.png (100%) rename applications/{plugins => external}/hid_app/assets/ButtonLeft_4x7.png (100%) rename applications/{plugins => external}/hid_app/assets/ButtonRight_4x7.png (100%) rename applications/{plugins => external}/hid_app/assets/ButtonUp_7x4.png (100%) rename applications/{plugins => external}/hid_app/assets/Button_18x18.png (100%) rename applications/{plugins => external}/hid_app/assets/Circles_47x47.png (100%) rename applications/{plugins => external}/hid_app/assets/Left_mouse_icon_9x9.png (100%) rename applications/{plugins => external}/hid_app/assets/Like_def_11x9.png (100%) rename applications/{plugins => external}/hid_app/assets/Like_pressed_17x17.png (100%) rename applications/{plugins => external}/hid_app/assets/Ok_btn_9x9.png (100%) rename applications/{plugins => external}/hid_app/assets/Ok_btn_pressed_13x13.png (100%) rename applications/{plugins => external}/hid_app/assets/Pin_arrow_down_7x9.png (100%) rename applications/{plugins => external}/hid_app/assets/Pin_arrow_left_9x7.png (100%) rename applications/{plugins => external}/hid_app/assets/Pin_arrow_right_9x7.png (100%) rename applications/{plugins => external}/hid_app/assets/Pin_arrow_up_7x9.png (100%) rename applications/{plugins => external}/hid_app/assets/Pin_back_arrow_10x8.png (100%) rename applications/{plugins => external}/hid_app/assets/Pressed_Button_13x13.png (100%) rename applications/{plugins => external}/hid_app/assets/Right_mouse_icon_9x9.png (100%) rename applications/{plugins => external}/hid_app/assets/Space_65x18.png (100%) rename applications/{plugins => external}/hid_app/assets/Voldwn_6x6.png (100%) rename applications/{plugins => external}/hid_app/assets/Volup_8x6.png (100%) rename applications/{plugins => external}/hid_app/hid.c (100%) rename applications/{plugins => external}/hid_app/hid.h (100%) rename applications/{plugins => external}/hid_app/hid_ble_10px.png (100%) rename applications/{plugins => external}/hid_app/hid_usb_10px.png (100%) rename applications/{plugins => external}/hid_app/views.h (100%) rename applications/{plugins => external}/hid_app/views/hid_keyboard.c (100%) rename applications/{plugins => external}/hid_app/views/hid_keyboard.h (100%) rename applications/{plugins => external}/hid_app/views/hid_keynote.c (100%) rename applications/{plugins => external}/hid_app/views/hid_keynote.h (100%) rename applications/{plugins => external}/hid_app/views/hid_media.c (100%) rename applications/{plugins => external}/hid_app/views/hid_media.h (100%) rename applications/{plugins => external}/hid_app/views/hid_mouse.c (100%) rename applications/{plugins => external}/hid_app/views/hid_mouse.h (100%) rename applications/{plugins => external}/hid_app/views/hid_mouse_jiggler.c (100%) rename applications/{plugins => external}/hid_app/views/hid_mouse_jiggler.h (100%) rename applications/{plugins => external}/hid_app/views/hid_tiktok.c (100%) rename applications/{plugins => external}/hid_app/views/hid_tiktok.h (100%) rename applications/{plugins => external}/music_player/application.fam (87%) rename applications/{plugins => external}/music_player/icons/music_10px.png (100%) rename applications/{plugins => external}/music_player/music_player.c (100%) rename applications/{plugins => external}/music_player/music_player_cli.c (100%) rename applications/{plugins => external}/music_player/music_player_worker.c (100%) rename applications/{plugins => external}/music_player/music_player_worker.h (100%) rename applications/{plugins => external}/nfc_magic/application.fam (100%) rename applications/{plugins => external}/nfc_magic/assets/DolphinCommon_56x48.png (100%) rename applications/{plugins => external}/nfc_magic/assets/DolphinNice_96x59.png (100%) rename applications/{plugins => external}/nfc_magic/assets/Loading_24.png (100%) rename applications/{plugins => external}/nfc_magic/assets/NFC_manual_60x50.png (100%) rename applications/{plugins => external}/nfc_magic/lib/magic/magic.c (100%) rename applications/{plugins => external}/nfc_magic/lib/magic/magic.h (100%) rename applications/{plugins => external}/nfc_magic/nfc_magic.c (100%) rename applications/{plugins => external}/nfc_magic/nfc_magic.h (100%) rename applications/{plugins => external}/nfc_magic/nfc_magic_i.h (100%) rename applications/{plugins => external}/nfc_magic/nfc_magic_worker.c (100%) rename applications/{plugins => external}/nfc_magic/nfc_magic_worker.h (100%) rename applications/{plugins => external}/nfc_magic/nfc_magic_worker_i.h (100%) rename applications/{plugins => external}/nfc_magic/scenes/nfc_magic_scene.c (100%) rename applications/{plugins => external}/nfc_magic/scenes/nfc_magic_scene.h (100%) rename applications/{plugins => external}/nfc_magic/scenes/nfc_magic_scene_check.c (100%) rename applications/{plugins => external}/nfc_magic/scenes/nfc_magic_scene_config.h (100%) rename applications/{plugins => external}/nfc_magic/scenes/nfc_magic_scene_file_select.c (100%) rename applications/{plugins => external}/nfc_magic/scenes/nfc_magic_scene_magic_info.c (100%) rename applications/{plugins => external}/nfc_magic/scenes/nfc_magic_scene_not_magic.c (100%) rename applications/{plugins => external}/nfc_magic/scenes/nfc_magic_scene_start.c (100%) rename applications/{plugins => external}/nfc_magic/scenes/nfc_magic_scene_success.c (100%) rename applications/{plugins => external}/nfc_magic/scenes/nfc_magic_scene_wipe.c (100%) rename applications/{plugins => external}/nfc_magic/scenes/nfc_magic_scene_wipe_fail.c (100%) rename applications/{plugins => external}/nfc_magic/scenes/nfc_magic_scene_write.c (100%) rename applications/{plugins => external}/nfc_magic/scenes/nfc_magic_scene_write_confirm.c (100%) rename applications/{plugins => external}/nfc_magic/scenes/nfc_magic_scene_write_fail.c (100%) rename applications/{plugins => external}/nfc_magic/scenes/nfc_magic_scene_wrong_card.c (100%) rename applications/{plugins => external}/picopass/125_10px.png (100%) rename applications/{plugins => external}/picopass/application.fam (100%) rename applications/{plugins => external}/picopass/helpers/iclass_elite_dict.c (100%) rename applications/{plugins => external}/picopass/helpers/iclass_elite_dict.h (100%) rename applications/{plugins => external}/picopass/icons/DolphinMafia_115x62.png (100%) rename applications/{plugins => external}/picopass/icons/DolphinNice_96x59.png (100%) rename applications/{plugins => external}/picopass/icons/Nfc_10px.png (100%) rename applications/{plugins => external}/picopass/icons/RFIDDolphinReceive_97x61.png (100%) rename applications/{plugins => external}/picopass/icons/RFIDDolphinSend_97x61.png (100%) rename applications/{plugins => external}/picopass/lib/loclass/optimized_cipher.c (100%) rename applications/{plugins => external}/picopass/lib/loclass/optimized_cipher.h (100%) rename applications/{plugins => external}/picopass/lib/loclass/optimized_cipherutils.c (100%) rename applications/{plugins => external}/picopass/lib/loclass/optimized_cipherutils.h (100%) rename applications/{plugins => external}/picopass/lib/loclass/optimized_elite.c (100%) rename applications/{plugins => external}/picopass/lib/loclass/optimized_elite.h (100%) rename applications/{plugins => external}/picopass/lib/loclass/optimized_ikeys.c (100%) rename applications/{plugins => external}/picopass/lib/loclass/optimized_ikeys.h (100%) rename applications/{plugins => external}/picopass/picopass.c (100%) rename applications/{plugins => external}/picopass/picopass.h (100%) rename applications/{plugins => external}/picopass/picopass_device.c (100%) rename applications/{plugins => external}/picopass/picopass_device.h (100%) rename applications/{plugins => external}/picopass/picopass_i.h (100%) rename applications/{plugins => external}/picopass/picopass_keys.c (100%) rename applications/{plugins => external}/picopass/picopass_keys.h (100%) rename applications/{plugins => external}/picopass/picopass_worker.c (100%) rename applications/{plugins => external}/picopass/picopass_worker.h (100%) rename applications/{plugins => external}/picopass/picopass_worker_i.h (100%) rename applications/{plugins => external}/picopass/rfal_picopass.c (100%) rename applications/{plugins => external}/picopass/rfal_picopass.h (100%) rename applications/{plugins => external}/picopass/scenes/picopass_scene.c (100%) rename applications/{plugins => external}/picopass/scenes/picopass_scene.h (100%) rename applications/{plugins => external}/picopass/scenes/picopass_scene_card_menu.c (100%) rename applications/{plugins => external}/picopass/scenes/picopass_scene_config.h (100%) rename applications/{plugins => external}/picopass/scenes/picopass_scene_delete.c (100%) rename applications/{plugins => external}/picopass/scenes/picopass_scene_delete_success.c (100%) rename applications/{plugins => external}/picopass/scenes/picopass_scene_device_info.c (100%) rename applications/{plugins => external}/picopass/scenes/picopass_scene_file_select.c (100%) rename applications/{plugins => external}/picopass/scenes/picopass_scene_key_menu.c (100%) rename applications/{plugins => external}/picopass/scenes/picopass_scene_read_card.c (100%) rename applications/{plugins => external}/picopass/scenes/picopass_scene_read_card_success.c (100%) rename applications/{plugins => external}/picopass/scenes/picopass_scene_read_factory_success.c (100%) rename applications/{plugins => external}/picopass/scenes/picopass_scene_save_name.c (100%) rename applications/{plugins => external}/picopass/scenes/picopass_scene_save_success.c (100%) rename applications/{plugins => external}/picopass/scenes/picopass_scene_saved_menu.c (100%) rename applications/{plugins => external}/picopass/scenes/picopass_scene_start.c (100%) rename applications/{plugins => external}/picopass/scenes/picopass_scene_write_card.c (100%) rename applications/{plugins => external}/picopass/scenes/picopass_scene_write_card_success.c (100%) rename applications/{plugins => external}/picopass/scenes/picopass_scene_write_key.c (100%) rename applications/{plugins => external}/signal_generator/application.fam (78%) rename applications/{plugins => external}/signal_generator/icons/SmallArrowDown_3x5.png (100%) rename applications/{plugins => external}/signal_generator/icons/SmallArrowUp_3x5.png (100%) rename applications/{plugins => external}/signal_generator/scenes/signal_gen_scene.c (100%) rename applications/{plugins => external}/signal_generator/scenes/signal_gen_scene.h (100%) rename applications/{plugins => external}/signal_generator/scenes/signal_gen_scene_config.h (100%) rename applications/{plugins => external}/signal_generator/scenes/signal_gen_scene_mco.c (100%) rename applications/{plugins => external}/signal_generator/scenes/signal_gen_scene_pwm.c (100%) rename applications/{plugins => external}/signal_generator/scenes/signal_gen_scene_start.c (100%) rename applications/{plugins => external}/signal_generator/signal_gen_10px.png (100%) rename applications/{plugins => external}/signal_generator/signal_gen_app.c (100%) rename applications/{plugins => external}/signal_generator/signal_gen_app_i.h (100%) rename applications/{plugins => external}/signal_generator/views/signal_gen_pwm.c (100%) rename applications/{plugins => external}/signal_generator/views/signal_gen_pwm.h (100%) rename applications/{plugins => external}/snake_game/application.fam (75%) rename applications/{plugins => external}/snake_game/snake_10px.png (100%) rename applications/{plugins => external}/snake_game/snake_game.c (100%) rename applications/{plugins => external}/spi_mem_manager/application.fam (100%) rename applications/{plugins => external}/spi_mem_manager/images/ChipLooking_64x64/frame_01.png (100%) rename applications/{plugins => external}/spi_mem_manager/images/ChipLooking_64x64/frame_02.png (100%) rename applications/{plugins => external}/spi_mem_manager/images/ChipLooking_64x64/frame_03.png (100%) rename applications/{plugins => external}/spi_mem_manager/images/ChipLooking_64x64/frame_rate (100%) rename applications/{plugins => external}/spi_mem_manager/images/Dip8_10px.png (100%) rename applications/{plugins => external}/spi_mem_manager/images/Dip8_32x36.png (100%) rename applications/{plugins => external}/spi_mem_manager/images/DolphinMafia_115x62.png (100%) rename applications/{plugins => external}/spi_mem_manager/images/DolphinNice_96x59.png (100%) rename applications/{plugins => external}/spi_mem_manager/images/SDQuestion_35x43.png (100%) rename applications/{plugins => external}/spi_mem_manager/images/Wiring_SPI_128x64.png (100%) rename applications/{plugins => external}/spi_mem_manager/lib/spi/spi_mem_chip.c (100%) rename applications/{plugins => external}/spi_mem_manager/lib/spi/spi_mem_chip.h (100%) rename applications/{plugins => external}/spi_mem_manager/lib/spi/spi_mem_chip_arr.c (100%) rename applications/{plugins => external}/spi_mem_manager/lib/spi/spi_mem_chip_i.h (100%) rename applications/{plugins => external}/spi_mem_manager/lib/spi/spi_mem_tools.c (100%) rename applications/{plugins => external}/spi_mem_manager/lib/spi/spi_mem_tools.h (100%) rename applications/{plugins => external}/spi_mem_manager/lib/spi/spi_mem_worker.c (100%) rename applications/{plugins => external}/spi_mem_manager/lib/spi/spi_mem_worker.h (100%) rename applications/{plugins => external}/spi_mem_manager/lib/spi/spi_mem_worker_i.h (100%) rename applications/{plugins => external}/spi_mem_manager/lib/spi/spi_mem_worker_modes.c (100%) rename applications/{plugins => external}/spi_mem_manager/scenes/spi_mem_scene.c (100%) rename applications/{plugins => external}/spi_mem_manager/scenes/spi_mem_scene.h (100%) rename applications/{plugins => external}/spi_mem_manager/scenes/spi_mem_scene_about.c (100%) rename applications/{plugins => external}/spi_mem_manager/scenes/spi_mem_scene_chip_detect.c (100%) rename applications/{plugins => external}/spi_mem_manager/scenes/spi_mem_scene_chip_detect_fail.c (100%) rename applications/{plugins => external}/spi_mem_manager/scenes/spi_mem_scene_chip_detected.c (100%) rename applications/{plugins => external}/spi_mem_manager/scenes/spi_mem_scene_chip_error.c (100%) rename applications/{plugins => external}/spi_mem_manager/scenes/spi_mem_scene_config.h (100%) rename applications/{plugins => external}/spi_mem_manager/scenes/spi_mem_scene_delete_confirm.c (100%) rename applications/{plugins => external}/spi_mem_manager/scenes/spi_mem_scene_erase.c (100%) rename applications/{plugins => external}/spi_mem_manager/scenes/spi_mem_scene_file_info.c (100%) rename applications/{plugins => external}/spi_mem_manager/scenes/spi_mem_scene_read.c (100%) rename applications/{plugins => external}/spi_mem_manager/scenes/spi_mem_scene_read_filename.c (100%) rename applications/{plugins => external}/spi_mem_manager/scenes/spi_mem_scene_saved_file_menu.c (100%) rename applications/{plugins => external}/spi_mem_manager/scenes/spi_mem_scene_select_file.c (100%) rename applications/{plugins => external}/spi_mem_manager/scenes/spi_mem_scene_select_model.c (100%) rename applications/{plugins => external}/spi_mem_manager/scenes/spi_mem_scene_select_vendor.c (100%) rename applications/{plugins => external}/spi_mem_manager/scenes/spi_mem_scene_start.c (100%) rename applications/{plugins => external}/spi_mem_manager/scenes/spi_mem_scene_storage_error.c (100%) rename applications/{plugins => external}/spi_mem_manager/scenes/spi_mem_scene_success.c (100%) rename applications/{plugins => external}/spi_mem_manager/scenes/spi_mem_scene_verify.c (100%) rename applications/{plugins => external}/spi_mem_manager/scenes/spi_mem_scene_verify_error.c (100%) rename applications/{plugins => external}/spi_mem_manager/scenes/spi_mem_scene_wiring.c (100%) rename applications/{plugins => external}/spi_mem_manager/scenes/spi_mem_scene_write.c (100%) rename applications/{plugins => external}/spi_mem_manager/spi_mem_app.c (100%) rename applications/{plugins => external}/spi_mem_manager/spi_mem_app.h (100%) rename applications/{plugins => external}/spi_mem_manager/spi_mem_app_i.h (100%) rename applications/{plugins => external}/spi_mem_manager/spi_mem_files.c (100%) rename applications/{plugins => external}/spi_mem_manager/spi_mem_files.h (100%) rename applications/{plugins => external}/spi_mem_manager/tools/README.md (100%) rename applications/{plugins => external}/spi_mem_manager/tools/chiplist/LICENSE (100%) rename applications/{plugins => external}/spi_mem_manager/tools/chiplist/chiplist.xml (100%) rename applications/{plugins => external}/spi_mem_manager/tools/chiplist_convert.py (100%) rename applications/{plugins => external}/spi_mem_manager/views/spi_mem_view_detect.c (100%) rename applications/{plugins => external}/spi_mem_manager/views/spi_mem_view_detect.h (100%) rename applications/{plugins => external}/spi_mem_manager/views/spi_mem_view_progress.c (100%) rename applications/{plugins => external}/spi_mem_manager/views/spi_mem_view_progress.h (100%) rename applications/{plugins => external}/weather_station/application.fam (79%) rename applications/{plugins => external}/weather_station/helpers/weather_station_event.h (100%) rename applications/{plugins => external}/weather_station/helpers/weather_station_types.h (100%) rename applications/{plugins => external}/weather_station/images/Humid_10x15.png (100%) rename applications/{plugins => external}/weather_station/images/Humid_8x13.png (100%) rename applications/{plugins => external}/weather_station/images/Lock_7x8.png (100%) rename applications/{plugins => external}/weather_station/images/Pin_back_arrow_10x8.png (100%) rename applications/{plugins => external}/weather_station/images/Quest_7x8.png (100%) rename applications/{plugins => external}/weather_station/images/Scanning_123x52.png (100%) rename applications/{plugins => external}/weather_station/images/Therm_7x16.png (100%) rename applications/{plugins => external}/weather_station/images/Timer_11x11.png (100%) rename applications/{plugins => external}/weather_station/images/Unlock_7x8.png (100%) rename applications/{plugins => external}/weather_station/images/WarningDolphin_45x42.png (100%) rename applications/{plugins => external}/weather_station/images/station_icon.png (100%) rename applications/{plugins => external}/weather_station/protocols/acurite_592txr.c (100%) rename applications/{plugins => external}/weather_station/protocols/acurite_592txr.h (100%) rename applications/{plugins => external}/weather_station/protocols/acurite_606tx.c (100%) rename applications/{plugins => external}/weather_station/protocols/acurite_606tx.h (100%) rename applications/{plugins => external}/weather_station/protocols/acurite_609txc.c (100%) rename applications/{plugins => external}/weather_station/protocols/acurite_609txc.h (100%) rename applications/{plugins => external}/weather_station/protocols/ambient_weather.c (100%) rename applications/{plugins => external}/weather_station/protocols/ambient_weather.h (100%) rename applications/{plugins => external}/weather_station/protocols/auriol_hg0601a.c (100%) rename applications/{plugins => external}/weather_station/protocols/auriol_hg0601a.h (100%) rename applications/{plugins => external}/weather_station/protocols/gt_wt_02.c (100%) rename applications/{plugins => external}/weather_station/protocols/gt_wt_02.h (100%) rename applications/{plugins => external}/weather_station/protocols/gt_wt_03.c (100%) rename applications/{plugins => external}/weather_station/protocols/gt_wt_03.h (100%) rename applications/{plugins => external}/weather_station/protocols/infactory.c (100%) rename applications/{plugins => external}/weather_station/protocols/infactory.h (100%) rename applications/{plugins => external}/weather_station/protocols/lacrosse_tx.c (100%) rename applications/{plugins => external}/weather_station/protocols/lacrosse_tx.h (100%) rename applications/{plugins => external}/weather_station/protocols/lacrosse_tx141thbv2.c (100%) rename applications/{plugins => external}/weather_station/protocols/lacrosse_tx141thbv2.h (100%) rename applications/{plugins => external}/weather_station/protocols/nexus_th.c (100%) rename applications/{plugins => external}/weather_station/protocols/nexus_th.h (100%) rename applications/{plugins => external}/weather_station/protocols/oregon2.c (100%) rename applications/{plugins => external}/weather_station/protocols/oregon2.h (100%) rename applications/{plugins => external}/weather_station/protocols/oregon_v1.c (100%) rename applications/{plugins => external}/weather_station/protocols/oregon_v1.h (100%) rename applications/{plugins => external}/weather_station/protocols/protocol_items.c (100%) rename applications/{plugins => external}/weather_station/protocols/protocol_items.h (100%) rename applications/{plugins => external}/weather_station/protocols/thermopro_tx4.c (100%) rename applications/{plugins => external}/weather_station/protocols/thermopro_tx4.h (100%) rename applications/{plugins => external}/weather_station/protocols/tx_8300.c (100%) rename applications/{plugins => external}/weather_station/protocols/tx_8300.h (100%) rename applications/{plugins => external}/weather_station/protocols/ws_generic.c (100%) rename applications/{plugins => external}/weather_station/protocols/ws_generic.h (100%) rename applications/{plugins => external}/weather_station/scenes/weather_station_receiver.c (100%) rename applications/{plugins => external}/weather_station/scenes/weather_station_scene.c (100%) rename applications/{plugins => external}/weather_station/scenes/weather_station_scene.h (100%) rename applications/{plugins => external}/weather_station/scenes/weather_station_scene_about.c (100%) rename applications/{plugins => external}/weather_station/scenes/weather_station_scene_config.h (100%) rename applications/{plugins => external}/weather_station/scenes/weather_station_scene_receiver_config.c (100%) rename applications/{plugins => external}/weather_station/scenes/weather_station_scene_receiver_info.c (100%) rename applications/{plugins => external}/weather_station/scenes/weather_station_scene_start.c (100%) rename applications/{plugins => external}/weather_station/views/weather_station_receiver.c (100%) rename applications/{plugins => external}/weather_station/views/weather_station_receiver.h (100%) rename applications/{plugins => external}/weather_station/views/weather_station_receiver_info.c (100%) rename applications/{plugins => external}/weather_station/views/weather_station_receiver_info.h (100%) rename applications/{plugins => external}/weather_station/weather_station_10px.png (100%) rename applications/{plugins => external}/weather_station/weather_station_app.c (100%) rename applications/{plugins => external}/weather_station/weather_station_app_i.c (100%) rename applications/{plugins => external}/weather_station/weather_station_app_i.h (100%) rename applications/{plugins => external}/weather_station/weather_station_history.c (100%) rename applications/{plugins => external}/weather_station/weather_station_history.h (100%) delete mode 100644 applications/main/fap_loader/elf_cpp/elf_hashtable.cpp delete mode 100644 applications/main/fap_loader/elf_cpp/elf_hashtable.h delete mode 100644 applications/main/fap_loader/elf_cpp/elf_hashtable_checks.hpp delete mode 100644 applications/main/fap_loader/elf_cpp/elf_hashtable_entry.h delete mode 100644 applications/plugins/application.fam create mode 100644 applications/services/loader/firmware_api/firmware_api.cpp create mode 100644 applications/services/loader/firmware_api/firmware_api.h create mode 100644 lib/flipper_application/api_hashtable/api_hashtable.cpp create mode 100644 lib/flipper_application/api_hashtable/api_hashtable.h rename {applications/main/fap_loader/elf_cpp => lib/flipper_application/api_hashtable}/compilesort.hpp (99%) create mode 100644 lib/flipper_application/plugins/composite_resolver.c create mode 100644 lib/flipper_application/plugins/composite_resolver.h create mode 100644 lib/flipper_application/plugins/plugin_manager.c create mode 100644 lib/flipper_application/plugins/plugin_manager.h create mode 100644 scripts/distfap.py create mode 100644 scripts/fbt/fapassets.py delete mode 100644 scripts/requirements.txt diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 69f8289f..0bc13024 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -22,8 +22,8 @@ /applications/main/subghz/ @skotopes @DrZlo13 @hedger @Skorpionm /applications/main/u2f/ @skotopes @DrZlo13 @hedger @nminaylov -/applications/plugins/bt_hid_app/ @skotopes @DrZlo13 @hedger @gornekich -/applications/plugins/picopass/ @skotopes @DrZlo13 @hedger @gornekich +/applications/external/bt_hid_app/ @skotopes @DrZlo13 @hedger @gornekich +/applications/external/picopass/ @skotopes @DrZlo13 @hedger @gornekich /applications/services/bt/ @skotopes @DrZlo13 @hedger @gornekich /applications/services/cli/ @skotopes @DrZlo13 @hedger @nminaylov diff --git a/.gitmodules b/.gitmodules index a97e0933..56368cd5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -28,6 +28,6 @@ [submodule "lib/cxxheaderparser"] path = lib/cxxheaderparser url = https://github.com/robotpy/cxxheaderparser.git -[submodule "applications/plugins/dap_link/lib/free-dap"] - path = applications/plugins/dap_link/lib/free-dap +[submodule "applications/external/dap_link/lib/free-dap"] + path = applications/external/dap_link/lib/free-dap url = https://github.com/ataradov/free-dap.git diff --git a/.pvsoptions b/.pvsoptions index ca1b2b57..6b22aed7 100644 --- a/.pvsoptions +++ b/.pvsoptions @@ -1 +1 @@ ---ignore-ccache -C gccarm --rules-config .pvsconfig -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/micro-ecc -e lib/microtar -e lib/mlib -e lib/qrcode -e lib/ST25RFAL002 -e lib/STM32CubeWB -e lib/u8g2 -e lib/nanopb -e */arm-none-eabi/* -e applications/plugins/dap_link/lib/free-dap +--ignore-ccache -C gccarm --rules-config .pvsconfig -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/micro-ecc -e lib/microtar -e lib/mlib -e lib/qrcode -e lib/ST25RFAL002 -e lib/STM32CubeWB -e lib/u8g2 -e lib/nanopb -e */arm-none-eabi/* -e applications/external/dap_link/lib/free-dap diff --git a/SConstruct b/SConstruct index 62e37dfd..090a9259 100644 --- a/SConstruct +++ b/SConstruct @@ -139,34 +139,33 @@ if GetOption("fullenv") or any( basic_dist = distenv.DistCommand("fw_dist", distenv["DIST_DEPENDS"]) distenv.Default(basic_dist) -dist_dir = distenv.GetProjetDirName() +dist_dir_name = distenv.GetProjetDirName() +dist_dir = distenv.Dir(f"#/dist/{dist_dir_name}") +external_apps_artifacts = firmware_env["FW_EXTAPPS"] +external_app_list = external_apps_artifacts.application_map.values() + fap_dist = [ distenv.Install( - distenv.Dir(f"#/dist/{dist_dir}/apps/debug_elf"), - list( - app_artifact.debug - for app_artifact in firmware_env["FW_EXTAPPS"].applications.values() - ), + dist_dir.Dir("debug_elf"), + list(app_artifact.debug for app_artifact in external_app_list), ), *( distenv.Install( - f"#/dist/{dist_dir}/apps/{app_artifact.app.fap_category}", - app_artifact.compact[0], + dist_dir.File(dist_entry[1]).dir, + app_artifact.compact, ) - for app_artifact in firmware_env["FW_EXTAPPS"].applications.values() + for app_artifact in external_app_list + for dist_entry in app_artifact.dist_entries ), ] Depends( fap_dist, - list( - app_artifact.validator - for app_artifact in firmware_env["FW_EXTAPPS"].applications.values() - ), + list(app_artifact.validator for app_artifact in external_app_list), ) Alias("fap_dist", fap_dist) # distenv.Default(fap_dist) -distenv.Depends(firmware_env["FW_RESOURCES"], firmware_env["FW_EXTAPPS"].resources_dist) +distenv.Depends(firmware_env["FW_RESOURCES"], external_apps_artifacts.resources_dist) # Copy all faps to device diff --git a/applications/examples/application.fam b/applications/examples/application.fam index 8556714c..347411fa 100644 --- a/applications/examples/application.fam +++ b/applications/examples/application.fam @@ -1,3 +1,4 @@ +# Placeholder App( appid="example_apps", name="Example apps bundle", diff --git a/applications/examples/example_plugins/application.fam b/applications/examples/example_plugins/application.fam new file mode 100644 index 00000000..a6e3c207 --- /dev/null +++ b/applications/examples/example_plugins/application.fam @@ -0,0 +1,31 @@ +App( + appid="example_plugins", + name="Example: App w/plugin", + apptype=FlipperAppType.EXTERNAL, + entry_point="example_plugins_app", + stack_size=2 * 1024, + fap_category="Examples", +) + +App( + appid="example_plugins_multi", + name="Example: App w/plugins", + apptype=FlipperAppType.EXTERNAL, + entry_point="example_plugins_multi_app", + stack_size=2 * 1024, + fap_category="Examples", +) + +App( + appid="example_plugin1", + apptype=FlipperAppType.PLUGIN, + entry_point="example_plugin1_ep", + requires=["example_plugins", "example_plugins_multi"], +) + +App( + appid="example_plugin2", + apptype=FlipperAppType.PLUGIN, + entry_point="example_plugin2_ep", + requires=["example_plugins_multi"], +) diff --git a/applications/examples/example_plugins/example_plugins.c b/applications/examples/example_plugins/example_plugins.c new file mode 100644 index 00000000..acc5903a --- /dev/null +++ b/applications/examples/example_plugins/example_plugins.c @@ -0,0 +1,70 @@ +/* + * An example of a plugin host application. + * Loads a single plugin and calls its methods. + */ + +#include "plugin_interface.h" + +#include + +#include +#include +#include + +#define TAG "example_plugins" + +int32_t example_plugins_app(void* p) { + UNUSED(p); + + FURI_LOG_I(TAG, "Starting"); + + Storage* storage = furi_record_open(RECORD_STORAGE); + + FlipperApplication* app = flipper_application_alloc(storage, firmware_api_interface); + + do { + FlipperApplicationPreloadStatus preload_res = + flipper_application_preload(app, APP_DATA_PATH("plugins/example_plugin1.fal")); + + if(preload_res != FlipperApplicationPreloadStatusSuccess) { + FURI_LOG_E(TAG, "Failed to preload plugin"); + break; + } + + if(!flipper_application_is_plugin(app)) { + FURI_LOG_E(TAG, "Plugin file is not a library"); + break; + } + + FlipperApplicationLoadStatus load_status = flipper_application_map_to_memory(app); + if(load_status != FlipperApplicationLoadStatusSuccess) { + FURI_LOG_E(TAG, "Failed to load plugin file"); + break; + } + + const FlipperAppPluginDescriptor* app_descriptor = + flipper_application_plugin_get_descriptor(app); + + FURI_LOG_I( + TAG, + "Loaded plugin for appid '%s', API %lu", + app_descriptor->appid, + app_descriptor->ep_api_version); + + furi_check(app_descriptor->ep_api_version == PLUGIN_API_VERSION); + furi_check(strcmp(app_descriptor->appid, PLUGIN_APP_ID) == 0); + + const ExamplePlugin* plugin = app_descriptor->entry_point; + + FURI_LOG_I(TAG, "Plugin name: %s", plugin->name); + FURI_LOG_I(TAG, "Plugin method1: %d", plugin->method1()); + FURI_LOG_I(TAG, "Plugin method2(7,8): %d", plugin->method2(7, 8)); + FURI_LOG_I(TAG, "Plugin method2(1337,228): %d", plugin->method2(1337, 228)); + } while(false); + flipper_application_free(app); + + furi_record_close(RECORD_STORAGE); + FURI_LOG_I(TAG, "Goodbye!"); + + return 0; +} diff --git a/applications/examples/example_plugins/example_plugins_multi.c b/applications/examples/example_plugins/example_plugins_multi.c new file mode 100644 index 00000000..12eba01c --- /dev/null +++ b/applications/examples/example_plugins/example_plugins_multi.c @@ -0,0 +1,43 @@ +/* + * An example of an advanced plugin host application. + * It uses PluginManager to load all plugins from a directory + */ + +#include "plugin_interface.h" + +#include +#include +#include + +#include + +#define TAG "example_plugins" + +int32_t example_plugins_multi_app(void* p) { + UNUSED(p); + + FURI_LOG_I(TAG, "Starting"); + + PluginManager* manager = + plugin_manager_alloc(PLUGIN_APP_ID, PLUGIN_API_VERSION, firmware_api_interface); + + if(plugin_manager_load_all(manager, APP_DATA_PATH("plugins")) != PluginManagerErrorNone) { + FURI_LOG_E(TAG, "Failed to load all libs"); + return 0; + } + + uint32_t plugin_count = plugin_manager_get_count(manager); + FURI_LOG_I(TAG, "Loaded %lu plugin(s)", plugin_count); + + for(uint32_t i = 0; i < plugin_count; i++) { + const ExamplePlugin* plugin = plugin_manager_get_ep(manager, i); + FURI_LOG_I(TAG, "plugin name: %s", plugin->name); + FURI_LOG_I(TAG, "plugin method1: %d", plugin->method1()); + FURI_LOG_I(TAG, "plugin method2(7,8): %d", plugin->method2(7, 8)); + } + + plugin_manager_free(manager); + FURI_LOG_I(TAG, "Goodbye!"); + + return 0; +} diff --git a/applications/examples/example_plugins/plugin1.c b/applications/examples/example_plugins/plugin1.c new file mode 100644 index 00000000..15621935 --- /dev/null +++ b/applications/examples/example_plugins/plugin1.c @@ -0,0 +1,32 @@ +/* A simple plugin implementing example_plugins application's plugin interface */ + +#include "plugin_interface.h" + +#include + +static int example_plugin1_method1() { + return 42; +} + +static int example_plugin1_method2(int arg1, int arg2) { + return arg1 + arg2; +} + +/* Actual implementation of app<>plugin interface */ +static const ExamplePlugin example_plugin1 = { + .name = "Demo App Plugin 1", + .method1 = &example_plugin1_method1, + .method2 = &example_plugin1_method2, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor example_plugin1_descriptor = { + .appid = PLUGIN_APP_ID, + .ep_api_version = PLUGIN_API_VERSION, + .entry_point = &example_plugin1, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* example_plugin1_ep() { + return &example_plugin1_descriptor; +} diff --git a/applications/examples/example_plugins/plugin2.c b/applications/examples/example_plugins/plugin2.c new file mode 100644 index 00000000..0b774dad --- /dev/null +++ b/applications/examples/example_plugins/plugin2.c @@ -0,0 +1,32 @@ +/* Second plugin implementing example_plugins application's plugin interface */ + +#include "plugin_interface.h" + +#include + +static int example_plugin2_method1() { + return 1337; +} + +static int example_plugin2_method2(int arg1, int arg2) { + return arg1 - arg2; +} + +/* Actual implementation of app<>plugin interface */ +static const ExamplePlugin example_plugin2 = { + .name = "Demo App Plugin 2", + .method1 = &example_plugin2_method1, + .method2 = &example_plugin2_method2, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor example_plugin2_descriptor = { + .appid = PLUGIN_APP_ID, + .ep_api_version = PLUGIN_API_VERSION, + .entry_point = &example_plugin2, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* example_plugin2_ep() { + return &example_plugin2_descriptor; +} diff --git a/applications/examples/example_plugins/plugin_interface.h b/applications/examples/example_plugins/plugin_interface.h new file mode 100644 index 00000000..e24bc47b --- /dev/null +++ b/applications/examples/example_plugins/plugin_interface.h @@ -0,0 +1,12 @@ +#pragma once + +/* Common interface between a plugin and host applicaion */ + +#define PLUGIN_APP_ID "example_plugins" +#define PLUGIN_API_VERSION 1 + +typedef struct { + const char* name; + int (*method1)(); + int (*method2)(int, int); +} ExamplePlugin; diff --git a/applications/examples/example_plugins_advanced/app_api.c b/applications/examples/example_plugins_advanced/app_api.c new file mode 100644 index 00000000..42b3a186 --- /dev/null +++ b/applications/examples/example_plugins_advanced/app_api.c @@ -0,0 +1,25 @@ +#include "app_api.h" + +/* Actual implementation of app's API and its private state */ + +static uint32_t accumulator = 0; + +void app_api_accumulator_set(uint32_t value) { + accumulator = value; +} + +uint32_t app_api_accumulator_get() { + return accumulator; +} + +void app_api_accumulator_add(uint32_t value) { + accumulator += value; +} + +void app_api_accumulator_sub(uint32_t value) { + accumulator -= value; +} + +void app_api_accumulator_mul(uint32_t value) { + accumulator *= value; +} diff --git a/applications/examples/example_plugins_advanced/app_api.h b/applications/examples/example_plugins_advanced/app_api.h new file mode 100644 index 00000000..7035b79f --- /dev/null +++ b/applications/examples/example_plugins_advanced/app_api.h @@ -0,0 +1,25 @@ +#pragma once + +/* + * This file contains an API that is internally implemented by the application + * It is also exposed to plugins to allow them to use the application's API. + */ +#include + +#ifdef __cplusplus +extern "C" { +#endif + +void app_api_accumulator_set(uint32_t value); + +uint32_t app_api_accumulator_get(); + +void app_api_accumulator_add(uint32_t value); + +void app_api_accumulator_sub(uint32_t value); + +void app_api_accumulator_mul(uint32_t value); + +#ifdef __cplusplus +} +#endif diff --git a/applications/examples/example_plugins_advanced/app_api_interface.h b/applications/examples/example_plugins_advanced/app_api_interface.h new file mode 100644 index 00000000..d0db44c4 --- /dev/null +++ b/applications/examples/example_plugins_advanced/app_api_interface.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +/* + * Resolver interface with private application's symbols. + * Implementation is contained in app_api_table.c + */ +extern const ElfApiInterface* const application_api_interface; \ No newline at end of file diff --git a/applications/examples/example_plugins_advanced/app_api_table.cpp b/applications/examples/example_plugins_advanced/app_api_table.cpp new file mode 100644 index 00000000..aacfb8c1 --- /dev/null +++ b/applications/examples/example_plugins_advanced/app_api_table.cpp @@ -0,0 +1,27 @@ +#include +#include + +/* + * This file contains an implementation of a symbol table + * with private app's symbols. It is used by composite API resolver + * to load plugins that use internal application's APIs. + */ +#include "app_api_table_i.h" + +static_assert(!has_hash_collisions(app_api_table), "Detected API method hash collision!"); + +constexpr HashtableApiInterface applicaton_hashtable_api_interface{ + { + .api_version_major = 0, + .api_version_minor = 0, + /* generic resolver using pre-sorted array */ + .resolver_callback = &elf_resolve_from_hashtable, + }, + /* pointers to application's API table boundaries */ + .table_cbegin = app_api_table.cbegin(), + .table_cend = app_api_table.cend(), +}; + +/* Casting to generic resolver to use in Composite API resolver */ +extern "C" const ElfApiInterface* const application_api_interface = + &applicaton_hashtable_api_interface; diff --git a/applications/examples/example_plugins_advanced/app_api_table_i.h b/applications/examples/example_plugins_advanced/app_api_table_i.h new file mode 100644 index 00000000..17cc8be5 --- /dev/null +++ b/applications/examples/example_plugins_advanced/app_api_table_i.h @@ -0,0 +1,13 @@ +#include "app_api.h" + +/* + * A list of app's private functions and objects to expose for plugins. + * It is used to generate a table of symbols for import resolver to use. + * TBD: automatically generate this table from app's header files + */ +static constexpr auto app_api_table = sort(create_array_t( + API_METHOD(app_api_accumulator_set, void, (uint32_t)), + API_METHOD(app_api_accumulator_get, uint32_t, ()), + API_METHOD(app_api_accumulator_add, void, (uint32_t)), + API_METHOD(app_api_accumulator_sub, void, (uint32_t)), + API_METHOD(app_api_accumulator_mul, void, (uint32_t)))); \ No newline at end of file diff --git a/applications/examples/example_plugins_advanced/application.fam b/applications/examples/example_plugins_advanced/application.fam new file mode 100644 index 00000000..d40c0dde --- /dev/null +++ b/applications/examples/example_plugins_advanced/application.fam @@ -0,0 +1,24 @@ +App( + appid="example_advanced_plugins", + name="Example: advanced plugins", + apptype=FlipperAppType.EXTERNAL, + entry_point="example_advanced_plugins_app", + stack_size=2 * 1024, + fap_category="Examples", +) + +App( + appid="advanced_plugin1", + apptype=FlipperAppType.PLUGIN, + entry_point="advanced_plugin1_ep", + requires=["example_advanced_plugins"], + sources=["plugin1.c"], +) + +App( + appid="advanced_plugin2", + apptype=FlipperAppType.PLUGIN, + entry_point="advanced_plugin2_ep", + requires=["example_advanced_plugins"], + sources=["plugin2.c"], +) diff --git a/applications/examples/example_plugins_advanced/example_advanced_plugins.c b/applications/examples/example_plugins_advanced/example_advanced_plugins.c new file mode 100644 index 00000000..f27b0a08 --- /dev/null +++ b/applications/examples/example_plugins_advanced/example_advanced_plugins.c @@ -0,0 +1,48 @@ +#include "app_api.h" +#include "plugin_interface.h" +#include "app_api_interface.h" + +#include +#include +#include + +#include + +#define TAG "example_advanced_plugins" + +int32_t example_advanced_plugins_app(void* p) { + UNUSED(p); + + FURI_LOG_I(TAG, "Starting"); + + CompositeApiResolver* resolver = composite_api_resolver_alloc(); + composite_api_resolver_add(resolver, firmware_api_interface); + composite_api_resolver_add(resolver, application_api_interface); + + PluginManager* manager = plugin_manager_alloc( + PLUGIN_APP_ID, PLUGIN_API_VERSION, composite_api_resolver_get(resolver)); + + do { + if(plugin_manager_load_all(manager, APP_DATA_PATH("plugins")) != PluginManagerErrorNone) { + FURI_LOG_E(TAG, "Failed to load all libs"); + break; + } + + uint32_t plugin_count = plugin_manager_get_count(manager); + FURI_LOG_I(TAG, "Loaded libs: %lu", plugin_count); + + for(uint32_t i = 0; i < plugin_count; i++) { + const AdvancedPlugin* plugin = plugin_manager_get_ep(manager, i); + FURI_LOG_I(TAG, "plugin name: %s. Calling methods", plugin->name); + plugin->method1(228); + plugin->method2(); + FURI_LOG_I(TAG, "Accumulator: %lu", app_api_accumulator_get()); + } + } while(0); + + plugin_manager_free(manager); + composite_api_resolver_free(resolver); + FURI_LOG_I(TAG, "Goodbye!"); + + return 0; +} diff --git a/applications/examples/example_plugins_advanced/plugin1.c b/applications/examples/example_plugins_advanced/plugin1.c new file mode 100644 index 00000000..bf0ab50b --- /dev/null +++ b/applications/examples/example_plugins_advanced/plugin1.c @@ -0,0 +1,40 @@ +/* + * This plugin uses both firmware's API interface and private application headers. + * It can be loaded by a plugin manager that uses CompoundApiInterface, + * which combines both interfaces. + */ + +#include "app_api.h" +#include "plugin_interface.h" + +#include +#include + +static void advanced_plugin1_method1(int arg1) { + /* This function is implemented inside host application */ + app_api_accumulator_add(arg1); +} + +static void advanced_plugin1_method2() { + /* Accumulator value is stored inside host application */ + FURI_LOG_I("TEST", "Plugin 1, accumulator: %lu", app_api_accumulator_get()); +} + +/* Actual implementation of app<>plugin interface */ +static const AdvancedPlugin advanced_plugin1 = { + .name = "Advanced Plugin 1", + .method1 = &advanced_plugin1_method1, + .method2 = &advanced_plugin1_method2, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor advanced_plugin1_descriptor = { + .appid = PLUGIN_APP_ID, + .ep_api_version = PLUGIN_API_VERSION, + .entry_point = &advanced_plugin1, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* advanced_plugin1_ep() { + return &advanced_plugin1_descriptor; +} diff --git a/applications/examples/example_plugins_advanced/plugin2.c b/applications/examples/example_plugins_advanced/plugin2.c new file mode 100644 index 00000000..f0b2f726 --- /dev/null +++ b/applications/examples/example_plugins_advanced/plugin2.c @@ -0,0 +1,40 @@ +/* + * This plugin uses both firmware's API interface and private application headers. + * It can be loaded by a plugin manager that uses CompoundApiInterface, + * which combines both interfaces. + */ + +#include "app_api.h" +#include "plugin_interface.h" + +#include +#include + +static void advanced_plugin2_method1(int arg1) { + /* This function is implemented inside host application */ + app_api_accumulator_mul(arg1); +} + +static void advanced_plugin2_method2() { + /* Accumulator value is stored inside host application */ + FURI_LOG_I("TEST", "Plugin 2, accumulator: %lu", app_api_accumulator_get()); +} + +/* Actual implementation of app<>plugin interface */ +static const AdvancedPlugin advanced_plugin2 = { + .name = "Advanced Plugin 2", + .method1 = &advanced_plugin2_method1, + .method2 = &advanced_plugin2_method2, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor advanced_plugin2_descriptor = { + .appid = PLUGIN_APP_ID, + .ep_api_version = PLUGIN_API_VERSION, + .entry_point = &advanced_plugin2, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* advanced_plugin2_ep() { + return &advanced_plugin2_descriptor; +} diff --git a/applications/examples/example_plugins_advanced/plugin_interface.h b/applications/examples/example_plugins_advanced/plugin_interface.h new file mode 100644 index 00000000..e8b5a22d --- /dev/null +++ b/applications/examples/example_plugins_advanced/plugin_interface.h @@ -0,0 +1,12 @@ +#pragma once + +/* Common interface between a plugin and host applicaion */ + +#define PLUGIN_APP_ID "example_plugins_advanced" +#define PLUGIN_API_VERSION 1 + +typedef struct { + const char* name; + void (*method1)(int); + void (*method2)(); +} AdvancedPlugin; diff --git a/applications/external/application.fam b/applications/external/application.fam new file mode 100644 index 00000000..12dc1cc1 --- /dev/null +++ b/applications/external/application.fam @@ -0,0 +1,6 @@ +# Placeholder +App( + appid="external_apps", + name="External apps bundle", + apptype=FlipperAppType.METAPACKAGE, +) diff --git a/applications/plugins/clock/application.fam b/applications/external/clock/application.fam similarity index 82% rename from applications/plugins/clock/application.fam rename to applications/external/clock/application.fam index 590f5dfe..a6a2eff3 100644 --- a/applications/plugins/clock/application.fam +++ b/applications/external/clock/application.fam @@ -1,7 +1,7 @@ App( appid="clock", name="Clock", - apptype=FlipperAppType.PLUGIN, + apptype=FlipperAppType.EXTERNAL, entry_point="clock_app", requires=["gui"], stack_size=2 * 1024, diff --git a/applications/plugins/clock/clock.png b/applications/external/clock/clock.png similarity index 100% rename from applications/plugins/clock/clock.png rename to applications/external/clock/clock.png diff --git a/applications/plugins/clock/clock_app.c b/applications/external/clock/clock_app.c similarity index 100% rename from applications/plugins/clock/clock_app.c rename to applications/external/clock/clock_app.c diff --git a/applications/plugins/dap_link/README.md b/applications/external/dap_link/README.md similarity index 100% rename from applications/plugins/dap_link/README.md rename to applications/external/dap_link/README.md diff --git a/applications/plugins/dap_link/application.fam b/applications/external/dap_link/application.fam similarity index 92% rename from applications/plugins/dap_link/application.fam rename to applications/external/dap_link/application.fam index 711e4833..01714380 100644 --- a/applications/plugins/dap_link/application.fam +++ b/applications/external/dap_link/application.fam @@ -1,7 +1,7 @@ App( appid="dap_link", name="DAP Link", - apptype=FlipperAppType.PLUGIN, + apptype=FlipperAppType.EXTERNAL, entry_point="dap_link_app", requires=[ "gui", diff --git a/applications/plugins/dap_link/dap_config.h b/applications/external/dap_link/dap_config.h similarity index 100% rename from applications/plugins/dap_link/dap_config.h rename to applications/external/dap_link/dap_config.h diff --git a/applications/plugins/dap_link/dap_link.c b/applications/external/dap_link/dap_link.c similarity index 100% rename from applications/plugins/dap_link/dap_link.c rename to applications/external/dap_link/dap_link.c diff --git a/applications/plugins/dap_link/dap_link.h b/applications/external/dap_link/dap_link.h similarity index 100% rename from applications/plugins/dap_link/dap_link.h rename to applications/external/dap_link/dap_link.h diff --git a/applications/plugins/dap_link/dap_link.png b/applications/external/dap_link/dap_link.png similarity index 100% rename from applications/plugins/dap_link/dap_link.png rename to applications/external/dap_link/dap_link.png diff --git a/applications/plugins/dap_link/gui/dap_gui.c b/applications/external/dap_link/gui/dap_gui.c similarity index 100% rename from applications/plugins/dap_link/gui/dap_gui.c rename to applications/external/dap_link/gui/dap_gui.c diff --git a/applications/plugins/dap_link/gui/dap_gui.h b/applications/external/dap_link/gui/dap_gui.h similarity index 100% rename from applications/plugins/dap_link/gui/dap_gui.h rename to applications/external/dap_link/gui/dap_gui.h diff --git a/applications/plugins/dap_link/gui/dap_gui_custom_event.h b/applications/external/dap_link/gui/dap_gui_custom_event.h similarity index 100% rename from applications/plugins/dap_link/gui/dap_gui_custom_event.h rename to applications/external/dap_link/gui/dap_gui_custom_event.h diff --git a/applications/plugins/dap_link/gui/dap_gui_i.h b/applications/external/dap_link/gui/dap_gui_i.h similarity index 100% rename from applications/plugins/dap_link/gui/dap_gui_i.h rename to applications/external/dap_link/gui/dap_gui_i.h diff --git a/applications/plugins/dap_link/gui/scenes/config/dap_scene.c b/applications/external/dap_link/gui/scenes/config/dap_scene.c similarity index 100% rename from applications/plugins/dap_link/gui/scenes/config/dap_scene.c rename to applications/external/dap_link/gui/scenes/config/dap_scene.c diff --git a/applications/plugins/dap_link/gui/scenes/config/dap_scene.h b/applications/external/dap_link/gui/scenes/config/dap_scene.h similarity index 100% rename from applications/plugins/dap_link/gui/scenes/config/dap_scene.h rename to applications/external/dap_link/gui/scenes/config/dap_scene.h diff --git a/applications/plugins/dap_link/gui/scenes/config/dap_scene_config.h b/applications/external/dap_link/gui/scenes/config/dap_scene_config.h similarity index 100% rename from applications/plugins/dap_link/gui/scenes/config/dap_scene_config.h rename to applications/external/dap_link/gui/scenes/config/dap_scene_config.h diff --git a/applications/plugins/dap_link/gui/scenes/dap_scene_about.c b/applications/external/dap_link/gui/scenes/dap_scene_about.c similarity index 100% rename from applications/plugins/dap_link/gui/scenes/dap_scene_about.c rename to applications/external/dap_link/gui/scenes/dap_scene_about.c diff --git a/applications/plugins/dap_link/gui/scenes/dap_scene_config.c b/applications/external/dap_link/gui/scenes/dap_scene_config.c similarity index 100% rename from applications/plugins/dap_link/gui/scenes/dap_scene_config.c rename to applications/external/dap_link/gui/scenes/dap_scene_config.c diff --git a/applications/plugins/dap_link/gui/scenes/dap_scene_help.c b/applications/external/dap_link/gui/scenes/dap_scene_help.c similarity index 100% rename from applications/plugins/dap_link/gui/scenes/dap_scene_help.c rename to applications/external/dap_link/gui/scenes/dap_scene_help.c diff --git a/applications/plugins/dap_link/gui/scenes/dap_scene_main.c b/applications/external/dap_link/gui/scenes/dap_scene_main.c similarity index 100% rename from applications/plugins/dap_link/gui/scenes/dap_scene_main.c rename to applications/external/dap_link/gui/scenes/dap_scene_main.c diff --git a/applications/plugins/dap_link/gui/views/dap_main_view.c b/applications/external/dap_link/gui/views/dap_main_view.c similarity index 100% rename from applications/plugins/dap_link/gui/views/dap_main_view.c rename to applications/external/dap_link/gui/views/dap_main_view.c diff --git a/applications/plugins/dap_link/gui/views/dap_main_view.h b/applications/external/dap_link/gui/views/dap_main_view.h similarity index 100% rename from applications/plugins/dap_link/gui/views/dap_main_view.h rename to applications/external/dap_link/gui/views/dap_main_view.h diff --git a/applications/plugins/dap_link/icons/ActiveConnection_50x64.png b/applications/external/dap_link/icons/ActiveConnection_50x64.png similarity index 100% rename from applications/plugins/dap_link/icons/ActiveConnection_50x64.png rename to applications/external/dap_link/icons/ActiveConnection_50x64.png diff --git a/applications/plugins/dap_link/icons/ArrowDownEmpty_12x18.png b/applications/external/dap_link/icons/ArrowDownEmpty_12x18.png similarity index 100% rename from applications/plugins/dap_link/icons/ArrowDownEmpty_12x18.png rename to applications/external/dap_link/icons/ArrowDownEmpty_12x18.png diff --git a/applications/plugins/dap_link/icons/ArrowDownFilled_12x18.png b/applications/external/dap_link/icons/ArrowDownFilled_12x18.png similarity index 100% rename from applications/plugins/dap_link/icons/ArrowDownFilled_12x18.png rename to applications/external/dap_link/icons/ArrowDownFilled_12x18.png diff --git a/applications/plugins/dap_link/icons/ArrowUpEmpty_12x18.png b/applications/external/dap_link/icons/ArrowUpEmpty_12x18.png similarity index 100% rename from applications/plugins/dap_link/icons/ArrowUpEmpty_12x18.png rename to applications/external/dap_link/icons/ArrowUpEmpty_12x18.png diff --git a/applications/plugins/dap_link/icons/ArrowUpFilled_12x18.png b/applications/external/dap_link/icons/ArrowUpFilled_12x18.png similarity index 100% rename from applications/plugins/dap_link/icons/ArrowUpFilled_12x18.png rename to applications/external/dap_link/icons/ArrowUpFilled_12x18.png diff --git a/applications/plugins/dap_link/lib/free-dap b/applications/external/dap_link/lib/free-dap similarity index 100% rename from applications/plugins/dap_link/lib/free-dap rename to applications/external/dap_link/lib/free-dap diff --git a/applications/plugins/dap_link/usb/dap_v2_usb.c b/applications/external/dap_link/usb/dap_v2_usb.c similarity index 100% rename from applications/plugins/dap_link/usb/dap_v2_usb.c rename to applications/external/dap_link/usb/dap_v2_usb.c diff --git a/applications/plugins/dap_link/usb/dap_v2_usb.h b/applications/external/dap_link/usb/dap_v2_usb.h similarity index 100% rename from applications/plugins/dap_link/usb/dap_v2_usb.h rename to applications/external/dap_link/usb/dap_v2_usb.h diff --git a/applications/plugins/dap_link/usb/usb_winusb.h b/applications/external/dap_link/usb/usb_winusb.h similarity index 100% rename from applications/plugins/dap_link/usb/usb_winusb.h rename to applications/external/dap_link/usb/usb_winusb.h diff --git a/applications/plugins/hid_app/application.fam b/applications/external/hid_app/application.fam similarity index 86% rename from applications/plugins/hid_app/application.fam rename to applications/external/hid_app/application.fam index b6e4e3bf..a9d8305d 100644 --- a/applications/plugins/hid_app/application.fam +++ b/applications/external/hid_app/application.fam @@ -1,7 +1,7 @@ App( appid="hid_usb", name="Remote", - apptype=FlipperAppType.PLUGIN, + apptype=FlipperAppType.EXTERNAL, entry_point="hid_usb_app", stack_size=1 * 1024, fap_category="USB", @@ -14,7 +14,7 @@ App( App( appid="hid_ble", name="Remote", - apptype=FlipperAppType.PLUGIN, + apptype=FlipperAppType.EXTERNAL, entry_point="hid_ble_app", stack_size=1 * 1024, fap_category="Bluetooth", diff --git a/applications/plugins/hid_app/assets/Arr_dwn_7x9.png b/applications/external/hid_app/assets/Arr_dwn_7x9.png similarity index 100% rename from applications/plugins/hid_app/assets/Arr_dwn_7x9.png rename to applications/external/hid_app/assets/Arr_dwn_7x9.png diff --git a/applications/plugins/hid_app/assets/Arr_up_7x9.png b/applications/external/hid_app/assets/Arr_up_7x9.png similarity index 100% rename from applications/plugins/hid_app/assets/Arr_up_7x9.png rename to applications/external/hid_app/assets/Arr_up_7x9.png diff --git a/applications/plugins/hid_app/assets/Ble_connected_15x15.png b/applications/external/hid_app/assets/Ble_connected_15x15.png similarity index 100% rename from applications/plugins/hid_app/assets/Ble_connected_15x15.png rename to applications/external/hid_app/assets/Ble_connected_15x15.png diff --git a/applications/plugins/hid_app/assets/Ble_disconnected_15x15.png b/applications/external/hid_app/assets/Ble_disconnected_15x15.png similarity index 100% rename from applications/plugins/hid_app/assets/Ble_disconnected_15x15.png rename to applications/external/hid_app/assets/Ble_disconnected_15x15.png diff --git a/applications/plugins/hid_app/assets/ButtonDown_7x4.png b/applications/external/hid_app/assets/ButtonDown_7x4.png similarity index 100% rename from applications/plugins/hid_app/assets/ButtonDown_7x4.png rename to applications/external/hid_app/assets/ButtonDown_7x4.png diff --git a/applications/plugins/hid_app/assets/ButtonF10_5x8.png b/applications/external/hid_app/assets/ButtonF10_5x8.png similarity index 100% rename from applications/plugins/hid_app/assets/ButtonF10_5x8.png rename to applications/external/hid_app/assets/ButtonF10_5x8.png diff --git a/applications/plugins/hid_app/assets/ButtonF11_5x8.png b/applications/external/hid_app/assets/ButtonF11_5x8.png similarity index 100% rename from applications/plugins/hid_app/assets/ButtonF11_5x8.png rename to applications/external/hid_app/assets/ButtonF11_5x8.png diff --git a/applications/plugins/hid_app/assets/ButtonF12_5x8.png b/applications/external/hid_app/assets/ButtonF12_5x8.png similarity index 100% rename from applications/plugins/hid_app/assets/ButtonF12_5x8.png rename to applications/external/hid_app/assets/ButtonF12_5x8.png diff --git a/applications/plugins/hid_app/assets/ButtonF1_5x8.png b/applications/external/hid_app/assets/ButtonF1_5x8.png similarity index 100% rename from applications/plugins/hid_app/assets/ButtonF1_5x8.png rename to applications/external/hid_app/assets/ButtonF1_5x8.png diff --git a/applications/plugins/hid_app/assets/ButtonF2_5x8.png b/applications/external/hid_app/assets/ButtonF2_5x8.png similarity index 100% rename from applications/plugins/hid_app/assets/ButtonF2_5x8.png rename to applications/external/hid_app/assets/ButtonF2_5x8.png diff --git a/applications/plugins/hid_app/assets/ButtonF3_5x8.png b/applications/external/hid_app/assets/ButtonF3_5x8.png similarity index 100% rename from applications/plugins/hid_app/assets/ButtonF3_5x8.png rename to applications/external/hid_app/assets/ButtonF3_5x8.png diff --git a/applications/plugins/hid_app/assets/ButtonF4_5x8.png b/applications/external/hid_app/assets/ButtonF4_5x8.png similarity index 100% rename from applications/plugins/hid_app/assets/ButtonF4_5x8.png rename to applications/external/hid_app/assets/ButtonF4_5x8.png diff --git a/applications/plugins/hid_app/assets/ButtonF5_5x8.png b/applications/external/hid_app/assets/ButtonF5_5x8.png similarity index 100% rename from applications/plugins/hid_app/assets/ButtonF5_5x8.png rename to applications/external/hid_app/assets/ButtonF5_5x8.png diff --git a/applications/plugins/hid_app/assets/ButtonF6_5x8.png b/applications/external/hid_app/assets/ButtonF6_5x8.png similarity index 100% rename from applications/plugins/hid_app/assets/ButtonF6_5x8.png rename to applications/external/hid_app/assets/ButtonF6_5x8.png diff --git a/applications/plugins/hid_app/assets/ButtonF7_5x8.png b/applications/external/hid_app/assets/ButtonF7_5x8.png similarity index 100% rename from applications/plugins/hid_app/assets/ButtonF7_5x8.png rename to applications/external/hid_app/assets/ButtonF7_5x8.png diff --git a/applications/plugins/hid_app/assets/ButtonF8_5x8.png b/applications/external/hid_app/assets/ButtonF8_5x8.png similarity index 100% rename from applications/plugins/hid_app/assets/ButtonF8_5x8.png rename to applications/external/hid_app/assets/ButtonF8_5x8.png diff --git a/applications/plugins/hid_app/assets/ButtonF9_5x8.png b/applications/external/hid_app/assets/ButtonF9_5x8.png similarity index 100% rename from applications/plugins/hid_app/assets/ButtonF9_5x8.png rename to applications/external/hid_app/assets/ButtonF9_5x8.png diff --git a/applications/plugins/hid_app/assets/ButtonLeft_4x7.png b/applications/external/hid_app/assets/ButtonLeft_4x7.png similarity index 100% rename from applications/plugins/hid_app/assets/ButtonLeft_4x7.png rename to applications/external/hid_app/assets/ButtonLeft_4x7.png diff --git a/applications/plugins/hid_app/assets/ButtonRight_4x7.png b/applications/external/hid_app/assets/ButtonRight_4x7.png similarity index 100% rename from applications/plugins/hid_app/assets/ButtonRight_4x7.png rename to applications/external/hid_app/assets/ButtonRight_4x7.png diff --git a/applications/plugins/hid_app/assets/ButtonUp_7x4.png b/applications/external/hid_app/assets/ButtonUp_7x4.png similarity index 100% rename from applications/plugins/hid_app/assets/ButtonUp_7x4.png rename to applications/external/hid_app/assets/ButtonUp_7x4.png diff --git a/applications/plugins/hid_app/assets/Button_18x18.png b/applications/external/hid_app/assets/Button_18x18.png similarity index 100% rename from applications/plugins/hid_app/assets/Button_18x18.png rename to applications/external/hid_app/assets/Button_18x18.png diff --git a/applications/plugins/hid_app/assets/Circles_47x47.png b/applications/external/hid_app/assets/Circles_47x47.png similarity index 100% rename from applications/plugins/hid_app/assets/Circles_47x47.png rename to applications/external/hid_app/assets/Circles_47x47.png diff --git a/applications/plugins/hid_app/assets/Left_mouse_icon_9x9.png b/applications/external/hid_app/assets/Left_mouse_icon_9x9.png similarity index 100% rename from applications/plugins/hid_app/assets/Left_mouse_icon_9x9.png rename to applications/external/hid_app/assets/Left_mouse_icon_9x9.png diff --git a/applications/plugins/hid_app/assets/Like_def_11x9.png b/applications/external/hid_app/assets/Like_def_11x9.png similarity index 100% rename from applications/plugins/hid_app/assets/Like_def_11x9.png rename to applications/external/hid_app/assets/Like_def_11x9.png diff --git a/applications/plugins/hid_app/assets/Like_pressed_17x17.png b/applications/external/hid_app/assets/Like_pressed_17x17.png similarity index 100% rename from applications/plugins/hid_app/assets/Like_pressed_17x17.png rename to applications/external/hid_app/assets/Like_pressed_17x17.png diff --git a/applications/plugins/hid_app/assets/Ok_btn_9x9.png b/applications/external/hid_app/assets/Ok_btn_9x9.png similarity index 100% rename from applications/plugins/hid_app/assets/Ok_btn_9x9.png rename to applications/external/hid_app/assets/Ok_btn_9x9.png diff --git a/applications/plugins/hid_app/assets/Ok_btn_pressed_13x13.png b/applications/external/hid_app/assets/Ok_btn_pressed_13x13.png similarity index 100% rename from applications/plugins/hid_app/assets/Ok_btn_pressed_13x13.png rename to applications/external/hid_app/assets/Ok_btn_pressed_13x13.png diff --git a/applications/plugins/hid_app/assets/Pin_arrow_down_7x9.png b/applications/external/hid_app/assets/Pin_arrow_down_7x9.png similarity index 100% rename from applications/plugins/hid_app/assets/Pin_arrow_down_7x9.png rename to applications/external/hid_app/assets/Pin_arrow_down_7x9.png diff --git a/applications/plugins/hid_app/assets/Pin_arrow_left_9x7.png b/applications/external/hid_app/assets/Pin_arrow_left_9x7.png similarity index 100% rename from applications/plugins/hid_app/assets/Pin_arrow_left_9x7.png rename to applications/external/hid_app/assets/Pin_arrow_left_9x7.png diff --git a/applications/plugins/hid_app/assets/Pin_arrow_right_9x7.png b/applications/external/hid_app/assets/Pin_arrow_right_9x7.png similarity index 100% rename from applications/plugins/hid_app/assets/Pin_arrow_right_9x7.png rename to applications/external/hid_app/assets/Pin_arrow_right_9x7.png diff --git a/applications/plugins/hid_app/assets/Pin_arrow_up_7x9.png b/applications/external/hid_app/assets/Pin_arrow_up_7x9.png similarity index 100% rename from applications/plugins/hid_app/assets/Pin_arrow_up_7x9.png rename to applications/external/hid_app/assets/Pin_arrow_up_7x9.png diff --git a/applications/plugins/hid_app/assets/Pin_back_arrow_10x8.png b/applications/external/hid_app/assets/Pin_back_arrow_10x8.png similarity index 100% rename from applications/plugins/hid_app/assets/Pin_back_arrow_10x8.png rename to applications/external/hid_app/assets/Pin_back_arrow_10x8.png diff --git a/applications/plugins/hid_app/assets/Pressed_Button_13x13.png b/applications/external/hid_app/assets/Pressed_Button_13x13.png similarity index 100% rename from applications/plugins/hid_app/assets/Pressed_Button_13x13.png rename to applications/external/hid_app/assets/Pressed_Button_13x13.png diff --git a/applications/plugins/hid_app/assets/Right_mouse_icon_9x9.png b/applications/external/hid_app/assets/Right_mouse_icon_9x9.png similarity index 100% rename from applications/plugins/hid_app/assets/Right_mouse_icon_9x9.png rename to applications/external/hid_app/assets/Right_mouse_icon_9x9.png diff --git a/applications/plugins/hid_app/assets/Space_65x18.png b/applications/external/hid_app/assets/Space_65x18.png similarity index 100% rename from applications/plugins/hid_app/assets/Space_65x18.png rename to applications/external/hid_app/assets/Space_65x18.png diff --git a/applications/plugins/hid_app/assets/Voldwn_6x6.png b/applications/external/hid_app/assets/Voldwn_6x6.png similarity index 100% rename from applications/plugins/hid_app/assets/Voldwn_6x6.png rename to applications/external/hid_app/assets/Voldwn_6x6.png diff --git a/applications/plugins/hid_app/assets/Volup_8x6.png b/applications/external/hid_app/assets/Volup_8x6.png similarity index 100% rename from applications/plugins/hid_app/assets/Volup_8x6.png rename to applications/external/hid_app/assets/Volup_8x6.png diff --git a/applications/plugins/hid_app/hid.c b/applications/external/hid_app/hid.c similarity index 100% rename from applications/plugins/hid_app/hid.c rename to applications/external/hid_app/hid.c diff --git a/applications/plugins/hid_app/hid.h b/applications/external/hid_app/hid.h similarity index 100% rename from applications/plugins/hid_app/hid.h rename to applications/external/hid_app/hid.h diff --git a/applications/plugins/hid_app/hid_ble_10px.png b/applications/external/hid_app/hid_ble_10px.png similarity index 100% rename from applications/plugins/hid_app/hid_ble_10px.png rename to applications/external/hid_app/hid_ble_10px.png diff --git a/applications/plugins/hid_app/hid_usb_10px.png b/applications/external/hid_app/hid_usb_10px.png similarity index 100% rename from applications/plugins/hid_app/hid_usb_10px.png rename to applications/external/hid_app/hid_usb_10px.png diff --git a/applications/plugins/hid_app/views.h b/applications/external/hid_app/views.h similarity index 100% rename from applications/plugins/hid_app/views.h rename to applications/external/hid_app/views.h diff --git a/applications/plugins/hid_app/views/hid_keyboard.c b/applications/external/hid_app/views/hid_keyboard.c similarity index 100% rename from applications/plugins/hid_app/views/hid_keyboard.c rename to applications/external/hid_app/views/hid_keyboard.c diff --git a/applications/plugins/hid_app/views/hid_keyboard.h b/applications/external/hid_app/views/hid_keyboard.h similarity index 100% rename from applications/plugins/hid_app/views/hid_keyboard.h rename to applications/external/hid_app/views/hid_keyboard.h diff --git a/applications/plugins/hid_app/views/hid_keynote.c b/applications/external/hid_app/views/hid_keynote.c similarity index 100% rename from applications/plugins/hid_app/views/hid_keynote.c rename to applications/external/hid_app/views/hid_keynote.c diff --git a/applications/plugins/hid_app/views/hid_keynote.h b/applications/external/hid_app/views/hid_keynote.h similarity index 100% rename from applications/plugins/hid_app/views/hid_keynote.h rename to applications/external/hid_app/views/hid_keynote.h diff --git a/applications/plugins/hid_app/views/hid_media.c b/applications/external/hid_app/views/hid_media.c similarity index 100% rename from applications/plugins/hid_app/views/hid_media.c rename to applications/external/hid_app/views/hid_media.c diff --git a/applications/plugins/hid_app/views/hid_media.h b/applications/external/hid_app/views/hid_media.h similarity index 100% rename from applications/plugins/hid_app/views/hid_media.h rename to applications/external/hid_app/views/hid_media.h diff --git a/applications/plugins/hid_app/views/hid_mouse.c b/applications/external/hid_app/views/hid_mouse.c similarity index 100% rename from applications/plugins/hid_app/views/hid_mouse.c rename to applications/external/hid_app/views/hid_mouse.c diff --git a/applications/plugins/hid_app/views/hid_mouse.h b/applications/external/hid_app/views/hid_mouse.h similarity index 100% rename from applications/plugins/hid_app/views/hid_mouse.h rename to applications/external/hid_app/views/hid_mouse.h diff --git a/applications/plugins/hid_app/views/hid_mouse_jiggler.c b/applications/external/hid_app/views/hid_mouse_jiggler.c similarity index 100% rename from applications/plugins/hid_app/views/hid_mouse_jiggler.c rename to applications/external/hid_app/views/hid_mouse_jiggler.c diff --git a/applications/plugins/hid_app/views/hid_mouse_jiggler.h b/applications/external/hid_app/views/hid_mouse_jiggler.h similarity index 100% rename from applications/plugins/hid_app/views/hid_mouse_jiggler.h rename to applications/external/hid_app/views/hid_mouse_jiggler.h diff --git a/applications/plugins/hid_app/views/hid_tiktok.c b/applications/external/hid_app/views/hid_tiktok.c similarity index 100% rename from applications/plugins/hid_app/views/hid_tiktok.c rename to applications/external/hid_app/views/hid_tiktok.c diff --git a/applications/plugins/hid_app/views/hid_tiktok.h b/applications/external/hid_app/views/hid_tiktok.h similarity index 100% rename from applications/plugins/hid_app/views/hid_tiktok.h rename to applications/external/hid_app/views/hid_tiktok.h diff --git a/applications/plugins/music_player/application.fam b/applications/external/music_player/application.fam similarity index 87% rename from applications/plugins/music_player/application.fam rename to applications/external/music_player/application.fam index c51abf19..3414c0a4 100644 --- a/applications/plugins/music_player/application.fam +++ b/applications/external/music_player/application.fam @@ -1,9 +1,8 @@ App( appid="music_player", name="Music Player", - apptype=FlipperAppType.PLUGIN, + apptype=FlipperAppType.EXTERNAL, entry_point="music_player_app", - cdefines=["APP_MUSIC_PLAYER"], requires=[ "gui", "dialogs", diff --git a/applications/plugins/music_player/icons/music_10px.png b/applications/external/music_player/icons/music_10px.png similarity index 100% rename from applications/plugins/music_player/icons/music_10px.png rename to applications/external/music_player/icons/music_10px.png diff --git a/applications/plugins/music_player/music_player.c b/applications/external/music_player/music_player.c similarity index 100% rename from applications/plugins/music_player/music_player.c rename to applications/external/music_player/music_player.c diff --git a/applications/plugins/music_player/music_player_cli.c b/applications/external/music_player/music_player_cli.c similarity index 100% rename from applications/plugins/music_player/music_player_cli.c rename to applications/external/music_player/music_player_cli.c diff --git a/applications/plugins/music_player/music_player_worker.c b/applications/external/music_player/music_player_worker.c similarity index 100% rename from applications/plugins/music_player/music_player_worker.c rename to applications/external/music_player/music_player_worker.c diff --git a/applications/plugins/music_player/music_player_worker.h b/applications/external/music_player/music_player_worker.h similarity index 100% rename from applications/plugins/music_player/music_player_worker.h rename to applications/external/music_player/music_player_worker.h diff --git a/applications/plugins/nfc_magic/application.fam b/applications/external/nfc_magic/application.fam similarity index 100% rename from applications/plugins/nfc_magic/application.fam rename to applications/external/nfc_magic/application.fam diff --git a/applications/plugins/nfc_magic/assets/DolphinCommon_56x48.png b/applications/external/nfc_magic/assets/DolphinCommon_56x48.png similarity index 100% rename from applications/plugins/nfc_magic/assets/DolphinCommon_56x48.png rename to applications/external/nfc_magic/assets/DolphinCommon_56x48.png diff --git a/applications/plugins/nfc_magic/assets/DolphinNice_96x59.png b/applications/external/nfc_magic/assets/DolphinNice_96x59.png similarity index 100% rename from applications/plugins/nfc_magic/assets/DolphinNice_96x59.png rename to applications/external/nfc_magic/assets/DolphinNice_96x59.png diff --git a/applications/plugins/nfc_magic/assets/Loading_24.png b/applications/external/nfc_magic/assets/Loading_24.png similarity index 100% rename from applications/plugins/nfc_magic/assets/Loading_24.png rename to applications/external/nfc_magic/assets/Loading_24.png diff --git a/applications/plugins/nfc_magic/assets/NFC_manual_60x50.png b/applications/external/nfc_magic/assets/NFC_manual_60x50.png similarity index 100% rename from applications/plugins/nfc_magic/assets/NFC_manual_60x50.png rename to applications/external/nfc_magic/assets/NFC_manual_60x50.png diff --git a/applications/plugins/nfc_magic/lib/magic/magic.c b/applications/external/nfc_magic/lib/magic/magic.c similarity index 100% rename from applications/plugins/nfc_magic/lib/magic/magic.c rename to applications/external/nfc_magic/lib/magic/magic.c diff --git a/applications/plugins/nfc_magic/lib/magic/magic.h b/applications/external/nfc_magic/lib/magic/magic.h similarity index 100% rename from applications/plugins/nfc_magic/lib/magic/magic.h rename to applications/external/nfc_magic/lib/magic/magic.h diff --git a/applications/plugins/nfc_magic/nfc_magic.c b/applications/external/nfc_magic/nfc_magic.c similarity index 100% rename from applications/plugins/nfc_magic/nfc_magic.c rename to applications/external/nfc_magic/nfc_magic.c diff --git a/applications/plugins/nfc_magic/nfc_magic.h b/applications/external/nfc_magic/nfc_magic.h similarity index 100% rename from applications/plugins/nfc_magic/nfc_magic.h rename to applications/external/nfc_magic/nfc_magic.h diff --git a/applications/plugins/nfc_magic/nfc_magic_i.h b/applications/external/nfc_magic/nfc_magic_i.h similarity index 100% rename from applications/plugins/nfc_magic/nfc_magic_i.h rename to applications/external/nfc_magic/nfc_magic_i.h diff --git a/applications/plugins/nfc_magic/nfc_magic_worker.c b/applications/external/nfc_magic/nfc_magic_worker.c similarity index 100% rename from applications/plugins/nfc_magic/nfc_magic_worker.c rename to applications/external/nfc_magic/nfc_magic_worker.c diff --git a/applications/plugins/nfc_magic/nfc_magic_worker.h b/applications/external/nfc_magic/nfc_magic_worker.h similarity index 100% rename from applications/plugins/nfc_magic/nfc_magic_worker.h rename to applications/external/nfc_magic/nfc_magic_worker.h diff --git a/applications/plugins/nfc_magic/nfc_magic_worker_i.h b/applications/external/nfc_magic/nfc_magic_worker_i.h similarity index 100% rename from applications/plugins/nfc_magic/nfc_magic_worker_i.h rename to applications/external/nfc_magic/nfc_magic_worker_i.h diff --git a/applications/plugins/nfc_magic/scenes/nfc_magic_scene.c b/applications/external/nfc_magic/scenes/nfc_magic_scene.c similarity index 100% rename from applications/plugins/nfc_magic/scenes/nfc_magic_scene.c rename to applications/external/nfc_magic/scenes/nfc_magic_scene.c diff --git a/applications/plugins/nfc_magic/scenes/nfc_magic_scene.h b/applications/external/nfc_magic/scenes/nfc_magic_scene.h similarity index 100% rename from applications/plugins/nfc_magic/scenes/nfc_magic_scene.h rename to applications/external/nfc_magic/scenes/nfc_magic_scene.h diff --git a/applications/plugins/nfc_magic/scenes/nfc_magic_scene_check.c b/applications/external/nfc_magic/scenes/nfc_magic_scene_check.c similarity index 100% rename from applications/plugins/nfc_magic/scenes/nfc_magic_scene_check.c rename to applications/external/nfc_magic/scenes/nfc_magic_scene_check.c diff --git a/applications/plugins/nfc_magic/scenes/nfc_magic_scene_config.h b/applications/external/nfc_magic/scenes/nfc_magic_scene_config.h similarity index 100% rename from applications/plugins/nfc_magic/scenes/nfc_magic_scene_config.h rename to applications/external/nfc_magic/scenes/nfc_magic_scene_config.h diff --git a/applications/plugins/nfc_magic/scenes/nfc_magic_scene_file_select.c b/applications/external/nfc_magic/scenes/nfc_magic_scene_file_select.c similarity index 100% rename from applications/plugins/nfc_magic/scenes/nfc_magic_scene_file_select.c rename to applications/external/nfc_magic/scenes/nfc_magic_scene_file_select.c diff --git a/applications/plugins/nfc_magic/scenes/nfc_magic_scene_magic_info.c b/applications/external/nfc_magic/scenes/nfc_magic_scene_magic_info.c similarity index 100% rename from applications/plugins/nfc_magic/scenes/nfc_magic_scene_magic_info.c rename to applications/external/nfc_magic/scenes/nfc_magic_scene_magic_info.c diff --git a/applications/plugins/nfc_magic/scenes/nfc_magic_scene_not_magic.c b/applications/external/nfc_magic/scenes/nfc_magic_scene_not_magic.c similarity index 100% rename from applications/plugins/nfc_magic/scenes/nfc_magic_scene_not_magic.c rename to applications/external/nfc_magic/scenes/nfc_magic_scene_not_magic.c diff --git a/applications/plugins/nfc_magic/scenes/nfc_magic_scene_start.c b/applications/external/nfc_magic/scenes/nfc_magic_scene_start.c similarity index 100% rename from applications/plugins/nfc_magic/scenes/nfc_magic_scene_start.c rename to applications/external/nfc_magic/scenes/nfc_magic_scene_start.c diff --git a/applications/plugins/nfc_magic/scenes/nfc_magic_scene_success.c b/applications/external/nfc_magic/scenes/nfc_magic_scene_success.c similarity index 100% rename from applications/plugins/nfc_magic/scenes/nfc_magic_scene_success.c rename to applications/external/nfc_magic/scenes/nfc_magic_scene_success.c diff --git a/applications/plugins/nfc_magic/scenes/nfc_magic_scene_wipe.c b/applications/external/nfc_magic/scenes/nfc_magic_scene_wipe.c similarity index 100% rename from applications/plugins/nfc_magic/scenes/nfc_magic_scene_wipe.c rename to applications/external/nfc_magic/scenes/nfc_magic_scene_wipe.c diff --git a/applications/plugins/nfc_magic/scenes/nfc_magic_scene_wipe_fail.c b/applications/external/nfc_magic/scenes/nfc_magic_scene_wipe_fail.c similarity index 100% rename from applications/plugins/nfc_magic/scenes/nfc_magic_scene_wipe_fail.c rename to applications/external/nfc_magic/scenes/nfc_magic_scene_wipe_fail.c diff --git a/applications/plugins/nfc_magic/scenes/nfc_magic_scene_write.c b/applications/external/nfc_magic/scenes/nfc_magic_scene_write.c similarity index 100% rename from applications/plugins/nfc_magic/scenes/nfc_magic_scene_write.c rename to applications/external/nfc_magic/scenes/nfc_magic_scene_write.c diff --git a/applications/plugins/nfc_magic/scenes/nfc_magic_scene_write_confirm.c b/applications/external/nfc_magic/scenes/nfc_magic_scene_write_confirm.c similarity index 100% rename from applications/plugins/nfc_magic/scenes/nfc_magic_scene_write_confirm.c rename to applications/external/nfc_magic/scenes/nfc_magic_scene_write_confirm.c diff --git a/applications/plugins/nfc_magic/scenes/nfc_magic_scene_write_fail.c b/applications/external/nfc_magic/scenes/nfc_magic_scene_write_fail.c similarity index 100% rename from applications/plugins/nfc_magic/scenes/nfc_magic_scene_write_fail.c rename to applications/external/nfc_magic/scenes/nfc_magic_scene_write_fail.c diff --git a/applications/plugins/nfc_magic/scenes/nfc_magic_scene_wrong_card.c b/applications/external/nfc_magic/scenes/nfc_magic_scene_wrong_card.c similarity index 100% rename from applications/plugins/nfc_magic/scenes/nfc_magic_scene_wrong_card.c rename to applications/external/nfc_magic/scenes/nfc_magic_scene_wrong_card.c diff --git a/applications/plugins/picopass/125_10px.png b/applications/external/picopass/125_10px.png similarity index 100% rename from applications/plugins/picopass/125_10px.png rename to applications/external/picopass/125_10px.png diff --git a/applications/plugins/picopass/application.fam b/applications/external/picopass/application.fam similarity index 100% rename from applications/plugins/picopass/application.fam rename to applications/external/picopass/application.fam diff --git a/applications/plugins/picopass/helpers/iclass_elite_dict.c b/applications/external/picopass/helpers/iclass_elite_dict.c similarity index 100% rename from applications/plugins/picopass/helpers/iclass_elite_dict.c rename to applications/external/picopass/helpers/iclass_elite_dict.c diff --git a/applications/plugins/picopass/helpers/iclass_elite_dict.h b/applications/external/picopass/helpers/iclass_elite_dict.h similarity index 100% rename from applications/plugins/picopass/helpers/iclass_elite_dict.h rename to applications/external/picopass/helpers/iclass_elite_dict.h diff --git a/applications/plugins/picopass/icons/DolphinMafia_115x62.png b/applications/external/picopass/icons/DolphinMafia_115x62.png similarity index 100% rename from applications/plugins/picopass/icons/DolphinMafia_115x62.png rename to applications/external/picopass/icons/DolphinMafia_115x62.png diff --git a/applications/plugins/picopass/icons/DolphinNice_96x59.png b/applications/external/picopass/icons/DolphinNice_96x59.png similarity index 100% rename from applications/plugins/picopass/icons/DolphinNice_96x59.png rename to applications/external/picopass/icons/DolphinNice_96x59.png diff --git a/applications/plugins/picopass/icons/Nfc_10px.png b/applications/external/picopass/icons/Nfc_10px.png similarity index 100% rename from applications/plugins/picopass/icons/Nfc_10px.png rename to applications/external/picopass/icons/Nfc_10px.png diff --git a/applications/plugins/picopass/icons/RFIDDolphinReceive_97x61.png b/applications/external/picopass/icons/RFIDDolphinReceive_97x61.png similarity index 100% rename from applications/plugins/picopass/icons/RFIDDolphinReceive_97x61.png rename to applications/external/picopass/icons/RFIDDolphinReceive_97x61.png diff --git a/applications/plugins/picopass/icons/RFIDDolphinSend_97x61.png b/applications/external/picopass/icons/RFIDDolphinSend_97x61.png similarity index 100% rename from applications/plugins/picopass/icons/RFIDDolphinSend_97x61.png rename to applications/external/picopass/icons/RFIDDolphinSend_97x61.png diff --git a/applications/plugins/picopass/lib/loclass/optimized_cipher.c b/applications/external/picopass/lib/loclass/optimized_cipher.c similarity index 100% rename from applications/plugins/picopass/lib/loclass/optimized_cipher.c rename to applications/external/picopass/lib/loclass/optimized_cipher.c diff --git a/applications/plugins/picopass/lib/loclass/optimized_cipher.h b/applications/external/picopass/lib/loclass/optimized_cipher.h similarity index 100% rename from applications/plugins/picopass/lib/loclass/optimized_cipher.h rename to applications/external/picopass/lib/loclass/optimized_cipher.h diff --git a/applications/plugins/picopass/lib/loclass/optimized_cipherutils.c b/applications/external/picopass/lib/loclass/optimized_cipherutils.c similarity index 100% rename from applications/plugins/picopass/lib/loclass/optimized_cipherutils.c rename to applications/external/picopass/lib/loclass/optimized_cipherutils.c diff --git a/applications/plugins/picopass/lib/loclass/optimized_cipherutils.h b/applications/external/picopass/lib/loclass/optimized_cipherutils.h similarity index 100% rename from applications/plugins/picopass/lib/loclass/optimized_cipherutils.h rename to applications/external/picopass/lib/loclass/optimized_cipherutils.h diff --git a/applications/plugins/picopass/lib/loclass/optimized_elite.c b/applications/external/picopass/lib/loclass/optimized_elite.c similarity index 100% rename from applications/plugins/picopass/lib/loclass/optimized_elite.c rename to applications/external/picopass/lib/loclass/optimized_elite.c diff --git a/applications/plugins/picopass/lib/loclass/optimized_elite.h b/applications/external/picopass/lib/loclass/optimized_elite.h similarity index 100% rename from applications/plugins/picopass/lib/loclass/optimized_elite.h rename to applications/external/picopass/lib/loclass/optimized_elite.h diff --git a/applications/plugins/picopass/lib/loclass/optimized_ikeys.c b/applications/external/picopass/lib/loclass/optimized_ikeys.c similarity index 100% rename from applications/plugins/picopass/lib/loclass/optimized_ikeys.c rename to applications/external/picopass/lib/loclass/optimized_ikeys.c diff --git a/applications/plugins/picopass/lib/loclass/optimized_ikeys.h b/applications/external/picopass/lib/loclass/optimized_ikeys.h similarity index 100% rename from applications/plugins/picopass/lib/loclass/optimized_ikeys.h rename to applications/external/picopass/lib/loclass/optimized_ikeys.h diff --git a/applications/plugins/picopass/picopass.c b/applications/external/picopass/picopass.c similarity index 100% rename from applications/plugins/picopass/picopass.c rename to applications/external/picopass/picopass.c diff --git a/applications/plugins/picopass/picopass.h b/applications/external/picopass/picopass.h similarity index 100% rename from applications/plugins/picopass/picopass.h rename to applications/external/picopass/picopass.h diff --git a/applications/plugins/picopass/picopass_device.c b/applications/external/picopass/picopass_device.c similarity index 100% rename from applications/plugins/picopass/picopass_device.c rename to applications/external/picopass/picopass_device.c diff --git a/applications/plugins/picopass/picopass_device.h b/applications/external/picopass/picopass_device.h similarity index 100% rename from applications/plugins/picopass/picopass_device.h rename to applications/external/picopass/picopass_device.h diff --git a/applications/plugins/picopass/picopass_i.h b/applications/external/picopass/picopass_i.h similarity index 100% rename from applications/plugins/picopass/picopass_i.h rename to applications/external/picopass/picopass_i.h diff --git a/applications/plugins/picopass/picopass_keys.c b/applications/external/picopass/picopass_keys.c similarity index 100% rename from applications/plugins/picopass/picopass_keys.c rename to applications/external/picopass/picopass_keys.c diff --git a/applications/plugins/picopass/picopass_keys.h b/applications/external/picopass/picopass_keys.h similarity index 100% rename from applications/plugins/picopass/picopass_keys.h rename to applications/external/picopass/picopass_keys.h diff --git a/applications/plugins/picopass/picopass_worker.c b/applications/external/picopass/picopass_worker.c similarity index 100% rename from applications/plugins/picopass/picopass_worker.c rename to applications/external/picopass/picopass_worker.c diff --git a/applications/plugins/picopass/picopass_worker.h b/applications/external/picopass/picopass_worker.h similarity index 100% rename from applications/plugins/picopass/picopass_worker.h rename to applications/external/picopass/picopass_worker.h diff --git a/applications/plugins/picopass/picopass_worker_i.h b/applications/external/picopass/picopass_worker_i.h similarity index 100% rename from applications/plugins/picopass/picopass_worker_i.h rename to applications/external/picopass/picopass_worker_i.h diff --git a/applications/plugins/picopass/rfal_picopass.c b/applications/external/picopass/rfal_picopass.c similarity index 100% rename from applications/plugins/picopass/rfal_picopass.c rename to applications/external/picopass/rfal_picopass.c diff --git a/applications/plugins/picopass/rfal_picopass.h b/applications/external/picopass/rfal_picopass.h similarity index 100% rename from applications/plugins/picopass/rfal_picopass.h rename to applications/external/picopass/rfal_picopass.h diff --git a/applications/plugins/picopass/scenes/picopass_scene.c b/applications/external/picopass/scenes/picopass_scene.c similarity index 100% rename from applications/plugins/picopass/scenes/picopass_scene.c rename to applications/external/picopass/scenes/picopass_scene.c diff --git a/applications/plugins/picopass/scenes/picopass_scene.h b/applications/external/picopass/scenes/picopass_scene.h similarity index 100% rename from applications/plugins/picopass/scenes/picopass_scene.h rename to applications/external/picopass/scenes/picopass_scene.h diff --git a/applications/plugins/picopass/scenes/picopass_scene_card_menu.c b/applications/external/picopass/scenes/picopass_scene_card_menu.c similarity index 100% rename from applications/plugins/picopass/scenes/picopass_scene_card_menu.c rename to applications/external/picopass/scenes/picopass_scene_card_menu.c diff --git a/applications/plugins/picopass/scenes/picopass_scene_config.h b/applications/external/picopass/scenes/picopass_scene_config.h similarity index 100% rename from applications/plugins/picopass/scenes/picopass_scene_config.h rename to applications/external/picopass/scenes/picopass_scene_config.h diff --git a/applications/plugins/picopass/scenes/picopass_scene_delete.c b/applications/external/picopass/scenes/picopass_scene_delete.c similarity index 100% rename from applications/plugins/picopass/scenes/picopass_scene_delete.c rename to applications/external/picopass/scenes/picopass_scene_delete.c diff --git a/applications/plugins/picopass/scenes/picopass_scene_delete_success.c b/applications/external/picopass/scenes/picopass_scene_delete_success.c similarity index 100% rename from applications/plugins/picopass/scenes/picopass_scene_delete_success.c rename to applications/external/picopass/scenes/picopass_scene_delete_success.c diff --git a/applications/plugins/picopass/scenes/picopass_scene_device_info.c b/applications/external/picopass/scenes/picopass_scene_device_info.c similarity index 100% rename from applications/plugins/picopass/scenes/picopass_scene_device_info.c rename to applications/external/picopass/scenes/picopass_scene_device_info.c diff --git a/applications/plugins/picopass/scenes/picopass_scene_file_select.c b/applications/external/picopass/scenes/picopass_scene_file_select.c similarity index 100% rename from applications/plugins/picopass/scenes/picopass_scene_file_select.c rename to applications/external/picopass/scenes/picopass_scene_file_select.c diff --git a/applications/plugins/picopass/scenes/picopass_scene_key_menu.c b/applications/external/picopass/scenes/picopass_scene_key_menu.c similarity index 100% rename from applications/plugins/picopass/scenes/picopass_scene_key_menu.c rename to applications/external/picopass/scenes/picopass_scene_key_menu.c diff --git a/applications/plugins/picopass/scenes/picopass_scene_read_card.c b/applications/external/picopass/scenes/picopass_scene_read_card.c similarity index 100% rename from applications/plugins/picopass/scenes/picopass_scene_read_card.c rename to applications/external/picopass/scenes/picopass_scene_read_card.c diff --git a/applications/plugins/picopass/scenes/picopass_scene_read_card_success.c b/applications/external/picopass/scenes/picopass_scene_read_card_success.c similarity index 100% rename from applications/plugins/picopass/scenes/picopass_scene_read_card_success.c rename to applications/external/picopass/scenes/picopass_scene_read_card_success.c diff --git a/applications/plugins/picopass/scenes/picopass_scene_read_factory_success.c b/applications/external/picopass/scenes/picopass_scene_read_factory_success.c similarity index 100% rename from applications/plugins/picopass/scenes/picopass_scene_read_factory_success.c rename to applications/external/picopass/scenes/picopass_scene_read_factory_success.c diff --git a/applications/plugins/picopass/scenes/picopass_scene_save_name.c b/applications/external/picopass/scenes/picopass_scene_save_name.c similarity index 100% rename from applications/plugins/picopass/scenes/picopass_scene_save_name.c rename to applications/external/picopass/scenes/picopass_scene_save_name.c diff --git a/applications/plugins/picopass/scenes/picopass_scene_save_success.c b/applications/external/picopass/scenes/picopass_scene_save_success.c similarity index 100% rename from applications/plugins/picopass/scenes/picopass_scene_save_success.c rename to applications/external/picopass/scenes/picopass_scene_save_success.c diff --git a/applications/plugins/picopass/scenes/picopass_scene_saved_menu.c b/applications/external/picopass/scenes/picopass_scene_saved_menu.c similarity index 100% rename from applications/plugins/picopass/scenes/picopass_scene_saved_menu.c rename to applications/external/picopass/scenes/picopass_scene_saved_menu.c diff --git a/applications/plugins/picopass/scenes/picopass_scene_start.c b/applications/external/picopass/scenes/picopass_scene_start.c similarity index 100% rename from applications/plugins/picopass/scenes/picopass_scene_start.c rename to applications/external/picopass/scenes/picopass_scene_start.c diff --git a/applications/plugins/picopass/scenes/picopass_scene_write_card.c b/applications/external/picopass/scenes/picopass_scene_write_card.c similarity index 100% rename from applications/plugins/picopass/scenes/picopass_scene_write_card.c rename to applications/external/picopass/scenes/picopass_scene_write_card.c diff --git a/applications/plugins/picopass/scenes/picopass_scene_write_card_success.c b/applications/external/picopass/scenes/picopass_scene_write_card_success.c similarity index 100% rename from applications/plugins/picopass/scenes/picopass_scene_write_card_success.c rename to applications/external/picopass/scenes/picopass_scene_write_card_success.c diff --git a/applications/plugins/picopass/scenes/picopass_scene_write_key.c b/applications/external/picopass/scenes/picopass_scene_write_key.c similarity index 100% rename from applications/plugins/picopass/scenes/picopass_scene_write_key.c rename to applications/external/picopass/scenes/picopass_scene_write_key.c diff --git a/applications/plugins/signal_generator/application.fam b/applications/external/signal_generator/application.fam similarity index 78% rename from applications/plugins/signal_generator/application.fam rename to applications/external/signal_generator/application.fam index 60f8deff..094e784c 100644 --- a/applications/plugins/signal_generator/application.fam +++ b/applications/external/signal_generator/application.fam @@ -1,9 +1,8 @@ App( appid="signal_generator", name="Signal Generator", - apptype=FlipperAppType.PLUGIN, + apptype=FlipperAppType.EXTERNAL, entry_point="signal_gen_app", - cdefines=["APP_SIGNAL_GEN"], requires=["gui"], stack_size=1 * 1024, order=50, diff --git a/applications/plugins/signal_generator/icons/SmallArrowDown_3x5.png b/applications/external/signal_generator/icons/SmallArrowDown_3x5.png similarity index 100% rename from applications/plugins/signal_generator/icons/SmallArrowDown_3x5.png rename to applications/external/signal_generator/icons/SmallArrowDown_3x5.png diff --git a/applications/plugins/signal_generator/icons/SmallArrowUp_3x5.png b/applications/external/signal_generator/icons/SmallArrowUp_3x5.png similarity index 100% rename from applications/plugins/signal_generator/icons/SmallArrowUp_3x5.png rename to applications/external/signal_generator/icons/SmallArrowUp_3x5.png diff --git a/applications/plugins/signal_generator/scenes/signal_gen_scene.c b/applications/external/signal_generator/scenes/signal_gen_scene.c similarity index 100% rename from applications/plugins/signal_generator/scenes/signal_gen_scene.c rename to applications/external/signal_generator/scenes/signal_gen_scene.c diff --git a/applications/plugins/signal_generator/scenes/signal_gen_scene.h b/applications/external/signal_generator/scenes/signal_gen_scene.h similarity index 100% rename from applications/plugins/signal_generator/scenes/signal_gen_scene.h rename to applications/external/signal_generator/scenes/signal_gen_scene.h diff --git a/applications/plugins/signal_generator/scenes/signal_gen_scene_config.h b/applications/external/signal_generator/scenes/signal_gen_scene_config.h similarity index 100% rename from applications/plugins/signal_generator/scenes/signal_gen_scene_config.h rename to applications/external/signal_generator/scenes/signal_gen_scene_config.h diff --git a/applications/plugins/signal_generator/scenes/signal_gen_scene_mco.c b/applications/external/signal_generator/scenes/signal_gen_scene_mco.c similarity index 100% rename from applications/plugins/signal_generator/scenes/signal_gen_scene_mco.c rename to applications/external/signal_generator/scenes/signal_gen_scene_mco.c diff --git a/applications/plugins/signal_generator/scenes/signal_gen_scene_pwm.c b/applications/external/signal_generator/scenes/signal_gen_scene_pwm.c similarity index 100% rename from applications/plugins/signal_generator/scenes/signal_gen_scene_pwm.c rename to applications/external/signal_generator/scenes/signal_gen_scene_pwm.c diff --git a/applications/plugins/signal_generator/scenes/signal_gen_scene_start.c b/applications/external/signal_generator/scenes/signal_gen_scene_start.c similarity index 100% rename from applications/plugins/signal_generator/scenes/signal_gen_scene_start.c rename to applications/external/signal_generator/scenes/signal_gen_scene_start.c diff --git a/applications/plugins/signal_generator/signal_gen_10px.png b/applications/external/signal_generator/signal_gen_10px.png similarity index 100% rename from applications/plugins/signal_generator/signal_gen_10px.png rename to applications/external/signal_generator/signal_gen_10px.png diff --git a/applications/plugins/signal_generator/signal_gen_app.c b/applications/external/signal_generator/signal_gen_app.c similarity index 100% rename from applications/plugins/signal_generator/signal_gen_app.c rename to applications/external/signal_generator/signal_gen_app.c diff --git a/applications/plugins/signal_generator/signal_gen_app_i.h b/applications/external/signal_generator/signal_gen_app_i.h similarity index 100% rename from applications/plugins/signal_generator/signal_gen_app_i.h rename to applications/external/signal_generator/signal_gen_app_i.h diff --git a/applications/plugins/signal_generator/views/signal_gen_pwm.c b/applications/external/signal_generator/views/signal_gen_pwm.c similarity index 100% rename from applications/plugins/signal_generator/views/signal_gen_pwm.c rename to applications/external/signal_generator/views/signal_gen_pwm.c diff --git a/applications/plugins/signal_generator/views/signal_gen_pwm.h b/applications/external/signal_generator/views/signal_gen_pwm.h similarity index 100% rename from applications/plugins/signal_generator/views/signal_gen_pwm.h rename to applications/external/signal_generator/views/signal_gen_pwm.h diff --git a/applications/plugins/snake_game/application.fam b/applications/external/snake_game/application.fam similarity index 75% rename from applications/plugins/snake_game/application.fam rename to applications/external/snake_game/application.fam index d55f53bb..c736a4dd 100644 --- a/applications/plugins/snake_game/application.fam +++ b/applications/external/snake_game/application.fam @@ -1,9 +1,8 @@ App( appid="snake_game", name="Snake Game", - apptype=FlipperAppType.PLUGIN, + apptype=FlipperAppType.EXTERNAL, entry_point="snake_game_app", - cdefines=["APP_SNAKE_GAME"], requires=["gui"], stack_size=1 * 1024, order=30, diff --git a/applications/plugins/snake_game/snake_10px.png b/applications/external/snake_game/snake_10px.png similarity index 100% rename from applications/plugins/snake_game/snake_10px.png rename to applications/external/snake_game/snake_10px.png diff --git a/applications/plugins/snake_game/snake_game.c b/applications/external/snake_game/snake_game.c similarity index 100% rename from applications/plugins/snake_game/snake_game.c rename to applications/external/snake_game/snake_game.c diff --git a/applications/plugins/spi_mem_manager/application.fam b/applications/external/spi_mem_manager/application.fam similarity index 100% rename from applications/plugins/spi_mem_manager/application.fam rename to applications/external/spi_mem_manager/application.fam diff --git a/applications/plugins/spi_mem_manager/images/ChipLooking_64x64/frame_01.png b/applications/external/spi_mem_manager/images/ChipLooking_64x64/frame_01.png similarity index 100% rename from applications/plugins/spi_mem_manager/images/ChipLooking_64x64/frame_01.png rename to applications/external/spi_mem_manager/images/ChipLooking_64x64/frame_01.png diff --git a/applications/plugins/spi_mem_manager/images/ChipLooking_64x64/frame_02.png b/applications/external/spi_mem_manager/images/ChipLooking_64x64/frame_02.png similarity index 100% rename from applications/plugins/spi_mem_manager/images/ChipLooking_64x64/frame_02.png rename to applications/external/spi_mem_manager/images/ChipLooking_64x64/frame_02.png diff --git a/applications/plugins/spi_mem_manager/images/ChipLooking_64x64/frame_03.png b/applications/external/spi_mem_manager/images/ChipLooking_64x64/frame_03.png similarity index 100% rename from applications/plugins/spi_mem_manager/images/ChipLooking_64x64/frame_03.png rename to applications/external/spi_mem_manager/images/ChipLooking_64x64/frame_03.png diff --git a/applications/plugins/spi_mem_manager/images/ChipLooking_64x64/frame_rate b/applications/external/spi_mem_manager/images/ChipLooking_64x64/frame_rate similarity index 100% rename from applications/plugins/spi_mem_manager/images/ChipLooking_64x64/frame_rate rename to applications/external/spi_mem_manager/images/ChipLooking_64x64/frame_rate diff --git a/applications/plugins/spi_mem_manager/images/Dip8_10px.png b/applications/external/spi_mem_manager/images/Dip8_10px.png similarity index 100% rename from applications/plugins/spi_mem_manager/images/Dip8_10px.png rename to applications/external/spi_mem_manager/images/Dip8_10px.png diff --git a/applications/plugins/spi_mem_manager/images/Dip8_32x36.png b/applications/external/spi_mem_manager/images/Dip8_32x36.png similarity index 100% rename from applications/plugins/spi_mem_manager/images/Dip8_32x36.png rename to applications/external/spi_mem_manager/images/Dip8_32x36.png diff --git a/applications/plugins/spi_mem_manager/images/DolphinMafia_115x62.png b/applications/external/spi_mem_manager/images/DolphinMafia_115x62.png similarity index 100% rename from applications/plugins/spi_mem_manager/images/DolphinMafia_115x62.png rename to applications/external/spi_mem_manager/images/DolphinMafia_115x62.png diff --git a/applications/plugins/spi_mem_manager/images/DolphinNice_96x59.png b/applications/external/spi_mem_manager/images/DolphinNice_96x59.png similarity index 100% rename from applications/plugins/spi_mem_manager/images/DolphinNice_96x59.png rename to applications/external/spi_mem_manager/images/DolphinNice_96x59.png diff --git a/applications/plugins/spi_mem_manager/images/SDQuestion_35x43.png b/applications/external/spi_mem_manager/images/SDQuestion_35x43.png similarity index 100% rename from applications/plugins/spi_mem_manager/images/SDQuestion_35x43.png rename to applications/external/spi_mem_manager/images/SDQuestion_35x43.png diff --git a/applications/plugins/spi_mem_manager/images/Wiring_SPI_128x64.png b/applications/external/spi_mem_manager/images/Wiring_SPI_128x64.png similarity index 100% rename from applications/plugins/spi_mem_manager/images/Wiring_SPI_128x64.png rename to applications/external/spi_mem_manager/images/Wiring_SPI_128x64.png diff --git a/applications/plugins/spi_mem_manager/lib/spi/spi_mem_chip.c b/applications/external/spi_mem_manager/lib/spi/spi_mem_chip.c similarity index 100% rename from applications/plugins/spi_mem_manager/lib/spi/spi_mem_chip.c rename to applications/external/spi_mem_manager/lib/spi/spi_mem_chip.c diff --git a/applications/plugins/spi_mem_manager/lib/spi/spi_mem_chip.h b/applications/external/spi_mem_manager/lib/spi/spi_mem_chip.h similarity index 100% rename from applications/plugins/spi_mem_manager/lib/spi/spi_mem_chip.h rename to applications/external/spi_mem_manager/lib/spi/spi_mem_chip.h diff --git a/applications/plugins/spi_mem_manager/lib/spi/spi_mem_chip_arr.c b/applications/external/spi_mem_manager/lib/spi/spi_mem_chip_arr.c similarity index 100% rename from applications/plugins/spi_mem_manager/lib/spi/spi_mem_chip_arr.c rename to applications/external/spi_mem_manager/lib/spi/spi_mem_chip_arr.c diff --git a/applications/plugins/spi_mem_manager/lib/spi/spi_mem_chip_i.h b/applications/external/spi_mem_manager/lib/spi/spi_mem_chip_i.h similarity index 100% rename from applications/plugins/spi_mem_manager/lib/spi/spi_mem_chip_i.h rename to applications/external/spi_mem_manager/lib/spi/spi_mem_chip_i.h diff --git a/applications/plugins/spi_mem_manager/lib/spi/spi_mem_tools.c b/applications/external/spi_mem_manager/lib/spi/spi_mem_tools.c similarity index 100% rename from applications/plugins/spi_mem_manager/lib/spi/spi_mem_tools.c rename to applications/external/spi_mem_manager/lib/spi/spi_mem_tools.c diff --git a/applications/plugins/spi_mem_manager/lib/spi/spi_mem_tools.h b/applications/external/spi_mem_manager/lib/spi/spi_mem_tools.h similarity index 100% rename from applications/plugins/spi_mem_manager/lib/spi/spi_mem_tools.h rename to applications/external/spi_mem_manager/lib/spi/spi_mem_tools.h diff --git a/applications/plugins/spi_mem_manager/lib/spi/spi_mem_worker.c b/applications/external/spi_mem_manager/lib/spi/spi_mem_worker.c similarity index 100% rename from applications/plugins/spi_mem_manager/lib/spi/spi_mem_worker.c rename to applications/external/spi_mem_manager/lib/spi/spi_mem_worker.c diff --git a/applications/plugins/spi_mem_manager/lib/spi/spi_mem_worker.h b/applications/external/spi_mem_manager/lib/spi/spi_mem_worker.h similarity index 100% rename from applications/plugins/spi_mem_manager/lib/spi/spi_mem_worker.h rename to applications/external/spi_mem_manager/lib/spi/spi_mem_worker.h diff --git a/applications/plugins/spi_mem_manager/lib/spi/spi_mem_worker_i.h b/applications/external/spi_mem_manager/lib/spi/spi_mem_worker_i.h similarity index 100% rename from applications/plugins/spi_mem_manager/lib/spi/spi_mem_worker_i.h rename to applications/external/spi_mem_manager/lib/spi/spi_mem_worker_i.h diff --git a/applications/plugins/spi_mem_manager/lib/spi/spi_mem_worker_modes.c b/applications/external/spi_mem_manager/lib/spi/spi_mem_worker_modes.c similarity index 100% rename from applications/plugins/spi_mem_manager/lib/spi/spi_mem_worker_modes.c rename to applications/external/spi_mem_manager/lib/spi/spi_mem_worker_modes.c diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene.c similarity index 100% rename from applications/plugins/spi_mem_manager/scenes/spi_mem_scene.c rename to applications/external/spi_mem_manager/scenes/spi_mem_scene.c diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene.h b/applications/external/spi_mem_manager/scenes/spi_mem_scene.h similarity index 100% rename from applications/plugins/spi_mem_manager/scenes/spi_mem_scene.h rename to applications/external/spi_mem_manager/scenes/spi_mem_scene.h diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_about.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_about.c similarity index 100% rename from applications/plugins/spi_mem_manager/scenes/spi_mem_scene_about.c rename to applications/external/spi_mem_manager/scenes/spi_mem_scene_about.c diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_chip_detect.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_chip_detect.c similarity index 100% rename from applications/plugins/spi_mem_manager/scenes/spi_mem_scene_chip_detect.c rename to applications/external/spi_mem_manager/scenes/spi_mem_scene_chip_detect.c diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_chip_detect_fail.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_chip_detect_fail.c similarity index 100% rename from applications/plugins/spi_mem_manager/scenes/spi_mem_scene_chip_detect_fail.c rename to applications/external/spi_mem_manager/scenes/spi_mem_scene_chip_detect_fail.c diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_chip_detected.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_chip_detected.c similarity index 100% rename from applications/plugins/spi_mem_manager/scenes/spi_mem_scene_chip_detected.c rename to applications/external/spi_mem_manager/scenes/spi_mem_scene_chip_detected.c diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_chip_error.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_chip_error.c similarity index 100% rename from applications/plugins/spi_mem_manager/scenes/spi_mem_scene_chip_error.c rename to applications/external/spi_mem_manager/scenes/spi_mem_scene_chip_error.c diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_config.h b/applications/external/spi_mem_manager/scenes/spi_mem_scene_config.h similarity index 100% rename from applications/plugins/spi_mem_manager/scenes/spi_mem_scene_config.h rename to applications/external/spi_mem_manager/scenes/spi_mem_scene_config.h diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_delete_confirm.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_delete_confirm.c similarity index 100% rename from applications/plugins/spi_mem_manager/scenes/spi_mem_scene_delete_confirm.c rename to applications/external/spi_mem_manager/scenes/spi_mem_scene_delete_confirm.c diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_erase.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_erase.c similarity index 100% rename from applications/plugins/spi_mem_manager/scenes/spi_mem_scene_erase.c rename to applications/external/spi_mem_manager/scenes/spi_mem_scene_erase.c diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_file_info.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_file_info.c similarity index 100% rename from applications/plugins/spi_mem_manager/scenes/spi_mem_scene_file_info.c rename to applications/external/spi_mem_manager/scenes/spi_mem_scene_file_info.c diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_read.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_read.c similarity index 100% rename from applications/plugins/spi_mem_manager/scenes/spi_mem_scene_read.c rename to applications/external/spi_mem_manager/scenes/spi_mem_scene_read.c diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_read_filename.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_read_filename.c similarity index 100% rename from applications/plugins/spi_mem_manager/scenes/spi_mem_scene_read_filename.c rename to applications/external/spi_mem_manager/scenes/spi_mem_scene_read_filename.c diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_saved_file_menu.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_saved_file_menu.c similarity index 100% rename from applications/plugins/spi_mem_manager/scenes/spi_mem_scene_saved_file_menu.c rename to applications/external/spi_mem_manager/scenes/spi_mem_scene_saved_file_menu.c diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_select_file.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_select_file.c similarity index 100% rename from applications/plugins/spi_mem_manager/scenes/spi_mem_scene_select_file.c rename to applications/external/spi_mem_manager/scenes/spi_mem_scene_select_file.c diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_select_model.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_select_model.c similarity index 100% rename from applications/plugins/spi_mem_manager/scenes/spi_mem_scene_select_model.c rename to applications/external/spi_mem_manager/scenes/spi_mem_scene_select_model.c diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_select_vendor.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_select_vendor.c similarity index 100% rename from applications/plugins/spi_mem_manager/scenes/spi_mem_scene_select_vendor.c rename to applications/external/spi_mem_manager/scenes/spi_mem_scene_select_vendor.c diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_start.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_start.c similarity index 100% rename from applications/plugins/spi_mem_manager/scenes/spi_mem_scene_start.c rename to applications/external/spi_mem_manager/scenes/spi_mem_scene_start.c diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_storage_error.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_storage_error.c similarity index 100% rename from applications/plugins/spi_mem_manager/scenes/spi_mem_scene_storage_error.c rename to applications/external/spi_mem_manager/scenes/spi_mem_scene_storage_error.c diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_success.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_success.c similarity index 100% rename from applications/plugins/spi_mem_manager/scenes/spi_mem_scene_success.c rename to applications/external/spi_mem_manager/scenes/spi_mem_scene_success.c diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_verify.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_verify.c similarity index 100% rename from applications/plugins/spi_mem_manager/scenes/spi_mem_scene_verify.c rename to applications/external/spi_mem_manager/scenes/spi_mem_scene_verify.c diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_verify_error.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_verify_error.c similarity index 100% rename from applications/plugins/spi_mem_manager/scenes/spi_mem_scene_verify_error.c rename to applications/external/spi_mem_manager/scenes/spi_mem_scene_verify_error.c diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_wiring.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_wiring.c similarity index 100% rename from applications/plugins/spi_mem_manager/scenes/spi_mem_scene_wiring.c rename to applications/external/spi_mem_manager/scenes/spi_mem_scene_wiring.c diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_write.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_write.c similarity index 100% rename from applications/plugins/spi_mem_manager/scenes/spi_mem_scene_write.c rename to applications/external/spi_mem_manager/scenes/spi_mem_scene_write.c diff --git a/applications/plugins/spi_mem_manager/spi_mem_app.c b/applications/external/spi_mem_manager/spi_mem_app.c similarity index 100% rename from applications/plugins/spi_mem_manager/spi_mem_app.c rename to applications/external/spi_mem_manager/spi_mem_app.c diff --git a/applications/plugins/spi_mem_manager/spi_mem_app.h b/applications/external/spi_mem_manager/spi_mem_app.h similarity index 100% rename from applications/plugins/spi_mem_manager/spi_mem_app.h rename to applications/external/spi_mem_manager/spi_mem_app.h diff --git a/applications/plugins/spi_mem_manager/spi_mem_app_i.h b/applications/external/spi_mem_manager/spi_mem_app_i.h similarity index 100% rename from applications/plugins/spi_mem_manager/spi_mem_app_i.h rename to applications/external/spi_mem_manager/spi_mem_app_i.h diff --git a/applications/plugins/spi_mem_manager/spi_mem_files.c b/applications/external/spi_mem_manager/spi_mem_files.c similarity index 100% rename from applications/plugins/spi_mem_manager/spi_mem_files.c rename to applications/external/spi_mem_manager/spi_mem_files.c diff --git a/applications/plugins/spi_mem_manager/spi_mem_files.h b/applications/external/spi_mem_manager/spi_mem_files.h similarity index 100% rename from applications/plugins/spi_mem_manager/spi_mem_files.h rename to applications/external/spi_mem_manager/spi_mem_files.h diff --git a/applications/plugins/spi_mem_manager/tools/README.md b/applications/external/spi_mem_manager/tools/README.md similarity index 100% rename from applications/plugins/spi_mem_manager/tools/README.md rename to applications/external/spi_mem_manager/tools/README.md diff --git a/applications/plugins/spi_mem_manager/tools/chiplist/LICENSE b/applications/external/spi_mem_manager/tools/chiplist/LICENSE similarity index 100% rename from applications/plugins/spi_mem_manager/tools/chiplist/LICENSE rename to applications/external/spi_mem_manager/tools/chiplist/LICENSE diff --git a/applications/plugins/spi_mem_manager/tools/chiplist/chiplist.xml b/applications/external/spi_mem_manager/tools/chiplist/chiplist.xml similarity index 100% rename from applications/plugins/spi_mem_manager/tools/chiplist/chiplist.xml rename to applications/external/spi_mem_manager/tools/chiplist/chiplist.xml diff --git a/applications/plugins/spi_mem_manager/tools/chiplist_convert.py b/applications/external/spi_mem_manager/tools/chiplist_convert.py similarity index 100% rename from applications/plugins/spi_mem_manager/tools/chiplist_convert.py rename to applications/external/spi_mem_manager/tools/chiplist_convert.py diff --git a/applications/plugins/spi_mem_manager/views/spi_mem_view_detect.c b/applications/external/spi_mem_manager/views/spi_mem_view_detect.c similarity index 100% rename from applications/plugins/spi_mem_manager/views/spi_mem_view_detect.c rename to applications/external/spi_mem_manager/views/spi_mem_view_detect.c diff --git a/applications/plugins/spi_mem_manager/views/spi_mem_view_detect.h b/applications/external/spi_mem_manager/views/spi_mem_view_detect.h similarity index 100% rename from applications/plugins/spi_mem_manager/views/spi_mem_view_detect.h rename to applications/external/spi_mem_manager/views/spi_mem_view_detect.h diff --git a/applications/plugins/spi_mem_manager/views/spi_mem_view_progress.c b/applications/external/spi_mem_manager/views/spi_mem_view_progress.c similarity index 100% rename from applications/plugins/spi_mem_manager/views/spi_mem_view_progress.c rename to applications/external/spi_mem_manager/views/spi_mem_view_progress.c diff --git a/applications/plugins/spi_mem_manager/views/spi_mem_view_progress.h b/applications/external/spi_mem_manager/views/spi_mem_view_progress.h similarity index 100% rename from applications/plugins/spi_mem_manager/views/spi_mem_view_progress.h rename to applications/external/spi_mem_manager/views/spi_mem_view_progress.h diff --git a/applications/plugins/weather_station/application.fam b/applications/external/weather_station/application.fam similarity index 79% rename from applications/plugins/weather_station/application.fam rename to applications/external/weather_station/application.fam index 935f9257..8dcaa125 100644 --- a/applications/plugins/weather_station/application.fam +++ b/applications/external/weather_station/application.fam @@ -1,10 +1,9 @@ App( appid="weather_station", name="Weather Station", - apptype=FlipperAppType.PLUGIN, + apptype=FlipperAppType.EXTERNAL, targets=["f7"], entry_point="weather_station_app", - cdefines=["APP_WEATHER_STATION"], requires=["gui"], stack_size=4 * 1024, order=50, diff --git a/applications/plugins/weather_station/helpers/weather_station_event.h b/applications/external/weather_station/helpers/weather_station_event.h similarity index 100% rename from applications/plugins/weather_station/helpers/weather_station_event.h rename to applications/external/weather_station/helpers/weather_station_event.h diff --git a/applications/plugins/weather_station/helpers/weather_station_types.h b/applications/external/weather_station/helpers/weather_station_types.h similarity index 100% rename from applications/plugins/weather_station/helpers/weather_station_types.h rename to applications/external/weather_station/helpers/weather_station_types.h diff --git a/applications/plugins/weather_station/images/Humid_10x15.png b/applications/external/weather_station/images/Humid_10x15.png similarity index 100% rename from applications/plugins/weather_station/images/Humid_10x15.png rename to applications/external/weather_station/images/Humid_10x15.png diff --git a/applications/plugins/weather_station/images/Humid_8x13.png b/applications/external/weather_station/images/Humid_8x13.png similarity index 100% rename from applications/plugins/weather_station/images/Humid_8x13.png rename to applications/external/weather_station/images/Humid_8x13.png diff --git a/applications/plugins/weather_station/images/Lock_7x8.png b/applications/external/weather_station/images/Lock_7x8.png similarity index 100% rename from applications/plugins/weather_station/images/Lock_7x8.png rename to applications/external/weather_station/images/Lock_7x8.png diff --git a/applications/plugins/weather_station/images/Pin_back_arrow_10x8.png b/applications/external/weather_station/images/Pin_back_arrow_10x8.png similarity index 100% rename from applications/plugins/weather_station/images/Pin_back_arrow_10x8.png rename to applications/external/weather_station/images/Pin_back_arrow_10x8.png diff --git a/applications/plugins/weather_station/images/Quest_7x8.png b/applications/external/weather_station/images/Quest_7x8.png similarity index 100% rename from applications/plugins/weather_station/images/Quest_7x8.png rename to applications/external/weather_station/images/Quest_7x8.png diff --git a/applications/plugins/weather_station/images/Scanning_123x52.png b/applications/external/weather_station/images/Scanning_123x52.png similarity index 100% rename from applications/plugins/weather_station/images/Scanning_123x52.png rename to applications/external/weather_station/images/Scanning_123x52.png diff --git a/applications/plugins/weather_station/images/Therm_7x16.png b/applications/external/weather_station/images/Therm_7x16.png similarity index 100% rename from applications/plugins/weather_station/images/Therm_7x16.png rename to applications/external/weather_station/images/Therm_7x16.png diff --git a/applications/plugins/weather_station/images/Timer_11x11.png b/applications/external/weather_station/images/Timer_11x11.png similarity index 100% rename from applications/plugins/weather_station/images/Timer_11x11.png rename to applications/external/weather_station/images/Timer_11x11.png diff --git a/applications/plugins/weather_station/images/Unlock_7x8.png b/applications/external/weather_station/images/Unlock_7x8.png similarity index 100% rename from applications/plugins/weather_station/images/Unlock_7x8.png rename to applications/external/weather_station/images/Unlock_7x8.png diff --git a/applications/plugins/weather_station/images/WarningDolphin_45x42.png b/applications/external/weather_station/images/WarningDolphin_45x42.png similarity index 100% rename from applications/plugins/weather_station/images/WarningDolphin_45x42.png rename to applications/external/weather_station/images/WarningDolphin_45x42.png diff --git a/applications/plugins/weather_station/images/station_icon.png b/applications/external/weather_station/images/station_icon.png similarity index 100% rename from applications/plugins/weather_station/images/station_icon.png rename to applications/external/weather_station/images/station_icon.png diff --git a/applications/plugins/weather_station/protocols/acurite_592txr.c b/applications/external/weather_station/protocols/acurite_592txr.c similarity index 100% rename from applications/plugins/weather_station/protocols/acurite_592txr.c rename to applications/external/weather_station/protocols/acurite_592txr.c diff --git a/applications/plugins/weather_station/protocols/acurite_592txr.h b/applications/external/weather_station/protocols/acurite_592txr.h similarity index 100% rename from applications/plugins/weather_station/protocols/acurite_592txr.h rename to applications/external/weather_station/protocols/acurite_592txr.h diff --git a/applications/plugins/weather_station/protocols/acurite_606tx.c b/applications/external/weather_station/protocols/acurite_606tx.c similarity index 100% rename from applications/plugins/weather_station/protocols/acurite_606tx.c rename to applications/external/weather_station/protocols/acurite_606tx.c diff --git a/applications/plugins/weather_station/protocols/acurite_606tx.h b/applications/external/weather_station/protocols/acurite_606tx.h similarity index 100% rename from applications/plugins/weather_station/protocols/acurite_606tx.h rename to applications/external/weather_station/protocols/acurite_606tx.h diff --git a/applications/plugins/weather_station/protocols/acurite_609txc.c b/applications/external/weather_station/protocols/acurite_609txc.c similarity index 100% rename from applications/plugins/weather_station/protocols/acurite_609txc.c rename to applications/external/weather_station/protocols/acurite_609txc.c diff --git a/applications/plugins/weather_station/protocols/acurite_609txc.h b/applications/external/weather_station/protocols/acurite_609txc.h similarity index 100% rename from applications/plugins/weather_station/protocols/acurite_609txc.h rename to applications/external/weather_station/protocols/acurite_609txc.h diff --git a/applications/plugins/weather_station/protocols/ambient_weather.c b/applications/external/weather_station/protocols/ambient_weather.c similarity index 100% rename from applications/plugins/weather_station/protocols/ambient_weather.c rename to applications/external/weather_station/protocols/ambient_weather.c diff --git a/applications/plugins/weather_station/protocols/ambient_weather.h b/applications/external/weather_station/protocols/ambient_weather.h similarity index 100% rename from applications/plugins/weather_station/protocols/ambient_weather.h rename to applications/external/weather_station/protocols/ambient_weather.h diff --git a/applications/plugins/weather_station/protocols/auriol_hg0601a.c b/applications/external/weather_station/protocols/auriol_hg0601a.c similarity index 100% rename from applications/plugins/weather_station/protocols/auriol_hg0601a.c rename to applications/external/weather_station/protocols/auriol_hg0601a.c diff --git a/applications/plugins/weather_station/protocols/auriol_hg0601a.h b/applications/external/weather_station/protocols/auriol_hg0601a.h similarity index 100% rename from applications/plugins/weather_station/protocols/auriol_hg0601a.h rename to applications/external/weather_station/protocols/auriol_hg0601a.h diff --git a/applications/plugins/weather_station/protocols/gt_wt_02.c b/applications/external/weather_station/protocols/gt_wt_02.c similarity index 100% rename from applications/plugins/weather_station/protocols/gt_wt_02.c rename to applications/external/weather_station/protocols/gt_wt_02.c diff --git a/applications/plugins/weather_station/protocols/gt_wt_02.h b/applications/external/weather_station/protocols/gt_wt_02.h similarity index 100% rename from applications/plugins/weather_station/protocols/gt_wt_02.h rename to applications/external/weather_station/protocols/gt_wt_02.h diff --git a/applications/plugins/weather_station/protocols/gt_wt_03.c b/applications/external/weather_station/protocols/gt_wt_03.c similarity index 100% rename from applications/plugins/weather_station/protocols/gt_wt_03.c rename to applications/external/weather_station/protocols/gt_wt_03.c diff --git a/applications/plugins/weather_station/protocols/gt_wt_03.h b/applications/external/weather_station/protocols/gt_wt_03.h similarity index 100% rename from applications/plugins/weather_station/protocols/gt_wt_03.h rename to applications/external/weather_station/protocols/gt_wt_03.h diff --git a/applications/plugins/weather_station/protocols/infactory.c b/applications/external/weather_station/protocols/infactory.c similarity index 100% rename from applications/plugins/weather_station/protocols/infactory.c rename to applications/external/weather_station/protocols/infactory.c diff --git a/applications/plugins/weather_station/protocols/infactory.h b/applications/external/weather_station/protocols/infactory.h similarity index 100% rename from applications/plugins/weather_station/protocols/infactory.h rename to applications/external/weather_station/protocols/infactory.h diff --git a/applications/plugins/weather_station/protocols/lacrosse_tx.c b/applications/external/weather_station/protocols/lacrosse_tx.c similarity index 100% rename from applications/plugins/weather_station/protocols/lacrosse_tx.c rename to applications/external/weather_station/protocols/lacrosse_tx.c diff --git a/applications/plugins/weather_station/protocols/lacrosse_tx.h b/applications/external/weather_station/protocols/lacrosse_tx.h similarity index 100% rename from applications/plugins/weather_station/protocols/lacrosse_tx.h rename to applications/external/weather_station/protocols/lacrosse_tx.h diff --git a/applications/plugins/weather_station/protocols/lacrosse_tx141thbv2.c b/applications/external/weather_station/protocols/lacrosse_tx141thbv2.c similarity index 100% rename from applications/plugins/weather_station/protocols/lacrosse_tx141thbv2.c rename to applications/external/weather_station/protocols/lacrosse_tx141thbv2.c diff --git a/applications/plugins/weather_station/protocols/lacrosse_tx141thbv2.h b/applications/external/weather_station/protocols/lacrosse_tx141thbv2.h similarity index 100% rename from applications/plugins/weather_station/protocols/lacrosse_tx141thbv2.h rename to applications/external/weather_station/protocols/lacrosse_tx141thbv2.h diff --git a/applications/plugins/weather_station/protocols/nexus_th.c b/applications/external/weather_station/protocols/nexus_th.c similarity index 100% rename from applications/plugins/weather_station/protocols/nexus_th.c rename to applications/external/weather_station/protocols/nexus_th.c diff --git a/applications/plugins/weather_station/protocols/nexus_th.h b/applications/external/weather_station/protocols/nexus_th.h similarity index 100% rename from applications/plugins/weather_station/protocols/nexus_th.h rename to applications/external/weather_station/protocols/nexus_th.h diff --git a/applications/plugins/weather_station/protocols/oregon2.c b/applications/external/weather_station/protocols/oregon2.c similarity index 100% rename from applications/plugins/weather_station/protocols/oregon2.c rename to applications/external/weather_station/protocols/oregon2.c diff --git a/applications/plugins/weather_station/protocols/oregon2.h b/applications/external/weather_station/protocols/oregon2.h similarity index 100% rename from applications/plugins/weather_station/protocols/oregon2.h rename to applications/external/weather_station/protocols/oregon2.h diff --git a/applications/plugins/weather_station/protocols/oregon_v1.c b/applications/external/weather_station/protocols/oregon_v1.c similarity index 100% rename from applications/plugins/weather_station/protocols/oregon_v1.c rename to applications/external/weather_station/protocols/oregon_v1.c diff --git a/applications/plugins/weather_station/protocols/oregon_v1.h b/applications/external/weather_station/protocols/oregon_v1.h similarity index 100% rename from applications/plugins/weather_station/protocols/oregon_v1.h rename to applications/external/weather_station/protocols/oregon_v1.h diff --git a/applications/plugins/weather_station/protocols/protocol_items.c b/applications/external/weather_station/protocols/protocol_items.c similarity index 100% rename from applications/plugins/weather_station/protocols/protocol_items.c rename to applications/external/weather_station/protocols/protocol_items.c diff --git a/applications/plugins/weather_station/protocols/protocol_items.h b/applications/external/weather_station/protocols/protocol_items.h similarity index 100% rename from applications/plugins/weather_station/protocols/protocol_items.h rename to applications/external/weather_station/protocols/protocol_items.h diff --git a/applications/plugins/weather_station/protocols/thermopro_tx4.c b/applications/external/weather_station/protocols/thermopro_tx4.c similarity index 100% rename from applications/plugins/weather_station/protocols/thermopro_tx4.c rename to applications/external/weather_station/protocols/thermopro_tx4.c diff --git a/applications/plugins/weather_station/protocols/thermopro_tx4.h b/applications/external/weather_station/protocols/thermopro_tx4.h similarity index 100% rename from applications/plugins/weather_station/protocols/thermopro_tx4.h rename to applications/external/weather_station/protocols/thermopro_tx4.h diff --git a/applications/plugins/weather_station/protocols/tx_8300.c b/applications/external/weather_station/protocols/tx_8300.c similarity index 100% rename from applications/plugins/weather_station/protocols/tx_8300.c rename to applications/external/weather_station/protocols/tx_8300.c diff --git a/applications/plugins/weather_station/protocols/tx_8300.h b/applications/external/weather_station/protocols/tx_8300.h similarity index 100% rename from applications/plugins/weather_station/protocols/tx_8300.h rename to applications/external/weather_station/protocols/tx_8300.h diff --git a/applications/plugins/weather_station/protocols/ws_generic.c b/applications/external/weather_station/protocols/ws_generic.c similarity index 100% rename from applications/plugins/weather_station/protocols/ws_generic.c rename to applications/external/weather_station/protocols/ws_generic.c diff --git a/applications/plugins/weather_station/protocols/ws_generic.h b/applications/external/weather_station/protocols/ws_generic.h similarity index 100% rename from applications/plugins/weather_station/protocols/ws_generic.h rename to applications/external/weather_station/protocols/ws_generic.h diff --git a/applications/plugins/weather_station/scenes/weather_station_receiver.c b/applications/external/weather_station/scenes/weather_station_receiver.c similarity index 100% rename from applications/plugins/weather_station/scenes/weather_station_receiver.c rename to applications/external/weather_station/scenes/weather_station_receiver.c diff --git a/applications/plugins/weather_station/scenes/weather_station_scene.c b/applications/external/weather_station/scenes/weather_station_scene.c similarity index 100% rename from applications/plugins/weather_station/scenes/weather_station_scene.c rename to applications/external/weather_station/scenes/weather_station_scene.c diff --git a/applications/plugins/weather_station/scenes/weather_station_scene.h b/applications/external/weather_station/scenes/weather_station_scene.h similarity index 100% rename from applications/plugins/weather_station/scenes/weather_station_scene.h rename to applications/external/weather_station/scenes/weather_station_scene.h diff --git a/applications/plugins/weather_station/scenes/weather_station_scene_about.c b/applications/external/weather_station/scenes/weather_station_scene_about.c similarity index 100% rename from applications/plugins/weather_station/scenes/weather_station_scene_about.c rename to applications/external/weather_station/scenes/weather_station_scene_about.c diff --git a/applications/plugins/weather_station/scenes/weather_station_scene_config.h b/applications/external/weather_station/scenes/weather_station_scene_config.h similarity index 100% rename from applications/plugins/weather_station/scenes/weather_station_scene_config.h rename to applications/external/weather_station/scenes/weather_station_scene_config.h diff --git a/applications/plugins/weather_station/scenes/weather_station_scene_receiver_config.c b/applications/external/weather_station/scenes/weather_station_scene_receiver_config.c similarity index 100% rename from applications/plugins/weather_station/scenes/weather_station_scene_receiver_config.c rename to applications/external/weather_station/scenes/weather_station_scene_receiver_config.c diff --git a/applications/plugins/weather_station/scenes/weather_station_scene_receiver_info.c b/applications/external/weather_station/scenes/weather_station_scene_receiver_info.c similarity index 100% rename from applications/plugins/weather_station/scenes/weather_station_scene_receiver_info.c rename to applications/external/weather_station/scenes/weather_station_scene_receiver_info.c diff --git a/applications/plugins/weather_station/scenes/weather_station_scene_start.c b/applications/external/weather_station/scenes/weather_station_scene_start.c similarity index 100% rename from applications/plugins/weather_station/scenes/weather_station_scene_start.c rename to applications/external/weather_station/scenes/weather_station_scene_start.c diff --git a/applications/plugins/weather_station/views/weather_station_receiver.c b/applications/external/weather_station/views/weather_station_receiver.c similarity index 100% rename from applications/plugins/weather_station/views/weather_station_receiver.c rename to applications/external/weather_station/views/weather_station_receiver.c diff --git a/applications/plugins/weather_station/views/weather_station_receiver.h b/applications/external/weather_station/views/weather_station_receiver.h similarity index 100% rename from applications/plugins/weather_station/views/weather_station_receiver.h rename to applications/external/weather_station/views/weather_station_receiver.h diff --git a/applications/plugins/weather_station/views/weather_station_receiver_info.c b/applications/external/weather_station/views/weather_station_receiver_info.c similarity index 100% rename from applications/plugins/weather_station/views/weather_station_receiver_info.c rename to applications/external/weather_station/views/weather_station_receiver_info.c diff --git a/applications/plugins/weather_station/views/weather_station_receiver_info.h b/applications/external/weather_station/views/weather_station_receiver_info.h similarity index 100% rename from applications/plugins/weather_station/views/weather_station_receiver_info.h rename to applications/external/weather_station/views/weather_station_receiver_info.h diff --git a/applications/plugins/weather_station/weather_station_10px.png b/applications/external/weather_station/weather_station_10px.png similarity index 100% rename from applications/plugins/weather_station/weather_station_10px.png rename to applications/external/weather_station/weather_station_10px.png diff --git a/applications/plugins/weather_station/weather_station_app.c b/applications/external/weather_station/weather_station_app.c similarity index 100% rename from applications/plugins/weather_station/weather_station_app.c rename to applications/external/weather_station/weather_station_app.c diff --git a/applications/plugins/weather_station/weather_station_app_i.c b/applications/external/weather_station/weather_station_app_i.c similarity index 100% rename from applications/plugins/weather_station/weather_station_app_i.c rename to applications/external/weather_station/weather_station_app_i.c diff --git a/applications/plugins/weather_station/weather_station_app_i.h b/applications/external/weather_station/weather_station_app_i.h similarity index 100% rename from applications/plugins/weather_station/weather_station_app_i.h rename to applications/external/weather_station/weather_station_app_i.h diff --git a/applications/plugins/weather_station/weather_station_history.c b/applications/external/weather_station/weather_station_history.c similarity index 100% rename from applications/plugins/weather_station/weather_station_history.c rename to applications/external/weather_station/weather_station_history.c diff --git a/applications/plugins/weather_station/weather_station_history.h b/applications/external/weather_station/weather_station_history.h similarity index 100% rename from applications/plugins/weather_station/weather_station_history.h rename to applications/external/weather_station/weather_station_history.h diff --git a/applications/main/fap_loader/application.fam b/applications/main/fap_loader/application.fam index 784ee950..b0e67cd4 100644 --- a/applications/main/fap_loader/application.fam +++ b/applications/main/fap_loader/application.fam @@ -7,6 +7,7 @@ App( requires=[ "gui", "storage", + "loader", ], stack_size=int(1.5 * 1024), icon="A_Plugins_14", diff --git a/applications/main/fap_loader/elf_cpp/elf_hashtable.cpp b/applications/main/fap_loader/elf_cpp/elf_hashtable.cpp deleted file mode 100644 index e845ed00..00000000 --- a/applications/main/fap_loader/elf_cpp/elf_hashtable.cpp +++ /dev/null @@ -1,48 +0,0 @@ -#include "compilesort.hpp" -#include "elf_hashtable.h" -#include "elf_hashtable_entry.h" -#include "elf_hashtable_checks.hpp" - -#include -#include - -/* Generated table */ -#include - -#define TAG "elf_hashtable" - -static_assert(!has_hash_collisions(elf_api_table), "Detected API method hash collision!"); - -/** - * Get function address by function name - * @param name function name - * @param address output for function address - * @return true if the table contains a function - */ - -bool elf_resolve_from_hashtable(const char* name, Elf32_Addr* address) { - bool result = false; - uint32_t gnu_sym_hash = elf_gnu_hash(name); - - sym_entry key = { - .hash = gnu_sym_hash, - .address = 0, - }; - - auto find_res = std::lower_bound(elf_api_table.cbegin(), elf_api_table.cend(), key); - if((find_res == elf_api_table.cend() || (find_res->hash != gnu_sym_hash))) { - FURI_LOG_W(TAG, "Can't find symbol '%s' (hash %lx)!", name, gnu_sym_hash); - result = false; - } else { - result = true; - *address = find_res->address; - } - - return result; -} - -const ElfApiInterface hashtable_api_interface = { - .api_version_major = (elf_api_version >> 16), - .api_version_minor = (elf_api_version & 0xFFFF), - .resolver_callback = &elf_resolve_from_hashtable, -}; diff --git a/applications/main/fap_loader/elf_cpp/elf_hashtable.h b/applications/main/fap_loader/elf_cpp/elf_hashtable.h deleted file mode 100644 index e574f116..00000000 --- a/applications/main/fap_loader/elf_cpp/elf_hashtable.h +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once -#include - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -extern const ElfApiInterface hashtable_api_interface; - -#ifdef __cplusplus -} -#endif diff --git a/applications/main/fap_loader/elf_cpp/elf_hashtable_checks.hpp b/applications/main/fap_loader/elf_cpp/elf_hashtable_checks.hpp deleted file mode 100644 index 61ee80e9..00000000 --- a/applications/main/fap_loader/elf_cpp/elf_hashtable_checks.hpp +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Check for multiple entries with the same hash value at compilation time. - */ - -#pragma once -#include -#include "elf_hashtable_entry.h" - -template -constexpr bool has_hash_collisions(const std::array api_methods) { - for(std::size_t i = 0; i < (N - 1); ++i) { - if(api_methods[i].hash == api_methods[i + 1].hash) { - return true; - } - } - - return false; -} diff --git a/applications/main/fap_loader/elf_cpp/elf_hashtable_entry.h b/applications/main/fap_loader/elf_cpp/elf_hashtable_entry.h deleted file mode 100644 index 7b540fba..00000000 --- a/applications/main/fap_loader/elf_cpp/elf_hashtable_entry.h +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once -#include - -#ifdef __cplusplus -extern "C" { -#endif - -struct sym_entry { - uint32_t hash; - uint32_t address; -}; - -#ifdef __cplusplus -} - -#include -#include - -#define API_METHOD(x, ret_type, args_type) \ - sym_entry { \ - .hash = elf_gnu_hash(#x), .address = (uint32_t)(static_cast(x)) \ - } - -#define API_VARIABLE(x, var_type) \ - sym_entry { \ - .hash = elf_gnu_hash(#x), .address = (uint32_t)(&(x)), \ - } - -constexpr bool operator<(const sym_entry& k1, const sym_entry& k2) { - return k1.hash < k2.hash; -} - -constexpr uint32_t elf_gnu_hash(const char* s) { - uint32_t h = 0x1505; - for(unsigned char c = *s; c != '\0'; c = *++s) { - h = (h << 5) + h + c; - } - return h; -} - -#endif diff --git a/applications/main/fap_loader/fap_loader_app.c b/applications/main/fap_loader/fap_loader_app.c index e81a3ce4..dcbad8e1 100644 --- a/applications/main/fap_loader/fap_loader_app.c +++ b/applications/main/fap_loader/fap_loader_app.c @@ -7,7 +7,7 @@ #include #include #include -#include "elf_cpp/elf_hashtable.h" +#include #include "fap_loader_app.h" #define TAG "fap_loader_app" @@ -27,7 +27,7 @@ bool fap_loader_load_name_and_icon( Storage* storage, uint8_t** icon_ptr, FuriString* item_name) { - FlipperApplication* app = flipper_application_alloc(storage, &hashtable_api_interface); + FlipperApplication* app = flipper_application_alloc(storage, firmware_api_interface); FlipperApplicationPreloadStatus preload_res = flipper_application_preload_manifest(app, furi_string_get_cstr(path)); @@ -71,7 +71,7 @@ static bool fap_loader_run_selected_app(FapLoader* loader) { bool show_error = true; do { file_selected = true; - loader->app = flipper_application_alloc(loader->storage, &hashtable_api_interface); + loader->app = flipper_application_alloc(loader->storage, firmware_api_interface); size_t start = furi_get_tick(); FURI_LOG_I(TAG, "FAP Loader is loading %s", furi_string_get_cstr(loader->fap_path)); diff --git a/applications/plugins/application.fam b/applications/plugins/application.fam deleted file mode 100644 index 6d25e45a..00000000 --- a/applications/plugins/application.fam +++ /dev/null @@ -1,9 +0,0 @@ -App( - appid="basic_plugins", - name="Basic applications for plug-in menu", - apptype=FlipperAppType.METAPACKAGE, - provides=[ - "music_player", - "snake_game", - ], -) diff --git a/applications/services/applications.h b/applications/services/applications.h index 871e9af5..85f73674 100644 --- a/applications/services/applications.h +++ b/applications/services/applications.h @@ -39,18 +39,6 @@ extern const size_t FLIPPER_APPS_COUNT; extern const FlipperOnStartHook FLIPPER_ON_SYSTEM_START[]; extern const size_t FLIPPER_ON_SYSTEM_START_COUNT; -/* Plugins list - * Spawned by loader - */ -extern const FlipperApplication FLIPPER_PLUGINS[]; -extern const size_t FLIPPER_PLUGINS_COUNT; - -/* Debug menu apps - * Spawned by loader - */ -extern const FlipperApplication FLIPPER_DEBUG_APPS[]; -extern const size_t FLIPPER_DEBUG_APPS_COUNT; - /* System apps * Can only be spawned by loader by name */ diff --git a/applications/services/loader/application.fam b/applications/services/loader/application.fam index 91103e46..49f3c414 100644 --- a/applications/services/loader/application.fam +++ b/applications/services/loader/application.fam @@ -7,5 +7,8 @@ App( requires=["gui"], stack_size=2 * 1024, order=90, - sdk_headers=["loader.h"], + sdk_headers=[ + "loader.h", + "firmware_api/firmware_api.h", + ], ) diff --git a/applications/services/loader/firmware_api/firmware_api.cpp b/applications/services/loader/firmware_api/firmware_api.cpp new file mode 100644 index 00000000..814dd82c --- /dev/null +++ b/applications/services/loader/firmware_api/firmware_api.cpp @@ -0,0 +1,21 @@ +#include "firmware_api.h" + +#include +#include + +/* Generated table */ +#include + +static_assert(!has_hash_collisions(elf_api_table), "Detected API method hash collision!"); + +constexpr HashtableApiInterface elf_api_interface{ + { + .api_version_major = (elf_api_version >> 16), + .api_version_minor = (elf_api_version & 0xFFFF), + .resolver_callback = &elf_resolve_from_hashtable, + }, + .table_cbegin = elf_api_table.cbegin(), + .table_cend = elf_api_table.cend(), +}; + +const ElfApiInterface* const firmware_api_interface = &elf_api_interface; diff --git a/applications/services/loader/firmware_api/firmware_api.h b/applications/services/loader/firmware_api/firmware_api.h new file mode 100644 index 00000000..c73ae896 --- /dev/null +++ b/applications/services/loader/firmware_api/firmware_api.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const ElfApiInterface* const firmware_api_interface; diff --git a/applications/services/loader/loader.c b/applications/services/loader/loader.c index 5f2d8a2e..f83d47d6 100644 --- a/applications/services/loader/loader.c +++ b/applications/services/loader/loader.c @@ -88,10 +88,6 @@ static FlipperApplication const* loader_find_application_by_name_in_list( const FlipperApplication* loader_find_application_by_name(const char* name) { const FlipperApplication* application = NULL; application = loader_find_application_by_name_in_list(name, FLIPPER_APPS, FLIPPER_APPS_COUNT); - if(!application) { - application = - loader_find_application_by_name_in_list(name, FLIPPER_PLUGINS, FLIPPER_PLUGINS_COUNT); - } if(!application) { application = loader_find_application_by_name_in_list( name, FLIPPER_SETTINGS_APPS, FLIPPER_SETTINGS_APPS_COUNT); @@ -100,10 +96,6 @@ const FlipperApplication* loader_find_application_by_name(const char* name) { application = loader_find_application_by_name_in_list( name, FLIPPER_SYSTEM_APPS, FLIPPER_SYSTEM_APPS_COUNT); } - if(!application) { - application = loader_find_application_by_name_in_list( - name, FLIPPER_DEBUG_APPS, FLIPPER_DEBUG_APPS_COUNT); - } return application; } @@ -160,18 +152,6 @@ static void loader_cli_list(Cli* cli, FuriString* args, Loader* instance) { for(size_t i = 0; i < FLIPPER_APPS_COUNT; i++) { printf("\t%s\r\n", FLIPPER_APPS[i].name); } - - printf("Plugins:\r\n"); - for(size_t i = 0; i < FLIPPER_PLUGINS_COUNT; i++) { - printf("\t%s\r\n", FLIPPER_PLUGINS[i].name); - } - - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - printf("Debug:\r\n"); - for(size_t i = 0; i < FLIPPER_DEBUG_APPS_COUNT; i++) { - printf("\t%s\r\n", FLIPPER_DEBUG_APPS[i].name); - } - } } static void loader_cli_info(Cli* cli, FuriString* args, Loader* instance) { @@ -341,22 +321,6 @@ static Loader* loader_alloc() { view_set_previous_callback(menu_get_view(instance->primary_menu), loader_hide_menu); view_dispatcher_add_view( instance->view_dispatcher, LoaderMenuViewPrimary, menu_get_view(instance->primary_menu)); - // Plugins menu - instance->plugins_menu = submenu_alloc(); - view_set_context(submenu_get_view(instance->plugins_menu), instance->plugins_menu); - view_set_previous_callback( - submenu_get_view(instance->plugins_menu), loader_back_to_primary_menu); - view_dispatcher_add_view( - instance->view_dispatcher, - LoaderMenuViewPlugins, - submenu_get_view(instance->plugins_menu)); - // Debug menu - instance->debug_menu = submenu_alloc(); - view_set_context(submenu_get_view(instance->debug_menu), instance->debug_menu); - view_set_previous_callback( - submenu_get_view(instance->debug_menu), loader_back_to_primary_menu); - view_dispatcher_add_view( - instance->view_dispatcher, LoaderMenuViewDebug, submenu_get_view(instance->debug_menu)); // Settings menu instance->settings_menu = submenu_alloc(); view_set_context(submenu_get_view(instance->settings_menu), instance->settings_menu); @@ -385,10 +349,6 @@ static void loader_free(Loader* instance) { menu_free(loader_instance->primary_menu); view_dispatcher_remove_view(loader_instance->view_dispatcher, LoaderMenuViewPrimary); - submenu_free(loader_instance->plugins_menu); - view_dispatcher_remove_view(loader_instance->view_dispatcher, LoaderMenuViewPlugins); - submenu_free(loader_instance->debug_menu); - view_dispatcher_remove_view(loader_instance->view_dispatcher, LoaderMenuViewDebug); submenu_free(loader_instance->settings_menu); view_dispatcher_remove_view(loader_instance->view_dispatcher, LoaderMenuViewSettings); view_dispatcher_free(loader_instance->view_dispatcher); @@ -411,24 +371,6 @@ static void loader_build_menu() { loader_menu_callback, (void*)&FLIPPER_APPS[i]); } - if(FLIPPER_PLUGINS_COUNT != 0) { - menu_add_item( - loader_instance->primary_menu, - "Plugins", - &A_Plugins_14, - i++, - loader_submenu_callback, - (void*)LoaderMenuViewPlugins); - } - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug) && (FLIPPER_DEBUG_APPS_COUNT > 0)) { - menu_add_item( - loader_instance->primary_menu, - "Debug Tools", - &A_Debug_14, - i++, - loader_submenu_callback, - (void*)LoaderMenuViewDebug); - } menu_add_item( loader_instance->primary_menu, "Settings", @@ -439,29 +381,8 @@ static void loader_build_menu() { } static void loader_build_submenu() { - FURI_LOG_I(TAG, "Building plugins menu"); - size_t i; - for(i = 0; i < FLIPPER_PLUGINS_COUNT; i++) { - submenu_add_item( - loader_instance->plugins_menu, - FLIPPER_PLUGINS[i].name, - i, - loader_menu_callback, - (void*)&FLIPPER_PLUGINS[i]); - } - - FURI_LOG_I(TAG, "Building debug menu"); - for(i = 0; i < FLIPPER_DEBUG_APPS_COUNT; i++) { - submenu_add_item( - loader_instance->debug_menu, - FLIPPER_DEBUG_APPS[i].name, - i, - loader_menu_callback, - (void*)&FLIPPER_DEBUG_APPS[i]); - } - FURI_LOG_I(TAG, "Building settings menu"); - for(i = 0; i < FLIPPER_SETTINGS_APPS_COUNT; i++) { + for(size_t i = 0; i < FLIPPER_SETTINGS_APPS_COUNT; i++) { submenu_add_item( loader_instance->settings_menu, FLIPPER_SETTINGS_APPS[i].name, diff --git a/applications/services/loader/loader_i.h b/applications/services/loader/loader_i.h index db91f806..00028cd6 100644 --- a/applications/services/loader/loader_i.h +++ b/applications/services/loader/loader_i.h @@ -26,8 +26,6 @@ struct Loader { ViewDispatcher* view_dispatcher; Menu* primary_menu; - Submenu* plugins_menu; - Submenu* debug_menu; Submenu* settings_menu; volatile uint8_t lock_count; @@ -37,7 +35,5 @@ struct Loader { typedef enum { LoaderMenuViewPrimary, - LoaderMenuViewPlugins, - LoaderMenuViewDebug, LoaderMenuViewSettings, } LoaderMenuView; diff --git a/assets/.gitignore b/assets/.gitignore index 26957704..a66a6eed 100644 --- a/assets/.gitignore +++ b/assets/.gitignore @@ -2,3 +2,4 @@ /resources/Manifest /resources/apps/* /resources/dolphin/* +/resources/apps_data/**/*.fal diff --git a/documentation/Doxyfile b/documentation/Doxyfile index 1824e5a5..9611e7f1 100644 --- a/documentation/Doxyfile +++ b/documentation/Doxyfile @@ -938,7 +938,7 @@ EXCLUDE = \ lib/microtar \ lib/mbedtls \ lib/cxxheaderparser \ - applications/plugins/dap_link/lib/free-dap + applications/external/dap_link/lib/free-dap # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded diff --git a/fbt_options.py b/fbt_options.py index 4850389a..a10c64b9 100644 --- a/fbt_options.py +++ b/fbt_options.py @@ -71,11 +71,6 @@ FIRMWARE_APPS = { "system_apps", # Settings "settings_apps", - # Stock plugins - no longer built into fw, now they're .faps - # Yet you can still build them as a part of fw - # "basic_plugins", - # Debug - # "debug_apps", ], "unit_tests": [ "basic_services", diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index 07c323a1..61195aba 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,+,18.1,, +Version,+,18.2,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -28,6 +28,7 @@ Header,+,applications/services/gui/modules/widget_elements/widget_element.h,, Header,+,applications/services/gui/view_dispatcher.h,, Header,+,applications/services/gui/view_stack.h,, Header,+,applications/services/input/input.h,, +Header,+,applications/services/loader/firmware_api/firmware_api.h,, Header,+,applications/services/loader/loader.h,, Header,+,applications/services/locale/locale.h,, Header,+,applications/services/notification/notification.h,, @@ -104,7 +105,11 @@ Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_tim.h,, Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_usart.h,, Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_utils.h,, Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_wwdg.h,, +Header,+,lib/flipper_application/api_hashtable/api_hashtable.h,, +Header,+,lib/flipper_application/api_hashtable/compilesort.hpp,, Header,+,lib/flipper_application/flipper_application.h,, +Header,+,lib/flipper_application/plugins/composite_resolver.h,, +Header,+,lib/flipper_application/plugins/plugin_manager.h,, Header,+,lib/flipper_format/flipper_format.h,, Header,+,lib/flipper_format/flipper_format_i.h,, Header,+,lib/libusb_stm32/inc/hid_usage_button.h,, @@ -567,6 +572,10 @@ Function,+,cli_session_close,void,Cli* Function,+,cli_session_open,void,"Cli*, void*" Function,+,cli_write,void,"Cli*, const uint8_t*, size_t" Function,-,clock,clock_t, +Function,+,composite_api_resolver_add,void,"CompositeApiResolver*, const ElfApiInterface*" +Function,+,composite_api_resolver_alloc,CompositeApiResolver*, +Function,+,composite_api_resolver_free,void,CompositeApiResolver* +Function,+,composite_api_resolver_get,const ElfApiInterface*,CompositeApiResolver* Function,+,crc32_calc_buffer,uint32_t,"uint32_t, const void*, size_t" Function,+,crc32_calc_file,uint32_t,"File*, const FileCrcProgressCb, void*" Function,-,ctermid,char*,char* @@ -639,6 +648,7 @@ Function,+,elements_slightly_rounded_box,void,"Canvas*, uint8_t, uint8_t, uint8_ Function,+,elements_slightly_rounded_frame,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" Function,+,elements_string_fit_width,void,"Canvas*, FuriString*, uint8_t" Function,+,elements_text_box,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, Align, Align, const char*, _Bool" +Function,+,elf_resolve_from_hashtable,_Bool,"const ElfApiInterface*, const char*, Elf32_Addr*" Function,+,empty_screen_alloc,EmptyScreen*, Function,+,empty_screen_free,void,EmptyScreen* Function,+,empty_screen_get_view,View*,EmptyScreen* @@ -696,14 +706,16 @@ Function,-,fiscanf,int,"FILE*, const char*, ..." Function,+,flipper_application_alloc,FlipperApplication*,"Storage*, const ElfApiInterface*" Function,+,flipper_application_free,void,FlipperApplication* Function,+,flipper_application_get_manifest,const FlipperApplicationManifest*,FlipperApplication* +Function,+,flipper_application_is_plugin,_Bool,FlipperApplication* Function,+,flipper_application_load_status_to_string,const char*,FlipperApplicationLoadStatus Function,+,flipper_application_manifest_is_compatible,_Bool,"const FlipperApplicationManifest*, const ElfApiInterface*" Function,+,flipper_application_manifest_is_target_compatible,_Bool,const FlipperApplicationManifest* Function,+,flipper_application_manifest_is_valid,_Bool,const FlipperApplicationManifest* Function,+,flipper_application_map_to_memory,FlipperApplicationLoadStatus,FlipperApplication* +Function,+,flipper_application_plugin_get_descriptor,const FlipperAppPluginDescriptor*,FlipperApplication* Function,+,flipper_application_preload,FlipperApplicationPreloadStatus,"FlipperApplication*, const char*" Function,+,flipper_application_preload_manifest,FlipperApplicationPreloadStatus,"FlipperApplication*, const char*" -Function,-,flipper_application_preload_status_to_string,const char*,FlipperApplicationPreloadStatus +Function,+,flipper_application_preload_status_to_string,const char*,FlipperApplicationPreloadStatus Function,+,flipper_application_spawn,FuriThread*,"FlipperApplication*, void*" Function,+,flipper_format_buffered_file_alloc,FlipperFormat*,Storage* Function,+,flipper_format_buffered_file_close,_Bool,FlipperFormat* @@ -1473,6 +1485,13 @@ Function,-,pcTaskGetName,char*,TaskHandle_t Function,-,pcTimerGetName,const char*,TimerHandle_t Function,-,pclose,int,FILE* Function,-,perror,void,const char* +Function,+,plugin_manager_alloc,PluginManager*,"const char*, uint32_t, const ElfApiInterface*" +Function,+,plugin_manager_free,void,PluginManager* +Function,+,plugin_manager_get,const FlipperAppPluginDescriptor*,"PluginManager*, uint32_t" +Function,+,plugin_manager_get_count,uint32_t,PluginManager* +Function,+,plugin_manager_get_ep,const void*,"PluginManager*, uint32_t" +Function,+,plugin_manager_load_all,PluginManagerError,"PluginManager*, const char*" +Function,+,plugin_manager_load_single,PluginManagerError,"PluginManager*, const char*" Function,-,popen,FILE*,"const char*, const char*" Function,+,popup_alloc,Popup*, Function,+,popup_disable_timeout,void,Popup* @@ -2053,6 +2072,7 @@ Variable,-,_sys_nerr,int, Variable,-,_timezone,long, Variable,-,_tzname,char*[2], Variable,+,cli_vcp,CliSession, +Variable,+,firmware_api_interface,const ElfApiInterface*, Variable,+,furi_hal_i2c_bus_external,FuriHalI2cBus, Variable,+,furi_hal_i2c_bus_power,FuriHalI2cBus, Variable,+,furi_hal_i2c_handle_external,FuriHalI2cBusHandle, diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index fcacaeee..e46322f4 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,+,18.1,, +Version,+,18.2,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -28,6 +28,7 @@ Header,+,applications/services/gui/modules/widget_elements/widget_element.h,, Header,+,applications/services/gui/view_dispatcher.h,, Header,+,applications/services/gui/view_stack.h,, Header,+,applications/services/input/input.h,, +Header,+,applications/services/loader/firmware_api/firmware_api.h,, Header,+,applications/services/loader/loader.h,, Header,+,applications/services/locale/locale.h,, Header,+,applications/services/notification/notification.h,, @@ -110,7 +111,11 @@ Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_tim.h,, Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_usart.h,, Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_utils.h,, Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_wwdg.h,, +Header,+,lib/flipper_application/api_hashtable/api_hashtable.h,, +Header,+,lib/flipper_application/api_hashtable/compilesort.hpp,, Header,+,lib/flipper_application/flipper_application.h,, +Header,+,lib/flipper_application/plugins/composite_resolver.h,, +Header,+,lib/flipper_application/plugins/plugin_manager.h,, Header,+,lib/flipper_format/flipper_format.h,, Header,+,lib/flipper_format/flipper_format_i.h,, Header,+,lib/infrared/encoder_decoder/infrared.h,, @@ -679,6 +684,10 @@ Function,+,cli_session_close,void,Cli* Function,+,cli_session_open,void,"Cli*, void*" Function,+,cli_write,void,"Cli*, const uint8_t*, size_t" Function,-,clock,clock_t, +Function,+,composite_api_resolver_add,void,"CompositeApiResolver*, const ElfApiInterface*" +Function,+,composite_api_resolver_alloc,CompositeApiResolver*, +Function,+,composite_api_resolver_free,void,CompositeApiResolver* +Function,+,composite_api_resolver_get,const ElfApiInterface*,CompositeApiResolver* Function,-,copysign,double,"double, double" Function,-,copysignf,float,"float, float" Function,-,copysignl,long double,"long double, long double" @@ -778,6 +787,7 @@ Function,+,elements_slightly_rounded_box,void,"Canvas*, uint8_t, uint8_t, uint8_ Function,+,elements_slightly_rounded_frame,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" Function,+,elements_string_fit_width,void,"Canvas*, FuriString*, uint8_t" Function,+,elements_text_box,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, Align, Align, const char*, _Bool" +Function,+,elf_resolve_from_hashtable,_Bool,"const ElfApiInterface*, const char*, Elf32_Addr*" Function,+,empty_screen_alloc,EmptyScreen*, Function,+,empty_screen_free,void,EmptyScreen* Function,+,empty_screen_get_view,View*,EmptyScreen* @@ -863,14 +873,16 @@ Function,-,fiscanf,int,"FILE*, const char*, ..." Function,+,flipper_application_alloc,FlipperApplication*,"Storage*, const ElfApiInterface*" Function,+,flipper_application_free,void,FlipperApplication* Function,+,flipper_application_get_manifest,const FlipperApplicationManifest*,FlipperApplication* +Function,+,flipper_application_is_plugin,_Bool,FlipperApplication* Function,+,flipper_application_load_status_to_string,const char*,FlipperApplicationLoadStatus Function,+,flipper_application_manifest_is_compatible,_Bool,"const FlipperApplicationManifest*, const ElfApiInterface*" Function,+,flipper_application_manifest_is_target_compatible,_Bool,const FlipperApplicationManifest* Function,+,flipper_application_manifest_is_valid,_Bool,const FlipperApplicationManifest* Function,+,flipper_application_map_to_memory,FlipperApplicationLoadStatus,FlipperApplication* +Function,+,flipper_application_plugin_get_descriptor,const FlipperAppPluginDescriptor*,FlipperApplication* Function,+,flipper_application_preload,FlipperApplicationPreloadStatus,"FlipperApplication*, const char*" Function,+,flipper_application_preload_manifest,FlipperApplicationPreloadStatus,"FlipperApplication*, const char*" -Function,-,flipper_application_preload_status_to_string,const char*,FlipperApplicationPreloadStatus +Function,+,flipper_application_preload_status_to_string,const char*,FlipperApplicationPreloadStatus Function,+,flipper_application_spawn,FuriThread*,"FlipperApplication*, void*" Function,+,flipper_format_buffered_file_alloc,FlipperFormat*,Storage* Function,+,flipper_format_buffered_file_close,_Bool,FlipperFormat* @@ -2091,6 +2103,13 @@ Function,-,platformProtectST25RComm,void, Function,-,platformSetIrqCallback,void,PlatformIrqCallback Function,-,platformSpiTxRx,_Bool,"const uint8_t*, uint8_t*, uint16_t" Function,-,platformUnprotectST25RComm,void, +Function,+,plugin_manager_alloc,PluginManager*,"const char*, uint32_t, const ElfApiInterface*" +Function,+,plugin_manager_free,void,PluginManager* +Function,+,plugin_manager_get,const FlipperAppPluginDescriptor*,"PluginManager*, uint32_t" +Function,+,plugin_manager_get_count,uint32_t,PluginManager* +Function,+,plugin_manager_get_ep,const void*,"PluginManager*, uint32_t" +Function,+,plugin_manager_load_all,PluginManagerError,"PluginManager*, const char*" +Function,+,plugin_manager_load_single,PluginManagerError,"PluginManager*, const char*" Function,-,popen,FILE*,"const char*, const char*" Function,+,popup_alloc,Popup*, Function,+,popup_disable_timeout,void,Popup* @@ -3021,6 +3040,7 @@ Variable,-,_sys_nerr,int, Variable,-,_timezone,long, Variable,-,_tzname,char*[2], Variable,+,cli_vcp,CliSession, +Variable,+,firmware_api_interface,const ElfApiInterface*, Variable,+,furi_hal_i2c_bus_external,FuriHalI2cBus, Variable,+,furi_hal_i2c_bus_power,FuriHalI2cBus, Variable,+,furi_hal_i2c_handle_external,FuriHalI2cBusHandle, diff --git a/furi/flipper.c b/furi/flipper.c index f0147c06..5c2ad813 100644 --- a/furi/flipper.c +++ b/furi/flipper.c @@ -33,7 +33,7 @@ void flipper_init() { FURI_LOG_I(TAG, "Boot mode %d, starting services", furi_hal_rtc_get_boot_mode()); for(size_t i = 0; i < FLIPPER_SERVICES_COUNT; i++) { - FURI_LOG_I(TAG, "Starting service %s", FLIPPER_SERVICES[i].name); + FURI_LOG_D(TAG, "Starting service %s", FLIPPER_SERVICES[i].name); FuriThread* thread = furi_thread_alloc_ex( FLIPPER_SERVICES[i].name, diff --git a/lib/flipper_application/SConscript b/lib/flipper_application/SConscript index 9fbbf95d..d253cc82 100644 --- a/lib/flipper_application/SConscript +++ b/lib/flipper_application/SConscript @@ -6,6 +6,10 @@ env.Append( ], SDK_HEADERS=[ File("flipper_application.h"), + File("plugins/plugin_manager.h"), + File("plugins/composite_resolver.h"), + File("api_hashtable/api_hashtable.h"), + File("api_hashtable/compilesort.hpp"), ], ) @@ -14,6 +18,7 @@ libenv = env.Clone(FW_LIB_NAME="flipper_application") libenv.ApplyLibFlags() sources = libenv.GlobRecursive("*.c") +sources.append(File("api_hashtable/api_hashtable.cpp")) lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources) libenv.Install("${LIB_DIST_DIR}", lib) diff --git a/lib/flipper_application/api_hashtable/api_hashtable.cpp b/lib/flipper_application/api_hashtable/api_hashtable.cpp new file mode 100644 index 00000000..022792dc --- /dev/null +++ b/lib/flipper_application/api_hashtable/api_hashtable.cpp @@ -0,0 +1,38 @@ +#include "api_hashtable.h" + +#include +#include + +#define TAG "hashtable_api" + +bool elf_resolve_from_hashtable( + const ElfApiInterface* interface, + const char* name, + Elf32_Addr* address) { + const HashtableApiInterface* hashtable_interface = + static_cast(interface); + bool result = false; + uint32_t gnu_sym_hash = elf_gnu_hash(name); + + sym_entry key = { + .hash = gnu_sym_hash, + .address = 0, + }; + + auto find_res = + std::lower_bound(hashtable_interface->table_cbegin, hashtable_interface->table_cend, key); + if((find_res == hashtable_interface->table_cend || (find_res->hash != gnu_sym_hash))) { + FURI_LOG_W( + TAG, + "Can't find symbol '%s' (hash %lx) @ %p!", + name, + gnu_sym_hash, + hashtable_interface->table_cbegin); + result = false; + } else { + result = true; + *address = find_res->address; + } + + return result; +} diff --git a/lib/flipper_application/api_hashtable/api_hashtable.h b/lib/flipper_application/api_hashtable/api_hashtable.h new file mode 100644 index 00000000..7e4b4aba --- /dev/null +++ b/lib/flipper_application/api_hashtable/api_hashtable.h @@ -0,0 +1,85 @@ +#pragma once + +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Symbol table entry + */ +struct sym_entry { + uint32_t hash; + uint32_t address; +}; + +/** + * @brief Resolver for API entries using a pre-sorted table with hashes + * @param interface pointer to HashtableApiInterface + * @param name function name + * @param address output for function address + * @return true if the table contains a function + */ +bool elf_resolve_from_hashtable( + const ElfApiInterface* interface, + const char* name, + Elf32_Addr* address); + +#ifdef __cplusplus +} + +#include +#include + +/** + * @brief HashtableApiInterface is an implementation of ElfApiInterface + * that uses a hash table to resolve function addresses. + * table_cbegin and table_cend must point to a sorted array of sym_entry + */ +struct HashtableApiInterface : public ElfApiInterface { + const sym_entry *table_cbegin, *table_cend; +}; + +#define API_METHOD(x, ret_type, args_type) \ + sym_entry { \ + .hash = elf_gnu_hash(#x), .address = (uint32_t)(static_cast(x)) \ + } + +#define API_VARIABLE(x, var_type) \ + sym_entry { .hash = elf_gnu_hash(#x), .address = (uint32_t)(&(x)), } + +constexpr bool operator<(const sym_entry& k1, const sym_entry& k2) { + return k1.hash < k2.hash; +} + +/** + * @brief Calculate hash for a string using the ELF GNU hash algorithm + * @param s string to calculate hash for + * @return hash value + */ +constexpr uint32_t elf_gnu_hash(const char* s) { + uint32_t h = 0x1505; + for(unsigned char c = *s; c != '\0'; c = *++s) { + h = (h << 5) + h + c; + } + return h; +} + +/* Compile-time check for hash collisions in API table. + * Usage: static_assert(!has_hash_collisions(api_methods), "Hash collision detected"); + */ +template +constexpr bool has_hash_collisions(const std::array& api_methods) { + for(std::size_t i = 0; i < (N - 1); ++i) { + if(api_methods[i].hash == api_methods[i + 1].hash) { + return true; + } + } + + return false; +} + +#endif \ No newline at end of file diff --git a/applications/main/fap_loader/elf_cpp/compilesort.hpp b/lib/flipper_application/api_hashtable/compilesort.hpp similarity index 99% rename from applications/main/fap_loader/elf_cpp/compilesort.hpp rename to lib/flipper_application/api_hashtable/compilesort.hpp index 74661169..9737fd02 100644 --- a/applications/main/fap_loader/elf_cpp/compilesort.hpp +++ b/lib/flipper_application/api_hashtable/compilesort.hpp @@ -4,6 +4,8 @@ #pragma once +#ifdef __cplusplus + #include #include @@ -109,3 +111,5 @@ constexpr auto create_array_t(const Ts&&... values) { static_assert(traits::are_same_type(), "all elements must have same type"); return std::array{static_cast(values)...}; } + +#endif diff --git a/lib/flipper_application/elf/elf_api_interface.h b/lib/flipper_application/elf/elf_api_interface.h index ca31fc48..f07df4ed 100644 --- a/lib/flipper_application/elf/elf_api_interface.h +++ b/lib/flipper_application/elf/elf_api_interface.h @@ -3,10 +3,14 @@ #include #include -#define ELF_INVALID_ADDRESS 0xFFFFFFFF - -typedef struct { +/** + * @brief Interface for ELF loader to resolve symbols + */ +typedef struct ElfApiInterface { uint16_t api_version_major; uint16_t api_version_minor; - bool (*resolver_callback)(const char* name, Elf32_Addr* address); + bool (*resolver_callback)( + const struct ElfApiInterface* interface, + const char* name, + Elf32_Addr* address); } ElfApiInterface; diff --git a/lib/flipper_application/elf/elf_file.c b/lib/flipper_application/elf/elf_file.c index 58e31533..146afccb 100644 --- a/lib/flipper_application/elf/elf_file.c +++ b/lib/flipper_application/elf/elf_file.c @@ -17,6 +17,8 @@ #define FURI_LOG_D(...) #endif +#define ELF_INVALID_ADDRESS 0xFFFFFFFF + #define TRAMPOLINE_CODE_SIZE 6 /** @@ -166,7 +168,7 @@ static ELFSection* elf_section_of(ELFFile* elf, int index) { static Elf32_Addr elf_address_of(ELFFile* elf, Elf32_Sym* sym, const char* sName) { if(sym->st_shndx == SHN_UNDEF) { Elf32_Addr addr = 0; - if(elf->api_interface->resolver_callback(sName, &addr)) { + if(elf->api_interface->resolver_callback(elf->api_interface, sName, &addr)) { return addr; } } else { @@ -514,10 +516,13 @@ static SectionType elf_preload_section( section_p->sec_idx = section_idx; if(section_header->sh_type == SHT_PREINIT_ARRAY) { + furi_assert(elf->preinit_array == NULL); elf->preinit_array = section_p; } else if(section_header->sh_type == SHT_INIT_ARRAY) { + furi_assert(elf->init_array == NULL); elf->init_array = section_p; } else if(section_header->sh_type == SHT_FINI_ARRAY) { + furi_assert(elf->fini_array == NULL); elf->fini_array = section_p; } @@ -605,10 +610,17 @@ ELFFile* elf_file_alloc(Storage* storage, const ElfApiInterface* api_interface) elf->api_interface = api_interface; ELFSectionDict_init(elf->sections); AddressCache_init(elf->trampoline_cache); + elf->init_array_called = false; return elf; } void elf_file_free(ELFFile* elf) { + // furi_check(!elf->init_array_called); + if(elf->init_array_called) { + FURI_LOG_W(TAG, "Init array was called, but fini array wasn't"); + elf_file_call_section_list(elf->fini_array, true); + } + // free sections data { ELFSectionDict_it_t it; @@ -774,19 +786,26 @@ ELFFileLoadStatus elf_file_load_sections(ELFFile* elf) { return status; } -void elf_file_pre_run(ELFFile* elf) { +void elf_file_call_init(ELFFile* elf) { + furi_check(!elf->init_array_called); elf_file_call_section_list(elf->preinit_array, false); elf_file_call_section_list(elf->init_array, false); + elf->init_array_called = true; } -int32_t elf_file_run(ELFFile* elf, void* args) { - int32_t result; - result = ((int32_t(*)(void*))elf->entry)(args); - return result; +bool elf_file_is_init_complete(ELFFile* elf) { + return elf->init_array_called; } -void elf_file_post_run(ELFFile* elf) { +void* elf_file_get_entry_point(ELFFile* elf) { + furi_check(elf->init_array_called); + return (void*)elf->entry; +} + +void elf_file_call_fini(ELFFile* elf) { + furi_check(elf->init_array_called); elf_file_call_section_list(elf->fini_array, true); + elf->init_array_called = false; } const ElfApiInterface* elf_file_get_api_interface(ELFFile* elf_file) { diff --git a/lib/flipper_application/elf/elf_file.h b/lib/flipper_application/elf/elf_file.h index f371cdb2..631fe122 100644 --- a/lib/flipper_application/elf/elf_file.h +++ b/lib/flipper_application/elf/elf_file.h @@ -82,24 +82,34 @@ bool elf_file_load_section_table(ELFFile* elf_file); ELFFileLoadStatus elf_file_load_sections(ELFFile* elf_file); /** - * @brief Execute ELF file pre-run stage, call static constructors for example (load stage #3) + * @brief Execute ELF file pre-run stage, + * call static constructors for example (load stage #3) + * Must be done before invoking any code from the ELF file * @param elf */ -void elf_file_pre_run(ELFFile* elf); +void elf_file_call_init(ELFFile* elf); /** - * @brief Run ELF file (load stage #4) + * @brief Check if ELF file pre-run stage was executed and its code is runnable + * @param elf + */ +bool elf_file_is_init_complete(ELFFile* elf); + +/** + * @brief Get actual entry point for ELF file * @param elf_file * @param args * @return int32_t */ -int32_t elf_file_run(ELFFile* elf_file, void* args); +void* elf_file_get_entry_point(ELFFile* elf_file); /** - * @brief Execute ELF file post-run stage, call static destructors for example (load stage #5) + * @brief Execute ELF file post-run stage, + * call static destructors for example (load stage #5) + * Must be done if any code from the ELF file was executed * @param elf */ -void elf_file_post_run(ELFFile* elf); +void elf_file_call_fini(ELFFile* elf); /** * @brief Get ELF file API interface diff --git a/lib/flipper_application/elf/elf_file_i.h b/lib/flipper_application/elf/elf_file_i.h index 9b38540b..af9a1d9b 100644 --- a/lib/flipper_application/elf/elf_file_i.h +++ b/lib/flipper_application/elf/elf_file_i.h @@ -45,6 +45,8 @@ struct ELFFile { ELFSection* preinit_array; ELFSection* init_array; ELFSection* fini_array; + + bool init_array_called; }; #ifdef __cplusplus diff --git a/lib/flipper_application/flipper_application.c b/lib/flipper_application/flipper_application.c index 6e20c080..ca917cf1 100644 --- a/lib/flipper_application/flipper_application.c +++ b/lib/flipper_application/flipper_application.c @@ -10,6 +10,7 @@ struct FlipperApplication { FlipperApplicationManifest manifest; ELFFile* elf; FuriThread* thread; + void* ep_thread_args; }; /* For debugger access to app state */ @@ -20,9 +21,14 @@ FlipperApplication* FlipperApplication* app = malloc(sizeof(FlipperApplication)); app->elf = elf_file_alloc(storage, api_interface); app->thread = NULL; + app->ep_thread_args = NULL; return app; } +bool flipper_application_is_plugin(FlipperApplication* app) { + return app->manifest.stack_size == 0; +} + void flipper_application_free(FlipperApplication* app) { furi_assert(app); @@ -31,9 +37,16 @@ void flipper_application_free(FlipperApplication* app) { furi_thread_free(app->thread); } - last_loaded_app = NULL; + if(!flipper_application_is_plugin(app)) { + last_loaded_app = NULL; + } elf_file_clear_debug_info(&app->state); + + if(elf_file_is_init_complete(app->elf)) { + elf_file_call_fini(app->elf); + } + elf_file_free(app->elf); free(app); } @@ -140,7 +153,9 @@ const FlipperApplicationManifest* flipper_application_get_manifest(FlipperApplic } FlipperApplicationLoadStatus flipper_application_map_to_memory(FlipperApplication* app) { - last_loaded_app = app; + if(!flipper_application_is_plugin(app)) { + last_loaded_app = app; + } ELFFileLoadStatus status = elf_file_load_sections(app->elf); switch(status) { @@ -157,9 +172,15 @@ FlipperApplicationLoadStatus flipper_application_map_to_memory(FlipperApplicatio } static int32_t flipper_application_thread(void* context) { - elf_file_pre_run(last_loaded_app->elf); - int32_t result = elf_file_run(last_loaded_app->elf, context); - elf_file_post_run(last_loaded_app->elf); + furi_assert(context); + FlipperApplication* app = (FlipperApplication*)context; + + elf_file_call_init(app->elf); + + FlipperApplicationEntryPoint entry_point = elf_file_get_entry_point(app->elf); + int32_t ret_code = entry_point(app->ep_thread_args); + + elf_file_call_fini(app->elf); // wait until all notifications from RAM are completed NotificationApp* notifications = furi_record_open(RECORD_NOTIFICATION); @@ -169,17 +190,17 @@ static int32_t flipper_application_thread(void* context) { notification_message_block(notifications, &sequence_empty); furi_record_close(RECORD_NOTIFICATION); - return result; + return ret_code; } FuriThread* flipper_application_spawn(FlipperApplication* app, void* args) { furi_check(app->thread == NULL); + furi_check(!flipper_application_is_plugin(app)); + app->ep_thread_args = args; const FlipperApplicationManifest* manifest = flipper_application_get_manifest(app); - furi_check(manifest->stack_size > 0); - app->thread = furi_thread_alloc_ex( - manifest->name, manifest->stack_size, flipper_application_thread, args); + manifest->name, manifest->stack_size, flipper_application_thread, app); return app->thread; } @@ -213,3 +234,28 @@ const char* flipper_application_load_status_to_string(FlipperApplicationLoadStat } return load_status_strings[status]; } + +const FlipperAppPluginDescriptor* + flipper_application_plugin_get_descriptor(FlipperApplication* app) { + if(!flipper_application_is_plugin(app)) { + return NULL; + } + + if(!elf_file_is_init_complete(app->elf)) { + elf_file_call_init(app->elf); + } + + typedef const FlipperAppPluginDescriptor* (*get_lib_descriptor_t)(void); + get_lib_descriptor_t lib_ep = elf_file_get_entry_point(app->elf); + furi_check(lib_ep); + + const FlipperAppPluginDescriptor* lib_descriptor = lib_ep(); + + FURI_LOG_D( + TAG, + "Library for %s, API v. %lu loaded", + lib_descriptor->appid, + lib_descriptor->ep_api_version); + + return lib_descriptor; +} \ No newline at end of file diff --git a/lib/flipper_application/flipper_application.h b/lib/flipper_application/flipper_application.h index b3e5996b..519cc397 100644 --- a/lib/flipper_application/flipper_application.h +++ b/lib/flipper_application/flipper_application.h @@ -115,6 +115,40 @@ FlipperApplicationLoadStatus flipper_application_map_to_memory(FlipperApplicatio */ FuriThread* flipper_application_spawn(FlipperApplication* app, void* args); +/** + * @brief Check if application is a plugin (not a runnable standalone app) + * @param app Application pointer + * @return true if application is a plugin, false otherwise + */ +bool flipper_application_is_plugin(FlipperApplication* app); + +/** + * @brief Entry point prototype for standalone applications + */ +typedef int32_t (*FlipperApplicationEntryPoint)(void*); + +/** + * @brief An object that describes a plugin - must be returned by plugin's entry point + */ +typedef struct { + const char* appid; + const uint32_t ep_api_version; + const void* entry_point; +} FlipperAppPluginDescriptor; + +/** + * @brief Entry point prototype for plugins + */ +typedef const FlipperAppPluginDescriptor* (*FlipperApplicationPluginEntryPoint)(void); + +/** + * @brief Get plugin descriptor for preloaded plugin + * @param app Application pointer + * @return Pointer to plugin descriptor + */ +const FlipperAppPluginDescriptor* + flipper_application_plugin_get_descriptor(FlipperApplication* app); + #ifdef __cplusplus } -#endif \ No newline at end of file +#endif diff --git a/lib/flipper_application/plugins/composite_resolver.c b/lib/flipper_application/plugins/composite_resolver.c new file mode 100644 index 00000000..1402c3ad --- /dev/null +++ b/lib/flipper_application/plugins/composite_resolver.c @@ -0,0 +1,52 @@ +#include "composite_resolver.h" + +#include +#include + +LIST_DEF(ElfApiInterfaceList, const ElfApiInterface*, M_POD_OPLIST) +#define M_OPL_ElfApiInterfaceList_t() LIST_OPLIST(ElfApiInterfaceList, M_POD_OPLIST) + +struct CompositeApiResolver { + ElfApiInterface api_interface; + ElfApiInterfaceList_t interfaces; +}; + +static bool composite_api_resolver_callback( + const ElfApiInterface* interface, + const char* name, + Elf32_Addr* address) { + CompositeApiResolver* resolver = (CompositeApiResolver*)interface; + for + M_EACH(interface, resolver->interfaces, ElfApiInterfaceList_t) { + if((*interface)->resolver_callback(*interface, name, address)) { + return true; + } + } + return false; +} + +CompositeApiResolver* composite_api_resolver_alloc() { + CompositeApiResolver* resolver = malloc(sizeof(CompositeApiResolver)); + resolver->api_interface.api_version_major = 0; + resolver->api_interface.api_version_minor = 0; + resolver->api_interface.resolver_callback = &composite_api_resolver_callback; + ElfApiInterfaceList_init(resolver->interfaces); + return resolver; +} + +void composite_api_resolver_free(CompositeApiResolver* resolver) { + ElfApiInterfaceList_clear(resolver->interfaces); + free(resolver); +} + +void composite_api_resolver_add(CompositeApiResolver* resolver, const ElfApiInterface* interface) { + if(ElfApiInterfaceList_empty_p(resolver->interfaces)) { + resolver->api_interface.api_version_major = interface->api_version_major; + resolver->api_interface.api_version_minor = interface->api_version_minor; + } + ElfApiInterfaceList_push_back(resolver->interfaces, interface); +} + +const ElfApiInterface* composite_api_resolver_get(CompositeApiResolver* resolver) { + return &resolver->api_interface; +} diff --git a/lib/flipper_application/plugins/composite_resolver.h b/lib/flipper_application/plugins/composite_resolver.h new file mode 100644 index 00000000..a2d4bab2 --- /dev/null +++ b/lib/flipper_application/plugins/composite_resolver.h @@ -0,0 +1,46 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Composite API resolver + * Resolves API interface by calling all resolvers in order + * Uses API version from first resolver + * Note: when using hashtable resolvers, collisions between tables are not detected + * Can be cast to ElfApiInterface* + */ +typedef struct CompositeApiResolver CompositeApiResolver; + +/** + * @brief Allocate composite API resolver + * @return CompositeApiResolver* instance + */ +CompositeApiResolver* composite_api_resolver_alloc(); + +/** + * @brief Free composite API resolver + * @param resolver Instance + */ +void composite_api_resolver_free(CompositeApiResolver* resolver); + +/** + * @brief Add API resolver to composite resolver + * @param resolver Instance + * @param interface API resolver + */ +void composite_api_resolver_add(CompositeApiResolver* resolver, const ElfApiInterface* interface); + +/** + * @brief Get API interface from composite resolver + * @param resolver Instance + * @return API interface + */ +const ElfApiInterface* composite_api_resolver_get(CompositeApiResolver* resolver); + +#ifdef __cplusplus +} +#endif diff --git a/lib/flipper_application/plugins/plugin_manager.c b/lib/flipper_application/plugins/plugin_manager.c new file mode 100644 index 00000000..101471dc --- /dev/null +++ b/lib/flipper_application/plugins/plugin_manager.c @@ -0,0 +1,153 @@ +#include "plugin_manager.h" + +#include +#include +#include + +#include +#include + +#include + +#define TAG "libmgr" + +ARRAY_DEF(FlipperApplicationList, FlipperApplication*, M_PTR_OPLIST) +#define M_OPL_FlipperApplicationList_t() ARRAY_OPLIST(FlipperApplicationList, M_PTR_OPLIST) + +struct PluginManager { + const char* application_id; + uint32_t api_version; + Storage* storage; + FlipperApplicationList_t libs; + const ElfApiInterface* api_interface; +}; + +PluginManager* plugin_manager_alloc( + const char* application_id, + uint32_t api_version, + const ElfApiInterface* api_interface) { + PluginManager* manager = malloc(sizeof(PluginManager)); + manager->application_id = application_id; + manager->api_version = api_version; + manager->api_interface = api_interface ? api_interface : firmware_api_interface; + manager->storage = furi_record_open(RECORD_STORAGE); + FlipperApplicationList_init(manager->libs); + return manager; +} + +void plugin_manager_free(PluginManager* manager) { + for + M_EACH(loaded_lib, manager->libs, FlipperApplicationList_t) { + flipper_application_free(*loaded_lib); + } + FlipperApplicationList_clear(manager->libs); + furi_record_close(RECORD_STORAGE); + free(manager); +} + +PluginManagerError plugin_manager_load_single(PluginManager* manager, const char* path) { + FlipperApplication* lib = flipper_application_alloc(manager->storage, manager->api_interface); + + PluginManagerError error = PluginManagerErrorNone; + do { + FlipperApplicationPreloadStatus preload_res = flipper_application_preload(lib, path); + + if(preload_res != FlipperApplicationPreloadStatusSuccess) { + FURI_LOG_E(TAG, "Failed to preload %s", path); + error = PluginManagerErrorLoaderError; + break; + } + + if(!flipper_application_is_plugin(lib)) { + FURI_LOG_E(TAG, "Not a plugin %s", path); + error = PluginManagerErrorLoaderError; + break; + } + + FlipperApplicationLoadStatus load_status = flipper_application_map_to_memory(lib); + if(load_status != FlipperApplicationLoadStatusSuccess) { + FURI_LOG_E(TAG, "Failed to load module_demo_plugin1.fal"); + break; + } + + const FlipperAppPluginDescriptor* app_descriptor = + flipper_application_plugin_get_descriptor(lib); + + if(!app_descriptor) { + FURI_LOG_E(TAG, "Failed to get descriptor %s", path); + error = PluginManagerErrorLoaderError; + break; + } + + if(strcmp(app_descriptor->appid, manager->application_id) != 0) { + FURI_LOG_E(TAG, "Application id mismatch %s", path); + error = PluginManagerErrorApplicationIdMismatch; + break; + } + + if(app_descriptor->ep_api_version != manager->api_version) { + FURI_LOG_E(TAG, "API version mismatch %s", path); + error = PluginManagerErrorAPIVersionMismatch; + break; + } + + FlipperApplicationList_push_back(manager->libs, lib); + } while(false); + + if(error != PluginManagerErrorNone) { + flipper_application_free(lib); + } + + return error; +} + +PluginManagerError plugin_manager_load_all(PluginManager* manager, const char* path) { + File* directory = storage_file_alloc(manager->storage); + char file_name_buffer[256]; + FuriString* file_name = furi_string_alloc(); + do { + if(!storage_dir_open(directory, path)) { + FURI_LOG_E(TAG, "Failed to open directory %s", path); + break; + } + while(true) { + if(!storage_dir_read(directory, NULL, file_name_buffer, sizeof(file_name_buffer))) { + break; + } + + furi_string_set(file_name, file_name_buffer); + if(!furi_string_end_with_str(file_name, ".fal")) { + continue; + } + + path_concat(path, file_name_buffer, file_name); + FURI_LOG_D(TAG, "Loading %s", furi_string_get_cstr(file_name)); + PluginManagerError error = + plugin_manager_load_single(manager, furi_string_get_cstr(file_name)); + + if(error != PluginManagerErrorNone) { + FURI_LOG_E(TAG, "Failed to load %s", furi_string_get_cstr(file_name)); + break; + } + } + } while(false); + storage_dir_close(directory); + storage_file_free(directory); + furi_string_free(file_name); + return PluginManagerErrorNone; +} + +uint32_t plugin_manager_get_count(PluginManager* manager) { + return FlipperApplicationList_size(manager->libs); +} + +const FlipperAppPluginDescriptor* plugin_manager_get(PluginManager* manager, uint32_t index) { + FlipperApplication* app = *FlipperApplicationList_get(manager->libs, index); + return flipper_application_plugin_get_descriptor(app); +} + +const void* plugin_manager_get_ep(PluginManager* manager, uint32_t index) { + const FlipperAppPluginDescriptor* lib_descr = plugin_manager_get(manager, index); + furi_check(lib_descr); + return lib_descr->entry_point; +} diff --git a/lib/flipper_application/plugins/plugin_manager.h b/lib/flipper_application/plugins/plugin_manager.h new file mode 100644 index 00000000..d94c25db --- /dev/null +++ b/lib/flipper_application/plugins/plugin_manager.h @@ -0,0 +1,82 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Object that manages plugins for an application + * Implements mass loading of plugins and provides access to their descriptors + */ +typedef struct PluginManager PluginManager; + +typedef enum { + PluginManagerErrorNone = 0, + PluginManagerErrorLoaderError, + PluginManagerErrorApplicationIdMismatch, + PluginManagerErrorAPIVersionMismatch, +} PluginManagerError; + +/** + * @brief Allocates new PluginManager + * @param application_id Application ID filter - only plugins with matching ID will be loaded + * @param api_version Application API version filter - only plugins with matching API version + * @param api_interface Application API interface - used to resolve plugins' API imports + * If plugin uses private application's API, use CompoundApiInterface + * @return new PluginManager instance + */ +PluginManager* plugin_manager_alloc( + const char* application_id, + uint32_t api_version, + const ElfApiInterface* api_interface); + +/** + * @brief Frees PluginManager + * @param manager PluginManager instance + */ +void plugin_manager_free(PluginManager* manager); + +/** + * @brief Loads single plugin by full path + * @param manager PluginManager instance + * @param path Path to plugin + * @return Error code + */ +PluginManagerError plugin_manager_load_single(PluginManager* manager, const char* path); + +/** + * @brief Loads all plugins from specified directory + * @param manager PluginManager instance + * @param path Path to directory + * @return Error code + */ +PluginManagerError plugin_manager_load_all(PluginManager* manager, const char* path); + +/** + * @brief Returns number of loaded plugins + * @param manager PluginManager instance + * @return Number of loaded plugins + */ +uint32_t plugin_manager_get_count(PluginManager* manager); + +/** + * @brief Returns plugin descriptor by index + * @param manager PluginManager instance + * @param index Plugin index + * @return Plugin descriptor + */ +const FlipperAppPluginDescriptor* plugin_manager_get(PluginManager* manager, uint32_t index); + +/** + * @brief Returns plugin entry point by index + * @param manager PluginManager instance + * @param index Plugin index + * @return Plugin entry point + */ +const void* plugin_manager_get_ep(PluginManager* manager, uint32_t index); + +#ifdef __cplusplus +} +#endif diff --git a/scripts/distfap.py b/scripts/distfap.py new file mode 100644 index 00000000..060fe26f --- /dev/null +++ b/scripts/distfap.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 + +from flipper.app import App +from flipper.storage import FlipperStorage, FlipperStorageOperations +from flipper.utils.cdc import resolve_port + +import os +import posixpath + + +class Main(App): + def init(self): + self.parser.add_argument("-p", "--port", help="CDC Port", default="auto") + self.parser.add_argument( + "-n", + "--no-launch", + dest="launch_app", + action="store_false", + help="Don't launch app", + ) + + self.parser.add_argument("fap_src_path", help="App file to upload") + self.parser.add_argument( + "--fap_dst_dir", help="Upload path", default="/ext/apps", required=False + ) + self.parser.set_defaults(func=self.install) + + def install(self): + if not (port := resolve_port(self.logger, self.args.port)): + return 1 + + try: + with FlipperStorage(port) as storage: + storage_ops = FlipperStorageOperations(storage) + fap_local_path = self.args.fap_src_path + self.args.fap_dst_dir = self.args.fap_dst_dir.rstrip("/\\") + + if not os.path.isfile(fap_local_path): + self.logger.error( + f"Error: source .fap ({fap_local_path}) not found" + ) + return 2 + + fap_dst_path = posixpath.join( + self.args.fap_dst_dir, os.path.basename(fap_local_path) + ) + + self.logger.info(f'Installing "{fap_local_path}" to {fap_dst_path}') + + storage_ops.recursive_send(fap_dst_path, fap_local_path, False) + + if not self.args.launch_app: + return 0 + + storage.send_and_wait_eol( + f'loader open "Applications" {fap_dst_path}\r' + ) + + if len(result := storage.read.until(storage.CLI_EOL)): + self.logger.error(f"Unexpected response: {result.decode('ascii')}") + return 3 + return 0 + + except Exception as e: + self.logger.error(f"Error: {e}") + # raise + return 4 + + +if __name__ == "__main__": + Main()() diff --git a/scripts/fbt/appmanifest.py b/scripts/fbt/appmanifest.py index eb1652b7..37ddc434 100644 --- a/scripts/fbt/appmanifest.py +++ b/scripts/fbt/appmanifest.py @@ -12,13 +12,13 @@ class FlipperAppType(Enum): SERVICE = "Service" SYSTEM = "System" APP = "App" - PLUGIN = "Plugin" DEBUG = "Debug" ARCHIVE = "Archive" SETTINGS = "Settings" STARTUP = "StartupHook" EXTERNAL = "External" METAPACKAGE = "Package" + PLUGIN = "Plugin" @dataclass @@ -69,12 +69,22 @@ class FlipperApplication: fap_private_libs: List[Library] = field(default_factory=list) fap_file_assets: Optional[str] = None # Internally used by fbt + _appmanager: Optional["AppManager"] = None _appdir: Optional[object] = None _apppath: Optional[str] = None + _plugins: List["FlipperApplication"] = field(default_factory=list) def supports_hardware_target(self, target: str): return target in self.targets or "all" in self.targets + @property + def is_default_deployable(self): + return self.apptype != FlipperAppType.DEBUG and self.fap_category != "Examples" + + def __post_init__(self): + if self.apptype == FlipperAppType.PLUGIN: + self.stack_size = 0 + class AppManager: def __init__(self): @@ -94,6 +104,23 @@ class AppManager: return app return None + def _validate_app_params(self, *args, **kw): + apptype = kw.get("apptype") + if apptype == FlipperAppType.PLUGIN: + if kw.get("stack_size"): + raise FlipperManifestException( + f"Plugin {kw.get('appid')} cannot have stack (did you mean FlipperAppType.EXTERNAL?)" + ) + if not kw.get("requires"): + raise FlipperManifestException( + f"Plugin {kw.get('appid')} must have 'requires' in manifest" + ) + # Harmless - cdefines for external apps are meaningless + # if apptype == FlipperAppType.EXTERNAL and kw.get("cdefines"): + # raise FlipperManifestException( + # f"External app {kw.get('appid')} must not have 'cdefines' in manifest" + # ) + def load_manifest(self, app_manifest_path: str, app_dir_node: object): if not os.path.exists(app_manifest_path): raise FlipperManifestException( @@ -105,12 +132,14 @@ class AppManager: def App(*args, **kw): nonlocal app_manifests + self._validate_app_params(*args, **kw) app_manifests.append( FlipperApplication( *args, **kw, _appdir=app_dir_node, _apppath=os.path.dirname(app_manifest_path), + _appmanager=self, ), ) @@ -155,7 +184,6 @@ class AppBuildset: FlipperAppType.SERVICE, FlipperAppType.SYSTEM, FlipperAppType.APP, - FlipperAppType.PLUGIN, FlipperAppType.DEBUG, FlipperAppType.ARCHIVE, FlipperAppType.SETTINGS, @@ -182,6 +210,7 @@ class AppBuildset: self._check_conflicts() self._check_unsatisfied() # unneeded? self._check_target_match() + self._group_plugins() self.apps = sorted( list(map(self.appmgr.get, self.appnames)), key=lambda app: app.appid, @@ -260,6 +289,18 @@ class AppBuildset: f"Apps incompatible with target {self.hw_target}: {', '.join(incompatible)}" ) + def _group_plugins(self): + known_extensions = self.get_apps_of_type(FlipperAppType.PLUGIN, all_known=True) + for extension_app in known_extensions: + for parent_app_id in extension_app.requires: + try: + parent_app = self.appmgr.get(parent_app_id) + parent_app._plugins.append(extension_app) + except FlipperManifestException as e: + self._writer( + f"Module {extension_app.appid} has unknown parent {parent_app_id}" + ) + def get_apps_cdefs(self): cdefs = set() for app in self.apps: @@ -301,7 +342,6 @@ class ApplicationsCGenerator: FlipperAppType.SERVICE: ("FlipperApplication", "FLIPPER_SERVICES"), FlipperAppType.SYSTEM: ("FlipperApplication", "FLIPPER_SYSTEM_APPS"), FlipperAppType.APP: ("FlipperApplication", "FLIPPER_APPS"), - FlipperAppType.PLUGIN: ("FlipperApplication", "FLIPPER_PLUGINS"), FlipperAppType.DEBUG: ("FlipperApplication", "FLIPPER_DEBUG_APPS"), FlipperAppType.SETTINGS: ("FlipperApplication", "FLIPPER_SETTINGS_APPS"), FlipperAppType.STARTUP: ("FlipperOnStartHook", "FLIPPER_ON_SYSTEM_START"), diff --git a/scripts/fbt/fapassets.py b/scripts/fbt/fapassets.py new file mode 100644 index 00000000..0649f03e --- /dev/null +++ b/scripts/fbt/fapassets.py @@ -0,0 +1,108 @@ +import os +import hashlib +import struct +from typing import TypedDict + + +class File(TypedDict): + path: str + size: int + content_path: str + + +class Dir(TypedDict): + path: str + + +class FileBundler: + """ + u32 magic + u32 version + u32 dirs_count + u32 files_count + u32 signature_size + u8[] signature + Dirs: + u32 dir_name length + u8[] dir_name + Files: + u32 file_name length + u8[] file_name + u32 file_content_size + u8[] file_content + """ + + def __init__(self, directory_path: str): + self.directory_path = directory_path + self.file_list: list[File] = [] + self.directory_list: list[Dir] = [] + self._gather() + + def _gather(self): + for root, dirs, files in os.walk(self.directory_path): + for file_info in files: + file_path = os.path.join(root, file_info) + file_size = os.path.getsize(file_path) + self.file_list.append( + { + "path": os.path.relpath(file_path, self.directory_path), + "size": file_size, + "content_path": file_path, + } + ) + + for dir_info in dirs: + dir_path = os.path.join(root, dir_info) + # dir_size = sum( + # os.path.getsize(os.path.join(dir_path, f)) for f in os.listdir(dir_path) + # ) + self.directory_list.append( + { + "path": os.path.relpath(dir_path, self.directory_path), + } + ) + + self.file_list.sort(key=lambda f: f["path"]) + self.directory_list.sort(key=lambda d: d["path"]) + + def export(self, target_path: str): + self._md5_hash = hashlib.md5() + with open(target_path, "wb") as f: + # Write header magic and version + f.write(struct.pack(" FlipperExternalAppInfo + EXT_LIBS={}, + _APP_ICONS=[], ) env.AddMethod(BuildAppElf) - env.AddMethod(GetExtAppFromPath) + env.AddMethod(GetExtAppByIdOrPath) env.Append( BUILDERS={ "FapDist": Builder( @@ -466,7 +427,7 @@ def generate(env, **kw): generator=generate_embed_app_metadata_actions, suffix=".fap", src_suffix=".elf", - # emitter=generate_embed_app_metadata_emitter, + emitter=embed_app_metadata_emitter, ), "ValidateAppImports": Builder( action=[ diff --git a/scripts/fbt_tools/fbt_sdk.py b/scripts/fbt_tools/fbt_sdk.py index 3a37eacc..32481981 100644 --- a/scripts/fbt_tools/fbt_sdk.py +++ b/scripts/fbt_tools/fbt_sdk.py @@ -220,7 +220,7 @@ def gen_sdk_data(sdk_cache: SdkCache): def _check_sdk_is_up2date(sdk_cache: SdkCache): if not sdk_cache.is_buildable(): raise UserError( - "SDK version is not finalized, please review changes and re-run operation" + "SDK version is not finalized, please review changes and re-run operation. See AppsOnSDCard.md for more details" ) diff --git a/scripts/flipper/storage.py b/scripts/flipper/storage.py index 9c9f5295..47e11236 100644 --- a/scripts/flipper/storage.py +++ b/scripts/flipper/storage.py @@ -4,6 +4,9 @@ import serial import time import hashlib import math +import logging +import posixpath +import enum def timing(func): @@ -25,12 +28,47 @@ def timing(func): return wrapper +class StorageErrorCode(enum.Enum): + OK = "OK" + NOT_READY = "filesystem not ready" + EXIST = "file/dir already exist" + NOT_EXIST = "file/dir not exist" + INVALID_PARAMETER = "invalid parameter" + DENIED = "access denied" + INVALID_NAME = "invalid name/path" + INTERNAL = "internal error" + NOT_IMPLEMENTED = "function not implemented" + ALREADY_OPEN = "file is already open" + UNKNOWN = "unknown error" + + @property + def is_error(self): + return self != self.OK + + @classmethod + def from_value(cls, s: str | bytes): + if isinstance(s, bytes): + s = s.decode("ascii") + for code in cls: + if code.value == s: + return code + return cls.UNKNOWN + + +class FlipperStorageException(Exception): + def __init__(self, message): + super().__init__(f"Storage error: {message}") + + def __init__(self, path: str, error_code: StorageErrorCode): + super().__init__(f"Storage error: path '{path}': {error_code.value}") + + class BufferedRead: def __init__(self, stream): self.buffer = bytearray() self.stream = stream - def until(self, eol="\n", cut_eol=True): + def until(self, eol: str = "\n", cut_eol: bool = True): eol = eol.encode("ascii") while True: # search in buffer @@ -59,9 +97,15 @@ class FlipperStorage: self.port.timeout = 2 self.port.baudrate = 115200 # Doesn't matter for VCP self.read = BufferedRead(self.port) - self.last_error = "" self.chunk_size = chunk_size + def __enter__(self): + self.start() + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.stop() + def start(self): self.port.open() self.port.reset_input_buffer() @@ -71,37 +115,34 @@ class FlipperStorage: # And read buffer until we get prompt self.read.until(self.CLI_PROMPT) - def stop(self): + def stop(self) -> None: self.port.close() - def send(self, line): + def send(self, line: str) -> None: self.port.write(line.encode("ascii")) - def send_and_wait_eol(self, line): + def send_and_wait_eol(self, line: str): self.send(line) return self.read.until(self.CLI_EOL) - def send_and_wait_prompt(self, line): + def send_and_wait_prompt(self, line: str): self.send(line) return self.read.until(self.CLI_PROMPT) - def has_error(self, data): - """Is data has error""" - if data.find(b"Storage error") != -1: - return True - else: - return False + def has_error(self, data: bytes | str) -> bool: + """Is data an error message""" + return data.find(b"Storage error:") != -1 - def get_error(self, data): + def get_error(self, data: bytes) -> StorageErrorCode: """Extract error text from data and print it""" - error, error_text = data.decode("ascii").split(": ") - return error_text.strip() + _, error_text = data.decode("ascii").split(": ") + return StorageErrorCode.from_value(error_text.strip()) - def list_tree(self, path="/", level=0): + def list_tree(self, path: str = "/", level: int = 0): """List files and dirs on Flipper""" path = path.replace("//", "/") - self.send_and_wait_eol('storage list "' + path + '"\r') + self.send_and_wait_eol(f'storage list "{path}"\r') data = self.read.until(self.CLI_PROMPT) lines = data.split(b"\r\n") @@ -139,7 +180,7 @@ class FlipperStorage: # Something wrong, pass pass - def walk(self, path="/"): + def walk(self, path: str = "/"): dirs = [] nondirs = [] walk_dirs = [] @@ -181,14 +222,15 @@ class FlipperStorage: # Something wrong, pass pass - # topdown walk, yield before recursy + # topdown walk, yield before recursing yield path, dirs, nondirs for new_path in walk_dirs: yield from self.walk(new_path) - def send_file(self, filename_from, filename_to): + def send_file(self, filename_from: str, filename_to: str): """Send file from local device to Flipper""" - self.remove(filename_to) + if self.exist_file(filename_to): + self.remove(filename_to) with open(filename_from, "rb") as file: filesize = os.fstat(file.fileno()).st_size @@ -203,9 +245,9 @@ class FlipperStorage: self.send_and_wait_eol(f'storage write_chunk "{filename_to}" {size}\r') answer = self.read.until(self.CLI_EOL) if self.has_error(answer): - self.last_error = self.get_error(answer) + last_error = self.get_error(answer) self.read.until(self.CLI_PROMPT) - return False + raise FlipperStorageException(filename_to, last_error) self.port.write(filedata) self.read.until(self.CLI_PROMPT) @@ -218,9 +260,8 @@ class FlipperStorage: ) sys.stdout.flush() print() - return True - def read_file(self, filename): + def read_file(self, filename: str): """Receive file from Flipper, and get filedata (bytes)""" buffer_size = self.chunk_size self.send_and_wait_eol( @@ -229,9 +270,10 @@ class FlipperStorage: answer = self.read.until(self.CLI_EOL) filedata = bytearray() if self.has_error(answer): - self.last_error = self.get_error(answer) + last_error = self.get_error(answer) self.read.until(self.CLI_PROMPT) - return filedata + raise FlipperStorageException(filename, last_error) + # return filedata size = int(answer.split(b": ")[1]) read_size = 0 @@ -251,121 +293,89 @@ class FlipperStorage: self.read.until(self.CLI_PROMPT) return filedata - def receive_file(self, filename_from, filename_to): + def receive_file(self, filename_from: str, filename_to: str): """Receive file from Flipper to local storage""" with open(filename_to, "wb") as file: data = self.read_file(filename_from) - if not data: - return False - else: - file.write(data) - return True + file.write(data) - def exist(self, path): - """Is file or dir exist on Flipper""" - self.send_and_wait_eol('storage stat "' + path + '"\r') - answer = self.read.until(self.CLI_EOL) + def exist(self, path: str): + """Does file or dir exist on Flipper""" + self.send_and_wait_eol(f'storage stat "{path}"\r') + response = self.read.until(self.CLI_EOL) self.read.until(self.CLI_PROMPT) - if self.has_error(answer): - self.last_error = self.get_error(answer) - return False - else: - return True + return not self.has_error(response) - def exist_dir(self, path): - """Is dir exist on Flipper""" - self.send_and_wait_eol('storage stat "' + path + '"\r') - answer = self.read.until(self.CLI_EOL) + def exist_dir(self, path: str): + """Does dir exist on Flipper""" + self.send_and_wait_eol(f'storage stat "{path}"\r') + response = self.read.until(self.CLI_EOL) + self.read.until(self.CLI_PROMPT) + if self.has_error(response): + error_code = self.get_error(response) + if error_code in ( + StorageErrorCode.NOT_EXIST, + StorageErrorCode.INVALID_NAME, + ): + return False + raise FlipperStorageException(path, error_code) + + return True + + def exist_file(self, path: str): + """Does file exist on Flipper""" + self.send_and_wait_eol(f'storage stat "{path}"\r') + response = self.read.until(self.CLI_EOL) self.read.until(self.CLI_PROMPT) - if self.has_error(answer): - self.last_error = self.get_error(answer) - return False - else: - if answer.find(b"Directory") != -1: - return True - elif answer.find(b"Storage") != -1: - return True - else: - return False + return response.find(b"File, size:") != -1 - def exist_file(self, path): - """Is file exist on Flipper""" - self.send_and_wait_eol('storage stat "' + path + '"\r') - answer = self.read.until(self.CLI_EOL) - self.read.until(self.CLI_PROMPT) + def _check_no_error(self, response, path=None): + if self.has_error(response): + raise FlipperStorageException(self.get_error(response)) - if self.has_error(answer): - self.last_error = self.get_error(answer) - return False - else: - if answer.find(b"File, size:") != -1: - return True - else: - return False - - def size(self, path): + def size(self, path: str): """file size on Flipper""" - self.send_and_wait_eol('storage stat "' + path + '"\r') - answer = self.read.until(self.CLI_EOL) + self.send_and_wait_eol(f'storage stat "{path}"\r') + response = self.read.until(self.CLI_EOL) self.read.until(self.CLI_PROMPT) - if self.has_error(answer): - self.last_error = self.get_error(answer) - return False - else: - if answer.find(b"File, size:") != -1: - size = int( - "".join( - ch - for ch in answer.split(b": ")[1].decode("ascii") - if ch.isdigit() - ) + self._check_no_error(response, path) + if response.find(b"File, size:") != -1: + size = int( + "".join( + ch + for ch in response.split(b": ")[1].decode("ascii") + if ch.isdigit() ) - return size - else: - self.last_error = "access denied" - return -1 + ) + return size + raise FlipperStorageException("Not a file") - def mkdir(self, path): + def mkdir(self, path: str): """Create a directory on Flipper""" - self.send_and_wait_eol('storage mkdir "' + path + '"\r') - answer = self.read.until(self.CLI_EOL) + self.send_and_wait_eol(f'storage mkdir "{path}"\r') + response = self.read.until(self.CLI_EOL) self.read.until(self.CLI_PROMPT) - - if self.has_error(answer): - self.last_error = self.get_error(answer) - return False - else: - return True + self._check_no_error(response, path) def format_ext(self): - """Create a directory on Flipper""" + """Format external storage on Flipper""" self.send_and_wait_eol("storage format /ext\r") self.send_and_wait_eol("y\r") - answer = self.read.until(self.CLI_EOL) + response = self.read.until(self.CLI_EOL) self.read.until(self.CLI_PROMPT) + self._check_no_error(response, "/ext") - if self.has_error(answer): - self.last_error = self.get_error(answer) - return False - else: - return True - - def remove(self, path): + def remove(self, path: str): """Remove file or directory on Flipper""" - self.send_and_wait_eol('storage remove "' + path + '"\r') - answer = self.read.until(self.CLI_EOL) + self.send_and_wait_eol(f'storage remove "{path}"\r') + response = self.read.until(self.CLI_EOL) self.read.until(self.CLI_PROMPT) + self._check_no_error(response, path) - if self.has_error(answer): - self.last_error = self.get_error(answer) - return False - else: - return True - - def hash_local(self, filename): + def hash_local(self, filename: str): """Hash of local file""" hash_md5 = hashlib.md5() with open(filename, "rb") as f: @@ -373,14 +383,112 @@ class FlipperStorage: hash_md5.update(chunk) return hash_md5.hexdigest() - def hash_flipper(self, filename): + def hash_flipper(self, filename: str): """Get hash of file on Flipper""" self.send_and_wait_eol('storage md5 "' + filename + '"\r') hash = self.read.until(self.CLI_EOL) self.read.until(self.CLI_PROMPT) + self._check_no_error(hash, filename) + return hash.decode("ascii") - if self.has_error(hash): - self.last_error = self.get_error(hash) - return "" + +class FlipperStorageOperations: + def __init__(self, storage): + self.storage: FlipperStorage = storage + self.logger = logging.getLogger("FStorageOps") + + def send_file_to_storage( + self, flipper_file_path: str, local_file_path: str, force: bool = False + ): + self.logger.debug( + f"* send_file_to_storage: {local_file_path}->{flipper_file_path}, {force=}" + ) + exists = self.storage.exist_file(flipper_file_path) + do_upload = not exists + if exists: + hash_local = self.storage.hash_local(local_file_path) + hash_flipper = self.storage.hash_flipper(flipper_file_path) + self.logger.debug(f"hash check: local {hash_local}, flipper {hash_flipper}") + do_upload = force or (hash_local != hash_flipper) + + if do_upload: + self.logger.info(f'Sending "{local_file_path}" to "{flipper_file_path}"') + self.storage.send_file(local_file_path, flipper_file_path) + + # make directory with exist check + def mkpath(self, flipper_dir_path: str): + path_components, dirs_to_create = flipper_dir_path.split("/"), [] + while not self.storage.exist_dir(dir_path := "/".join(path_components)): + self.logger.debug(f'"{dir_path}" does not exist, will create') + dirs_to_create.append(path_components.pop()) + for dir_to_create in reversed(dirs_to_create): + path_components.append(dir_to_create) + self.storage.mkdir("/".join(path_components)) + + # send file or folder recursively + def recursive_send(self, flipper_path: str, local_path: str, force: bool = False): + if not os.path.exists(local_path): + raise FlipperStorageException(f'"{local_path}" does not exist') + + if os.path.isdir(local_path): + # create parent dir + self.mkpath(flipper_path) + + for dirpath, dirnames, filenames in os.walk(local_path): + self.logger.debug(f'Processing directory "{os.path.normpath(dirpath)}"') + dirnames.sort() + filenames.sort() + rel_path = os.path.relpath(dirpath, local_path) + + # create subdirs + for dirname in dirnames: + flipper_dir_path = os.path.join(flipper_path, rel_path, dirname) + flipper_dir_path = os.path.normpath(flipper_dir_path).replace( + os.sep, "/" + ) + self.mkpath(flipper_dir_path) + + # send files + for filename in filenames: + flipper_file_path = os.path.join(flipper_path, rel_path, filename) + flipper_file_path = os.path.normpath(flipper_file_path).replace( + os.sep, "/" + ) + local_file_path = os.path.normpath(os.path.join(dirpath, filename)) + self.send_file_to_storage(flipper_file_path, local_file_path, force) else: - return hash.decode("ascii") + self.mkpath(posixpath.dirname(flipper_path)) + self.send_file_to_storage(flipper_path, local_path, force) + + def recursive_receive(self, flipper_path: str, local_path: str): + if self.storage.exist_dir(flipper_path): + for dirpath, dirnames, filenames in self.storage.walk(flipper_path): + self.logger.debug( + f'Processing directory "{os.path.normpath(dirpath)}"'.replace( + os.sep, "/" + ) + ) + dirnames.sort() + filenames.sort() + + rel_path = os.path.relpath(dirpath, flipper_path) + + for dirname in dirnames: + local_dir_path = os.path.join(local_path, rel_path, dirname) + local_dir_path = os.path.normpath(local_dir_path) + os.makedirs(local_dir_path, exist_ok=True) + + for filename in filenames: + local_file_path = os.path.join(local_path, rel_path, filename) + local_file_path = os.path.normpath(local_file_path) + flipper_file_path = os.path.normpath( + os.path.join(dirpath, filename) + ).replace(os.sep, "/") + self.logger.info( + f'Receiving "{flipper_file_path}" to "{local_file_path}"' + ) + self.storage.receive_file(flipper_file_path, local_file_path) + + else: + self.logger.info(f'Receiving "{flipper_path}" to "{local_path}"') + self.storage.receive_file(flipper_path, local_path) diff --git a/scripts/requirements.txt b/scripts/requirements.txt deleted file mode 100644 index 5b6fac5f..00000000 --- a/scripts/requirements.txt +++ /dev/null @@ -1,9 +0,0 @@ -ansi==0.3.6 -black==22.6.0 -colorlog==6.7.0 -heatshrink2==0.11.0 -Pillow==9.1.1 -protobuf==3.20.1 -pyserial==3.5 -python3-protobuf==2.5.0 -SCons==4.4.0 diff --git a/scripts/runfap.py b/scripts/runfap.py index 410b3e7d..f8ff607c 100644 --- a/scripts/runfap.py +++ b/scripts/runfap.py @@ -1,108 +1,86 @@ #!/usr/bin/env python3 -import posixpath -from typing import final from flipper.app import App -from flipper.storage import FlipperStorage +from flipper.storage import FlipperStorage, FlipperStorageOperations from flipper.utils.cdc import resolve_port -import logging import os -import pathlib -import serial.tools.list_ports as list_ports +import posixpath +from functools import reduce +import operator class Main(App): def init(self): self.parser.add_argument("-p", "--port", help="CDC Port", default="auto") self.parser.add_argument( - "-n", - "--no-launch", - dest="launch_app", - action="store_false", - help="Don't launch app", + "--sources", + "-s", + nargs="+", + action="append", + default=[], + help="Files to send", + ) + self.parser.add_argument( + "--targets", + "-t", + nargs="+", + action="append", + default=[], + help="File destinations (must be same length as -s)", + ) + self.parser.add_argument( + "--host-app", + "-a", + help="Host app to launch", ) - self.parser.add_argument("fap_src_path", help="App file to upload") - self.parser.add_argument( - "--fap_dst_dir", help="Upload path", default="/ext/apps", required=False - ) self.parser.set_defaults(func=self.install) - # logging - self.logger = logging.getLogger() - - # make directory with exist check - def mkdir_on_storage(self, storage, flipper_dir_path): - if not storage.exist_dir(flipper_dir_path): - self.logger.debug(f'"{flipper_dir_path}" does not exist, creating') - if not storage.mkdir(flipper_dir_path): - self.logger.error(f"Error: {storage.last_error}") - return False - else: - self.logger.debug(f'"{flipper_dir_path}" already exists') - return True - - # send file with exist check and hash check - def send_file_to_storage(self, storage, flipper_file_path, local_file_path, force): - exists = storage.exist_file(flipper_file_path) - do_upload = not exists - if exists: - hash_local = storage.hash_local(local_file_path) - hash_flipper = storage.hash_flipper(flipper_file_path) - self.logger.debug(f"hash check: local {hash_local}, flipper {hash_flipper}") - do_upload = force or (hash_local != hash_flipper) - - if do_upload: - self.logger.info(f'Sending "{local_file_path}" to "{flipper_file_path}"') - if not storage.send_file(local_file_path, flipper_file_path): - self.logger.error(f"Error: {storage.last_error}") - return False - return True + @staticmethod + def flatten(l): + return reduce(operator.concat, l, []) def install(self): - if not (port := resolve_port(self.logger, self.args.port)): + self.args.sources = self.flatten(self.args.sources) + self.args.targets = self.flatten(self.args.targets) + + if len(self.args.sources) != len(self.args.targets): + self.logger.error( + f"Error: sources ({self.args.sources}) and targets ({self.args.targets}) must be same length" + ) return 1 - storage = FlipperStorage(port) - storage.start() + if not (port := resolve_port(self.logger, self.args.port)): + return 2 try: - fap_local_path = self.args.fap_src_path - self.args.fap_dst_dir = self.args.fap_dst_dir.rstrip("/\\") + with FlipperStorage(port) as storage: + storage_ops = FlipperStorageOperations(storage) + for fap_local_path, fap_dst_path in zip( + self.args.sources, self.args.targets + ): + self.logger.info(f'Installing "{fap_local_path}" to {fap_dst_path}') - if not os.path.isfile(fap_local_path): - self.logger.error(f"Error: source .fap ({fap_local_path}) not found") - return -1 + storage_ops.recursive_send(fap_dst_path, fap_local_path, False) - fap_dst_path = posixpath.join( - self.args.fap_dst_dir, os.path.basename(fap_local_path) - ) + fap_host_app = self.args.targets[0] + startup_command = f'"Applications" {fap_host_app}' + if self.args.host_app: + startup_command = self.args.host_app - self.logger.info(f'Installing "{fap_local_path}" to {fap_dst_path}') + self.logger.info(f"Launching app: {startup_command}") + storage.send_and_wait_eol(f"loader open {startup_command}\r") - if not self.mkdir_on_storage(storage, self.args.fap_dst_dir): - self.logger.error(f"Error: cannot create dir: {storage.last_error}") - return -2 - - if not self.send_file_to_storage( - storage, fap_dst_path, fap_local_path, False - ): - self.logger.error(f"Error: upload failed: {storage.last_error}") - return -3 - - if self.args.launch_app: - storage.send_and_wait_eol( - f'loader open "Applications" {fap_dst_path}\r' - ) - result = storage.read.until(storage.CLI_EOL) - if len(result): + if len(result := storage.read.until(storage.CLI_EOL)): self.logger.error(f"Unexpected response: {result.decode('ascii')}") - return -4 + return 3 + return 0 - return 0 - finally: - storage.stop() + except Exception as e: + self.logger.error(f"Error: {e}") + # raise + return 4 if __name__ == "__main__": diff --git a/scripts/selfupdate.py b/scripts/selfupdate.py index 1c16c5ca..9bfbfefa 100644 --- a/scripts/selfupdate.py +++ b/scripts/selfupdate.py @@ -2,7 +2,7 @@ from typing import final from flipper.app import App -from flipper.storage import FlipperStorage +from flipper.storage import FlipperStorage, FlipperStorageOperations from flipper.utils.cdc import resolve_port import logging @@ -24,89 +24,47 @@ class Main(App): # logging self.logger = logging.getLogger() - # make directory with exist check - def mkdir_on_storage(self, storage, flipper_dir_path): - if not storage.exist_dir(flipper_dir_path): - self.logger.debug(f'"{flipper_dir_path}" does not exist, creating') - if not storage.mkdir(flipper_dir_path): - self.logger.error(f"Error: {storage.last_error}") - return False - else: - self.logger.debug(f'"{flipper_dir_path}" already exists') - return True - - # send file with exist check and hash check - def send_file_to_storage(self, storage, flipper_file_path, local_file_path, force): - exists = storage.exist_file(flipper_file_path) - do_upload = not exists - if exists: - hash_local = storage.hash_local(local_file_path) - hash_flipper = storage.hash_flipper(flipper_file_path) - self.logger.debug(f"hash check: local {hash_local}, flipper {hash_flipper}") - do_upload = force or (hash_local != hash_flipper) - - if do_upload: - self.logger.info(f'Sending "{local_file_path}" to "{flipper_file_path}"') - if not storage.send_file(local_file_path, flipper_file_path): - self.logger.error(f"Error: {storage.last_error}") - return False - return True - def install(self): if not (port := resolve_port(self.logger, self.args.port)): return 1 - storage = FlipperStorage(port) - storage.start() + if not os.path.isfile(self.args.manifest_path): + self.logger.error("Error: manifest not found") + return 2 + + manifest_path = pathlib.Path(os.path.abspath(self.args.manifest_path)) + manifest_name, pkg_name = manifest_path.parts[-1], manifest_path.parts[-2] + + pkg_dir_name = self.args.pkg_dir_name or pkg_name + update_root = "/ext/update" + flipper_update_path = f"{update_root}/{pkg_dir_name}" + + self.logger.info(f'Installing "{pkg_name}" from {flipper_update_path}') try: - if not os.path.isfile(self.args.manifest_path): - self.logger.error("Error: manifest not found") - return 2 + with FlipperStorage(port) as storage: + storage_ops = FlipperStorageOperations(storage) + storage_ops.mkpath(update_root) + storage_ops.mkpath(flipper_update_path) + storage_ops.recursive_send( + flipper_update_path, manifest_path.parents[0] + ) - manifest_path = pathlib.Path(os.path.abspath(self.args.manifest_path)) - manifest_name, pkg_name = manifest_path.parts[-1], manifest_path.parts[-2] - - pkg_dir_name = self.args.pkg_dir_name or pkg_name - update_root = "/ext/update" - flipper_update_path = f"{update_root}/{pkg_dir_name}" - - self.logger.info(f'Installing "{pkg_name}" from {flipper_update_path}') - # if not os.path.exists(self.args.manifest_path): - # self.logger.error("Error: package not found") - if not self.mkdir_on_storage( - storage, update_root - ) or not self.mkdir_on_storage(storage, flipper_update_path): - self.logger.error(f"Error: cannot create {storage.last_error}") - return -2 - - for dirpath, dirnames, filenames in os.walk(manifest_path.parents[0]): - for fname in filenames: - self.logger.debug(f"Uploading {fname}") - local_file_path = os.path.join(dirpath, fname) - flipper_file_path = f"{flipper_update_path}/{fname}" - if not self.send_file_to_storage( - storage, flipper_file_path, local_file_path, False - ): - self.logger.error(f"Error: {storage.last_error}") - return -3 - - # return -11 storage.send_and_wait_eol( f"update install {flipper_update_path}/{manifest_name}\r" ) result = storage.read.until(storage.CLI_EOL) if not b"Verifying" in result: self.logger.error(f"Unexpected response: {result.decode('ascii')}") - return -4 + return 3 result = storage.read.until(storage.CLI_EOL) if not result.startswith(b"OK"): self.logger.error(result.decode("ascii")) - return -5 - break - return 0 - finally: - storage.stop() + return 4 + return 0 + except Exception as e: + self.logger.error(e) + return 5 if __name__ == "__main__": diff --git a/scripts/storage.py b/scripts/storage.py index ee5dabd4..84c01021 100755 --- a/scripts/storage.py +++ b/scripts/storage.py @@ -1,16 +1,28 @@ #!/usr/bin/env python3 from flipper.app import App -from flipper.storage import FlipperStorage +from flipper.storage import FlipperStorage, FlipperStorageOperations from flipper.utils.cdc import resolve_port -import logging import os import binascii import filecmp import tempfile +def WrapStorageOp(func): + def wrapper(*args, **kwargs): + try: + func(*args, **kwargs) + return 0 + except Exception as e: + print(f"Error: {e}") + # raise # uncomment to debug + return 1 + + return wrapper + + class Main(App): def init(self): self.parser.add_argument("-p", "--port", help="CDC Port", default="auto") @@ -71,229 +83,71 @@ class Main(App): ) self.parser_stress.set_defaults(func=self.stress) - def _get_storage(self): + def _get_port(self): if not (port := resolve_port(self.logger, self.args.port)): - return None - - storage = FlipperStorage(port) - storage.start() - return storage + raise Exception("Failed to resolve port") + return port + @WrapStorageOp def mkdir(self): - if not (storage := self._get_storage()): - return 1 - self.logger.debug(f'Creating "{self.args.flipper_path}"') - if not storage.mkdir(self.args.flipper_path): - self.logger.error(f"Error: {storage.last_error}") - storage.stop() - return 0 + with FlipperStorage(self._get_port()) as storage: + storage.mkdir(self.args.flipper_path) + @WrapStorageOp def remove(self): - if not (storage := self._get_storage()): - return 1 - self.logger.debug(f'Removing "{self.args.flipper_path}"') - if not storage.remove(self.args.flipper_path): - self.logger.error(f"Error: {storage.last_error}") - storage.stop() - return 0 + with FlipperStorage(self._get_port()) as storage: + storage.remove(self.args.flipper_path) + @WrapStorageOp def receive(self): - if not (storage := self._get_storage()): - return 1 - - if storage.exist_dir(self.args.flipper_path): - for dirpath, dirnames, filenames in storage.walk(self.args.flipper_path): - self.logger.debug( - f'Processing directory "{os.path.normpath(dirpath)}"'.replace( - os.sep, "/" - ) - ) - dirnames.sort() - filenames.sort() - - rel_path = os.path.relpath(dirpath, self.args.flipper_path) - - for dirname in dirnames: - local_dir_path = os.path.join( - self.args.local_path, rel_path, dirname - ) - local_dir_path = os.path.normpath(local_dir_path) - os.makedirs(local_dir_path, exist_ok=True) - - for filename in filenames: - local_file_path = os.path.join( - self.args.local_path, rel_path, filename - ) - local_file_path = os.path.normpath(local_file_path) - flipper_file_path = os.path.normpath( - os.path.join(dirpath, filename) - ).replace(os.sep, "/") - self.logger.info( - f'Receiving "{flipper_file_path}" to "{local_file_path}"' - ) - if not storage.receive_file(flipper_file_path, local_file_path): - self.logger.error(f"Error: {storage.last_error}") - - else: - self.logger.info( - f'Receiving "{self.args.flipper_path}" to "{self.args.local_path}"' + with FlipperStorage(self._get_port()) as storage: + FlipperStorageOperations(storage).recursive_receive( + self.args.flipper_path, self.args.local_path ) - if not storage.receive_file(self.args.flipper_path, self.args.local_path): - self.logger.error(f"Error: {storage.last_error}") - storage.stop() - return 0 + @WrapStorageOp def send(self): - if not (storage := self._get_storage()): - return 1 - - self.send_to_storage( - storage, self.args.flipper_path, self.args.local_path, self.args.force - ) - storage.stop() - return 0 - - # send file or folder recursively - def send_to_storage(self, storage, flipper_path, local_path, force): - if not os.path.exists(local_path): - self.logger.error(f'Error: "{local_path}" is not exist') - - if os.path.isdir(local_path): - # create parent dir - self.mkdir_on_storage(storage, flipper_path) - - for dirpath, dirnames, filenames in os.walk(local_path): - self.logger.debug(f'Processing directory "{os.path.normpath(dirpath)}"') - dirnames.sort() - filenames.sort() - rel_path = os.path.relpath(dirpath, local_path) - - # create subdirs - for dirname in dirnames: - flipper_dir_path = os.path.join(flipper_path, rel_path, dirname) - flipper_dir_path = os.path.normpath(flipper_dir_path).replace( - os.sep, "/" - ) - self.mkdir_on_storage(storage, flipper_dir_path) - - # send files - for filename in filenames: - flipper_file_path = os.path.join(flipper_path, rel_path, filename) - flipper_file_path = os.path.normpath(flipper_file_path).replace( - os.sep, "/" - ) - local_file_path = os.path.normpath(os.path.join(dirpath, filename)) - self.send_file_to_storage( - storage, flipper_file_path, local_file_path, force - ) - else: - self.send_file_to_storage(storage, flipper_path, local_path, force) - - # make directory with exist check - def mkdir_on_storage(self, storage, flipper_dir_path): - if not storage.exist_dir(flipper_dir_path): - self.logger.debug(f'"{flipper_dir_path}" does not exist, creating') - if not storage.mkdir(flipper_dir_path): - self.logger.error(f"Error: {storage.last_error}") - else: - self.logger.debug(f'"{flipper_dir_path}" already exists') - - # send file with exist check and hash check - def send_file_to_storage(self, storage, flipper_file_path, local_file_path, force): - if not storage.exist_file(flipper_file_path): - self.logger.debug( - f'"{flipper_file_path}" does not exist, sending "{local_file_path}"' + with FlipperStorage(self._get_port()) as storage: + FlipperStorageOperations(storage).recursive_send( + self.args.flipper_path, self.args.local_path, self.args.force ) - self.logger.info(f'Sending "{local_file_path}" to "{flipper_file_path}"') - if not storage.send_file(local_file_path, flipper_file_path): - self.logger.error(f"Error: {storage.last_error}") - elif force: - self.logger.debug( - f'"{flipper_file_path}" exists, but will be overwritten by "{local_file_path}"' - ) - self.logger.info(f'Sending "{local_file_path}" to "{flipper_file_path}"') - if not storage.send_file(local_file_path, flipper_file_path): - self.logger.error(f"Error: {storage.last_error}") - else: - self.logger.debug( - f'"{flipper_file_path}" exists, compare hash with "{local_file_path}"' - ) - hash_local = storage.hash_local(local_file_path) - hash_flipper = storage.hash_flipper(flipper_file_path) - - if not hash_flipper: - self.logger.error(f"Error: {storage.last_error}") - - if hash_local == hash_flipper: - self.logger.debug( - f'"{flipper_file_path}" is equal to "{local_file_path}"' - ) - else: - self.logger.debug( - f'"{flipper_file_path}" is NOT equal to "{local_file_path}"' - ) - self.logger.info( - f'Sending "{local_file_path}" to "{flipper_file_path}"' - ) - if not storage.send_file(local_file_path, flipper_file_path): - self.logger.error(f"Error: {storage.last_error}") + @WrapStorageOp def read(self): - if not (storage := self._get_storage()): - return 1 - self.logger.debug(f'Reading "{self.args.flipper_path}"') - data = storage.read_file(self.args.flipper_path) - if not data: - self.logger.error(f"Error: {storage.last_error}") - else: + with FlipperStorage(self._get_port()) as storage: + data = storage.read_file(self.args.flipper_path) try: print("Text data:") print(data.decode()) except: print("Binary hexadecimal data:") print(binascii.hexlify(data).decode()) - storage.stop() - return 0 + @WrapStorageOp def size(self): - if not (storage := self._get_storage()): - return 1 - self.logger.debug(f'Getting size of "{self.args.flipper_path}"') - size = storage.size(self.args.flipper_path) - if size < 0: - self.logger.error(f"Error: {storage.last_error}") - else: - print(size) - storage.stop() - return 0 + with FlipperStorage(self._get_port()) as storage: + print(storage.size(self.args.flipper_path)) + @WrapStorageOp def list(self): - if not (storage := self._get_storage()): - return 1 - self.logger.debug(f'Listing "{self.args.flipper_path}"') - storage.list_tree(self.args.flipper_path) - storage.stop() - return 0 + with FlipperStorage(self._get_port()) as storage: + storage.list_tree(self.args.flipper_path) + @WrapStorageOp def format_ext(self): - if not (storage := self._get_storage()): - return 1 - self.logger.debug("Formatting /ext SD card") + with FlipperStorage(self._get_port()) as storage: + storage.format_ext() - if not storage.format_ext(): - self.logger.error(f"Error: {storage.last_error}") - storage.stop() - return 0 - + @WrapStorageOp def stress(self): self.logger.error("This test is wearing out flash memory.") - self.logger.error("Never use it with internal storage(/int)") + self.logger.error("Never use it with internal storage (/int)") if self.args.flipper_path.startswith( "/int" @@ -312,24 +166,19 @@ class Main(App): with open(send_file_name, "w") as fout: fout.write("A" * self.args.file_size) - storage = self._get_storage() - if not storage: - return 1 - - if storage.exist_file(self.args.flipper_path): - self.logger.error("File exists, remove it first") - return - while self.args.count > 0: - storage.send_file(send_file_name, self.args.flipper_path) - storage.receive_file(self.args.flipper_path, receive_file_name) - if not filecmp.cmp(receive_file_name, send_file_name): - self.logger.error("Files mismatch") - break - storage.remove(self.args.flipper_path) - os.unlink(receive_file_name) - self.args.count -= 1 - storage.stop() - return 0 + with FlipperStorage(self._get_port()) as storage: + if storage.exist_file(self.args.flipper_path): + self.logger.error("File exists, remove it first") + return + while self.args.count > 0: + storage.send_file(send_file_name, self.args.flipper_path) + storage.receive_file(self.args.flipper_path, receive_file_name) + if not filecmp.cmp(receive_file_name, send_file_name): + self.logger.error("Files mismatch") + break + storage.remove(self.args.flipper_path) + os.unlink(receive_file_name) + self.args.count -= 1 if __name__ == "__main__": diff --git a/site_scons/commandline.scons b/site_scons/commandline.scons index e3ddc59a..d832a466 100644 --- a/site_scons/commandline.scons +++ b/site_scons/commandline.scons @@ -194,10 +194,6 @@ vars.AddVariables( "system_apps", # Settings "settings_apps", - # Plugins - # "basic_plugins", - # Debug - # "debug_apps", ), }, ), @@ -222,7 +218,7 @@ vars.AddVariables( ("applications/settings", False), ("applications/system", False), ("applications/debug", False), - ("applications/plugins", False), + ("applications/external", False), ("applications/examples", False), ("applications_user", False), ], diff --git a/site_scons/extapps.scons b/site_scons/extapps.scons index abe1a453..208b7577 100644 --- a/site_scons/extapps.scons +++ b/site_scons/extapps.scons @@ -1,7 +1,9 @@ from dataclasses import dataclass, field +from os.path import dirname + from SCons.Node import NodeList from SCons.Warnings import warn, WarningOnByDefault - +from SCons.Errors import UserError Import("ENV") @@ -12,7 +14,8 @@ appenv = ENV["APPENV"] = ENV.Clone( "fbt_extapps", "fbt_assets", "fbt_sdk", - ] + ], + RESOURCES_ROOT=ENV.Dir("#/assets/resources"), ) appenv.Replace( @@ -57,7 +60,7 @@ appenv.AppendUnique( @dataclass class FlipperExtAppBuildArtifacts: - applications: dict = field(default_factory=dict) + application_map: dict = field(default_factory=dict) resources_dist: NodeList = field(default_factory=NodeList) sdk_tree: NodeList = field(default_factory=NodeList) @@ -86,6 +89,9 @@ for app in known_extapps: appenv.BuildAppElf(app) +extapps = FlipperExtAppBuildArtifacts() +extapps.application_map = appenv["EXT_APPS"] + if incompatible_apps: warn( WarningOnByDefault, @@ -95,27 +101,60 @@ if incompatible_apps: if appenv["FORCE"]: appenv.AlwaysBuild( - list(app_artifact.compact for app_artifact in appenv["EXT_APPS"].values()) + list(app_artifact.compact for app_artifact in extapps.application_map.values()) ) Alias( - "faps", list(app_artifact.validator for app_artifact in appenv["EXT_APPS"].values()) + "faps", + list(app_artifact.validator for app_artifact in extapps.application_map.values()), ) -extapps = FlipperExtAppBuildArtifacts() -extapps.applications = appenv["EXT_APPS"] -extapps.resources_dist = appenv.FapDist(appenv.Dir("#/assets/resources/apps"), []) +extapps.resources_dist = appenv.FapDist(appenv["RESOURCES_ROOT"], []) if appsrc := appenv.subst("$APPSRC"): - app_artifacts = appenv.GetExtAppFromPath(appsrc) + deploy_sources, flipp_dist_paths, validators = [], [], [] + run_script_extra_ars = "" + + def _add_dist_targets(app_artifacts): + validators.append(app_artifacts.validator) + for _, ext_path in app_artifacts.dist_entries: + deploy_sources.append(app_artifacts.compact) + flipp_dist_paths.append(f"/ext/{ext_path}") + return app_artifacts + + def _add_host_app_to_targets(host_app): + artifacts_app_to_run = appenv["EXT_APPS"].get(host_app.appid, None) + _add_dist_targets(artifacts_app_to_run) + for plugin in host_app._plugins: + _add_dist_targets(appenv["EXT_APPS"].get(plugin.appid, None)) + + artifacts_app_to_run = appenv.GetExtAppByIdOrPath(appsrc) + if artifacts_app_to_run.app.apptype == FlipperAppType.PLUGIN: + # We deploy host app instead + host_app = appenv["APPMGR"].get(artifacts_app_to_run.app.requires[0]) + + if host_app: + if host_app.apptype == FlipperAppType.EXTERNAL: + _add_host_app_to_targets(host_app) + else: + # host app is a built-in app + run_script_extra_ars = f"-a {host_app.name}" + _add_dist_targets(artifacts_app_to_run) + else: + raise UserError("Host app is unknown") + else: + _add_host_app_to_targets(artifacts_app_to_run.app) + + # print(deploy_sources, flipp_dist_paths) appenv.PhonyTarget( "launch_app", - '${PYTHON3} "${APP_RUN_SCRIPT}" "${SOURCE}" --fap_dst_dir "/ext/apps/${FAP_CATEGORY}"', - source=app_artifacts.compact, - FAP_CATEGORY=app_artifacts.app.fap_category, + '${PYTHON3} "${APP_RUN_SCRIPT}" ${EXTRA_ARGS} -s ${SOURCES} -t ${FLIPPER_FILE_TARGETS}', + source=deploy_sources, + FLIPPER_FILE_TARGETS=flipp_dist_paths, + EXTRA_ARGS=run_script_extra_ars, ) - appenv.Alias("launch_app", app_artifacts.validator) + appenv.Alias("launch_app", validators) # SDK management From ccaa3864d54b3792b8ab0ec0a2efbd82b3c2f7ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Wed, 15 Mar 2023 00:02:27 +0900 Subject: [PATCH 02/32] Dolphin: new spring animation, weight adjust, drop winter animation. (#2489) * Dolphin: add new spring animation, drop winter animation, adjust weights * Readme: update application folder structure info --- applications/ReadMe.md | 15 ++++++++---- .../external/L1_Senpai_128x64/frame_0.png | Bin 0 -> 1756 bytes .../external/L1_Senpai_128x64/frame_1.png | Bin 0 -> 1841 bytes .../external/L1_Senpai_128x64/frame_10.png | Bin 0 -> 1846 bytes .../external/L1_Senpai_128x64/frame_11.png | Bin 0 -> 1824 bytes .../external/L1_Senpai_128x64/frame_12.png | Bin 0 -> 1826 bytes .../external/L1_Senpai_128x64/frame_13.png | Bin 0 -> 1862 bytes .../external/L1_Senpai_128x64/frame_14.png | Bin 0 -> 1815 bytes .../external/L1_Senpai_128x64/frame_15.png | Bin 0 -> 1855 bytes .../external/L1_Senpai_128x64/frame_16.png | Bin 0 -> 2009 bytes .../external/L1_Senpai_128x64/frame_17.png | Bin 0 -> 1918 bytes .../external/L1_Senpai_128x64/frame_18.png | Bin 0 -> 1686 bytes .../external/L1_Senpai_128x64/frame_19.png | Bin 0 -> 1593 bytes .../external/L1_Senpai_128x64/frame_2.png | Bin 0 -> 1879 bytes .../external/L1_Senpai_128x64/frame_20.png | Bin 0 -> 1281 bytes .../external/L1_Senpai_128x64/frame_21.png | Bin 0 -> 1318 bytes .../external/L1_Senpai_128x64/frame_22.png | Bin 0 -> 1102 bytes .../external/L1_Senpai_128x64/frame_23.png | Bin 0 -> 1537 bytes .../external/L1_Senpai_128x64/frame_24.png | Bin 0 -> 1414 bytes .../external/L1_Senpai_128x64/frame_25.png | Bin 0 -> 1486 bytes .../external/L1_Senpai_128x64/frame_26.png | Bin 0 -> 1364 bytes .../external/L1_Senpai_128x64/frame_27.png | Bin 0 -> 1325 bytes .../external/L1_Senpai_128x64/frame_28.png | Bin 0 -> 1278 bytes .../external/L1_Senpai_128x64/frame_29.png | Bin 0 -> 1179 bytes .../external/L1_Senpai_128x64/frame_3.png | Bin 0 -> 1861 bytes .../external/L1_Senpai_128x64/frame_30.png | Bin 0 -> 1198 bytes .../external/L1_Senpai_128x64/frame_31.png | Bin 0 -> 1204 bytes .../external/L1_Senpai_128x64/frame_32.png | Bin 0 -> 1248 bytes .../external/L1_Senpai_128x64/frame_33.png | Bin 0 -> 1669 bytes .../external/L1_Senpai_128x64/frame_34.png | Bin 0 -> 1767 bytes .../external/L1_Senpai_128x64/frame_35.png | Bin 0 -> 1832 bytes .../external/L1_Senpai_128x64/frame_4.png | Bin 0 -> 1769 bytes .../external/L1_Senpai_128x64/frame_5.png | Bin 0 -> 1869 bytes .../external/L1_Senpai_128x64/frame_6.png | Bin 0 -> 1893 bytes .../external/L1_Senpai_128x64/frame_7.png | Bin 0 -> 1835 bytes .../external/L1_Senpai_128x64/frame_8.png | Bin 0 -> 1772 bytes .../external/L1_Senpai_128x64/frame_9.png | Bin 0 -> 1827 bytes .../external/L1_Senpai_128x64/meta.txt | 23 ++++++++++++++++++ .../L1_Sleigh_ride_128x64/frame_0.png | Bin 1656 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_1.png | Bin 1754 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_10.png | Bin 1494 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_11.png | Bin 1637 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_12.png | Bin 1713 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_13.png | Bin 1585 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_14.png | Bin 1634 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_15.png | Bin 1771 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_16.png | Bin 1681 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_17.png | Bin 1503 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_18.png | Bin 1663 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_19.png | Bin 1661 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_2.png | Bin 1681 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_20.png | Bin 1559 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_21.png | Bin 1542 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_22.png | Bin 1736 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_23.png | Bin 1621 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_24.png | Bin 1628 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_25.png | Bin 1671 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_26.png | Bin 1636 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_27.png | Bin 1621 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_28.png | Bin 1099 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_29.png | Bin 812 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_3.png | Bin 1651 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_30.png | Bin 536 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_31.png | Bin 492 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_32.png | Bin 503 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_33.png | Bin 897 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_34.png | Bin 1490 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_35.png | Bin 1741 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_36.png | Bin 1538 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_4.png | Bin 1668 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_5.png | Bin 1555 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_6.png | Bin 1521 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_7.png | Bin 1642 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_8.png | Bin 1694 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_9.png | Bin 1605 -> 0 bytes .../external/L1_Sleigh_ride_128x64/meta.txt | 23 ------------------ assets/dolphin/external/manifest.txt | 18 +++++++------- 77 files changed, 42 insertions(+), 37 deletions(-) create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_0.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_1.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_10.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_11.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_12.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_13.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_14.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_15.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_16.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_17.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_18.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_19.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_2.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_20.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_21.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_22.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_23.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_24.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_25.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_26.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_27.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_28.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_29.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_3.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_30.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_31.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_32.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_33.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_34.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_35.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_4.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_5.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_6.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_7.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_8.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_9.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/meta.txt delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_0.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_1.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_10.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_11.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_12.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_13.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_14.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_15.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_16.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_17.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_18.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_19.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_2.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_20.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_21.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_22.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_23.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_24.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_25.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_26.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_27.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_28.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_29.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_3.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_30.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_31.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_32.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_33.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_34.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_35.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_36.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_4.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_5.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_6.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_7.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_8.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_9.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/meta.txt diff --git a/applications/ReadMe.md b/applications/ReadMe.md index 6224cb45..e50d8e46 100644 --- a/applications/ReadMe.md +++ b/applications/ReadMe.md @@ -36,15 +36,20 @@ Applications for main Flipper menu. - `u2f` - U2F Application -## plugins +## External -Extra apps for Plugins & App Loader menus. +External applications deployed to SD Card -- `bt_hid_app` - BT Remote controller +- `clock` - Clock application +- `dap_link` - DAP Link OnChip debugger +- `hid_app` - USB/BT Remote controller - `music_player` - Music player app (demo) -- `picopass` - Picopass tool +- `nfc_magic` - NFC MFC Magic card application +- `picopass` - Picopass reader / writer +- `signal_generator` - Signal generator app: PWM and clock generator - `snake_game` - Snake game application - +- `spi_mem_manager` - SPI Memory reader / flasher +- `weather_station` - SubGHz weather station ## services diff --git a/assets/dolphin/external/L1_Senpai_128x64/frame_0.png b/assets/dolphin/external/L1_Senpai_128x64/frame_0.png new file mode 100644 index 0000000000000000000000000000000000000000..ed37723ac245322aa254e648ab48da409e7e35d8 GIT binary patch literal 1756 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Senpai_128x64/frame_10.png b/assets/dolphin/external/L1_Senpai_128x64/frame_10.png new file mode 100644 index 0000000000000000000000000000000000000000..e385018bf6531534e1d809b371b0172361c657af GIT binary patch literal 1846 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 O0000lE>hmW@WGRyB&Rwv znb;IXLd5HUl<&6h`@U@(?fXvS|AB4Wh=^X=Zv0w!5D`@VXut7m<8ihCH_pXIMEme` zlmbu`7^#8iJxa)HR^J=vW8)lv=%>;(K0rjOk$47lbBycZN%ZQeLm8I$az&zhIkan< z;h_Gvp{&D`NcYspf^#E5@FI&G$Lg=$d!vpgYK$(pRE{(T?OxswdH=ZgR`_~A10b@w z0BZLXW{suc<`3(!jS|4}UXS%_`MW|ctMo$&Y# z$C*Z`tugtCu3k4r;2`;W4##w7c9DwlesUm+ZZtjK5rE((nv*l9phxy zjfm)>ExzX6qn$5)v{bbESf%003LvZeAx~ugLwlww(40sgTu!-eG6FsM7i|K5O@8+7 zmx9WE$XeyN=m9%WQGT>K8f})`Ug<3RWQ+>&0wn9(Rxp}ePK5eP1ojeXR8U%@HG8CQD-mLSOn4X;)s=Zg-s2sjma|hD3G$W zc|5PT#wJou1GUbUAgEnFKqTz1V*4F9DiTGNiS9$N^!v6GC_6+2^8~IuP&ZbFWNn_v zoE1VkP*(j(=wUM=#$vjBvQ@(}^_6zt78tH4<{ z&fBDHk$EicRLTEd$sUFJMi56fzi2{oXd4xLjH8HSS9Gw($wWR2R@GF2`@@jR1X0$j zJ&e4*LiyFatQmE7HxE7k8CdqqAnwIh$V1fu+Dy3;Bn!_(kSi#iBa{NF@Jh-^r0~Y6 zAZk$7PTtiNS;gtU4fOJ6*Fsy7(A8+^e*MqN8w@(ywH2r$hjBkfB{$HUEtt zI4uQON?4^JIJB2(sI#s}ywwBj_-IKZxk>hH(zT)foNRM&ND*gT?ui zh<+!c??m+5C!Xw~@^bcNmJXTDSjVgZ{IhM_H{<%p0Bu*F{T{;^Mi5Q`|Lu5v8+r3n z6+d8|Q)pOu#y@0|XCSKq4L~Yb+q>ttERbzEB1U|Kq07CHBYDA_0YqwYAl&)&%p{_V zepE%UIvR|zUko5(?Vm{cJ_X1KRzw3H(L)>noqAo}5V*`%pGq278J>~CSEhuozZB{n zmRwoc9NbW|9+c_ZsgQ`mSEYofrC96iUXnzKqYcKY*N-?~m;sVcYa^O}jg0H@jRQn1 zOnZr=;&mMeR4HL-vU__3DXVx$f zY0*#(ff=%k{z+LQ5hN;yS6yYE0TFZwb0a9L0JKrWH)OIE#?jRT0+K(fq^|intUZDV zWHsQq1SZf_6k)gvNEtGICmw7Ro z^P_Xi&nwYs#1V8nmqjKmyc`DcP~ovtsbm=zwG>c+X9P%&>iCs=BlC7f0lvnbb4Zdd z+u)yyMY%*PDI^*49=7UT+QU|frZs-Gf0S5u0qMu8%7%=*-%}2TCP5106Ux$WOk{C~ z@i_xDpVcoqhqh=(0_A7kK46jbj1I@RPj8{DMa}?4ikZcZGI)kPk07*qoM6N<$f=Lf+7ytkO literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Senpai_128x64/frame_13.png b/assets/dolphin/external/L1_Senpai_128x64/frame_13.png new file mode 100644 index 0000000000000000000000000000000000000000..a996443fe46be0e95aa380d17883daf5dacbb55a GIT binary patch literal 1862 zcmV-M2f6r(P)YNI1HuJ0Q1=Y|C|2OJkDSS?n`ycFz0a5VmpNo zP&APxiV}AzZnu5k_ifu`-*;($H*DJ`BJ$F9B3BUrm z>nMh2EZZv`M>l?!05f#5N>(wd>~~A+O#w7h*@K+QxXNi`Mj`aPy{IZsMu1s& ztM0Gn|G_q&S$AvKi>WRknh%wit15sV;kPw6&xZD@JwxM0cx7$KqoRGY%KOw`*aZ5S z{Tz+AjM{!kTPz!(#f+p8fmz**xmXB9`@`B% zNO&?~BgR=0V;Mzm$}$}(8S)yRgi*dDk7ETKxq2qwC+Zx<8ZSZ2ga?L<=NQG2%G9d_ zNLUeVFSlZ}MaXR+>+CUt+U)`|Zhuv_KMO}FP*#{|9D*m`x068IAwHw_$UF41RTvdBYux$6vtD1Tcz8KOoK{o4Ui?PqI z=N`3mb=_GXZ_V$bEF>HwV_&crA^n}t_A$Q6XHkqQC5h)T+gr-V z3QBNV2=D~FGQL^`J6iy#Z`=0GdHzv|4ykl|!;$Mf!no2Q z;8&vZt+~uk7JS1MvhW6WgSt9FgbH8<%+BuhkOjP5601u>^ee2Diu$GinVK9hbE+B5 z+8nmgM|nF>*t5MsqxGTy*=+bq?%$p!9yRAmto8=lqo;BJbnA6BMX=C3dwqIu@n%{g zo6ib+#gX~ft6?6|t0sN+IN2)dnFlOcMEtS4l35`Ez8&1a?(R|fWdEE6_Cu2_PnEvC z>tw%>@(hqEL55#TTg&k+2godn{fOg8t3&Sl>ek+XuA>{6B6zo^-nzAXkj!8efUKPL z^X~GmB#cphtksPwx~BwsJ~k$!1k8i$wl@Xv5{;`Tej>i0vzCLBuWBAt^`z~4M5EUe zApW*(dw(y$X8A}75VI>VYF!x~qfOIpQ;&(}s_@|P6Va~DWBbFmgEI?R>E5$SHnPE1 zh={fqS2O&L@DnKkqk%r7{tXY?V8JG5V`d&wM6@dIKKsn}Lw_`{9=*{VK?8Z}O!m)s zZ*UKSsshl;qC-ffJoI8*6*Cf1#6MDk5gxL9&hWan8h9YWvywfefwPsMSIE5<#N7Xi zswXQ0El@2xYk+plOBtv;Y68)uUbVnN$B{}f5(*;zwYT#y;sv}=VB@`2Qu_=gvZ+_Y zy!d9bKAshgv;->9{WDgaq1QW}WyCWRI30F+J)zgft5DG24DLx_v>}!_+gLBJ z7|Fm#;;l_yS7z@lpUN1Q?SllW^D*RsS^l%{yZ_I{_gWWgpH(K!rZY;*6KEvt-lR}J zv&k-MXL|;(gKc>iWtSBoFEJv3(X1QA#t4XNB$Ug{K%l0oQh@Lj#3%_W%F@07*qoM6N<$f*OH~ Aa{vGU literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Senpai_128x64/frame_14.png b/assets/dolphin/external/L1_Senpai_128x64/frame_14.png new file mode 100644 index 0000000000000000000000000000000000000000..628d58b93a3143142a7dd78ec0a0dca53023a984 GIT binary patch literal 1815 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Senpai_128x64/frame_16.png b/assets/dolphin/external/L1_Senpai_128x64/frame_16.png new file mode 100644 index 0000000000000000000000000000000000000000..3ec3727989dd33181d644399bfecf66a2146b9d6 GIT binary patch literal 2009 zcmV;~2PXK5P)I;*U-BV`q?}SN%P|IO01b$e z#;)lq*Loxp1j#1Q;69dZ+qPv{WZO0wUk8?D5fOQM_QtP;4-tWzKc2twYvbc?2i~|B zi->IFq@xsoqQFQEBKs&|-$iZTxF3tw03ts&-O~di;*umI&|71?9#)a(lR7pdI=y-$ zQC|)3oktwh{!Ntgu!{I6m05625`-+WSUXXBukRautmq-T;C6E?8N9x_9XkD^?|Z|~ z2PA+&`n~+!5tmWuzbh?dX>R!bGvYyjr-#MTE!%i& zi_rrNsnF*cU{jgb=V~%|Ys~oabPq5(W~bj%>QQvipV|9`?4-|51GDVSBxOcM_OQA3 zMdazNH$b!j7ARb2Ih;A!QR#SO!OzeWGe}T5RP1MKYexVPep5VlM*S+cjSM05xE-9S zAv!myjE$q~tH=NF9Q022c&-&<)2Cg#tn9XaB7fa7P!Dc9p~0gKWX{h+dICA>Q<)=*vdFG(kM?&(jk8=63B*na%qB7m zGxL4~y`g83*V>M>mBuawpod=L$Px5>*s%7Zuz!ZyzY{Y^;6)Qe0jNDW~5J6k#n9s$|f8A3qeTH%EW< zI#3Y>|MFoD8(RxpB?oW|Avp%J`rrhqi+HOXZ{kAuTmfYx*xcT1hy8h>Ws5}^==QD zJ`8rkQwkB_6W>JSPZ9Y?M1H47miWZqQQk$T-irVt*F~%=bVro{|B1*K5&6C>%L?AR z17&URx(%0 z1kc8`?~bhFjsV1D{}z!y*v+?2W@B9imed1B1z~5CvO{IJq8yBh#mnVwuWf+qV5*2sx71?+o*u zlTsysXTKW-J89$y3DEvb`%|&D-L zlnD@Pyz2A}u&2-Z5&4z*8%4U#T&s5^Q5%m0c8wlmq7)Eytc21^n+|4(u(^_GBLbD?hS*_x|N2IWo0Zaj2{>y663gas$@W^S6z?||*ZSDDs z)&<#){EXVOLKeNFz8N$OFv_wbeqNab8982w1fD%ljOx$yMUWTXY)GG3@>SA7H(hl4 zp|$YZApa|&o%P4_1MP?QrF;`;CyoZMZn< zXQcqIZ6!Rj=vFR_?mp;1Mn&8~k(e?BuLi^fWLl|i^38^SR3&)xL#YFiFIpr0d{+kQ z92E_!9j{>6LX19QuhY+4;0#^jjCrHEN5ruQ(#{!ee&p{{dy)L7f{_uXPvq2*cBs8~ r3RpV>6#_(?g-}Ux00000NkvXXu0mjfjU?0v literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Senpai_128x64/frame_17.png b/assets/dolphin/external/L1_Senpai_128x64/frame_17.png new file mode 100644 index 0000000000000000000000000000000000000000..11b247eca27efdf52b168912f14183521e1c1b63 GIT binary patch literal 1918 zcmV-^2Z8vBP)OPdIS%Uoa+Gy=i1;U!*>LS72w7yYa-#m;*cZm}K#lB%TjN+~@W$$X==AroFN3cK zbO4c<3!wJQV38~jFaEF|t)~Fd>CITbcYlwN%P90;c`amDoyAvi%uff{H5OC1%^%6cjVdko#y1rU1bh{mDlk<}2aUWjyl zCF6^5`xx~_^_eO^(HYwV35W#h6WtYv#xNVoa0stXQC)nd#|-0VVVk`M8U1>HTf6c0 z&Fb;cAJF8;;7E983^X1os28E1uTJ}B8b1r$B79n`qnzElEbZ=nyk{cjb#Np^8z%Bx zJ;vVu_TQE0gEEMfMwYjoD4;c6iPXY^!^uG^m~fwqI4AY$;5 zA|qbE8Wjh~`c|AZ<5t<+NB2_e`o@HIG!UI!k7-&(?-1r$vf6Q%>j~xiOopJLqwr@X z(54VsIyS1fo$u~o$X0)5`)6`z?SYq4Gvi`4~R%;#d(hsMo%AR zOg1)*0ISNf$oRHQeAa3A?qq#}k4A%8W0~ksscec2aDbV{iDX@o@WJvx;M1B}he8Gd#R zL^Y1t`gTORBIy3rI>3J-^4?n89fAqXZDxO#HB9x&;eJn<@fS;|kcQpBm zYT+!|%-oeZ7+ueb0xZH8asnKnQk3~<-#%mwDYueIADzE4#uk;%asq273;S$$fJO43 z4fNV)g+K4apm}Gb@)nNKjd22R>{-w{?l^#_lxhFfTKjY60uY=2sD_%Rm?1A-h`Zw6>p)I8a<6+}%fl5tNBXUB z_}y;^VN0Enqd=muC=Z^^x~u6_<@Amim?<>b{@MKYz&`zo7?$~4VG$ubi~X*0I_4_q zVCI}*R7QrE&F!992SA1fsAWO?Y)7$D3pJyzDg)6=d}DGeA`><--A3_8;}}lsIG2E2EBg z7`zk5rqfwft+A;572&R{jDw;KL5)YB#2Eb?3j_zsWBsDGBWO(sbu@X z6nP?ErrTjD_^DG;P^jLqtlSZdjAd#bNAzH?g`y*?f;kfzedS3pZW1ey+Wgxf1S5+z_m; zS(-RfJMbbfdpO~L7-7@9i1k;2BxU$hNv5N4NC~mOOWP(Hc^IAJgKgIx+>!2c&(eX(CEYeoxMBIzbYFcaZ8&dcE$l)8?1ONa407*qoM6N<$ Ef|8}Dz5oCK literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Senpai_128x64/frame_18.png b/assets/dolphin/external/L1_Senpai_128x64/frame_18.png new file mode 100644 index 0000000000000000000000000000000000000000..bb15041331abe5f7a1007befcd37135b1f0b6381 GIT binary patch literal 1686 zcmV;H25I?;P)V9!Ergj2gmjfkWw1@dEpt`J3!8vQc6QU zFT5h1;7MU#zD94S`e5B9WsJ}%w$)GL=$T>fL zkWu+P8KK-6cpEFqOMBU8RHTolY_u-BC8fv?L_~MlfExh)9uX*=M%!Pt1w_Bgep(+3 z(LH_#Xg1)w*Y2aWzM+Vg6fTV+qZmcB$2i)4?lGjNjc3uUG5P%YJueG*5DBc7WR%O` zI+klUPe-)B%QpPGwQol!F*`bulWX9PuoG)&L1WcP%C&oKLKfBX$VV)Ju5(#ZDKv?h zNuLLrPAD%yr<76p)EEXCSu;NW{=xv3vR1_nd6zV}rkt1dJW0_~nQ z>79V~O@0pnCD1kM?gi2dF#uwSo7PB9MBCdeCx{-?X0wOaC^ZH|w2!v39^-nq5*eVH z-H=^#&UCYAq6p9?j~0PXKEf1oQAeTmcxH`feJ|IJIxTn@fPda=dqsiV*N{*ykoQA< z_%%(=OjvW}_iJMmpf&N1(K2VRW7X^n8GtL+qEsZS-z{n6uym_Yy|xXGgk)tDd^ILe z89*w}2iGPay^g-GbchiVs%7Hk-i-NZHj>{pR(oLp-q`E)H3dD8iPJ3OTZZ( z;&Q(ik#*7nO)|obduy#T5+aq6M@Xwb$LM4YjXzuVX#J61=$dw7LH%UHeMeqZqZMs` z^CnZ=F?ON+opTWhX#=FY$0>;DH3#r2P=j|oEmt@Tj&iCRyLx3q23Q%HtBu}z?6StF zv0RT%HGO;64?YfV9aP(&tUAG2A?LyKo`~_ivSwk*ABK2nef9n?WlOZ`Vi{RFf+fSV z9wXQ6k^IkafL&o3VrJpS>i{14J$fQ%y@Ph|4xU9jOC0dy9KYu6E1^0-(9?bs(Px(y zi*e*uGYfKSX*mg$J~w_r)|H(L_nnCTxHP{|MI>>QNhcah`+2k>dV3=%a18uGMBj+$ z7ZH7NYkL58iT)M)yEYaAAO9W{_Lts_|A#g={+ThtalQQ`e;fFNh(7)Q{Zz@LhYDBe zVL`I+3OpSHTgt-!k?Cza*$%b2v@UcsLZjP2R8H&m@${!*9kjEh@jcifN8peZMz38^ z_0i+_Dd(KI5Qo5@MD(?)(MH%I8H}FWN0jG1?3UifKITEtO6)MNXi0d~Ga6GTlmkeG zB#vIlR?IB-)?`YwIzg>(6lONBMa>S;)Pfa}HR{zbYn-0SpOo=n0BKZD_IySsK$vyp z8uid=lvAPcpaVqS=6MfJ(DL>Y!t5iPR}Tq`#qa#fg;zut+nee4z7*rh)4X^*3*dvV zWPnjo)e9{F?q!A}woz}6lTvzp7O=8By811c2qJQ=60Sw}jaAo>K1ByjhP$SJlp$s< zk|n2h4C8&YYse8+I)MaYg+~s66#9rj#&3O%3$0_#3AC|#P&>-2?*!Hsf=3I-$cH3H z6>g$=5RtvlO0+sv3#@shl-eHaJ{}}=Bu)jZDqbG6JPmymR20KSn7}h0(ix56#Zw(X zcGmX9T@$xn^h96n2-wO5R(o%yR%7X>N}z14F3Q&?wZ101~@v>wTT{)Ko)scjug!ac)!vXL6cR(%j4jh z5Q3G&3AnX3T=vsTp`|#$l|i4Dqa0l@LW{q;uA#A!)kbS)$!MAD z3;Q4jK#^X{EHx!oRJc6bQtdoEL(f(w(Arvk&=%o2!8N}WYE`c+#~BUetln#@;mrw3 zTMaGjq0Rm2U&TfheL7D9`qlc4hNat)n6dX;1tiVkEyoX40xjVMM`z!bT+i0F0ud(g gj3t5cHEmn|2Mp`eeNyah;Q#;t07*qoM6N<$f`7{!ssI20 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Senpai_128x64/frame_19.png b/assets/dolphin/external/L1_Senpai_128x64/frame_19.png new file mode 100644 index 0000000000000000000000000000000000000000..f953c8ef1950ee6ae7dd15ce03c7a56d931e8f5c GIT binary patch literal 1593 zcmV-92FCe`P)q$gGRCt{2T-%P@I1H7I01I><`v0HVhb~Z{fcBvygTwMJQgS9a zK;a~oC5k+}Nb>aPz4!LU%i`QEz#Dt2G|ZN4=Zr|=E<>E8DMC$=5?+gYU$*G=b0UK1+>G7e;7h( z3ZUBK0t4Q|xrYl$(wNeOvN4F@F;JB^S4N4mHGZ<(Y9wU60CEH%=d)(+hQzypL-4pB;AL?Qxlp2N8D&%ST;{&ToKwM^Lt1}T5*NKx5gXY zk`g4PREAusZ!XtMSeYWuoyNE3R7x4J2-U~{$I2|UEUN_XN`h-^mD@YN1$8ZsflQ&X7cv7B*Px}Ct>K#_J z!;_##;pcI`+{CC!%uY_~oAXAiOrGe$$-L9^2iU`)h|vZtk-$?6aBZ`QVCm0WFIc=Z zI#y;Od(N;>GT69k>4JhMEM_PIMllOKMJF6XD(-gSlqN&vYz&wdEV(~Yc)@Y*3`)LP znJeZD^?!2Vgev&pJ?u4FfjF*F_Io^Z1u7!Z4qOoc8`NVouF~~*epG?GNI@0F&j_*x z;MTR8vLl{9qrA*wC_4U3F;@kv)3%280jNeWzvJJyPOY9llM*o|^oK7RX_EgYv@F0Xob**FqrMJ8?>4U+d)6usv6 z0fqn{pMN*zC|MkgX~TJ=WE3h|tw6@g#j+nVLr{;5*FJ!DZLR$PpC4Y2iLe(Efy&{# zmRgZI!t%_HWi7i80Kb2H{toX)mN*&9KKT0%^**B1I{c-4SNSh^hHTnQ2>+Bq6 z!#`d{QBdCrq`t3MuHYTFG49=nFl+qi29y=lss-AQJPV)%q70G&ZX5U+Q04jApbF7J zgvT9(%R*%?)%vo0ULliZoKFz|H(Kp|-c1s&53AgfUA(V`5fOSPUI^J85e7U4jrPBr z2-^5Ke^;RV{|ThR4n{NzWGICyj8^xS-R;niB)nAs!{UkmrZ`+^4L!L#B04u-7;*4ykMQ5pe; zFy2TIUF+_8Bj_%O5&`Qtl>l&%h+!x)R3Db?#w>|FM4x)w~n|u7Q$?AcfGC+tXgGj5HQ3 rBfvFaBKs|Q^e#JYt*w7w!Giw*R39cKIu>1m00000NkvXXu0mjfTl?|s literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Senpai_128x64/frame_2.png b/assets/dolphin/external/L1_Senpai_128x64/frame_2.png new file mode 100644 index 0000000000000000000000000000000000000000..36c3b4abe1768c8cace03ee61dea063f00ada2d5 GIT binary patch literal 1879 zcmV-d2dMaoP)_^tiM?~TXV3fwpsn~3af zr=t*nyg&;LBJW9H&NE%DZ+PZX2CT{5F*cF<#_$s*c<(LqQ>lkTjN+Vu(7%y+Wl?pZSeJg1Q79a z0n{ED%#+1%=MU?#wGzPFy;0WB;&)GS>AC(pEX>Cj0bUx5rCVfpD=U)^WJrZR&qS2p zXq-txw#NE19V_ZNKpPnCewL|w-a&uPK3|A-`id~n``ltxNQ&~R>}I3#Mdalwo4_-G z1#;I>4A1OruXG$;_*n+b$jOqdvZ=D4t*kc!Xr;0baw_90Pa89Y(DU}9N}!AZv-(yl zE!I|DU#K%C;Yv9P$Gy6VK=P1^A31TKZFl0Q(D2^mk zFA0#bBH3PU#bk?+(?HhQQv|i!8_2l*RoVV596_M0G0`{#&%SR@0&Rz^Vv)i)~i%AH2_8ePkm5WwO@G|-~rDLyf#SFtLTS#tGwNj?7SABEZyA}YtK$mS7O!A$*T z$zq9IZ|=13J1zr9jql|I))RQLGuJCR&GI^)Ec84CwO0WY2>Biy)0^d|^szum=@d#!-pmEbrjSsdzpQUe(lt@QWcm6J)bq zb~E-m+J&tCT@6O+Y{{(OUm+YU`(}{d#h%MU)q(XQvkg2d&t~Bj4{`;gYotOzFQSq% z<0)pyX)>y=-Wzi@L-ykI?*>M3lP!LA=b`cJ^J*MDpNGQpM;PY}5`>R}!Q%WaB7cg=cMe1C@~yedkz?VrV{XG0vhW6Wg1UOW!rupIuYtjL`Qc4m zu=DIs!Vx8^%~4j)`$m9FO%B@16lG=DnS@~Xdz7c}jLitrize%Z0ISZ`wS4sSZwmSaCx7^($d*0`KH&)}$-lKO)za zy-M`xTVF@-Tc2LM{6a{kEMg0c6h9KtVkC2!u-&5PKG8Zy@;vVw5!|!cO9;ri7Tr%X zQvV%UUTGaZ0d{??VZ4%%jc4nPJce$2%K>!l86DVUT-R!3-TSCMdhurv-6MgXkB!NY zz^)Cl*&6}8MB{3)w5m^b=b+@P>S3y$w0(|b^m+oM-?nY<-wUvD29W?My8_Y8%+MiP zH0?IgywS6Rs*a3&Om=l1+aG>AICCQ_-Fuc~BMXe`Yb)D}s~P=9^odBoWUxxX_HTID z28*aPMEyq)DQ|V?nXi)UqCc&xCvP-I&_JF#l8q8{3->`#)c~ke(GO&*aq;MC0s-kC zmB1P^i9BO1Y&Gyef@dXrh=H?7u+j^7`m?w^6ImN*gKFE^4QR)_l!3aV77$J9RSPV1 zdo%9vFl%geVa*HFTlIt*g2 zMDKDN7qc-xJGXtm5;IBMurjF?=|5NbssgO5@C2g7$ht)q0(y`o!I54-j}^(C)Qu#2 zC=_JY#U|;t4gORtiY2V1(Bp0L!EUaH7*`{~hz_G9Kw;MsqO~J}>V8k%nJIz{rYE$e zUz;e3lT{+17&g+cfi2o7yg1BIx$Y&1Sv{VSF+7%6fIP*>VrPsTxzcJj-=kl&fjoG9 zyb1-q(m8&N^NnB0G;JG-RgsJc%F`kl{G1*w%<88c9(3=GI5TJ*I$TN5$#9sIAH?tN^?*F8!@bg3) R;+6ma002ovPDHLkV1jF*qVE6z literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Senpai_128x64/frame_20.png b/assets/dolphin/external/L1_Senpai_128x64/frame_20.png new file mode 100644 index 0000000000000000000000000000000000000000..d683b9f625f773ebfba1ae7b290f962e80c5a0f6 GIT binary patch literal 1281 zcmV+c1^)VpP)5Y=Xt`7%i<+kfE%&632<@T1h_bE0$dz70WOZ$$j*=BKtDgv6a6>u zgr$~&+IM+pm*nb*N&|b!?;(0ZIf`w^7A^Y-Axpq6%0G_dNmh#Nt-M$}S>R59U6fz? zu9u@Ci-ZK!_q^e@1V{#-&2>s#dxltMgrx+~Fuo_&aU2h;Y=cFQiEaCplAs`(499t% zr*-72b#)`=erb_;!u7p;eEOi3*UI2Hn+Mu$R@X(f<4pjn`q zA@IYXU6!yl-<#xVw}ab2D~sY=MYUHgtjAeW^CELAXE*_7Yya#SPhhUT{=3A2TQ6XZ zS}rV(TSfq@!B|_E1=>l>LQn}pJpi@dKYG>+*c#ot=fY!by*|R1fDxjVUO*{BXW2*L zA{_x{mKGro2hvDBitI}WV4<+c4IhB<##ZF!jWx^(V3GTe-dR1_J;jgC9as@Gg6L+a zf?c;T7t{rIwqgxTJb-1&H&d)+^JUg&Cgpp3ffoSzWJXP%1qre#UJC6PUIFO)+8HN| zx<}wCd$#iR2#*-R!m6y@GY1*TxdWbpKx%E8%5E%j-@!7jf?*cGP}DB~_^OuX1`8JF z5G=BQ?*QkaA;1~_KHN@&qv@M{9J7S5gy<- z{89b~bd(vqq&=IqhD$^jhv` z6I=iBK-=d?##&a6yy$lWSGl{DnLr&4bFMvEX2q+KoU7)LQ?6%w01vE=1FV$ae%^}- zyqAjFx-n3_M2#l`aFvwdxK@3&P=1X}!CwJJ0;$&t{h2rodIQGtr z)h?$^uO&kCQ?VX(08U%Is-A>Il6Sf7XR&?&VIc_MSwYsyuOV%hv(~Ou3U@r%#?>mQ r{?DM5f?~?cWI0rDl#eZCt7C>wS1=;$EP)zbW*x_N?8Gyj zPpVexP7){n{F`aZaU2KS_^^1{Ex-*Qlu}INZUUSfrIfDnC8YT5Ezpg4SgoDHO@I@@ zQ}Gq)2vACaJ1_kYsnV@ymW}{7Vl2Ifn*d3$Qt22s0XD%?;WHsKRT)_0|1N7-0j_Mb zpr?FkF7=^3z^a5ZiqIaipOyfbtA!OF%xmvy2!It@!@#V2Ta7a*z9J0)B&Y}MaR^={ zidMQsM$O(*5P%XGdjU_ydmu{&?}6!bS<<7 zvy=e%pfy6)XRYHY86uRP)yg@95}-Zg*#pAkz2~;dkquheLJ80cGi$+~73W#nTc`44*V@mg2qTdRZY@#RZtm2}o zhaHxx^_W{5L0aZg^}dJly~potNVSy^XG|KndVtvj!kxdJ%BSQ3$d&z5GFjxD_5dEW zxhK5iy>&iopFz4-n?qUGGZP@2Rd~yT4^~0~ue?AzPpf=w)uARpq~fja-_z=Q`{NQ- z>Uk@)d56XF@tk4;WV3jHIIG9N7KYc8uB}xNwSK#{c3#e_3<;1?@c?^c{pe+tSve~3 z8Y9JXeHG5DbgO_}ikCEo0DR@L#P|w}MS4RTu3D5*XaekfAFuzX9OO%me`OO(jZs z(mM!`#*U=Mm#jmDBda%faZvsb0QfUA0dBU`p9oNM*R^q}w@dk0MoTY*DpQB!40i#q z0Pr0EegVK&<=Dr?H|8ACu6!nGlqSKJlJIF>B>~<5;1j%C{3Y7RvqV@`22x$p(VCL; z)%Ld5u+p8r6J(akg1X$fow8TA&`?khcZ)mH49#q%C66}igoPwguyEEi9Jv=17 zvmPMQ>a~ZTDJzcxzL}&*s%bzShvJPOP8e@UUxRAD_i~SCvwBQ~-qkhryHbknzl7n1 zydh~6)$d2K+1gX@&6gdc7O+bd5bg5WrbXwXrgwSc6)LhRUvo~Q3PR-pRw=)|p{u{c zpB1eqz#c@J9CDr_oAM>Vdmrsf>;a-nJSw?q*X3mBZJ2BI_B;pm-PzGQACU*xrTi9> zya3PHuF-pz`u=L@?P*AW)yNG?;Zkc$)m(B24N5~GWv csIj~87qbzVAj79-wg3PC07*qoM6N<$f|ZkdMF0Q* literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Senpai_128x64/frame_22.png b/assets/dolphin/external/L1_Senpai_128x64/frame_22.png new file mode 100644 index 0000000000000000000000000000000000000000..dd241d24ae578441ddb0870387494b574909677c GIT binary patch literal 1102 zcmV-U1hM;xP)-TmB;NUUqxyH1B)8AVfAY!<; z+;zDqyd(WQ{~k9oP^t!yJZO~YA-;x(_>$Lh4yC%g0Ig1I zitZ8qBZyxUcIN)Udb8mrG(s|D-m| zEL+yIuGBu-hxGLd@vrL=MS!0G@ErhtA>z0EA=e9kY@gLnDjmfiXuE^wxURCBK8tc5&r@Jq_1h=T|f~@cmRGe zsfO^c_j3#|nw&%#U~AE}6Go2s9N}AS96krAokT?uJF#M>YbS3OvwrxjVI%@X4WHi8 zN9(Yu$LOp#xeoSBm?Gv9H8^IJgm0_u@X&SeDxxnhl)NxS`;rErFTTe36b;}}1q2XV za7x~8ZWb?n4n=FMJ{e%etdE*c^r*GFB>b!yS|@ngWA;h{SKN66>?L{)?Xs*>DkL{0w58fltrhnwVxG* zUfaFQC@u+;mQT^rt-OqDWGU!YxENR2NRjLmH0X_o$N*G0DLc<*3oNgzTP9%V05ihT z_j0b5N1iD;oVhzR%GQ`J2;snaf35iB&Uu0dZXXvxW909Zh$k(O( zO0^WikN9>h|9pUaT`EO@{AWjeJBAFPJp+4*+7W&oZl4h1b@_4ljQeK<9kG4+00gC3 UrPkqm<^TWy07*qoM6N<$f~Qyv(f|Me literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Senpai_128x64/frame_23.png b/assets/dolphin/external/L1_Senpai_128x64/frame_23.png new file mode 100644 index 0000000000000000000000000000000000000000..944bdc74e99bc530de3bbf37368532a2dedaa377 GIT binary patch literal 1537 zcmV+c2LAbpP)Mu1*` zAAPD^?QR1S=z%19rX0s{;KbYFvYUYudvPkjo8we~H^->}Z;n#|-W;a_yg4oaP)b4X zS}|%j>YoKKAG7#bjp+R8wE#Gd<3K3|Gp;ii;hjquS@O9=g}PJtBTv=3e+6V2so4Wm zjP6gY!Ez5UVxr#D-)2lY((ctf!g3GLd)8wxbF%zd#fZ+Ud8#^hX-&3!fSHy?X&lFK z`^?_|ODT7s-H?>ox451M=y{On z0<_v^fqEH^=)ANT(YaYPQ3j0?T&&Umh)L<%Vp4obIFg@)dWbdJLsWR5Z)j*)yt40sH{)FMz+S1hsTV+ODyN-nqG#uMxD~&r+|~ zjAz9y3j=#Ce1M+-{=k>To8t#c92g&AM#r!sQvo#mTNHSP@B#cj3s5lei7(*;0pSDV z7fiHcWVK%%OY9bYz21(4dmzMr^qK)MOSARDNmk%k$86D-czJ}ta)$6*#ul<tzP53*|0%XKi2m(u`)?ih4*5U&nE{6yTpy%7?bGHP5XWO`b2%$ZDu!^cEz;3wY}r zvm$*CO2=FW0Pqnl;Ar|=f~eVxm<36>B7v}m-iX$<)p*oVi=H)F$Cm`H5k9y6Q9w8& z7U;dkwZigSG_KlQITPMDj(YuepZE9Z^nTMAh?kev_!M_rz%KXcp*vIfRxi*K*hrY! z9>6+h_i^w}XeKGOu`IS{D`=fpc$CR&6xM#%XA-_w@Lp(yH#)4+rhQR(gs&N+H!6=$NhN7Ei;+v%)LptnDrm08)N{^}+}6`+o#k1_Yi@@VwoO?amsHw1Ubk zCD4qcP6HoR@SdPED*Whn$-i^HRtX<4YyrSy34aGq8FdC)SCF1(1>~ku@My5g;7+Va zlSZ^7!l%OJ;6k4v{9zW*8Z63C1gmQbJszUMr&kIt{OA^AJ`3m#wi=pB+e%~@fY$Qs z)vE|z#Tou>;HnB5w|2T1JD@QD%|VgocPTT-I3tkgaD@IpKn7!(d;1KpmF145akvNI z?N)5T-xOfQzS|OflwM|aPt&GHuO)D446X!L!E3V<$qRh@+dwTr%CyDuW(YnLvkicI n1xX2{LXp+GQ5kXiRA#>bsTpnn;ED`h00000NkvXXu0mjfG)dy~ literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Senpai_128x64/frame_24.png b/assets/dolphin/external/L1_Senpai_128x64/frame_24.png new file mode 100644 index 0000000000000000000000000000000000000000..3f445593af79067b2b2ab416f92953a1ed9c4cd5 GIT binary patch literal 1414 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Senpai_128x64/frame_29.png b/assets/dolphin/external/L1_Senpai_128x64/frame_29.png new file mode 100644 index 0000000000000000000000000000000000000000..e2767bd65540a0a0a456f6e0f8938ffd9ea77722 GIT binary patch literal 1179 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 M07*qoM6N<$f`pe5KL7v# literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Senpai_128x64/frame_31.png b/assets/dolphin/external/L1_Senpai_128x64/frame_31.png new file mode 100644 index 0000000000000000000000000000000000000000..037bdc8ed6eba76a6f3431eac40d0af8ee688ee7 GIT binary patch literal 1204 zcmV;l1WWsgP)PeYpey z0OpY(-%TF{d4KMfiYZRgjHD|SwL|jd9V;rdglg3)s*aK*(zNC(M(MHdJKW2%B&X97 z84Y=I9i9O?L+}&6mjHGR*~m#!0(z0aEBK?AF92F!p7sDeL%lk}C7_2bXBhcU)y39n z;H;BCNgEb6^>kSEdlboPhj;*$C75#@9C-(hZB8k$UiPSMd-$_<5@6K6q;;e^qwN+n8};x6 zh;R~@+Yl`q(%KWA05z<9Ge_EP4Q0MBv295L%)*gIe!@k71z|Z8Ek}T8Yw!qo?m?C# zz?spjL`rZGU?F&%&K{x^92vFIDYH2CH4>ub0Lw#?Mglb2p?3wtd*O7N}VSzOXwZ40*qRNkE=JZaV<6h7RM&Q;@AXO9Gd`(xV|J-;Fx!i&PcSKekr4+8sYrGWU32?VB9-zJF)>qp*UW4!i7!7P9mBJA~Dr65Upqb-N z%GbPCC<0i+pdLu(xRdfTq_R0YDuTF$q|^5Znaik2_hZex?a`7NeJM1`{X&N=e}?l z)w{1VNT6MZ>fby9M!ih01@PY2UF!`rpvcWe0V~GiXNQ+o^xhg~R0(kAmk=!;k4^$F zrx~C^fGf;kw0JyXy4e%?{ay$VC8v!Mm6RjLQ}C2SN-0(*5P%liogFhz5{!_;moPAr zpnK!-5qr6Ej@Ec*O3M*z8p|8q?Z zjY4}%wJe}4L2G`mFmQV1YhZQedO=x&);=r*_+=Qg46e?oViL6Abp%WgiBNl#?@b)( zd=G&6t6w}Zs$j)i2h#Z-`^(p3RKdy>B>0jF-d2DHB^Pb>PAc0`Wn{JI`!dJ~ZGneI zv0fy=cMnwCCV&uGkfp-Cowkv{cMkdAXB2=tZ+wDM%&77%uloURJdS3bDcv%Iw87&n z8THmh){c^w+rZXM)vDkwRx(CgfmVO*I{4Z40UF5llKBT9%&-Nuq}UfAGl7~HofS11 z;0qV35ALUFtF^c0D?#b9eE#LW02n85)uW*G0Z5B5Bu5Bdr`D#q=daxaQqT9jz5(7* z0C#mu)e&-Yac$HbtPikac2Il#aWcYfHHC46lIno7Djc4dLFErSJgbo4$|KLRZmSXq z0A2`)pOKeARQhms5@_E~^I?oA_$cSiA1r5aN}`ng-tc79=4W>bC1%@H#aMlv1L7 z06baGOGHIWI2vd@bxD6IIm<8+E1h!gF{=MZ5XGe03*8O)9ZP7^yi&z;=DOy&b4ed6 zyH^`m>zmsirTJ)`E#;3|;10m=X!U12-dmeOF2~{ZYtDKrJp2G33hP9U{CF1t0000< KMNUMnLSTX?97I_F literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Senpai_128x64/frame_33.png b/assets/dolphin/external/L1_Senpai_128x64/frame_33.png new file mode 100644 index 0000000000000000000000000000000000000000..e3e7799db4dd44dccb311dab62120e0203c85f71 GIT binary patch literal 1669 zcmV;027394P)j%|4n`=#|$QL4&9nzu^v|~u0;=M zbhRv#G)ZxhTwTxeJj*v~t%dx#@r{vsiPCEGrTa8`o@+;~H6_?5p&4K`EozgD3?6$P zBOh+V4@>80LC&z%u~+_DG<|Ev)NC@^2@Wm4Z2ZOu#dAF)njTxw`!aG-9!KcTjWi^~ zQUgX7V%7;bkL>^pU>PWjTQ+$98~8SY234 zPGHHg9bf`R89!3kQG-Y1xb01#u^7GbxqUco$}iJR{E7hE5p;O;gH|D$LszVYwrON- z9<{mcx0n7-0RecbY|T)fITk-YCeiUa!LfG$&Wrb zf%HSiN|F0`&vi@ZD9?NCrSY$|ex-nYB4|U}$EV>OMTQZO=pz{=zEaexrIb6@*b(3n z)+$DGDIzP}D-VwdoDUl3k*wDGRRi?Abnx!T>VxVu%h%IQXgsKGdCF>k-?+dVeLy>M zQQb-_9PyDBuXXs;aN`VjG58zOs+o<5G5LK6lH9e{ubsecY#mpmM$INsS)`Xx&hxzA z2k3drDmmIG(}`vliP`opviCm| z{4Jf81@(gi@PbJ@5~V{PW}}NlRODshRr)N$aQ?Fk>PGwSFvuIudlWwEQ0eL-a|ZZ= zn{@z3>ABj#<}KFC5)ouQ+`H!Xj2Fe5xkK`q7-c?Ru z$xowQ7Fh@3hDJ`x9T*j|uCIn^tqHG^N4AR3-~`@#u80DfuDwO1>o7eoMG} zAp{AdffMHpxgeZZbfxlpr28!eQ(9=e2bpJz&~2Y7&@=HAj_y0p6W0Kims)~KcpLj4YO~m9oR*GG P00000NkvXXu0mjfIqw{B literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Senpai_128x64/frame_34.png b/assets/dolphin/external/L1_Senpai_128x64/frame_34.png new file mode 100644 index 0000000000000000000000000000000000000000..a28aac4e0f1e76750594be887fe23ec7778094db GIT binary patch literal 1767 zcmVzK!>l%Rx%kk#J$3<$ z9d}D2B~oNj@=PwJl=Q*d;&xhr56l=ATGJ2*nlIlXfwtfqjvFq)CS!@}*JgSDO-R_mDS zK@ch?g<>R-ioL2MoYXridxwshodhz2)6T(mS3=b}YJezO=kFP}S5HjeUjO{n=5e=2 zc0Yc81qX=e+3VCT2J`f>(l54`V_1<|!(Ho$LM=y#2s(|KJC31c=UMB9pDlwk9a`*= zbN-3|Y@mp85f8lrBF698?J4Q=Wn(hukk7O}-ZP8>md#NAI}0^d?!)lJNFh%06r#Iv zBy4Lk(4aGRu4T;n&Wc2g=;u;O@k%~4tTt(@lGu&j1@+pe#OGz@bFEKQ50>X!oCYEL z+~iE>xL2jbESRd}qy_jcw_u*^=pgj~E_*mIAd<7l{}%(ONdG(1nrX zs7}$b<@g#ow@5pVbJvW`6c6VW!g8ZKvjR?*juVKo;s{Rd`WzSqjf8JL;7t+8-N z;#{v5!RTE8LyPel0rYr*C-Vep)XqLow}>2zJB@IlQOCObP1^H|rlf>N3i0G?^&!%% zvQ!(#ihM1=_M>{c-wNM{mEWbpl!!lvfb(UMvIe1&-}`&zhm`iGyj>R=TYsB%Vt zEW9OxT*2rPp%l;yudIti3U8biU0OrE)#qx4?8Wh44Ds^jiiKteTC3{zi2Re-?f`OE z$^YfMT{vlkt2n`t2!NrLae^H?KZ;d`x`GoNO97UFSLRoAu+s)$CGSqma)7@ljk67S zOL-C_$GM}J;Q)W6l>V78&NlSa<2#IV!U^1?V6r%Wrj&k9DgBjF`t8)c{3sX^Z8^eS zt=lentq$;S&iRM&{-xj@Qpx8PyKf{{%L}J~pG4(H4VkZO_=+pA@Cxk)bu~d$|2_cQ z8HUkc-y4EhT@vE>s|n(h14L?aqRch6(IB*i-WBaEW6zETb*u*mh_d1D5dKH7I&G|u z1~Q_DH~_o#y1FAsovk*#w|Fymq9zZGz2eCH*RO_IgT4~!la6}wfF%k~Kkcq$R!VsI z3n7uRh}Ebk^Cs$`$<^kfm3>}0SkW_2w$9#oR`m5EnPsyl4iNDu?Mob|c3q%KxfIxS zbcMF7ty@!Tz1f#+Ob#GJi+5q;-R19af@mHe)kiM=3`e(|K+4nlM4SNEhS}`N0W6|% zHR9K*Pju%n^3@t)S|e%w?2XYI37CF4=jY!Gz&yj8fGN8I-ehLz;4PXAn@p}23B3pz z)yEsVRSveNza1Qz$ja~@b+VBKMkBJZt-Ko1Z$ux@3A8cT&#ZmL4Idyh;TBsRfu}s` z&?B#f?6QASS8Kdcjv&DzwKp~+=m_&5sMP?hRFMZVt$A^GHNn92_ex-inT|YTEwCC; z|62Hr@WwoFwiB$30+#+-UY?1l4WvP(?QjEPpNBF~Hk1Q+Lak~+OC7J`1fKpf+KtGu z+E}Ro^F2B#zGFl-^s8Z3enXZ#vsAo-6SVYMm9i|p)$)!J&d_C$7E0tU$GjNL`BB;U z^GeKg;)<1|HYH9HBY3zBlA*#HkP#yci&_fkLDUI$MgiG3rPxi~2-#z)AhIq<((&0% z%X64Zw30&hNAjUfuE#O1<^&@;j5+~JyXX-0Idf3i?k*c^iZFvofmr%gL0+D0bp)2f zM%q@a(y^X7oB3NuI|pTY%$@I>iX-)555Sma>$waFi2a{qp&Y=uqF~ zlpep+-^vBNKG9s=6K$+i&`Sb)9H7SuS!A$M2)?)G8(E7w?tf9NempQLiU0rr002ov JPDHLkV1lcdZI%E4 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Senpai_128x64/frame_35.png b/assets/dolphin/external/L1_Senpai_128x64/frame_35.png new file mode 100644 index 0000000000000000000000000000000000000000..04f8c1a7f30fbdc51cb1c9d7ba2d0a6b5e7f87e5 GIT binary patch literal 1832 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 LNkvXXu0mjfzS&Re literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Senpai_128x64/frame_5.png b/assets/dolphin/external/L1_Senpai_128x64/frame_5.png new file mode 100644 index 0000000000000000000000000000000000000000..7a111afd03b4a20d23675dadfdf070cf0be65452 GIT binary patch literal 1869 zcmV-T2eSByP)q4TA|+B}Q9YjHJkRqu4mr_tw`xSrca#v!!aQB}Jed#Ag#>&<6N#i&nI8dg>Sy~c0)F!vwcnnrKN&2=XpjR!mx19z?s6xtC`9Gz3xRt&Sy^?`i#-Xb2hX;UOPH$ zPtVv$amLHNFvUa!^qSqEN{pQ3S-pF!iOlgl`#w?UEZ0N?F*?PJm#+$&9;%%wa&{KT zSe~5h)JJoRI8Ot;&XFRh-7bI$`&Ddz1$IRu)|hAP*KERT2>py;=1 zu9c+^4xD#`~;>n*S%I;qTY4qk7Pe~2$qC(HHi#V=2ZP7eEkn+B9USa&vyzDu3?` zu=Q~^&j?rpM9#P4HgSe9vU_*?cLTG$xoV@MlQ&vMRbrHP={9Cv+Z_Ofd`tdz;Vz?B ztP`arMpkJG@L)rn#|f(Ce>ZppHQLMDQb1PzRLMl_{SH(*VGI9E3SH#@e=nM63wp0n zD-CsbBwFDBe~8FG3+CB^-HblN-A<5R1%u7`Q$&6jk-tRbw|A@288G(~QQs<^?%2m( z2l)3mjvvA2zYNfIb@c-&MyK8TNvD9HCB_eyT)aD?=M)|)pYb=HP$^dl=m6Hh-rc<( zQlQ%fZF5#_663@>9Npf9?BoTP6yQCYL~T1m*35M$J1>(4X63QEXt|XS!(Ji)Bj_%& zGIQPetH7?MYJEDQQ#XO$v-{nS;BCezd1KdLdbINr;olo@7?x%XIO4HFa zQvsBEPZ;G<#r^Jg3Jk`Hv1WBZ+O@SMv-XDtYaY3;$bud_K;olV|8UB z2&+d{UG=O15p;|2VNkCD(6wqiNzsY&=Gu5KMQ3tVx zMowxS7!g_Ot6@=TVp;OaQt=&}fE{y36o~ekC6Ux0gm|GupK_ZQ*__YHwy!I((uoHdJKn)Ee1_MK1-6Aaeqw$LRbcV;Q*>QGhudmZaM@_^)D7F7ZkVONJt4 z+FT=!;VpKQXh!po_GgLZDj@qrRaw(f_j}sOkPu`rEzp*JO;E%g+Up)@d2G&QWO$pl z6DU9H_J)Y0_t>z_`^+{f+TF zEw=Q_7J?K;ij1H)3fOV8r!^v6sN7;<4eIsrQks389pL`}5S7jm`a^e200000NkvXX Hu0mjfzsZOo literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Senpai_128x64/frame_6.png b/assets/dolphin/external/L1_Senpai_128x64/frame_6.png new file mode 100644 index 0000000000000000000000000000000000000000..318c7eca0a6779c44ac6a7e40e50333a19f5dbb7 GIT binary patch literal 1893 zcmV-r2b%baP)Li{6-5le3coMyz)L{(k^m0R@u^ien z&2dow+fmlxNu;0D$cA$#LGU7rlwLc-UeR}=m11E z7eMWv!7N!CZvL21H3gROE=H(QdWcyM4SqJ?u5s0 zIL>rJZIAh9bu6)afHBZJ{TfrZq67aNyp+s24P@Oi7qI#9%bQGGL& zme!WtU#|Z{+kEYGk9NM8>;k;`Pv{&O98aJb>)`lV~+BfOEPyRuh zfIpKzd*h{|vLEtRDHkoU12ZRoz)C~fUZFmb=bBS#<2;>-#Oq*hapSW^;E)H(1;`VG zEmb+!~i?Q#K;u)m7!ci{*JiW(D*L$K`oaT6#zL>2QCt}Lh< zv&M@$q{p3R^_tzww-lh|32&f8CQ^K3PS0XhDwEDtiIRN$>z6`h3E`DvSwwlnRnVzl zmn@CQRk_o?@3;({HNM3OWF)X;cczWbsAI`O_aUhLDuC|-5HiL`1kf|EH)luwo?+v> zP3jhz$Kp;i7gDp1rT#_`M>fA`N^)oy63sotZUgfgZL@7LLRCP(9K~dX5~p1o{1n=Fgiyl1ytdcl#xi`$;s%F z4fWQTs~NJ2)4v<&<;@ivjV`o~EK84`&%)sjz)zL-qUb1`X@tsJB{A;6DG>m@$k3jx zm^X`6j*`(w@}n-DPD=rnfmh~N>0oDv;)+3MZvO_xoLvs^caxmMAm37+iHJcv(<6^2 z9S-mZ5&hF3=P*?2@gtn%NGBL@uY$qm{7FQ=6VYEp^xL<-*+WEh_4|m7Zq|GqX*E{WA8p_Jdj_$_=mK%^!I%A9(PsLxOcQk-yV3ZACDg4XZ#LSrL zI##ER)zKhb2BNoKS91gjuCr{ftRWBu%X(__Nn@|LGJpLmVPgX8&DT{3(}28A*4Vn$ z?`(Ki-~2iHdGx8`?OzCqltpOLv+`!NjMJW3WuI3L4pi2O^!DUgk;{u@md!pmK;(4r ze%vGZpVg-fE&6JwTZ0#^EE1@V*Vc_9hRFdq0r6hgL_*h9(DCwQ$mwu&%L({=+L(wF zNc$k0eK>$cG_Fqkh-^bgPYxnq*{UHMNyleTMl}*3{kCoU{9b^}Gsp>$vMVrqx*|Gy zo5sT?ALGrH(ZSNklbxMM_J`jNjx1zlc#k^S%mzy%BH316jp#R{kLLuM46Ra-{RbYh zLBcByQU4iu%1a%h;Xe9I_CtTTu9m!6j=+H;wI>@9bP4k!C~E*zs*IClohVJ_EOF}~ zqDX(OgsbWh)w3h&%4*<&1b2}=7?=*BC?^chFSSdvgFQE@hXuZI%Y)_7;+J; zy^dBUp_f5gDB-7E=EZ2vkIpTBj-aYTrxOo!asdQH5+it30Wwrr0ueEiVNpu~6-1rD z3xBg!fi`x`m(kBcG!;blMUwQ7w%xE2nk4#MJ4G|63so6fKcsJD914@3KH2qay7A|h z{u+=&AP438y)J?@!JZ*CqeSv#))At{iJU@dEV-@@Sb11O=D9@>qk23u9w`N6!23r| zaYSBqyvB$-2^>#5)ktXdu__eww+8bj(Ay9xT-#WSSIjW*nS3kB>q_>n@u|q+l6{at zIUhq2SmQtYzT5v?eD!o`?OkP3x13p7mO(vZS4km%Cdn>p*Y*s)4wmJe$1XEKQDVjb zv#cBCMh}^?6tQqQPlWaJc1w=M3t;3Rvj58%OUcNXNFJ(2n@m*@$+tu`2dJhpE=!v` fYHVvSEXVi{Ub^TjTGP(Q00000NkvXXu0mjfD1DE3 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Senpai_128x64/frame_7.png b/assets/dolphin/external/L1_Senpai_128x64/frame_7.png new file mode 100644 index 0000000000000000000000000000000000000000..b56b995dc61244f395c0ab2713e32e00b8909c94 GIT binary patch literal 1835 zcmV+`2h{k9P)#P5NWd)xPY-?okReW&qvVB0n#qL*toel0wR2r7SEzwvA1akc z0#Fnfse$M{O2~Uw+Z*R&;~aqKr_wb(Kt!sMcm{NHjO*b^^y;ZY8J72QMWVhO+C9y1 zQ2X0Z`tT&uJvFl6+(;0-$RfwF+G~Ap)bT_YqYEyTBaK1p%iAIEAAN6y_X8RLk8r(2s4D0G9W9tY6FD6>@2X{yQzK$Cm-TTuhd3p5ocMy(T$u;zszo9#31;*Rdl40it<*BlUX++ zqKCHlns<-ueDR~DqSeMK4Ld7LvT(d2R>)LtrBe$S*8Yq5|)Rxqo4 zIk!fPSHN>FDWfH;9Or3SNQ8xJ3ibp>RvoDf(B~{Va(>j~(7lK}nz5nv(XJzBduqx? zf=8@e3saGZfIdeTs1p@Wo|QXWO?ZZ<>Gz2md%4CUh?W;etbA42)X;5Ama#{Hl%>t% zdA&6@k#ZWSHMRsn*X095!hRRqUxA|{QB;|zAA+Udx1B)QAS#$AaOHtkqcbFH^F-#X z5YmCl^Sq>h2m?nQB{GrX?3_M>sO(*-Od3}uO7i|+FNJdD@V{E;ETTN(DnQY1)mSs* zAR<@gPW!&&GH_P;7AKIAz#4mH+E^KNtg)m=%4fY=tRPZs5B95z~gEdYj@>#H|rV892hEyhqvR>_B z;7{sspr{awSL>o{1n=P&!8_1ytdcl#xi`jZ;C? zpsby|t0}UI(|;T2<;_(KZAFqRMwz2l>z*uM8AFF$sQ^%XJ2MnA=4S_m^FZZwr%@n-2WJ$?dr4NV>rVI!YSa#j@P%5H$PSJ z1J*f(hR!qoA(K1<=>{|asbFpIo_A@GtsM~~KElxDUdWNW;LQLcH8~LO{CZ{*(M3P1 zBIu3=W9%0Lh*` z%I4sPn)RSe+m#B5D7-5rJT1jqXZMmMN*rx4R=s}2`N9m4eA+c4`vu#`UIy3W8wZG3 znD!D!#p_leP^E;S$?okDbU@02PEEaNhhDNVIRFne-hoZ@9k@U4YCp+VSZThTuJ1XULZ_ zpgcRnEv&qc#^mj-{TWXd*%$sg*b{V2)`h5%RTkeOS8{C6OFNywGXi-H$rIr-4|yRX zh}CD-Fc4|cAc@WDau1PP$-bizBr1njU1@zaBi3XeFgJoS3g8p+&wN8BTVX^Ml`)zu zko-|4t(u=hGn$I=Z`+Tp;4ZRLcZ_#@3>sbU^fMFTW0! z1jI`Xd=fOcmO5sQKpTIyDnR!=xyHOzRHK$uG%`|Yz$j&eei~+#CXywuEEQLlKbj|0 zJv1{;R}`?;I!YqLKMvK(AU>H@GF73KKbb|95ACt|l|~$aR%vArscaBl4ukl@dhAqs zlw8zmZbq;oL6u|7Mif34QJyeo#vVXx9N|jxGrtD^RV>OST1kPd&U)CYrAHN3WyDzp zRoVE;ssZT-aV6eX>h>uILz5td@d;%r7!z6CVSLV@8Y-=R)-xOlO-4(p(Q3gO_n~>W z7s4w_km;d|J+JWQ4n)$0<}|X6X3XO+a!>?wComGuWD3y8Vh01y7=bre2VYqUi{NoS zh9WRp`_abyck$I+tzBQWI7fMG6j35LJC2o#5pRnSWOy7b!?njQGst>oc0BDKV!&D- z4_*ZFWQxdP)%{wTjhxmh$zM2t<@G#-%W&f5GmS0JqN6Ivz#?2xV?&S%)!N8=Su#GZ Z{R`QooeYqW*75)V002ovPDHLkV1hqFYH$Dm literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Senpai_128x64/frame_8.png b/assets/dolphin/external/L1_Senpai_128x64/frame_8.png new file mode 100644 index 0000000000000000000000000000000000000000..6c4b8757060ecd792db21380ce0c77ab5129d93c GIT binary patch literal 1772 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 RW^(`l002ovPDHLkV1i{(Q6&HX literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Senpai_128x64/meta.txt b/assets/dolphin/external/L1_Senpai_128x64/meta.txt new file mode 100644 index 00000000..f68f0b56 --- /dev/null +++ b/assets/dolphin/external/L1_Senpai_128x64/meta.txt @@ -0,0 +1,23 @@ +Filetype: Flipper Animation +Version: 1 + +Width: 128 +Height: 64 +Passive frames: 16 +Active frames: 22 +Frames order: 0 1 2 3 4 5 6 7 8 9 10 11 0 12 13 14 0 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 +Active cycles: 1 +Frame rate: 2 +Duration: 3600 +Active cooldown: 7 + +Bubble slots: 1 + +Slot: 0 +X: 5 +Y: 29 +Text: SENPAI !!! +AlignH: Right +AlignV: Center +StartFrame: 28 +EndFrame: 31 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_0.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_0.png deleted file mode 100644 index 340ceb47f99ee859720fa8cd89aa51f234799bca..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1656 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 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_10.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_10.png deleted file mode 100644 index 890ea5d70da8bab182cd7d25c234a2e2a1e9d610..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1494 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 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_13.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_13.png deleted file mode 100644 index 6eda42b5d814555b5e795572face69b78fcfb0fd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1585 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 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_15.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_15.png deleted file mode 100644 index 554a177a93d8203134179860bc35ac3899407026..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1771 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 b4o3I~m)4F_96F#z00000NkvXXu0mjf;*=8M diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_17.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_17.png deleted file mode 100644 index 971e8f55b5c36b7f5e24c68d4adcd62512986417..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1503 zcmV<51t9u~P)jd3cN7lpBDK~TTc%nL2xyv)oMky*1pDDh#Ld! zR{rk!BY4vG;xfQb!#6O@Nq83~QyCz`2v3TLD)bnChaUehz;#`*#NiD*`;#!jHh&m^ z4rG;CPu+dAd@@7f+IrV`wbojXQ2~4=(8l~SNG*mLAR76+%s34d@W~Om10w;I$^oqU ztbxBQa0XI2fK{*U^W4y1!=Hr<02;vGsO6FFJ$Fxjs589F8NLEP9|l>G?mg2Ik$pZ1 zt7JPN7rNyD0KbCGW`dKQ;K`r?{Nfc@Im!g1r-f2C2O@&zC9Y z;bm3QD9oG=vZo2@HfzSc0j`J6_vtJX{xlHao+C*0GfxvEOt9*1;@wUVCIfKr905R% zY_DE>rX^zJGYBd-A5VII^3hhq1?)?qsm{)+A@i3H}G--ZN45%iSV5a0B|?3wl`;@hAO^i(HWT| z^&{(AYU~{h5TV3~@o9dL%>}Xy;I;E|3@)Rzhg03`EdRNhpM+?{^T^`w@cm3B(K#jK z@n@}nWP5rX1MtOD1<-n}AGXInQrp=|LK%1&HLA4c6O7;5f*gQCiVXfgLaMjMrTsFI zEy<$N#{mYVz=M>0S(!nO7u8$_Id_KO&ux0m`$bD!W0 z+ZiBA%cEmUiqDJ@VUFFApDELF&vbyue%7-MSp73j5SeFpq|YvweHfJ80{A!@ysaUM z9{N17)bwC7Ky)wUii-4+q9!s=6jsE0b7^bYZN#3kSN;pw4?{c()jZ8o@(h~JjF*w& z9hm@S)z)J5zg+}zkBZcLeIkk5ccI4KoVU_MTph_T}DB6~o(RoZ=Kl;5w4t_c-f6I$#!buo3ha zpDSZFs1snh(&*9}dm{c;wgT-@C*TfY)zLP5QPy0Mc6?k3BTJ9&u|0cv<+xtC-2o&e zWJS^p9dV{g_UbiuMApjr(+m)aI?aF)29U;z&gIE9I#*_#462!98Ut_x;$#`cWF5e2 z%NNlrmp4x@X6GiA#&1oy2e8oE{Y#nI{!-uT(tiNL+i1zqh+LIGS$-nyivQ$2Qw}A5 z*&M*y*QIBxFI@;#npLm0iM;br%>ddQ05wS`j#HHghQ1ZdDVGao)obI8)bsgAQDGnp ze5=BDEB1J=41X=03ZzBeifGbgLpiuy(Z00(3P<2HjwPr0*=6c?0owkH0`CBilxPwr zIcr5y8G*$J{CRdc<@*3!fisw4bj(Qq*^VI1vnukt7$CC{X<>*R+e8m`F~UkGD06@a zrP8*v{&cL8Q_2yhmz7(9XIO2|>k(mO3)q#cX0akC_zyu3=q+V8lFR@A002ovPDHLk FV1g}Xz1si) diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_18.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_18.png deleted file mode 100644 index 4026dc37364f9ff38c20c0ece6b94777fa4c8810..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1663 zcmV-_27vjAP)Fbt%F{{Jtl4+Xaj!(*t+$7X=QYg-a29wSO= zdJs`-tr6om4*KHv;3b_;MATX%{p^l-!W?$s#Toy!%zxT^dVnOs(UMlX4cS`zI&ULB z7~pK1s8$|YR!U)^E zF@PK>x>h6g?5*XK8H(rDJIAB7)_R;ulrw=h=9fX{VweHY%-7dS&`1TJoRMc>B%w+* zfcM^dj*Rui`6$%@-h0c@+*UjK>-cNoAfiU}PRrV<(S4NZcXjq%_V6?CM`kMjUAcO= z^Oka#PFD!Dj~al8-0ZgQWqYq3q{syW;i-6A%B`bd=~#pbxGneX*&SFx^u>CF-vyvs zH|?wn^=ECrfv^^@=%qZtL(T)x%;!GdmQsFZf@Mm5fBTsP{B0mnp0!9;duIlWRXd7Z z1?d$EI~1)o`cv&dl-CFz&;z3tneWNd3^pqJF3x9^7~rFGVHc1+iwuXfiTYJ>Hz)R@ zwHDU^$N>7p4>ca@e12c~F2SoU(EHy|7w{a10zeL?w}jH5*-2tmY5lj=M(d#4SMQ&S zDFMLHfq-N-I;I}i+I{FY(RUl34o_wRlNm>9fDG2LvBwe`l@rJ6F?G*8?hfnFj%YFi zL_&!^mx7GOWu{wgkrT>l=11oocY|ie5(A(obqG8ruf>j6Tk8bbP0g?03-9Yb^j;Kao)bconzR&VpQ{dklfFfozLp{zz?*)dZfr9*kx;ZE z7@x;G&3Hi#;MF9IUC!4w*E%gd36z)}XOd#kjGfIG)tUndmYvQgqW`}Q^bFjKY@M71 zgju}I)4FAuKa)|k%~v~L>i;&Ef9?FqA_n!^tPX(cm^+t^UZ+`@k@@nOY#S~CmQviA zKat`Hdio#7`%y3osdF`BcLV{;@oa=FZ+eW(PA~2#KiXKCQG0`+*ZHhlFM2X+HzTl~ z@+$S0^RR5`CCNunTl{aWPu?K9kER)~y_SPGG>hhpM!oSWPKo;UjytkkAkQQFb#DVJ zPK_e58U&s!6pge$tg)mXa_!=_DD6i&@pw{BM!#n}cZTzcDE?uHN8Oo0EdxhmWjc8BP=-NumE*~A z?{G!zU?Kb`VHq7~5ob}R1QHNd8{`=>hYT|KuHfXz`KYZlNFy zHA~6S=wXrGK4)aQoM&cdOLyI`YYE64X;D8@sct)xvAyGX0g3eAxhzDwS&5*7WoZo{ zm68V0?{)y0Xv8`ZqgYrG1X4aq`+8219o(9rX9@6tP!PjgE)W@BaTB3e9-@zw(L?7_ zzY+Oz9BX;7?w9>Iw3-XBERPs2P!jB0`R?<|Hn0*1S-TNA6^E{L{*0iv#^W&~=cCDx+Op>y0G;@#f$l=* zs{+9*2t8RI8+Z)aTJgV%jf6+-dJSatzapKF&NCaYXI!)n_zPhwl15tgi)R1;002ov JPDHLkV1gE38lnII diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_19.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_19.png deleted file mode 100644 index b8cfa50ce63ac29e44091e6a3257d7fa093d7e81..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1661 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 JPDHLkV1f!6*p~nR diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_21.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_21.png deleted file mode 100644 index d0e44937c1dc542699af72070b551bd9e9017462..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1542 zcmV+h2Ko7kP)bG@H6lRj=2)NJ9B%jhyW{`3I8MgXhfa5rPjf1f{ZT=my z;jXB#%?AU}g{BU!9f)H(ndQYAgn9t$KJECA;|Mts$4BSAsN(>D0q{xPJacF7)x{l*!e!6K z?!aB1;XCj}vlg#B(`WDL6=4|=Gd*%nu(Olj<@_r=o&4$n0Q4FjWdiBGHs`No+!a6* z_?zDV_zZ-urvqG93J5COgEv@ep!PGr3y?Z{ZJfpoJTPYSX5c~kqJ}T)ChzREtJNcD z;6b7tD)GMzV@T`q{ry$AzYPSK)3wF~GlrN!V~z+5?0V9;(-XuP!)5?!0&cK_3A~1? zo)1`M##`pRxQp`~<#{OZ1*jxh#&SL&+wseyuLoc9Z*s9us>?<4J< zXgrG_-&yAXQCRb>GX`m%<$x9S-y`=)5CfnK+=$#^8tY^pJ3YtJl0Ke$+L*gSvip(% zpwNsbJq37G$$mG%$9fp=$<7C0CxKn4&~v7-)DD_7c@Y_W&Yj@u$ySwsYL_0FYn?Vm zVKLGjg1~b;|6x0C07M(a*@5Gnyyos~YX zbn6jlOaqYrk!n_MSDC0}xR?F`0t{eA(aH5ulkI6TD_1JFqY@n?9lNPpb7Ib zK*eYg$9v^WjTYFa=V_+f*^wY9_W;$b&&=n20sr&Ji)8lfzV#kJuHkq+)A;~R2SF>c zN*fcAHkIJ6~Q)C@)hFbHo>vjhN|D5YZkz#Cux&&y{vNJfiEN|JcYG|ZZpY3dKFj#2&MUZBo)yS3> z)UO&0SG{`C_I0piNHjElewDI;K*pjvUcq_Uo*?RcfF=g8>Wx;LvzfaJT;D4=HR61_ z2ZSdeS{Q)SDZeKL$61UpGs@i1mclJ_uYJDuvq_c61f6f{fzFMujRm7;<*$5(3_A1~=7$(`X0MzHkMSd{MB zW3T1^8AO!F>LA^@`TRzKif zOS(_dz5S@pne}JR=ek(-I6VLmQZtxs+GgxRv2y#F@{ph14Vke#N4usjBJV8R|W%Ry$03c>`-nor4 z8G*mgI6o2$w+R5mj!vG-+Q|sshLFznIA8s|D*$#G>kM42N3e8?K0UQFz>@=E7c@qo s@MO8wEaGRUd>i<}2Df3xmfk+#A2tH&C4KmzjQ{`u07*qoM6N<$f}r2zivR!s diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_22.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_22.png deleted file mode 100644 index fd800b8b936fe6d4b2dad012c12f0ddb54903236..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1736 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 Tw_x!v00000NkvXXu0mjf>uC`M diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_24.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_24.png deleted file mode 100644 index 05c5e969d7c10cb70d949d7d28c0fdf9b2c51c1e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1628 zcmV-i2BZ0jP)e#l8e)t}|xbcaIT5BZB?uaMoumZ0h@lT8Vr|DBcBnYbg~R+*)g`VpJlX31nZg{~v?Q#V`XzBVS&T(`aQ}z$ZuK85jwu zR1RR(cOk6$9}8R$sT{zn_Z+8R``-`Oz(z!kh<4GLxwp#ixU;y+9)1S?UItl_?%ktu zUKT;$DO@2xSY`Z6=qhsnqW^-8FhR%NqW>Gf1NAK{h&~;GM|Sq=A(B7Hwm*#HgQcx^ z0ntd-n89O+ECv3YU_RbfC1-?L3=mm1j)Jy)eY0bQ-v$!Z_WEAVP+^0kUbM_`ggfmZ zT5CB6=orZnWIx%%p-R+dx47SevIBHP4}p*aL@3xhYw(O!enIjjPS9GTSLH!@n#WK0 z&}m=A_-LFibj9%U8SDNE`}3pGbu^Pegvk4XK7ePaXryKH1bH}xo<;ZH68Rh{2k@em z=yPIcXW6J9&naYNTWkJVyOUKwPW~p!t>vhF=ExTr1PL02H@3I*MDv%sgX{4zC&a!P zWbM&M4|FW2fr1&6E+e_(fDz`%o@`zXk=@vC7Z5$~_v*CvT(JJXte559xYd(IO+Kx! zJ2*k)aE|M?%K@w-+$bW+AIc9Z)X%S@he{(SoM$2+2iLi2q(M%tWBl8JMUQ5mqSo1TAs*$sd_~HZdKTD{L0=#ct=RvYgFGEy}l=Tc< zjeNvFNRA3dBiv&|I1kOJjYd#pN$}cQGIUruN7UmZ<42swVj7SAIF2xO#tB}YfHP5v zjtDF9zCGj1|09v@)m!&mmRW20Oa?HMz{+}tsQYNhXtNZqPM&2vuU}r@Wa30W3; z8;I6otxc5ry#6xJddq}tUzx;g4xrVK$Wf8oTB?YG=KxwXXda$%f{O7o2A9uNBG+qI zVHEDTD~ylkqqP?Mi5RrbO3%`OoCO)i6PUM+7(L=xE7#x(U03ClbA$r3w+($FNIHQW zKz6FJf%ObmHUn+qGR_oQVm{WSrm%SFW>V z11kXK{Dwagh6b}HziJrEvEd(MW&fdaFuHWX$R=a6V%|kROr#OI|IDYR5dR0h$u3&6L z_sGzhpeubf%tCAX{ke38Rr$dTbbW}EqUnt2}eC;Q?snPD{p z>;wRjuSJWcAVS38M>E1~Wah?W)on|>4n7)ESnWOoYf`K>vrt(cqRR)WFUd%>|EbtB z5%F47Z&AFq_O8--#*XI*;}a47W{}n4s_k3U5jbYFJrW}cqt$LmCbR@ZjC=)0SV`wI z5!ET;B>cIG{-IwAy_zFjC89@xj4kgg#?LrI_(P#xsF5}^@D$^7pu?Qm#-DN!lyL;F z-4ig|=`(A7G#PetsmeAms`x5aI2%Vff|d|!V`*%l?T}h)V;>2NwtKoG%+^!(SLrvJ a2mAw+lNpPBDxqfp0000(^U_4#5#o7iWbZG?H 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 RwzmKP002ovPDHLkV1h+$`q%&f diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_29.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_29.png deleted file mode 100644 index c6caf88d94b0d9536098767bffc4c66c4b9dd2a3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 812 zcmV+{1JnG8P)?aEn`=V06RJ#06u%2);$!TqlGheHXR&I21tN1jb6=91hC-v zo+yBAPcvtl0$LG4iBuHWEC&0GB!1olSRogEyf)evfZhoDlLcrsVian7HICmc3$Vfr zXK9S2M;Z<_8Zg4lnW}9J4*@%Zi+@;UvBY>WKxa2IVJo>49hn{pIocVJq{DNH~d&cdy39L27_9Y z@YYT&sUlykKvvz0APp~AC>W{7Y*A{fDFBHxZf6n9F3HQsPF)0u>?nd6v%W++c2xmL zoc+7PHP&h)0Ki^RfFr#IP%Z%HJ&yv=68<&xQ8FF@jN<|@jtjszj{@+u)dt;%L0Z99 zHd$j^0n`ps{CN4xmI7#{u-1>4Q?}a#Vw}3uo5WE>M}V4hddi)8uvTM`BTfLlWlxk9 z#MI8m3YQZx0>Ey}BvlY&z7xUS*PQ0h#4YIrQ3zfg%HKFsiV)ZZpmqJ#D%u>@EP&hi zQ7HS(or$msK&$fs=)*Rr2)26<;NV~pfacjh3$7^O5x_Vu06}m>0T+OAmJ7hwe_410 zFpdjA7_0XIE&$`W01OjCZ~<7y1z?nzjky2}lBVM9^#IN~N<}DQO2V8rBUMDL+DR9H qB5J?()q3U$7l1-2S8d+}8s`u2Q7b?iK{5mY0000%G@RH6aA}XbjzIR33VGc9!;*7sr=HG2TEr=w+Rw}J#8?u!0IBz1p z7+|&WSC1dTotBp>1MCei;FvSvDoh3$VB5BjkB2*D#3-~le}x@C7+~ABJ#w`&^4=99 z`ND!4&8O5#wQi^I#*QLiFjdTK1ZP zfa9%pXMdacr2yTFKq4Z|1d-Z+Xl&v5OE^QIy;%{WHkw9z22u{{_v=_?1L@nhen9j| z=bnGK6^H!HG?WWn>1 z>gjy#%UFT5xA3V7t8Z&4|GeO-ct!S+$)) z>vskjCa{(VeYR1nF|6-*V*+c*4gV(40%C&Nz_OiJpjkk&&^C6Ye+_qF0(8*#_Burn ztpj9xuU)jiY->3ku?{-p@Pg>7g<)hKX$HtFH2~^>qJ1u04NJ8hVOJ+wie2rY!f9y!BQpQX z`CH;NFv0*{AM{*Lv=u13MrEuVV`VUCM4WH258pfVEx?o{iq90h*j1 zK;N?ruN9jOIj7l~yn;D+u827#K7JMKIovaB>vM?_)L(-P1itpr^J-^DgTccab+9u0 zXlMs9B6?(kO)~=j`wUK->3kxw6Uc@@owI_&3aDkrf;X;3eYHMF4)v@xo zBLZL-W*8PJ-y+L;F44Mv&kBSQ?8S0MIG<>60PI4IV$h=h-ar^t7@nCCBJI0$IwAme z;RETsR#=Xh#u*qP8ziz{i{6I@z&^-8IzP(!))*@oL4R*?ekM4k1b|e8=vX=*i4}|x zEeK`bnzOwC=>9KatVtlqnt;F0$mmvB`py_9;{2#i=yiL@K7co1rofXpi}P0kF@l8m x%B{b9tT2oLv|<@Xc<>2}5#;>{+;v|B#vk{1sswN{smTBU002ovPDHLkV1h3f3a9`8 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_30.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_30.png deleted file mode 100644 index 8710e1a0a96e97627d7f0938515217a35defa493..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 536 zcmeAS@N?(olHy`uVBq!ia0vp^4M6O`!3HERU8}DLQk(@Ik;M!Q+`=Ht$S`Y;1Oo%( zDNh&2kcv5PXWi~QtiZ#Xc=iAP)c3-nBBkcM7RL(G?yZ)pws;bD_Ka6j*4oY9H)hSh z$r^L1k$G|)3fnD0Scemq{ls_4=-(5G6>5ajkV|uSO zR9|r1$o$d!KksJV{qAxZ?YO@+F%7cq|2rp|%GFEP_I`XJxZ(GQ+qZ14-?(?F_Va!@ z8?^)P4(Ho^J@H*y(8lAxZG%<1;GU%wE(}xe-tVaR`1xPM;h*()SOcW%OzsI4uviFc z?p@BZj%ma1#~&s&utZ4kbKYS|_WHoUJvTVg<}8D!Pglo`ylFvNzjmy>`|2M*fb&SbRsVP^tleqWl& z5Vv&u=6$AiuS;`<8r*BEUtiz!Y}?Kw*BL9W#3%og+TQbUHG|AQ+igiJ<~o-$@cozk z#FOQ`g6qITd;eUHcIJW~=gUl5<}1&?^YFW}(z1>pKc-)M#2`~Q-_ErlgL#F&Wz$-L zE~XDJ-rv8cd61W3bKiqx2D!hFXSzRNYxr%>(EqRSsVAgBp9kDNL6Br*LY(r+3NZkcv5P=bX+vY{0`Z-Sz+f=~tZ3qOI3bHq_YK zGJe((W9VaTNM?Kx!C*luGeSja-n>J)Ae{+~9RXMGp9e7?G@QDsuPV0S)v`DHJ+&RO z-7X_r&4Qg+o&y}`a@-h_B>^2Rh9|=oT}B26!PR2 zN4hnf3v0P*xaEQM%kK>CC5qqH)t*&f`*-dprk=S8VxQi4Em^}bckhMm--R{x4+I{) zZ0%Ud7o diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_33.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_33.png deleted file mode 100644 index 94b79d2ed9aac30fb2e73993906569b17f80079f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 897 zcmV-{1AhF8P)cfKZZMOCo}i?TbIa*qCpZXK-ys{c{8fAxT&PwBKpa{;3xIuUfjSa9;#ss3kyp#qCG0HA!nr2mYzGx=lVn za0kHe;j87dIf8_cB&-AAnM#SEcR%-n5`{5;?@B)L6s!aVz(gaFB&c#KoZo^4E^3`RRd^5;4rKt?gXHn zNmPM|*XA8H0yUNhxDtT0>TdMz#nri$Bj9n~unTY_0O?H91idE&%bD*ad_QyiTftNy zM^2zE4tqkFae~N&-zUSD7JgYCZeYn0Ag$Khy;R~QZF|qvw&^W5t*=`PK!8m(0GC*c^`5KtrR_&s z%GTd)H9)8$P}-ilheNxiOb4(YL}x3=F>;VqL_hh%xDY@?o6M6oe2WXX`ZO?7b=|g+ zQO4WDl>;=SHGr3)b0Y9E#vaFyf+>0*z~FmMBUuJKj{A#C0RZ&>8%S@qNvzl+_}U_n z+TmD@#R9hiXy8E292(68-|{%xvcFke)&O5dpY2=pDY<`Dm`>L+?i~QY{_sUQ3wO-Y z)2E6d1kkV!5+{V6Njd2N?w0_7eaX1(oL_>ggP_yAmVAH|z<=Cux~Cz80CvyFmqhRn X`cZE4+77BK00000NkvXXu0mjf+t!%6 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_34.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_34.png deleted file mode 100644 index ce0e210f4f2202902eb5e63bb6ea0de06f4d7e92..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1490 zcmV;@1ugoCP)(^Xn1&= zKJWX!<3sQ~&(Znj)@0}XgsuH|djVcVo$v{p-vR9j@H|iI1fEulu4}LWCqUL=Rd%vi z6=X+Ex|84sZIH&z$mVt5tcmx1Lu-AWr)@}r6JTODf$9v;Gf|RsB|-Zf;08EHMV-fy zq~l@uBfw#FBf${@yzhHe{vva0_oAMm6A6wgz%3$pH{7$fZzI7G0??AKV={CD9Y}CA z1ax>{2`eeBbs`CTyWep<1au^U=1^(9XcfCfglknmmoPw2{#vik@26uD(?M zYGNt^`p`U;9|)lvDQ&D3SF4pGy9FH-Y$E#%$EyJE^@?O`stW2=Zzp8Q*y3G)=jF?; zqYy86PT0)a_y`5?fHHY4!Lkpjm3nA&3JV^T5g@zC(#e-Ha?4iLDXp+|V-0N;K>9jH zESFgAa-@__tfWNM$Pj%8EeOEX+}6pLHfX({I!&wfEu45woWbtgB?Q=sv6cdGO29dN zZR2v~sJ2Gh0;cnR6Iv+1gva?(S5Er6+Kz^S2r9Dw6UqW_1xHVRq&qn8r}uJB*P2eA z)1O`YNu@v#1gZcT=g+U4#B*4iIg&z1*}ru9wS2+~1Jy5vs#V}>2w0sJ`9Q`xetlVD zy9`I?)5eSD%z@?^R$hnPG55b8YU2+s(^Bihdk&+GfjZXp1PFHOG%j3 z2bkB;Yn0ZVIn@#8UsV8l(yFl9mz=28|B~~o$~10%Z& z=WAr}K9d!Wq{@JEvZZQsT-AQ3KM_{8F!hu1kA9Yn|HIc8#D6 zqy)|zVl3x-a}_PKkB;HiBbA`sSjnb!PX=3`3EWYENOBgDKTl&uQiG@}WOEa3tcHrj z_V~xbwZ(fRQnG0^bgZ$KUF~E_G20?R6r8f`Vv7LL2eM)!Vq;dj*6H`Iy@CYN`aEdS z2iOIz-`5ko3y(d^`JPOiI#bK&5ehKpL1LQ#G|}Py%xjiRuZ}~2Bf^doJZt0fk6jX= zl#&DdWe}IaQxc$AY=IKNl5)>I(zP*ohgpfH_IFKPb^%KQBP9)osz%2OPmbWJ8a#1a z>Qh*@HPEoxh5(4zHtmd&>(E8!=H`j6-J=jz;^!?prNs3k%RmW~(O&0Bm4!FE_l(OB zTyuW(86aA!6-1Ew2=vu_Mk{wb^{&UZWN#PQ6Y3eqm8yvK zbLC)oU(p2M8X+944x0D=F3^bJsoS%p*d;+pA3$n&>_{LYhE~Pl)h4HUl{vtiQ)?Ef zV=EMZ`w#0L-;RFHJd&a1hb0j(up{i>NCBPW*xPIb9@8UKQDqH3d0??9Xj2IDC sbaVa zMNY9&gk-#G{ zYzvVABnR^vt78t{aej&ez1IgCp}eY&Mjk36sjSS4p(`u-tBk+H?&NEI;JbmS60G_G zIl&cg1b2?)yXHpq^8Y9pMQ7w}KU+9ThZvdNhSYv%C%7A5{6X|o$x^uG7-%1F$=2Fj z#V+3khZJBPs!(_fM)NQNqz})bk0p~Ib4!l%lNsV|HISUZGQM_=&`UTT}ALTHaYwrz;h7PMW^UJ1eM9#(y^lH z0F7a_{|PWtz=DSzyhp`|qR~qYDJ?{cJkR@rZYFpF=A3vJkclqOV7!l()1{pOy3fFp zx%(MH6>EKN2B4i5v@IO(x7yPpO_R@Sfhv~lt80Rtz;{Js1X2JO!k9xkE$kGGf70g6gb1@8f0INEj4`zJAh zE7FDS^I;dzy-;aCkG|(Da)!dK!jXi<5=!ozz17wvGiO4LyOEn0U?Uh64 zQMvM30_E5Ux*|Ny0F`K_i_mrgkujolt3_ml2`nR8I~BxS60V4J7XxHGm*;RQ96hIt z_Q>Y3X|!c`E}%DJ&B~T{VT(#yfr_Qas~W{kc=z75N7S zh(^EFKAQt6duX7nqNN-395IV0Vj@f+_0Krp=?w5!FR>G{OuDe-jZt%)NPphm8~L4) zC#9sT8Q@R^b{f`$t}#*0_4e6;juDg5^>mMOy90Ql)=~;;j~1!P_eM&j{h7{y^w7ot zROx3rf=01Xa5;!(EhoD>@}o{+IZK)WBt>?@%7a#4PkWT(t@a&E5S4ePGkDjN`aM7v z-1zJBoO)HiK$WaH3zz4S?^Sx``_AiHCx1@Y>ROM)0BbRpvUyv{AiNE71a1szoG3GS z7=b>kGQktBYpsXZ!4wizYi*I@k>NAQrX}udS_<01I33Kub(Ug)4&{60B4f}t{8?o@ zBrC!IJ6@^YBK=eb=v2H1C^EYpPf7#)`3^>n(m7wFwT>~sFM^PAJSm_G?#UVFi}c}X z0X^TrxZcR8Yl$$z$5Aj+NJhC4XybXuk{mYDUus*S*DL7WT9bL5^YecMSqk({zzd{& zkMTY8@G_TY9aJZGM|$Tx4zGiyMQe(!ID*AE5+n+bj7#N2`>noL89mBoAqL=`EoD_F zi6)#ur7x8+M&1#2k8n2wtVLOQg-@=c%&Gx99DvFi;~GXls02ZDe0mN2gNQlt{|!dp zd-HD=oyH%-{?VzY-&(VEUH#}a(qEESW`HF$62W{C=zG*AYQU~^lkFQFV+ j=ym|Kh+pJz1ta_c)_1ni5SD6$00000NkvXXu0mjfMD{rX diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_36.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_36.png deleted file mode 100644 index 1aac855834c50765ba9674b304504ad9a2ed2d19..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1538 zcmV+d2L1VoP)pZ zz;5I3Uf+W!9WPx5cn`dRW3Gg|Fqz5#$8ntgS$I-LR3YR19dA0nkJeh_*{unSMp>1dA8d>;z+W%(5#G$#HbUp$ zr9iq1Mr`>}0|0zxZie>xrX-m^?8gEBe+!^Y006EdK@?~N5#q-H*TDe*4dCpsUiOND z6vs>BuKqOfO#%8Y0s#Ox6L@L^fZD?Gn{b6d`@@X@^S&LsY(Rbc$2$Pd zbZ%|3_ri$Y%WN_`_Xiu)rthZ!DQm4v9(h}6jgc7R7XxTrWKudILmqLiUZL*U0G)BsX*72wptv5z-et?qu#T&EPax zaE|rG5ywicwT{(qWqwrtQ_x!TeGA}y8WUJg58f9_+WOXbt`zMbapsem0R8t-18@WK ztmWSfD5wLetlpbKx(1i!VFKxCf$y_dV@TIeV*+lc69Y(Z0eepbR!tx+Org~nUM09J z?KQS%eh*Jz0?j@jKL`Pc(KQP9qIb+>*Oi>^2^Q_XCz!5|o<45|hz5XEFpi9k$m8=o zPUqvZxI+-QoKD(TU*s84BhY$4dhF8*Mz3sMcUb$5)C5v{)tW#&+r6#C003)_0j>mh z(x|~_7IeGWAmwb00i-j|V=!Ln_rNor_hCzAMhz`JRalvyZBgzDXiqyOEs$RXjb`2m zO-kACWdu)QC>4IAunNghlsegB0L)@;_|>2r;~8tQjFstDHDA$M>t$`gZ85!VY`3FX zGn!>mR&fLM_S$Plb@QfF&5fMqmFu4oooc2#98U1Mi3aEz`K{uU5{r3^K0YyY18Bg$Lr@?afx#%e}DJ!nhym63cwZnO+$F@Q7<;2DfS_tUdWx0Q~7p0OmOWQT3Ik-%^%WUCiYwO$mt%lTAW&S* o`8%+b5oR3$tFq?FSiuPY0nQUmgswmI@Bjb+07*qoM6N<$f>dYDyZ`_I diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_4.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_4.png deleted file mode 100644 index ef5e2fc53b5ac0d5110ccf4c1b96219f6c65516c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1668 zcmV-~27CF5P)e^kD*EFS~Lv9sEdDqpe=U;_ir zZSQKW_1=3MxUQ>xkw@?SQG579o!{EO1@L=M#3!ub4*ZO0eNxmS*s-3|vL8u;Uj{gZ z3BH+I2P7EZ7+`k<@79mtlY;c>;A)=Wn4K8gIQHqZa-czFfCwXeQbtrEb>`jQ(Kdxoy(DAe;R}A zUf(nWpqbA{>FB5#M-BY;47HdExKIyZeeQkssd)ODLp^}?xrXta_wS26xLRwywbw}I z+3a0M?zsjXkU3~g_11R6T0H!|3br!cyB^>_N1Q+UgcF#d>fMJo=LF_^>j7GOJ=12G zU?B2N;c3}~I`1^QTYF`=AN~RD+Otd$*&Hp?hYkD$aRMUWpEx1QIbN9B`dt9(REZg) z&!GBt2ZPn;Jn`Ck~>`s0`H!+ zNi*-;t(R62-PB&x))ULXz>h!WKX{MOn*W}_LnITk{eYE)k=}*&M*P0^PF{KwdJ^F8 z1Bed?YrS=fh-^^RIbB1fu|qQaROh!AWB?A25eAUVMs*?1*G?7awdGK<k<~0ljyu z2kk%Y}RWx3bEUPEc3sr~NuSf?jrrC)11Z?F zo+Qfzv(7&uc%H%n0M>m7BhgA+}ogeqD zcM04k=^P%-d^%Pi;qGPuZC@Y;Ksx*kBu1e3r3D~%X>w=mMBZUY_r1Vf3Deoa=$3#L zfsm1DJ!6#_boBIiPR*>P3Nz~t|G$9Bv$Z7)>AQexy&T2!JW4lMy-w z4u#bCNMn{kG&m|YgX-1nuP856Uu6D@G5jKkH+9}z3(+7MjTzS=WuZs+(#V@#K1Z@A z!Ytfw3s3`BFs9!GA`9Rf8I>_0Ii7wdWwMv)+DL8JbXn`AbEW4k+~xFC50FK)re@?1 zdC`W?v)Sq;Bw3a|OJ>ymBN!ly>UfhjcwF-lVE`*wc!`i~L4s^m50e?;Ca?;Tx{1Pb z4Ao6-@XCay%d>^sB|Wn{^S3d;3V6N70G;DGzbZ(15NHANF$|F1zzj2Fby-1>k%#Kz zh%nNs@ht!>`a>2(Hy1@@uB0mq&-p9Lm82K#U6zcUtuWfecpE0wI#iyeAF1^cyn3wZ z@UEk_dOZM&*&q;)fe|X?bFIzP0)#I*TM8o3?BG93*V;YHzDy8kLE#0<6az>GQo-mV zwQ{f`!y}Ak&DR27S{pJCou3)$T9Er82GHO!0}=!kS;N@`k)35VZB0>DSP=x$7?m43 z1$eYbqfpuiYvVk4ay6SY`z=V!<;k}KgUaQfS2{m4ssX^WK>Jw=0%=36x}^ogOkcp| zWuDKl*X|(Tc24yG7BbGsI>M4=1yi)_c>1U;UV@NvU_B!xRz*+XjZ?o3)Tr`qP+JBl z^Hl1%F6@8#NWsY=B~jM-bgk12AW=RyRGWVnq$ZFeVoSz;lmXGKr=M43zILCF<{yZ# z7LN1-(k4_vlNr$^Ie=bgY_OVK?fyW<>7H%NdH_wSW|-8h%<4imU$arOlh3g*8-P?7 z>U65RqH$dEg%B^3DoTdM02Zvq4b@BKL~OI>dQPv9Cyh(K51>)1R|6|`TbN;?NV!1t zP-oLo3Wkb|zl8xfyk4QA_D1cgTDDc+?^sWy;giN&z7W#8{Y_%}Ia+_SsBTC#+oQb* zeLgx;8Ambz2ihY@bFxjACa)UNE?v9!o~v+v`6(a@@6;e6+PQl*yP6Rsn=G59AV38cS7A*m7&a@-dN}xTC6^tvK|H1$f>OvNiLFK6-!Y+3OKYDT)bCx;X z>j9#MRs+pqgwC}M9oWHqXmGA_I@|+1B@if1qJ9rvAm1^D|5E78oGebH^GbL<&^87@ z4Atg%^D;P%&Z~o7>w&g2Kz8A)Sm6ZcQ>+YxqZpuKL7$9OfiPsQ2!g+^GYZtf-cH{D O00005Az&DLY{*`% zwcdMg*LAhtduy$=>$(~Sz7PM$zv;WU4F}r)5BK2#WC4+sK#5Eb(YP&!_q_^MjPVV4 z069QthH!8m+?IQPC4MU73xYQV@OEHDKXnN1p5{zHjDaTz-V^|?9KlkGpwFxM|Aff> zCMUf*Gn+_$S34&~;tAxA!YT7*6=0NyQ&sq;$gz+i|Bm%}Q-G1=vx71HpAaRxqoOt+ zy8yswVzk;}HojSTs_OdSiZ{y{yw0&pbnjxv4A2xF#nVH6*1J4A18@J|Okx$4wRxQb zXj?qbV44xyJ6`9~4yk^~;|b(&i>saedx6_WzZp-S9 zK0LLDT59m@8nD!iP=o51L@BlXXNOT0;G?lYW_4?)9YSTdp7@^Eo(fL+3J7>a8sj34 zn`CzKBmPDJY_rJMj z&_(BJ3F$p(Zygp{AGAg*!3pTSfBroHJjlTNzIMb+9Z0GJB8ODjd401Iz&bzZ&5HL^ zMTj2xO7e>d#5P*T)7%HH|EvT!4ZhY}+67eA!0N}U_&FtzCS;B0Eh4QsoD5E3uj`6! z11s=uA+r%!ElBCO++t~V?dT-ZJn&UL2pVlnYrfM(4?O$)NMS0e5%;@d_iT7b$Jb>J zfDLS|zGxXjyt|Xg3FZ#uM@hd$0W`xUWKR~kF(SJLcrP-}YE($a*U(AetO;2Ad<)u+ zgUjKWa}RD;v1*q<#~Veyx(mRVxiyluX`{x`>Htq+3h)${+EzA-p(Ixn07vPl$ypPh z(H|wp+G5g3$M0$VMbj2%>niel@9`8c+X*TQD1poWcnmro*N!8{`W>k?R+1i40BA_% zaEC%D0vhc!pHpxV4_7Jjx!-u~skwY+4L}!#POJhck6P`bV^CPcd*ocJ0koQf_J8N~ zNsZGcJX)H)$f%7~z-gGh=wRR7gTwk=tol{PrLsJ$Tt z$U>8EDN?lv=7^G%ghs_I+#UH*CxNx`v`;%gCYm(jX}ijEjBM1?K2}Eh%B#x(Bq{e` zW@LInYXN}QIqoJYlP2&SOA6qLSgyS#@v9tzrW9}5fOb2O>bZi=QKbr{04X;&R&4^{?fk+mWGmYamBQ%s$>>( zHR)Cg^YpjuPr_av$B+%Fz}tQ{Xb#1Sb$5=Z>x1u0wZIk%fFY$6DX&>6i0glrdaP6g zjOg6AQveKzDlNkCb5HwGu$*U>B0zuC25gHK@mYIrQ2+`Y;T!>yBH(+tZDdN>NaNnm zM0R%E%J^^B9UK5)Nj-HG4j$?Dw2vaHOfw3?V-j(f&CZm|)2s4&;B>CGZ0EOlV zbRw0$wC9zAaz?>8y1%U*0KkzWElQ>@?m0$viL5areLz3M3eXcA0KgT4ocvcEp;{PP zV?v*IMEsEoKrvFzt-^9v9+&!FLHe-@KrvFzSCKD`xhmqnLIEgd$@z%f9UW%{=~EP7 z)na*V7#Y#49RbEVQUP{y1dNk}ZmA;7N&!1K!s%F{2!G|LaRgS1$SeQ=002ovPDHLk FV1oG?)rtTB diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_6.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_6.png deleted file mode 100644 index 789eebdfaa25f10335d19e85b2162518cd947a34..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1521 zcmVGwk@m0LO8}3Wpm+_BUaMZ9W)) z4rI02NIm;#`DBK|wN=-6j4?*VsQ^9`Xk*fTT;|6vc}z1vH1m0z2^z`iQvWAs>nhA7 zO{xa4o?BTxGu97lk*Wc#=h{Bco#{Cj=kY#2^xy!10q~i%JUhMX(!|0Tm2p?F#a+(u z9eDp3WM#T{OiUhB7jXl0*e21!0UM^wLGFxYrm4{5e#P6yf3M1;<@f|2WJ zF#(tVohTB&1_Jn*nynck%;DX$GMvlos2lEff-o6?gVzWEbEgN1DLh1-kIyrwlMXhx zlk)+R4DhFOFten75f-r+B4cO_w5XnUbK(qA48Y+rg4TNP5h9r#)s4>;d2jJ8b-@^8 zjsZBbHG(w`&ssWmWZ$>+$>{M^pdF_eKpK=X0?)YUdjsxd?l8@H{4}6d`Bv@mWG0}e zpSPVm89;)Rh0*r}8WTu$fL1p|b&AN}8InEQ89x@jBvb;yI@3$zdPer@22Zw=)y@utDW}sKKsw876Gr4DCHZ%4T!6Y^X+0pZ zjaKbfTby$T=w6O(hx5f6AetqaJv1_AlwvE}K(&8ld~N%fMXQr729PF>7%5}O$e2_y zO*Rv7niFO-pPvq->uQiK$?z-#oDC_f0QxdF;vi2(%Y5lN!f4x^9btg0p*uc#9fRNL z2k&dGmif{(UBlA7{4NaOWwe)FyYz_2ST%sO9q^zy-TAv2fbQ`fg;Iwe7O1Y-8bEu_ zbvV2IZ-s#)pvI|Wc!e3N^U?jh!{|6xBzPXh0JvCc*a{m|*dv;@IA^*MsSuDGZ<)1g|}Lsef0w!YLcSca}pt zw*@*M|L*o5ovRL@nq{*NqWvWnsIW-2Pq*`>akcxrJ?-aU%c0sciQ;DG%Z$aHh^Y4O zaJskN(`x_?R&8K8c_s+7u{;?~H_0g;X7~8naW2CE6d6k8R6bK!(z!M8xmGwK5>(lB zUY-Fc_}*M`@a)_ccvX$}x}9wmjN1K2FaU?e2o(nCEFd*q0pg4xp{n03BVF>xpfU76 zL8pew?!Ogy!GpS@Y^240w9WMItO+j303048P;i-S_E+~_ss9Sow$~ze2mpi@2o?)O zs9Tb^=E@XgbllFuu&o9Fi2ZBWl^_+1cr6)8g6C*Hm(;haZv`uy|G@xJ>grfb7c`^r z=g~q@lbzibJbia&e5eM9819OJ;AL@y&NVhcbgVZTY^$6et^uAJ2ox)+--ESXZE=3| z6!4@#=%VvZaMsz{Mgl-m)RXb#c3?G~cL)7t8KAoGtFXdqI$sqCmt%mA1w9#eWQ6|# XYhRhvxX$)N00000NkvXXu0mjf-Ye1b diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_7.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_7.png deleted file mode 100644 index cc4f5cbfff2c3c96ad0a63df6c9a20631c04b5ab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1642 zcmV-w29^1VP)=Kfb^KBn|WR$3dY?YOH_4hAF8(nusn zDW%q03*k79^5Xa4!;N1`DYe!j-`x>U*ux5ZDB_qRAN3FHC7_}5XTD3XpJFovm4BZS6jePmcI1OK* z^&QRY{k-^(o`6Z^09Jq0z}8$XO!TrvA5uAh)vraSG!Spg-?NN@*y2SFM=7P2a*kSQ z46ofRTsTLj0`bQ7FvDGH_!0R2G02K^ug#x?Y&!xT@u-jg^k9|oufSM8a)46)FKjjw zWEeop8N6?QOC)E&1Nb95sI!*|MphA4!pIpxvcPM6j8pDq{VgC{x@qmmcukI1Em%g5 zjQQ>OSe-Juv}cwX(D*2##;1PiTzpS&<&gMmU@5U1yxwX;8dw{jiE47VnU#Z-bqCa1 zZ3aLe@EoC(zUl!nh5Q{MqF^5FT!vMR%qVTIj-!Zw3Qc_+tjW=LksJXRITva_jS1ilYI6b*1d6t74q)X3 z9NHBzlJ6lq{_5bt#C7;*g4>(glrYz zeV?sNqH<;D@TPYn!K$POF?<3TY zoS}s+D8gk96WxcFGiW)0)-U(B()dVk)31UNP*fp@;KuS2GfNiDA6b|6ek2{s<7ai` zf5)~S0j==PCUgX#wsky2)_{<4rddYQ)-@vXxgKfk6b6WRpw=gyLvBjjpy%xo*&ay# zD*Be|w~irCH=2VDzX_DIjg+s^IajXy#$pX?gVLF_b!9Ny_qy^Bd0<7lJif! z3f9hV`Hnk{HBO$N{6|7lfVHkDJfqEa3dsOmH}bvIrF=j92tZVXgh!JV&4D;j6wFL* zRxm)1laMYn=HM9W04Uj@w&ny8c;hn0?xB7ubGNah&R}7v185$nB{egn)PlFJ2zDEH zgz-z6d=H=*dY9ozw#+|xp9a2{-Nr>vT4PqXQyn0>81JaqwJ!AC6^xCH-@31*H-T1; zAls`Fx(u#h?5OdzAd>HwIsg}JWvN%o83?R_9Y%~H947onib^Zt4I8lnf&J?{$Q1V(7`BIeH_0M0tB_ z2Ox~3b1$(Rp{}U2MV!RiOK2@mZ~($cI-g~HO-3f-qa~i!KCR(N4nSBz=OcmcJw_Q{ zdq)^{6F4H#83mpbP diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_8.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_8.png deleted file mode 100644 index 1a1fe5d2d4a034e644e717e621cc315e92baa085..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1694 zcmV;P24VS$P)4r{O-HzUS&p5R;KO>M# z+Ns(`ZLk{?OfvwQ`TQ9P8p-JuXYD`c0y0zsSmRb!&&WCp{5ss*!)`2)MW_a_#v z8_F}>SjYRRgctPDckYCvwbom+Gr;>hwXh%~BX>3vr7yzhmp~)vY4AVk%d;2V?0E7_ z_x`5O0VMP3XPKHpVv7i(K|R)}a{3M|@>K)0_MHKAypjpHAtQGX6=$@R)jaEGH38Y%#$(fD-{x+~RKU1SM1Hv5MXI2*SIn|wvw0=dASXpR#Fd2Y@ z*9fi6bq^9#ctD*$#-(m#qotd#! z7&=cefFzVM0%r#F-hdmFJ4$l~?*_Cx1JC)I4tIx?jHLlU7vk+%gb6g~NH=$MjII^A z#j9{`1!Ze{s(@4nl{0|0FlPnyp1?Z}tU#0AK{|lQu^_d8J3&e+{#0v#Y=!SB!-D`( zk_QLXPg*0g56j_PNA#W|15byD9LtXUsRn>`i82E&7g3VV<8m}udTUPeoGBeoS|Fc^ zsM9~5P56HR3Dtoh$+c+TTN|Nct1GbNYb?O^wb;o5VpC~4VdShEyfMRREIq44&Sg+%*wEDJOVs+|A906QR4-2V#9$ghOd-5~Xh_MX;#R0hHFdw^L~JBxP~){h2nj5C0ARM7P` z8IsP@I+roP3{6`yrD(4P!YtMtO)sfX<6|7iepht?=`VvUMKz;VSL0crLB(lY%3w*3 zXLLSKT2qsW$WL_vm9S{7s*A|HKPWdEe3H)E8IKrUJKIrRwx|K#Z3H_rQ3uZiLDu=y z_!(#!8x4Zl^-G^KH2{Z4XHvv-dbO%wX&pp;FVifiSjU{Fs}}?r(^uC5r?-I-r+P-^ z^CLP$;W<~*7df*t;BqBhFJXYp2CUCfBSkaZlZnok^yTxdAVA?Ur&VAss{s(Dd3`{Y z)6UKeth1FGkFJZ-zNHhF7by(6j@I9iOs6^_&yxQMcov>WmXlCS|d{&BJ~r(0H<~V9t~=d zEoDwrZL3I&%F8eXcLsky6M$CLuYGfDusiQ9G0z6Oy z@=cr>kBIXvs;xNFXTaHHi!LAoFO#Iqsbl~P(mCzvs#WRMdoz%Z(XAPPBSWdtqRCs^ zg4B^{x_ZT!Br^l6U5q?03jisrHG^cSmabKud1%mTZCh2V*?a8fZwm(S44|F6vqfA6 z$eqQQNB@q>%j}~f2rjDukp7s@jnoKqEYq%O@*?kBEv@uk#uZw72?xOrMIZ{xh+04) zK_kuA)|zb_z26a;T7rzVLN&n3>a-e20VWwED~pa->B}iwT6<=_@G&qL0KKHxDPXJ& zlg1;mq_KhQ-lMj&Dt$}#bPa$gx^p0);})H2>`}4bXkbbm1fu(N4e-=Jps1LeMYMB3 z+R11TKt3oMk oNVkHS0El9jKv<2P0s#$zUs0JT=-M&wV*mgE07*qoM6N<$f;eRor2qf` diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_9.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_9.png deleted file mode 100644 index a19da7382eadaf0bc885f76da8dec3899034528c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1605 zcmV-L2DVtUtjJl073;Y!fgJw#SBa zU#+#?dvC3^_IkY<|C>YV!@uKL-bVt;&q9(vEAubn%@>jksTm&;F*NTRky(Y+@}Gh? z8HU>4`;fuOd3axGd<85Ws^_{hB0=!O0N7c(PVk~19YVS$jsF+9b%+GPF9zVP6)hbUmI%4KoKdpTq^YZW62|a^E z zY0QwpUt*AtRs^;-7NH|fK=M88%K5vDe}bchY5>ppS|zSs%NB;34&HkO>~k8f(v3Ub z2XD0Y?FgegfXLWP#UF9XlZ|~6zP8cYy8zxXQ`kv;v^0X_Glq_&j*5EV>G)VTz0?rd zHCQzU9}`8?_|koA2l7{8GQh9S$%v22jBX7_jTUjjtjx$@Nmb0V~=x7SPTIyj_4c2i}Vok(vId%tZ0OzT&r)SPgJMX=Lw?-8J)G} zN#H64Ta%B038Om_4O+1n-Kj(ml5nnF$Qpe~4Ul009ET5LVI&PmGNig<6t^=vYf~qI z5rsy^q2qY_d=K6ZM5_F=y3g!%vcN;DD&sHN1yr0R%oczd`FPKoOJXZ)7c+xWBky1U zu6U(SRqs-Nwm-sogSExv_i+*V{h5@qB*LAx+ySalk)sh@9?~oRjXI!_t?$tS6UXgr z%N<~6Zk&O{1d;S$MZWZ%=F-|LjJ~V}&~oAm88gfvIj3d#=sj(brFoAq{t5=*s49pM zBQiNN5k2!ybk0cRTQvfgceU|*@3jX(lGo3`lNFhZ43IIhcD-uRNmjMok#@<;AYAl} zQ{W=uI4j3y^+RNwX@xN~9A5)y#_)`($(kAC>4!RIvDyi;Y4h?L0COrj1E;!GuojIw z2iI{%rycZ6^YE6>kIy}tPL>=5p%c&t!TU)D(F{ulDmHJd-2LsGF39Ut>f5|QY4LLF%(Uxrk zwWyTtY2#Mc3OFaumznUb3`2#Q`KoL>8RBV%Nf9e zRv%y__CEHneFj61>DlJTW=ZS5ikuyQ#!E-H^vCOy!VIKkMG@g@BT-uUG|3)f&p zxt0cIRGnp&6O5e+jHtZJeh={DfeKUL!&&`TL(8#O&WemU}6&X;p;tIr-dH#E) zmLuSU&B(IuXHv?Fg0WiOtsH Date: Wed, 15 Mar 2023 16:01:33 +0400 Subject: [PATCH 03/32] Nfc: fixes for latest PVS-studio 7.23 (#2490) --- lib/nfc/parsers/plantain_4k_parser.c | 2 +- lib/nfc/parsers/plantain_parser.c | 2 +- lib/nfc/parsers/troika_4k_parser.c | 2 +- lib/nfc/parsers/troika_parser.c | 2 +- lib/nfc/parsers/two_cities.c | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/nfc/parsers/plantain_4k_parser.c b/lib/nfc/parsers/plantain_4k_parser.c index aed41965..19da0b5e 100644 --- a/lib/nfc/parsers/plantain_4k_parser.c +++ b/lib/nfc/parsers/plantain_4k_parser.c @@ -118,7 +118,7 @@ bool plantain_4k_parser_parse(NfcDeviceData* dev_data) { } furi_string_printf( - dev_data->parsed_data, "\e#Plantain\nN:%llu-\nBalance:%ld\n", card_number, balance); + dev_data->parsed_data, "\e#Plantain\nN:%llu-\nBalance:%lu\n", card_number, balance); return true; } diff --git a/lib/nfc/parsers/plantain_parser.c b/lib/nfc/parsers/plantain_parser.c index 3a1d1773..2e4091dd 100644 --- a/lib/nfc/parsers/plantain_parser.c +++ b/lib/nfc/parsers/plantain_parser.c @@ -91,7 +91,7 @@ bool plantain_parser_parse(NfcDeviceData* dev_data) { } furi_string_printf( - dev_data->parsed_data, "\e#Plantain\nN:%llu-\nBalance:%ld\n", card_number, balance); + dev_data->parsed_data, "\e#Plantain\nN:%llu-\nBalance:%lu\n", card_number, balance); return true; } diff --git a/lib/nfc/parsers/troika_4k_parser.c b/lib/nfc/parsers/troika_4k_parser.c index d87b4eba..1f1b85a5 100644 --- a/lib/nfc/parsers/troika_4k_parser.c +++ b/lib/nfc/parsers/troika_4k_parser.c @@ -99,7 +99,7 @@ bool troika_4k_parser_parse(NfcDeviceData* dev_data) { number >>= 4; furi_string_printf( - dev_data->parsed_data, "\e#Troika\nNum: %ld\nBalance: %d rur.", number, balance); + dev_data->parsed_data, "\e#Troika\nNum: %lu\nBalance: %u rur.", number, balance); return true; } diff --git a/lib/nfc/parsers/troika_parser.c b/lib/nfc/parsers/troika_parser.c index 9c16296f..bfd22364 100644 --- a/lib/nfc/parsers/troika_parser.c +++ b/lib/nfc/parsers/troika_parser.c @@ -79,7 +79,7 @@ bool troika_parser_parse(NfcDeviceData* dev_data) { number >>= 4; furi_string_printf( - dev_data->parsed_data, "\e#Troika\nNum: %ld\nBalance: %d rur.", number, balance); + dev_data->parsed_data, "\e#Troika\nNum: %lu\nBalance: %u rur.", number, balance); troika_parsed = true; } while(false); diff --git a/lib/nfc/parsers/two_cities.c b/lib/nfc/parsers/two_cities.c index 0e2ed569..d6d4279d 100644 --- a/lib/nfc/parsers/two_cities.c +++ b/lib/nfc/parsers/two_cities.c @@ -136,7 +136,7 @@ bool two_cities_parser_parse(NfcDeviceData* dev_data) { furi_string_printf( dev_data->parsed_data, - "\e#Troika+Plantain\nPN: %llu-\nPB: %ld rur.\nTN: %ld\nTB: %d rur.\n", + "\e#Troika+Plantain\nPN: %llu-\nPB: %lu rur.\nTN: %lu\nTB: %u rur.\n", card_number, balance, troika_number, From 3a242e5fc319f28bf7ee2aa02869744cabdc5d86 Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Wed, 15 Mar 2023 16:16:52 +0400 Subject: [PATCH 04/32] SubGhz: bugfix unable to send, new generated secplus_v2 protocol (#2488) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- lib/subghz/protocols/keeloq.c | 4 +++- lib/subghz/protocols/secplus_v2.c | 9 +++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/subghz/protocols/keeloq.c b/lib/subghz/protocols/keeloq.c index 57d1cd22..7748da1e 100644 --- a/lib/subghz/protocols/keeloq.c +++ b/lib/subghz/protocols/keeloq.c @@ -182,7 +182,9 @@ bool subghz_protocol_keeloq_create_data( instance->generic.data_count_bit = 64; bool res = subghz_protocol_keeloq_gen_data(instance, btn); if(res) { - res = subghz_block_generic_serialize(&instance->generic, flipper_format, preset); + if(subghz_block_generic_serialize(&instance->generic, flipper_format, preset) != + SubGhzProtocolStatusOk) + res = false; } return res; } diff --git a/lib/subghz/protocols/secplus_v2.c b/lib/subghz/protocols/secplus_v2.c index 4a3815f0..c8ecbea2 100644 --- a/lib/subghz/protocols/secplus_v2.c +++ b/lib/subghz/protocols/secplus_v2.c @@ -599,19 +599,20 @@ bool subghz_protocol_secplus_v2_create_data( instance->generic.data_count_bit = (uint8_t)subghz_protocol_secplus_v2_const.min_count_bit_for_found; subghz_protocol_secplus_v2_encode(instance); - bool res = subghz_block_generic_serialize(&instance->generic, flipper_format, preset); + SubGhzProtocolStatus res = + subghz_block_generic_serialize(&instance->generic, flipper_format, preset); uint8_t key_data[sizeof(uint64_t)] = {0}; for(size_t i = 0; i < sizeof(uint64_t); i++) { key_data[sizeof(uint64_t) - i - 1] = (instance->secplus_packet_1 >> (i * 8)) & 0xFF; } - if(res && + if((res == SubGhzProtocolStatusOk) && !flipper_format_write_hex(flipper_format, "Secplus_packet_1", key_data, sizeof(uint64_t))) { FURI_LOG_E(TAG, "Unable to add Secplus_packet_1"); - res = false; + res = SubGhzProtocolStatusErrorParserOthers; } - return res; + return res == SubGhzProtocolStatusOk; } void* subghz_protocol_decoder_secplus_v2_alloc(SubGhzEnvironment* environment) { From e22668e19610ead06697a3b19b2ac88cdd4376d1 Mon Sep 17 00:00:00 2001 From: Eric Betts Date: Wed, 15 Mar 2023 05:35:11 -0700 Subject: [PATCH 05/32] Picopass standard KDF dictionary (#2478) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Split iclass dictionaries based on KDF * Allow cancelling during key test Co-authored-by: あく --- .../picopass/helpers/iclass_elite_dict.c | 15 +++++- .../picopass/helpers/iclass_elite_dict.h | 3 +- .../external/picopass/picopass_worker.c | 46 ++++++++---------- .../picopass/assets/iclass_elite_dict.txt | 12 ----- .../picopass/assets/iclass_standard_dict.txt | 47 +++++++++++++++++++ 5 files changed, 82 insertions(+), 41 deletions(-) create mode 100644 assets/resources/apps_data/picopass/assets/iclass_standard_dict.txt diff --git a/applications/external/picopass/helpers/iclass_elite_dict.c b/applications/external/picopass/helpers/iclass_elite_dict.c index e8c13dd1..f92dce0a 100644 --- a/applications/external/picopass/helpers/iclass_elite_dict.c +++ b/applications/external/picopass/helpers/iclass_elite_dict.c @@ -5,6 +5,7 @@ #define ICLASS_ELITE_DICT_FLIPPER_NAME APP_DATA_PATH("assets/iclass_elite_dict.txt") #define ICLASS_ELITE_DICT_USER_NAME APP_DATA_PATH("assets/iclass_elite_dict_user.txt") +#define ICLASS_STANDARD_DICT_FLIPPER_NAME APP_DATA_PATH("assets/iclass_standard_dict.txt") #define TAG "IclassEliteDict" @@ -25,6 +26,9 @@ bool iclass_elite_dict_check_presence(IclassEliteDictType dict_type) { (storage_common_stat(storage, ICLASS_ELITE_DICT_FLIPPER_NAME, NULL) == FSE_OK); } else if(dict_type == IclassEliteDictTypeUser) { dict_present = (storage_common_stat(storage, ICLASS_ELITE_DICT_USER_NAME, NULL) == FSE_OK); + } else if(dict_type == IclassStandardDictTypeFlipper) { + dict_present = + (storage_common_stat(storage, ICLASS_STANDARD_DICT_FLIPPER_NAME, NULL) == FSE_OK); } furi_record_close(RECORD_STORAGE); @@ -52,6 +56,15 @@ IclassEliteDict* iclass_elite_dict_alloc(IclassEliteDictType dict_type) { buffered_file_stream_close(dict->stream); break; } + } else if(dict_type == IclassStandardDictTypeFlipper) { + if(!buffered_file_stream_open( + dict->stream, + ICLASS_STANDARD_DICT_FLIPPER_NAME, + FSAM_READ, + FSOM_OPEN_EXISTING)) { + buffered_file_stream_close(dict->stream); + break; + } } // Read total amount of keys @@ -148,4 +161,4 @@ bool iclass_elite_dict_add_key(IclassEliteDict* dict, uint8_t* key) { furi_string_free(key_str); return key_added; -} \ No newline at end of file +} diff --git a/applications/external/picopass/helpers/iclass_elite_dict.h b/applications/external/picopass/helpers/iclass_elite_dict.h index e5ec8dfc..150cd1b7 100644 --- a/applications/external/picopass/helpers/iclass_elite_dict.h +++ b/applications/external/picopass/helpers/iclass_elite_dict.h @@ -9,6 +9,7 @@ typedef enum { IclassEliteDictTypeUser, IclassEliteDictTypeFlipper, + IclassStandardDictTypeFlipper, } IclassEliteDictType; typedef struct IclassEliteDict IclassEliteDict; @@ -25,4 +26,4 @@ bool iclass_elite_dict_get_next_key(IclassEliteDict* dict, uint8_t* key); bool iclass_elite_dict_rewind(IclassEliteDict* dict); -bool iclass_elite_dict_add_key(IclassEliteDict* dict, uint8_t* key); \ No newline at end of file +bool iclass_elite_dict_add_key(IclassEliteDict* dict, uint8_t* key); diff --git a/applications/external/picopass/picopass_worker.c b/applications/external/picopass/picopass_worker.c index f2e9e82b..e61b67d9 100644 --- a/applications/external/picopass/picopass_worker.c +++ b/applications/external/picopass/picopass_worker.c @@ -172,14 +172,18 @@ ReturnCode picopass_read_preauth(PicopassBlock* AA1) { return ERR_NONE; } -static ReturnCode picopass_auth_dict( - uint8_t* csn, - PicopassPacs* pacs, - uint8_t* div_key, - IclassEliteDictType dict_type, - bool elite) { +static ReturnCode + picopass_auth_dict(PicopassWorker* picopass_worker, IclassEliteDictType dict_type) { rfalPicoPassReadCheckRes rcRes; rfalPicoPassCheckRes chkRes; + bool elite = (dict_type != IclassStandardDictTypeFlipper); + + PicopassDeviceData* dev_data = picopass_worker->dev_data; + PicopassBlock* AA1 = dev_data->AA1; + PicopassPacs* pacs = &dev_data->pacs; + + uint8_t* csn = AA1[PICOPASS_CSN_BLOCK_INDEX].data; + uint8_t* div_key = AA1[PICOPASS_KD_BLOCK_INDEX].data; ReturnCode err = ERR_PARAM; @@ -204,7 +208,8 @@ static ReturnCode picopass_auth_dict( while(iclass_elite_dict_get_next_key(dict, key)) { FURI_LOG_D( TAG, - "Try to auth with key %zu %02x%02x%02x%02x%02x%02x%02x%02x", + "Try to %s auth with key %zu %02x%02x%02x%02x%02x%02x%02x%02x", + elite ? "elite" : "standard", index++, key[0], key[1], @@ -230,6 +235,8 @@ static ReturnCode picopass_auth_dict( memcpy(pacs->key, key, PICOPASS_BLOCK_LEN); break; } + + if(picopass_worker->state != PicopassWorkerStateDetect) break; } iclass_elite_dict_free(dict); @@ -237,38 +244,23 @@ static ReturnCode picopass_auth_dict( return err; } -ReturnCode picopass_auth(PicopassBlock* AA1, PicopassPacs* pacs) { +ReturnCode picopass_auth(PicopassWorker* picopass_worker) { ReturnCode err; FURI_LOG_I(TAG, "Starting system dictionary attack [Standard KDF]"); - err = picopass_auth_dict( - AA1[PICOPASS_CSN_BLOCK_INDEX].data, - pacs, - AA1[PICOPASS_KD_BLOCK_INDEX].data, - IclassEliteDictTypeFlipper, - false); + err = picopass_auth_dict(picopass_worker, IclassStandardDictTypeFlipper); if(err == ERR_NONE) { return ERR_NONE; } FURI_LOG_I(TAG, "Starting user dictionary attack [Elite KDF]"); - err = picopass_auth_dict( - AA1[PICOPASS_CSN_BLOCK_INDEX].data, - pacs, - AA1[PICOPASS_KD_BLOCK_INDEX].data, - IclassEliteDictTypeUser, - true); + err = picopass_auth_dict(picopass_worker, IclassEliteDictTypeUser); if(err == ERR_NONE) { return ERR_NONE; } FURI_LOG_I(TAG, "Starting system dictionary attack [Elite KDF]"); - err = picopass_auth_dict( - AA1[PICOPASS_CSN_BLOCK_INDEX].data, - pacs, - AA1[PICOPASS_KD_BLOCK_INDEX].data, - IclassEliteDictTypeFlipper, - true); + err = picopass_auth_dict(picopass_worker, IclassEliteDictTypeFlipper); if(err == ERR_NONE) { return ERR_NONE; } @@ -520,7 +512,7 @@ void picopass_worker_detect(PicopassWorker* picopass_worker) { } if(nextState == PicopassWorkerEventSuccess) { - err = picopass_auth(AA1, pacs); + err = picopass_auth(picopass_worker); if(err != ERR_NONE) { FURI_LOG_E(TAG, "picopass_try_auth error %d", err); nextState = PicopassWorkerEventFail; diff --git a/assets/resources/apps_data/picopass/assets/iclass_elite_dict.txt b/assets/resources/apps_data/picopass/assets/iclass_elite_dict.txt index 5da2a2fa..d1189237 100644 --- a/assets/resources/apps_data/picopass/assets/iclass_elite_dict.txt +++ b/assets/resources/apps_data/picopass/assets/iclass_elite_dict.txt @@ -1,16 +1,10 @@ ## From https://github.com/RfidResearchGroup/proxmark3/blob/master/client/dictionaries/iclass_default_keys.dic -# AA1 -AEA684A6DAB23278 # key1/Kc from PicoPass 2k documentation 7665544332211000 # SAGEM 0123456789ABCDEF -# from loclass demo file. -5b7c62c491c11b39 -# Kd from PicoPass 2k documentation -F0E1D2C3B4A59687 # PicoPass Default Exchange Key 5CBCF1DA45D5FB4F # From HID multiclassSE reader @@ -19,12 +13,6 @@ F0E1D2C3B4A59687 6EFD46EFCBB3C875 E033CA419AEE43F9 -# iCopy-x DRM keys -# iCL tags -2020666666668888 -# iCS tags reversed from the SOs -6666202066668888 - # default picopass KD / Page 0 / Book 1 FDCB5A52EA8F3090 237FF9079863DF44 diff --git a/assets/resources/apps_data/picopass/assets/iclass_standard_dict.txt b/assets/resources/apps_data/picopass/assets/iclass_standard_dict.txt new file mode 100644 index 00000000..46808ef6 --- /dev/null +++ b/assets/resources/apps_data/picopass/assets/iclass_standard_dict.txt @@ -0,0 +1,47 @@ + +## From https://github.com/RfidResearchGroup/proxmark3/blob/master/client/dictionaries/iclass_default_keys.dic + +# AA1 +AEA684A6DAB23278 +# key1/Kc from PicoPass 2k documentation +7665544332211000 +# SAGEM +0123456789ABCDEF +# from loclass demo file. +5b7c62c491c11b39 +# Kd from PicoPass 2k documentation +F0E1D2C3B4A59687 +# PicoPass Default Exchange Key +5CBCF1DA45D5FB4F +# From HID multiclassSE reader +31ad7ebd2f282168 +# From pastebin: https://pastebin.com/uHqpjiuU +6EFD46EFCBB3C875 +E033CA419AEE43F9 + +# iCopy-x DRM keys +# iCL tags +2020666666668888 +# iCS tags reversed from the SOs +6666202066668888 + +# default picopass KD / Page 0 / Book 1 +FDCB5A52EA8F3090 +237FF9079863DF44 +5ADC25FB27181D32 +83B881F2936B2E49 +43644E61EE866BA5 +897034143D016080 +82D17B44C0122963 +4895CA7DE65E2025 +DADAD4C57BE271B7 +E41E9EDEF5719ABF +293D275EC3AF9C7F +C3C169251B8A70FB +F41DAF58B20C8B91 +28877A609EC0DD2B +66584C91EE80D5E5 +C1B74D7478053AE2 + +# default iCLASS RFIDeas +6B65797374726B72 From a69ae93871a4bf8cd9a1b5bb2cba06218199c6aa Mon Sep 17 00:00:00 2001 From: Leopold Date: Wed, 15 Mar 2023 21:52:32 +0800 Subject: [PATCH 06/32] Add new nfc apdu cli command (#2482) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- applications/main/nfc/nfc_cli.c | 68 +++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/applications/main/nfc/nfc_cli.c b/applications/main/nfc/nfc_cli.c index 23335e29..6e6e04ca 100644 --- a/applications/main/nfc/nfc_cli.c +++ b/applications/main/nfc/nfc_cli.c @@ -2,6 +2,7 @@ #include #include #include +#include #include #include @@ -12,6 +13,7 @@ static void nfc_cli_print_usage() { printf("Cmd list:\r\n"); printf("\tdetect\t - detect nfc device\r\n"); printf("\temulate\t - emulate predefined nfca card\r\n"); + printf("\tapdu\t - Send APDU and print response \r\n"); if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { printf("\tfield\t - turn field on\r\n"); } @@ -98,6 +100,67 @@ static void nfc_cli_field(Cli* cli, FuriString* args) { furi_hal_nfc_sleep(); } +static void nfc_cli_apdu(Cli* cli, FuriString* args) { + UNUSED(cli); + if(furi_hal_nfc_is_busy()) { + printf("Nfc is busy\r\n"); + return; + } + + furi_hal_nfc_exit_sleep(); + FuriString* data = NULL; + data = furi_string_alloc(); + FuriHalNfcTxRxContext tx_rx = {}; + FuriHalNfcDevData dev_data = {}; + uint8_t* req_buffer = NULL; + uint8_t* resp_buffer = NULL; + size_t apdu_size = 0; + size_t resp_size = 0; + + do { + if(!args_read_string_and_trim(args, data)) { + printf( + "Use like `nfc apdu 00a404000e325041592e5359532e444446303100 00a4040008a0000003010102` \r\n"); + break; + } + + printf("detecting tag\r\n"); + if(!furi_hal_nfc_detect(&dev_data, 300)) { + printf("Failed to detect tag\r\n"); + break; + } + do { + apdu_size = furi_string_size(data) / 2; + req_buffer = malloc(apdu_size); + hex_chars_to_uint8(furi_string_get_cstr(data), req_buffer); + + memcpy(tx_rx.tx_data, req_buffer, apdu_size); + tx_rx.tx_bits = apdu_size * 8; + tx_rx.tx_rx_type = FuriHalNfcTxRxTypeDefault; + + printf("Sending APDU:%s to Tag\r\n", furi_string_get_cstr(data)); + if(!furi_hal_nfc_tx_rx(&tx_rx, 300)) { + printf("Failed to tx_rx\r\n"); + break; + } + resp_size = (tx_rx.rx_bits / 8) * 2; + resp_buffer = malloc(resp_size); + uint8_to_hex_chars(tx_rx.rx_data, resp_buffer, resp_size); + resp_buffer[resp_size] = 0; + printf("Response: %s\r\n", resp_buffer); + free(req_buffer); + free(resp_buffer); + req_buffer = NULL; + resp_buffer = NULL; + } while(args_read_string_and_trim(args, data)); + } while(false); + + free(req_buffer); + free(resp_buffer); + furi_string_free(data); + furi_hal_nfc_sleep(); +} + static void nfc_cli(Cli* cli, FuriString* args, void* context) { UNUSED(context); FuriString* cmd; @@ -117,6 +180,11 @@ static void nfc_cli(Cli* cli, FuriString* args, void* context) { break; } + if(furi_string_cmp_str(cmd, "apdu") == 0) { + nfc_cli_apdu(cli, args); + break; + } + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { if(furi_string_cmp_str(cmd, "field") == 0) { nfc_cli_field(cli, args); From c27d4d78f98869ba8ae3fdb01b322c52efe39d5e Mon Sep 17 00:00:00 2001 From: Liam Hays Date: Wed, 15 Mar 2023 08:51:15 -0600 Subject: [PATCH 07/32] Fix auto-capitalization in the keyboard when the text box is empty. (#2483) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: hedger Co-authored-by: あく --- applications/services/gui/modules/text_input.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/applications/services/gui/modules/text_input.c b/applications/services/gui/modules/text_input.c index 32607e88..86b7bca1 100644 --- a/applications/services/gui/modules/text_input.c +++ b/applications/services/gui/modules/text_input.c @@ -309,9 +309,9 @@ static void text_input_handle_ok(TextInput* text_input, TextInputModel* model, b char selected = get_selected_char(model); size_t text_length = strlen(model->text_buffer); - bool toogle_case = text_length == 0; - if(shift) toogle_case = !toogle_case; - if(toogle_case) { + bool toggle_case = text_length == 0 || model->clear_default_text; + if(shift) toggle_case = !toggle_case; + if(toggle_case) { selected = char_to_uppercase(selected); } From d8385b7f917ba10bd2ba49e5c076374de8b4c2ac Mon Sep 17 00:00:00 2001 From: hedger Date: Wed, 15 Mar 2023 19:24:56 +0400 Subject: [PATCH 08/32] gh: use shallow clones whenever possible (#2491) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * gh: use shallow clones whenever possible * gh: reverted submodule checks * gh: lint: joined linting scripts * gh: renamed linter workflow * check python linter output * gh: reworked linter * checking c linter * gh: merged submodule check & lint * gh: renamed step * gh: removed redundant `submodules: false` Co-authored-by: あく --- .github/workflows/build.yml | 6 +-- ...dules.yml => lint_and_submodule_check.yml} | 34 +++++++++++--- .github/workflows/lint_c.yml | 47 ------------------- .github/workflows/lint_python.yml | 33 ------------- .github/workflows/merge_report.yml | 2 +- .github/workflows/pvs_studio.yml | 2 +- .github/workflows/unit_tests.yml | 2 +- .github/workflows/updater_test.yml | 5 +- fbt | 4 +- fbt.cmd | 4 +- 10 files changed, 41 insertions(+), 98 deletions(-) rename .github/workflows/{check_submodules.yml => lint_and_submodule_check.yml} (56%) delete mode 100644 .github/workflows/lint_c.yml delete mode 100644 .github/workflows/lint_python.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 46d95ede..79535c93 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -27,7 +27,7 @@ jobs: - name: 'Checkout code' uses: actions/checkout@v3 with: - fetch-depth: 0 + fetch-depth: 1 ref: ${{ github.event.pull_request.head.sha }} - name: 'Get commit details' @@ -177,8 +177,8 @@ jobs: - name: 'Checkout code' uses: actions/checkout@v3 with: - fetch-depth: 0 - submodules: true + fetch-depth: 1 + submodules: false ref: ${{ github.event.pull_request.head.sha }} - name: 'Get commit details' diff --git a/.github/workflows/check_submodules.yml b/.github/workflows/lint_and_submodule_check.yml similarity index 56% rename from .github/workflows/check_submodules.yml rename to .github/workflows/lint_and_submodule_check.yml index 2eb2027c..ede35793 100644 --- a/.github/workflows/check_submodules.yml +++ b/.github/workflows/lint_and_submodule_check.yml @@ -1,4 +1,4 @@ -name: 'Check submodules branch' +name: 'Lint sources & check submodule integrity' on: push: @@ -9,9 +9,14 @@ on: - '*' pull_request: +env: + TARGETS: f7 + FBT_TOOLCHAIN_PATH: /runner/_work + SET_GH_OUTPUT: 1 + jobs: - check_protobuf: - runs-on: [self-hosted, FlipperZeroShell] + lint_sources_check_submodules: + runs-on: [self-hosted,FlipperZeroShell] steps: - name: 'Decontaminate previous build leftovers' run: | @@ -22,9 +27,10 @@ jobs: - name: 'Checkout code' uses: actions/checkout@v3 with: - fetch-depth: 0 + fetch-depth: 1 ref: ${{ github.event.pull_request.head.sha }} + - name: 'Check protobuf branch' run: | git submodule update --init @@ -36,12 +42,28 @@ jobs: BRANCHES=$(git branch -r --contains "$SUBMODULE_HASH"); COMMITS_IN_BRANCH="$(git rev-list --count dev)"; if [ $COMMITS_IN_BRANCH -lt $SUB_COMMITS_MIN ]; then - echo "name=fails::error" >> $GITHUB_OUTPUT + echo "name=fails::error" >> $GITHUB_OUTPUT; echo "::error::Error: Too low commits in $SUB_BRANCH of submodule $SUB_PATH: $COMMITS_IN_BRANCH(expected $SUB_COMMITS_MIN+)"; exit 1; fi if ! grep -q "/$SUB_BRANCH" <<< "$BRANCHES"; then - echo "name=fails::error" >> $GITHUB_OUTPUT + echo "name=fails::error" >> $GITHUB_OUTPUT; echo "::error::Error: Submodule $SUB_PATH is not on branch $SUB_BRANCH"; exit 1; fi + + - name: 'Check Python code formatting' + id: syntax_check_py + run: ./fbt lint_py 2>&1 >/dev/null || echo "errors=1" >> $GITHUB_OUTPUT + + - name: 'Check C++ code formatting' + if: always() + id: syntax_check_cpp + run: ./fbt lint 2>&1 >/dev/null || echo "errors=1" >> $GITHUB_OUTPUT + + - name: Report code formatting errors + if: ( steps.syntax_check_py.outputs.errors || steps.syntax_check_cpp.outputs.errors ) && github.event.pull_request + run: | + echo "Code formatting errors found"; + echo "Please run './fbt format' or './fbt format_py' to fix them"; + exit 1; diff --git a/.github/workflows/lint_c.yml b/.github/workflows/lint_c.yml deleted file mode 100644 index a6fd5127..00000000 --- a/.github/workflows/lint_c.yml +++ /dev/null @@ -1,47 +0,0 @@ -name: 'Lint C/C++ with clang-format' - -on: - push: - branches: - - dev - - "release*" - tags: - - '*' - pull_request: - -env: - TARGETS: f7 - FBT_TOOLCHAIN_PATH: /runner/_work - SET_GH_OUTPUT: 1 - -jobs: - lint_c_cpp: - runs-on: [self-hosted,FlipperZeroShell] - steps: - - name: 'Decontaminate previous build leftovers' - run: | - if [ -d .git ]; then - git submodule status || git checkout "$(git rev-list --max-parents=0 HEAD | tail -n 1)" - fi - - - name: 'Checkout code' - uses: actions/checkout@v3 - with: - fetch-depth: 0 - ref: ${{ github.event.pull_request.head.sha }} - - - name: 'Check code formatting' - id: syntax_check - run: ./fbt lint - - - name: Report code formatting errors - if: failure() && steps.syntax_check.outputs.errors && github.event.pull_request - uses: peter-evans/create-or-update-comment@v1 - with: - issue-number: ${{ github.event.pull_request.number }} - body: | - Please fix following code formatting errors: - ``` - ${{ steps.syntax_check.outputs.errors }} - ``` - You might want to run `./fbt format` for an auto-fix. diff --git a/.github/workflows/lint_python.yml b/.github/workflows/lint_python.yml deleted file mode 100644 index 66c36064..00000000 --- a/.github/workflows/lint_python.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: 'Python Lint' - -on: - push: - branches: - - dev - - "release*" - tags: - - '*' - pull_request: - -env: - FBT_TOOLCHAIN_PATH: /runner/_work - SET_GH_OUTPUT: 1 - -jobs: - lint_python: - runs-on: [self-hosted,FlipperZeroShell] - steps: - - name: 'Decontaminate previous build leftovers' - run: | - if [ -d .git ]; then - git submodule status || git checkout "$(git rev-list --max-parents=0 HEAD | tail -n 1)" - fi - - - name: 'Checkout code' - uses: actions/checkout@v3 - with: - fetch-depth: 0 - ref: ${{ github.event.pull_request.head.sha }} - - - name: 'Check code formatting' - run: ./fbt lint_py diff --git a/.github/workflows/merge_report.yml b/.github/workflows/merge_report.yml index 13fab094..3b7cd234 100644 --- a/.github/workflows/merge_report.yml +++ b/.github/workflows/merge_report.yml @@ -21,7 +21,7 @@ jobs: - name: 'Checkout code' uses: actions/checkout@v3 with: - fetch-depth: 0 + fetch-depth: 1 ref: ${{ github.event.pull_request.head.sha }} - name: 'Get commit details' diff --git a/.github/workflows/pvs_studio.yml b/.github/workflows/pvs_studio.yml index 65a8b615..9105a0fd 100644 --- a/.github/workflows/pvs_studio.yml +++ b/.github/workflows/pvs_studio.yml @@ -28,7 +28,7 @@ jobs: - name: 'Checkout code' uses: actions/checkout@v3 with: - fetch-depth: 0 + fetch-depth: 1 ref: ${{ github.event.pull_request.head.sha }} - name: 'Get commit details' diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 527e9a71..bed5a470 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -21,7 +21,7 @@ jobs: - name: Checkout code uses: actions/checkout@v3 with: - fetch-depth: 0 + fetch-depth: 1 ref: ${{ github.event.pull_request.head.sha }} - name: 'Get flipper from device manager (mock)' diff --git a/.github/workflows/updater_test.yml b/.github/workflows/updater_test.yml index 300440aa..eba34e98 100644 --- a/.github/workflows/updater_test.yml +++ b/.github/workflows/updater_test.yml @@ -21,7 +21,8 @@ jobs: - name: Checkout code uses: actions/checkout@v3 with: - fetch-depth: 0 + fetch-depth: 1 + submodules: false ref: ${{ github.event.pull_request.head.sha }} - name: 'Get flipper from device manager (mock)' @@ -62,7 +63,7 @@ jobs: uses: actions/checkout@v3 if: failure() with: - fetch-depth: 0 + fetch-depth: 1 ref: ${{ steps.release_tag.outputs.tag }} - name: 'Flash last release' diff --git a/fbt b/fbt index f80e802b..efe625f0 100755 --- a/fbt +++ b/fbt @@ -25,10 +25,10 @@ fi if [ -z "$FBT_NO_SYNC" ]; then if [ ! -d "$SCRIPT_PATH/.git" ]; then - echo "\".git\" directory not found, please clone repo via \"git clone --recursive\""; + echo "\".git\" directory not found, please clone repo via \"git clone\""; exit 1; fi - git submodule update --init; + git submodule update --init --depth 1; fi $SCONS_EP $SCONS_DEFAULT_FLAGS "$@" diff --git a/fbt.cmd b/fbt.cmd index 92c73486..6e839c77 100644 --- a/fbt.cmd +++ b/fbt.cmd @@ -5,9 +5,9 @@ set SCONS_EP=python -m SCons if [%FBT_NO_SYNC%] == [] ( if exist ".git" ( - git submodule update --init + git submodule update --init --depth 1 ) else ( - echo Not in a git repo, please clone with git clone --recursive + echo Not in a git repo, please clone with "git clone" exit /b 1 ) ) From 9fbf3270286beff340d8cfc67a219606237ad7d6 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Thu, 16 Mar 2023 10:28:50 +0200 Subject: [PATCH 09/32] [FL-1799] Require the trailing slash for root paths (#2486) * Require the trailing slash * Fix the swapped storages * Fix root paths --- .../services/storage/storage_processing.c | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/applications/services/storage/storage_processing.c b/applications/services/storage/storage_processing.c index 2a335e36..59527e76 100644 --- a/applications/services/storage/storage_processing.c +++ b/applications/services/storage/storage_processing.c @@ -33,12 +33,22 @@ static StorageType storage_get_type_by_path(FuriString* path) { StorageType type = ST_ERROR; const char* path_cstr = furi_string_get_cstr(path); - if(memcmp(path_cstr, STORAGE_EXT_PATH_PREFIX, strlen(STORAGE_EXT_PATH_PREFIX)) == 0) { - type = ST_EXT; - } else if(memcmp(path_cstr, STORAGE_INT_PATH_PREFIX, strlen(STORAGE_INT_PATH_PREFIX)) == 0) { - type = ST_INT; - } else if(memcmp(path_cstr, STORAGE_ANY_PATH_PREFIX, strlen(STORAGE_ANY_PATH_PREFIX)) == 0) { - type = ST_ANY; + if(furi_string_size(path) == 4) { + if(memcmp(path_cstr, STORAGE_EXT_PATH_PREFIX, strlen(STORAGE_EXT_PATH_PREFIX)) == 0) { + type = ST_EXT; + } else if(memcmp(path_cstr, STORAGE_INT_PATH_PREFIX, strlen(STORAGE_INT_PATH_PREFIX)) == 0) { + type = ST_INT; + } else if(memcmp(path_cstr, STORAGE_ANY_PATH_PREFIX, strlen(STORAGE_ANY_PATH_PREFIX)) == 0) { + type = ST_ANY; + } + } else if(furi_string_size(path) > 4) { + if(memcmp(path_cstr, EXT_PATH(""), strlen(EXT_PATH(""))) == 0) { + type = ST_EXT; + } else if(memcmp(path_cstr, INT_PATH(""), strlen(INT_PATH(""))) == 0) { + type = ST_INT; + } else if(memcmp(path_cstr, ANY_PATH(""), strlen(ANY_PATH(""))) == 0) { + type = ST_ANY; + } } return type; From e90042368f941f2c167563444fdb5cf22a5da9ad Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Thu, 16 Mar 2023 10:58:07 +0200 Subject: [PATCH 10/32] [FL-3156] Mark keys as not found when they couldn't auth successfully (#2476) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Mark keys as not found when they couldn't auth successfully * Improve logging and fix the reading bug Co-authored-by: あく --- lib/nfc/nfc_worker.c | 13 +++++++++++-- lib/nfc/protocols/mifare_classic.c | 26 ++++++++++++++++++++++---- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/lib/nfc/nfc_worker.c b/lib/nfc/nfc_worker.c index 062a3953..4561ff2a 100644 --- a/lib/nfc/nfc_worker.c +++ b/lib/nfc/nfc_worker.c @@ -739,7 +739,7 @@ void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker) { if(mf_classic_authenticate_skip_activate( &tx_rx, block_num, key, MfClassicKeyA, !deactivated, cuid)) { mf_classic_set_key_found(data, i, MfClassicKeyA, key); - FURI_LOG_D(TAG, "Key found"); + FURI_LOG_D(TAG, "Key A found"); nfc_worker->callback(NfcWorkerEventFoundKeyA, nfc_worker->context); uint64_t found_key; @@ -753,22 +753,31 @@ void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker) { } nfc_worker_mf_classic_key_attack(nfc_worker, found_key, &tx_rx, i + 1); + break; } nfc_worker_mf_classic_key_attack(nfc_worker, key, &tx_rx, i + 1); } furi_hal_nfc_sleep(); deactivated = true; + } else { + mf_classic_set_key_not_found(data, i, MfClassicKeyA); + is_key_a_found = false; + FURI_LOG_D(TAG, "Key %dA not found in attack", i); } if(!is_key_b_found) { is_key_b_found = mf_classic_is_key_found(data, i, MfClassicKeyB); if(mf_classic_authenticate_skip_activate( &tx_rx, block_num, key, MfClassicKeyB, !deactivated, cuid)) { - FURI_LOG_D(TAG, "Key found"); + FURI_LOG_D(TAG, "Key B found"); mf_classic_set_key_found(data, i, MfClassicKeyB, key); nfc_worker->callback(NfcWorkerEventFoundKeyB, nfc_worker->context); nfc_worker_mf_classic_key_attack(nfc_worker, key, &tx_rx, i + 1); } deactivated = true; + } else { + mf_classic_set_key_not_found(data, i, MfClassicKeyB); + is_key_b_found = false; + FURI_LOG_D(TAG, "Key %dB not found in attack", i); } if(is_key_a_found && is_key_b_found) break; if(nfc_worker->state != NfcWorkerStateMfClassicDictAttack) break; diff --git a/lib/nfc/protocols/mifare_classic.c b/lib/nfc/protocols/mifare_classic.c index 71242871..d2d7467d 100644 --- a/lib/nfc/protocols/mifare_classic.c +++ b/lib/nfc/protocols/mifare_classic.c @@ -651,7 +651,12 @@ void mf_classic_read_sector(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data, u if(!key_a_found) break; FURI_LOG_D(TAG, "Try to read blocks with key A"); key = nfc_util_bytes2num(sec_tr->key_a, sizeof(sec_tr->key_a)); - if(!mf_classic_auth(tx_rx, start_block, key, MfClassicKeyA, &crypto, false, 0)) break; + if(!mf_classic_auth(tx_rx, start_block, key, MfClassicKeyA, &crypto, false, 0)) { + mf_classic_set_key_not_found(data, sec_num, MfClassicKeyA); + FURI_LOG_D(TAG, "Key %dA not found in read", sec_num); + break; + } + for(size_t i = start_block; i < start_block + total_blocks; i++) { if(!mf_classic_is_block_read(data, i)) { if(mf_classic_read_block(tx_rx, &crypto, i, &block_tmp)) { @@ -660,7 +665,11 @@ void mf_classic_read_sector(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data, u } else if(i > start_block) { // Try to re-auth to read block in case prevous block was protected from read furi_hal_nfc_sleep(); - if(!mf_classic_auth(tx_rx, i, key, MfClassicKeyA, &crypto, false, 0)) break; + if(!mf_classic_auth(tx_rx, i, key, MfClassicKeyA, &crypto, false, 0)) { + mf_classic_set_key_not_found(data, sec_num, MfClassicKeyA); + FURI_LOG_D(TAG, "Key %dA not found in read", sec_num); + break; + } if(mf_classic_read_block(tx_rx, &crypto, i, &block_tmp)) { mf_classic_set_block_read(data, i, &block_tmp); blocks_read++; @@ -680,7 +689,12 @@ void mf_classic_read_sector(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data, u } FURI_LOG_D(TAG, "Try to read blocks with key B"); key = nfc_util_bytes2num(sec_tr->key_b, sizeof(sec_tr->key_b)); - if(!mf_classic_auth(tx_rx, start_block, key, MfClassicKeyB, &crypto, false, 0)) break; + if(!mf_classic_auth(tx_rx, start_block, key, MfClassicKeyB, &crypto, false, 0)) { + mf_classic_set_key_not_found(data, sec_num, MfClassicKeyB); + FURI_LOG_D(TAG, "Key %dB not found in read", sec_num); + break; + } + for(size_t i = start_block; i < start_block + total_blocks; i++) { if(!mf_classic_is_block_read(data, i)) { if(mf_classic_read_block(tx_rx, &crypto, i, &block_tmp)) { @@ -689,7 +703,11 @@ void mf_classic_read_sector(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data, u } else if(i > start_block) { // Try to re-auth to read block in case prevous block was protected from read furi_hal_nfc_sleep(); - if(!mf_classic_auth(tx_rx, i, key, MfClassicKeyB, &crypto, false, 0)) break; + if(!mf_classic_auth(tx_rx, i, key, MfClassicKeyB, &crypto, false, 0)) { + mf_classic_set_key_not_found(data, sec_num, MfClassicKeyB); + FURI_LOG_D(TAG, "Key %dB not found in read", sec_num); + break; + } if(mf_classic_read_block(tx_rx, &crypto, i, &block_tmp)) { mf_classic_set_block_read(data, i, &block_tmp); blocks_read++; From 6aa0c08f3a47ea9750f2d28c4386fec61c55efd8 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Thu, 16 Mar 2023 11:06:11 +0200 Subject: [PATCH 11/32] [FL-3064] Skip the read when the card is not present (#2494) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .../main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c | 3 ++- applications/main/nfc/views/dict_attack.c | 8 ++++++++ applications/main/nfc/views/dict_attack.h | 2 ++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c index b82bf552..cb2f3a82 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c @@ -115,7 +115,8 @@ bool nfc_scene_mf_classic_dict_attack_on_event(void* context, SceneManagerEvent consumed = true; } } else if(event.event == NfcWorkerEventAborted) { - if(state == DictAttackStateUserDictInProgress) { + if(state == DictAttackStateUserDictInProgress && + dict_attack_get_card_state(nfc->dict_attack)) { nfc_scene_mf_classic_dict_attack_prepare_view(nfc, state); consumed = true; } else { diff --git a/applications/main/nfc/views/dict_attack.c b/applications/main/nfc/views/dict_attack.c index a539e514..8f4bd063 100644 --- a/applications/main/nfc/views/dict_attack.c +++ b/applications/main/nfc/views/dict_attack.c @@ -11,6 +11,7 @@ struct DictAttack { View* view; DictAttackCallback callback; void* context; + bool card_present; }; typedef struct { @@ -162,6 +163,7 @@ void dict_attack_set_header(DictAttack* dict_attack, const char* header) { void dict_attack_set_card_detected(DictAttack* dict_attack, MfClassicType type) { furi_assert(dict_attack); + dict_attack->card_present = true; with_view_model( dict_attack->view, DictAttackViewModel * model, @@ -175,6 +177,7 @@ void dict_attack_set_card_detected(DictAttack* dict_attack, MfClassicType type) void dict_attack_set_card_removed(DictAttack* dict_attack) { furi_assert(dict_attack); + dict_attack->card_present = false; with_view_model( dict_attack->view, DictAttackViewModel * model, @@ -182,6 +185,11 @@ void dict_attack_set_card_removed(DictAttack* dict_attack) { true); } +bool dict_attack_get_card_state(DictAttack* dict_attack) { + furi_assert(dict_attack); + return dict_attack->card_present; +} + void dict_attack_set_sector_read(DictAttack* dict_attack, uint8_t sec_read) { furi_assert(dict_attack); with_view_model( diff --git a/applications/main/nfc/views/dict_attack.h b/applications/main/nfc/views/dict_attack.h index 2839534a..73b98a1b 100644 --- a/applications/main/nfc/views/dict_attack.h +++ b/applications/main/nfc/views/dict_attack.h @@ -25,6 +25,8 @@ void dict_attack_set_card_detected(DictAttack* dict_attack, MfClassicType type); void dict_attack_set_card_removed(DictAttack* dict_attack); +bool dict_attack_get_card_state(DictAttack* dict_attack); + void dict_attack_set_sector_read(DictAttack* dict_attack, uint8_t sec_read); void dict_attack_set_keys_found(DictAttack* dict_attack, uint8_t keys_found); From 771c47f809eb1e25c2bb3f55ff40780c3709e388 Mon Sep 17 00:00:00 2001 From: hedger Date: Thu, 16 Mar 2023 17:46:18 +0400 Subject: [PATCH 12/32] fbt: explicitly set dist suffix length, not depending on environment settings. See (#2497) --- scripts/version.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/version.py b/scripts/version.py index 896b58a4..880a9728 100644 --- a/scripts/version.py +++ b/scripts/version.py @@ -9,11 +9,16 @@ from datetime import date, datetime class GitVersion: + REVISION_SUFFIX_LENGTH = 8 + def __init__(self, source_dir): self.source_dir = source_dir def get_version_info(self): - commit = self._exec_git("rev-parse --short HEAD") or "unknown" + commit = ( + self._exec_git(f"rev-parse --short={self.REVISION_SUFFIX_LENGTH} HEAD") + or "unknown" + ) dirty = False try: From 25fd3c3400ecaa0e3b66771a895844de8ddd52f9 Mon Sep 17 00:00:00 2001 From: Guido Giorgi Date: Thu, 16 Mar 2023 15:12:43 +0100 Subject: [PATCH 13/32] iButton: Add support for Dallas DS1971 v2 (#2492) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * iButton: Add DS1971 Support refactored for v2 * Fix requested by gsurkov * Fix DALLAS_COMMON_CMD_* use, cusotm data field to Eeprom and COPY_SCRAPTCHPAD * Fix Emulation + Memory Info + Docs * Fix docs, strings, refactor code Co-authored-by: Georgii Surkov Co-authored-by: あく --- .../file_formats/iButtonFileFormat.md | 13 +- .../ibutton/protocols/dallas/dallas_common.c | 27 +- .../ibutton/protocols/dallas/dallas_common.h | 5 +- .../protocols/dallas/protocol_ds1971.c | 270 ++++++++++++++++++ .../protocols/dallas/protocol_ds1971.h | 5 + .../protocols/dallas/protocol_ds1992.c | 3 +- .../protocols/dallas/protocol_ds1996.c | 3 +- .../dallas/protocol_group_dallas_defs.c | 2 + .../dallas/protocol_group_dallas_defs.h | 1 + 9 files changed, 311 insertions(+), 18 deletions(-) create mode 100644 lib/one_wire/ibutton/protocols/dallas/protocol_ds1971.c create mode 100644 lib/one_wire/ibutton/protocols/dallas/protocol_ds1971.h diff --git a/documentation/file_formats/iButtonFileFormat.md b/documentation/file_formats/iButtonFileFormat.md index adb493e0..d31c297a 100644 --- a/documentation/file_formats/iButtonFileFormat.md +++ b/documentation/file_formats/iButtonFileFormat.md @@ -24,12 +24,13 @@ Changelog: #### Format fields -| Name | Type | Description | -| --------- | ------ | -------------------------------------------- | -| Protocol | string | Currently supported: DS1990, DS1992, DS1996, DSGeneric*, Cyfral, Metakom | -| Rom Data | hex | Read-only memory data (Dallas protocols only) | -| Sram Data | hex | Static RAM data (DS1992 and DS1996 only) -| Data | hex | Key data (Cyfral & Metakom only) | +| Name | Type | Description | +| ----------- | ------ | -------------------------------------------- | +| Protocol | string | Currently supported: DS1990, DS1992, DS1996, DS1997, DSGeneric*, Cyfral, Metakom | +| Rom Data | hex | Read-only memory data (Dallas protocols only) | +| Sram Data | hex | Static RAM data (DS1992 and DS1996 only) +| Eeprom Data | hex | EEPROM data (DS1971 only) +| Data | hex | Key data (Cyfral & Metakom only) | NOTE 1: DSGeneric is a catch-all protocol for all unknown 1-Wire devices. It reads only the ROM and does not perform any checks on the read data. It can also be used if a key with a deliberately invalid family code or checksum is required. diff --git a/lib/one_wire/ibutton/protocols/dallas/dallas_common.c b/lib/one_wire/ibutton/protocols/dallas/dallas_common.c index 57a873b1..22b25db8 100644 --- a/lib/one_wire/ibutton/protocols/dallas/dallas_common.c +++ b/lib/one_wire/ibutton/protocols/dallas/dallas_common.c @@ -21,6 +21,7 @@ #define BITS_IN_BYTE 8U #define BITS_IN_KBIT 1024U +#define BITS_IN_MBIT (BITS_IN_KBIT * 1024U) bool dallas_common_skip_rom(OneWireHost* host) { onewire_host_write(host, DALLAS_COMMON_CMD_SKIP_ROM); @@ -210,25 +211,35 @@ bool dallas_common_is_valid_crc(const DallasCommonRomData* rom_data) { void dallas_common_render_brief_data( FuriString* result, const DallasCommonRomData* rom_data, - const uint8_t* sram_data, - size_t sram_data_size) { + const uint8_t* mem_data, + size_t mem_size, + const char* mem_name) { for(size_t i = 0; i < sizeof(rom_data->bytes); ++i) { furi_string_cat_printf(result, "%02X ", rom_data->bytes[i]); } + const char* size_prefix = ""; + size_t mem_size_bits = mem_size * BITS_IN_BYTE; + + if(mem_size_bits >= BITS_IN_MBIT) { + size_prefix = "M"; + mem_size_bits /= BITS_IN_MBIT; + } else if(mem_size_bits >= BITS_IN_KBIT) { + size_prefix = "K"; + mem_size_bits /= BITS_IN_KBIT; + } + furi_string_cat_printf( - result, - "\nInternal SRAM: %zu Kbit\n", - (size_t)(sram_data_size * BITS_IN_BYTE / BITS_IN_KBIT)); + result, "\nInternal %s: %zu %sbit\n", mem_name, mem_size_bits, size_prefix); for(size_t i = 0; i < DALLAS_COMMON_BRIEF_HEAD_COUNT; ++i) { - furi_string_cat_printf(result, "%02X ", sram_data[i]); + furi_string_cat_printf(result, "%02X ", mem_data[i]); } furi_string_cat_printf(result, "[ . . . ]"); - for(size_t i = sram_data_size - DALLAS_COMMON_BRIEF_TAIL_COUNT; i < sram_data_size; ++i) { - furi_string_cat_printf(result, " %02X", sram_data[i]); + for(size_t i = mem_size - DALLAS_COMMON_BRIEF_TAIL_COUNT; i < mem_size; ++i) { + furi_string_cat_printf(result, " %02X", mem_data[i]); } } diff --git a/lib/one_wire/ibutton/protocols/dallas/dallas_common.h b/lib/one_wire/ibutton/protocols/dallas/dallas_common.h index 7ad13eb2..7991a1f8 100644 --- a/lib/one_wire/ibutton/protocols/dallas/dallas_common.h +++ b/lib/one_wire/ibutton/protocols/dallas/dallas_common.h @@ -99,8 +99,9 @@ bool dallas_common_is_valid_crc(const DallasCommonRomData* rom_data); void dallas_common_render_brief_data( FuriString* result, const DallasCommonRomData* rom_data, - const uint8_t* sram_data, - size_t sram_data_size); + const uint8_t* mem_data, + size_t mem_size, + const char* mem_name); void dallas_common_render_crc_error(FuriString* result, const DallasCommonRomData* rom_data); diff --git a/lib/one_wire/ibutton/protocols/dallas/protocol_ds1971.c b/lib/one_wire/ibutton/protocols/dallas/protocol_ds1971.c new file mode 100644 index 00000000..eb5b330b --- /dev/null +++ b/lib/one_wire/ibutton/protocols/dallas/protocol_ds1971.c @@ -0,0 +1,270 @@ +#include "protocol_ds1971.h" + +#include +#include + +#include "dallas_common.h" + +#define DS1971_FAMILY_CODE 0x14U +#define DS1971_FAMILY_NAME "DS1971" + +#define DS1971_EEPROM_DATA_SIZE 32U +#define DS1971_SRAM_PAGE_SIZE 32U +#define DS1971_COPY_SCRATCH_DELAY_US 250U + +#define DS1971_DATA_BYTE_COUNT 4U + +#define DS1971_EEPROM_DATA_KEY "Eeprom Data" +#define DS1971_MEMORY_TYPE "EEPROM" + +#define DS1971_CMD_FINALIZATION 0xA5 + +typedef struct { + OneWireSlave* bus; + DallasCommonCommandState command_state; +} DS1971ProtocolState; + +typedef struct { + DallasCommonRomData rom_data; + uint8_t eeprom_data[DS1971_EEPROM_DATA_SIZE]; + DS1971ProtocolState state; +} DS1971ProtocolData; + +static bool dallas_ds1971_read(OneWireHost*, void*); +static bool dallas_ds1971_write_copy(OneWireHost*, iButtonProtocolData*); +static void dallas_ds1971_emulate(OneWireSlave*, iButtonProtocolData*); +static bool dallas_ds1971_load(FlipperFormat*, uint32_t, iButtonProtocolData*); +static bool dallas_ds1971_save(FlipperFormat*, const iButtonProtocolData*); +static void dallas_ds1971_render_data(FuriString*, const iButtonProtocolData*); +static void dallas_ds1971_render_brief_data(FuriString*, const iButtonProtocolData*); +static void dallas_ds1971_render_error(FuriString*, const iButtonProtocolData*); +static bool dallas_ds1971_is_data_valid(const iButtonProtocolData*); +static void dallas_ds1971_get_editable_data(iButtonEditableData*, iButtonProtocolData*); +static void dallas_ds1971_apply_edits(iButtonProtocolData*); +static bool + dallas_ds1971_read_mem(OneWireHost* host, uint8_t address, uint8_t* data, size_t data_size); +static bool ds1971_emulate_read_mem(OneWireSlave* bus, const uint8_t* data, size_t data_size); + +const iButtonProtocolDallasBase ibutton_protocol_ds1971 = { + .family_code = DS1971_FAMILY_CODE, + .features = iButtonProtocolFeatureExtData | iButtonProtocolFeatureWriteCopy, + .data_size = sizeof(DS1971ProtocolData), + .manufacturer = DALLAS_COMMON_MANUFACTURER_NAME, + .name = DS1971_FAMILY_NAME, + + .read = dallas_ds1971_read, + .write_blank = NULL, /* No data to write a blank */ + .write_copy = dallas_ds1971_write_copy, + .emulate = dallas_ds1971_emulate, + .save = dallas_ds1971_save, + .load = dallas_ds1971_load, + .render_data = dallas_ds1971_render_data, + .render_brief_data = dallas_ds1971_render_brief_data, + .render_error = dallas_ds1971_render_error, + .is_valid = dallas_ds1971_is_data_valid, + .get_editable_data = dallas_ds1971_get_editable_data, + .apply_edits = dallas_ds1971_apply_edits, +}; + +bool dallas_ds1971_read(OneWireHost* host, iButtonProtocolData* protocol_data) { + DS1971ProtocolData* data = protocol_data; + return onewire_host_reset(host) && dallas_common_read_rom(host, &data->rom_data) && + dallas_ds1971_read_mem(host, 0, data->eeprom_data, DS1971_EEPROM_DATA_SIZE); +} + +bool dallas_ds1971_write_copy(OneWireHost* host, iButtonProtocolData* protocol_data) { + DS1971ProtocolData* data = protocol_data; + + onewire_host_reset(host); + onewire_host_skip(host); + // Starting writing from address 0x0000 + onewire_host_write(host, DALLAS_COMMON_CMD_WRITE_SCRATCH); + onewire_host_write(host, 0x00); + // Write data to scratchpad + onewire_host_write_bytes(host, data->eeprom_data, DS1971_EEPROM_DATA_SIZE); + + // Read data from scratchpad and verify + bool pad_valid = false; + if(onewire_host_reset(host)) { + pad_valid = true; + onewire_host_skip(host); + onewire_host_write(host, DALLAS_COMMON_CMD_READ_SCRATCH); + onewire_host_write(host, 0x00); + + for(size_t i = 0; i < DS1971_EEPROM_DATA_SIZE; ++i) { + uint8_t scratch = onewire_host_read(host); + if(data->eeprom_data[i] != scratch) { + pad_valid = false; + break; + } + } + } + + // Copy scratchpad to memory and confirm + if(pad_valid) { + if(onewire_host_reset(host)) { + onewire_host_skip(host); + onewire_host_write(host, DALLAS_COMMON_CMD_COPY_SCRATCH); + onewire_host_write(host, DS1971_CMD_FINALIZATION); + + furi_delay_us(DS1971_COPY_SCRATCH_DELAY_US); + } + } + + return pad_valid; +} + +static void dallas_ds1971_reset_callback(void* context) { + furi_assert(context); + DS1971ProtocolData* data = context; + data->state.command_state = DallasCommonCommandStateIdle; +} + +static bool dallas_ds1971_command_callback(uint8_t command, void* context) { + furi_assert(context); + DS1971ProtocolData* data = context; + OneWireSlave* bus = data->state.bus; + + switch(command) { + case DALLAS_COMMON_CMD_SEARCH_ROM: + if(data->state.command_state == DallasCommonCommandStateIdle) { + data->state.command_state = DallasCommonCommandStateRomCmd; + return dallas_common_emulate_search_rom(bus, &data->rom_data); + + } else if(data->state.command_state == DallasCommonCommandStateRomCmd) { + data->state.command_state = DallasCommonCommandStateMemCmd; + ds1971_emulate_read_mem(bus, data->eeprom_data, DS1971_EEPROM_DATA_SIZE); + return false; + + } else { + return false; + } + + case DALLAS_COMMON_CMD_READ_ROM: + if(data->state.command_state == DallasCommonCommandStateIdle) { + data->state.command_state = DallasCommonCommandStateRomCmd; + return dallas_common_emulate_read_rom(bus, &data->rom_data); + } else { + return false; + } + + case DALLAS_COMMON_CMD_SKIP_ROM: + if(data->state.command_state == DallasCommonCommandStateIdle) { + data->state.command_state = DallasCommonCommandStateRomCmd; + return true; + } else { + return false; + } + + default: + return false; + } +} + +void dallas_ds1971_emulate(OneWireSlave* bus, iButtonProtocolData* protocol_data) { + DS1971ProtocolData* data = protocol_data; + data->state.bus = bus; + + onewire_slave_set_reset_callback(bus, dallas_ds1971_reset_callback, protocol_data); + onewire_slave_set_command_callback(bus, dallas_ds1971_command_callback, protocol_data); +} + +bool dallas_ds1971_load( + FlipperFormat* ff, + uint32_t format_version, + iButtonProtocolData* protocol_data) { + DS1971ProtocolData* data = protocol_data; + bool success = false; + + do { + if(format_version < 2) break; + if(!dallas_common_load_rom_data(ff, format_version, &data->rom_data)) break; + if(!flipper_format_read_hex( + ff, DS1971_EEPROM_DATA_KEY, data->eeprom_data, DS1971_EEPROM_DATA_SIZE)) + break; + success = true; + } while(false); + + return success; +} + +bool dallas_ds1971_save(FlipperFormat* ff, const iButtonProtocolData* protocol_data) { + const DS1971ProtocolData* data = protocol_data; + bool success = false; + + do { + if(!dallas_common_save_rom_data(ff, &data->rom_data)) break; + if(!flipper_format_write_hex( + ff, DS1971_EEPROM_DATA_KEY, data->eeprom_data, DS1971_EEPROM_DATA_SIZE)) + break; + success = true; + } while(false); + + return success; +} + +void dallas_ds1971_render_data(FuriString* result, const iButtonProtocolData* protocol_data) { + const DS1971ProtocolData* data = protocol_data; + pretty_format_bytes_hex_canonical( + result, + DS1971_DATA_BYTE_COUNT, + PRETTY_FORMAT_FONT_MONOSPACE, + data->eeprom_data, + DS1971_EEPROM_DATA_SIZE); +} + +void dallas_ds1971_render_brief_data(FuriString* result, const iButtonProtocolData* protocol_data) { + const DS1971ProtocolData* data = protocol_data; + dallas_common_render_brief_data( + result, &data->rom_data, data->eeprom_data, DS1971_EEPROM_DATA_SIZE, DS1971_MEMORY_TYPE); +} + +void dallas_ds1971_render_error(FuriString* result, const iButtonProtocolData* protocol_data) { + const DS1971ProtocolData* data = protocol_data; + + if(!dallas_common_is_valid_crc(&data->rom_data)) { + dallas_common_render_crc_error(result, &data->rom_data); + } +} + +bool dallas_ds1971_is_data_valid(const iButtonProtocolData* protocol_data) { + const DS1971ProtocolData* data = protocol_data; + return dallas_common_is_valid_crc(&data->rom_data); +} + +void dallas_ds1971_get_editable_data( + iButtonEditableData* editable_data, + iButtonProtocolData* protocol_data) { + DS1971ProtocolData* data = protocol_data; + editable_data->ptr = data->rom_data.bytes; + editable_data->size = sizeof(DallasCommonRomData); +} + +void dallas_ds1971_apply_edits(iButtonProtocolData* protocol_data) { + DS1971ProtocolData* data = protocol_data; + dallas_common_apply_edits(&data->rom_data, DS1971_FAMILY_CODE); +} + +bool dallas_ds1971_read_mem(OneWireHost* host, uint8_t address, uint8_t* data, size_t data_size) { + onewire_host_write(host, DALLAS_COMMON_CMD_READ_MEM); + + onewire_host_write(host, address); + onewire_host_read_bytes(host, data, (uint8_t)data_size); + + return true; +} + +bool ds1971_emulate_read_mem(OneWireSlave* bus, const uint8_t* data, size_t data_size) { + bool success = false; + + do { + uint8_t address; + if(!onewire_slave_receive(bus, &address, sizeof(address))) break; + if(address >= data_size) break; + if(!onewire_slave_send(bus, data + address, data_size - address)) break; + + success = true; + } while(false); + + return success; +} diff --git a/lib/one_wire/ibutton/protocols/dallas/protocol_ds1971.h b/lib/one_wire/ibutton/protocols/dallas/protocol_ds1971.h new file mode 100644 index 00000000..522b612d --- /dev/null +++ b/lib/one_wire/ibutton/protocols/dallas/protocol_ds1971.h @@ -0,0 +1,5 @@ +#pragma once + +#include "protocol_dallas_base.h" + +extern const iButtonProtocolDallasBase ibutton_protocol_ds1971; diff --git a/lib/one_wire/ibutton/protocols/dallas/protocol_ds1992.c b/lib/one_wire/ibutton/protocols/dallas/protocol_ds1992.c index 131bc634..17d63125 100644 --- a/lib/one_wire/ibutton/protocols/dallas/protocol_ds1992.c +++ b/lib/one_wire/ibutton/protocols/dallas/protocol_ds1992.c @@ -17,6 +17,7 @@ #define DS1992_DATA_BYTE_COUNT 4U #define DS1992_SRAM_DATA_KEY "Sram Data" +#define DS1992_MEMORY_TYPE "SRAM" typedef struct { OneWireSlave* bus; @@ -188,7 +189,7 @@ void dallas_ds1992_render_data(FuriString* result, const iButtonProtocolData* pr void dallas_ds1992_render_brief_data(FuriString* result, const iButtonProtocolData* protocol_data) { const DS1992ProtocolData* data = protocol_data; dallas_common_render_brief_data( - result, &data->rom_data, data->sram_data, DS1992_SRAM_DATA_SIZE); + result, &data->rom_data, data->sram_data, DS1992_SRAM_DATA_SIZE, DS1992_MEMORY_TYPE); } void dallas_ds1992_render_error(FuriString* result, const iButtonProtocolData* protocol_data) { diff --git a/lib/one_wire/ibutton/protocols/dallas/protocol_ds1996.c b/lib/one_wire/ibutton/protocols/dallas/protocol_ds1996.c index e69145c5..74a5792c 100644 --- a/lib/one_wire/ibutton/protocols/dallas/protocol_ds1996.c +++ b/lib/one_wire/ibutton/protocols/dallas/protocol_ds1996.c @@ -15,6 +15,7 @@ #define DS1996_DATA_BYTE_COUNT 4U #define DS1996_SRAM_DATA_KEY "Sram Data" +#define DS1996_MEMORY_TYPE "SRAM" typedef struct { OneWireSlave* bus; @@ -182,7 +183,7 @@ void dallas_ds1996_render_data(FuriString* result, const iButtonProtocolData* pr void dallas_ds1996_render_brief_data(FuriString* result, const iButtonProtocolData* protocol_data) { const DS1996ProtocolData* data = protocol_data; dallas_common_render_brief_data( - result, &data->rom_data, data->sram_data, DS1996_SRAM_DATA_SIZE); + result, &data->rom_data, data->sram_data, DS1996_SRAM_DATA_SIZE, DS1996_MEMORY_TYPE); } void dallas_ds1996_render_error(FuriString* result, const iButtonProtocolData* protocol_data) { diff --git a/lib/one_wire/ibutton/protocols/dallas/protocol_group_dallas_defs.c b/lib/one_wire/ibutton/protocols/dallas/protocol_group_dallas_defs.c index e54c3125..b4dd51ce 100644 --- a/lib/one_wire/ibutton/protocols/dallas/protocol_group_dallas_defs.c +++ b/lib/one_wire/ibutton/protocols/dallas/protocol_group_dallas_defs.c @@ -3,12 +3,14 @@ #include "protocol_ds1990.h" #include "protocol_ds1992.h" #include "protocol_ds1996.h" +#include "protocol_ds1971.h" #include "protocol_ds_generic.h" const iButtonProtocolDallasBase* ibutton_protocols_dallas[] = { [iButtonProtocolDS1990] = &ibutton_protocol_ds1990, [iButtonProtocolDS1992] = &ibutton_protocol_ds1992, [iButtonProtocolDS1996] = &ibutton_protocol_ds1996, + [iButtonProtocolDS1971] = &ibutton_protocol_ds1971, /* Add new 1-Wire protocols here */ /* Default catch-all 1-Wire protocol */ diff --git a/lib/one_wire/ibutton/protocols/dallas/protocol_group_dallas_defs.h b/lib/one_wire/ibutton/protocols/dallas/protocol_group_dallas_defs.h index ba74c0c2..2ba1dd39 100644 --- a/lib/one_wire/ibutton/protocols/dallas/protocol_group_dallas_defs.h +++ b/lib/one_wire/ibutton/protocols/dallas/protocol_group_dallas_defs.h @@ -6,6 +6,7 @@ typedef enum { iButtonProtocolDS1990, iButtonProtocolDS1992, iButtonProtocolDS1996, + iButtonProtocolDS1971, /* Add new 1-Wire protocols here */ /* Default catch-all 1-Wire protocol */ From 7de7fa293b6eb8def92ecfecc1c1aea63a269624 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Fri, 17 Mar 2023 16:45:42 +0200 Subject: [PATCH 14/32] Optimize trailing slash check (#2503) * Optimize trailing slash check --- .../services/storage/storage_processing.c | 40 +++++++++++-------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/applications/services/storage/storage_processing.c b/applications/services/storage/storage_processing.c index 59527e76..a3076f27 100644 --- a/applications/services/storage/storage_processing.c +++ b/applications/services/storage/storage_processing.c @@ -2,6 +2,17 @@ #include #include +#define STORAGE_PATH_PREFIX_LEN 4u +_Static_assert( + sizeof(STORAGE_ANY_PATH_PREFIX) == STORAGE_PATH_PREFIX_LEN + 1, + "Any path prefix len mismatch"); +_Static_assert( + sizeof(STORAGE_EXT_PATH_PREFIX) == STORAGE_PATH_PREFIX_LEN + 1, + "Ext path prefix len mismatch"); +_Static_assert( + sizeof(STORAGE_INT_PATH_PREFIX) == STORAGE_PATH_PREFIX_LEN + 1, + "Int path prefix len mismatch"); + #define FS_CALL(_storage, _fn) ret = _storage->fs_api->_fn; static bool storage_type_is_valid(StorageType type) { @@ -26,34 +37,29 @@ static StorageData* get_storage_by_file(File* file, StorageData* storages) { static const char* cstr_path_without_vfs_prefix(FuriString* path) { const char* path_cstr = furi_string_get_cstr(path); - return path_cstr + MIN(4u, strlen(path_cstr)); + return path_cstr + MIN(STORAGE_PATH_PREFIX_LEN, strlen(path_cstr)); } static StorageType storage_get_type_by_path(FuriString* path) { StorageType type = ST_ERROR; const char* path_cstr = furi_string_get_cstr(path); - if(furi_string_size(path) == 4) { - if(memcmp(path_cstr, STORAGE_EXT_PATH_PREFIX, strlen(STORAGE_EXT_PATH_PREFIX)) == 0) { - type = ST_EXT; - } else if(memcmp(path_cstr, STORAGE_INT_PATH_PREFIX, strlen(STORAGE_INT_PATH_PREFIX)) == 0) { - type = ST_INT; - } else if(memcmp(path_cstr, STORAGE_ANY_PATH_PREFIX, strlen(STORAGE_ANY_PATH_PREFIX)) == 0) { - type = ST_ANY; - } - } else if(furi_string_size(path) > 4) { - if(memcmp(path_cstr, EXT_PATH(""), strlen(EXT_PATH(""))) == 0) { - type = ST_EXT; - } else if(memcmp(path_cstr, INT_PATH(""), strlen(INT_PATH(""))) == 0) { - type = ST_INT; - } else if(memcmp(path_cstr, ANY_PATH(""), strlen(ANY_PATH(""))) == 0) { - type = ST_ANY; + if(furi_string_size(path) > STORAGE_PATH_PREFIX_LEN) { + if(path_cstr[STORAGE_PATH_PREFIX_LEN] != '/') { + return ST_ERROR; } } + if(memcmp(path_cstr, STORAGE_EXT_PATH_PREFIX, strlen(STORAGE_EXT_PATH_PREFIX)) == 0) { + type = ST_EXT; + } else if(memcmp(path_cstr, STORAGE_INT_PATH_PREFIX, strlen(STORAGE_INT_PATH_PREFIX)) == 0) { + type = ST_INT; + } else if(memcmp(path_cstr, STORAGE_ANY_PATH_PREFIX, strlen(STORAGE_ANY_PATH_PREFIX)) == 0) { + type = ST_ANY; + } + return type; } - static void storage_path_change_to_real_storage(FuriString* path, StorageType real_storage) { if(furi_string_search(path, STORAGE_ANY_PATH_PREFIX) == 0) { switch(real_storage) { From 6ec62f48f99520711b601bb89c0f614f7e6143ca Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Mon, 20 Mar 2023 05:07:17 -0700 Subject: [PATCH 15/32] [FL-3180] OTP programmer: return exit code based on error type (#2504) --- scripts/flipper/utils/programmer_openocd.py | 27 ++++++++++---- scripts/otp.py | 39 +++++++++++++-------- 2 files changed, 45 insertions(+), 21 deletions(-) diff --git a/scripts/flipper/utils/programmer_openocd.py b/scripts/flipper/utils/programmer_openocd.py index b3340610..3d217185 100644 --- a/scripts/flipper/utils/programmer_openocd.py +++ b/scripts/flipper/utils/programmer_openocd.py @@ -1,6 +1,7 @@ import logging import os import typing +from enum import Enum from flipper.utils.programmer import Programmer from flipper.utils.openocd import OpenOCD @@ -8,6 +9,14 @@ from flipper.utils.stm32wb55 import STM32WB55 from flipper.assets.obdata import OptionBytesData +class OpenOCDProgrammerResult(Enum): + Success = 0 + ErrorGeneric = 1 + ErrorAlignment = 2 + ErrorAlreadyWritten = 3 + ErrorValidation = 4 + + class OpenOCDProgrammer(Programmer): def __init__( self, @@ -199,18 +208,18 @@ class OpenOCDProgrammer(Programmer): return True - def otp_write(self, address: int, file_path: str) -> bool: + def otp_write(self, address: int, file_path: str) -> OpenOCDProgrammerResult: # Open file, check that it aligned to 8 bytes with open(file_path, "rb") as f: data = f.read() if len(data) % 8 != 0: self.logger.error(f"File {file_path} is not aligned to 8 bytes") - return False + return OpenOCDProgrammerResult.ErrorAlignment # Check that address is aligned to 8 bytes if address % 8 != 0: self.logger.error(f"Address {address} is not aligned to 8 bytes") - return False + return OpenOCDProgrammerResult.ErrorAlignment # Get size of data data_size = len(data) @@ -218,7 +227,7 @@ class OpenOCDProgrammer(Programmer): # Check that data size is aligned to 8 bytes if data_size % 8 != 0: self.logger.error(f"Data size {data_size} is not aligned to 8 bytes") - return False + return OpenOCDProgrammerResult.ErrorAlignment self.logger.debug(f"Writing {data_size} bytes to OTP at {address:08X}") self.logger.debug(f"Data: {data.hex().upper()}") @@ -241,14 +250,14 @@ class OpenOCDProgrammer(Programmer): self.logger.error( f"OTP memory at {address + i:08X} is not empty: {device_word:08X}" ) - raise Exception("OTP memory is not empty") + return OpenOCDProgrammerResult.ErrorAlreadyWritten if device_word != file_word: already_written = False if already_written: self.logger.info(f"OTP memory is already written with the given data") - return True + return OpenOCDProgrammerResult.Success self.reset(self.RunMode.Stop) stm32.clear_flash_errors(oocd) @@ -278,4 +287,8 @@ class OpenOCDProgrammer(Programmer): stm32.reset(oocd, stm32.RunMode.Run) oocd.stop() - return validation_result + return ( + OpenOCDProgrammerResult.Success + if validation_result + else OpenOCDProgrammerResult.ErrorValidation + ) diff --git a/scripts/otp.py b/scripts/otp.py index 3bfe30d2..cb76bdc8 100755 --- a/scripts/otp.py +++ b/scripts/otp.py @@ -34,8 +34,16 @@ OTP_DISPLAYS = { } from flipper.app import App -from flipper.cube import CubeProgrammer -from flipper.utils.programmer_openocd import OpenOCDProgrammer +from flipper.utils.programmer_openocd import OpenOCDProgrammer, OpenOCDProgrammerResult + + +class OTPException(Exception): + def __init__(self, message: str, result: OpenOCDProgrammerResult): + self.message = message + self.result = result + + def get_exit_code(self) -> int: + return int(self.result.value) class Main(App): @@ -183,13 +191,14 @@ class Main(App): self.args.serial, ) - if not openocd.otp_write(0x1FFF7000, filename): - raise Exception("Failed to flash OTP") + programmer_result = openocd.otp_write(0x1FFF7000, filename) + if programmer_result != OpenOCDProgrammerResult.Success: + raise OTPException("Failed to flash OTP", programmer_result) self.logger.info(f"Flashed Successfully") - except Exception as e: + except OTPException as e: self.logger.exception(e) - return 1 + return e.get_exit_code() finally: os.remove(filename) @@ -215,13 +224,14 @@ class Main(App): self.args.serial, ) - if not openocd.otp_write(0x1FFF7010, filename): - raise Exception("Failed to flash OTP") + programmer_result = openocd.otp_write(0x1FFF7010, filename) + if programmer_result != OpenOCDProgrammerResult.Success: + raise OTPException("Failed to flash OTP", programmer_result) self.logger.info(f"Flashed Successfully") - except Exception as e: + except OTPException as e: self.logger.exception(e) - return 1 + return e.get_exit_code() finally: os.remove(filename) @@ -249,13 +259,14 @@ class Main(App): self.args.serial, ) - if not openocd.otp_write(0x1FFF7000, filename): - raise Exception("Failed to flash OTP") + programmer_result = openocd.otp_write(0x1FFF7000, filename) + if programmer_result != OpenOCDProgrammerResult.Success: + raise OTPException("Failed to flash OTP", programmer_result) self.logger.info(f"Flashed Successfully") - except Exception as e: + except OTPException as e: self.logger.exception(e) - return 1 + return e.get_exit_code() finally: os.remove(filename) From f7024cff786751309f67ca3cdbf6d527b9eb5214 Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Mon, 20 Mar 2023 06:09:10 -0700 Subject: [PATCH 16/32] SD Driver: reinit sd card on error (#2493) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * SD Driver: reinit sd card on error * SD Driver: cleanup fatfs bindings * Storage: optimized glue * Storage: move fatfs initialization to appropriate subsystems, minor code cleanup * SD Driver: minor code cleanup Co-authored-by: あく --- applications/services/storage/storage_glue.c | 61 ++--- applications/services/storage/storage_glue.h | 2 +- .../services/storage/storage_processing.c | 8 +- .../services/storage/storages/storage_ext.c | 4 +- firmware/targets/f18/furi_hal/furi_hal.c | 6 - firmware/targets/f7/fatfs/fatfs.c | 47 +--- firmware/targets/f7/fatfs/fatfs.h | 50 +--- firmware/targets/f7/fatfs/ffconf.h | 2 +- firmware/targets/f7/fatfs/syscall.c | 116 --------- firmware/targets/f7/fatfs/user_diskio.c | 228 ++++++++---------- firmware/targets/f7/fatfs/user_diskio.h | 40 +-- firmware/targets/f7/furi_hal/furi_hal.c | 6 - firmware/targets/f7/src/update.c | 2 +- 13 files changed, 154 insertions(+), 418 deletions(-) delete mode 100644 firmware/targets/f7/fatfs/syscall.c diff --git a/applications/services/storage/storage_glue.c b/applications/services/storage/storage_glue.c index 5dabfa38..63e44c9d 100644 --- a/applications/services/storage/storage_glue.c +++ b/applications/services/storage/storage_glue.c @@ -73,29 +73,34 @@ uint32_t storage_data_get_timestamp(StorageData* storage) { /****************** storage glue ******************/ -bool storage_has_file(const File* file, StorageData* storage_data) { - bool result = false; +static StorageFile* storage_get_file(const File* file, StorageData* storage) { + StorageFile* storage_file_ref = NULL; StorageFileList_it_t it; - for(StorageFileList_it(it, storage_data->files); !StorageFileList_end_p(it); + for(StorageFileList_it(it, storage->files); !StorageFileList_end_p(it); StorageFileList_next(it)) { - const StorageFile* storage_file = StorageFileList_cref(it); + StorageFile* storage_file = StorageFileList_ref(it); if(storage_file->file->file_id == file->file_id) { - result = true; + storage_file_ref = storage_file; break; } } - return result; + return storage_file_ref; } -bool storage_path_already_open(FuriString* path, StorageFileList_t array) { +bool storage_has_file(const File* file, StorageData* storage) { + return storage_get_file(file, storage) != NULL; +} + +bool storage_path_already_open(FuriString* path, StorageData* storage) { bool open = false; StorageFileList_it_t it; - for(StorageFileList_it(it, array); !StorageFileList_end_p(it); StorageFileList_next(it)) { + for(StorageFileList_it(it, storage->files); !StorageFileList_end_p(it); + StorageFileList_next(it)) { const StorageFile* storage_file = StorageFileList_cref(it); if(furi_string_cmp(storage_file->path, path) == 0) { @@ -108,43 +113,15 @@ bool storage_path_already_open(FuriString* path, StorageFileList_t array) { } void storage_set_storage_file_data(const File* file, void* file_data, StorageData* storage) { - StorageFile* founded_file = NULL; - - StorageFileList_it_t it; - - for(StorageFileList_it(it, storage->files); !StorageFileList_end_p(it); - StorageFileList_next(it)) { - StorageFile* storage_file = StorageFileList_ref(it); - - if(storage_file->file->file_id == file->file_id) { - founded_file = storage_file; - break; - } - } - - furi_check(founded_file != NULL); - - founded_file->file_data = file_data; + StorageFile* storage_file_ref = storage_get_file(file, storage); + furi_check(storage_file_ref != NULL); + storage_file_ref->file_data = file_data; } void* storage_get_storage_file_data(const File* file, StorageData* storage) { - const StorageFile* founded_file = NULL; - - StorageFileList_it_t it; - - for(StorageFileList_it(it, storage->files); !StorageFileList_end_p(it); - StorageFileList_next(it)) { - const StorageFile* storage_file = StorageFileList_cref(it); - - if(storage_file->file->file_id == file->file_id) { - founded_file = storage_file; - break; - } - } - - furi_check(founded_file != NULL); - - return founded_file->file_data; + StorageFile* storage_file_ref = storage_get_file(file, storage); + furi_check(storage_file_ref != NULL); + return storage_file_ref->file_data; } void storage_push_storage_file(File* file, FuriString* path, StorageData* storage) { diff --git a/applications/services/storage/storage_glue.h b/applications/services/storage/storage_glue.h index bf0a1c69..f1064034 100644 --- a/applications/services/storage/storage_glue.h +++ b/applications/services/storage/storage_glue.h @@ -60,7 +60,7 @@ struct StorageData { }; bool storage_has_file(const File* file, StorageData* storage_data); -bool storage_path_already_open(FuriString* path, StorageFileList_t files); +bool storage_path_already_open(FuriString* path, StorageData* storage_data); void storage_set_storage_file_data(const File* file, void* file_data, StorageData* storage); void* storage_get_storage_file_data(const File* file, StorageData* storage); diff --git a/applications/services/storage/storage_processing.c b/applications/services/storage/storage_processing.c index a3076f27..e6b42696 100644 --- a/applications/services/storage/storage_processing.c +++ b/applications/services/storage/storage_processing.c @@ -77,7 +77,7 @@ static void storage_path_change_to_real_storage(FuriString* path, StorageType re } } -FS_Error storage_get_data(Storage* app, FuriString* path, StorageData** storage) { +static FS_Error storage_get_data(Storage* app, FuriString* path, StorageData** storage) { StorageType type = storage_get_type_by_path(path); if(storage_type_is_valid(type)) { @@ -111,7 +111,7 @@ bool storage_process_file_open( file->error_id = storage_get_data(app, path, &storage); if(file->error_id == FSE_OK) { - if(storage_path_already_open(path, storage->files)) { + if(storage_path_already_open(path, storage)) { file->error_id = FSE_ALREADY_OPEN; } else { if(access_mode & FSAM_WRITE) { @@ -268,7 +268,7 @@ bool storage_process_dir_open(Storage* app, File* file, FuriString* path) { file->error_id = storage_get_data(app, path, &storage); if(file->error_id == FSE_OK) { - if(storage_path_already_open(path, storage->files)) { + if(storage_path_already_open(path, storage)) { file->error_id = FSE_ALREADY_OPEN; } else { storage_push_storage_file(file, path, storage); @@ -357,7 +357,7 @@ static FS_Error storage_process_common_remove(Storage* app, FuriString* path) { FS_Error ret = storage_get_data(app, path, &storage); do { - if(storage_path_already_open(path, storage->files)) { + if(storage_path_already_open(path, storage)) { ret = FSE_ALREADY_OPEN; break; } diff --git a/applications/services/storage/storages/storage_ext.c b/applications/services/storage/storages/storage_ext.c index 530c88f8..d802d6e9 100644 --- a/applications/services/storage/storages/storage_ext.c +++ b/applications/services/storage/storages/storage_ext.c @@ -618,8 +618,10 @@ static const FS_Api fs_api = { }; void storage_ext_init(StorageData* storage) { + fatfs_init(); + SDData* sd_data = malloc(sizeof(SDData)); - sd_data->fs = &USERFatFS; + sd_data->fs = &fatfs_object; sd_data->path = "0:/"; sd_data->sd_was_present = true; diff --git a/firmware/targets/f18/furi_hal/furi_hal.c b/firmware/targets/f18/furi_hal/furi_hal.c index 0a68fdb6..2c255fa0 100644 --- a/firmware/targets/f18/furi_hal/furi_hal.c +++ b/firmware/targets/f18/furi_hal/furi_hal.c @@ -3,8 +3,6 @@ #include -#include - #define TAG "FuriHal" void furi_hal_init_early() { @@ -74,10 +72,6 @@ void furi_hal_init() { #endif furi_hal_bt_init(); furi_hal_compress_icon_init(); - - // FatFS driver initialization - MX_FATFS_Init(); - FURI_LOG_I(TAG, "FATFS OK"); } void furi_hal_switch(void* address) { diff --git a/firmware/targets/f7/fatfs/fatfs.c b/firmware/targets/f7/fatfs/fatfs.c index 1aa5fe44..2c0e77fe 100644 --- a/firmware/targets/f7/fatfs/fatfs.c +++ b/firmware/targets/f7/fatfs/fatfs.c @@ -1,39 +1,12 @@ -/** - ****************************************************************************** - * @file fatfs.c - * @brief Code for fatfs applications - ****************************************************************************** - * @attention - * - *

© Copyright (c) 2020 STMicroelectronics. - * All rights reserved.

- * - * This software component is licensed by ST under Ultimate Liberty license - * SLA0044, the "License"; You may not use this file except in compliance with - * the License. You may obtain a copy of the License at: - * www.st.com/SLA0044 - * - ****************************************************************************** - */ - #include "fatfs.h" -uint8_t retUSER; /* Return value for USER */ -char USERPath[4]; /* USER logical drive path */ -FATFS USERFatFS; /* File system object for USER logical drive */ -FIL USERFile; /* File object for USER */ +/** logical drive path */ +char fatfs_path[4]; +/** File system object */ +FATFS fatfs_object; -/* USER CODE BEGIN Variables */ - -/* USER CODE END Variables */ - -void MX_FATFS_Init(void) { - /*## FatFS: Link the USER driver ###########################*/ - retUSER = FATFS_LinkDriver(&USER_Driver, USERPath); - - /* USER CODE BEGIN Init */ - /* additional user code for init */ - /* USER CODE END Init */ +void fatfs_init(void) { + FATFS_LinkDriver(&sd_fatfs_driver, fatfs_path); } /** @@ -42,13 +15,5 @@ void MX_FATFS_Init(void) { * @retval Time in DWORD */ DWORD get_fattime(void) { - /* USER CODE BEGIN get_fattime */ return 0; - /* USER CODE END get_fattime */ } - -/* USER CODE BEGIN Application */ - -/* USER CODE END Application */ - -/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ diff --git a/firmware/targets/f7/fatfs/fatfs.h b/firmware/targets/f7/fatfs/fatfs.h index a0775d88..8376bf6c 100644 --- a/firmware/targets/f7/fatfs/fatfs.h +++ b/firmware/targets/f7/fatfs/fatfs.h @@ -1,49 +1,19 @@ -/** - ****************************************************************************** - * @file fatfs.h - * @brief Header for fatfs applications - ****************************************************************************** - * @attention - * - *

© Copyright (c) 2020 STMicroelectronics. - * All rights reserved.

- * - * This software component is licensed by ST under Ultimate Liberty license - * SLA0044, the "License"; You may not use this file except in compliance with - * the License. You may obtain a copy of the License at: - * www.st.com/SLA0044 - * - ****************************************************************************** - */ +#pragma once + +#include "fatfs/ff.h" +#include "fatfs/ff_gen_drv.h" +#include "user_diskio.h" -/* Define to prevent recursive inclusion -------------------------------------*/ -#ifndef __fatfs_H -#define __fatfs_H #ifdef __cplusplus extern "C" { #endif -#include "fatfs/ff.h" -#include "fatfs/ff_gen_drv.h" -#include "user_diskio.h" /* defines USER_Driver as external */ +/** File system object */ +extern FATFS fatfs_object; -/* USER CODE BEGIN Includes */ +/** Init file system driver */ +void fatfs_init(void); -/* USER CODE END Includes */ - -extern uint8_t retUSER; /* Return value for USER */ -extern char USERPath[4]; /* USER logical drive path */ -extern FATFS USERFatFS; /* File system object for USER logical drive */ -extern FIL USERFile; /* File object for USER */ - -void MX_FATFS_Init(void); - -/* USER CODE BEGIN Prototypes */ - -/* USER CODE END Prototypes */ #ifdef __cplusplus } -#endif -#endif /*__fatfs_H */ - -/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ +#endif \ No newline at end of file diff --git a/firmware/targets/f7/fatfs/ffconf.h b/firmware/targets/f7/fatfs/ffconf.h index 9410cedc..a4452155 100644 --- a/firmware/targets/f7/fatfs/ffconf.h +++ b/firmware/targets/f7/fatfs/ffconf.h @@ -164,7 +164,7 @@ /* USER CODE BEGIN Volumes */ #define _STR_VOLUME_ID 0 /* 0:Use only 0-9 for drive ID, 1:Use strings for drive ID */ -#define _VOLUME_STRS "RAM", "NAND", "CF", "SD1", "SD2", "USB1", "USB2", "USB3" +#define _VOLUME_STRS "SD" /* _STR_VOLUME_ID switches string support of volume ID. / When _STR_VOLUME_ID is set to 1, also pre-defined strings can be used as drive / number in the path name. _VOLUME_STRS defines the drive ID strings for each diff --git a/firmware/targets/f7/fatfs/syscall.c b/firmware/targets/f7/fatfs/syscall.c deleted file mode 100644 index 00eb8aed..00000000 --- a/firmware/targets/f7/fatfs/syscall.c +++ /dev/null @@ -1,116 +0,0 @@ -/*------------------------------------------------------------------------*/ -/* Sample code of OS dependent controls for FatFs */ -/* (C)ChaN, 2014 */ -/* Portions COPYRIGHT 2017 STMicroelectronics */ -/* Portions Copyright (C) 2014, ChaN, all right reserved */ -/*------------------------------------------------------------------------*/ - -/** - ****************************************************************************** - * @attention - * - * Copyright (c) 2017 STMicroelectronics. All rights reserved. - * - * This software component is licensed by ST under BSD 3-Clause license, - * the "License"; You may not use this file except in compliance with the - * License. You may obtain a copy of the License at: - * opensource.org/licenses/BSD-3-Clause - * - ****************************************************************************** -**/ - -#include "fatfs/ff.h" - -#if _FS_REENTRANT -/*------------------------------------------------------------------------*/ -/* Create a Synchronization Object */ -/*------------------------------------------------------------------------*/ -/* This function is called in f_mount() function to create a new -/ synchronization object, such as semaphore and mutex. When a 0 is returned, -/ the f_mount() function fails with FR_INT_ERR. -*/ - -int ff_cre_syncobj(/* 1:Function succeeded, 0:Could not create the sync object */ - BYTE vol, /* Corresponding volume (logical drive number) */ - _SYNC_t* sobj /* Pointer to return the created sync object */ -) { - int ret; - - //osSemaphoreDef(SEM); - //*sobj = osSemaphoreCreate(osSemaphore(SEM), 1); - *sobj = furi_mutex_alloc(FuriMutexTypeNormal); - ret = (*sobj != NULL); - - return ret; -} - -/*------------------------------------------------------------------------*/ -/* Delete a Synchronization Object */ -/*------------------------------------------------------------------------*/ -/* This function is called in f_mount() function to delete a synchronization -/ object that created with ff_cre_syncobj() function. When a 0 is returned, -/ the f_mount() function fails with FR_INT_ERR. -*/ - -int ff_del_syncobj(/* 1:Function succeeded, 0:Could not delete due to any error */ - _SYNC_t sobj /* Sync object tied to the logical drive to be deleted */ -) { - furi_mutex_free(sobj); - return 1; -} - -/*------------------------------------------------------------------------*/ -/* Request Grant to Access the Volume */ -/*------------------------------------------------------------------------*/ -/* This function is called on entering file functions to lock the volume. -/ When a 0 is returned, the file function fails with FR_TIMEOUT. -*/ - -int ff_req_grant(/* 1:Got a grant to access the volume, 0:Could not get a grant */ - _SYNC_t sobj /* Sync object to wait */ -) { - int ret = 0; - - if(furi_mutex_acquire(sobj, _FS_TIMEOUT) == FuriStatusOk) { - ret = 1; - } - - return ret; -} - -/*------------------------------------------------------------------------*/ -/* Release Grant to Access the Volume */ -/*------------------------------------------------------------------------*/ -/* This function is called on leaving file functions to unlock the volume. -*/ - -void ff_rel_grant(_SYNC_t sobj /* Sync object to be signaled */ -) { - furi_mutex_release(sobj); -} - -#endif - -#if _USE_LFN == 3 /* LFN with a working buffer on the heap */ -/*------------------------------------------------------------------------*/ -/* Allocate a memory block */ -/*------------------------------------------------------------------------*/ -/* If a NULL is returned, the file function fails with FR_NOT_ENOUGH_CORE. -*/ - -void* ff_memalloc(/* Returns pointer to the allocated memory block */ - UINT msize /* Number of bytes to allocate */ -) { - return ff_malloc(msize); /* Allocate a new memory block with POSIX API */ -} - -/*------------------------------------------------------------------------*/ -/* Free a memory block */ -/*------------------------------------------------------------------------*/ - -void ff_memfree(void* mblock /* Pointer to the memory block to free */ -) { - ff_free(mblock); /* Discard the memory block with POSIX API */ -} - -#endif diff --git a/firmware/targets/f7/fatfs/user_diskio.c b/firmware/targets/f7/fatfs/user_diskio.c index d7be09c5..74bf26f6 100644 --- a/firmware/targets/f7/fatfs/user_diskio.c +++ b/firmware/targets/f7/fatfs/user_diskio.c @@ -1,50 +1,10 @@ -/* USER CODE BEGIN Header */ -/** - ****************************************************************************** - * @file user_diskio.c - * @brief This file includes a diskio driver skeleton to be completed by the user. - ****************************************************************************** - * @attention - * - *

© Copyright (c) 2020 STMicroelectronics. - * All rights reserved.

- * - * This software component is licensed by ST under Ultimate Liberty license - * SLA0044, the "License"; You may not use this file except in compliance with - * the License. You may obtain a copy of the License at: - * www.st.com/SLA0044 - * - ****************************************************************************** - */ -/* USER CODE END Header */ - -#ifdef USE_OBSOLETE_USER_CODE_SECTION_0 -/* - * Warning: the user section 0 is no more in use (starting from CubeMx version 4.16.0) - * To be suppressed in the future. - * Kept to ensure backward compatibility with previous CubeMx versions when - * migrating projects. - * User code previously added there should be copied in the new user sections before - * the section contents can be deleted. - */ -/* USER CODE BEGIN 0 */ -/* USER CODE END 0 */ -#endif - -/* USER CODE BEGIN DECL */ - -/* Includes ------------------------------------------------------------------*/ #include "user_diskio.h" #include #include "sector_cache.h" -/* Private typedef -----------------------------------------------------------*/ -/* Private define ------------------------------------------------------------*/ -/* Private variables ---------------------------------------------------------*/ -/* Disk status */ static volatile DSTATUS Stat = STA_NOINIT; -static DSTATUS User_CheckStatus(BYTE lun) { +static DSTATUS driver_check_status(BYTE lun) { UNUSED(lun); Stat = STA_NOINIT; if(sd_get_card_state() == SdSpiStatusOK) { @@ -54,32 +14,20 @@ static DSTATUS User_CheckStatus(BYTE lun) { return Stat; } -/* USER CODE END DECL */ +static DSTATUS driver_initialize(BYTE pdrv); +static DSTATUS driver_status(BYTE pdrv); +static DRESULT driver_read(BYTE pdrv, BYTE* buff, DWORD sector, UINT count); +static DRESULT driver_write(BYTE pdrv, const BYTE* buff, DWORD sector, UINT count); +static DRESULT driver_ioctl(BYTE pdrv, BYTE cmd, void* buff); -/* Private function prototypes -----------------------------------------------*/ -DSTATUS USER_initialize(BYTE pdrv); -DSTATUS USER_status(BYTE pdrv); -DRESULT USER_read(BYTE pdrv, BYTE* buff, DWORD sector, UINT count); -#if _USE_WRITE == 1 -DRESULT USER_write(BYTE pdrv, const BYTE* buff, DWORD sector, UINT count); -#endif /* _USE_WRITE == 1 */ -#if _USE_IOCTL == 1 -DRESULT USER_ioctl(BYTE pdrv, BYTE cmd, void* buff); -#endif /* _USE_IOCTL == 1 */ - -Diskio_drvTypeDef USER_Driver = { - USER_initialize, - USER_status, - USER_read, -#if _USE_WRITE - USER_write, -#endif /* _USE_WRITE == 1 */ -#if _USE_IOCTL == 1 - USER_ioctl, -#endif /* _USE_IOCTL == 1 */ +Diskio_drvTypeDef sd_fatfs_driver = { + driver_initialize, + driver_status, + driver_read, + driver_write, + driver_ioctl, }; -/* Private functions ---------------------------------------------------------*/ static inline bool sd_cache_get(uint32_t address, uint32_t* data) { uint8_t* cached_data = sector_cache_get(address); if(cached_data) { @@ -101,24 +49,73 @@ static inline void sd_cache_invalidate_all() { sector_cache_init(); } +static bool sd_device_read(uint32_t* buff, uint32_t sector, uint32_t count) { + bool result = false; + + furi_hal_spi_acquire(&furi_hal_spi_bus_handle_sd_fast); + furi_hal_sd_spi_handle = &furi_hal_spi_bus_handle_sd_fast; + + if(sd_read_blocks(buff, sector, count, SD_TIMEOUT_MS) == SdSpiStatusOK) { + FuriHalCortexTimer timer = furi_hal_cortex_timer_get(SD_TIMEOUT_MS * 1000); + + /* wait until the read operation is finished */ + result = true; + while(sd_get_card_state() != SdSpiStatusOK) { + if(furi_hal_cortex_timer_is_expired(timer)) { + result = false; + break; + } + } + } + + furi_hal_sd_spi_handle = NULL; + furi_hal_spi_release(&furi_hal_spi_bus_handle_sd_fast); + + return result; +} + +static bool sd_device_write(uint32_t* buff, uint32_t sector, uint32_t count) { + bool result = false; + + furi_hal_spi_acquire(&furi_hal_spi_bus_handle_sd_fast); + furi_hal_sd_spi_handle = &furi_hal_spi_bus_handle_sd_fast; + + if(sd_write_blocks(buff, sector, count, SD_TIMEOUT_MS) == SdSpiStatusOK) { + FuriHalCortexTimer timer = furi_hal_cortex_timer_get(SD_TIMEOUT_MS * 1000); + + /* wait until the Write operation is finished */ + result = true; + while(sd_get_card_state() != SdSpiStatusOK) { + if(furi_hal_cortex_timer_is_expired(timer)) { + sd_cache_invalidate_all(); + + result = false; + break; + } + } + } + + furi_hal_sd_spi_handle = NULL; + furi_hal_spi_release(&furi_hal_spi_bus_handle_sd_fast); + + return result; +} + /** * @brief Initializes a Drive * @param pdrv: Physical drive number (0..) * @retval DSTATUS: Operation status */ -DSTATUS USER_initialize(BYTE pdrv) { - /* USER CODE BEGIN INIT */ - +static DSTATUS driver_initialize(BYTE pdrv) { furi_hal_spi_acquire(&furi_hal_spi_bus_handle_sd_fast); furi_hal_sd_spi_handle = &furi_hal_spi_bus_handle_sd_fast; - DSTATUS status = User_CheckStatus(pdrv); + DSTATUS status = driver_check_status(pdrv); furi_hal_sd_spi_handle = NULL; furi_hal_spi_release(&furi_hal_spi_bus_handle_sd_fast); return status; - /* USER CODE END INIT */ } /** @@ -126,11 +123,9 @@ DSTATUS USER_initialize(BYTE pdrv) { * @param pdrv: Physical drive number (0..) * @retval DSTATUS: Operation status */ -DSTATUS USER_status(BYTE pdrv) { - /* USER CODE BEGIN STATUS */ +static DSTATUS driver_status(BYTE pdrv) { UNUSED(pdrv); return Stat; - /* USER CODE END STATUS */ } /** @@ -141,11 +136,10 @@ DSTATUS USER_status(BYTE pdrv) { * @param count: Number of sectors to read (1..128) * @retval DRESULT: Operation result */ -DRESULT USER_read(BYTE pdrv, BYTE* buff, DWORD sector, UINT count) { - /* USER CODE BEGIN READ */ +static DRESULT driver_read(BYTE pdrv, BYTE* buff, DWORD sector, UINT count) { UNUSED(pdrv); - DRESULT res = RES_ERROR; + bool result; bool single_sector = count == 1; if(single_sector) { @@ -154,32 +148,33 @@ DRESULT USER_read(BYTE pdrv, BYTE* buff, DWORD sector, UINT count) { } } - furi_hal_spi_acquire(&furi_hal_spi_bus_handle_sd_fast); - furi_hal_sd_spi_handle = &furi_hal_spi_bus_handle_sd_fast; + result = sd_device_read((uint32_t*)buff, (uint32_t)(sector), count); - if(sd_read_blocks((uint32_t*)buff, (uint32_t)(sector), count, SD_TIMEOUT_MS) == - SdSpiStatusOK) { - FuriHalCortexTimer timer = furi_hal_cortex_timer_get(SD_TIMEOUT_MS * 1000); + if(!result) { + uint8_t counter = sd_max_mount_retry_count(); - /* wait until the read operation is finished */ - res = RES_OK; - while(sd_get_card_state() != SdSpiStatusOK) { - if(furi_hal_cortex_timer_is_expired(timer)) { - res = RES_ERROR; - break; + while(result == false && counter > 0 && hal_sd_detect()) { + SdSpiStatus status; + + if((counter % 2) == 0) { + // power reset sd card + status = sd_init(true); + } else { + status = sd_init(false); } + + if(status == SdSpiStatusOK) { + result = sd_device_read((uint32_t*)buff, (uint32_t)(sector), count); + } + counter--; } } - furi_hal_sd_spi_handle = NULL; - furi_hal_spi_release(&furi_hal_spi_bus_handle_sd_fast); - - if(single_sector && res == RES_OK) { + if(single_sector && result == true) { sd_cache_put(sector, (uint32_t*)buff); } - return res; - /* USER CODE END READ */ + return result ? RES_OK : RES_ERROR; } /** @@ -190,41 +185,36 @@ DRESULT USER_read(BYTE pdrv, BYTE* buff, DWORD sector, UINT count) { * @param count: Number of sectors to write (1..128) * @retval DRESULT: Operation result */ -#if _USE_WRITE == 1 -DRESULT USER_write(BYTE pdrv, const BYTE* buff, DWORD sector, UINT count) { - /* USER CODE BEGIN WRITE */ - /* USER CODE HERE */ +static DRESULT driver_write(BYTE pdrv, const BYTE* buff, DWORD sector, UINT count) { UNUSED(pdrv); - DRESULT res = RES_ERROR; + bool result; sd_cache_invalidate_range(sector, sector + count); - furi_hal_spi_acquire(&furi_hal_spi_bus_handle_sd_fast); - furi_hal_sd_spi_handle = &furi_hal_spi_bus_handle_sd_fast; + result = sd_device_write((uint32_t*)buff, (uint32_t)(sector), count); - if(sd_write_blocks((uint32_t*)buff, (uint32_t)(sector), count, SD_TIMEOUT_MS) == - SdSpiStatusOK) { - FuriHalCortexTimer timer = furi_hal_cortex_timer_get(SD_TIMEOUT_MS * 1000); + if(!result) { + uint8_t counter = sd_max_mount_retry_count(); - /* wait until the Write operation is finished */ - res = RES_OK; - while(sd_get_card_state() != SdSpiStatusOK) { - if(furi_hal_cortex_timer_is_expired(timer)) { - sd_cache_invalidate_all(); + while(result == false && counter > 0 && hal_sd_detect()) { + SdSpiStatus status; - res = RES_ERROR; - break; + if((counter % 2) == 0) { + // power reset sd card + status = sd_init(true); + } else { + status = sd_init(false); } + + if(status == SdSpiStatusOK) { + result = sd_device_write((uint32_t*)buff, (uint32_t)(sector), count); + } + counter--; } } - furi_hal_sd_spi_handle = NULL; - furi_hal_spi_release(&furi_hal_spi_bus_handle_sd_fast); - - return res; - /* USER CODE END WRITE */ + return result ? RES_OK : RES_ERROR; } -#endif /* _USE_WRITE == 1 */ /** * @brief I/O control operation @@ -233,9 +223,7 @@ DRESULT USER_write(BYTE pdrv, const BYTE* buff, DWORD sector, UINT count) { * @param *buff: Buffer to send/receive control data * @retval DRESULT: Operation result */ -#if _USE_IOCTL == 1 -DRESULT USER_ioctl(BYTE pdrv, BYTE cmd, void* buff) { - /* USER CODE BEGIN IOCTL */ +static DRESULT driver_ioctl(BYTE pdrv, BYTE cmd, void* buff) { UNUSED(pdrv); DRESULT res = RES_ERROR; SD_CardInfo CardInfo; @@ -280,8 +268,4 @@ DRESULT USER_ioctl(BYTE pdrv, BYTE cmd, void* buff) { furi_hal_spi_release(&furi_hal_spi_bus_handle_sd_fast); return res; - /* USER CODE END IOCTL */ } -#endif /* _USE_IOCTL == 1 */ - -/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ diff --git a/firmware/targets/f7/fatfs/user_diskio.h b/firmware/targets/f7/fatfs/user_diskio.h index 12e0f27d..7b3f2bb9 100644 --- a/firmware/targets/f7/fatfs/user_diskio.h +++ b/firmware/targets/f7/fatfs/user_diskio.h @@ -1,48 +1,14 @@ -/* USER CODE BEGIN Header */ -/** - ****************************************************************************** - * @file user_diskio.h - * @brief This file contains the common defines and functions prototypes for - * the user_diskio driver. - ****************************************************************************** - * @attention - * - *

© Copyright (c) 2020 STMicroelectronics. - * All rights reserved.

- * - * This software component is licensed by ST under Ultimate Liberty license - * SLA0044, the "License"; You may not use this file except in compliance with - * the License. You may obtain a copy of the License at: - * www.st.com/SLA0044 - * - ****************************************************************************** - */ -/* USER CODE END Header */ - -/* Define to prevent recursive inclusion -------------------------------------*/ -#ifndef __USER_DISKIO_H -#define __USER_DISKIO_H +#pragma once #ifdef __cplusplus extern "C" { #endif -/* USER CODE BEGIN 0 */ - -/* Includes ------------------------------------------------------------------*/ #include "sd_spi_io.h" #include "fatfs/ff_gen_drv.h" -/* Exported types ------------------------------------------------------------*/ -/* Exported constants --------------------------------------------------------*/ -/* Exported functions ------------------------------------------------------- */ -extern Diskio_drvTypeDef USER_Driver; -/* USER CODE END 0 */ +extern Diskio_drvTypeDef sd_fatfs_driver; #ifdef __cplusplus } -#endif - -#endif /* __USER_DISKIO_H */ - -/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ +#endif \ No newline at end of file diff --git a/firmware/targets/f7/furi_hal/furi_hal.c b/firmware/targets/f7/furi_hal/furi_hal.c index afe46c4e..5840a697 100644 --- a/firmware/targets/f7/furi_hal/furi_hal.c +++ b/firmware/targets/f7/furi_hal/furi_hal.c @@ -4,8 +4,6 @@ #include -#include - #define TAG "FuriHal" void furi_hal_init_early() { @@ -81,10 +79,6 @@ void furi_hal_init() { furi_hal_nfc_init(); furi_hal_rfid_init(); #endif - - // FatFS driver initialization - MX_FATFS_Init(); - FURI_LOG_I(TAG, "FATFS OK"); } void furi_hal_switch(void* address) { diff --git a/firmware/targets/f7/src/update.c b/firmware/targets/f7/src/update.c index d8d26eb7..c1e1084c 100644 --- a/firmware/targets/f7/src/update.c +++ b/firmware/targets/f7/src/update.c @@ -44,7 +44,7 @@ static bool flipper_update_init() { furi_hal_spi_config_init(); - MX_FATFS_Init(); + fatfs_init(); if(!hal_sd_detect()) { return false; } From 60ac2e98810fa9638f9955ea7fa6a553d1cedaa9 Mon Sep 17 00:00:00 2001 From: hedger Date: Mon, 20 Mar 2023 19:03:55 +0400 Subject: [PATCH 17/32] [FL-3161] Improved debugging experience for external apps (#2507) * debug: automated support for multiple debug symbol files * faploader: extra checks for app list state * debug: trigger BP before fap's EP if under debugger * faploader, debug: better naming * docs: info on load breakpoint * faploader: header cleanup * faploader: naming fixes * debug: less verbose; setting debug flag more often * typo fix --- applications/main/fap_loader/fap_loader_app.c | 19 ++- debug/flipperapps.py | 113 +++++++++++------- documentation/AppsOnSDCard.md | 2 + lib/flipper_application/elf/elf_file.c | 5 +- lib/flipper_application/flipper_application.c | 47 ++++++-- 5 files changed, 127 insertions(+), 59 deletions(-) diff --git a/applications/main/fap_loader/fap_loader_app.c b/applications/main/fap_loader/fap_loader_app.c index dcbad8e1..f5c7af02 100644 --- a/applications/main/fap_loader/fap_loader_app.c +++ b/applications/main/fap_loader/fap_loader_app.c @@ -1,16 +1,17 @@ +#include "fap_loader_app.h" + #include -#include + #include +#include #include -#include #include #include #include #include #include -#include "fap_loader_app.h" -#define TAG "fap_loader_app" +#define TAG "FapLoader" struct FapLoader { FlipperApplication* app; @@ -22,6 +23,8 @@ struct FapLoader { Loading* loading; }; +volatile bool fap_loader_debug_active = false; + bool fap_loader_load_name_and_icon( FuriString* path, Storage* storage, @@ -107,6 +110,14 @@ static bool fap_loader_run_selected_app(FapLoader* loader) { FuriThread* thread = flipper_application_spawn(loader->app, NULL); + /* This flag is set by the debugger - to break on app start */ + if(fap_loader_debug_active) { + FURI_LOG_W(TAG, "Triggering BP for debugger"); + /* After hitting this, you can set breakpoints in your .fap's code + * Note that you have to toggle breakpoints that were set before */ + __asm volatile("bkpt 0"); + } + FuriString* app_name = furi_string_alloc(); path_extract_filename_no_ext(furi_string_get_cstr(loader->fap_path), app_name); furi_thread_set_appid(thread, furi_string_get_cstr(app_name)); diff --git a/debug/flipperapps.py b/debug/flipperapps.py index e815e40b..1dc5ebd0 100644 --- a/debug/flipperapps.py +++ b/debug/flipperapps.py @@ -2,7 +2,6 @@ from dataclasses import dataclass from typing import Optional, Tuple, Dict, ClassVar import struct import posixpath -import os import zlib import gdb @@ -66,9 +65,9 @@ class AppState: def get_gdb_unload_command(self) -> str: return f"remove-symbol-file -a 0x{self.text_address:08x}" - def is_loaded_in_gdb(self, gdb_app) -> bool: - # Avoid constructing full app wrapper for comparison - return self.entry_address == int(gdb_app["state"]["entry"]) + @staticmethod + def get_gdb_app_ep(app) -> int: + return int(app["state"]["entry"]) @staticmethod def parse_debug_link_data(section_data: bytes) -> Tuple[str, int]: @@ -79,10 +78,10 @@ class AppState: crc32 = struct.unpack(" "AppState": + @classmethod + def from_gdb(cls, gdb_app: "AppState") -> "AppState": state = AppState(str(gdb_app["manifest"]["name"].string())) - state.entry_address = int(gdb_app["state"]["entry"]) + state.entry_address = cls.get_gdb_app_ep(gdb_app) app_state = gdb_app["state"] if debug_link_size := int(app_state["debug_link_info"]["debug_link_size"]): @@ -123,59 +122,83 @@ class SetFapDebugElfRoot(gdb.Command): try: global helper print(f"Set '{arg}' as debug info lookup path for Flipper external apps") - helper.attach_fw() + helper.attach_to_fw() gdb.events.stop.connect(helper.handle_stop) + gdb.events.exited.connect(helper.handle_exit) except gdb.error as e: print(f"Support for Flipper external apps debug is not available: {e}") -SetFapDebugElfRoot() - - -class FlipperAppDebugHelper: +class FlipperAppStateHelper: def __init__(self): - self.app_ptr = None self.app_type_ptr = None - self.current_app: AppState = None + self.app_list_ptr = None + self.app_list_entry_type = None + self._current_apps: list[AppState] = [] - def attach_fw(self) -> None: - self.app_ptr = gdb.lookup_global_symbol("last_loaded_app") - self.app_type_ptr = gdb.lookup_type("FlipperApplication").pointer() - self._check_app_state() + def _walk_app_list(self, list_head): + while list_head: + if app := list_head["data"]: + yield app.dereference() + list_head = list_head["next"] - def _check_app_state(self) -> None: - app_ptr_value = self.app_ptr.value() - if not app_ptr_value and self.current_app: - # There is an ELF loaded in GDB, but nothing is running on the device - self._unload_debug_elf() - elif app_ptr_value: - # There is an app running on the device - loaded_app = app_ptr_value.cast(self.app_type_ptr).dereference() - - if self.current_app and not self.current_app.is_loaded_in_gdb(loaded_app): - # Currently loaded ELF is not the one running on the device - self._unload_debug_elf() - - if not self.current_app: - # Load ELF for the app running on the device - self._load_debug_elf(loaded_app) - - def _unload_debug_elf(self) -> None: + def _exec_gdb_command(self, command: str) -> bool: try: - gdb.execute(self.current_app.get_gdb_unload_command()) + gdb.execute(command) + return True except gdb.error as e: - print(f"Failed to unload debug ELF: {e} (might not be an error)") - self.current_app = None + print(f"Failed to execute GDB command '{command}': {e}") + return False - def _load_debug_elf(self, app_object) -> None: - self.current_app = AppState.from_gdb(app_object) + def _sync_apps(self) -> None: + self.set_debug_mode(True) + if not (app_list := self.app_list_ptr.value()): + print("Reset app loader state") + for app in self._current_apps: + self._exec_gdb_command(app.get_gdb_unload_command()) + self._current_apps = [] + return - if self.current_app.is_debug_available(): - gdb.execute(self.current_app.get_gdb_load_command()) + loaded_apps: dict[int, gdb.Value] = dict( + (AppState.get_gdb_app_ep(app), app) + for app in self._walk_app_list(app_list[0]) + ) + + for app in self._current_apps.copy(): + if app.entry_address not in loaded_apps: + print(f"Application {app.name} is no longer loaded") + if not self._exec_gdb_command(app.get_gdb_unload_command()): + print(f"Failed to unload debug info for {app.name}") + self._current_apps.remove(app) + + for entry_point, app in loaded_apps.items(): + if entry_point not in set(app.entry_address for app in self._current_apps): + new_app_state = AppState.from_gdb(app) + print(f"New application loaded. Adding debug info") + if self._exec_gdb_command(new_app_state.get_gdb_load_command()): + self._current_apps.append(new_app_state) + else: + print(f"Failed to load debug info for {new_app_state}") + + def attach_to_fw(self) -> None: + print("Attaching to Flipper firmware") + self.app_list_ptr = gdb.lookup_global_symbol( + "flipper_application_loaded_app_list" + ) + self.app_type_ptr = gdb.lookup_type("FlipperApplication").pointer() + self.app_list_entry_type = gdb.lookup_type("struct FlipperApplicationList_s") def handle_stop(self, event) -> None: - self._check_app_state() + self._sync_apps() + + def handle_exit(self, event) -> None: + self.set_debug_mode(False) + + def set_debug_mode(self, mode: bool) -> None: + gdb.execute(f"set variable fap_loader_debug_active = {int(mode)}") -helper = FlipperAppDebugHelper() +# Init additional 'fap-set-debug-elf-root' command and set up hooks +SetFapDebugElfRoot() +helper = FlipperAppStateHelper() print("Support for Flipper external apps debug is loaded") diff --git a/documentation/AppsOnSDCard.md b/documentation/AppsOnSDCard.md index 9ab7e9b2..75430570 100644 --- a/documentation/AppsOnSDCard.md +++ b/documentation/AppsOnSDCard.md @@ -32,6 +32,8 @@ Images and animated icons should follow the same [naming convention](../assets/R With it, you can debug FAPs as if they were a part of the main firmware — inspect variables, set breakpoints, step through the code, etc. +If debugging session is active, firmware will trigger a breakpoint after loading a FAP it into memory, but before running any code from it. This allows you to set breakpoints in the FAP's code. Note that any breakpoints set before the FAP is loaded may need re-setting after the FAP is actually loaded, since before loading it debugger cannot know the exact address of the FAP's code. + ### Setting up debugging environment The debugging support script looks up debugging information in the latest firmware build directory (`build/latest`). That directory is symlinked by `fbt` to the latest firmware configuration (Debug or Release) build directory when you run `./fbt` for the chosen configuration. See [fbt docs](./fbt.md#nb) for details. diff --git a/lib/flipper_application/elf/elf_file.c b/lib/flipper_application/elf/elf_file.c index 146afccb..0338144a 100644 --- a/lib/flipper_application/elf/elf_file.c +++ b/lib/flipper_application/elf/elf_file.c @@ -830,8 +830,9 @@ void elf_file_init_debug_info(ELFFile* elf, ELFDebugInfo* debug_info) { const void* data_ptr = itref->value.data; if(data_ptr) { - debug_info->mmap_entries[mmap_entry_idx].address = (uint32_t)data_ptr; - debug_info->mmap_entries[mmap_entry_idx].name = itref->key; + ELFMemoryMapEntry* entry = &debug_info->mmap_entries[mmap_entry_idx]; + entry->address = (uint32_t)data_ptr; + entry->name = itref->key; mmap_entry_idx++; } } diff --git a/lib/flipper_application/flipper_application.c b/lib/flipper_application/flipper_application.c index ca917cf1..1b4f5681 100644 --- a/lib/flipper_application/flipper_application.c +++ b/lib/flipper_application/flipper_application.c @@ -3,7 +3,9 @@ #include #include "application_assets.h" -#define TAG "fapp" +#include + +#define TAG "Fap" struct FlipperApplication { ELFDebugInfo state; @@ -13,8 +15,39 @@ struct FlipperApplication { void* ep_thread_args; }; -/* For debugger access to app state */ -FlipperApplication* last_loaded_app = NULL; +/********************** Debugger access to loader state **********************/ + +LIST_DEF(FlipperApplicationList, const FlipperApplication*, M_POD_OPLIST); + +FlipperApplicationList_t flipper_application_loaded_app_list = {0}; +static bool flipper_application_loaded_app_list_initialized = false; + +static void flipper_application_list_add_app(const FlipperApplication* app) { + furi_assert(app); + + if(!flipper_application_loaded_app_list_initialized) { + FlipperApplicationList_init(flipper_application_loaded_app_list); + flipper_application_loaded_app_list_initialized = true; + } + FlipperApplicationList_push_back(flipper_application_loaded_app_list, app); +} + +static void flipper_application_list_remove_app(const FlipperApplication* app) { + furi_assert(flipper_application_loaded_app_list_initialized); + furi_assert(app); + + FlipperApplicationList_it_t it; + for(FlipperApplicationList_it(it, flipper_application_loaded_app_list); + !FlipperApplicationList_end_p(it); + FlipperApplicationList_next(it)) { + if(*FlipperApplicationList_ref(it) == app) { + FlipperApplicationList_remove(flipper_application_loaded_app_list, it); + break; + } + } +} + +/*****************************************************************************/ FlipperApplication* flipper_application_alloc(Storage* storage, const ElfApiInterface* api_interface) { @@ -37,8 +70,8 @@ void flipper_application_free(FlipperApplication* app) { furi_thread_free(app->thread); } - if(!flipper_application_is_plugin(app)) { - last_loaded_app = NULL; + if(app->state.entry) { + flipper_application_list_remove_app(app); } elf_file_clear_debug_info(&app->state); @@ -153,14 +186,12 @@ const FlipperApplicationManifest* flipper_application_get_manifest(FlipperApplic } FlipperApplicationLoadStatus flipper_application_map_to_memory(FlipperApplication* app) { - if(!flipper_application_is_plugin(app)) { - last_loaded_app = app; - } ELFFileLoadStatus status = elf_file_load_sections(app->elf); switch(status) { case ELFFileLoadStatusSuccess: elf_file_init_debug_info(app->elf, &app->state); + flipper_application_list_add_app(app); return FlipperApplicationLoadStatusSuccess; case ELFFileLoadStatusNoFreeMemory: return FlipperApplicationLoadStatusNoFreeMemory; From 1d91a572cc15fc452272baec9cbd09a7fdf7f57a Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Mon, 20 Mar 2023 18:22:40 +0300 Subject: [PATCH 18/32] [FL-3182] Fix typos in iButton (#2506) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix a typo: right shift instead of greater than * Fix a typo: proper iButton part number Co-authored-by: あく --- documentation/file_formats/iButtonFileFormat.md | 2 +- lib/one_wire/ibutton/protocols/dallas/dallas_common.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/file_formats/iButtonFileFormat.md b/documentation/file_formats/iButtonFileFormat.md index d31c297a..63743f06 100644 --- a/documentation/file_formats/iButtonFileFormat.md +++ b/documentation/file_formats/iButtonFileFormat.md @@ -26,7 +26,7 @@ Changelog: | Name | Type | Description | | ----------- | ------ | -------------------------------------------- | -| Protocol | string | Currently supported: DS1990, DS1992, DS1996, DS1997, DSGeneric*, Cyfral, Metakom | +| Protocol | string | Currently supported: DS1990, DS1992, DS1996, DS1971, DSGeneric*, Cyfral, Metakom | | Rom Data | hex | Read-only memory data (Dallas protocols only) | | Sram Data | hex | Static RAM data (DS1992 and DS1996 only) | Eeprom Data | hex | EEPROM data (DS1971 only) diff --git a/lib/one_wire/ibutton/protocols/dallas/dallas_common.c b/lib/one_wire/ibutton/protocols/dallas/dallas_common.c index 22b25db8..ebf57e55 100644 --- a/lib/one_wire/ibutton/protocols/dallas/dallas_common.c +++ b/lib/one_wire/ibutton/protocols/dallas/dallas_common.c @@ -85,7 +85,7 @@ bool dallas_common_read_mem(OneWireHost* host, uint16_t address, uint8_t* data, onewire_host_write(host, DALLAS_COMMON_CMD_READ_MEM); onewire_host_write(host, (uint8_t)address); - onewire_host_write(host, (uint8_t)(address > BITS_IN_BYTE)); + onewire_host_write(host, (uint8_t)(address >> BITS_IN_BYTE)); onewire_host_read_bytes(host, data, (uint16_t)data_size); From 0917494a80da701dd787e2d06aa08b12e1b71099 Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Mon, 20 Mar 2023 19:23:17 +0300 Subject: [PATCH 19/32] [FL-3168] Add one_wire lib to f18, separate ibutton (#2509) * Separate ibutton to its own module, add one_wire to f18 * Move onewire cli to a separate app Co-authored-by: Aleksandr Kutuzov --- applications/main/application.fam | 1 + applications/main/ibutton/ibutton_cli.c | 64 +---------------- applications/main/ibutton/ibutton_i.h | 4 +- applications/main/onewire/application.fam | 14 ++++ applications/main/onewire/onewire_cli.c | 72 +++++++++++++++++++ firmware/targets/f18/api_symbols.csv | 31 ++++++++ firmware/targets/f18/target.json | 5 +- firmware/targets/f7/api_symbols.csv | 6 +- firmware/targets/f7/target.json | 3 +- lib/SConscript | 1 + lib/ibutton/SConscript | 24 +++++++ lib/{one_wire => }/ibutton/ibutton_key.c | 0 lib/{one_wire => }/ibutton/ibutton_key.h | 0 lib/{one_wire => }/ibutton/ibutton_key_i.h | 0 .../ibutton/ibutton_protocols.c | 0 .../ibutton/ibutton_protocols.h | 0 lib/{one_wire => }/ibutton/ibutton_worker.c | 0 lib/{one_wire => }/ibutton/ibutton_worker.h | 0 lib/{one_wire => }/ibutton/ibutton_worker_i.h | 0 .../ibutton/ibutton_worker_modes.c | 0 .../ibutton/protocols/blanks/rw1990.c | 0 .../ibutton/protocols/blanks/rw1990.h | 0 .../ibutton/protocols/blanks/tm2004.c | 0 .../ibutton/protocols/blanks/tm2004.h | 0 .../ibutton/protocols/dallas/dallas_common.c | 0 .../ibutton/protocols/dallas/dallas_common.h | 0 .../protocols/dallas/protocol_dallas_base.h | 0 .../protocols/dallas/protocol_ds1971.c | 0 .../protocols/dallas/protocol_ds1971.h | 0 .../protocols/dallas/protocol_ds1990.c | 0 .../protocols/dallas/protocol_ds1990.h | 0 .../protocols/dallas/protocol_ds1992.c | 0 .../protocols/dallas/protocol_ds1992.h | 0 .../protocols/dallas/protocol_ds1996.c | 0 .../protocols/dallas/protocol_ds1996.h | 0 .../protocols/dallas/protocol_ds_generic.c | 0 .../protocols/dallas/protocol_ds_generic.h | 0 .../protocols/dallas/protocol_group_dallas.c | 0 .../protocols/dallas/protocol_group_dallas.h | 0 .../dallas/protocol_group_dallas_defs.c | 0 .../dallas/protocol_group_dallas_defs.h | 0 .../ibutton/protocols/misc/protocol_cyfral.c | 0 .../ibutton/protocols/misc/protocol_cyfral.h | 0 .../protocols/misc/protocol_group_misc.c | 0 .../protocols/misc/protocol_group_misc.h | 0 .../protocols/misc/protocol_group_misc_defs.c | 0 .../protocols/misc/protocol_group_misc_defs.h | 0 .../ibutton/protocols/misc/protocol_metakom.c | 0 .../ibutton/protocols/misc/protocol_metakom.h | 0 .../ibutton/protocols/protocol_common.h | 0 .../ibutton/protocols/protocol_common_i.h | 0 .../ibutton/protocols/protocol_group_base.h | 0 .../ibutton/protocols/protocol_group_defs.c | 0 .../ibutton/protocols/protocol_group_defs.h | 0 lib/one_wire/SConscript | 3 - 55 files changed, 156 insertions(+), 72 deletions(-) create mode 100644 applications/main/onewire/application.fam create mode 100644 applications/main/onewire/onewire_cli.c create mode 100644 lib/ibutton/SConscript rename lib/{one_wire => }/ibutton/ibutton_key.c (100%) rename lib/{one_wire => }/ibutton/ibutton_key.h (100%) rename lib/{one_wire => }/ibutton/ibutton_key_i.h (100%) rename lib/{one_wire => }/ibutton/ibutton_protocols.c (100%) rename lib/{one_wire => }/ibutton/ibutton_protocols.h (100%) rename lib/{one_wire => }/ibutton/ibutton_worker.c (100%) rename lib/{one_wire => }/ibutton/ibutton_worker.h (100%) rename lib/{one_wire => }/ibutton/ibutton_worker_i.h (100%) rename lib/{one_wire => }/ibutton/ibutton_worker_modes.c (100%) rename lib/{one_wire => }/ibutton/protocols/blanks/rw1990.c (100%) rename lib/{one_wire => }/ibutton/protocols/blanks/rw1990.h (100%) rename lib/{one_wire => }/ibutton/protocols/blanks/tm2004.c (100%) rename lib/{one_wire => }/ibutton/protocols/blanks/tm2004.h (100%) rename lib/{one_wire => }/ibutton/protocols/dallas/dallas_common.c (100%) rename lib/{one_wire => }/ibutton/protocols/dallas/dallas_common.h (100%) rename lib/{one_wire => }/ibutton/protocols/dallas/protocol_dallas_base.h (100%) rename lib/{one_wire => }/ibutton/protocols/dallas/protocol_ds1971.c (100%) rename lib/{one_wire => }/ibutton/protocols/dallas/protocol_ds1971.h (100%) rename lib/{one_wire => }/ibutton/protocols/dallas/protocol_ds1990.c (100%) rename lib/{one_wire => }/ibutton/protocols/dallas/protocol_ds1990.h (100%) rename lib/{one_wire => }/ibutton/protocols/dallas/protocol_ds1992.c (100%) rename lib/{one_wire => }/ibutton/protocols/dallas/protocol_ds1992.h (100%) rename lib/{one_wire => }/ibutton/protocols/dallas/protocol_ds1996.c (100%) rename lib/{one_wire => }/ibutton/protocols/dallas/protocol_ds1996.h (100%) rename lib/{one_wire => }/ibutton/protocols/dallas/protocol_ds_generic.c (100%) rename lib/{one_wire => }/ibutton/protocols/dallas/protocol_ds_generic.h (100%) rename lib/{one_wire => }/ibutton/protocols/dallas/protocol_group_dallas.c (100%) rename lib/{one_wire => }/ibutton/protocols/dallas/protocol_group_dallas.h (100%) rename lib/{one_wire => }/ibutton/protocols/dallas/protocol_group_dallas_defs.c (100%) rename lib/{one_wire => }/ibutton/protocols/dallas/protocol_group_dallas_defs.h (100%) rename lib/{one_wire => }/ibutton/protocols/misc/protocol_cyfral.c (100%) rename lib/{one_wire => }/ibutton/protocols/misc/protocol_cyfral.h (100%) rename lib/{one_wire => }/ibutton/protocols/misc/protocol_group_misc.c (100%) rename lib/{one_wire => }/ibutton/protocols/misc/protocol_group_misc.h (100%) rename lib/{one_wire => }/ibutton/protocols/misc/protocol_group_misc_defs.c (100%) rename lib/{one_wire => }/ibutton/protocols/misc/protocol_group_misc_defs.h (100%) rename lib/{one_wire => }/ibutton/protocols/misc/protocol_metakom.c (100%) rename lib/{one_wire => }/ibutton/protocols/misc/protocol_metakom.h (100%) rename lib/{one_wire => }/ibutton/protocols/protocol_common.h (100%) rename lib/{one_wire => }/ibutton/protocols/protocol_common_i.h (100%) rename lib/{one_wire => }/ibutton/protocols/protocol_group_base.h (100%) rename lib/{one_wire => }/ibutton/protocols/protocol_group_defs.c (100%) rename lib/{one_wire => }/ibutton/protocols/protocol_group_defs.h (100%) diff --git a/applications/main/application.fam b/applications/main/application.fam index 1fc30990..5c2c21d3 100644 --- a/applications/main/application.fam +++ b/applications/main/application.fam @@ -4,6 +4,7 @@ App( apptype=FlipperAppType.METAPACKAGE, provides=[ "gpio", + "onewire", "ibutton", "infrared", "lfrfid", diff --git a/applications/main/ibutton/ibutton_cli.c b/applications/main/ibutton/ibutton_cli.c index 2b88b200..54bc808b 100644 --- a/applications/main/ibutton/ibutton_cli.c +++ b/applications/main/ibutton/ibutton_cli.c @@ -4,25 +4,20 @@ #include #include -#include - -#include -#include -#include +#include +#include +#include static void ibutton_cli(Cli* cli, FuriString* args, void* context); -static void onewire_cli(Cli* cli, FuriString* args, void* context); // app cli function void ibutton_on_system_start() { #ifdef SRV_CLI Cli* cli = furi_record_open(RECORD_CLI); cli_add_command(cli, "ikey", CliCommandFlagDefault, ibutton_cli, cli); - cli_add_command(cli, "onewire", CliCommandFlagDefault, onewire_cli, cli); furi_record_close(RECORD_CLI); #else UNUSED(ibutton_cli); - UNUSED(onewire_cli); #endif } @@ -257,56 +252,3 @@ void ibutton_cli(Cli* cli, FuriString* args, void* context) { furi_string_free(cmd); } - -static void onewire_cli_print_usage() { - printf("Usage:\r\n"); - printf("onewire search\r\n"); -}; - -static void onewire_cli_search(Cli* cli) { - UNUSED(cli); - OneWireHost* onewire = onewire_host_alloc(&ibutton_gpio); - uint8_t address[8]; - bool done = false; - - printf("Search started\r\n"); - - onewire_host_start(onewire); - furi_hal_power_enable_otg(); - - while(!done) { - if(onewire_host_search(onewire, address, OneWireHostSearchModeNormal) != 1) { - printf("Search finished\r\n"); - onewire_host_reset_search(onewire); - done = true; - } else { - printf("Found: "); - for(uint8_t i = 0; i < 8; i++) { - printf("%02X", address[i]); - } - printf("\r\n"); - } - furi_delay_ms(100); - } - - furi_hal_power_disable_otg(); - onewire_host_free(onewire); -} - -void onewire_cli(Cli* cli, FuriString* args, void* context) { - UNUSED(context); - FuriString* cmd; - cmd = furi_string_alloc(); - - if(!args_read_string_and_trim(args, cmd)) { - furi_string_free(cmd); - onewire_cli_print_usage(); - return; - } - - if(furi_string_cmp_str(cmd, "search") == 0) { - onewire_cli_search(cli); - } - - furi_string_free(cmd); -} diff --git a/applications/main/ibutton/ibutton_i.h b/applications/main/ibutton/ibutton_i.h index 8ad0b90e..50927921 100644 --- a/applications/main/ibutton/ibutton_i.h +++ b/applications/main/ibutton/ibutton_i.h @@ -7,8 +7,8 @@ #include #include -#include -#include +#include +#include #include #include diff --git a/applications/main/onewire/application.fam b/applications/main/onewire/application.fam new file mode 100644 index 00000000..68d4f671 --- /dev/null +++ b/applications/main/onewire/application.fam @@ -0,0 +1,14 @@ +App( + appid="onewire", + name="1-Wire", + apptype=FlipperAppType.METAPACKAGE, + provides=["onewire_start"], +) + +App( + appid="onewire_start", + apptype=FlipperAppType.STARTUP, + entry_point="onewire_on_system_start", + requires=["onewire"], + order=60, +) diff --git a/applications/main/onewire/onewire_cli.c b/applications/main/onewire/onewire_cli.c new file mode 100644 index 00000000..4c16fb38 --- /dev/null +++ b/applications/main/onewire/onewire_cli.c @@ -0,0 +1,72 @@ +#include +#include + +#include +#include + +#include + +static void onewire_cli(Cli* cli, FuriString* args, void* context); + +void onewire_on_system_start() { +#ifdef SRV_CLI + Cli* cli = furi_record_open(RECORD_CLI); + cli_add_command(cli, "onewire", CliCommandFlagDefault, onewire_cli, cli); + furi_record_close(RECORD_CLI); +#else + UNUSED(onewire_cli); +#endif +} + +static void onewire_cli_print_usage() { + printf("Usage:\r\n"); + printf("onewire search\r\n"); +}; + +static void onewire_cli_search(Cli* cli) { + UNUSED(cli); + OneWireHost* onewire = onewire_host_alloc(&ibutton_gpio); + uint8_t address[8]; + bool done = false; + + printf("Search started\r\n"); + + onewire_host_start(onewire); + furi_hal_power_enable_otg(); + + while(!done) { + if(onewire_host_search(onewire, address, OneWireHostSearchModeNormal) != 1) { + printf("Search finished\r\n"); + onewire_host_reset_search(onewire); + done = true; + } else { + printf("Found: "); + for(uint8_t i = 0; i < 8; i++) { + printf("%02X", address[i]); + } + printf("\r\n"); + } + furi_delay_ms(100); + } + + furi_hal_power_disable_otg(); + onewire_host_free(onewire); +} + +void onewire_cli(Cli* cli, FuriString* args, void* context) { + UNUSED(context); + FuriString* cmd; + cmd = furi_string_alloc(); + + if(!args_read_string_and_trim(args, cmd)) { + furi_string_free(cmd); + onewire_cli_print_usage(); + return; + } + + if(furi_string_cmp_str(cmd, "search") == 0) { + onewire_cli_search(cli); + } + + furi_string_free(cmd); +} diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index 61195aba..7fa269c9 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -151,6 +151,10 @@ Header,+,lib/mlib/m-list.h,, Header,+,lib/mlib/m-rbtree.h,, Header,+,lib/mlib/m-tuple.h,, Header,+,lib/mlib/m-variant.h,, +Header,+,lib/one_wire/maxim_crc.h,, +Header,+,lib/one_wire/one_wire_host.h,, +Header,+,lib/one_wire/one_wire_host_timing.h,, +Header,+,lib/one_wire/one_wire_slave.h,, Header,+,lib/print/wrappers.h,, Header,+,lib/toolbox/args.h,, Header,+,lib/toolbox/crc32_calc.h,, @@ -1394,6 +1398,7 @@ Function,+,manchester_advance,_Bool,"ManchesterState, ManchesterEvent, Mancheste Function,+,manchester_encoder_advance,_Bool,"ManchesterEncoderState*, const _Bool, ManchesterEncoderResult*" Function,+,manchester_encoder_finish,ManchesterEncoderResult,ManchesterEncoderState* Function,+,manchester_encoder_reset,void,ManchesterEncoderState* +Function,+,maxim_crc8,uint8_t,"const uint8_t*, const uint8_t, const uint8_t" Function,-,mbedtls_des3_crypt_cbc,int,"mbedtls_des3_context*, int, size_t, unsigned char[8], const unsigned char*, unsigned char*" Function,-,mbedtls_des3_crypt_ecb,int,"mbedtls_des3_context*, const unsigned char[8], unsigned char[8]" Function,-,mbedtls_des3_free,void,mbedtls_des3_context* @@ -1472,6 +1477,32 @@ Function,+,notification_message,void,"NotificationApp*, const NotificationSequen Function,+,notification_message_block,void,"NotificationApp*, const NotificationSequence*" Function,-,nrand48,long,unsigned short[3] Function,-,on_exit,int,"void (*)(int, void*), void*" +Function,+,onewire_host_alloc,OneWireHost*,const GpioPin* +Function,+,onewire_host_free,void,OneWireHost* +Function,+,onewire_host_read,uint8_t,OneWireHost* +Function,+,onewire_host_read_bit,_Bool,OneWireHost* +Function,+,onewire_host_read_bytes,void,"OneWireHost*, uint8_t*, uint16_t" +Function,+,onewire_host_reset,_Bool,OneWireHost* +Function,+,onewire_host_reset_search,void,OneWireHost* +Function,+,onewire_host_search,uint8_t,"OneWireHost*, uint8_t*, OneWireHostSearchMode" +Function,+,onewire_host_skip,void,OneWireHost* +Function,+,onewire_host_start,void,OneWireHost* +Function,+,onewire_host_stop,void,OneWireHost* +Function,+,onewire_host_target_search,void,"OneWireHost*, uint8_t" +Function,+,onewire_host_write,void,"OneWireHost*, uint8_t" +Function,+,onewire_host_write_bit,void,"OneWireHost*, _Bool" +Function,+,onewire_host_write_bytes,void,"OneWireHost*, const uint8_t*, uint16_t" +Function,+,onewire_slave_alloc,OneWireSlave*,const GpioPin* +Function,+,onewire_slave_free,void,OneWireSlave* +Function,+,onewire_slave_receive,_Bool,"OneWireSlave*, uint8_t*, size_t" +Function,+,onewire_slave_receive_bit,_Bool,OneWireSlave* +Function,+,onewire_slave_send,_Bool,"OneWireSlave*, const uint8_t*, size_t" +Function,+,onewire_slave_send_bit,_Bool,"OneWireSlave*, _Bool" +Function,+,onewire_slave_set_command_callback,void,"OneWireSlave*, OneWireSlaveCommandCallback, void*" +Function,+,onewire_slave_set_reset_callback,void,"OneWireSlave*, OneWireSlaveResetCallback, void*" +Function,+,onewire_slave_set_result_callback,void,"OneWireSlave*, OneWireSlaveResultCallback, void*" +Function,+,onewire_slave_start,void,OneWireSlave* +Function,+,onewire_slave_stop,void,OneWireSlave* Function,-,open_memstream,FILE*,"char**, size_t*" Function,+,path_append,void,"FuriString*, const char*" Function,+,path_concat,void,"const char*, const char*, FuriString*" diff --git a/firmware/targets/f18/target.json b/firmware/targets/f18/target.json index 2c3b27ab..f1963fb0 100644 --- a/firmware/targets/f18/target.json +++ b/firmware/targets/f18/target.json @@ -24,6 +24,7 @@ "usb_stm32", "appframe", "assets", + "one_wire", "misc", "flipper_application", "flipperformat", @@ -45,11 +46,11 @@ "furi_hal_subghz_configs.h" ], "excluded_modules": [ - "one_wire", "nfc", "lfrfid", "subghz", + "ibutton", "infrared", "st25rfal002" ] -} \ No newline at end of file +} diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index e46322f4..c98e5467 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -118,6 +118,9 @@ Header,+,lib/flipper_application/plugins/composite_resolver.h,, Header,+,lib/flipper_application/plugins/plugin_manager.h,, Header,+,lib/flipper_format/flipper_format.h,, Header,+,lib/flipper_format/flipper_format_i.h,, +Header,+,lib/ibutton/ibutton_key.h,, +Header,+,lib/ibutton/ibutton_protocols.h,, +Header,+,lib/ibutton/ibutton_worker.h,, Header,+,lib/infrared/encoder_decoder/infrared.h,, Header,+,lib/infrared/worker/infrared_transmit.h,, Header,+,lib/infrared/worker/infrared_worker.h,, @@ -167,9 +170,6 @@ 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/one_wire/ibutton/ibutton_key.h,, -Header,+,lib/one_wire/ibutton/ibutton_protocols.h,, -Header,+,lib/one_wire/ibutton/ibutton_worker.h,, Header,+,lib/one_wire/maxim_crc.h,, Header,+,lib/one_wire/one_wire_host.h,, Header,+,lib/one_wire/one_wire_host_timing.h,, diff --git a/firmware/targets/f7/target.json b/firmware/targets/f7/target.json index 49aa109b..14bb1cd0 100644 --- a/firmware/targets/f7/target.json +++ b/firmware/targets/f7/target.json @@ -35,6 +35,7 @@ "appframe", "assets", "one_wire", + "ibutton", "misc", "mbedtls", "lfrfid", @@ -42,4 +43,4 @@ "flipperformat", "toolbox" ] -} \ No newline at end of file +} diff --git a/lib/SConscript b/lib/SConscript index d1e8e8c7..f5d4689f 100644 --- a/lib/SConscript +++ b/lib/SConscript @@ -87,6 +87,7 @@ libs = env.BuildModules( "fatfs", "flipper_format", "one_wire", + "ibutton", "infrared", "littlefs", "mbedtls", diff --git a/lib/ibutton/SConscript b/lib/ibutton/SConscript new file mode 100644 index 00000000..238d65f7 --- /dev/null +++ b/lib/ibutton/SConscript @@ -0,0 +1,24 @@ +Import("env") + +env.Append( + LINT_SOURCES=[ + Dir("."), + ], + CPPPATH=[ + "#/lib/ibutton", + ], + SDK_HEADERS=[ + File("ibutton_key.h"), + File("ibutton_worker.h"), + File("ibutton_protocols.h"), + ], +) + +libenv = env.Clone(FW_LIB_NAME="ibutton") +libenv.ApplyLibFlags() + +sources = libenv.GlobRecursive("*.c*") + +lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources) +libenv.Install("${LIB_DIST_DIR}", lib) +Return("lib") diff --git a/lib/one_wire/ibutton/ibutton_key.c b/lib/ibutton/ibutton_key.c similarity index 100% rename from lib/one_wire/ibutton/ibutton_key.c rename to lib/ibutton/ibutton_key.c diff --git a/lib/one_wire/ibutton/ibutton_key.h b/lib/ibutton/ibutton_key.h similarity index 100% rename from lib/one_wire/ibutton/ibutton_key.h rename to lib/ibutton/ibutton_key.h diff --git a/lib/one_wire/ibutton/ibutton_key_i.h b/lib/ibutton/ibutton_key_i.h similarity index 100% rename from lib/one_wire/ibutton/ibutton_key_i.h rename to lib/ibutton/ibutton_key_i.h diff --git a/lib/one_wire/ibutton/ibutton_protocols.c b/lib/ibutton/ibutton_protocols.c similarity index 100% rename from lib/one_wire/ibutton/ibutton_protocols.c rename to lib/ibutton/ibutton_protocols.c diff --git a/lib/one_wire/ibutton/ibutton_protocols.h b/lib/ibutton/ibutton_protocols.h similarity index 100% rename from lib/one_wire/ibutton/ibutton_protocols.h rename to lib/ibutton/ibutton_protocols.h diff --git a/lib/one_wire/ibutton/ibutton_worker.c b/lib/ibutton/ibutton_worker.c similarity index 100% rename from lib/one_wire/ibutton/ibutton_worker.c rename to lib/ibutton/ibutton_worker.c diff --git a/lib/one_wire/ibutton/ibutton_worker.h b/lib/ibutton/ibutton_worker.h similarity index 100% rename from lib/one_wire/ibutton/ibutton_worker.h rename to lib/ibutton/ibutton_worker.h diff --git a/lib/one_wire/ibutton/ibutton_worker_i.h b/lib/ibutton/ibutton_worker_i.h similarity index 100% rename from lib/one_wire/ibutton/ibutton_worker_i.h rename to lib/ibutton/ibutton_worker_i.h diff --git a/lib/one_wire/ibutton/ibutton_worker_modes.c b/lib/ibutton/ibutton_worker_modes.c similarity index 100% rename from lib/one_wire/ibutton/ibutton_worker_modes.c rename to lib/ibutton/ibutton_worker_modes.c diff --git a/lib/one_wire/ibutton/protocols/blanks/rw1990.c b/lib/ibutton/protocols/blanks/rw1990.c similarity index 100% rename from lib/one_wire/ibutton/protocols/blanks/rw1990.c rename to lib/ibutton/protocols/blanks/rw1990.c diff --git a/lib/one_wire/ibutton/protocols/blanks/rw1990.h b/lib/ibutton/protocols/blanks/rw1990.h similarity index 100% rename from lib/one_wire/ibutton/protocols/blanks/rw1990.h rename to lib/ibutton/protocols/blanks/rw1990.h diff --git a/lib/one_wire/ibutton/protocols/blanks/tm2004.c b/lib/ibutton/protocols/blanks/tm2004.c similarity index 100% rename from lib/one_wire/ibutton/protocols/blanks/tm2004.c rename to lib/ibutton/protocols/blanks/tm2004.c diff --git a/lib/one_wire/ibutton/protocols/blanks/tm2004.h b/lib/ibutton/protocols/blanks/tm2004.h similarity index 100% rename from lib/one_wire/ibutton/protocols/blanks/tm2004.h rename to lib/ibutton/protocols/blanks/tm2004.h diff --git a/lib/one_wire/ibutton/protocols/dallas/dallas_common.c b/lib/ibutton/protocols/dallas/dallas_common.c similarity index 100% rename from lib/one_wire/ibutton/protocols/dallas/dallas_common.c rename to lib/ibutton/protocols/dallas/dallas_common.c diff --git a/lib/one_wire/ibutton/protocols/dallas/dallas_common.h b/lib/ibutton/protocols/dallas/dallas_common.h similarity index 100% rename from lib/one_wire/ibutton/protocols/dallas/dallas_common.h rename to lib/ibutton/protocols/dallas/dallas_common.h diff --git a/lib/one_wire/ibutton/protocols/dallas/protocol_dallas_base.h b/lib/ibutton/protocols/dallas/protocol_dallas_base.h similarity index 100% rename from lib/one_wire/ibutton/protocols/dallas/protocol_dallas_base.h rename to lib/ibutton/protocols/dallas/protocol_dallas_base.h diff --git a/lib/one_wire/ibutton/protocols/dallas/protocol_ds1971.c b/lib/ibutton/protocols/dallas/protocol_ds1971.c similarity index 100% rename from lib/one_wire/ibutton/protocols/dallas/protocol_ds1971.c rename to lib/ibutton/protocols/dallas/protocol_ds1971.c diff --git a/lib/one_wire/ibutton/protocols/dallas/protocol_ds1971.h b/lib/ibutton/protocols/dallas/protocol_ds1971.h similarity index 100% rename from lib/one_wire/ibutton/protocols/dallas/protocol_ds1971.h rename to lib/ibutton/protocols/dallas/protocol_ds1971.h diff --git a/lib/one_wire/ibutton/protocols/dallas/protocol_ds1990.c b/lib/ibutton/protocols/dallas/protocol_ds1990.c similarity index 100% rename from lib/one_wire/ibutton/protocols/dallas/protocol_ds1990.c rename to lib/ibutton/protocols/dallas/protocol_ds1990.c diff --git a/lib/one_wire/ibutton/protocols/dallas/protocol_ds1990.h b/lib/ibutton/protocols/dallas/protocol_ds1990.h similarity index 100% rename from lib/one_wire/ibutton/protocols/dallas/protocol_ds1990.h rename to lib/ibutton/protocols/dallas/protocol_ds1990.h diff --git a/lib/one_wire/ibutton/protocols/dallas/protocol_ds1992.c b/lib/ibutton/protocols/dallas/protocol_ds1992.c similarity index 100% rename from lib/one_wire/ibutton/protocols/dallas/protocol_ds1992.c rename to lib/ibutton/protocols/dallas/protocol_ds1992.c diff --git a/lib/one_wire/ibutton/protocols/dallas/protocol_ds1992.h b/lib/ibutton/protocols/dallas/protocol_ds1992.h similarity index 100% rename from lib/one_wire/ibutton/protocols/dallas/protocol_ds1992.h rename to lib/ibutton/protocols/dallas/protocol_ds1992.h diff --git a/lib/one_wire/ibutton/protocols/dallas/protocol_ds1996.c b/lib/ibutton/protocols/dallas/protocol_ds1996.c similarity index 100% rename from lib/one_wire/ibutton/protocols/dallas/protocol_ds1996.c rename to lib/ibutton/protocols/dallas/protocol_ds1996.c diff --git a/lib/one_wire/ibutton/protocols/dallas/protocol_ds1996.h b/lib/ibutton/protocols/dallas/protocol_ds1996.h similarity index 100% rename from lib/one_wire/ibutton/protocols/dallas/protocol_ds1996.h rename to lib/ibutton/protocols/dallas/protocol_ds1996.h diff --git a/lib/one_wire/ibutton/protocols/dallas/protocol_ds_generic.c b/lib/ibutton/protocols/dallas/protocol_ds_generic.c similarity index 100% rename from lib/one_wire/ibutton/protocols/dallas/protocol_ds_generic.c rename to lib/ibutton/protocols/dallas/protocol_ds_generic.c diff --git a/lib/one_wire/ibutton/protocols/dallas/protocol_ds_generic.h b/lib/ibutton/protocols/dallas/protocol_ds_generic.h similarity index 100% rename from lib/one_wire/ibutton/protocols/dallas/protocol_ds_generic.h rename to lib/ibutton/protocols/dallas/protocol_ds_generic.h diff --git a/lib/one_wire/ibutton/protocols/dallas/protocol_group_dallas.c b/lib/ibutton/protocols/dallas/protocol_group_dallas.c similarity index 100% rename from lib/one_wire/ibutton/protocols/dallas/protocol_group_dallas.c rename to lib/ibutton/protocols/dallas/protocol_group_dallas.c diff --git a/lib/one_wire/ibutton/protocols/dallas/protocol_group_dallas.h b/lib/ibutton/protocols/dallas/protocol_group_dallas.h similarity index 100% rename from lib/one_wire/ibutton/protocols/dallas/protocol_group_dallas.h rename to lib/ibutton/protocols/dallas/protocol_group_dallas.h diff --git a/lib/one_wire/ibutton/protocols/dallas/protocol_group_dallas_defs.c b/lib/ibutton/protocols/dallas/protocol_group_dallas_defs.c similarity index 100% rename from lib/one_wire/ibutton/protocols/dallas/protocol_group_dallas_defs.c rename to lib/ibutton/protocols/dallas/protocol_group_dallas_defs.c diff --git a/lib/one_wire/ibutton/protocols/dallas/protocol_group_dallas_defs.h b/lib/ibutton/protocols/dallas/protocol_group_dallas_defs.h similarity index 100% rename from lib/one_wire/ibutton/protocols/dallas/protocol_group_dallas_defs.h rename to lib/ibutton/protocols/dallas/protocol_group_dallas_defs.h diff --git a/lib/one_wire/ibutton/protocols/misc/protocol_cyfral.c b/lib/ibutton/protocols/misc/protocol_cyfral.c similarity index 100% rename from lib/one_wire/ibutton/protocols/misc/protocol_cyfral.c rename to lib/ibutton/protocols/misc/protocol_cyfral.c diff --git a/lib/one_wire/ibutton/protocols/misc/protocol_cyfral.h b/lib/ibutton/protocols/misc/protocol_cyfral.h similarity index 100% rename from lib/one_wire/ibutton/protocols/misc/protocol_cyfral.h rename to lib/ibutton/protocols/misc/protocol_cyfral.h diff --git a/lib/one_wire/ibutton/protocols/misc/protocol_group_misc.c b/lib/ibutton/protocols/misc/protocol_group_misc.c similarity index 100% rename from lib/one_wire/ibutton/protocols/misc/protocol_group_misc.c rename to lib/ibutton/protocols/misc/protocol_group_misc.c diff --git a/lib/one_wire/ibutton/protocols/misc/protocol_group_misc.h b/lib/ibutton/protocols/misc/protocol_group_misc.h similarity index 100% rename from lib/one_wire/ibutton/protocols/misc/protocol_group_misc.h rename to lib/ibutton/protocols/misc/protocol_group_misc.h diff --git a/lib/one_wire/ibutton/protocols/misc/protocol_group_misc_defs.c b/lib/ibutton/protocols/misc/protocol_group_misc_defs.c similarity index 100% rename from lib/one_wire/ibutton/protocols/misc/protocol_group_misc_defs.c rename to lib/ibutton/protocols/misc/protocol_group_misc_defs.c diff --git a/lib/one_wire/ibutton/protocols/misc/protocol_group_misc_defs.h b/lib/ibutton/protocols/misc/protocol_group_misc_defs.h similarity index 100% rename from lib/one_wire/ibutton/protocols/misc/protocol_group_misc_defs.h rename to lib/ibutton/protocols/misc/protocol_group_misc_defs.h diff --git a/lib/one_wire/ibutton/protocols/misc/protocol_metakom.c b/lib/ibutton/protocols/misc/protocol_metakom.c similarity index 100% rename from lib/one_wire/ibutton/protocols/misc/protocol_metakom.c rename to lib/ibutton/protocols/misc/protocol_metakom.c diff --git a/lib/one_wire/ibutton/protocols/misc/protocol_metakom.h b/lib/ibutton/protocols/misc/protocol_metakom.h similarity index 100% rename from lib/one_wire/ibutton/protocols/misc/protocol_metakom.h rename to lib/ibutton/protocols/misc/protocol_metakom.h diff --git a/lib/one_wire/ibutton/protocols/protocol_common.h b/lib/ibutton/protocols/protocol_common.h similarity index 100% rename from lib/one_wire/ibutton/protocols/protocol_common.h rename to lib/ibutton/protocols/protocol_common.h diff --git a/lib/one_wire/ibutton/protocols/protocol_common_i.h b/lib/ibutton/protocols/protocol_common_i.h similarity index 100% rename from lib/one_wire/ibutton/protocols/protocol_common_i.h rename to lib/ibutton/protocols/protocol_common_i.h diff --git a/lib/one_wire/ibutton/protocols/protocol_group_base.h b/lib/ibutton/protocols/protocol_group_base.h similarity index 100% rename from lib/one_wire/ibutton/protocols/protocol_group_base.h rename to lib/ibutton/protocols/protocol_group_base.h diff --git a/lib/one_wire/ibutton/protocols/protocol_group_defs.c b/lib/ibutton/protocols/protocol_group_defs.c similarity index 100% rename from lib/one_wire/ibutton/protocols/protocol_group_defs.c rename to lib/ibutton/protocols/protocol_group_defs.c diff --git a/lib/one_wire/ibutton/protocols/protocol_group_defs.h b/lib/ibutton/protocols/protocol_group_defs.h similarity index 100% rename from lib/one_wire/ibutton/protocols/protocol_group_defs.h rename to lib/ibutton/protocols/protocol_group_defs.h diff --git a/lib/one_wire/SConscript b/lib/one_wire/SConscript index 56d4759e..8d73c9db 100644 --- a/lib/one_wire/SConscript +++ b/lib/one_wire/SConscript @@ -11,9 +11,6 @@ env.Append( File("one_wire_host_timing.h"), File("one_wire_host.h"), File("one_wire_slave.h"), - File("ibutton/ibutton_key.h"), - File("ibutton/ibutton_worker.h"), - File("ibutton/ibutton_protocols.h"), File("maxim_crc.h"), ], ) From 445a1aa7b01d27b88b9cc0793e0a318d9829a3f7 Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Mon, 20 Mar 2023 20:30:57 +0400 Subject: [PATCH 20/32] SubGhz: fix Incorrect comparison in subghz_setting_get_hopper_frequency (#2518) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- lib/subghz/subghz_setting.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/subghz/subghz_setting.c b/lib/subghz/subghz_setting.c index 5d7ea0ce..65658004 100644 --- a/lib/subghz/subghz_setting.c +++ b/lib/subghz/subghz_setting.c @@ -541,7 +541,7 @@ uint32_t subghz_setting_get_frequency(SubGhzSetting* instance, size_t idx) { uint32_t subghz_setting_get_hopper_frequency(SubGhzSetting* instance, size_t idx) { furi_assert(instance); - if(idx < FrequencyList_size(instance->frequencies)) { + if(idx < FrequencyList_size(instance->hopper_frequencies)) { return *FrequencyList_get(instance->hopper_frequencies, idx); } else { return 0; From 0444a80f195b7f12b96b11e9da714c00c8a79d40 Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Mon, 20 Mar 2023 20:22:03 +0300 Subject: [PATCH 21/32] [FL-3134] BadUSB: Script interpreter refactoring (#2485) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Script command and character tables * Non-blocking stringdelay, docs update * altchar/altstring fix * Layout select UI fix * Remove debug print Co-authored-by: あく --- applications/main/bad_usb/bad_usb_app_i.h | 2 +- .../ducky_script.c} | 326 +++++++++-------- .../ducky_script.h} | 27 +- .../bad_usb/helpers/ducky_script_commands.c | 177 ++++++++++ .../main/bad_usb/helpers/ducky_script_i.h | 69 ++++ .../bad_usb/helpers/ducky_script_keycodes.c | 79 +++++ applications/main/bad_usb/mnemonic.c | 327 ------------------ applications/main/bad_usb/mnemonic.h | 96 ----- .../bad_usb/scenes/bad_usb_scene_config.c | 2 +- .../scenes/bad_usb_scene_config_layout.c | 4 +- .../main/bad_usb/scenes/bad_usb_scene_work.c | 2 +- .../main/bad_usb/views/bad_usb_view.c | 9 +- .../main/bad_usb/views/bad_usb_view.h | 2 +- applications/services/gui/modules/submenu.c | 2 +- .../file_formats/BadUsbScriptFormat.md | 82 +++-- .../targets/f7/furi_hal/furi_hal_usb_hid.c | 3 - .../furi_hal_include/furi_hal_usb_hid.h | 5 + 17 files changed, 576 insertions(+), 638 deletions(-) rename applications/main/bad_usb/{bad_usb_script.c => helpers/ducky_script.c} (74%) rename applications/main/bad_usb/{bad_usb_script.h => helpers/ducky_script.h} (61%) create mode 100644 applications/main/bad_usb/helpers/ducky_script_commands.c create mode 100644 applications/main/bad_usb/helpers/ducky_script_i.h create mode 100644 applications/main/bad_usb/helpers/ducky_script_keycodes.c delete mode 100644 applications/main/bad_usb/mnemonic.c delete mode 100644 applications/main/bad_usb/mnemonic.h diff --git a/applications/main/bad_usb/bad_usb_app_i.h b/applications/main/bad_usb/bad_usb_app_i.h index 588c4c2d..cf1c02eb 100644 --- a/applications/main/bad_usb/bad_usb_app_i.h +++ b/applications/main/bad_usb/bad_usb_app_i.h @@ -2,7 +2,7 @@ #include "bad_usb_app.h" #include "scenes/bad_usb_scene.h" -#include "bad_usb_script.h" +#include "helpers/ducky_script.h" #include #include diff --git a/applications/main/bad_usb/bad_usb_script.c b/applications/main/bad_usb/helpers/ducky_script.c similarity index 74% rename from applications/main/bad_usb/bad_usb_script.c rename to applications/main/bad_usb/helpers/ducky_script.c index 12abc766..0206b7d0 100644 --- a/applications/main/bad_usb/bad_usb_script.c +++ b/applications/main/bad_usb/helpers/ducky_script.c @@ -5,17 +5,13 @@ #include #include #include -#include "bad_usb_script.h" -#include "mnemonic.h" +#include "ducky_script.h" +#include "ducky_script_i.h" #include #define TAG "BadUSB" #define WORKER_TAG TAG "Worker" -#define SCRIPT_STATE_ERROR (-1) -#define SCRIPT_STATE_END (-2) -#define SCRIPT_STATE_NEXT_LINE (-3) - #define BADUSB_ASCII_TO_KEY(script, x) \ (((uint8_t)x < 128) ? (script->layout[(uint8_t)x]) : HID_KEYBOARD_NONE) @@ -26,87 +22,20 @@ typedef enum { WorkerEvtDisconnect = (1 << 3), } WorkerEvtFlags; -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}, -}; - -static const char ducky_cmd_comment[] = {"REM"}; static const char ducky_cmd_id[] = {"ID"}; -static const char ducky_cmd_delay[] = {"DELAY "}; -static const char ducky_cmd_string[] = {"STRING "}; -static const char ducky_cmd_stringln[] = {"STRINGLN "}; -static const char ducky_cmd_defdelay_1[] = {"DEFAULT_DELAY "}; -static const char ducky_cmd_defdelay_2[] = {"DEFAULTDELAY "}; -static const char ducky_cmd_stringdelay_1[] = {"STRINGDELAY "}; -static const char ducky_cmd_stringdelay_2[] = {"STRING_DELAY "}; -static const char ducky_cmd_repeat[] = {"REPEAT "}; -static const char ducky_cmd_sysrq[] = {"SYSRQ "}; -static const char ducky_cmd_hold[] = {"HOLD "}; -static const char ducky_cmd_release[] = {"RELEASE "}; -static const char ducky_cmd_altchar[] = {"ALTCHAR "}; -static const char ducky_cmd_altstr_1[] = {"ALTSTRING "}; -static const char ducky_cmd_altstr_2[] = {"ALTCODE "}; +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); @@ -121,76 +50,150 @@ bool ducky_is_line_end(const char chr) { } uint16_t ducky_get_keycode(BadUsbScript* bad_usb, const char* param, bool accept_chars) { - for(size_t i = 0; i < (sizeof(ducky_keys) / sizeof(ducky_keys[0])); 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; - } + uint16_t keycode = ducky_get_keycode_by_name(param); + if(keycode != HID_KEYBOARD_NONE) { + return keycode; } + if((accept_chars) && (strlen(param) > 0)) { return (BADUSB_ASCII_TO_KEY(bad_usb, param[0]) & 0xFF); } return 0; } -static int32_t - ducky_parse_line(BadUsbScript* bad_usb, FuriString* line, char* error, size_t error_len) { +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() { + if((furi_hal_hid_get_led_state() & HID_KB_LED_NUM) == 0) { + furi_hal_hid_kb_press(HID_KEYBOARD_LOCK_NUM_LOCK); + furi_hal_hid_kb_release(HID_KEYBOARD_LOCK_NUM_LOCK); + } +} +bool ducky_numpad_press(const char num) { + if((num < '0') || (num > '9')) return false; + + uint16_t key = numpad_keys[num - '0']; + furi_hal_hid_kb_press(key); + furi_hal_hid_kb_release(key); + + return true; +} + +bool ducky_altchar(const char* charcode) { + uint8_t i = 0; + bool state = false; + + furi_hal_hid_kb_press(KEY_MOD_LEFT_ALT); + + while(!ducky_is_line_end(charcode[i])) { + state = ducky_numpad_press(charcode[i]); + if(state == false) break; + i++; + } + + furi_hal_hid_kb_release(KEY_MOD_LEFT_ALT); + return state; +} + +bool ducky_altstring(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(temp_str); + if(state == false) break; + i++; + } + return state; +} + +int32_t ducky_error(BadUsbScript* bad_usb, const char* text, ...) { + va_list args; + va_start(args, text); + + vsnprintf(bad_usb->st.error, sizeof(bad_usb->st.error), text, args); + + va_end(args); + return SCRIPT_STATE_ERROR; +} + +bool ducky_string(BadUsbScript* bad_usb, const char* param) { + uint32_t i = 0; + + while(param[i] != '\0') { + if(param[i] != '\n') { + uint16_t keycode = BADUSB_ASCII_TO_KEY(bad_usb, param[i]); + if(keycode != HID_KEYBOARD_NONE) { + furi_hal_hid_kb_press(keycode); + furi_hal_hid_kb_release(keycode); + } + } else { + furi_hal_hid_kb_press(HID_KEYBOARD_RETURN); + furi_hal_hid_kb_release(HID_KEYBOARD_RETURN); + } + i++; + } + bad_usb->stringdelay = 0; + return true; +} + +static bool ducky_string_next(BadUsbScript* bad_usb) { + if(bad_usb->string_print_pos >= furi_string_size(bad_usb->string_print)) { + return true; + } + + char print_char = furi_string_get_char(bad_usb->string_print, bad_usb->string_print_pos); + + if(print_char != '\n') { + uint16_t keycode = BADUSB_ASCII_TO_KEY(bad_usb, print_char); + if(keycode != HID_KEYBOARD_NONE) { + furi_hal_hid_kb_press(keycode); + furi_hal_hid_kb_release(keycode); + } + } else { + furi_hal_hid_kb_press(HID_KEYBOARD_RETURN); + furi_hal_hid_kb_release(HID_KEYBOARD_RETURN); + } + + bad_usb->string_print_pos++; + + return false; +} + +static int32_t ducky_parse_line(BadUsbScript* bad_usb, FuriString* line) { uint32_t line_len = furi_string_size(line); const char* line_tmp = furi_string_get_cstr(line); - const char* ducky_cmd_table[] = { - ducky_cmd_comment, - ducky_cmd_id, - ducky_cmd_delay, - ducky_cmd_string, - ducky_cmd_defdelay_1, - ducky_cmd_defdelay_2, - ducky_cmd_stringdelay_1, - ducky_cmd_stringdelay_2, - ducky_cmd_repeat, - ducky_cmd_sysrq, - ducky_cmd_altchar, - ducky_cmd_altstr_1, - ducky_cmd_altstr_2, - ducky_cmd_stringln, - ducky_cmd_hold, - ducky_cmd_release, - NULL}; - int32_t (*fnc_ptr[])(BadUsbScript*, FuriString*, const char*, char*, size_t) = { - &ducky_fnc_noop, - &ducky_fnc_noop, - &ducky_fnc_delay, - &ducky_fnc_string, - &ducky_fnc_defdelay, - &ducky_fnc_defdelay, - &ducky_fnc_strdelay, - &ducky_fnc_strdelay, - &ducky_fnc_repeat, - &ducky_fnc_sysrq, - &ducky_fnc_altchar, - &ducky_fnc_altstring, - &ducky_fnc_altstring, - &ducky_fnc_stringln, - &ducky_fnc_hold, - &ducky_fnc_release, - NULL}; if(line_len == 0) { return SCRIPT_STATE_NEXT_LINE; // Skip empty lines } FURI_LOG_D(WORKER_TAG, "line:%s", line_tmp); + // Ducky Lang Functions - for(size_t i = 0; ducky_cmd_table[i]; i++) { - if(strncmp(line_tmp, ducky_cmd_table[i], strlen(ducky_cmd_table[i])) == 0) - return ((fnc_ptr[i])(bad_usb, line, line_tmp, error, error_len)); + int32_t cmd_result = ducky_execute_cmd(bad_usb, line_tmp); + if(cmd_result != SCRIPT_STATE_CMD_UNKNOWN) { + return cmd_result; } + // Special keys + modifiers uint16_t key = ducky_get_keycode(bad_usb, line_tmp, false); if(key == HID_KEYBOARD_NONE) { - if(error != NULL) { - snprintf(error, error_len, "No keycode defined for %s", line_tmp); - } - return SCRIPT_STATE_ERROR; + return ducky_error(bad_usb, "No keycode defined for %s", line_tmp); } if((key & 0xFF00) != 0) { // It's a modifier key @@ -199,7 +202,7 @@ static int32_t } furi_hal_hid_kb_press(key); furi_hal_hid_kb_release(key); - return (0); + return 0; } static bool ducky_set_usb_id(BadUsbScript* bad_usb, const char* line) { @@ -277,8 +280,7 @@ static int32_t ducky_script_execute_next(BadUsbScript* bad_usb, File* script_fil if(bad_usb->repeat_cnt > 0) { bad_usb->repeat_cnt--; - delay_val = ducky_parse_line( - bad_usb, bad_usb->line_prev, bad_usb->st.error, sizeof(bad_usb->st.error)); + delay_val = ducky_parse_line(bad_usb, bad_usb->line_prev); if(delay_val == SCRIPT_STATE_NEXT_LINE) { // Empty line return 0; } else if(delay_val < 0) { // Script error @@ -313,10 +315,11 @@ static int32_t ducky_script_execute_next(BadUsbScript* bad_usb, File* script_fil bad_usb->buf_len = bad_usb->buf_len + bad_usb->buf_start - (i + 1); bad_usb->buf_start = i + 1; furi_string_trim(bad_usb->line); - delay_val = ducky_parse_line( - bad_usb, bad_usb->line, bad_usb->st.error, sizeof(bad_usb->st.error)); + delay_val = ducky_parse_line(bad_usb, bad_usb->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 < 0) { bad_usb->st.error_line = bad_usb->st.line_cur; FURI_LOG_E(WORKER_TAG, "Unknown command at line %u", bad_usb->st.line_cur); @@ -339,10 +342,11 @@ static void bad_usb_hid_state_callback(bool state, void* context) { furi_assert(context); BadUsbScript* bad_usb = context; - if(state == true) + if(state == true) { furi_thread_flags_set(furi_thread_get_id(bad_usb->thread), WorkerEvtConnect); - else + } else { furi_thread_flags_set(furi_thread_get_id(bad_usb->thread), WorkerEvtDisconnect); + } } static uint32_t bad_usb_flags_get(uint32_t flags_mask, uint32_t timeout) { @@ -368,6 +372,7 @@ static int32_t bad_usb_worker(void* context) { File* script_file = storage_file_alloc(furi_record_open(RECORD_STORAGE)); bad_usb->line = furi_string_alloc(); bad_usb->line_prev = furi_string_alloc(); + bad_usb->string_print = furi_string_alloc(); furi_hal_hid_set_state_callback(bad_usb_hid_state_callback, bad_usb); @@ -420,6 +425,7 @@ static int32_t bad_usb_worker(void* context) { bad_usb->defdelay = 0; bad_usb->stringdelay = 0; bad_usb->repeat_cnt = 0; + bad_usb->key_hold_nb = 0; bad_usb->file_end = false; storage_file_seek(script_file, 0, true); worker_state = BadUsbStateRunning; @@ -492,12 +498,17 @@ static int32_t bad_usb_worker(void* context) { delay_val = 0; worker_state = BadUsbStateScriptError; bad_usb->st.state = worker_state; + furi_hal_hid_kb_release_all(); } else if(delay_val == SCRIPT_STATE_END) { // End of script delay_val = 0; worker_state = BadUsbStateIdle; bad_usb->st.state = BadUsbStateDone; furi_hal_hid_kb_release_all(); continue; + } else if(delay_val == SCRIPT_STATE_STRING_START) { // Start printing string with delays + delay_val = bad_usb->defdelay; + bad_usb->string_print_pos = 0; + worker_state = BadUsbStateStringDelay; } else if(delay_val > 1000) { bad_usb->st.state = BadUsbStateDelay; // Show long delays bad_usb->st.delay_remain = delay_val / 1000; @@ -505,7 +516,35 @@ static int32_t bad_usb_worker(void* context) { } else { furi_check((flags & FuriFlagError) == 0); } + } else if(worker_state == BadUsbStateStringDelay) { // State: print string with delays + uint32_t flags = furi_thread_flags_wait( + WorkerEvtEnd | WorkerEvtToggle | WorkerEvtDisconnect, + FuriFlagWaitAny, + bad_usb->stringdelay); + if(!(flags & FuriFlagError)) { + if(flags & WorkerEvtEnd) { + break; + } else if(flags & WorkerEvtToggle) { + worker_state = BadUsbStateIdle; // Stop executing script + furi_hal_hid_kb_release_all(); + } else if(flags & WorkerEvtDisconnect) { + worker_state = BadUsbStateNotConnected; // USB disconnected + furi_hal_hid_kb_release_all(); + } + bad_usb->st.state = worker_state; + continue; + } else if( + (flags == (unsigned)FuriFlagErrorTimeout) || + (flags == (unsigned)FuriFlagErrorResource)) { + bool string_end = ducky_string_next(bad_usb); + if(string_end) { + bad_usb->stringdelay = 0; + worker_state = BadUsbStateRunning; + } + } else { + furi_check((flags & FuriFlagError) == 0); + } } else if( (worker_state == BadUsbStateFileError) || (worker_state == BadUsbStateScriptError)) { // State: error @@ -524,6 +563,7 @@ static int32_t bad_usb_worker(void* context) { storage_file_free(script_file); furi_string_free(bad_usb->line); furi_string_free(bad_usb->line_prev); + furi_string_free(bad_usb->string_print); FURI_LOG_I(WORKER_TAG, "End"); diff --git a/applications/main/bad_usb/bad_usb_script.h b/applications/main/bad_usb/helpers/ducky_script.h similarity index 61% rename from applications/main/bad_usb/bad_usb_script.h rename to applications/main/bad_usb/helpers/ducky_script.h index fef2deae..0e616242 100644 --- a/applications/main/bad_usb/bad_usb_script.h +++ b/applications/main/bad_usb/helpers/ducky_script.h @@ -7,8 +7,6 @@ extern "C" { #include #include -#define FILE_BUFFER_LEN 16 - typedef enum { BadUsbStateInit, BadUsbStateNotConnected, @@ -16,6 +14,7 @@ typedef enum { BadUsbStateWillRun, BadUsbStateRunning, BadUsbStateDelay, + BadUsbStateStringDelay, BadUsbStateDone, BadUsbStateScriptError, BadUsbStateFileError, @@ -30,23 +29,7 @@ typedef struct { char error[64]; } BadUsbState; -typedef struct BadUsbScript { - FuriHalUsbHidConfig hid_cfg; - BadUsbState st; - FuriString* file_path; - uint32_t defdelay; - uint16_t layout[128]; - uint32_t stringdelay; - FuriThread* thread; - uint8_t file_buf[FILE_BUFFER_LEN + 1]; - uint8_t buf_start; - uint8_t buf_len; - bool file_end; - FuriString* line; - - FuriString* line_prev; - uint32_t repeat_cnt; -} BadUsbScript; +typedef struct BadUsbScript BadUsbScript; BadUsbScript* bad_usb_script_open(FuriString* file_path); @@ -62,12 +45,6 @@ void bad_usb_script_toggle(BadUsbScript* bad_usb); BadUsbState* bad_usb_script_get_state(BadUsbScript* bad_usb); -uint16_t ducky_get_keycode(BadUsbScript* bad_usb, const char* param, bool accept_chars); - -uint32_t ducky_get_command_len(const char* line); - -bool ducky_is_line_end(const char chr); - #ifdef __cplusplus } #endif diff --git a/applications/main/bad_usb/helpers/ducky_script_commands.c b/applications/main/bad_usb/helpers/ducky_script_commands.c new file mode 100644 index 00000000..f3de43c1 --- /dev/null +++ b/applications/main/bad_usb/helpers/ducky_script_commands.c @@ -0,0 +1,177 @@ +#include +#include +#include "ducky_script.h" +#include "ducky_script_i.h" + +typedef int32_t (*DuckyCmdCallback)(BadUsbScript* bad_usb, const char* line, int32_t param); + +typedef struct { + char* name; + DuckyCmdCallback callback; + int32_t param; +} DuckyCmd; + +static int32_t ducky_fnc_delay(BadUsbScript* bad_usb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + uint32_t delay_val = 0; + bool state = ducky_get_number(line, &delay_val); + if((state) && (delay_val > 0)) { + return (int32_t)delay_val; + } + + return ducky_error(bad_usb, "Invalid number %s", line); +} + +static int32_t ducky_fnc_defdelay(BadUsbScript* bad_usb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + bool state = ducky_get_number(line, &bad_usb->defdelay); + if(!state) { + return ducky_error(bad_usb, "Invalid number %s", line); + } + return 0; +} + +static int32_t ducky_fnc_strdelay(BadUsbScript* bad_usb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + bool state = ducky_get_number(line, &bad_usb->stringdelay); + if(!state) { + return ducky_error(bad_usb, "Invalid number %s", line); + } + return 0; +} + +static int32_t ducky_fnc_string(BadUsbScript* bad_usb, const char* line, int32_t param) { + line = &line[ducky_get_command_len(line) + 1]; + furi_string_set_str(bad_usb->string_print, line); + if(param == 1) { + furi_string_cat(bad_usb->string_print, "\n"); + } + + if(bad_usb->stringdelay == 0) { // stringdelay not set - run command immidiately + bool state = ducky_string(bad_usb, furi_string_get_cstr(bad_usb->string_print)); + if(!state) { + return ducky_error(bad_usb, "Invalid string %s", line); + } + } else { // stringdelay is set - run command in thread to keep handling external events + return SCRIPT_STATE_STRING_START; + } + + return 0; +} + +static int32_t ducky_fnc_repeat(BadUsbScript* bad_usb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + bool state = ducky_get_number(line, &bad_usb->repeat_cnt); + if((!state) || (bad_usb->repeat_cnt == 0)) { + return ducky_error(bad_usb, "Invalid number %s", line); + } + return 0; +} + +static int32_t ducky_fnc_sysrq(BadUsbScript* bad_usb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + uint16_t key = ducky_get_keycode(bad_usb, line, true); + furi_hal_hid_kb_press(KEY_MOD_LEFT_ALT | HID_KEYBOARD_PRINT_SCREEN); + furi_hal_hid_kb_press(key); + furi_hal_hid_kb_release_all(); + return 0; +} + +static int32_t ducky_fnc_altchar(BadUsbScript* bad_usb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + ducky_numlock_on(); + bool state = ducky_altchar(line); + if(!state) { + return ducky_error(bad_usb, "Invalid altchar %s", line); + } + return 0; +} + +static int32_t ducky_fnc_altstring(BadUsbScript* bad_usb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + ducky_numlock_on(); + bool state = ducky_altstring(line); + if(!state) { + return ducky_error(bad_usb, "Invalid altstring %s", line); + } + return 0; +} + +static int32_t ducky_fnc_hold(BadUsbScript* bad_usb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + uint16_t key = ducky_get_keycode(bad_usb, line, true); + if(key == HID_KEYBOARD_NONE) { + return ducky_error(bad_usb, "No keycode defined for %s", line); + } + bad_usb->key_hold_nb++; + if(bad_usb->key_hold_nb > (HID_KB_MAX_KEYS - 1)) { + return ducky_error(bad_usb, "Too many keys are hold"); + } + furi_hal_hid_kb_press(key); + return 0; +} + +static int32_t ducky_fnc_release(BadUsbScript* bad_usb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + uint16_t key = ducky_get_keycode(bad_usb, line, true); + if(key == HID_KEYBOARD_NONE) { + return ducky_error(bad_usb, "No keycode defined for %s", line); + } + if(bad_usb->key_hold_nb == 0) { + return ducky_error(bad_usb, "No keys are hold"); + } + bad_usb->key_hold_nb--; + furi_hal_hid_kb_release(key); + return 0; +} + +static const DuckyCmd ducky_commands[] = { + {"REM ", NULL, -1}, + {"ID ", NULL, -1}, + {"DELAY ", ducky_fnc_delay, -1}, + {"STRING ", ducky_fnc_string, 0}, + {"STRINGLN ", ducky_fnc_string, 1}, + {"DEFAULT_DELAY ", ducky_fnc_defdelay, -1}, + {"DEFAULTDELAY ", ducky_fnc_defdelay, -1}, + {"STRINGDELAY ", ducky_fnc_strdelay, -1}, + {"STRING_DELAY ", ducky_fnc_strdelay, -1}, + {"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}, +}; + +int32_t ducky_execute_cmd(BadUsbScript* bad_usb, const char* line) { + for(size_t i = 0; i < COUNT_OF(ducky_commands); i++) { + if(strncmp(line, ducky_commands[i].name, strlen(ducky_commands[i].name)) == 0) { + if(ducky_commands[i].callback == NULL) { + return 0; + } else { + return ((ducky_commands[i].callback)(bad_usb, line, ducky_commands[i].param)); + } + } + } + + return SCRIPT_STATE_CMD_UNKNOWN; +} diff --git a/applications/main/bad_usb/helpers/ducky_script_i.h b/applications/main/bad_usb/helpers/ducky_script_i.h new file mode 100644 index 00000000..0cda0fa2 --- /dev/null +++ b/applications/main/bad_usb/helpers/ducky_script_i.h @@ -0,0 +1,69 @@ +#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 FILE_BUFFER_LEN 16 + +struct BadUsbScript { + FuriHalUsbHidConfig hid_cfg; + FuriThread* thread; + BadUsbState st; + + FuriString* file_path; + uint8_t file_buf[FILE_BUFFER_LEN + 1]; + uint8_t buf_start; + uint8_t buf_len; + bool file_end; + + uint32_t defdelay; + uint32_t stringdelay; + uint16_t layout[128]; + + FuriString* line; + FuriString* line_prev; + uint32_t repeat_cnt; + uint8_t key_hold_nb; + + FuriString* string_print; + size_t string_print_pos; +}; + +uint16_t ducky_get_keycode(BadUsbScript* bad_usb, const char* param, bool accept_chars); + +uint32_t ducky_get_command_len(const char* line); + +bool ducky_is_line_end(const char chr); + +uint16_t ducky_get_keycode_by_name(const char* param); + +bool ducky_get_number(const char* param, uint32_t* val); + +void ducky_numlock_on(void); + +bool ducky_numpad_press(const char num); + +bool ducky_altchar(const char* charcode); + +bool ducky_altstring(const char* param); + +bool ducky_string(BadUsbScript* bad_usb, const char* param); + +int32_t ducky_execute_cmd(BadUsbScript* bad_usb, const char* line); + +int32_t ducky_error(BadUsbScript* bad_usb, const char* text, ...); + +#ifdef __cplusplus +} +#endif diff --git a/applications/main/bad_usb/helpers/ducky_script_keycodes.c b/applications/main/bad_usb/helpers/ducky_script_keycodes.c new file mode 100644 index 00000000..da2fc22f --- /dev/null +++ b/applications/main/bad_usb/helpers/ducky_script_keycodes.c @@ -0,0 +1,79 @@ +#include +#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/main/bad_usb/mnemonic.c b/applications/main/bad_usb/mnemonic.c deleted file mode 100644 index f21cc98b..00000000 --- a/applications/main/bad_usb/mnemonic.c +++ /dev/null @@ -1,327 +0,0 @@ -#include -#include -#include "mnemonic.h" - -#define TAG "BadUSB" -#define WORKER_TAG TAG "Worker" - -#define FILE_BUFFER_LEN 16 -#define SCRIPT_STATE_ERROR (-1) -#define SCRIPT_STATE_END (-2) -#define SCRIPT_STATE_NEXT_LINE (-3) - -#define BADUSB_ASCII_TO_KEY(script, x) \ - (((uint8_t)x < 128) ? (script->layout[(uint8_t)x]) : HID_KEYBOARD_NONE) - -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, -}; - -static 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; -} - -static void ducky_numlock_on() { - if((furi_hal_hid_get_led_state() & HID_KB_LED_NUM) == 0) { - furi_hal_hid_kb_press(HID_KEYBOARD_LOCK_NUM_LOCK); - furi_hal_hid_kb_release(HID_KEYBOARD_LOCK_NUM_LOCK); - } -} -static bool ducky_numpad_press(const char num) { - if((num < '0') || (num > '9')) return false; - - uint16_t key = numpad_keys[num - '0']; - furi_hal_hid_kb_press(key); - furi_hal_hid_kb_release(key); - - return true; -} - -static bool ducky_altchar(const char* charcode) { - uint8_t i = 0; - bool state = false; - - FURI_LOG_I(WORKER_TAG, "char %s", charcode); - - furi_hal_hid_kb_press(KEY_MOD_LEFT_ALT); - - while(!ducky_is_line_end(charcode[i])) { - state = ducky_numpad_press(charcode[i]); - if(state == false) break; - i++; - } - - furi_hal_hid_kb_release(KEY_MOD_LEFT_ALT); - return state; -} - -static bool ducky_altstring(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(temp_str); - if(state == false) break; - i++; - } - return state; -} - -static bool ducky_string(BadUsbScript* bad_usb, const char* param) { - uint32_t i = 0; - - while(param[i] != '\0') { - uint16_t keycode = BADUSB_ASCII_TO_KEY(bad_usb, param[i]); - if(keycode != HID_KEYBOARD_NONE) { - furi_hal_hid_kb_press(keycode); - furi_hal_hid_kb_release(keycode); - if(bad_usb->stringdelay > 0) { - furi_delay_ms(bad_usb->stringdelay); - } - } - i++; - } - bad_usb->stringdelay = 0; - return true; -} - -int32_t ducky_fnc_noop( - BadUsbScript* bad_usb, - FuriString* line, - const char* line_tmp, - char* error, - size_t error_len) { - (void)bad_usb; - (void)line; - (void)line_tmp; - (void)error; - (void)error_len; - return (0); -} - -int32_t ducky_fnc_delay( - BadUsbScript* bad_usb, - FuriString* line, - const char* line_tmp, - char* error, - size_t error_len) { - bool state = false; - (void)bad_usb; - (void)line; - - line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; - uint32_t delay_val = 0; - state = ducky_get_number(line_tmp, &delay_val); - if((state) && (delay_val > 0)) { - return (int32_t)delay_val; - } - if(error != NULL) { - snprintf(error, error_len, "Invalid number %s", line_tmp); - } - return SCRIPT_STATE_ERROR; -} - -int32_t ducky_fnc_defdelay( - BadUsbScript* bad_usb, - FuriString* line, - const char* line_tmp, - char* error, - size_t error_len) { - bool state = false; - (void)line; - - line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; - state = ducky_get_number(line_tmp, &bad_usb->defdelay); - if(!state && error != NULL) { - snprintf(error, error_len, "Invalid number %s", line_tmp); - } - return (state) ? (0) : SCRIPT_STATE_ERROR; -} - -int32_t ducky_fnc_strdelay( - BadUsbScript* bad_usb, - FuriString* line, - const char* line_tmp, - char* error, - size_t error_len) { - bool state = false; - (void)line; - - line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; - state = ducky_get_number(line_tmp, &bad_usb->stringdelay); - if((state) && (bad_usb->stringdelay > 0)) { - return state; - } - if(error != NULL) { - snprintf(error, error_len, "Invalid number %s", line_tmp); - } - return SCRIPT_STATE_ERROR; -} - -int32_t ducky_fnc_string( - BadUsbScript* bad_usb, - FuriString* line, - const char* line_tmp, - char* error, - size_t error_len) { - bool state = false; - (void)line; - - line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; - state = ducky_string(bad_usb, line_tmp); - if(!state && error != NULL) { - snprintf(error, error_len, "Invalid string %s", line_tmp); - } - return (state) ? (0) : SCRIPT_STATE_ERROR; -} - -int32_t ducky_fnc_repeat( - BadUsbScript* bad_usb, - FuriString* line, - const char* line_tmp, - char* error, - size_t error_len) { - bool state = false; - (void)line; - - line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; - state = ducky_get_number(line_tmp, &bad_usb->repeat_cnt); - if(!state && error != NULL) { - snprintf(error, error_len, "Invalid number %s", line_tmp); - } - return (state) ? (0) : SCRIPT_STATE_ERROR; -} - -int32_t ducky_fnc_sysrq( - BadUsbScript* bad_usb, - FuriString* line, - const char* line_tmp, - char* error, - size_t error_len) { - (void)error; - (void)error_len; - (void)line; - line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; - uint16_t key = ducky_get_keycode(bad_usb, line_tmp, true); - furi_hal_hid_kb_press(KEY_MOD_LEFT_ALT | HID_KEYBOARD_PRINT_SCREEN); - furi_hal_hid_kb_press(key); - furi_hal_hid_kb_release_all(); - return (0); -} - -int32_t ducky_fnc_altchar( - BadUsbScript* bad_usb, - FuriString* line, - const char* line_tmp, - char* error, - size_t error_len) { - bool state = false; - (void)bad_usb; - (void)line; - - line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; - ducky_numlock_on(); - state = ducky_altchar(line_tmp); - if(!state && error != NULL) { - snprintf(error, error_len, "Invalid altchar %s", line_tmp); - } - return (state) ? (0) : SCRIPT_STATE_ERROR; -} - -int32_t ducky_fnc_altstring( - BadUsbScript* bad_usb, - FuriString* line, - const char* line_tmp, - char* error, - size_t error_len) { - bool state = false; - (void)bad_usb; - (void)line; - - line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; - ducky_numlock_on(); - state = ducky_altstring(line_tmp); - if(!state && error != NULL) { - snprintf(error, error_len, "Invalid altstring %s", line_tmp); - } - return (state) ? (0) : SCRIPT_STATE_ERROR; -} - -int32_t ducky_fnc_stringln( - BadUsbScript* bad_usb, - FuriString* line, - const char* line_tmp, - char* error, - size_t error_len) { - bool state = false; - (void)line; - - line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; - state = ducky_string(bad_usb, line_tmp); - if(!state && error != NULL) { - snprintf(error, error_len, "Invalid string %s", line_tmp); - } - furi_hal_hid_kb_press(HID_KEYBOARD_RETURN); - furi_hal_hid_kb_release(HID_KEYBOARD_RETURN); - return (state) ? (0) : SCRIPT_STATE_ERROR; -} - -int32_t ducky_fnc_hold( - BadUsbScript* bad_usb, - FuriString* line, - const char* line_tmp, - char* error, - size_t error_len) { - (void)line; - line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; - uint16_t key = ducky_get_keycode(bad_usb, line_tmp, true); - if(key == HID_KEYBOARD_NONE) { - if(error != NULL) { - snprintf(error, error_len, "No keycode defined for %s", line_tmp); - } - return SCRIPT_STATE_ERROR; - } - furi_hal_hid_kb_press(key); - return (0); -} - -int32_t ducky_fnc_release( - BadUsbScript* bad_usb, - FuriString* line, - const char* line_tmp, - char* error, - size_t error_len) { - (void)line; - line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; - uint16_t key = ducky_get_keycode(bad_usb, line_tmp, true); - if(key == HID_KEYBOARD_NONE) { - if(error != NULL) { - snprintf(error, error_len, "No keycode defined for %s", line_tmp); - } - return SCRIPT_STATE_ERROR; - } - furi_hal_hid_kb_release(key); - return (0); -} diff --git a/applications/main/bad_usb/mnemonic.h b/applications/main/bad_usb/mnemonic.h deleted file mode 100644 index a85627c3..00000000 --- a/applications/main/bad_usb/mnemonic.h +++ /dev/null @@ -1,96 +0,0 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#include "bad_usb_script.h" - -// A no opperation function -int32_t ducky_fnc_noop( - BadUsbScript* bad_usb, - FuriString* line, - const char* line_tmp, - char* error, - size_t error_len); -// DELAY -int32_t ducky_fnc_delay( - BadUsbScript* bad_usb, - FuriString* line, - const char* line_tmp, - char* error, - size_t error_len); -// DEFAULTDELAY -int32_t ducky_fnc_defdelay( - BadUsbScript* bad_usb, - FuriString* line, - const char* line_tmp, - char* error, - size_t error_len); -// STRINGDELAY -int32_t ducky_fnc_strdelay( - BadUsbScript* bad_usb, - FuriString* line, - const char* line_tmp, - char* error, - size_t error_len); -// STRING -int32_t ducky_fnc_string( - BadUsbScript* bad_usb, - FuriString* line, - const char* line_tmp, - char* error, - size_t error_len); -// STRINGLN -int32_t ducky_fnc_stringln( - BadUsbScript* bad_usb, - FuriString* line, - const char* line_tmp, - char* error, - size_t error_len); -// REPEAT -int32_t ducky_fnc_repeat( - BadUsbScript* bad_usb, - FuriString* line, - const char* line_tmp, - char* error, - size_t error_len); -// SYSRQ -int32_t ducky_fnc_sysrq( - BadUsbScript* bad_usb, - FuriString* line, - const char* line_tmp, - char* error, - size_t error_len); -// ALTCHAR -int32_t ducky_fnc_altchar( - BadUsbScript* bad_usb, - FuriString* line, - const char* line_tmp, - char* error, - size_t error_len); -// ALTSTRING -int32_t ducky_fnc_altstring( - BadUsbScript* bad_usb, - FuriString* line, - const char* line_tmp, - char* error, - size_t error_len); -// HOLD -int32_t ducky_fnc_hold( - BadUsbScript* bad_usb, - FuriString* line, - const char* line_tmp, - char* error, - size_t error_len); -// RELEASE -int32_t ducky_fnc_release( - BadUsbScript* bad_usb, - FuriString* line, - const char* line_tmp, - char* error, - size_t error_len); - -#ifdef __cplusplus -} -#endif diff --git a/applications/main/bad_usb/scenes/bad_usb_scene_config.c b/applications/main/bad_usb/scenes/bad_usb_scene_config.c index c88cae03..5477c5e8 100644 --- a/applications/main/bad_usb/scenes/bad_usb_scene_config.c +++ b/applications/main/bad_usb/scenes/bad_usb_scene_config.c @@ -17,7 +17,7 @@ void bad_usb_scene_config_on_enter(void* context) { submenu_add_item( submenu, - "Keyboard Layout", + "Keyboard Layout (global)", SubmenuIndexKeyboardLayout, bad_usb_scene_config_submenu_callback, bad_usb); diff --git a/applications/main/bad_usb/scenes/bad_usb_scene_config_layout.c b/applications/main/bad_usb/scenes/bad_usb_scene_config_layout.c index 7708ed1d..5d70b801 100644 --- a/applications/main/bad_usb/scenes/bad_usb_scene_config_layout.c +++ b/applications/main/bad_usb/scenes/bad_usb_scene_config_layout.c @@ -33,8 +33,10 @@ void bad_usb_scene_config_layout_on_enter(void* context) { if(bad_usb_layout_select(bad_usb)) { bad_usb_script_set_keyboard_layout(bad_usb->bad_usb_script, bad_usb->keyboard_layout); + scene_manager_search_and_switch_to_previous_scene(bad_usb->scene_manager, BadUsbSceneWork); + } else { + scene_manager_previous_scene(bad_usb->scene_manager); } - scene_manager_previous_scene(bad_usb->scene_manager); } bool bad_usb_scene_config_layout_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/main/bad_usb/scenes/bad_usb_scene_work.c b/applications/main/bad_usb/scenes/bad_usb_scene_work.c index 6f2b8269..afc2e6f6 100644 --- a/applications/main/bad_usb/scenes/bad_usb_scene_work.c +++ b/applications/main/bad_usb/scenes/bad_usb_scene_work.c @@ -1,4 +1,4 @@ -#include "../bad_usb_script.h" +#include "../helpers/ducky_script.h" #include "../bad_usb_app_i.h" #include "../views/bad_usb_view.h" #include diff --git a/applications/main/bad_usb/views/bad_usb_view.c b/applications/main/bad_usb/views/bad_usb_view.c index 9ee9dc34..874d677c 100644 --- a/applications/main/bad_usb/views/bad_usb_view.c +++ b/applications/main/bad_usb/views/bad_usb_view.c @@ -1,5 +1,5 @@ #include "bad_usb_view.h" -#include "../bad_usb_script.h" +#include "../helpers/ducky_script.h" #include #include #include @@ -79,7 +79,12 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) { canvas_draw_str_aligned( canvas, 127, 46, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); furi_string_reset(disp_str); - canvas_draw_str_aligned(canvas, 127, 56, AlignRight, AlignBottom, model->state.error); + + 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 == BadUsbStateIdle) { canvas_draw_icon(canvas, 4, 26, &I_Smile_18x18); canvas_set_font(canvas, FontBigNumbers); diff --git a/applications/main/bad_usb/views/bad_usb_view.h b/applications/main/bad_usb/views/bad_usb_view.h index 2fc01688..6210835f 100644 --- a/applications/main/bad_usb/views/bad_usb_view.h +++ b/applications/main/bad_usb/views/bad_usb_view.h @@ -1,7 +1,7 @@ #pragma once #include -#include "../bad_usb_script.h" +#include "../helpers/ducky_script.h" typedef struct BadUsb BadUsb; typedef void (*BadUsbButtonCallback)(InputKey key, void* context); diff --git a/applications/services/gui/modules/submenu.c b/applications/services/gui/modules/submenu.c index 00e4d68b..9d81c30b 100644 --- a/applications/services/gui/modules/submenu.c +++ b/applications/services/gui/modules/submenu.c @@ -98,7 +98,7 @@ static void submenu_view_draw_callback(Canvas* canvas, void* _model) { FuriString* disp_str; disp_str = furi_string_alloc_set(SubmenuItemArray_cref(it)->label); - elements_string_fit_width(canvas, disp_str, item_width - 20); + elements_string_fit_width(canvas, disp_str, item_width - (6 * 2)); canvas_draw_str( canvas, diff --git a/documentation/file_formats/BadUsbScriptFormat.md b/documentation/file_formats/BadUsbScriptFormat.md index 94dee594..8373bf68 100644 --- a/documentation/file_formats/BadUsbScriptFormat.md +++ b/documentation/file_formats/BadUsbScriptFormat.md @@ -11,18 +11,18 @@ BadUsb app can execute only text scrips from `.txt` files, no compilation is req ## Comment line Just a single comment line. The interpreter will ignore all text after the REM command. -|Command|Parameters|Notes| -|-|-|-| -|REM|Comment text|| +| Command | Parameters | Notes | +| ------- | ------------ | ----- | +| REM | Comment text | | ## Delay Pause script execution by a defined time. -|Command|Parameters|Notes| -|-|-|-| -|DELAY|Delay value in ms|Single delay| -|DEFAULT_DELAY|Delay value in ms|Add delay before every next command| -|DEFAULTDELAY|Delay value in ms|Same as DEFAULT_DELAY| +| Command | Parameters | Notes | +| ------------- | ----------------- | ----------------------------------- | +| DELAY | Delay value in ms | Single delay | +| DEFAULT_DELAY | Delay value in ms | Add delay before every next command | +| DEFAULTDELAY | Delay value in ms | Same as DEFAULT_DELAY | ## Special keys @@ -56,32 +56,42 @@ Pause script execution by a defined time. ## Modifier keys Can be combined with a special key command or a single character. -|Command|Notes| -|-|-| -|CONTROL / CTRL|| -|SHIFT|| -|ALT|| -|WINDOWS / GUI|| -|CTRL-ALT|CTRL+ALT| -|CTRL-SHIFT|CTRL+SHIFT| -|ALT-SHIFT|ALT+SHIFT| -|ALT-GUI|ALT+WIN| -|GUI-SHIFT|WIN+SHIFT| -|GUI-CTRL|WIN+CTRL| +| Command | Notes | +| -------------- | ---------- | +| CONTROL / CTRL | | +| SHIFT | | +| ALT | | +| WINDOWS / GUI | | +| CTRL-ALT | CTRL+ALT | +| CTRL-SHIFT | CTRL+SHIFT | +| ALT-SHIFT | ALT+SHIFT | +| ALT-GUI | ALT+WIN | +| GUI-SHIFT | WIN+SHIFT | +| GUI-CTRL | WIN+CTRL | + +## Key hold and release + +Up to 5 keys can be hold simultaneously. +| Command | Parameters | Notes | +| ------- | ------------------------------- | ----------------------------------------- | +| HOLD | Special key or single character | Press and hold key untill RELEASE command | +| RELEASE | Special key or single character | Release key | + ## String -| Command | Parameters | Notes | -| ------- | ----------- | ----------------- | -| STRING | Text string | Print text string | +| Command | Parameters | Notes | +| ------- | ----------- | ----------------- | +| STRING | Text string | Print text string | +| STRINGLN | Text string | Print text string and press enter after it | ## String delay Delay between keypresses. -|Command|Parameters|Notes| -|-|-|-| -|STRING_DELAY|Delay value in ms|Applied once to next appearing string| -|STRINGDELAY|Delay value in ms|Same as STRING_DELAY| +| Command | Parameters | Notes | +| ------------ | ----------------- | --------------------------------------------- | +| STRING_DELAY | Delay value in ms | Applied once to next appearing STRING command | +| STRINGDELAY | Delay value in ms | Same as STRING_DELAY | ## Repeat @@ -91,19 +101,19 @@ Delay between keypresses. ## ALT+Numpad input -On Windows and some Linux systems, you can print characters by pressing `ALT` key and entering its code on Numpad. -|Command|Parameters|Notes| -|-|-|-| -|ALTCHAR|Character code|Print single character| -|ALTSTRING|Text string|Print text string using ALT+Numpad method| -|ALTCODE|Text string|Same as ALTSTRING, presents in some Duckyscript implementations| +On Windows and some Linux systems, you can print characters by holding `ALT` key and entering its code on Numpad. +| Command | Parameters | Notes | +| --------- | -------------- | --------------------------------------------------------------- | +| ALTCHAR | Character code | Print single character | +| ALTSTRING | Text string | Print text string using ALT+Numpad method | +| ALTCODE | Text string | Same as ALTSTRING, presents in some Duckyscript implementations | ## SysRq Send [SysRq command](https://en.wikipedia.org/wiki/Magic_SysRq_key) -|Command|Parameters|Notes| -|-|-|-| -|SYSRQ|Single character|| +| Command | Parameters | Notes | +| ------- | ---------------- | ----- | +| SYSRQ | Single character | | ## USB device ID diff --git a/firmware/targets/f7/furi_hal/furi_hal_usb_hid.c b/firmware/targets/f7/furi_hal/furi_hal_usb_hid.c index a3bc8422..5cb7fd29 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_usb_hid.c +++ b/firmware/targets/f7/furi_hal/furi_hal_usb_hid.c @@ -11,9 +11,6 @@ #define HID_EP_OUT 0x01 #define HID_EP_SZ 0x10 -#define HID_KB_MAX_KEYS 6 -#define HID_CONSUMER_MAX_KEYS 2 - #define HID_INTERVAL 2 #define HID_VID_DEFAULT 0x046D diff --git a/firmware/targets/furi_hal_include/furi_hal_usb_hid.h b/firmware/targets/furi_hal_include/furi_hal_usb_hid.h index a9f09481..13e83ef6 100644 --- a/firmware/targets/furi_hal_include/furi_hal_usb_hid.h +++ b/firmware/targets/furi_hal_include/furi_hal_usb_hid.h @@ -9,6 +9,11 @@ extern "C" { #endif +/** Max number of simultaneously pressed keys (keyboard) */ +#define HID_KB_MAX_KEYS 6 +/** Max number of simultaneously pressed keys (consumer control) */ +#define HID_CONSUMER_MAX_KEYS 2 + #define HID_KEYBOARD_NONE 0x00 /** HID keyboard modifier keys */ From fd8607398dbdb986888ee0b7517d9ef5c79e06e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Tue, 21 Mar 2023 18:55:20 +0900 Subject: [PATCH 22/32] Github: unshallow on decontamination (#2521) * Github: unshallow on decontamination * Github: fix syntax * Github: decontaminate without full tree * Github: update decontaminate action in all workflows --- .github/workflows/build.yml | 7 ++++++- .github/workflows/lint_and_submodule_check.yml | 7 ++++++- .github/workflows/merge_report.yml | 7 ++++++- .github/workflows/pvs_studio.yml | 7 ++++++- .github/workflows/unit_tests.yml | 9 +++++++-- .github/workflows/updater_test.yml | 7 ++++++- 6 files changed, 37 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 79535c93..6ab2490c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,7 +21,12 @@ jobs: - name: 'Decontaminate previous build leftovers' run: | if [ -d .git ]; then - git submodule status || git checkout "$(git rev-list --max-parents=0 HEAD | tail -n 1)" + git submodule status || ( + git ls-files --stage | egrep '^160000' | awk '{print $4}' | while read submodule + do + git rm -rf --cached "$submodule" + done + ) fi - name: 'Checkout code' diff --git a/.github/workflows/lint_and_submodule_check.yml b/.github/workflows/lint_and_submodule_check.yml index ede35793..46cca5c0 100644 --- a/.github/workflows/lint_and_submodule_check.yml +++ b/.github/workflows/lint_and_submodule_check.yml @@ -21,7 +21,12 @@ jobs: - name: 'Decontaminate previous build leftovers' run: | if [ -d .git ]; then - git submodule status || git checkout "$(git rev-list --max-parents=0 HEAD | tail -n 1)" + git submodule status || ( + git ls-files --stage | egrep '^160000' | awk '{print $4}' | while read submodule + do + git rm -rf --cached "$submodule" + done + ) fi - name: 'Checkout code' diff --git a/.github/workflows/merge_report.yml b/.github/workflows/merge_report.yml index 3b7cd234..e88346ed 100644 --- a/.github/workflows/merge_report.yml +++ b/.github/workflows/merge_report.yml @@ -15,7 +15,12 @@ jobs: - name: 'Decontaminate previous build leftovers' run: | if [ -d .git ]; then - git submodule status || git checkout "$(git rev-list --max-parents=0 HEAD | tail -n 1)" + git submodule status || ( + git ls-files --stage | egrep '^160000' | awk '{print $4}' | while read submodule + do + git rm -rf --cached "$submodule" + done + ) fi - name: 'Checkout code' diff --git a/.github/workflows/pvs_studio.yml b/.github/workflows/pvs_studio.yml index 9105a0fd..65ffd195 100644 --- a/.github/workflows/pvs_studio.yml +++ b/.github/workflows/pvs_studio.yml @@ -22,7 +22,12 @@ jobs: - name: 'Decontaminate previous build leftovers' run: | if [ -d .git ]; then - git submodule status || git checkout "$(git rev-list --max-parents=0 HEAD | tail -n 1)" + git submodule status || ( + git ls-files --stage | egrep '^160000' | awk '{print $4}' | while read submodule + do + git rm -rf --cached "$submodule" + done + ) fi - name: 'Checkout code' diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index bed5a470..6f044ebc 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -15,7 +15,12 @@ jobs: - name: 'Decontaminate previous build leftovers' run: | if [ -d .git ]; then - git submodule status || git checkout "$(git rev-list --max-parents=0 HEAD | tail -n 1)" + git submodule status || ( + git ls-files --stage | egrep '^160000' | awk '{print $4}' | while read submodule + do + git rm -rf --cached "$submodule" + done + ) fi - name: Checkout code @@ -32,7 +37,7 @@ jobs: - name: 'Flash unit tests firmware' id: flashing if: success() - run: | + run: | ./fbt flash OPENOCD_ADAPTER_SERIAL=2A0906016415303030303032 FIRMWARE_APP_SET=unit_tests FORCE=1 - name: 'Wait for flipper and format ext' diff --git a/.github/workflows/updater_test.yml b/.github/workflows/updater_test.yml index eba34e98..c04d526f 100644 --- a/.github/workflows/updater_test.yml +++ b/.github/workflows/updater_test.yml @@ -15,7 +15,12 @@ jobs: - name: 'Decontaminate previous build leftovers' run: | if [ -d .git ]; then - git submodule status || git checkout "$(git rev-list --max-parents=0 HEAD | tail -n 1)" + git submodule status || ( + git ls-files --stage | egrep '^160000' | awk '{print $4}' | while read submodule + do + git rm -rf --cached "$submodule" + done + ) fi - name: Checkout code From bf70f4b71a6d75204613fb0ce19c84598e9f0457 Mon Sep 17 00:00:00 2001 From: AloneLiberty <111039319+AloneLiberty@users.noreply.github.com> Date: Tue, 21 Mar 2023 15:03:14 +0300 Subject: [PATCH 23/32] NFC: Fixed writing gen1a magic tags with invalid BCC (#2511) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- applications/external/nfc_magic/lib/magic/magic.c | 5 ++--- applications/external/nfc_magic/nfc_magic_worker.c | 8 +++++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/applications/external/nfc_magic/lib/magic/magic.c b/applications/external/nfc_magic/lib/magic/magic.c index a922bc7a..9a71daaa 100644 --- a/applications/external/nfc_magic/lib/magic/magic.c +++ b/applications/external/nfc_magic/lib/magic/magic.c @@ -6,8 +6,7 @@ #define MAGIC_CMD_WUPA (0x40) #define MAGIC_CMD_WIPE (0x41) -#define MAGIC_CMD_READ (0x43) -#define MAGIC_CMD_WRITE (0x43) +#define MAGIC_CMD_ACCESS (0x43) #define MAGIC_MIFARE_READ_CMD (0x30) #define MAGIC_MIFARE_WRITE_CMD (0xA0) @@ -70,7 +69,7 @@ bool magic_data_access_cmd() { FuriHalNfcReturn ret = 0; do { - tx_data[0] = MAGIC_CMD_WRITE; + tx_data[0] = MAGIC_CMD_ACCESS; ret = furi_hal_nfc_ll_txrx_bits( tx_data, 8, diff --git a/applications/external/nfc_magic/nfc_magic_worker.c b/applications/external/nfc_magic/nfc_magic_worker.c index 523c794f..92eb793a 100644 --- a/applications/external/nfc_magic/nfc_magic_worker.c +++ b/applications/external/nfc_magic/nfc_magic_worker.c @@ -85,15 +85,17 @@ void nfc_magic_worker_write(NfcMagicWorker* nfc_magic_worker) { card_found_notified = true; } furi_hal_nfc_sleep(); - if(!magic_wupa()) { - FURI_LOG_E(TAG, "Not Magic card"); + FURI_LOG_E(TAG, "No card response to WUPA (not a magic card)"); nfc_magic_worker->callback( NfcMagicWorkerEventWrongCard, nfc_magic_worker->context); break; } + furi_hal_nfc_sleep(); + } + if(magic_wupa()) { if(!magic_data_access_cmd()) { - FURI_LOG_E(TAG, "Not Magic card"); + FURI_LOG_E(TAG, "No card response to data access command (not a magic card)"); nfc_magic_worker->callback( NfcMagicWorkerEventWrongCard, nfc_magic_worker->context); break; From ce50b09b286835d24d5d4d1aeca048260f1205a6 Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Tue, 21 Mar 2023 15:29:54 +0300 Subject: [PATCH 24/32] Remove hmac_sha256 from public API (#2519) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- {lib/toolbox => applications/main/u2f}/hmac_sha256.c | 0 {lib/toolbox => applications/main/u2f}/hmac_sha256.h | 0 applications/main/u2f/u2f.c | 2 +- firmware/targets/f18/api_symbols.csv | 7 ++----- firmware/targets/f7/api_symbols.csv | 7 ++----- lib/toolbox/SConscript | 2 +- 6 files changed, 6 insertions(+), 12 deletions(-) rename {lib/toolbox => applications/main/u2f}/hmac_sha256.c (100%) rename {lib/toolbox => applications/main/u2f}/hmac_sha256.h (100%) diff --git a/lib/toolbox/hmac_sha256.c b/applications/main/u2f/hmac_sha256.c similarity index 100% rename from lib/toolbox/hmac_sha256.c rename to applications/main/u2f/hmac_sha256.c diff --git a/lib/toolbox/hmac_sha256.h b/applications/main/u2f/hmac_sha256.h similarity index 100% rename from lib/toolbox/hmac_sha256.h rename to applications/main/u2f/hmac_sha256.h diff --git a/applications/main/u2f/u2f.c b/applications/main/u2f/u2f.c index 767733ce..0ed5ebb2 100644 --- a/applications/main/u2f/u2f.c +++ b/applications/main/u2f/u2f.c @@ -7,7 +7,7 @@ #include // for lfs_tobe32 #include "toolbox/sha256.h" -#include "toolbox/hmac_sha256.h" +#include "hmac_sha256.h" #include "micro-ecc/uECC.h" #define TAG "U2F" diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index 7fa269c9..b6be56f6 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,+,18.2,, +Version,+,19.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -160,7 +160,6 @@ Header,+,lib/toolbox/args.h,, Header,+,lib/toolbox/crc32_calc.h,, Header,+,lib/toolbox/dir_walk.h,, Header,+,lib/toolbox/float_tools.h,, -Header,+,lib/toolbox/hmac_sha256.h,, Header,+,lib/toolbox/manchester_decoder.h,, Header,+,lib/toolbox/manchester_encoder.h,, Header,+,lib/toolbox/md5.h,, @@ -169,6 +168,7 @@ Header,+,lib/toolbox/pretty_format.h,, Header,+,lib/toolbox/protocols/protocol_dict.h,, Header,+,lib/toolbox/random_name.h,, Header,+,lib/toolbox/saved_struct.h,, +Header,+,lib/toolbox/sha256.h,, Header,+,lib/toolbox/stream/buffered_file_stream.h,, Header,+,lib/toolbox/stream/file_stream.h,, Header,+,lib/toolbox/stream/stream.h,, @@ -1316,9 +1316,6 @@ Function,+,gui_view_port_send_to_front,void,"Gui*, ViewPort*" Function,+,hal_sd_detect,_Bool, Function,+,hal_sd_detect_init,void, Function,+,hal_sd_detect_set_low,void, -Function,+,hmac_sha256_finish,void,"const hmac_sha256_context*, const uint8_t*, uint8_t*" -Function,+,hmac_sha256_init,void,"hmac_sha256_context*, const uint8_t*" -Function,+,hmac_sha256_update,void,"const hmac_sha256_context*, const uint8_t*, unsigned" Function,+,icon_animation_alloc,IconAnimation*,const Icon* Function,+,icon_animation_free,void,IconAnimation* Function,+,icon_animation_get_height,uint8_t,const IconAnimation* diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index c98e5467..e6de39b1 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,+,18.2,, +Version,+,19.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -192,7 +192,6 @@ Header,+,lib/toolbox/args.h,, Header,+,lib/toolbox/crc32_calc.h,, Header,+,lib/toolbox/dir_walk.h,, Header,+,lib/toolbox/float_tools.h,, -Header,+,lib/toolbox/hmac_sha256.h,, Header,+,lib/toolbox/manchester_decoder.h,, Header,+,lib/toolbox/manchester_encoder.h,, Header,+,lib/toolbox/md5.h,, @@ -201,6 +200,7 @@ Header,+,lib/toolbox/pretty_format.h,, Header,+,lib/toolbox/protocols/protocol_dict.h,, Header,+,lib/toolbox/random_name.h,, Header,+,lib/toolbox/saved_struct.h,, +Header,+,lib/toolbox/sha256.h,, Header,+,lib/toolbox/stream/buffered_file_stream.h,, Header,+,lib/toolbox/stream/file_stream.h,, Header,+,lib/toolbox/stream/stream.h,, @@ -1604,9 +1604,6 @@ Function,+,gui_view_port_send_to_front,void,"Gui*, ViewPort*" Function,+,hal_sd_detect,_Bool, Function,+,hal_sd_detect_init,void, Function,+,hal_sd_detect_set_low,void, -Function,+,hmac_sha256_finish,void,"const hmac_sha256_context*, const uint8_t*, uint8_t*" -Function,+,hmac_sha256_init,void,"hmac_sha256_context*, const uint8_t*" -Function,+,hmac_sha256_update,void,"const hmac_sha256_context*, const uint8_t*, unsigned" Function,-,hypot,double,"double, double" Function,-,hypotf,float,"float, float" Function,-,hypotl,long double,"long double, long double" diff --git a/lib/toolbox/SConscript b/lib/toolbox/SConscript index d7b0e7bb..fad4c558 100644 --- a/lib/toolbox/SConscript +++ b/lib/toolbox/SConscript @@ -12,7 +12,7 @@ env.Append( File("manchester_encoder.h"), File("path.h"), File("random_name.h"), - File("hmac_sha256.h"), + File("sha256.h"), File("crc32_calc.h"), File("dir_walk.h"), File("md5.h"), From 6089e9210f67712284e14c147123938cd7be9bd9 Mon Sep 17 00:00:00 2001 From: Sam Edwards Date: Tue, 21 Mar 2023 08:53:07 -0600 Subject: [PATCH 25/32] BadUSB: implement boot protocol (#2496) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * BadUSB: remove unused out EP * BadUSB: do not use iad for a single interface * BadUSB: implement the boot protocol * BadUSB: implement SET_PROTOCOL * Improve HID report descriptor readability * CODEOWNERS update Co-authored-by: nminaylov Co-authored-by: あく --- .github/CODEOWNERS | 3 + .../targets/f7/furi_hal/furi_hal_usb_hid.c | 247 ++++++++++-------- 2 files changed, 136 insertions(+), 114 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 0bc13024..c1684aa9 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -44,6 +44,9 @@ /applications/examples/example_thermo/ @skotopes @DrZlo13 @hedger @gsurkov +# Firmware targets +/firmware/ @skotopes @DrZlo13 @hedger @nminaylov + # Assets /assets/resources/infrared/ @skotopes @DrZlo13 @hedger @gsurkov diff --git a/firmware/targets/f7/furi_hal/furi_hal_usb_hid.c b/firmware/targets/f7/furi_hal/furi_hal_usb_hid.c index 5cb7fd29..d2761341 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_usb_hid.c +++ b/firmware/targets/f7/furi_hal/furi_hal_usb_hid.c @@ -8,7 +8,6 @@ #include "usb_hid.h" #define HID_EP_IN 0x81 -#define HID_EP_OUT 0x01 #define HID_EP_SZ 0x10 #define HID_INTERVAL 2 @@ -16,17 +15,15 @@ #define HID_VID_DEFAULT 0x046D #define HID_PID_DEFAULT 0xC529 -struct HidIadDescriptor { - struct usb_iad_descriptor hid_iad; +struct HidIntfDescriptor { struct usb_interface_descriptor hid; struct usb_hid_descriptor hid_desc; struct usb_endpoint_descriptor hid_ep_in; - struct usb_endpoint_descriptor hid_ep_out; }; struct HidConfigDescriptor { struct usb_config_descriptor config; - struct HidIadDescriptor iad_0; + struct HidIntfDescriptor intf_0; } __attribute__((packed)); enum HidReportId { @@ -35,78 +32,98 @@ enum HidReportId { ReportIdConsumer = 3, }; -/* HID report: keyboard+mouse */ +/* HID report descriptor: keyboard + mouse + consumer control */ static const uint8_t hid_report_desc[] = { + // clang-format off HID_USAGE_PAGE(HID_PAGE_DESKTOP), HID_USAGE(HID_DESKTOP_KEYBOARD), HID_COLLECTION(HID_APPLICATION_COLLECTION), - HID_REPORT_ID(ReportIdKeyboard), - HID_USAGE_PAGE(HID_DESKTOP_KEYPAD), - HID_USAGE_MINIMUM(HID_KEYBOARD_L_CTRL), - HID_USAGE_MAXIMUM(HID_KEYBOARD_R_GUI), - HID_LOGICAL_MINIMUM(0), - HID_LOGICAL_MAXIMUM(1), - HID_REPORT_SIZE(1), - HID_REPORT_COUNT(8), - HID_INPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), - HID_REPORT_COUNT(1), - HID_REPORT_SIZE(8), - HID_INPUT(HID_IOF_CONSTANT | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), - HID_USAGE_PAGE(HID_PAGE_LED), - HID_REPORT_COUNT(8), - HID_REPORT_SIZE(1), - HID_USAGE_MINIMUM(1), - HID_USAGE_MAXIMUM(8), - HID_OUTPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), - HID_REPORT_COUNT(HID_KB_MAX_KEYS), - HID_REPORT_SIZE(8), - HID_LOGICAL_MINIMUM(0), - HID_LOGICAL_MAXIMUM(101), - HID_USAGE_PAGE(HID_DESKTOP_KEYPAD), - HID_USAGE_MINIMUM(0), - HID_USAGE_MAXIMUM(101), - HID_INPUT(HID_IOF_DATA | HID_IOF_ARRAY | HID_IOF_ABSOLUTE), + HID_REPORT_ID(ReportIdKeyboard), + // Keyboard report + HID_USAGE_PAGE(HID_DESKTOP_KEYPAD), + HID_USAGE_MINIMUM(HID_KEYBOARD_L_CTRL), + HID_USAGE_MAXIMUM(HID_KEYBOARD_R_GUI), + HID_LOGICAL_MINIMUM(0), + HID_LOGICAL_MAXIMUM(1), + HID_REPORT_SIZE(1), + HID_REPORT_COUNT(8), + // Input - Modifier keys byte + HID_INPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), + + HID_REPORT_COUNT(1), + HID_REPORT_SIZE(8), + // Input - Reserved byte + HID_INPUT(HID_IOF_CONSTANT | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), + + HID_USAGE_PAGE(HID_PAGE_LED), + HID_REPORT_COUNT(8), + HID_REPORT_SIZE(1), + HID_USAGE_MINIMUM(1), + HID_USAGE_MAXIMUM(8), + // Output - LEDs + HID_OUTPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), + + HID_REPORT_COUNT(HID_KB_MAX_KEYS), + HID_REPORT_SIZE(8), + HID_LOGICAL_MINIMUM(0), + HID_LOGICAL_MAXIMUM(101), + HID_USAGE_PAGE(HID_DESKTOP_KEYPAD), + HID_USAGE_MINIMUM(0), + HID_USAGE_MAXIMUM(101), + // Input - Key codes + HID_INPUT(HID_IOF_DATA | HID_IOF_ARRAY | HID_IOF_ABSOLUTE), HID_END_COLLECTION, + HID_USAGE_PAGE(HID_PAGE_DESKTOP), HID_USAGE(HID_DESKTOP_MOUSE), HID_COLLECTION(HID_APPLICATION_COLLECTION), - HID_USAGE(HID_DESKTOP_POINTER), - HID_COLLECTION(HID_PHYSICAL_COLLECTION), - HID_REPORT_ID(ReportIdMouse), - HID_USAGE_PAGE(HID_PAGE_BUTTON), - HID_USAGE_MINIMUM(1), - HID_USAGE_MAXIMUM(3), - HID_LOGICAL_MINIMUM(0), - HID_LOGICAL_MAXIMUM(1), - HID_REPORT_COUNT(3), - HID_REPORT_SIZE(1), - HID_INPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), - HID_REPORT_SIZE(1), - HID_REPORT_COUNT(5), - HID_INPUT(HID_IOF_CONSTANT | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), - HID_USAGE_PAGE(HID_PAGE_DESKTOP), - HID_USAGE(HID_DESKTOP_X), - HID_USAGE(HID_DESKTOP_Y), - HID_USAGE(HID_DESKTOP_WHEEL), - HID_LOGICAL_MINIMUM(-127), - HID_LOGICAL_MAXIMUM(127), - HID_REPORT_SIZE(8), - HID_REPORT_COUNT(3), - HID_INPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_RELATIVE), - HID_END_COLLECTION, + HID_USAGE(HID_DESKTOP_POINTER), + HID_COLLECTION(HID_PHYSICAL_COLLECTION), + HID_REPORT_ID(ReportIdMouse), + // Mouse report + HID_USAGE_PAGE(HID_PAGE_BUTTON), + HID_USAGE_MINIMUM(1), + HID_USAGE_MAXIMUM(3), + HID_LOGICAL_MINIMUM(0), + HID_LOGICAL_MAXIMUM(1), + HID_REPORT_COUNT(3), + HID_REPORT_SIZE(1), + // Input - Mouse keys + HID_INPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), + + HID_REPORT_SIZE(1), + HID_REPORT_COUNT(5), + // Input - Mouse keys padding + HID_INPUT(HID_IOF_CONSTANT | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), + + HID_USAGE_PAGE(HID_PAGE_DESKTOP), + HID_USAGE(HID_DESKTOP_X), + HID_USAGE(HID_DESKTOP_Y), + HID_USAGE(HID_DESKTOP_WHEEL), + HID_LOGICAL_MINIMUM(-127), + HID_LOGICAL_MAXIMUM(127), + HID_REPORT_SIZE(8), + HID_REPORT_COUNT(3), + // Input - Mouse movement data (x, y, scroll) + HID_INPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_RELATIVE), + HID_END_COLLECTION, HID_END_COLLECTION, + HID_USAGE_PAGE(HID_PAGE_CONSUMER), HID_USAGE(HID_CONSUMER_CONTROL), HID_COLLECTION(HID_APPLICATION_COLLECTION), - HID_REPORT_ID(ReportIdConsumer), - HID_LOGICAL_MINIMUM(0), - HID_RI_LOGICAL_MAXIMUM(16, 0x3FF), - HID_USAGE_MINIMUM(0), - HID_RI_USAGE_MAXIMUM(16, 0x3FF), - HID_REPORT_COUNT(HID_CONSUMER_MAX_KEYS), - HID_REPORT_SIZE(16), - HID_INPUT(HID_IOF_DATA | HID_IOF_ARRAY | HID_IOF_ABSOLUTE), + HID_REPORT_ID(ReportIdConsumer), + // Consumer report + HID_LOGICAL_MINIMUM(0), + HID_RI_LOGICAL_MAXIMUM(16, 0x3FF), + HID_USAGE_MINIMUM(0), + HID_RI_USAGE_MAXIMUM(16, 0x3FF), + HID_REPORT_COUNT(HID_CONSUMER_MAX_KEYS), + HID_REPORT_SIZE(16), + // Input - Consumer control keys + HID_INPUT(HID_IOF_DATA | HID_IOF_ARRAY | HID_IOF_ABSOLUTE), HID_END_COLLECTION, + // clang-format on }; /* Device descriptor */ @@ -114,9 +131,9 @@ static struct usb_device_descriptor hid_device_desc = { .bLength = sizeof(struct usb_device_descriptor), .bDescriptorType = USB_DTYPE_DEVICE, .bcdUSB = VERSION_BCD(2, 0, 0), - .bDeviceClass = USB_CLASS_IAD, - .bDeviceSubClass = USB_SUBCLASS_IAD, - .bDeviceProtocol = USB_PROTO_IAD, + .bDeviceClass = USB_CLASS_PER_INTERFACE, + .bDeviceSubClass = USB_SUBCLASS_NONE, + .bDeviceProtocol = USB_PROTO_NONE, .bMaxPacketSize0 = USB_EP0_SIZE, .idVendor = HID_VID_DEFAULT, .idProduct = HID_PID_DEFAULT, @@ -140,29 +157,18 @@ static const struct HidConfigDescriptor hid_cfg_desc = { .bmAttributes = USB_CFG_ATTR_RESERVED | USB_CFG_ATTR_SELFPOWERED, .bMaxPower = USB_CFG_POWER_MA(100), }, - .iad_0 = + .intf_0 = { - .hid_iad = - { - .bLength = sizeof(struct usb_iad_descriptor), - .bDescriptorType = USB_DTYPE_INTERFASEASSOC, - .bFirstInterface = 0, - .bInterfaceCount = 1, - .bFunctionClass = USB_CLASS_PER_INTERFACE, - .bFunctionSubClass = USB_SUBCLASS_NONE, - .bFunctionProtocol = USB_PROTO_NONE, - .iFunction = NO_DESCRIPTOR, - }, .hid = { .bLength = sizeof(struct usb_interface_descriptor), .bDescriptorType = USB_DTYPE_INTERFACE, .bInterfaceNumber = 0, .bAlternateSetting = 0, - .bNumEndpoints = 2, + .bNumEndpoints = 1, .bInterfaceClass = USB_CLASS_HID, - .bInterfaceSubClass = USB_HID_SUBCLASS_NONBOOT, - .bInterfaceProtocol = USB_HID_PROTO_NONBOOT, + .bInterfaceSubClass = USB_HID_SUBCLASS_BOOT, + .bInterfaceProtocol = USB_HID_PROTO_KEYBOARD, .iInterface = NO_DESCRIPTOR, }, .hid_desc = @@ -184,15 +190,6 @@ static const struct HidConfigDescriptor hid_cfg_desc = { .wMaxPacketSize = HID_EP_SZ, .bInterval = HID_INTERVAL, }, - .hid_ep_out = - { - .bLength = sizeof(struct usb_endpoint_descriptor), - .bDescriptorType = USB_DTYPE_ENDPOINT, - .bEndpointAddress = HID_EP_OUT, - .bmAttributes = USB_EPTYPE_INTERRUPT, - .wMaxPacketSize = HID_EP_SZ, - .bInterval = HID_INTERVAL, - }, }, }; @@ -206,9 +203,11 @@ struct HidReportMouse { struct HidReportKB { uint8_t report_id; - uint8_t mods; - uint8_t reserved; - uint8_t btn[HID_KB_MAX_KEYS]; + struct { + uint8_t mods; + uint8_t reserved; + uint8_t btn[HID_KB_MAX_KEYS]; + } boot; } __attribute__((packed)); struct HidReportConsumer { @@ -256,6 +255,7 @@ static bool hid_connected = false; static HidStateCallback callback; static void* cb_ctx; static uint8_t led_state; +static bool boot_protocol = false; bool furi_hal_hid_is_connected() { return hid_connected; @@ -280,31 +280,31 @@ void furi_hal_hid_set_state_callback(HidStateCallback cb, void* ctx) { bool furi_hal_hid_kb_press(uint16_t button) { for(uint8_t key_nb = 0; key_nb < HID_KB_MAX_KEYS; key_nb++) { - if(hid_report.keyboard.btn[key_nb] == 0) { - hid_report.keyboard.btn[key_nb] = button & 0xFF; + if(hid_report.keyboard.boot.btn[key_nb] == 0) { + hid_report.keyboard.boot.btn[key_nb] = button & 0xFF; break; } } - hid_report.keyboard.mods |= (button >> 8); + hid_report.keyboard.boot.mods |= (button >> 8); return hid_send_report(ReportIdKeyboard); } bool furi_hal_hid_kb_release(uint16_t button) { for(uint8_t key_nb = 0; key_nb < HID_KB_MAX_KEYS; key_nb++) { - if(hid_report.keyboard.btn[key_nb] == (button & 0xFF)) { - hid_report.keyboard.btn[key_nb] = 0; + if(hid_report.keyboard.boot.btn[key_nb] == (button & 0xFF)) { + hid_report.keyboard.boot.btn[key_nb] = 0; break; } } - hid_report.keyboard.mods &= ~(button >> 8); + hid_report.keyboard.boot.mods &= ~(button >> 8); return hid_send_report(ReportIdKeyboard); } bool furi_hal_hid_kb_release_all() { for(uint8_t key_nb = 0; key_nb < HID_KB_MAX_KEYS; key_nb++) { - hid_report.keyboard.btn[key_nb] = 0; + hid_report.keyboard.boot.btn[key_nb] = 0; } - hid_report.keyboard.mods = 0; + hid_report.keyboard.boot.mods = 0; return hid_send_report(ReportIdKeyboard); } @@ -434,27 +434,35 @@ static void hid_on_suspend(usbd_device* dev) { static bool hid_send_report(uint8_t report_id) { if((hid_semaphore == NULL) || (hid_connected == false)) return false; + if((boot_protocol == true) && (report_id != ReportIdKeyboard)) return false; furi_check(furi_semaphore_acquire(hid_semaphore, FuriWaitForever) == FuriStatusOk); - if(hid_connected == true) { + if(hid_connected == false) { + return false; + } + if(boot_protocol == true) { + usbd_ep_write( + usb_dev, HID_EP_IN, &hid_report.keyboard.boot, sizeof(hid_report.keyboard.boot)); + } else { if(report_id == ReportIdKeyboard) usbd_ep_write(usb_dev, HID_EP_IN, &hid_report.keyboard, sizeof(hid_report.keyboard)); else if(report_id == ReportIdMouse) usbd_ep_write(usb_dev, HID_EP_IN, &hid_report.mouse, sizeof(hid_report.mouse)); else if(report_id == ReportIdConsumer) usbd_ep_write(usb_dev, HID_EP_IN, &hid_report.consumer, sizeof(hid_report.consumer)); - return true; } - return false; + return true; } static void hid_txrx_ep_callback(usbd_device* dev, uint8_t event, uint8_t ep) { UNUSED(dev); if(event == usbd_evt_eptx) { furi_semaphore_release(hid_semaphore); + } else if(boot_protocol == true) { + usbd_ep_read(usb_dev, ep, &led_state, sizeof(led_state)); } else { struct HidReportLED leds; - usbd_ep_read(usb_dev, ep, &leds, 2); + usbd_ep_read(usb_dev, ep, &leds, sizeof(leds)); led_state = leds.led_state; } } @@ -464,18 +472,15 @@ static usbd_respond hid_ep_config(usbd_device* dev, uint8_t cfg) { switch(cfg) { case 0: /* deconfiguring device */ - usbd_ep_deconfig(dev, HID_EP_OUT); usbd_ep_deconfig(dev, HID_EP_IN); - usbd_reg_endpoint(dev, HID_EP_OUT, 0); usbd_reg_endpoint(dev, HID_EP_IN, 0); return usbd_ack; case 1: /* configuring device */ usbd_ep_config(dev, HID_EP_IN, USB_EPTYPE_INTERRUPT, HID_EP_SZ); - usbd_ep_config(dev, HID_EP_OUT, USB_EPTYPE_INTERRUPT, HID_EP_SZ); usbd_reg_endpoint(dev, HID_EP_IN, hid_txrx_ep_callback); - usbd_reg_endpoint(dev, HID_EP_OUT, hid_txrx_ep_callback); usbd_ep_write(dev, HID_EP_IN, 0, 0); + boot_protocol = false; /* BIOS will SET_PROTOCOL if it wants this */ return usbd_ack; default: return usbd_fail; @@ -493,8 +498,21 @@ static usbd_respond hid_control(usbd_device* dev, usbd_ctlreq* req, usbd_rqc_cal case USB_HID_SETIDLE: return usbd_ack; case USB_HID_GETREPORT: - dev->status.data_ptr = &hid_report; - dev->status.data_count = sizeof(hid_report); + if(boot_protocol == true) { + dev->status.data_ptr = &hid_report.keyboard.boot; + dev->status.data_count = sizeof(hid_report.keyboard.boot); + } else { + dev->status.data_ptr = &hid_report; + dev->status.data_count = sizeof(hid_report); + } + return usbd_ack; + case USB_HID_SETPROTOCOL: + if(req->wValue == 0) + boot_protocol = true; + else if(req->wValue == 1) + boot_protocol = false; + else + return usbd_fail; return usbd_ack; default: return usbd_fail; @@ -505,10 +523,11 @@ static usbd_respond hid_control(usbd_device* dev, usbd_ctlreq* req, usbd_rqc_cal req->wIndex == 0 && req->bRequest == USB_STD_GET_DESCRIPTOR) { switch(req->wValue >> 8) { case USB_DTYPE_HID: - dev->status.data_ptr = (uint8_t*)&(hid_cfg_desc.iad_0.hid_desc); - dev->status.data_count = sizeof(hid_cfg_desc.iad_0.hid_desc); + dev->status.data_ptr = (uint8_t*)&(hid_cfg_desc.intf_0.hid_desc); + dev->status.data_count = sizeof(hid_cfg_desc.intf_0.hid_desc); return usbd_ack; case USB_DTYPE_HID_REPORT: + boot_protocol = false; /* BIOS does not read this */ dev->status.data_ptr = (uint8_t*)hid_report_desc; dev->status.data_count = sizeof(hid_report_desc); return usbd_ack; From 204b50381a0975b72af8b0be33b6371e69c024a0 Mon Sep 17 00:00:00 2001 From: Shukai Ni Date: Wed, 22 Mar 2023 05:47:47 -0400 Subject: [PATCH 26/32] Correct FAP default upload path in AppsOnSDCard.md (#2524) Since the fap's source code is in `applications_user`, the documentation should also point to `applications_user` as the parent directory --- documentation/AppsOnSDCard.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/AppsOnSDCard.md b/documentation/AppsOnSDCard.md index 75430570..212df5b1 100644 --- a/documentation/AppsOnSDCard.md +++ b/documentation/AppsOnSDCard.md @@ -13,7 +13,7 @@ FAPs are created and developed the same way as internal applications that are pa To build your application as a FAP, create a folder with your app's source code in `applications_user`, then write its code the way you'd do when creating a regular built-in application. Then configure its `application.fam` manifest, and set its _apptype_ to FlipperAppType.EXTERNAL. See [Application Manifests](./AppManifests.md#application-definition) for more details. - To build your application, run `./fbt fap_{APPID}`, where APPID is your application's ID in its manifest. -- To build your app and upload it over USB to run on Flipper, use `./fbt launch_app APPSRC=applications/path/to/app`. This command is configured in the default [VS Code profile](../.vscode/ReadMe.md) as a "Launch App on Flipper" build action (Ctrl+Shift+B menu). +- To build your app and upload it over USB to run on Flipper, use `./fbt launch_app APPSRC=applications_user/path/to/app`. This command is configured in the default [VS Code profile](../.vscode/ReadMe.md) as a "Launch App on Flipper" build action (Ctrl+Shift+B menu). - To build all FAPs, run `./fbt faps` or `./fbt fap_dist`. ## FAP assets From acc32f66e843a13996ae16c6287de6cbcf98be70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Wed, 22 Mar 2023 19:48:41 +0900 Subject: [PATCH 27/32] Github: force cleanup tree on decontaminate (#2526) --- .github/workflows/build.yml | 2 +- .github/workflows/lint_and_submodule_check.yml | 2 +- .github/workflows/merge_report.yml | 2 +- .github/workflows/pvs_studio.yml | 2 +- .github/workflows/unit_tests.yml | 2 +- .github/workflows/updater_test.yml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6ab2490c..898a1291 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,7 +24,7 @@ jobs: git submodule status || ( git ls-files --stage | egrep '^160000' | awk '{print $4}' | while read submodule do - git rm -rf --cached "$submodule" + git rm -rf "$submodule" done ) fi diff --git a/.github/workflows/lint_and_submodule_check.yml b/.github/workflows/lint_and_submodule_check.yml index 46cca5c0..10b3c1d9 100644 --- a/.github/workflows/lint_and_submodule_check.yml +++ b/.github/workflows/lint_and_submodule_check.yml @@ -24,7 +24,7 @@ jobs: git submodule status || ( git ls-files --stage | egrep '^160000' | awk '{print $4}' | while read submodule do - git rm -rf --cached "$submodule" + git rm -rf "$submodule" done ) fi diff --git a/.github/workflows/merge_report.yml b/.github/workflows/merge_report.yml index e88346ed..a382733d 100644 --- a/.github/workflows/merge_report.yml +++ b/.github/workflows/merge_report.yml @@ -18,7 +18,7 @@ jobs: git submodule status || ( git ls-files --stage | egrep '^160000' | awk '{print $4}' | while read submodule do - git rm -rf --cached "$submodule" + git rm -rf "$submodule" done ) fi diff --git a/.github/workflows/pvs_studio.yml b/.github/workflows/pvs_studio.yml index 65ffd195..d11e268d 100644 --- a/.github/workflows/pvs_studio.yml +++ b/.github/workflows/pvs_studio.yml @@ -25,7 +25,7 @@ jobs: git submodule status || ( git ls-files --stage | egrep '^160000' | awk '{print $4}' | while read submodule do - git rm -rf --cached "$submodule" + git rm -rf "$submodule" done ) fi diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 6f044ebc..0ec53106 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -18,7 +18,7 @@ jobs: git submodule status || ( git ls-files --stage | egrep '^160000' | awk '{print $4}' | while read submodule do - git rm -rf --cached "$submodule" + git rm -rf "$submodule" done ) fi diff --git a/.github/workflows/updater_test.yml b/.github/workflows/updater_test.yml index c04d526f..e1e655b0 100644 --- a/.github/workflows/updater_test.yml +++ b/.github/workflows/updater_test.yml @@ -18,7 +18,7 @@ jobs: git submodule status || ( git ls-files --stage | egrep '^160000' | awk '{print $4}' | while read submodule do - git rm -rf --cached "$submodule" + git rm -rf "$submodule" done ) fi From 973287b09b2e001503d91d94e05d9fe04bfafd6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Wed, 22 Mar 2023 20:26:40 +0900 Subject: [PATCH 28/32] Github: wipe workspace before checkout (#2527) * Github: wipe workspace before checkout * Github: allow find to fail * Github: limit maxdepth for find --- .github/workflows/build.yml | 21 ++++--------------- .../workflows/lint_and_submodule_check.yml | 12 ++--------- .github/workflows/merge_report.yml | 12 ++--------- .github/workflows/pvs_studio.yml | 12 ++--------- .github/workflows/unit_tests.yml | 12 ++--------- .github/workflows/updater_test.yml | 20 ++++-------------- 6 files changed, 16 insertions(+), 73 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 898a1291..56e50d5f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,16 +18,8 @@ jobs: main: runs-on: [self-hosted,FlipperZeroShell] steps: - - name: 'Decontaminate previous build leftovers' - run: | - if [ -d .git ]; then - git submodule status || ( - git ls-files --stage | egrep '^160000' | awk '{print $4}' | while read submodule - do - git rm -rf "$submodule" - done - ) - fi + - name: 'Wipe workspace' + run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; - name: 'Checkout code' uses: actions/checkout@v3 @@ -171,13 +163,8 @@ jobs: if: ${{ !startsWith(github.ref, 'refs/tags') }} runs-on: [self-hosted,FlipperZeroShell] steps: - - name: 'Decontaminate previous build leftovers' - run: | - if [ -d .git ] - then - git submodule status \ - || git checkout "$(git rev-list --max-parents=0 HEAD | tail -n 1)" - fi + - name: 'Wipe workspace' + run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; - name: 'Checkout code' uses: actions/checkout@v3 diff --git a/.github/workflows/lint_and_submodule_check.yml b/.github/workflows/lint_and_submodule_check.yml index 10b3c1d9..cecfd124 100644 --- a/.github/workflows/lint_and_submodule_check.yml +++ b/.github/workflows/lint_and_submodule_check.yml @@ -18,16 +18,8 @@ jobs: lint_sources_check_submodules: runs-on: [self-hosted,FlipperZeroShell] steps: - - name: 'Decontaminate previous build leftovers' - run: | - if [ -d .git ]; then - git submodule status || ( - git ls-files --stage | egrep '^160000' | awk '{print $4}' | while read submodule - do - git rm -rf "$submodule" - done - ) - fi + - name: 'Wipe workspace' + run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; - name: 'Checkout code' uses: actions/checkout@v3 diff --git a/.github/workflows/merge_report.yml b/.github/workflows/merge_report.yml index a382733d..71515e1c 100644 --- a/.github/workflows/merge_report.yml +++ b/.github/workflows/merge_report.yml @@ -12,16 +12,8 @@ jobs: merge_report: runs-on: [self-hosted,FlipperZeroShell] steps: - - name: 'Decontaminate previous build leftovers' - run: | - if [ -d .git ]; then - git submodule status || ( - git ls-files --stage | egrep '^160000' | awk '{print $4}' | while read submodule - do - git rm -rf "$submodule" - done - ) - fi + - name: 'Wipe workspace' + run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; - name: 'Checkout code' uses: actions/checkout@v3 diff --git a/.github/workflows/pvs_studio.yml b/.github/workflows/pvs_studio.yml index d11e268d..6dbf84ed 100644 --- a/.github/workflows/pvs_studio.yml +++ b/.github/workflows/pvs_studio.yml @@ -19,16 +19,8 @@ jobs: if: ${{ !github.event.pull_request.head.repo.fork }} runs-on: [self-hosted, FlipperZeroShell] steps: - - name: 'Decontaminate previous build leftovers' - run: | - if [ -d .git ]; then - git submodule status || ( - git ls-files --stage | egrep '^160000' | awk '{print $4}' | while read submodule - do - git rm -rf "$submodule" - done - ) - fi + - name: 'Wipe workspace' + run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; - name: 'Checkout code' uses: actions/checkout@v3 diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 0ec53106..6a824fac 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -12,16 +12,8 @@ jobs: run_units_on_bench: runs-on: [self-hosted, FlipperZeroUnitTest] steps: - - name: 'Decontaminate previous build leftovers' - run: | - if [ -d .git ]; then - git submodule status || ( - git ls-files --stage | egrep '^160000' | awk '{print $4}' | while read submodule - do - git rm -rf "$submodule" - done - ) - fi + - name: 'Wipe workspace' + run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; - name: Checkout code uses: actions/checkout@v3 diff --git a/.github/workflows/updater_test.yml b/.github/workflows/updater_test.yml index e1e655b0..2861529d 100644 --- a/.github/workflows/updater_test.yml +++ b/.github/workflows/updater_test.yml @@ -12,16 +12,8 @@ jobs: test_updater_on_bench: runs-on: [self-hosted, FlipperZeroUpdaterTest] steps: - - name: 'Decontaminate previous build leftovers' - run: | - if [ -d .git ]; then - git submodule status || ( - git ls-files --stage | egrep '^160000' | awk '{print $4}' | while read submodule - do - git rm -rf "$submodule" - done - ) - fi + - name: 'Wipe workspace' + run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; - name: Checkout code uses: actions/checkout@v3 @@ -57,12 +49,8 @@ jobs: run: | echo "tag=$(git tag -l --sort=-version:refname | grep -v "rc\|RC" | head -1)" >> $GITHUB_OUTPUT - - name: 'Decontaminate previous build leftovers' - if: failure() - run: | - if [ -d .git ]; then - git submodule status || git checkout "$(git rev-list --max-parents=0 HEAD | tail -n 1)" - fi + - name: 'Wipe workspace' + run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; - name: 'Checkout latest release' uses: actions/checkout@v3 From 1f236ede0e4d13f2ca0c031bd7c0e2017540d68c Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Wed, 22 Mar 2023 17:41:14 +0300 Subject: [PATCH 29/32] [#2501] Disable UART IRQs by default (#2523) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- firmware/targets/f7/furi_hal/furi_hal_uart.c | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/firmware/targets/f7/furi_hal/furi_hal_uart.c b/firmware/targets/f7/furi_hal/furi_hal_uart.c index 54232e67..71b5c7ba 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_uart.c +++ b/firmware/targets/f7/furi_hal/furi_hal_uart.c @@ -44,7 +44,8 @@ static void furi_hal_usart_init(uint32_t baud) { while(!LL_USART_IsActiveFlag_TEACK(USART1) || !LL_USART_IsActiveFlag_REACK(USART1)) ; - LL_USART_EnableIT_RXNE_RXFNE(USART1); + LL_USART_DisableIT_ERROR(USART1); + NVIC_SetPriority(USART1_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 5, 0)); } @@ -79,8 +80,8 @@ static void furi_hal_lpuart_init(uint32_t baud) { ; furi_hal_uart_set_br(FuriHalUartIdLPUART1, baud); + LL_LPUART_DisableIT_ERROR(LPUART1); - LL_LPUART_EnableIT_RXNE_RXFNE(LPUART1); NVIC_SetPriority(LPUART1_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 5, 0)); } @@ -190,19 +191,25 @@ void furi_hal_uart_set_irq_cb( void (*cb)(UartIrqEvent ev, uint8_t data, void* ctx), void* ctx) { if(cb == NULL) { - if(ch == FuriHalUartIdUSART1) + if(ch == FuriHalUartIdUSART1) { NVIC_DisableIRQ(USART1_IRQn); - else if(ch == FuriHalUartIdLPUART1) + LL_USART_DisableIT_RXNE_RXFNE(USART1); + } else if(ch == FuriHalUartIdLPUART1) { NVIC_DisableIRQ(LPUART1_IRQn); + LL_LPUART_DisableIT_RXNE_RXFNE(LPUART1); + } irq_cb[ch] = cb; irq_ctx[ch] = ctx; } else { irq_ctx[ch] = ctx; irq_cb[ch] = cb; - if(ch == FuriHalUartIdUSART1) + if(ch == FuriHalUartIdUSART1) { NVIC_EnableIRQ(USART1_IRQn); - else if(ch == FuriHalUartIdLPUART1) + LL_USART_EnableIT_RXNE_RXFNE(USART1); + } else if(ch == FuriHalUartIdLPUART1) { NVIC_EnableIRQ(LPUART1_IRQn); + LL_LPUART_EnableIT_RXNE_RXFNE(LPUART1); + } } } From 8b224ecb15d0899c3e67edd693a18f58d9029b57 Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Wed, 22 Mar 2023 17:54:06 +0300 Subject: [PATCH 30/32] [FL-3179] 1-Wire Overdrive Mode (#2522) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Separate ibutton to its own module, add one_wire to f18 * Move onewire cli to a separate app * Add definitions for normal and overdrive timings * Update api definitions * Add rough overdrive timings definition for onewire emulation * Remove one_wire_host_timing.h * Add rough overdrive timings for onewire host * Improve overdrive mode * Working overdrive mode from flipper to flipper * Update thermometer example app * Turn on otg power when running thermometer example app * Implement reset overdrive switching * Always exit out of overdrive mode * Improve overdrive timings * Fix typos * Fix reset behaviour * Use overdrive mode everywhere in DS1996 * Improve comments * Bump API version Co-authored-by: あく --- .../examples/example_thermo/example_thermo.c | 13 +- firmware/targets/f18/api_symbols.csv | 8 +- firmware/targets/f7/api_symbols.csv | 8 +- lib/ibutton/protocols/dallas/dallas_common.h | 4 +- .../protocols/dallas/protocol_dallas_base.h | 4 +- .../protocols/dallas/protocol_ds1971.c | 18 +- .../protocols/dallas/protocol_ds1990.c | 10 +- .../protocols/dallas/protocol_ds1992.c | 10 +- .../protocols/dallas/protocol_ds1996.c | 62 ++++-- .../protocols/dallas/protocol_ds_generic.c | 10 +- lib/one_wire/SConscript | 1 - lib/one_wire/one_wire_host.c | 85 ++++++-- lib/one_wire/one_wire_host.h | 89 +++++---- lib/one_wire/one_wire_host_timing.h | 30 --- lib/one_wire/one_wire_slave.c | 188 +++++++++++------- lib/one_wire/one_wire_slave.h | 84 +++++--- 16 files changed, 397 insertions(+), 227 deletions(-) delete mode 100644 lib/one_wire/one_wire_host_timing.h diff --git a/applications/examples/example_thermo/example_thermo.c b/applications/examples/example_thermo/example_thermo.c index b3bc7cd9..4241cb59 100644 --- a/applications/examples/example_thermo/example_thermo.c +++ b/applications/examples/example_thermo/example_thermo.c @@ -19,9 +19,12 @@ #include #include +#include + #define UPDATE_PERIOD_MS 1000UL #define TEXT_STORE_SIZE 64U +#define DS18B20_CMD_SKIP_ROM 0xccU #define DS18B20_CMD_CONVERT 0x44U #define DS18B20_CMD_READ_SCRATCHPAD 0xbeU @@ -92,7 +95,7 @@ static void example_thermo_request_temperature(ExampleThermoContext* context) { /* After the reset, a ROM operation must follow. If there is only one device connected, the "Skip ROM" command is most appropriate (it can also be used to address all of the connected devices in some cases).*/ - onewire_host_skip(onewire); + onewire_host_write(onewire, DS18B20_CMD_SKIP_ROM); /* After the ROM operation, a device-specific command is issued. In this case, it's a request to start measuring the temperature. */ onewire_host_write(onewire, DS18B20_CMD_CONVERT); @@ -133,7 +136,7 @@ static void example_thermo_read_temperature(ExampleThermoContext* context) { /* After the reset, a ROM operation must follow. If there is only one device connected, the "Skip ROM" command is most appropriate (it can also be used to address all of the connected devices in some cases).*/ - onewire_host_skip(onewire); + onewire_host_write(onewire, DS18B20_CMD_SKIP_ROM); /* After the ROM operation, a device-specific command is issued. This time, it will be the "Read Scratchpad" command which will @@ -267,6 +270,9 @@ static void example_thermo_input_callback(InputEvent* event, void* ctx) { /* Starts the reader thread and handles the input */ static void example_thermo_run(ExampleThermoContext* context) { + /* Enable power on external pins */ + furi_hal_power_enable_otg(); + /* Configure the hardware in host mode */ onewire_host_start(context->onewire); @@ -299,6 +305,9 @@ static void example_thermo_run(ExampleThermoContext* context) { /* Reset the hardware */ onewire_host_stop(context->onewire); + + /* Disable power on external pins */ + furi_hal_power_disable_otg(); } /******************** Initialisation & startup *****************************/ diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index b6be56f6..e6fae33e 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,+,19.0,, +Version,+,20.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -153,7 +153,6 @@ Header,+,lib/mlib/m-tuple.h,, Header,+,lib/mlib/m-variant.h,, Header,+,lib/one_wire/maxim_crc.h,, Header,+,lib/one_wire/one_wire_host.h,, -Header,+,lib/one_wire/one_wire_host_timing.h,, Header,+,lib/one_wire/one_wire_slave.h,, Header,+,lib/print/wrappers.h,, Header,+,lib/toolbox/args.h,, @@ -1481,8 +1480,8 @@ Function,+,onewire_host_read_bit,_Bool,OneWireHost* Function,+,onewire_host_read_bytes,void,"OneWireHost*, uint8_t*, uint16_t" Function,+,onewire_host_reset,_Bool,OneWireHost* Function,+,onewire_host_reset_search,void,OneWireHost* -Function,+,onewire_host_search,uint8_t,"OneWireHost*, uint8_t*, OneWireHostSearchMode" -Function,+,onewire_host_skip,void,OneWireHost* +Function,+,onewire_host_search,_Bool,"OneWireHost*, uint8_t*, OneWireHostSearchMode" +Function,+,onewire_host_set_overdrive,void,"OneWireHost*, _Bool" Function,+,onewire_host_start,void,OneWireHost* Function,+,onewire_host_stop,void,OneWireHost* Function,+,onewire_host_target_search,void,"OneWireHost*, uint8_t" @@ -1496,6 +1495,7 @@ Function,+,onewire_slave_receive_bit,_Bool,OneWireSlave* Function,+,onewire_slave_send,_Bool,"OneWireSlave*, const uint8_t*, size_t" Function,+,onewire_slave_send_bit,_Bool,"OneWireSlave*, _Bool" Function,+,onewire_slave_set_command_callback,void,"OneWireSlave*, OneWireSlaveCommandCallback, void*" +Function,+,onewire_slave_set_overdrive,void,"OneWireSlave*, _Bool" Function,+,onewire_slave_set_reset_callback,void,"OneWireSlave*, OneWireSlaveResetCallback, void*" Function,+,onewire_slave_set_result_callback,void,"OneWireSlave*, OneWireSlaveResultCallback, void*" Function,+,onewire_slave_start,void,OneWireSlave* diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index e6de39b1..7ac9a245 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,+,19.0,, +Version,+,20.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -172,7 +172,6 @@ Header,+,lib/mlib/m-variant.h,, Header,+,lib/nfc/nfc_device.h,, Header,+,lib/one_wire/maxim_crc.h,, Header,+,lib/one_wire/one_wire_host.h,, -Header,+,lib/one_wire/one_wire_host_timing.h,, Header,+,lib/one_wire/one_wire_slave.h,, Header,+,lib/print/wrappers.h,, Header,+,lib/subghz/blocks/const.h,, @@ -2062,8 +2061,8 @@ Function,+,onewire_host_read_bit,_Bool,OneWireHost* Function,+,onewire_host_read_bytes,void,"OneWireHost*, uint8_t*, uint16_t" Function,+,onewire_host_reset,_Bool,OneWireHost* Function,+,onewire_host_reset_search,void,OneWireHost* -Function,+,onewire_host_search,uint8_t,"OneWireHost*, uint8_t*, OneWireHostSearchMode" -Function,+,onewire_host_skip,void,OneWireHost* +Function,+,onewire_host_search,_Bool,"OneWireHost*, uint8_t*, OneWireHostSearchMode" +Function,+,onewire_host_set_overdrive,void,"OneWireHost*, _Bool" Function,+,onewire_host_start,void,OneWireHost* Function,+,onewire_host_stop,void,OneWireHost* Function,+,onewire_host_target_search,void,"OneWireHost*, uint8_t" @@ -2077,6 +2076,7 @@ Function,+,onewire_slave_receive_bit,_Bool,OneWireSlave* Function,+,onewire_slave_send,_Bool,"OneWireSlave*, const uint8_t*, size_t" Function,+,onewire_slave_send_bit,_Bool,"OneWireSlave*, _Bool" Function,+,onewire_slave_set_command_callback,void,"OneWireSlave*, OneWireSlaveCommandCallback, void*" +Function,+,onewire_slave_set_overdrive,void,"OneWireSlave*, _Bool" Function,+,onewire_slave_set_reset_callback,void,"OneWireSlave*, OneWireSlaveResetCallback, void*" Function,+,onewire_slave_set_result_callback,void,"OneWireSlave*, OneWireSlaveResultCallback, void*" Function,+,onewire_slave_start,void,OneWireSlave* diff --git a/lib/ibutton/protocols/dallas/dallas_common.h b/lib/ibutton/protocols/dallas/dallas_common.h index 7991a1f8..6f5ff7cc 100644 --- a/lib/ibutton/protocols/dallas/dallas_common.h +++ b/lib/ibutton/protocols/dallas/dallas_common.h @@ -1,10 +1,10 @@ #pragma once -#include - #include #include +#include + #define DALLAS_COMMON_MANUFACTURER_NAME "Dallas" #define DALLAS_COMMON_CMD_READ_ROM 0x33U diff --git a/lib/ibutton/protocols/dallas/protocol_dallas_base.h b/lib/ibutton/protocols/dallas/protocol_dallas_base.h index b4edb2b2..55e10993 100644 --- a/lib/ibutton/protocols/dallas/protocol_dallas_base.h +++ b/lib/ibutton/protocols/dallas/protocol_dallas_base.h @@ -2,11 +2,11 @@ #include "../protocol_common_i.h" -#include - #include #include +#include + typedef bool (*iButtonProtocolDallasReadWriteFunc)(OneWireHost*, iButtonProtocolData*); typedef void (*iButtonProtocolDallasEmulateFunc)(OneWireSlave*, iButtonProtocolData*); typedef bool (*iButtonProtocolDallasSaveFunc)(FlipperFormat*, const iButtonProtocolData*); diff --git a/lib/ibutton/protocols/dallas/protocol_ds1971.c b/lib/ibutton/protocols/dallas/protocol_ds1971.c index eb5b330b..a806acb2 100644 --- a/lib/ibutton/protocols/dallas/protocol_ds1971.c +++ b/lib/ibutton/protocols/dallas/protocol_ds1971.c @@ -53,7 +53,7 @@ const iButtonProtocolDallasBase ibutton_protocol_ds1971 = { .name = DS1971_FAMILY_NAME, .read = dallas_ds1971_read, - .write_blank = NULL, /* No data to write a blank */ + .write_blank = NULL, // TODO: Implement writing to blank .write_copy = dallas_ds1971_write_copy, .emulate = dallas_ds1971_emulate, .save = dallas_ds1971_save, @@ -76,7 +76,7 @@ bool dallas_ds1971_write_copy(OneWireHost* host, iButtonProtocolData* protocol_d DS1971ProtocolData* data = protocol_data; onewire_host_reset(host); - onewire_host_skip(host); + onewire_host_write(host, DALLAS_COMMON_CMD_SKIP_ROM); // Starting writing from address 0x0000 onewire_host_write(host, DALLAS_COMMON_CMD_WRITE_SCRATCH); onewire_host_write(host, 0x00); @@ -87,7 +87,7 @@ bool dallas_ds1971_write_copy(OneWireHost* host, iButtonProtocolData* protocol_d bool pad_valid = false; if(onewire_host_reset(host)) { pad_valid = true; - onewire_host_skip(host); + onewire_host_write(host, DALLAS_COMMON_CMD_SKIP_ROM); onewire_host_write(host, DALLAS_COMMON_CMD_READ_SCRATCH); onewire_host_write(host, 0x00); @@ -103,7 +103,7 @@ bool dallas_ds1971_write_copy(OneWireHost* host, iButtonProtocolData* protocol_d // Copy scratchpad to memory and confirm if(pad_valid) { if(onewire_host_reset(host)) { - onewire_host_skip(host); + onewire_host_write(host, DALLAS_COMMON_CMD_SKIP_ROM); onewire_host_write(host, DALLAS_COMMON_CMD_COPY_SCRATCH); onewire_host_write(host, DS1971_CMD_FINALIZATION); @@ -114,10 +114,16 @@ bool dallas_ds1971_write_copy(OneWireHost* host, iButtonProtocolData* protocol_d return pad_valid; } -static void dallas_ds1971_reset_callback(void* context) { +static bool dallas_ds1971_reset_callback(bool is_short, void* context) { furi_assert(context); DS1971ProtocolData* data = context; - data->state.command_state = DallasCommonCommandStateIdle; + + if(!is_short) { + data->state.command_state = DallasCommonCommandStateIdle; + onewire_slave_set_overdrive(data->state.bus, is_short); + } + + return !is_short; } static bool dallas_ds1971_command_callback(uint8_t command, void* context) { diff --git a/lib/ibutton/protocols/dallas/protocol_ds1990.c b/lib/ibutton/protocols/dallas/protocol_ds1990.c index 0d9c937e..86d39f1b 100644 --- a/lib/ibutton/protocols/dallas/protocol_ds1990.c +++ b/lib/ibutton/protocols/dallas/protocol_ds1990.c @@ -67,6 +67,14 @@ bool dallas_ds1990_write_blank(OneWireHost* host, iButtonProtocolData* protocol_ tm2004_write(host, data->rom_data.bytes, sizeof(DallasCommonRomData)); } +static bool dallas_ds1990_reset_callback(bool is_short, void* context) { + DS1990ProtocolData* data = context; + if(!is_short) { + onewire_slave_set_overdrive(data->state.bus, is_short); + } + return !is_short; +} + static bool dallas_ds1990_command_callback(uint8_t command, void* context) { furi_assert(context); DS1990ProtocolData* data = context; @@ -92,7 +100,7 @@ void dallas_ds1990_emulate(OneWireSlave* bus, iButtonProtocolData* protocol_data DS1990ProtocolData* data = protocol_data; data->state.bus = bus; - onewire_slave_set_reset_callback(bus, NULL, NULL); + onewire_slave_set_reset_callback(bus, dallas_ds1990_reset_callback, protocol_data); onewire_slave_set_command_callback(bus, dallas_ds1990_command_callback, protocol_data); } diff --git a/lib/ibutton/protocols/dallas/protocol_ds1992.c b/lib/ibutton/protocols/dallas/protocol_ds1992.c index 17d63125..0b4d4b34 100644 --- a/lib/ibutton/protocols/dallas/protocol_ds1992.c +++ b/lib/ibutton/protocols/dallas/protocol_ds1992.c @@ -87,10 +87,16 @@ bool dallas_ds1992_write_copy(OneWireHost* host, iButtonProtocolData* protocol_d DS1992_SRAM_DATA_SIZE); } -static void dallas_ds1992_reset_callback(void* context) { +static bool dallas_ds1992_reset_callback(bool is_short, void* context) { furi_assert(context); DS1992ProtocolData* data = context; - data->state.command_state = DallasCommonCommandStateIdle; + + if(!is_short) { + data->state.command_state = DallasCommonCommandStateIdle; + onewire_slave_set_overdrive(data->state.bus, is_short); + } + + return !is_short; } static bool dallas_ds1992_command_callback(uint8_t command, void* context) { diff --git a/lib/ibutton/protocols/dallas/protocol_ds1996.c b/lib/ibutton/protocols/dallas/protocol_ds1996.c index 74a5792c..5358b63e 100644 --- a/lib/ibutton/protocols/dallas/protocol_ds1996.c +++ b/lib/ibutton/protocols/dallas/protocol_ds1996.c @@ -63,24 +63,54 @@ const iButtonProtocolDallasBase ibutton_protocol_ds1996 = { bool dallas_ds1996_read(OneWireHost* host, iButtonProtocolData* protocol_data) { DS1996ProtocolData* data = protocol_data; - return onewire_host_reset(host) && dallas_common_read_rom(host, &data->rom_data) && - dallas_common_read_mem(host, 0, data->sram_data, DS1996_SRAM_DATA_SIZE); + bool success = false; + + do { + if(!onewire_host_reset(host)) break; + if(!dallas_common_read_rom(host, &data->rom_data)) break; + if(!onewire_host_reset(host)) break; + + onewire_host_write(host, DALLAS_COMMON_CMD_OVERDRIVE_SKIP_ROM); + onewire_host_set_overdrive(host, true); + + if(!dallas_common_read_mem(host, 0, data->sram_data, DS1996_SRAM_DATA_SIZE)) break; + success = true; + } while(false); + + onewire_host_set_overdrive(host, false); + return success; } bool dallas_ds1996_write_copy(OneWireHost* host, iButtonProtocolData* protocol_data) { DS1996ProtocolData* data = protocol_data; - return dallas_common_write_mem( - host, - DS1996_COPY_SCRATCH_TIMEOUT_US, - DS1996_SRAM_PAGE_SIZE, - data->sram_data, - DS1996_SRAM_DATA_SIZE); + bool success = false; + + do { + if(!onewire_host_reset(host)) break; + + onewire_host_write(host, DALLAS_COMMON_CMD_OVERDRIVE_SKIP_ROM); + onewire_host_set_overdrive(host, true); + + if(!dallas_common_write_mem( + host, + DS1996_COPY_SCRATCH_TIMEOUT_US, + DS1996_SRAM_PAGE_SIZE, + data->sram_data, + DS1996_SRAM_DATA_SIZE)) + break; + success = true; + } while(false); + + onewire_host_set_overdrive(host, false); + return success; } -static void dallas_ds1996_reset_callback(void* context) { +static bool dallas_ds1996_reset_callback(bool is_short, void* context) { furi_assert(context); DS1996ProtocolData* data = context; data->state.command_state = DallasCommonCommandStateIdle; + onewire_slave_set_overdrive(data->state.bus, is_short); + return true; } static bool dallas_ds1996_command_callback(uint8_t command, void* context) { @@ -96,8 +126,7 @@ static bool dallas_ds1996_command_callback(uint8_t command, void* context) { } else if(data->state.command_state == DallasCommonCommandStateRomCmd) { data->state.command_state = DallasCommonCommandStateMemCmd; - dallas_common_emulate_read_mem(bus, data->sram_data, DS1996_SRAM_DATA_SIZE); - return false; + return dallas_common_emulate_read_mem(bus, data->sram_data, DS1996_SRAM_DATA_SIZE); } else { return false; @@ -120,8 +149,17 @@ static bool dallas_ds1996_command_callback(uint8_t command, void* context) { } case DALLAS_COMMON_CMD_OVERDRIVE_SKIP_ROM: + if(data->state.command_state == DallasCommonCommandStateIdle) { + data->state.command_state = DallasCommonCommandStateRomCmd; + onewire_slave_set_overdrive(bus, true); + return true; + } else { + return false; + } + + case DALLAS_COMMON_CMD_MATCH_ROM: case DALLAS_COMMON_CMD_OVERDRIVE_MATCH_ROM: - /* TODO: Overdrive mode support */ + /* TODO: Match ROM command support */ default: return false; } diff --git a/lib/ibutton/protocols/dallas/protocol_ds_generic.c b/lib/ibutton/protocols/dallas/protocol_ds_generic.c index 50fd0451..af355f46 100644 --- a/lib/ibutton/protocols/dallas/protocol_ds_generic.c +++ b/lib/ibutton/protocols/dallas/protocol_ds_generic.c @@ -61,6 +61,14 @@ bool ds_generic_write_blank(OneWireHost* host, iButtonProtocolData* protocol_dat return tm2004_write(host, data->rom_data.bytes, sizeof(DallasCommonRomData)); } +static bool ds_generic_reset_callback(bool is_short, void* context) { + DallasGenericProtocolData* data = context; + if(!is_short) { + onewire_slave_set_overdrive(data->state.bus, is_short); + } + return !is_short; +} + static bool ds_generic_command_callback(uint8_t command, void* context) { furi_assert(context); DallasGenericProtocolData* data = context; @@ -85,7 +93,7 @@ void ds_generic_emulate(OneWireSlave* bus, iButtonProtocolData* protocol_data) { DallasGenericProtocolData* data = protocol_data; data->state.bus = bus; - onewire_slave_set_reset_callback(bus, NULL, NULL); + onewire_slave_set_reset_callback(bus, ds_generic_reset_callback, NULL); onewire_slave_set_command_callback(bus, ds_generic_command_callback, protocol_data); } diff --git a/lib/one_wire/SConscript b/lib/one_wire/SConscript index 8d73c9db..2dde9153 100644 --- a/lib/one_wire/SConscript +++ b/lib/one_wire/SConscript @@ -8,7 +8,6 @@ env.Append( "#/lib/one_wire", ], SDK_HEADERS=[ - File("one_wire_host_timing.h"), File("one_wire_host.h"), File("one_wire_slave.h"), File("maxim_crc.h"), diff --git a/lib/one_wire/one_wire_host.c b/lib/one_wire/one_wire_host.c index 0a4a79f5..67881210 100644 --- a/lib/one_wire/one_wire_host.c +++ b/lib/one_wire/one_wire_host.c @@ -1,10 +1,54 @@ #include +/** + * Timings based on Application Note 126: + * https://www.analog.com/media/en/technical-documentation/tech-articles/1wire-communication-through-software--maxim-integrated.pdf + */ + #include "one_wire_host.h" -#include "one_wire_host_timing.h" + +typedef struct { + uint16_t a; + uint16_t b; + uint16_t c; + uint16_t d; + uint16_t e; + uint16_t f; + uint16_t g; + uint16_t h; + uint16_t i; + uint16_t j; +} OneWireHostTimings; + +static const OneWireHostTimings onewire_host_timings_normal = { + .a = 9, + .b = 64, + .c = 64, + .d = 14, + .e = 9, + .f = 55, + .g = 0, + .h = 480, + .i = 70, + .j = 410, +}; + +static const OneWireHostTimings onewire_host_timings_overdrive = { + .a = 1, + .b = 8, + .c = 8, + .d = 3, + .e = 1, + .f = 7, + .g = 3, + .h = 70, + .i = 9, + .j = 40, +}; struct OneWireHost { const GpioPin* gpio_pin; + const OneWireHostTimings* timings; unsigned char saved_rom[8]; /** < global search state */ uint8_t last_discrepancy; uint8_t last_family_discrepancy; @@ -15,6 +59,7 @@ OneWireHost* onewire_host_alloc(const GpioPin* gpio_pin) { OneWireHost* host = malloc(sizeof(OneWireHost)); host->gpio_pin = gpio_pin; onewire_host_reset_search(host); + onewire_host_set_overdrive(host, false); return host; } @@ -27,6 +72,8 @@ bool onewire_host_reset(OneWireHost* host) { uint8_t r; uint8_t retries = 125; + const OneWireHostTimings* timings = host->timings; + // wait until the gpio is high furi_hal_gpio_write(host->gpio_pin, true); do { @@ -35,19 +82,19 @@ bool onewire_host_reset(OneWireHost* host) { } while(!furi_hal_gpio_read(host->gpio_pin)); // pre delay - furi_delay_us(OWH_RESET_DELAY_PRE); + furi_delay_us(timings->g); // drive low furi_hal_gpio_write(host->gpio_pin, false); - furi_delay_us(OWH_RESET_DRIVE); + furi_delay_us(timings->h); // release furi_hal_gpio_write(host->gpio_pin, true); - furi_delay_us(OWH_RESET_RELEASE); + furi_delay_us(timings->i); // read and post delay r = !furi_hal_gpio_read(host->gpio_pin); - furi_delay_us(OWH_RESET_DELAY_POST); + furi_delay_us(timings->j); return r; } @@ -55,17 +102,19 @@ bool onewire_host_reset(OneWireHost* host) { bool onewire_host_read_bit(OneWireHost* host) { bool result; + const OneWireHostTimings* timings = host->timings; + // drive low furi_hal_gpio_write(host->gpio_pin, false); - furi_delay_us(OWH_READ_DRIVE); + furi_delay_us(timings->a); // release furi_hal_gpio_write(host->gpio_pin, true); - furi_delay_us(OWH_READ_RELEASE); + furi_delay_us(timings->e); // read and post delay result = furi_hal_gpio_read(host->gpio_pin); - furi_delay_us(OWH_READ_DELAY_POST); + furi_delay_us(timings->f); return result; } @@ -89,22 +138,24 @@ void onewire_host_read_bytes(OneWireHost* host, uint8_t* buffer, uint16_t count) } void onewire_host_write_bit(OneWireHost* host, bool value) { + const OneWireHostTimings* timings = host->timings; + if(value) { // drive low furi_hal_gpio_write(host->gpio_pin, false); - furi_delay_us(OWH_WRITE_1_DRIVE); + furi_delay_us(timings->a); // release furi_hal_gpio_write(host->gpio_pin, true); - furi_delay_us(OWH_WRITE_1_RELEASE); + furi_delay_us(timings->b); } else { // drive low furi_hal_gpio_write(host->gpio_pin, false); - furi_delay_us(OWH_WRITE_0_DRIVE); + furi_delay_us(timings->c); // release furi_hal_gpio_write(host->gpio_pin, true); - furi_delay_us(OWH_WRITE_0_RELEASE); + furi_delay_us(timings->d); } } @@ -122,10 +173,6 @@ void onewire_host_write_bytes(OneWireHost* host, const uint8_t* buffer, uint16_t } } -void onewire_host_skip(OneWireHost* host) { - onewire_host_write(host, 0xCC); -} - void onewire_host_start(OneWireHost* host) { furi_hal_gpio_write(host->gpio_pin, true); furi_hal_gpio_init(host->gpio_pin, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow); @@ -154,7 +201,7 @@ void onewire_host_target_search(OneWireHost* host, uint8_t family_code) { host->last_device_flag = false; } -uint8_t onewire_host_search(OneWireHost* host, uint8_t* new_addr, OneWireHostSearchMode mode) { +bool onewire_host_search(OneWireHost* host, uint8_t* new_addr, OneWireHostSearchMode mode) { uint8_t id_bit_number; uint8_t last_zero, rom_byte_number, search_result; uint8_t id_bit, cmp_id_bit; @@ -268,3 +315,7 @@ uint8_t onewire_host_search(OneWireHost* host, uint8_t* new_addr, OneWireHostSea return search_result; } + +void onewire_host_set_overdrive(OneWireHost* host, bool set) { + host->timings = set ? &onewire_host_timings_overdrive : &onewire_host_timings_normal; +} diff --git a/lib/one_wire/one_wire_host.h b/lib/one_wire/one_wire_host.h index dc469904..9f9bd4ff 100644 --- a/lib/one_wire/one_wire_host.h +++ b/lib/one_wire/one_wire_host.h @@ -15,114 +15,115 @@ extern "C" { typedef enum { OneWireHostSearchModeConditional = 0, /**< Search for alarmed device */ - OneWireHostSearchModeNormal = 1, /**< Search all devices */ + OneWireHostSearchModeNormal = 1, /**< Search for all devices */ } OneWireHostSearchMode; typedef struct OneWireHost OneWireHost; /** - * Allocate onewire host bus - * @param pin - * @return OneWireHost* + * Allocate OneWireHost instance + * @param [in] gpio_pin connection pin + * @return pointer to OneWireHost instance */ OneWireHost* onewire_host_alloc(const GpioPin* gpio_pin); /** - * Deallocate onewire host bus - * @param host + * Destroy OneWireHost instance, free resources + * @param [in] host pointer to OneWireHost instance */ void onewire_host_free(OneWireHost* host); /** - * Reset bus - * @param host - * @return bool + * Reset the 1-Wire bus + * @param [in] host pointer to OneWireHost instance + * @return true if presence was detected, false otherwise */ bool onewire_host_reset(OneWireHost* host); /** * Read one bit - * @param host - * @return bool + * @param [in] host pointer to OneWireHost instance + * @return received bit value */ bool onewire_host_read_bit(OneWireHost* host); /** * Read one byte - * @param host - * @return uint8_t + * @param [in] host pointer to OneWireHost instance + * @return received byte value */ uint8_t onewire_host_read(OneWireHost* host); /** - * Read many bytes - * @param host - * @param buffer - * @param count + * Read one or more bytes + * @param [in] host pointer to OneWireHost instance + * @param [out] buffer received data buffer + * @param [in] count number of bytes to read */ void onewire_host_read_bytes(OneWireHost* host, uint8_t* buffer, uint16_t count); /** * Write one bit - * @param host - * @param value + * @param [in] host pointer to OneWireHost instance + * @param value bit value to write */ void onewire_host_write_bit(OneWireHost* host, bool value); /** * Write one byte - * @param host - * @param value + * @param [in] host pointer to OneWireHost instance + * @param value byte value to write */ void onewire_host_write(OneWireHost* host, uint8_t value); /** - * Write many bytes - * @param host - * @param buffer - * @param count + * Write one or more bytes + * @param [in] host pointer to OneWireHost instance + * @param [in] buffer pointer to the data to write + * @param [in] count size of the data to write */ void onewire_host_write_bytes(OneWireHost* host, const uint8_t* buffer, uint16_t count); -/** - * Skip ROM command - * @param host - */ -void onewire_host_skip(OneWireHost* host); - /** * Start working with the bus - * @param host + * @param [in] host pointer to OneWireHost instance */ void onewire_host_start(OneWireHost* host); /** * Stop working with the bus - * @param host + * @param [in] host pointer to OneWireHost instance */ void onewire_host_stop(OneWireHost* host); /** - * - * @param host + * Reset previous search results + * @param [in] host pointer to OneWireHost instance */ void onewire_host_reset_search(OneWireHost* host); /** - * - * @param host - * @param family_code + * Set the family code to search for + * @param [in] host pointer to OneWireHost instance + * @param [in] family_code device family code */ void onewire_host_target_search(OneWireHost* host, uint8_t family_code); /** - * - * @param host - * @param newAddr - * @param mode - * @return uint8_t + * Search for devices on the 1-Wire bus + * @param [in] host pointer to OneWireHost instance + * @param [out] new_addr pointer to the buffer to contain the unique ROM of the found device + * @param [in] mode search mode + * @return true on success, false otherwise */ -uint8_t onewire_host_search(OneWireHost* host, uint8_t* new_addr, OneWireHostSearchMode mode); +bool onewire_host_search(OneWireHost* host, uint8_t* new_addr, OneWireHostSearchMode mode); + +/** + * Enable overdrive mode + * @param [in] host pointer to OneWireHost instance + * @param [in] set true to turn overdrive on, false to turn it off + */ +void onewire_host_set_overdrive(OneWireHost* host, bool set); #ifdef __cplusplus } diff --git a/lib/one_wire/one_wire_host_timing.h b/lib/one_wire/one_wire_host_timing.h deleted file mode 100644 index f95dd356..00000000 --- a/lib/one_wire/one_wire_host_timing.h +++ /dev/null @@ -1,30 +0,0 @@ -/** - * @file one_wire_host_timing.h - * - * 1-Wire library, timing list - */ - -#pragma once - -#define OWH_TIMING_A 9 -#define OWH_TIMING_B 64 -#define OWH_TIMING_C 64 -#define OWH_TIMING_D 14 -#define OWH_TIMING_E 9 -#define OWH_TIMING_F 55 -#define OWH_TIMING_G 0 -#define OWH_TIMING_H 480 -#define OWH_TIMING_I 70 -#define OWH_TIMING_J 410 - -#define OWH_WRITE_1_DRIVE OWH_TIMING_A -#define OWH_WRITE_1_RELEASE OWH_TIMING_B -#define OWH_WRITE_0_DRIVE OWH_TIMING_C -#define OWH_WRITE_0_RELEASE OWH_TIMING_D -#define OWH_READ_DRIVE 3 -#define OWH_READ_RELEASE OWH_TIMING_E -#define OWH_READ_DELAY_POST OWH_TIMING_F -#define OWH_RESET_DELAY_PRE OWH_TIMING_G -#define OWH_RESET_DRIVE OWH_TIMING_H -#define OWH_RESET_RELEASE OWH_TIMING_I -#define OWH_RESET_DELAY_POST OWH_TIMING_J diff --git a/lib/one_wire/one_wire_slave.c b/lib/one_wire/one_wire_slave.c index d1676cf3..733b36e3 100644 --- a/lib/one_wire/one_wire_slave.c +++ b/lib/one_wire/one_wire_slave.c @@ -3,20 +3,7 @@ #include #include -#define ONEWIRE_TRSTL_MIN 270 /* Minimum Reset Low time */ -#define ONEWIRE_TRSTL_MAX 1200 /* Maximum Reset Low time */ - -#define ONEWIRE_TPDH_TYP 20 /* Typical Presence Detect High time */ -#define ONEWIRE_TPDL_MIN 100 /* Minimum Presence Detect Low time */ -#define ONEWIRE_TPDL_MAX 480 /* Maximum Presence Detect Low time */ - -#define ONEWIRE_TSLOT_MIN 60 /* Minimum Read/Write Slot time */ -#define ONEWIRE_TSLOT_MAX 135 /* Maximum Read/Write Slot time */ - -#define ONEWIRE_TW1L_MAX 20 /* Maximum Master Write 1 time */ -#define ONEWIRE_TRL_TMSR_MAX 30 /* Maximum Master Read Low + Read Sample time */ - -#define ONEWIRE_TH_TIMEOUT 15000 /* Maximum time before general timeout */ +#define TH_TIMEOUT_MAX 15000 /* Maximum time before general timeout */ typedef enum { OneWireSlaveErrorNone = 0, @@ -26,10 +13,29 @@ typedef enum { OneWireSlaveErrorTimeout, } OneWireSlaveError; +typedef struct { + uint16_t trstl_min; /* Minimum Reset Low time */ + uint16_t trstl_max; /* Maximum Reset Low time */ + + uint16_t tpdh_typ; /* Typical Presence Detect High time */ + uint16_t tpdl_min; /* Minimum Presence Detect Low time */ + uint16_t tpdl_max; /* Maximum Presence Detect Low time */ + + uint16_t tslot_min; /* Minimum Read/Write Slot time */ + uint16_t tslot_max; /* Maximum Read/Write Slot time */ + + uint16_t tw1l_max; /* Maximum Master Write 1 time */ + uint16_t trl_tmsr_max; /* Maximum Master Read Low + Read Sample time */ +} OneWireSlaveTimings; + struct OneWireSlave { const GpioPin* gpio_pin; + const OneWireSlaveTimings* timings; OneWireSlaveError error; + bool is_first_reset; + bool is_short_reset; + OneWireSlaveResetCallback reset_callback; OneWireSlaveCommandCallback command_callback; OneWireSlaveResultCallback result_callback; @@ -39,42 +45,72 @@ struct OneWireSlave { void* command_callback_context; }; +static const OneWireSlaveTimings onewire_slave_timings_normal = { + .trstl_min = 270, + .trstl_max = 1200, + + .tpdh_typ = 20, + .tpdl_min = 100, + .tpdl_max = 480, + + .tslot_min = 60, + .tslot_max = 135, + + .tw1l_max = 20, + .trl_tmsr_max = 30, +}; + +static const OneWireSlaveTimings onewire_slave_timings_overdrive = { + .trstl_min = 48, + .trstl_max = 80, + + .tpdh_typ = 0, + .tpdl_min = 8, + .tpdl_max = 24, + + .tslot_min = 6, + .tslot_max = 16, + + .tw1l_max = 2, + .trl_tmsr_max = 3, +}; + /*********************** PRIVATE ***********************/ -static uint32_t - onewire_slave_wait_while_gpio_is(OneWireSlave* bus, uint32_t time, const bool pin_value) { - uint32_t start = DWT->CYCCNT; - uint32_t time_ticks = time * furi_hal_cortex_instructions_per_microsecond(); - uint32_t time_captured; +static bool + onewire_slave_wait_while_gpio_is(OneWireSlave* bus, uint32_t time_us, const bool pin_value) { + const uint32_t time_start = DWT->CYCCNT; + const uint32_t time_ticks = time_us * furi_hal_cortex_instructions_per_microsecond(); + + uint32_t time_elapsed; do { //-V1044 - time_captured = DWT->CYCCNT; + time_elapsed = DWT->CYCCNT - time_start; if(furi_hal_gpio_read(bus->gpio_pin) != pin_value) { - uint32_t remaining_time = time_ticks - (time_captured - start); - remaining_time /= furi_hal_cortex_instructions_per_microsecond(); - return remaining_time; + return time_ticks >= time_elapsed; } - } while((time_captured - start) < time_ticks); + } while(time_elapsed < time_ticks); - return 0; + return false; } -static bool onewire_slave_show_presence(OneWireSlave* bus) { +static inline bool onewire_slave_show_presence(OneWireSlave* bus) { + const OneWireSlaveTimings* timings = bus->timings; // wait until the bus is high (might return immediately) - onewire_slave_wait_while_gpio_is(bus, ONEWIRE_TRSTL_MAX, false); + onewire_slave_wait_while_gpio_is(bus, timings->trstl_max, false); // wait while master delay presence check - furi_delay_us(ONEWIRE_TPDH_TYP); + furi_delay_us(timings->tpdh_typ); // show presence furi_hal_gpio_write(bus->gpio_pin, false); - furi_delay_us(ONEWIRE_TPDL_MIN); + furi_delay_us(timings->tpdl_min); furi_hal_gpio_write(bus->gpio_pin, true); // somebody also can show presence - const uint32_t wait_low_time = ONEWIRE_TPDL_MAX - ONEWIRE_TPDL_MIN; + const uint32_t wait_low_time = timings->tpdl_max - timings->tpdl_min; // so we will wait - if(onewire_slave_wait_while_gpio_is(bus, wait_low_time, false) == 0) { + if(!onewire_slave_wait_while_gpio_is(bus, wait_low_time, false)) { bus->error = OneWireSlaveErrorPresenceConflict; return false; } @@ -85,27 +121,36 @@ static bool onewire_slave_show_presence(OneWireSlave* bus) { static inline bool onewire_slave_receive_and_process_command(OneWireSlave* bus) { /* Reset condition detected, send a presence pulse and reset protocol state */ if(bus->error == OneWireSlaveErrorResetInProgress) { - if(onewire_slave_show_presence(bus)) { - bus->error = OneWireSlaveErrorNone; + if(!bus->is_first_reset) { + /* Guess the reset type */ + bus->is_short_reset = onewire_slave_wait_while_gpio_is( + bus, + onewire_slave_timings_overdrive.trstl_max - + onewire_slave_timings_overdrive.tslot_max, + false); + } else { + bus->is_first_reset = false; + } - if(bus->reset_callback != NULL) { - bus->reset_callback(bus->reset_callback_context); + furi_assert(bus->reset_callback); + + if(bus->reset_callback(bus->is_short_reset, bus->reset_callback_context)) { + if(onewire_slave_show_presence(bus)) { + bus->error = OneWireSlaveErrorNone; + return true; } - - return true; } } else if(bus->error == OneWireSlaveErrorNone) { uint8_t command; - if(!onewire_slave_receive(bus, &command, 1)) { - /* Upon failure, request an additional iteration to - choose the appropriate action by checking bus->error */ - return true; - } else if(bus->command_callback) { - return bus->command_callback(command, bus->command_callback_context); - } else { - bus->error = OneWireSlaveErrorInvalidCommand; + if(onewire_slave_receive(bus, &command, sizeof(command))) { + furi_assert(bus->command_callback); + if(bus->command_callback(command, bus->command_callback_context)) { + return true; + } } + + return (bus->error == OneWireSlaveErrorResetInProgress); } return false; @@ -115,9 +160,6 @@ static inline bool onewire_slave_bus_start(OneWireSlave* bus) { FURI_CRITICAL_ENTER(); furi_hal_gpio_init(bus->gpio_pin, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow); - /* Start in Reset state in order to send a presence pulse immediately */ - bus->error = OneWireSlaveErrorResetInProgress; - while(onewire_slave_receive_and_process_command(bus)) ; @@ -139,7 +181,15 @@ static void onewire_slave_exti_callback(void* context) { const uint32_t pulse_length = (DWT->CYCCNT - pulse_start) / furi_hal_cortex_instructions_per_microsecond(); - if((pulse_length >= ONEWIRE_TRSTL_MIN) && pulse_length <= (ONEWIRE_TRSTL_MAX)) { + if((pulse_length >= onewire_slave_timings_overdrive.trstl_min) && + (pulse_length <= onewire_slave_timings_normal.trstl_max)) { + /* Start in reset state in order to send a presence pulse immediately */ + bus->error = OneWireSlaveErrorResetInProgress; + /* Determine reset type (chooses speed mode if supported by the emulated device) */ + bus->is_short_reset = pulse_length <= onewire_slave_timings_overdrive.trstl_max; + /* Initial reset allows going directly into overdrive mode */ + bus->is_first_reset = true; + const bool result = onewire_slave_bus_start(bus); if(result && bus->result_callback != NULL) { @@ -158,6 +208,7 @@ OneWireSlave* onewire_slave_alloc(const GpioPin* gpio_pin) { OneWireSlave* bus = malloc(sizeof(OneWireSlave)); bus->gpio_pin = gpio_pin; + bus->timings = &onewire_slave_timings_normal; bus->error = OneWireSlaveErrorNone; return bus; @@ -205,52 +256,45 @@ void onewire_slave_set_result_callback( } bool onewire_slave_receive_bit(OneWireSlave* bus) { + const OneWireSlaveTimings* timings = bus->timings; // wait while bus is low - uint32_t time = ONEWIRE_TSLOT_MAX; - time = onewire_slave_wait_while_gpio_is(bus, time, false); - if(time == 0) { + if(!onewire_slave_wait_while_gpio_is(bus, timings->tslot_max, false)) { bus->error = OneWireSlaveErrorResetInProgress; return false; } // wait while bus is high - time = ONEWIRE_TH_TIMEOUT; - time = onewire_slave_wait_while_gpio_is(bus, time, true); - if(time == 0) { + if(!onewire_slave_wait_while_gpio_is(bus, TH_TIMEOUT_MAX, true)) { bus->error = OneWireSlaveErrorTimeout; return false; } // wait a time of zero - time = ONEWIRE_TW1L_MAX; - time = onewire_slave_wait_while_gpio_is(bus, time, false); - - return (time > 0); + return onewire_slave_wait_while_gpio_is(bus, timings->tw1l_max, false); } bool onewire_slave_send_bit(OneWireSlave* bus, bool value) { + const OneWireSlaveTimings* timings = bus->timings; // wait while bus is low - uint32_t time = ONEWIRE_TSLOT_MAX; - time = onewire_slave_wait_while_gpio_is(bus, time, false); - if(time == 0) { + if(!onewire_slave_wait_while_gpio_is(bus, timings->tslot_max, false)) { bus->error = OneWireSlaveErrorResetInProgress; return false; } // wait while bus is high - time = ONEWIRE_TH_TIMEOUT; - time = onewire_slave_wait_while_gpio_is(bus, time, true); - if(time == 0) { + if(!onewire_slave_wait_while_gpio_is(bus, TH_TIMEOUT_MAX, true)) { bus->error = OneWireSlaveErrorTimeout; return false; } // choose write time + uint32_t time; + if(!value) { furi_hal_gpio_write(bus->gpio_pin, false); - time = ONEWIRE_TRL_TMSR_MAX; + time = timings->trl_tmsr_max; } else { - time = ONEWIRE_TSLOT_MIN; + time = timings->tslot_min; } // hold line for ZERO or ONE time @@ -301,3 +345,13 @@ bool onewire_slave_receive(OneWireSlave* bus, uint8_t* data, size_t data_size) { } return true; } + +void onewire_slave_set_overdrive(OneWireSlave* bus, bool set) { + const OneWireSlaveTimings* new_timings = set ? &onewire_slave_timings_overdrive : + &onewire_slave_timings_normal; + if(bus->timings != new_timings) { + /* Prevent erroneous reset by waiting for the previous time slot to finish */ + onewire_slave_wait_while_gpio_is(bus, bus->timings->tslot_max, false); + bus->timings = new_timings; + } +} diff --git a/lib/one_wire/one_wire_slave.h b/lib/one_wire/one_wire_slave.h index 914cd933..21114b91 100644 --- a/lib/one_wire/one_wire_slave.h +++ b/lib/one_wire/one_wire_slave.h @@ -18,68 +18,85 @@ extern "C" { typedef struct OneWireDevice OneWireDevice; typedef struct OneWireSlave OneWireSlave; -typedef void (*OneWireSlaveResetCallback)(void* context); -typedef void (*OneWireSlaveResultCallback)(void* context); +typedef bool (*OneWireSlaveResetCallback)(bool is_short, void* context); typedef bool (*OneWireSlaveCommandCallback)(uint8_t command, void* context); +typedef void (*OneWireSlaveResultCallback)(void* context); /** - * Allocate onewire slave - * @param gpio_pin - * @return OneWireSlave* + * Allocate OneWireSlave instance + * @param [in] gpio_pin connection pin + * @return pointer to OneWireSlave instance */ OneWireSlave* onewire_slave_alloc(const GpioPin* gpio_pin); /** - * Free onewire slave - * @param bus + * Destroy OneWireSlave instance, free resources + * @param [in] bus pointer to OneWireSlave instance */ void onewire_slave_free(OneWireSlave* bus); /** * Start working with the bus - * @param bus + * @param [in] bus pointer to OneWireSlave instance */ void onewire_slave_start(OneWireSlave* bus); /** * Stop working with the bus - * @param bus + * @param [in] bus pointer to OneWireSlave instance */ void onewire_slave_stop(OneWireSlave* bus); /** - * TODO: description comment + * Receive one bit + * @param [in] bus pointer to OneWireSlave instance + * @return received bit value */ bool onewire_slave_receive_bit(OneWireSlave* bus); /** - * TODO: description comment + * Send one bit + * @param [in] bus pointer to OneWireSlave instance + * @param [in] value bit value to send + * @return true on success, false on failure */ bool onewire_slave_send_bit(OneWireSlave* bus, bool value); /** - * Send data - * @param bus - * @param data - * @param data_size - * @return bool + * Send one or more bytes of data + * @param [in] bus pointer to OneWireSlave instance + * @param [in] data pointer to the data to send + * @param [in] data_size size of the data to send + * @return true on success, false on failure */ bool onewire_slave_send(OneWireSlave* bus, const uint8_t* data, size_t data_size); /** - * Receive data - * @param bus - * @param data - * @param data_size - * @return bool + * Receive one or more bytes of data + * @param [in] bus pointer to OneWireSlave instance + * @param [out] data pointer to the receive buffer + * @param [in] data_size number of bytes to receive + * @return true on success, false on failure */ bool onewire_slave_receive(OneWireSlave* bus, uint8_t* data, size_t data_size); /** - * Set a callback to be called on each reset - * @param bus - * @param callback - * @param context + * Enable overdrive mode + * @param [in] bus pointer to OneWireSlave instance + * @param [in] set true to turn overdrive on, false to turn it off + */ +void onewire_slave_set_overdrive(OneWireSlave* bus, bool set); + +/** + * Set a callback function to be called on each reset. + * The return value of the callback determines whether the emulated device + * supports the short reset (passed as the is_short parameter). + * In most applications, it should also call onewire_slave_set_overdrive() + * to set the appropriate speed mode. + * + * @param [in] bus pointer to OneWireSlave instance + * @param [in] callback pointer to a callback function + * @param [in] context additional parameter to be passed to the callback */ void onewire_slave_set_reset_callback( OneWireSlave* bus, @@ -87,10 +104,13 @@ void onewire_slave_set_reset_callback( void* context); /** - * Set a callback to be called on each command - * @param bus - * @param callback - * @param context + * Set a callback function to be called on each command. + * The return value of the callback determines whether further operation + * is possible. As a rule of thumb, return true unless a critical error happened. + * + * @param [in] bus pointer to OneWireSlave instance + * @param [in] callback pointer to a callback function + * @param [in] context additional parameter to be passed to the callback */ void onewire_slave_set_command_callback( OneWireSlave* bus, @@ -99,9 +119,9 @@ void onewire_slave_set_command_callback( /** * Set a callback to report emulation success - * @param bus - * @param result_cb - * @param context + * @param [in] bus pointer to OneWireSlave instance + * @param [in] result_cb pointer to a callback function + * @param [in] context additional parameter to be passed to the callback */ void onewire_slave_set_result_callback( OneWireSlave* bus, From 7bf0a4786c6987b756683f6ee0af866833a8b4cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Thu, 23 Mar 2023 02:00:48 +0900 Subject: [PATCH 31/32] [FL-3152] Screen streaming improvements (#2498) * Rpc: reserve some bandwidth when screen streaming * Move furi_hal_compress to toolbox/comporess * Lib: heatshrink as external submodule, compile warnings fixes, better buffer management * Lib: cleanup compressor definitions * Rpc: add canvas orientation support * Format Sources --- .gitmodules | 3 + applications/services/gui/canvas.c | 9 +- applications/services/gui/canvas_i.h | 2 + applications/services/gui/gui.c | 1 + applications/services/gui/gui.h | 6 +- applications/services/rpc/rpc_gui.c | 26 +- assets/protobuf | 2 +- firmware/targets/f18/api_symbols.csv | 13 +- firmware/targets/f18/furi_hal/furi_hal.c | 34 +- .../targets/f18/furi_hal/furi_hal_resources.c | 4 + firmware/targets/f7/api_symbols.csv | 7 - firmware/targets/f7/furi_hal/furi_hal.c | 26 - .../targets/f7/furi_hal/furi_hal_ibutton.c | 3 + .../targets/f7/furi_hal/furi_hal_resources.c | 4 + firmware/targets/f7/src/dfu.c | 7 +- firmware/targets/f7/src/recovery.c | 6 +- firmware/targets/furi_hal_include/furi_hal.h | 1 - .../furi_hal_include/furi_hal_compress.h | 87 --- lib/err.h | 4 + lib/heatshrink | 1 + lib/heatshrink/heatshrink_common.h | 20 - lib/heatshrink/heatshrink_config.h | 28 - lib/heatshrink/heatshrink_decoder.c | 364 ----------- lib/heatshrink/heatshrink_decoder.h | 100 --- lib/heatshrink/heatshrink_encoder.c | 602 ------------------ lib/heatshrink/heatshrink_encoder.h | 109 ---- lib/misc.scons | 7 +- .../toolbox/compress.c | 127 ++-- lib/toolbox/compress.h | 96 +++ 29 files changed, 242 insertions(+), 1457 deletions(-) delete mode 100644 firmware/targets/furi_hal_include/furi_hal_compress.h create mode 100644 lib/err.h create mode 160000 lib/heatshrink delete mode 100644 lib/heatshrink/heatshrink_common.h delete mode 100644 lib/heatshrink/heatshrink_config.h delete mode 100644 lib/heatshrink/heatshrink_decoder.c delete mode 100644 lib/heatshrink/heatshrink_decoder.h delete mode 100644 lib/heatshrink/heatshrink_encoder.c delete mode 100644 lib/heatshrink/heatshrink_encoder.h rename firmware/targets/f7/furi_hal/furi_hal_compress.c => lib/toolbox/compress.c (67%) create mode 100644 lib/toolbox/compress.h diff --git a/.gitmodules b/.gitmodules index 56368cd5..3a15177b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -31,3 +31,6 @@ [submodule "applications/external/dap_link/lib/free-dap"] path = applications/external/dap_link/lib/free-dap url = https://github.com/ataradov/free-dap.git +[submodule "lib/heatshrink"] + path = lib/heatshrink + url = https://github.com/flipperdevices/heatshrink.git diff --git a/applications/services/gui/canvas.c b/applications/services/gui/canvas.c index 9c29a39f..40797c08 100644 --- a/applications/services/gui/canvas.c +++ b/applications/services/gui/canvas.c @@ -17,6 +17,7 @@ const CanvasFontParameters canvas_font_params[FontTotalNumber] = { Canvas* canvas_init() { Canvas* canvas = malloc(sizeof(Canvas)); + canvas->compress_icon = compress_icon_alloc(); // Setup u8g2 u8g2_Setup_st756x_flipper(&canvas->fb, U8G2_R0, u8x8_hw_spi_stm32, u8g2_gpio_and_delay_stm32); @@ -35,6 +36,7 @@ Canvas* canvas_init() { void canvas_free(Canvas* canvas) { furi_assert(canvas); + compress_icon_free(canvas->compress_icon); free(canvas); } @@ -218,7 +220,7 @@ void canvas_draw_bitmap( x += canvas->offset_x; y += canvas->offset_y; uint8_t* bitmap_data = NULL; - furi_hal_compress_icon_decode(compressed_bitmap_data, &bitmap_data); + compress_icon_decode(canvas->compress_icon, compressed_bitmap_data, &bitmap_data); u8g2_DrawXBM(&canvas->fb, x, y, width, height, bitmap_data); } @@ -233,7 +235,8 @@ void canvas_draw_icon_animation( x += canvas->offset_x; y += canvas->offset_y; uint8_t* icon_data = NULL; - furi_hal_compress_icon_decode(icon_animation_get_data(icon_animation), &icon_data); + compress_icon_decode( + canvas->compress_icon, icon_animation_get_data(icon_animation), &icon_data); u8g2_DrawXBM( &canvas->fb, x, @@ -250,7 +253,7 @@ void canvas_draw_icon(Canvas* canvas, uint8_t x, uint8_t y, const Icon* icon) { x += canvas->offset_x; y += canvas->offset_y; uint8_t* icon_data = NULL; - furi_hal_compress_icon_decode(icon_get_data(icon), &icon_data); + compress_icon_decode(canvas->compress_icon, icon_get_data(icon), &icon_data); u8g2_DrawXBM(&canvas->fb, x, y, icon_get_width(icon), icon_get_height(icon), icon_data); } diff --git a/applications/services/gui/canvas_i.h b/applications/services/gui/canvas_i.h index 12cabfa7..39e7021b 100644 --- a/applications/services/gui/canvas_i.h +++ b/applications/services/gui/canvas_i.h @@ -7,6 +7,7 @@ #include "canvas.h" #include +#include /** Canvas structure */ @@ -17,6 +18,7 @@ struct Canvas { uint8_t offset_y; uint8_t width; uint8_t height; + CompressIcon* compress_icon; }; /** Allocate memory and initialize canvas diff --git a/applications/services/gui/gui.c b/applications/services/gui/gui.c index 24b48a83..39201162 100644 --- a/applications/services/gui/gui.c +++ b/applications/services/gui/gui.c @@ -250,6 +250,7 @@ static void gui_redraw(Gui* gui) { p->callback( canvas_get_buffer(gui->canvas), canvas_get_buffer_size(gui->canvas), + canvas_get_orientation(gui->canvas), p->context); } } while(false); diff --git a/applications/services/gui/gui.h b/applications/services/gui/gui.h index d7d73f27..1b5987ed 100644 --- a/applications/services/gui/gui.h +++ b/applications/services/gui/gui.h @@ -27,7 +27,11 @@ typedef enum { } GuiLayer; /** Gui Canvas Commit Callback */ -typedef void (*GuiCanvasCommitCallback)(uint8_t* data, size_t size, void* context); +typedef void (*GuiCanvasCommitCallback)( + uint8_t* data, + size_t size, + CanvasOrientation orientation, + void* context); #define RECORD_GUI "gui" diff --git a/applications/services/rpc/rpc_gui.c b/applications/services/rpc/rpc_gui.c index c2af425e..0c70702c 100644 --- a/applications/services/rpc/rpc_gui.c +++ b/applications/services/rpc/rpc_gui.c @@ -33,8 +33,18 @@ typedef struct { uint32_t input_counter; } RpcGuiSystem; -static void - rpc_system_gui_screen_stream_frame_callback(uint8_t* data, size_t size, void* context) { +static const PB_Gui_ScreenOrientation rpc_system_gui_screen_orientation_map[] = { + [CanvasOrientationHorizontal] = PB_Gui_ScreenOrientation_HORIZONTAL, + [CanvasOrientationHorizontalFlip] = PB_Gui_ScreenOrientation_HORIZONTAL_FLIP, + [CanvasOrientationVertical] = PB_Gui_ScreenOrientation_VERTICAL, + [CanvasOrientationVerticalFlip] = PB_Gui_ScreenOrientation_VERTICAL_FLIP, +}; + +static void rpc_system_gui_screen_stream_frame_callback( + uint8_t* data, + size_t size, + CanvasOrientation orientation, + void* context) { furi_assert(data); furi_assert(context); @@ -44,6 +54,8 @@ static void furi_assert(size == rpc_gui->transmit_frame->content.gui_screen_frame.data->size); memcpy(buffer, data, size); + rpc_gui->transmit_frame->content.gui_screen_frame.orientation = + rpc_system_gui_screen_orientation_map[orientation]; furi_thread_flags_set(furi_thread_get_id(rpc_gui->transmit_thread), RpcGuiWorkerFlagTransmit); } @@ -53,12 +65,22 @@ static int32_t rpc_system_gui_screen_stream_frame_transmit_thread(void* context) RpcGuiSystem* rpc_gui = (RpcGuiSystem*)context; + uint32_t transmit_time = 0; while(true) { uint32_t flags = furi_thread_flags_wait(RpcGuiWorkerFlagAny, FuriFlagWaitAny, FuriWaitForever); + if(flags & RpcGuiWorkerFlagTransmit) { + transmit_time = furi_get_tick(); rpc_send(rpc_gui->session, rpc_gui->transmit_frame); + transmit_time = furi_get_tick() - transmit_time; + + // Guaranteed bandwidth reserve + uint32_t extra_delay = transmit_time / 20; + if(extra_delay > 500) extra_delay = 500; + if(extra_delay) furi_delay_tick(extra_delay); } + if(flags & RpcGuiWorkerFlagExit) { break; } diff --git a/assets/protobuf b/assets/protobuf index 64606602..1f6b4a08 160000 --- a/assets/protobuf +++ b/assets/protobuf @@ -1 +1 @@ -Subproject commit 6460660237005d02d5c223835659b40e373bade9 +Subproject commit 1f6b4a08c5d05c2b17926a3ba79f60109638932f diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index e6fae33e..40e23a74 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -57,7 +57,6 @@ Header,+,firmware/targets/furi_hal_include/furi_hal.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_bt.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_bt_hid.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_bt_serial.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_compress.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_cortex.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_crypto.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_debug.h,, @@ -876,12 +875,12 @@ Function,-,furi_hal_clock_resume_tick,void, Function,-,furi_hal_clock_suspend_tick,void, Function,-,furi_hal_clock_switch_to_hsi,void, Function,-,furi_hal_clock_switch_to_pll,void, -Function,-,furi_hal_compress_alloc,FuriHalCompress*,uint16_t -Function,-,furi_hal_compress_decode,_Bool,"FuriHalCompress*, uint8_t*, size_t, uint8_t*, size_t, size_t*" -Function,-,furi_hal_compress_encode,_Bool,"FuriHalCompress*, uint8_t*, size_t, uint8_t*, size_t, size_t*" -Function,-,furi_hal_compress_free,void,FuriHalCompress* -Function,-,furi_hal_compress_icon_decode,void,"const uint8_t*, uint8_t**" -Function,-,furi_hal_compress_icon_init,void, +Function,-,compress_alloc,Compress*,uint16_t +Function,-,compress_decode,_Bool,"Compress*, uint8_t*, size_t, uint8_t*, size_t, size_t*" +Function,-,compress_encode,_Bool,"Compress*, uint8_t*, size_t, uint8_t*, size_t, size_t*" +Function,-,compress_free,void,Compress* +Function,-,compress_icon_decode,void,"const uint8_t*, uint8_t**" +Function,-,compress_icon_init,void, Function,+,furi_hal_console_disable,void, Function,+,furi_hal_console_enable,void, Function,+,furi_hal_console_init,void, diff --git a/firmware/targets/f18/furi_hal/furi_hal.c b/firmware/targets/f18/furi_hal/furi_hal.c index 2c255fa0..4064dd64 100644 --- a/firmware/targets/f18/furi_hal/furi_hal.c +++ b/firmware/targets/f18/furi_hal/furi_hal.c @@ -1,5 +1,6 @@ #include #include +#include #include @@ -7,29 +8,20 @@ void furi_hal_init_early() { furi_hal_cortex_init_early(); - furi_hal_clock_init_early(); - furi_hal_resources_init_early(); - furi_hal_os_init(); - furi_hal_spi_config_init_early(); - furi_hal_i2c_init_early(); furi_hal_light_init(); - furi_hal_rtc_init_early(); } void furi_hal_deinit_early() { furi_hal_rtc_deinit_early(); - furi_hal_i2c_deinit_early(); furi_hal_spi_config_deinit_early(); - furi_hal_resources_deinit_early(); - furi_hal_clock_deinit_early(); } @@ -38,40 +30,24 @@ void furi_hal_init() { furi_hal_clock_init(); furi_hal_console_init(); furi_hal_rtc_init(); - furi_hal_interrupt_init(); - furi_hal_flash_init(); - furi_hal_resources_init(); - FURI_LOG_I(TAG, "GPIO OK"); - furi_hal_version_init(); - furi_hal_spi_config_init(); furi_hal_spi_dma_init(); - furi_hal_speaker_init(); - FURI_LOG_I(TAG, "Speaker OK"); - furi_hal_crypto_init(); - - // USB -#ifndef FURI_RAM_EXEC - furi_hal_usb_init(); - FURI_LOG_I(TAG, "USB OK"); -#endif - furi_hal_i2c_init(); - - // High Level furi_hal_power_init(); furi_hal_light_init(); + furi_hal_bt_init(); + furi_hal_memory_init(); + #ifndef FURI_RAM_EXEC + furi_hal_usb_init(); furi_hal_vibro_init(); #endif - furi_hal_bt_init(); - furi_hal_compress_icon_init(); } void furi_hal_switch(void* address) { diff --git a/firmware/targets/f18/furi_hal/furi_hal_resources.c b/firmware/targets/f18/furi_hal/furi_hal_resources.c index 41cc80bf..abb258cb 100644 --- a/firmware/targets/f18/furi_hal/furi_hal_resources.c +++ b/firmware/targets/f18/furi_hal/furi_hal_resources.c @@ -4,6 +4,8 @@ #include #include +#define TAG "FuriHalResources" + const GpioPin vibro_gpio = {.port = GPIOA, .pin = LL_GPIO_PIN_8}; const GpioPin ibutton_gpio = {.port = GPIOB, .pin = LL_GPIO_PIN_14}; @@ -198,6 +200,8 @@ void furi_hal_resources_init() { NVIC_SetPriority(EXTI15_10_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 5, 0)); NVIC_EnableIRQ(EXTI15_10_IRQn); + + FURI_LOG_I(TAG, "Init OK"); } int32_t furi_hal_resources_get_ext_pin_number(const GpioPin* gpio) { diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 7ac9a245..8b1d29b1 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -62,7 +62,6 @@ Header,+,firmware/targets/furi_hal_include/furi_hal.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_bt.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_bt_hid.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_bt_serial.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_compress.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_cortex.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_crypto.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_debug.h,, @@ -1057,12 +1056,6 @@ Function,-,furi_hal_clock_resume_tick,void, Function,-,furi_hal_clock_suspend_tick,void, Function,-,furi_hal_clock_switch_to_hsi,void, Function,-,furi_hal_clock_switch_to_pll,void, -Function,-,furi_hal_compress_alloc,FuriHalCompress*,uint16_t -Function,-,furi_hal_compress_decode,_Bool,"FuriHalCompress*, uint8_t*, size_t, uint8_t*, size_t, size_t*" -Function,-,furi_hal_compress_encode,_Bool,"FuriHalCompress*, uint8_t*, size_t, uint8_t*, size_t, size_t*" -Function,-,furi_hal_compress_free,void,FuriHalCompress* -Function,-,furi_hal_compress_icon_decode,void,"const uint8_t*, uint8_t**" -Function,-,furi_hal_compress_icon_init,void, Function,+,furi_hal_console_disable,void, Function,+,furi_hal_console_enable,void, Function,+,furi_hal_console_init,void, diff --git a/firmware/targets/f7/furi_hal/furi_hal.c b/firmware/targets/f7/furi_hal/furi_hal.c index 5840a697..1b710bb9 100644 --- a/firmware/targets/f7/furi_hal/furi_hal.c +++ b/firmware/targets/f7/furi_hal/furi_hal.c @@ -8,29 +8,20 @@ void furi_hal_init_early() { furi_hal_cortex_init_early(); - furi_hal_clock_init_early(); - furi_hal_resources_init_early(); - furi_hal_os_init(); - furi_hal_spi_config_init_early(); - furi_hal_i2c_init_early(); furi_hal_light_init(); - furi_hal_rtc_init_early(); } void furi_hal_deinit_early() { furi_hal_rtc_deinit_early(); - furi_hal_i2c_deinit_early(); furi_hal_spi_config_deinit_early(); - furi_hal_resources_deinit_early(); - furi_hal_clock_deinit_early(); } @@ -39,41 +30,24 @@ void furi_hal_init() { furi_hal_clock_init(); furi_hal_console_init(); furi_hal_rtc_init(); - furi_hal_interrupt_init(); - furi_hal_flash_init(); - furi_hal_resources_init(); - FURI_LOG_I(TAG, "GPIO OK"); - furi_hal_version_init(); furi_hal_region_init(); - furi_hal_spi_config_init(); furi_hal_spi_dma_init(); - furi_hal_ibutton_init(); - FURI_LOG_I(TAG, "iButton OK"); furi_hal_speaker_init(); - FURI_LOG_I(TAG, "Speaker OK"); - furi_hal_crypto_init(); - furi_hal_i2c_init(); - - // High Level furi_hal_power_init(); furi_hal_light_init(); - furi_hal_bt_init(); furi_hal_memory_init(); - furi_hal_compress_icon_init(); #ifndef FURI_RAM_EXEC - // USB furi_hal_usb_init(); - FURI_LOG_I(TAG, "USB OK"); furi_hal_vibro_init(); furi_hal_subghz_init(); furi_hal_nfc_init(); diff --git a/firmware/targets/f7/furi_hal/furi_hal_ibutton.c b/firmware/targets/f7/furi_hal/furi_hal_ibutton.c index c05cd69a..f19fd0a0 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_ibutton.c +++ b/firmware/targets/f7/furi_hal/furi_hal_ibutton.c @@ -7,6 +7,7 @@ #include +#define TAG "FuriHalIbutton" #define FURI_HAL_IBUTTON_TIMER TIM1 #define FURI_HAL_IBUTTON_TIMER_IRQ FuriHalInterruptIdTim1UpTim16 @@ -33,6 +34,8 @@ static void furi_hal_ibutton_emulate_isr() { void furi_hal_ibutton_init() { furi_hal_ibutton = malloc(sizeof(FuriHalIbutton)); furi_hal_ibutton->state = FuriHalIbuttonStateIdle; + + FURI_LOG_I(TAG, "Init OK"); } void furi_hal_ibutton_emulate_start( diff --git a/firmware/targets/f7/furi_hal/furi_hal_resources.c b/firmware/targets/f7/furi_hal/furi_hal_resources.c index c0eb9ee6..d0d85cb2 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_resources.c +++ b/firmware/targets/f7/furi_hal/furi_hal_resources.c @@ -4,6 +4,8 @@ #include #include +#define TAG "FuriHalResources" + const GpioPin vibro_gpio = {.port = VIBRO_GPIO_Port, .pin = VIBRO_Pin}; const GpioPin ibutton_gpio = {.port = iBTN_GPIO_Port, .pin = iBTN_Pin}; @@ -190,6 +192,8 @@ void furi_hal_resources_init() { NVIC_SetPriority(EXTI15_10_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 5, 0)); NVIC_EnableIRQ(EXTI15_10_IRQn); + + FURI_LOG_I(TAG, "Init OK"); } int32_t furi_hal_resources_get_ext_pin_number(const GpioPin* gpio) { diff --git a/firmware/targets/f7/src/dfu.c b/firmware/targets/f7/src/dfu.c index f32ac2ac..b060bc8d 100644 --- a/firmware/targets/f7/src/dfu.c +++ b/firmware/targets/f7/src/dfu.c @@ -4,10 +4,11 @@ #include #include #include +#include void flipper_boot_dfu_show_splash() { // Initialize - furi_hal_compress_icon_init(); + CompressIcon* compress_icon = compress_icon_alloc(); u8g2_t* fb = malloc(sizeof(u8g2_t)); memset(fb, 0, sizeof(u8g2_t)); @@ -15,13 +16,15 @@ void flipper_boot_dfu_show_splash() { u8g2_InitDisplay(fb); u8g2_SetDrawColor(fb, 0x01); uint8_t* splash_data = NULL; - furi_hal_compress_icon_decode(icon_get_data(&I_DFU_128x50), &splash_data); + compress_icon_decode(compress_icon, icon_get_data(&I_DFU_128x50), &splash_data); u8g2_DrawXBM(fb, 0, 64 - 50, 128, 50, splash_data); u8g2_SetFont(fb, u8g2_font_helvB08_tr); u8g2_DrawStr(fb, 2, 8, "Update & Recovery Mode"); u8g2_DrawStr(fb, 2, 21, "DFU Started"); u8g2_SetPowerSave(fb, 0); u8g2_SendBuffer(fb); + + compress_icon_free(compress_icon); } void flipper_boot_dfu_exec() { diff --git a/firmware/targets/f7/src/recovery.c b/firmware/targets/f7/src/recovery.c index db538b0d..d037e811 100644 --- a/firmware/targets/f7/src/recovery.c +++ b/firmware/targets/f7/src/recovery.c @@ -4,6 +4,7 @@ #include #include #include +#include #define COUNTER_VALUE (136U) @@ -27,9 +28,9 @@ void flipper_boot_recovery_exec() { u8g2_Setup_st756x_flipper(fb, U8G2_R0, u8x8_hw_spi_stm32, u8g2_gpio_and_delay_stm32); u8g2_InitDisplay(fb); - furi_hal_compress_icon_init(); + CompressIcon* compress_icon = compress_icon_alloc(); uint8_t* splash_data = NULL; - furi_hal_compress_icon_decode(icon_get_data(&I_Erase_pin_128x64), &splash_data); + compress_icon_decode(compress_icon, icon_get_data(&I_Erase_pin_128x64), &splash_data); u8g2_ClearBuffer(fb); u8g2_SetDrawColor(fb, 0x01); @@ -38,6 +39,7 @@ void flipper_boot_recovery_exec() { u8g2_DrawXBM(fb, 0, 0, 128, 64, splash_data); u8g2_SendBuffer(fb); u8g2_SetPowerSave(fb, 0); + compress_icon_free(compress_icon); size_t counter = COUNTER_VALUE; while(counter) { diff --git a/firmware/targets/furi_hal_include/furi_hal.h b/firmware/targets/furi_hal_include/furi_hal.h index ad4340dd..2eb4688d 100644 --- a/firmware/targets/furi_hal_include/furi_hal.h +++ b/firmware/targets/furi_hal_include/furi_hal.h @@ -33,7 +33,6 @@ struct STOP_EXTERNING_ME {}; #include #include #include -#include #include #include #include diff --git a/firmware/targets/furi_hal_include/furi_hal_compress.h b/firmware/targets/furi_hal_include/furi_hal_compress.h deleted file mode 100644 index f80aee51..00000000 --- a/firmware/targets/furi_hal_include/furi_hal_compress.h +++ /dev/null @@ -1,87 +0,0 @@ -/** - * @file furi_hal_compress.h - * LZSS based compression HAL API - */ -#pragma once - -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** Defines encoder and decoder window size */ -#define FURI_HAL_COMPRESS_EXP_BUFF_SIZE_LOG (8) - -/** Defines encoder and decoder lookahead buffer size */ -#define FURI_HAL_COMPRESS_LOOKAHEAD_BUFF_SIZE_LOG (4) - -/** FuriHalCompress control structure */ -typedef struct FuriHalCompress FuriHalCompress; - -/** Initialize icon decoder - */ -void furi_hal_compress_icon_init(); - -/** Icon decoder - * - * @param icon_data pointer to icon data - * @param decoded_buff pointer to decoded buffer - */ -void furi_hal_compress_icon_decode(const uint8_t* icon_data, uint8_t** decoded_buff); - -/** Allocate encoder and decoder - * - * @param compress_buff_size size of decoder and encoder buffer to allocate - * - * @return FuriHalCompress instance - */ -FuriHalCompress* furi_hal_compress_alloc(uint16_t compress_buff_size); - -/** Free encoder and decoder - * - * @param compress FuriHalCompress instance - */ -void furi_hal_compress_free(FuriHalCompress* compress); - -/** Encode data - * - * @param compress FuriHalCompress instance - * @param data_in pointer to input data - * @param data_in_size size of input data - * @param data_out maximum size of output data - * @param data_res_size pointer to result output data size - * - * @return true on success - */ -bool furi_hal_compress_encode( - FuriHalCompress* compress, - uint8_t* data_in, - size_t data_in_size, - uint8_t* data_out, - size_t data_out_size, - size_t* data_res_size); - -/** Decode data - * - * @param compress FuriHalCompress instance - * @param data_in pointer to input data - * @param data_in_size size of input data - * @param data_out maximum size of output data - * @param data_res_size pointer to result output data size - * - * @return true on success - */ -bool furi_hal_compress_decode( - FuriHalCompress* compress, - uint8_t* data_in, - size_t data_in_size, - uint8_t* data_out, - size_t data_out_size, - size_t* data_res_size); - -#ifdef __cplusplus -} -#endif diff --git a/lib/err.h b/lib/err.h new file mode 100644 index 00000000..a0e93874 --- /dev/null +++ b/lib/err.h @@ -0,0 +1,4 @@ +#pragma once +#include + +#define err(...) FURI_LOG_E("Heatshrink", "Error: %d-%s", __VA_ARGS__) \ No newline at end of file diff --git a/lib/heatshrink b/lib/heatshrink new file mode 160000 index 00000000..7398ccc9 --- /dev/null +++ b/lib/heatshrink @@ -0,0 +1 @@ +Subproject commit 7398ccc91652a33483245200cfa1a83b073bc206 diff --git a/lib/heatshrink/heatshrink_common.h b/lib/heatshrink/heatshrink_common.h deleted file mode 100644 index 243f4470..00000000 --- a/lib/heatshrink/heatshrink_common.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef HEATSHRINK_H -#define HEATSHRINK_H - -#define HEATSHRINK_AUTHOR "Scott Vokes " -#define HEATSHRINK_URL "https://github.com/atomicobject/heatshrink" - -/* Version 0.4.1 */ -#define HEATSHRINK_VERSION_MAJOR 0 -#define HEATSHRINK_VERSION_MINOR 4 -#define HEATSHRINK_VERSION_PATCH 1 - -#define HEATSHRINK_MIN_WINDOW_BITS 4 -#define HEATSHRINK_MAX_WINDOW_BITS 15 - -#define HEATSHRINK_MIN_LOOKAHEAD_BITS 3 - -#define HEATSHRINK_LITERAL_MARKER 0x01 -#define HEATSHRINK_BACKREF_MARKER 0x00 - -#endif diff --git a/lib/heatshrink/heatshrink_config.h b/lib/heatshrink/heatshrink_config.h deleted file mode 100644 index 7f2373c0..00000000 --- a/lib/heatshrink/heatshrink_config.h +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef HEATSHRINK_CONFIG_H -#define HEATSHRINK_CONFIG_H - -#include - -/* Should functionality assuming dynamic allocation be used? */ -#ifndef HEATSHRINK_DYNAMIC_ALLOC -#define HEATSHRINK_DYNAMIC_ALLOC 1 -#endif - -#if HEATSHRINK_DYNAMIC_ALLOC - /* Optional replacement of malloc/free */ - #define HEATSHRINK_MALLOC(SZ) malloc(SZ) - #define HEATSHRINK_FREE(P, SZ) free(P) -#else - /* Required parameters for static configuration */ - #define HEATSHRINK_STATIC_INPUT_BUFFER_SIZE 1024 - #define HEATSHRINK_STATIC_WINDOW_BITS 8 - #define HEATSHRINK_STATIC_LOOKAHEAD_BITS 4 -#endif - -/* Turn on logging for debugging. */ -#define HEATSHRINK_DEBUGGING_LOGS 0 - -/* Use indexing for faster compression. (This requires additional space.) */ -#define HEATSHRINK_USE_INDEX 1 - -#endif diff --git a/lib/heatshrink/heatshrink_decoder.c b/lib/heatshrink/heatshrink_decoder.c deleted file mode 100644 index 28782836..00000000 --- a/lib/heatshrink/heatshrink_decoder.c +++ /dev/null @@ -1,364 +0,0 @@ -#include -#include -#include "heatshrink_decoder.h" - -/* States for the polling state machine. */ -typedef enum { - HSDS_TAG_BIT, /* tag bit */ - HSDS_YIELD_LITERAL, /* ready to yield literal byte */ - HSDS_BACKREF_INDEX_MSB, /* most significant byte of index */ - HSDS_BACKREF_INDEX_LSB, /* least significant byte of index */ - HSDS_BACKREF_COUNT_MSB, /* most significant byte of count */ - HSDS_BACKREF_COUNT_LSB, /* least significant byte of count */ - HSDS_YIELD_BACKREF, /* ready to yield back-reference */ -} HSD_state; - -#if HEATSHRINK_DEBUGGING_LOGS -#include -#include -#include -#define LOG(...) fprintf(stderr, __VA_ARGS__) -#define ASSERT(X) assert(X) -static const char *state_names[] = { - "tag_bit", - "yield_literal", - "backref_index_msb", - "backref_index_lsb", - "backref_count_msb", - "backref_count_lsb", - "yield_backref", -}; -#else -#define LOG(...) /* no-op */ -#define ASSERT(X) /* no-op */ -#endif - -typedef struct { - uint8_t *buf; /* output buffer */ - size_t buf_size; /* buffer size */ - size_t *output_size; /* bytes pushed to buffer, so far */ -} output_info; - -#define NO_BITS ((uint16_t)-1) - -/* Forward references. */ -static uint16_t get_bits(heatshrink_decoder *hsd, uint8_t count); -static void push_byte(heatshrink_decoder *hsd, output_info *oi, uint8_t byte); - -#if HEATSHRINK_DYNAMIC_ALLOC -heatshrink_decoder *heatshrink_decoder_alloc(uint8_t* buffer, - uint16_t input_buffer_size, - uint8_t window_sz2, - uint8_t lookahead_sz2) { - if ((window_sz2 < HEATSHRINK_MIN_WINDOW_BITS) || - (window_sz2 > HEATSHRINK_MAX_WINDOW_BITS) || - (input_buffer_size == 0) || - (lookahead_sz2 < HEATSHRINK_MIN_LOOKAHEAD_BITS) || - (lookahead_sz2 >= window_sz2)) { - return NULL; - } - size_t sz = sizeof(heatshrink_decoder); - heatshrink_decoder *hsd = HEATSHRINK_MALLOC(sz); - if (hsd == NULL) { return NULL; } - hsd->input_buffer_size = input_buffer_size; - hsd->window_sz2 = window_sz2; - hsd->lookahead_sz2 = lookahead_sz2; - hsd->buffers = buffer; - heatshrink_decoder_reset(hsd); - LOG("-- allocated decoder with buffer size of %zu (%zu + %u + %u)\n", - sz, sizeof(heatshrink_decoder), (1 << window_sz2), input_buffer_size); - return hsd; -} - -void heatshrink_decoder_free(heatshrink_decoder *hsd) { - size_t sz = sizeof(heatshrink_decoder); - HEATSHRINK_FREE(hsd, sz); - (void)sz; /* may not be used by free */ -} -#endif - -void heatshrink_decoder_reset(heatshrink_decoder *hsd) { - hsd->state = HSDS_TAG_BIT; - hsd->input_size = 0; - hsd->input_index = 0; - hsd->bit_index = 0x00; - hsd->current_byte = 0x00; - hsd->output_count = 0; - hsd->output_index = 0; - hsd->head_index = 0; -} - -/* Copy SIZE bytes into the decoder's input buffer, if it will fit. */ -HSD_sink_res heatshrink_decoder_sink(heatshrink_decoder *hsd, - uint8_t *in_buf, size_t size, size_t *input_size) { - if ((hsd == NULL) || (in_buf == NULL) || (input_size == NULL)) { - return HSDR_SINK_ERROR_NULL; - } - - size_t rem = HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(hsd) - hsd->input_size; - if (rem == 0) { - *input_size = 0; - return HSDR_SINK_FULL; - } - - size = rem < size ? rem : size; - LOG("-- sinking %zd bytes\n", size); - /* copy into input buffer (at head of buffers) */ - memcpy(&hsd->buffers[hsd->input_size], in_buf, size); - hsd->input_size += size; - *input_size = size; - return HSDR_SINK_OK; -} - - -/***************** - * Decompression * - *****************/ - -#define BACKREF_COUNT_BITS(HSD) (HEATSHRINK_DECODER_LOOKAHEAD_BITS(HSD)) -#define BACKREF_INDEX_BITS(HSD) (HEATSHRINK_DECODER_WINDOW_BITS(HSD)) - -// States -static HSD_state st_tag_bit(heatshrink_decoder *hsd); -static HSD_state st_yield_literal(heatshrink_decoder *hsd, - output_info *oi); -static HSD_state st_backref_index_msb(heatshrink_decoder *hsd); -static HSD_state st_backref_index_lsb(heatshrink_decoder *hsd); -static HSD_state st_backref_count_msb(heatshrink_decoder *hsd); -static HSD_state st_backref_count_lsb(heatshrink_decoder *hsd); -static HSD_state st_yield_backref(heatshrink_decoder *hsd, - output_info *oi); - -HSD_poll_res heatshrink_decoder_poll(heatshrink_decoder *hsd, - uint8_t *out_buf, size_t out_buf_size, size_t *output_size) { - if ((hsd == NULL) || (out_buf == NULL) || (output_size == NULL)) { - return HSDR_POLL_ERROR_NULL; - } - *output_size = 0; - - output_info oi; - oi.buf = out_buf; - oi.buf_size = out_buf_size; - oi.output_size = output_size; - - while (1) { - LOG("-- poll, state is %d (%s), input_size %d\n", - hsd->state, state_names[hsd->state], hsd->input_size); - uint8_t in_state = hsd->state; - switch (in_state) { - case HSDS_TAG_BIT: - hsd->state = st_tag_bit(hsd); - break; - case HSDS_YIELD_LITERAL: - hsd->state = st_yield_literal(hsd, &oi); - break; - case HSDS_BACKREF_INDEX_MSB: - hsd->state = st_backref_index_msb(hsd); - break; - case HSDS_BACKREF_INDEX_LSB: - hsd->state = st_backref_index_lsb(hsd); - break; - case HSDS_BACKREF_COUNT_MSB: - hsd->state = st_backref_count_msb(hsd); - break; - case HSDS_BACKREF_COUNT_LSB: - hsd->state = st_backref_count_lsb(hsd); - break; - case HSDS_YIELD_BACKREF: - hsd->state = st_yield_backref(hsd, &oi); - break; - default: - return HSDR_POLL_ERROR_UNKNOWN; - } - - /* If the current state cannot advance, check if input or output - * buffer are exhausted. */ - if (hsd->state == in_state) { - if (*output_size == out_buf_size) { return HSDR_POLL_MORE; } - return HSDR_POLL_EMPTY; - } - } -} - -static HSD_state st_tag_bit(heatshrink_decoder *hsd) { - uint32_t bits = get_bits(hsd, 1); // get tag bit - if (bits == NO_BITS) { - return HSDS_TAG_BIT; - } else if (bits) { - return HSDS_YIELD_LITERAL; - } else if (HEATSHRINK_DECODER_WINDOW_BITS(hsd) > 8) { - return HSDS_BACKREF_INDEX_MSB; - } else { - hsd->output_index = 0; - return HSDS_BACKREF_INDEX_LSB; - } -} - -static HSD_state st_yield_literal(heatshrink_decoder *hsd, - output_info *oi) { - /* Emit a repeated section from the window buffer, and add it (again) - * to the window buffer. (Note that the repetition can include - * itself.)*/ - if (*oi->output_size < oi->buf_size) { - uint16_t byte = get_bits(hsd, 8); - if (byte == NO_BITS) { return HSDS_YIELD_LITERAL; } /* out of input */ - uint8_t *buf = &hsd->buffers[HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(hsd)]; - uint16_t mask = (1 << HEATSHRINK_DECODER_WINDOW_BITS(hsd)) - 1; - uint8_t c = byte & 0xFF; - LOG("-- emitting literal byte 0x%02x ('%c')\n", c, isprint(c) ? c : '.'); - buf[hsd->head_index++ & mask] = c; - push_byte(hsd, oi, c); - return HSDS_TAG_BIT; - } else { - return HSDS_YIELD_LITERAL; - } -} - -static HSD_state st_backref_index_msb(heatshrink_decoder *hsd) { - uint8_t bit_ct = BACKREF_INDEX_BITS(hsd); - ASSERT(bit_ct > 8); - uint16_t bits = get_bits(hsd, bit_ct - 8); - LOG("-- backref index (msb), got 0x%04x (+1)\n", bits); - if (bits == NO_BITS) { return HSDS_BACKREF_INDEX_MSB; } - hsd->output_index = bits << 8; - return HSDS_BACKREF_INDEX_LSB; -} - -static HSD_state st_backref_index_lsb(heatshrink_decoder *hsd) { - uint8_t bit_ct = BACKREF_INDEX_BITS(hsd); - uint16_t bits = get_bits(hsd, bit_ct < 8 ? bit_ct : 8); - LOG("-- backref index (lsb), got 0x%04x (+1)\n", bits); - if (bits == NO_BITS) { return HSDS_BACKREF_INDEX_LSB; } - hsd->output_index |= bits; - hsd->output_index++; - uint8_t br_bit_ct = BACKREF_COUNT_BITS(hsd); - hsd->output_count = 0; - return (br_bit_ct > 8) ? HSDS_BACKREF_COUNT_MSB : HSDS_BACKREF_COUNT_LSB; -} - -static HSD_state st_backref_count_msb(heatshrink_decoder *hsd) { - uint8_t br_bit_ct = BACKREF_COUNT_BITS(hsd); - ASSERT(br_bit_ct > 8); - uint16_t bits = get_bits(hsd, br_bit_ct - 8); - LOG("-- backref count (msb), got 0x%04x (+1)\n", bits); - if (bits == NO_BITS) { return HSDS_BACKREF_COUNT_MSB; } - hsd->output_count = bits << 8; - return HSDS_BACKREF_COUNT_LSB; -} - -static HSD_state st_backref_count_lsb(heatshrink_decoder *hsd) { - uint8_t br_bit_ct = BACKREF_COUNT_BITS(hsd); - uint16_t bits = get_bits(hsd, br_bit_ct < 8 ? br_bit_ct : 8); - LOG("-- backref count (lsb), got 0x%04x (+1)\n", bits); - if (bits == NO_BITS) { return HSDS_BACKREF_COUNT_LSB; } - hsd->output_count |= bits; - hsd->output_count++; - return HSDS_YIELD_BACKREF; -} - -static HSD_state st_yield_backref(heatshrink_decoder *hsd, - output_info *oi) { - size_t count = oi->buf_size - *oi->output_size; - if (count > 0) { - size_t i = 0; - if (hsd->output_count < count) count = hsd->output_count; - uint8_t *buf = &hsd->buffers[HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(hsd)]; - uint16_t mask = (1 << HEATSHRINK_DECODER_WINDOW_BITS(hsd)) - 1; - uint16_t neg_offset = hsd->output_index; - LOG("-- emitting %zu bytes from -%u bytes back\n", count, neg_offset); - ASSERT(neg_offset <= mask + 1); - ASSERT(count <= (size_t)(1 << BACKREF_COUNT_BITS(hsd))); - - for (i=0; ihead_index - neg_offset) & mask]; - push_byte(hsd, oi, c); - buf[hsd->head_index & mask] = c; - hsd->head_index++; - LOG(" -- ++ 0x%02x\n", c); - } - hsd->output_count -= count; - if (hsd->output_count == 0) { return HSDS_TAG_BIT; } - } - return HSDS_YIELD_BACKREF; -} - -/* Get the next COUNT bits from the input buffer, saving incremental progress. - * Returns NO_BITS on end of input, or if more than 15 bits are requested. */ -static uint16_t get_bits(heatshrink_decoder *hsd, uint8_t count) { - uint16_t accumulator = 0; - int i = 0; - if (count > 15) { return NO_BITS; } - LOG("-- popping %u bit(s)\n", count); - - /* If we aren't able to get COUNT bits, suspend immediately, because we - * don't track how many bits of COUNT we've accumulated before suspend. */ - if (hsd->input_size == 0) { - if (hsd->bit_index < (1 << (count - 1))) { return NO_BITS; } - } - - for (i = 0; i < count; i++) { - if (hsd->bit_index == 0x00) { - if (hsd->input_size == 0) { - LOG(" -- out of bits, suspending w/ accumulator of %u (0x%02x)\n", - accumulator, accumulator); - return NO_BITS; - } - hsd->current_byte = hsd->buffers[hsd->input_index++]; - LOG(" -- pulled byte 0x%02x\n", hsd->current_byte); - if (hsd->input_index == hsd->input_size) { - hsd->input_index = 0; /* input is exhausted */ - hsd->input_size = 0; - } - hsd->bit_index = 0x80; - } - accumulator <<= 1; - if (hsd->current_byte & hsd->bit_index) { - accumulator |= 0x01; - if (0) { - LOG(" -- got 1, accumulator 0x%04x, bit_index 0x%02x\n", - accumulator, hsd->bit_index); - } - } else { - if (0) { - LOG(" -- got 0, accumulator 0x%04x, bit_index 0x%02x\n", - accumulator, hsd->bit_index); - } - } - hsd->bit_index >>= 1; - } - - if (count > 1) { LOG(" -- accumulated %08x\n", accumulator); } - return accumulator; -} - -HSD_finish_res heatshrink_decoder_finish(heatshrink_decoder *hsd) { - if (hsd == NULL) { return HSDR_FINISH_ERROR_NULL; } - switch (hsd->state) { - case HSDS_TAG_BIT: - return hsd->input_size == 0 ? HSDR_FINISH_DONE : HSDR_FINISH_MORE; - - /* If we want to finish with no input, but are in these states, it's - * because the 0-bit padding to the last byte looks like a backref - * marker bit followed by all 0s for index and count bits. */ - case HSDS_BACKREF_INDEX_LSB: - case HSDS_BACKREF_INDEX_MSB: - case HSDS_BACKREF_COUNT_LSB: - case HSDS_BACKREF_COUNT_MSB: - return hsd->input_size == 0 ? HSDR_FINISH_DONE : HSDR_FINISH_MORE; - - /* If the output stream is padded with 0xFFs (possibly due to being in - * flash memory), also explicitly check the input size rather than - * uselessly returning MORE but yielding 0 bytes when polling. */ - case HSDS_YIELD_LITERAL: - return hsd->input_size == 0 ? HSDR_FINISH_DONE : HSDR_FINISH_MORE; - - default: - return HSDR_FINISH_MORE; - } -} - -static void push_byte(heatshrink_decoder *hsd, output_info *oi, uint8_t byte) { - LOG(" -- pushing byte: 0x%02x ('%c')\n", byte, isprint(byte) ? byte : '.'); - oi->buf[(*oi->output_size)++] = byte; - (void)hsd; -} diff --git a/lib/heatshrink/heatshrink_decoder.h b/lib/heatshrink/heatshrink_decoder.h deleted file mode 100644 index 687b0806..00000000 --- a/lib/heatshrink/heatshrink_decoder.h +++ /dev/null @@ -1,100 +0,0 @@ -#ifndef HEATSHRINK_DECODER_H -#define HEATSHRINK_DECODER_H - -#include -#include -#include "heatshrink_common.h" -#include "heatshrink_config.h" - -typedef enum { - HSDR_SINK_OK, /* data sunk, ready to poll */ - HSDR_SINK_FULL, /* out of space in internal buffer */ - HSDR_SINK_ERROR_NULL=-1, /* NULL argument */ -} HSD_sink_res; - -typedef enum { - HSDR_POLL_EMPTY, /* input exhausted */ - HSDR_POLL_MORE, /* more data remaining, call again w/ fresh output buffer */ - HSDR_POLL_ERROR_NULL=-1, /* NULL arguments */ - HSDR_POLL_ERROR_UNKNOWN=-2, -} HSD_poll_res; - -typedef enum { - HSDR_FINISH_DONE, /* output is done */ - HSDR_FINISH_MORE, /* more output remains */ - HSDR_FINISH_ERROR_NULL=-1, /* NULL arguments */ -} HSD_finish_res; - -#if HEATSHRINK_DYNAMIC_ALLOC -#define HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(BUF) \ - ((BUF)->input_buffer_size) -#define HEATSHRINK_DECODER_WINDOW_BITS(BUF) \ - ((BUF)->window_sz2) -#define HEATSHRINK_DECODER_LOOKAHEAD_BITS(BUF) \ - ((BUF)->lookahead_sz2) -#else -#define HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(_) \ - HEATSHRINK_STATIC_INPUT_BUFFER_SIZE -#define HEATSHRINK_DECODER_WINDOW_BITS(_) \ - (HEATSHRINK_STATIC_WINDOW_BITS) -#define HEATSHRINK_DECODER_LOOKAHEAD_BITS(BUF) \ - (HEATSHRINK_STATIC_LOOKAHEAD_BITS) -#endif - -typedef struct { - uint16_t input_size; /* bytes in input buffer */ - uint16_t input_index; /* offset to next unprocessed input byte */ - uint16_t output_count; /* how many bytes to output */ - uint16_t output_index; /* index for bytes to output */ - uint16_t head_index; /* head of window buffer */ - uint8_t state; /* current state machine node */ - uint8_t current_byte; /* current byte of input */ - uint8_t bit_index; /* current bit index */ - -#if HEATSHRINK_DYNAMIC_ALLOC - /* Fields that are only used if dynamically allocated. */ - uint8_t window_sz2; /* window buffer bits */ - uint8_t lookahead_sz2; /* lookahead bits */ - uint16_t input_buffer_size; /* input buffer size */ - - /* Input buffer, then expansion window buffer */ - uint8_t* buffers; -#else - /* Input buffer, then expansion window buffer */ - uint8_t buffers[(1 << HEATSHRINK_DECODER_WINDOW_BITS(_)) - + HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(_)]; -#endif -} heatshrink_decoder; - -#if HEATSHRINK_DYNAMIC_ALLOC -/* Allocate a decoder with an input buffer of INPUT_BUFFER_SIZE bytes, - * an expansion buffer size of 2^WINDOW_SZ2, and a lookahead - * size of 2^lookahead_sz2. (The window buffer and lookahead sizes - * must match the settings used when the data was compressed.) - * Returns NULL on error. */ -heatshrink_decoder *heatshrink_decoder_alloc(uint8_t* buffer, uint16_t input_buffer_size, - uint8_t expansion_buffer_sz2, uint8_t lookahead_sz2); - -/* Free a decoder. */ -void heatshrink_decoder_free(heatshrink_decoder *hsd); -#endif - -/* Reset a decoder. */ -void heatshrink_decoder_reset(heatshrink_decoder *hsd); - -/* Sink at most SIZE bytes from IN_BUF into the decoder. *INPUT_SIZE is set to - * indicate how many bytes were actually sunk (in case a buffer was filled). */ -HSD_sink_res heatshrink_decoder_sink(heatshrink_decoder *hsd, - uint8_t *in_buf, size_t size, size_t *input_size); - -/* Poll for output from the decoder, copying at most OUT_BUF_SIZE bytes into - * OUT_BUF (setting *OUTPUT_SIZE to the actual amount copied). */ -HSD_poll_res heatshrink_decoder_poll(heatshrink_decoder *hsd, - uint8_t *out_buf, size_t out_buf_size, size_t *output_size); - -/* Notify the dencoder that the input stream is finished. - * If the return value is HSDR_FINISH_MORE, there is still more output, so - * call heatshrink_decoder_poll and repeat. */ -HSD_finish_res heatshrink_decoder_finish(heatshrink_decoder *hsd); - -#endif diff --git a/lib/heatshrink/heatshrink_encoder.c b/lib/heatshrink/heatshrink_encoder.c deleted file mode 100644 index 98f27dff..00000000 --- a/lib/heatshrink/heatshrink_encoder.c +++ /dev/null @@ -1,602 +0,0 @@ -#include -#include -#include -#include "heatshrink_encoder.h" - -typedef enum { - HSES_NOT_FULL, /* input buffer not full enough */ - HSES_FILLED, /* buffer is full */ - HSES_SEARCH, /* searching for patterns */ - HSES_YIELD_TAG_BIT, /* yield tag bit */ - HSES_YIELD_LITERAL, /* emit literal byte */ - HSES_YIELD_BR_INDEX, /* yielding backref index */ - HSES_YIELD_BR_LENGTH, /* yielding backref length */ - HSES_SAVE_BACKLOG, /* copying buffer to backlog */ - HSES_FLUSH_BITS, /* flush bit buffer */ - HSES_DONE, /* done */ -} HSE_state; - -#if HEATSHRINK_DEBUGGING_LOGS -#include -#include -#include -#define LOG(...) fprintf(stderr, __VA_ARGS__) -#define ASSERT(X) assert(X) -static const char *state_names[] = { - "not_full", - "filled", - "search", - "yield_tag_bit", - "yield_literal", - "yield_br_index", - "yield_br_length", - "save_backlog", - "flush_bits", - "done", -}; -#else -#define LOG(...) /* no-op */ -#define ASSERT(X) /* no-op */ -#endif - -// Encoder flags -enum { - FLAG_IS_FINISHING = 0x01, -}; - -typedef struct { - uint8_t *buf; /* output buffer */ - size_t buf_size; /* buffer size */ - size_t *output_size; /* bytes pushed to buffer, so far */ -} output_info; - -#define MATCH_NOT_FOUND ((uint16_t)-1) - -static uint16_t get_input_offset(heatshrink_encoder *hse); -static uint16_t get_input_buffer_size(heatshrink_encoder *hse); -static uint16_t get_lookahead_size(heatshrink_encoder *hse); -static void add_tag_bit(heatshrink_encoder *hse, output_info *oi, uint8_t tag); -static int can_take_byte(output_info *oi); -static int is_finishing(heatshrink_encoder *hse); -static void save_backlog(heatshrink_encoder *hse); - -/* Push COUNT (max 8) bits to the output buffer, which has room. */ -static void push_bits(heatshrink_encoder *hse, uint8_t count, uint8_t bits, - output_info *oi); -static uint8_t push_outgoing_bits(heatshrink_encoder *hse, output_info *oi); -static void push_literal_byte(heatshrink_encoder *hse, output_info *oi); - -#if HEATSHRINK_DYNAMIC_ALLOC -heatshrink_encoder *heatshrink_encoder_alloc(uint8_t* buffer, uint8_t window_sz2, - uint8_t lookahead_sz2) { - if ((window_sz2 < HEATSHRINK_MIN_WINDOW_BITS) || - (window_sz2 > HEATSHRINK_MAX_WINDOW_BITS) || - (lookahead_sz2 < HEATSHRINK_MIN_LOOKAHEAD_BITS) || - (lookahead_sz2 >= window_sz2)) { - return NULL; - } - - /* Note: 2 * the window size is used because the buffer needs to fit - * (1 << window_sz2) bytes for the current input, and an additional - * (1 << window_sz2) bytes for the previous buffer of input, which - * will be scanned for useful backreferences. */ - size_t buf_sz = (2 << window_sz2); - - heatshrink_encoder *hse = HEATSHRINK_MALLOC(sizeof(*hse)); - if (hse == NULL) { return NULL; } - hse->window_sz2 = window_sz2; - hse->lookahead_sz2 = lookahead_sz2; - hse->buffer = buffer; - heatshrink_encoder_reset(hse); - -#if HEATSHRINK_USE_INDEX - size_t index_sz = buf_sz*sizeof(uint16_t); - hse->search_index = HEATSHRINK_MALLOC(index_sz + sizeof(struct hs_index)); - if (hse->search_index == NULL) { - HEATSHRINK_FREE(hse, sizeof(*hse) + buf_sz); - return NULL; - } - hse->search_index->size = index_sz; -#endif - - LOG("-- allocated encoder with buffer size of %zu (%u byte input size)\n", - buf_sz, get_input_buffer_size(hse)); - return hse; -} - -void heatshrink_encoder_free(heatshrink_encoder *hse) { -#if HEATSHRINK_USE_INDEX - size_t index_sz = sizeof(struct hs_index) + hse->search_index->size; - HEATSHRINK_FREE(hse->search_index, index_sz); - (void)index_sz; -#endif - HEATSHRINK_FREE(hse, sizeof(heatshrink_encoder)); -} -#endif - -void heatshrink_encoder_reset(heatshrink_encoder *hse) { - hse->input_size = 0; - hse->state = HSES_NOT_FULL; - hse->match_scan_index = 0; - hse->flags = 0; - hse->bit_index = 0x80; - hse->current_byte = 0x00; - hse->match_length = 0; - - hse->outgoing_bits = 0x0000; - hse->outgoing_bits_count = 0; - - #ifdef LOOP_DETECT - hse->loop_detect = (uint32_t)-1; - #endif -} - -HSE_sink_res heatshrink_encoder_sink(heatshrink_encoder *hse, - uint8_t *in_buf, size_t size, size_t *input_size) { - if ((hse == NULL) || (in_buf == NULL) || (input_size == NULL)) { - return HSER_SINK_ERROR_NULL; - } - - /* Sinking more content after saying the content is done, tsk tsk */ - if (is_finishing(hse)) { return HSER_SINK_ERROR_MISUSE; } - - /* Sinking more content before processing is done */ - if (hse->state != HSES_NOT_FULL) { return HSER_SINK_ERROR_MISUSE; } - - uint16_t write_offset = get_input_offset(hse) + hse->input_size; - uint16_t ibs = get_input_buffer_size(hse); - uint16_t rem = ibs - hse->input_size; - uint16_t cp_sz = rem < size ? rem : size; - - memcpy(&hse->buffer[write_offset], in_buf, cp_sz); - *input_size = cp_sz; - hse->input_size += cp_sz; - - LOG("-- sunk %u bytes (of %zu) into encoder at %d, input buffer now has %u\n", - cp_sz, size, write_offset, hse->input_size); - if (cp_sz == rem) { - LOG("-- internal buffer is now full\n"); - hse->state = HSES_FILLED; - } - - return HSER_SINK_OK; -} - - -/*************** - * Compression * - ***************/ - -static uint16_t find_longest_match(heatshrink_encoder *hse, uint16_t start, - uint16_t end, const uint16_t maxlen, uint16_t *match_length); -static void do_indexing(heatshrink_encoder *hse); - -static HSE_state st_step_search(heatshrink_encoder *hse); -static HSE_state st_yield_tag_bit(heatshrink_encoder *hse, - output_info *oi); -static HSE_state st_yield_literal(heatshrink_encoder *hse, - output_info *oi); -static HSE_state st_yield_br_index(heatshrink_encoder *hse, - output_info *oi); -static HSE_state st_yield_br_length(heatshrink_encoder *hse, - output_info *oi); -static HSE_state st_save_backlog(heatshrink_encoder *hse); -static HSE_state st_flush_bit_buffer(heatshrink_encoder *hse, - output_info *oi); - -HSE_poll_res heatshrink_encoder_poll(heatshrink_encoder *hse, - uint8_t *out_buf, size_t out_buf_size, size_t *output_size) { - if ((hse == NULL) || (out_buf == NULL) || (output_size == NULL)) { - return HSER_POLL_ERROR_NULL; - } - if (out_buf_size == 0) { - LOG("-- MISUSE: output buffer size is 0\n"); - return HSER_POLL_ERROR_MISUSE; - } - *output_size = 0; - - output_info oi; - oi.buf = out_buf; - oi.buf_size = out_buf_size; - oi.output_size = output_size; - - while (1) { - LOG("-- polling, state %u (%s), flags 0x%02x\n", - hse->state, state_names[hse->state], hse->flags); - - uint8_t in_state = hse->state; - switch (in_state) { - case HSES_NOT_FULL: - return HSER_POLL_EMPTY; - case HSES_FILLED: - do_indexing(hse); - hse->state = HSES_SEARCH; - break; - case HSES_SEARCH: - hse->state = st_step_search(hse); - break; - case HSES_YIELD_TAG_BIT: - hse->state = st_yield_tag_bit(hse, &oi); - break; - case HSES_YIELD_LITERAL: - hse->state = st_yield_literal(hse, &oi); - break; - case HSES_YIELD_BR_INDEX: - hse->state = st_yield_br_index(hse, &oi); - break; - case HSES_YIELD_BR_LENGTH: - hse->state = st_yield_br_length(hse, &oi); - break; - case HSES_SAVE_BACKLOG: - hse->state = st_save_backlog(hse); - break; - case HSES_FLUSH_BITS: - hse->state = st_flush_bit_buffer(hse, &oi); - /* fall through */ - case HSES_DONE: - return HSER_POLL_EMPTY; - default: - LOG("-- bad state %s\n", state_names[hse->state]); - return HSER_POLL_ERROR_MISUSE; - } - - if (hse->state == in_state) { - /* Check if output buffer is exhausted. */ - if (*output_size == out_buf_size) return HSER_POLL_MORE; - } - } -} - -HSE_finish_res heatshrink_encoder_finish(heatshrink_encoder *hse) { - if (hse == NULL) { return HSER_FINISH_ERROR_NULL; } - LOG("-- setting is_finishing flag\n"); - hse->flags |= FLAG_IS_FINISHING; - if (hse->state == HSES_NOT_FULL) { hse->state = HSES_FILLED; } - return hse->state == HSES_DONE ? HSER_FINISH_DONE : HSER_FINISH_MORE; -} - -static HSE_state st_step_search(heatshrink_encoder *hse) { - uint16_t window_length = get_input_buffer_size(hse); - uint16_t lookahead_sz = get_lookahead_size(hse); - uint16_t msi = hse->match_scan_index; - LOG("## step_search, scan @ +%d (%d/%d), input size %d\n", - msi, hse->input_size + msi, 2*window_length, hse->input_size); - - bool fin = is_finishing(hse); - if (msi > hse->input_size - (fin ? 1 : lookahead_sz)) { - /* Current search buffer is exhausted, copy it into the - * backlog and await more input. */ - LOG("-- end of search @ %d\n", msi); - return fin ? HSES_FLUSH_BITS : HSES_SAVE_BACKLOG; - } - - uint16_t input_offset = get_input_offset(hse); - uint16_t end = input_offset + msi; - uint16_t start = end - window_length; - - uint16_t max_possible = lookahead_sz; - if (hse->input_size - msi < lookahead_sz) { - max_possible = hse->input_size - msi; - } - - uint16_t match_length = 0; - uint16_t match_pos = find_longest_match(hse, - start, end, max_possible, &match_length); - - if (match_pos == MATCH_NOT_FOUND) { - LOG("ss Match not found\n"); - hse->match_scan_index++; - hse->match_length = 0; - return HSES_YIELD_TAG_BIT; - } else { - LOG("ss Found match of %d bytes at %d\n", match_length, match_pos); - hse->match_pos = match_pos; - hse->match_length = match_length; - ASSERT(match_pos <= 1 << HEATSHRINK_ENCODER_WINDOW_BITS(hse) /*window_length*/); - - return HSES_YIELD_TAG_BIT; - } -} - -static HSE_state st_yield_tag_bit(heatshrink_encoder *hse, - output_info *oi) { - if (can_take_byte(oi)) { - if (hse->match_length == 0) { - add_tag_bit(hse, oi, HEATSHRINK_LITERAL_MARKER); - return HSES_YIELD_LITERAL; - } else { - add_tag_bit(hse, oi, HEATSHRINK_BACKREF_MARKER); - hse->outgoing_bits = hse->match_pos - 1; - hse->outgoing_bits_count = HEATSHRINK_ENCODER_WINDOW_BITS(hse); - return HSES_YIELD_BR_INDEX; - } - } else { - return HSES_YIELD_TAG_BIT; /* output is full, continue */ - } -} - -static HSE_state st_yield_literal(heatshrink_encoder *hse, - output_info *oi) { - if (can_take_byte(oi)) { - push_literal_byte(hse, oi); - return HSES_SEARCH; - } else { - return HSES_YIELD_LITERAL; - } -} - -static HSE_state st_yield_br_index(heatshrink_encoder *hse, - output_info *oi) { - if (can_take_byte(oi)) { - LOG("-- yielding backref index %u\n", hse->match_pos); - if (push_outgoing_bits(hse, oi) > 0) { - return HSES_YIELD_BR_INDEX; /* continue */ - } else { - hse->outgoing_bits = hse->match_length - 1; - hse->outgoing_bits_count = HEATSHRINK_ENCODER_LOOKAHEAD_BITS(hse); - return HSES_YIELD_BR_LENGTH; /* done */ - } - } else { - return HSES_YIELD_BR_INDEX; /* continue */ - } -} - -static HSE_state st_yield_br_length(heatshrink_encoder *hse, - output_info *oi) { - if (can_take_byte(oi)) { - LOG("-- yielding backref length %u\n", hse->match_length); - if (push_outgoing_bits(hse, oi) > 0) { - return HSES_YIELD_BR_LENGTH; - } else { - hse->match_scan_index += hse->match_length; - hse->match_length = 0; - return HSES_SEARCH; - } - } else { - return HSES_YIELD_BR_LENGTH; - } -} - -static HSE_state st_save_backlog(heatshrink_encoder *hse) { - LOG("-- saving backlog\n"); - save_backlog(hse); - return HSES_NOT_FULL; -} - -static HSE_state st_flush_bit_buffer(heatshrink_encoder *hse, - output_info *oi) { - if (hse->bit_index == 0x80) { - LOG("-- done!\n"); - return HSES_DONE; - } else if (can_take_byte(oi)) { - LOG("-- flushing remaining byte (bit_index == 0x%02x)\n", hse->bit_index); - oi->buf[(*oi->output_size)++] = hse->current_byte; - LOG("-- done!\n"); - return HSES_DONE; - } else { - return HSES_FLUSH_BITS; - } -} - -static void add_tag_bit(heatshrink_encoder *hse, output_info *oi, uint8_t tag) { - LOG("-- adding tag bit: %d\n", tag); - push_bits(hse, 1, tag, oi); -} - -static uint16_t get_input_offset(heatshrink_encoder *hse) { - return get_input_buffer_size(hse); -} - -static uint16_t get_input_buffer_size(heatshrink_encoder *hse) { - return (1 << HEATSHRINK_ENCODER_WINDOW_BITS(hse)); - (void)hse; -} - -static uint16_t get_lookahead_size(heatshrink_encoder *hse) { - return (1 << HEATSHRINK_ENCODER_LOOKAHEAD_BITS(hse)); - (void)hse; -} - -static void do_indexing(heatshrink_encoder *hse) { -#if HEATSHRINK_USE_INDEX - /* Build an index array I that contains flattened linked lists - * for the previous instances of every byte in the buffer. - * - * For example, if buf[200] == 'x', then index[200] will either - * be an offset i such that buf[i] == 'x', or a negative offset - * to indicate end-of-list. This significantly speeds up matching, - * while only using sizeof(uint16_t)*sizeof(buffer) bytes of RAM. - * - * Future optimization options: - * 1. Since any negative value represents end-of-list, the other - * 15 bits could be used to improve the index dynamically. - * - * 2. Likewise, the last lookahead_sz bytes of the index will - * not be usable, so temporary data could be stored there to - * dynamically improve the index. - * */ - struct hs_index *hsi = HEATSHRINK_ENCODER_INDEX(hse); - int16_t last[256]; - memset(last, 0xFF, sizeof(last)); - - uint8_t * const data = hse->buffer; - int16_t * const index = hsi->index; - - const uint16_t input_offset = get_input_offset(hse); - const uint16_t end = input_offset + hse->input_size; - - for (uint16_t i=0; iflags & FLAG_IS_FINISHING; -} - -static int can_take_byte(output_info *oi) { - return *oi->output_size < oi->buf_size; -} - -/* Return the longest match for the bytes at buf[end:end+maxlen] between - * buf[start] and buf[end-1]. If no match is found, return -1. */ -static uint16_t find_longest_match(heatshrink_encoder *hse, uint16_t start, - uint16_t end, const uint16_t maxlen, uint16_t *match_length) { - LOG("-- scanning for match of buf[%u:%u] between buf[%u:%u] (max %u bytes)\n", - end, end + maxlen, start, end + maxlen - 1, maxlen); - uint8_t *buf = hse->buffer; - - uint16_t match_maxlen = 0; - uint16_t match_index = MATCH_NOT_FOUND; - - uint16_t len = 0; - uint8_t * const needlepoint = &buf[end]; -#if HEATSHRINK_USE_INDEX - struct hs_index *hsi = HEATSHRINK_ENCODER_INDEX(hse); - int16_t pos = hsi->index[end]; - - while (pos - (int16_t)start >= 0) { - uint8_t * const pospoint = &buf[pos]; - len = 0; - - /* Only check matches that will potentially beat the current maxlen. - * This is redundant with the index if match_maxlen is 0, but the - * added branch overhead to check if it == 0 seems to be worse. */ - if (pospoint[match_maxlen] != needlepoint[match_maxlen]) { - pos = hsi->index[pos]; - continue; - } - - for (len = 1; len < maxlen; len++) { - if (pospoint[len] != needlepoint[len]) break; - } - - if (len > match_maxlen) { - match_maxlen = len; - match_index = pos; - if (len == maxlen) { break; } /* won't find better */ - } - pos = hsi->index[pos]; - } -#else - for (int16_t pos=end - 1; pos - (int16_t)start >= 0; pos--) { - uint8_t * const pospoint = &buf[pos]; - if ((pospoint[match_maxlen] == needlepoint[match_maxlen]) - && (*pospoint == *needlepoint)) { - for (len=1; len cmp buf[%d] == 0x%02x against %02x (start %u)\n", - pos + len, pospoint[len], needlepoint[len], start); - } - if (pospoint[len] != needlepoint[len]) { break; } - } - if (len > match_maxlen) { - match_maxlen = len; - match_index = pos; - if (len == maxlen) { break; } /* don't keep searching */ - } - } - } -#endif - - const size_t break_even_point = - (1 + HEATSHRINK_ENCODER_WINDOW_BITS(hse) + - HEATSHRINK_ENCODER_LOOKAHEAD_BITS(hse)); - - /* Instead of comparing break_even_point against 8*match_maxlen, - * compare match_maxlen against break_even_point/8 to avoid - * overflow. Since MIN_WINDOW_BITS and MIN_LOOKAHEAD_BITS are 4 and - * 3, respectively, break_even_point/8 will always be at least 1. */ - if (match_maxlen > (break_even_point / 8)) { - LOG("-- best match: %u bytes at -%u\n", - match_maxlen, end - match_index); - *match_length = match_maxlen; - return end - match_index; - } - LOG("-- none found\n"); - return MATCH_NOT_FOUND; -} - -static uint8_t push_outgoing_bits(heatshrink_encoder *hse, output_info *oi) { - uint8_t count = 0; - uint8_t bits = 0; - if (hse->outgoing_bits_count > 8) { - count = 8; - bits = hse->outgoing_bits >> (hse->outgoing_bits_count - 8); - } else { - count = hse->outgoing_bits_count; - bits = hse->outgoing_bits; - } - - if (count > 0) { - LOG("-- pushing %d outgoing bits: 0x%02x\n", count, bits); - push_bits(hse, count, bits, oi); - hse->outgoing_bits_count -= count; - } - return count; -} - -/* Push COUNT (max 8) bits to the output buffer, which has room. - * Bytes are set from the lowest bits, up. */ -static void push_bits(heatshrink_encoder *hse, uint8_t count, uint8_t bits, - output_info *oi) { - ASSERT(count <= 8); - LOG("++ push_bits: %d bits, input of 0x%02x\n", count, bits); - - /* If adding a whole byte and at the start of a new output byte, - * just push it through whole and skip the bit IO loop. */ - if (count == 8 && hse->bit_index == 0x80) { - oi->buf[(*oi->output_size)++] = bits; - } else { - for (int i=count - 1; i>=0; i--) { - bool bit = bits & (1 << i); - if (bit) { hse->current_byte |= hse->bit_index; } - if (0) { - LOG(" -- setting bit %d at bit index 0x%02x, byte => 0x%02x\n", - bit ? 1 : 0, hse->bit_index, hse->current_byte); - } - hse->bit_index >>= 1; - if (hse->bit_index == 0x00) { - hse->bit_index = 0x80; - LOG(" > pushing byte 0x%02x\n", hse->current_byte); - oi->buf[(*oi->output_size)++] = hse->current_byte; - hse->current_byte = 0x00; - } - } - } -} - -static void push_literal_byte(heatshrink_encoder *hse, output_info *oi) { - uint16_t processed_offset = hse->match_scan_index - 1; - uint16_t input_offset = get_input_offset(hse) + processed_offset; - uint8_t c = hse->buffer[input_offset]; - LOG("-- yielded literal byte 0x%02x ('%c') from +%d\n", - c, isprint(c) ? c : '.', input_offset); - push_bits(hse, 8, c, oi); -} - -static void save_backlog(heatshrink_encoder *hse) { - size_t input_buf_sz = get_input_buffer_size(hse); - - uint16_t msi = hse->match_scan_index; - - /* Copy processed data to beginning of buffer, so it can be - * used for future matches. Don't bother checking whether the - * input is less than the maximum size, because if it isn't, - * we're done anyway. */ - uint16_t rem = input_buf_sz - msi; // unprocessed bytes - uint16_t shift_sz = input_buf_sz + rem; - - memmove(&hse->buffer[0], - &hse->buffer[input_buf_sz - rem], - shift_sz); - - hse->match_scan_index = 0; - hse->input_size -= input_buf_sz - rem; -} diff --git a/lib/heatshrink/heatshrink_encoder.h b/lib/heatshrink/heatshrink_encoder.h deleted file mode 100644 index e2ccb44c..00000000 --- a/lib/heatshrink/heatshrink_encoder.h +++ /dev/null @@ -1,109 +0,0 @@ -#ifndef HEATSHRINK_ENCODER_H -#define HEATSHRINK_ENCODER_H - -#include -#include -#include "heatshrink_common.h" -#include "heatshrink_config.h" - -typedef enum { - HSER_SINK_OK, /* data sunk into input buffer */ - HSER_SINK_ERROR_NULL=-1, /* NULL argument */ - HSER_SINK_ERROR_MISUSE=-2, /* API misuse */ -} HSE_sink_res; - -typedef enum { - HSER_POLL_EMPTY, /* input exhausted */ - HSER_POLL_MORE, /* poll again for more output */ - HSER_POLL_ERROR_NULL=-1, /* NULL argument */ - HSER_POLL_ERROR_MISUSE=-2, /* API misuse */ -} HSE_poll_res; - -typedef enum { - HSER_FINISH_DONE, /* encoding is complete */ - HSER_FINISH_MORE, /* more output remaining; use poll */ - HSER_FINISH_ERROR_NULL=-1, /* NULL argument */ -} HSE_finish_res; - -#if HEATSHRINK_DYNAMIC_ALLOC -#define HEATSHRINK_ENCODER_WINDOW_BITS(HSE) \ - ((HSE)->window_sz2) -#define HEATSHRINK_ENCODER_LOOKAHEAD_BITS(HSE) \ - ((HSE)->lookahead_sz2) -#define HEATSHRINK_ENCODER_INDEX(HSE) \ - ((HSE)->search_index) -struct hs_index { - uint16_t size; - int16_t index[]; -}; -#else -#define HEATSHRINK_ENCODER_WINDOW_BITS(_) \ - (HEATSHRINK_STATIC_WINDOW_BITS) -#define HEATSHRINK_ENCODER_LOOKAHEAD_BITS(_) \ - (HEATSHRINK_STATIC_LOOKAHEAD_BITS) -#define HEATSHRINK_ENCODER_INDEX(HSE) \ - (&(HSE)->search_index) -struct hs_index { - uint16_t size; - int16_t index[2 << HEATSHRINK_STATIC_WINDOW_BITS]; -}; -#endif - -typedef struct { - uint16_t input_size; /* bytes in input buffer */ - uint16_t match_scan_index; - uint16_t match_length; - uint16_t match_pos; - uint16_t outgoing_bits; /* enqueued outgoing bits */ - uint8_t outgoing_bits_count; - uint8_t flags; - uint8_t state; /* current state machine node */ - uint8_t current_byte; /* current byte of output */ - uint8_t bit_index; /* current bit index */ -#if HEATSHRINK_DYNAMIC_ALLOC - uint8_t window_sz2; /* 2^n size of window */ - uint8_t lookahead_sz2; /* 2^n size of lookahead */ -#if HEATSHRINK_USE_INDEX - struct hs_index *search_index; -#endif - /* input buffer and / sliding window for expansion */ - uint8_t* buffer; -#else - #if HEATSHRINK_USE_INDEX - struct hs_index search_index; - #endif - /* input buffer and / sliding window for expansion */ - uint8_t buffer[2 << HEATSHRINK_ENCODER_WINDOW_BITS(_)]; -#endif -} heatshrink_encoder; - -#if HEATSHRINK_DYNAMIC_ALLOC -/* Allocate a new encoder struct and its buffers. - * Returns NULL on error. */ -heatshrink_encoder *heatshrink_encoder_alloc(uint8_t* buffer, uint8_t window_sz2, - uint8_t lookahead_sz2); - -/* Free an encoder. */ -void heatshrink_encoder_free(heatshrink_encoder *hse); -#endif - -/* Reset an encoder. */ -void heatshrink_encoder_reset(heatshrink_encoder *hse); - -/* Sink up to SIZE bytes from IN_BUF into the encoder. - * INPUT_SIZE is set to the number of bytes actually sunk (in case a - * buffer was filled.). */ -HSE_sink_res heatshrink_encoder_sink(heatshrink_encoder *hse, - uint8_t *in_buf, size_t size, size_t *input_size); - -/* Poll for output from the encoder, copying at most OUT_BUF_SIZE bytes into - * OUT_BUF (setting *OUTPUT_SIZE to the actual amount copied). */ -HSE_poll_res heatshrink_encoder_poll(heatshrink_encoder *hse, - uint8_t *out_buf, size_t out_buf_size, size_t *output_size); - -/* Notify the encoder that the input stream is finished. - * If the return value is HSER_FINISH_MORE, there is still more output, so - * call heatshrink_encoder_poll and repeat. */ -HSE_finish_res heatshrink_encoder_finish(heatshrink_encoder *hse); - -#endif diff --git a/lib/misc.scons b/lib/misc.scons index 49b6b61d..b479851b 100644 --- a/lib/misc.scons +++ b/lib/misc.scons @@ -36,7 +36,6 @@ for lib in libs_recurse: sources += libenv.GlobRecursive("*.c*", lib) libs_plain = [ - "heatshrink", "nanopb", ] @@ -47,6 +46,12 @@ for lib in libs_plain: source=True, ) +sources += Glob( + "heatshrink/heatshrink_*.c*", + exclude=GLOB_FILE_EXCLUSION, + source=True, +) + lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources) libenv.Install("${LIB_DIST_DIR}", lib) Return("lib") diff --git a/firmware/targets/f7/furi_hal/furi_hal_compress.c b/lib/toolbox/compress.c similarity index 67% rename from firmware/targets/f7/furi_hal/furi_hal_compress.c rename to lib/toolbox/compress.c index 7e31dbbf..0d5e1c65 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_compress.c +++ b/lib/toolbox/compress.c @@ -1,115 +1,112 @@ -#include +#include "compress.h" #include #include #include -#define TAG "FuriHalCompress" +/** Defines encoder and decoder window size */ +#define COMPRESS_EXP_BUFF_SIZE_LOG (8u) -#define FURI_HAL_COMPRESS_ICON_ENCODED_BUFF_SIZE (2 * 512) -#define FURI_HAL_COMPRESS_ICON_DECODED_BUFF_SIZE (1024) +/** Defines encoder and decoder lookahead buffer size */ +#define COMPRESS_LOOKAHEAD_BUFF_SIZE_LOG (4u) -#define FURI_HAL_COMPRESS_EXP_BUFF_SIZE (1 << FURI_HAL_COMPRESS_EXP_BUFF_SIZE_LOG) +/** Buffer sizes for input and output data */ +#define COMPRESS_ICON_ENCODED_BUFF_SIZE (1024u) +#define COMPRESS_ICON_DECODED_BUFF_SIZE (1024u) typedef struct { uint8_t is_compressed; uint8_t reserved; uint16_t compressed_buff_size; -} FuriHalCompressHeader; +} CompressHeader; -typedef struct { - heatshrink_decoder* decoder; - uint8_t - compress_buff[FURI_HAL_COMPRESS_EXP_BUFF_SIZE + FURI_HAL_COMPRESS_ICON_ENCODED_BUFF_SIZE]; - uint8_t decoded_buff[FURI_HAL_COMPRESS_ICON_DECODED_BUFF_SIZE]; -} FuriHalCompressIcon; +_Static_assert(sizeof(CompressHeader) == 4, "Incorrect CompressHeader size"); -struct FuriHalCompress { - heatshrink_encoder* encoder; +struct CompressIcon { heatshrink_decoder* decoder; - uint8_t* compress_buff; - uint16_t compress_buff_size; + uint8_t decoded_buff[COMPRESS_ICON_DECODED_BUFF_SIZE]; }; -static FuriHalCompressIcon* icon_decoder; +CompressIcon* compress_icon_alloc() { + CompressIcon* instance = malloc(sizeof(CompressIcon)); + instance->decoder = heatshrink_decoder_alloc( + COMPRESS_ICON_ENCODED_BUFF_SIZE, + COMPRESS_EXP_BUFF_SIZE_LOG, + COMPRESS_LOOKAHEAD_BUFF_SIZE_LOG); + heatshrink_decoder_reset(instance->decoder); + memset(instance->decoded_buff, 0, sizeof(instance->decoded_buff)); -static void furi_hal_compress_reset(FuriHalCompress* compress) { - furi_assert(compress); - heatshrink_encoder_reset(compress->encoder); - heatshrink_decoder_reset(compress->decoder); - memset(compress->compress_buff, 0, compress->compress_buff_size); + return instance; } -void furi_hal_compress_icon_init() { - icon_decoder = malloc(sizeof(FuriHalCompressIcon)); - icon_decoder->decoder = heatshrink_decoder_alloc( - icon_decoder->compress_buff, - FURI_HAL_COMPRESS_ICON_ENCODED_BUFF_SIZE, - FURI_HAL_COMPRESS_EXP_BUFF_SIZE_LOG, - FURI_HAL_COMPRESS_LOOKAHEAD_BUFF_SIZE_LOG); - heatshrink_decoder_reset(icon_decoder->decoder); - memset(icon_decoder->decoded_buff, 0, sizeof(icon_decoder->decoded_buff)); - FURI_LOG_I(TAG, "Init OK"); +void compress_icon_free(CompressIcon* instance) { + furi_assert(instance); + heatshrink_decoder_free(instance->decoder); + free(instance); } -void furi_hal_compress_icon_decode(const uint8_t* icon_data, uint8_t** decoded_buff) { +void compress_icon_decode(CompressIcon* instance, const uint8_t* icon_data, uint8_t** decoded_buff) { + furi_assert(instance); furi_assert(icon_data); furi_assert(decoded_buff); - FuriHalCompressHeader* header = (FuriHalCompressHeader*)icon_data; + CompressHeader* header = (CompressHeader*)icon_data; if(header->is_compressed) { size_t data_processed = 0; heatshrink_decoder_sink( - icon_decoder->decoder, - (uint8_t*)&icon_data[4], + instance->decoder, + (uint8_t*)&icon_data[sizeof(CompressHeader)], header->compressed_buff_size, &data_processed); while(1) { HSD_poll_res res = heatshrink_decoder_poll( - icon_decoder->decoder, - icon_decoder->decoded_buff, - sizeof(icon_decoder->decoded_buff), + instance->decoder, + instance->decoded_buff, + sizeof(instance->decoded_buff), &data_processed); furi_assert((res == HSDR_POLL_EMPTY) || (res == HSDR_POLL_MORE)); if(res != HSDR_POLL_MORE) { break; } } - heatshrink_decoder_reset(icon_decoder->decoder); - memset(icon_decoder->compress_buff, 0, sizeof(icon_decoder->compress_buff)); - *decoded_buff = icon_decoder->decoded_buff; + heatshrink_decoder_reset(instance->decoder); + *decoded_buff = instance->decoded_buff; } else { *decoded_buff = (uint8_t*)&icon_data[1]; } } -FuriHalCompress* furi_hal_compress_alloc(uint16_t compress_buff_size) { - FuriHalCompress* compress = malloc(sizeof(FuriHalCompress)); - compress->compress_buff = malloc(compress_buff_size + FURI_HAL_COMPRESS_EXP_BUFF_SIZE); - compress->encoder = heatshrink_encoder_alloc( - compress->compress_buff, - FURI_HAL_COMPRESS_EXP_BUFF_SIZE_LOG, - FURI_HAL_COMPRESS_LOOKAHEAD_BUFF_SIZE_LOG); +struct Compress { + heatshrink_encoder* encoder; + heatshrink_decoder* decoder; +}; + +static void compress_reset(Compress* compress) { + furi_assert(compress); + heatshrink_encoder_reset(compress->encoder); + heatshrink_decoder_reset(compress->decoder); +} + +Compress* compress_alloc(uint16_t compress_buff_size) { + Compress* compress = malloc(sizeof(Compress)); + compress->encoder = + heatshrink_encoder_alloc(COMPRESS_EXP_BUFF_SIZE_LOG, COMPRESS_LOOKAHEAD_BUFF_SIZE_LOG); compress->decoder = heatshrink_decoder_alloc( - compress->compress_buff, - compress_buff_size, - FURI_HAL_COMPRESS_EXP_BUFF_SIZE_LOG, - FURI_HAL_COMPRESS_LOOKAHEAD_BUFF_SIZE_LOG); + compress_buff_size, COMPRESS_EXP_BUFF_SIZE_LOG, COMPRESS_LOOKAHEAD_BUFF_SIZE_LOG); return compress; } -void furi_hal_compress_free(FuriHalCompress* compress) { +void compress_free(Compress* compress) { furi_assert(compress); heatshrink_encoder_free(compress->encoder); heatshrink_decoder_free(compress->decoder); - free(compress->compress_buff); free(compress); } -bool furi_hal_compress_encode( - FuriHalCompress* compress, +bool compress_encode( + Compress* compress, uint8_t* data_in, size_t data_in_size, uint8_t* data_out, @@ -126,7 +123,7 @@ bool furi_hal_compress_encode( HSE_finish_res finish_res; bool encode_failed = false; size_t sunk = 0; - size_t res_buff_size = sizeof(FuriHalCompressHeader); + size_t res_buff_size = sizeof(CompressHeader); // Sink data to encoding buffer while((sunk < data_in_size) && !encode_failed) { @@ -174,7 +171,7 @@ bool furi_hal_compress_encode( bool result = true; // Write encoded data to output buffer if compression is efficient. Else - write header and original data if(!encode_failed && (res_buff_size < data_in_size + 1)) { - FuriHalCompressHeader header = { + CompressHeader header = { .is_compressed = 0x01, .reserved = 0x00, .compressed_buff_size = res_buff_size}; memcpy(data_out, &header, sizeof(header)); *data_res_size = res_buff_size; @@ -186,13 +183,13 @@ bool furi_hal_compress_encode( *data_res_size = 0; result = false; } - furi_hal_compress_reset(compress); + compress_reset(compress); return result; } -bool furi_hal_compress_decode( - FuriHalCompress* compress, +bool compress_decode( + Compress* compress, uint8_t* data_in, size_t data_in_size, uint8_t* data_out, @@ -212,11 +209,11 @@ bool furi_hal_compress_decode( size_t res_buff_size = 0; size_t poll_size = 0; - FuriHalCompressHeader* header = (FuriHalCompressHeader*)data_in; + CompressHeader* header = (CompressHeader*)data_in; if(header->is_compressed) { // Sink data to decoding buffer size_t compressed_size = header->compressed_buff_size; - size_t sunk = sizeof(FuriHalCompressHeader); + size_t sunk = sizeof(CompressHeader); while(sunk < compressed_size && !decode_failed) { sink_res = heatshrink_decoder_sink( compress->decoder, &data_in[sunk], compressed_size - sunk, &sink_size); @@ -258,7 +255,7 @@ bool furi_hal_compress_decode( } else { result = false; } - furi_hal_compress_reset(compress); + compress_reset(compress); return result; } diff --git a/lib/toolbox/compress.h b/lib/toolbox/compress.h new file mode 100644 index 00000000..a18551d7 --- /dev/null +++ b/lib/toolbox/compress.h @@ -0,0 +1,96 @@ +/** + * @file compress.h + * LZSS based compression HAL API + */ +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** Compress Icon control structure */ +typedef struct CompressIcon CompressIcon; + +/** Initialize icon compressor + * + * @return Compress Icon instance + */ +CompressIcon* compress_icon_alloc(); + +/** Free icon compressor + * + * @param instance The Compress Icon instance + */ +void compress_icon_free(CompressIcon* instance); + +/** Decompress icon + * + * @warning decoded_buff pointer set by this function is valid till next + * `compress_icon_decode` or `compress_icon_free` call + * + * @param instance The Compress Icon instance + * @param icon_data pointer to icon data + * @param[in] decoded_buff pointer to decoded buffer pointer + */ +void compress_icon_decode(CompressIcon* instance, const uint8_t* icon_data, uint8_t** decoded_buff); + +/** Compress control structure */ +typedef struct Compress Compress; + +/** Allocate encoder and decoder + * + * @param compress_buff_size size of decoder and encoder buffer to allocate + * + * @return Compress instance + */ +Compress* compress_alloc(uint16_t compress_buff_size); + +/** Free encoder and decoder + * + * @param compress Compress instance + */ +void compress_free(Compress* compress); + +/** Encode data + * + * @param compress Compress instance + * @param data_in pointer to input data + * @param data_in_size size of input data + * @param data_out maximum size of output data + * @param data_res_size pointer to result output data size + * + * @return true on success + */ +bool compress_encode( + Compress* compress, + uint8_t* data_in, + size_t data_in_size, + uint8_t* data_out, + size_t data_out_size, + size_t* data_res_size); + +/** Decode data + * + * @param compress Compress instance + * @param data_in pointer to input data + * @param data_in_size size of input data + * @param data_out maximum size of output data + * @param data_res_size pointer to result output data size + * + * @return true on success + */ +bool compress_decode( + Compress* compress, + uint8_t* data_in, + size_t data_in_size, + uint8_t* data_out, + size_t data_out_size, + size_t* data_res_size); + +#ifdef __cplusplus +} +#endif From 9a14699aa062590744acd8d3fb65476308c26a42 Mon Sep 17 00:00:00 2001 From: Shukai Ni Date: Wed, 22 Mar 2023 16:19:07 -0400 Subject: [PATCH 32/32] Fix relative links in .md files (#2528) Fixed some incorrect relative link, which caused 404 error when viewing in GitHub. --- documentation/UnitTests.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/documentation/UnitTests.md b/documentation/UnitTests.md index d38d4c4b..4717daa8 100644 --- a/documentation/UnitTests.md +++ b/documentation/UnitTests.md @@ -17,11 +17,11 @@ To run the unit tests, follow these steps: 1. Compile the firmware with the tests enabled: `./fbt FIRMWARE_APP_SET=unit_tests`. 2. Flash the firmware using your preferred method. -3. Copy the [assets/unit_tests](assets/unit_tests) folder to the root of your Flipper Zero's SD card. +3. Copy the [assets/unit_tests](/assets/unit_tests) folder to the root of your Flipper Zero's SD card. 4. Launch the CLI session and run the `unit_tests` command. **NOTE:** To run a particular test (and skip all others), specify its name as the command argument. -See [test_index.c](applications/debug/unit_tests/test_index.c) for the complete list of test names. +See [test_index.c](/applications/debug/unit_tests/test_index.c) for the complete list of test names. ## Adding unit tests @@ -29,7 +29,7 @@ See [test_index.c](applications/debug/unit_tests/test_index.c) for the complete #### Entry point -The common entry point for all tests is the [unit_tests](applications/debug/unit_tests) application. Test-specific code is placed into an arbitrarily named subdirectory and is then called from the [test_index.c](applications/debug/unit_tests/test_index.c) source file. +The common entry point for all tests is the [unit_tests](/applications/debug/unit_tests) application. Test-specific code is placed into an arbitrarily named subdirectory and is then called from the [test_index.c](/applications/debug/unit_tests/test_index.c) source file. #### Test assets @@ -42,10 +42,10 @@ Some unit tests require external data in order to function. These files (commonl Each infrared protocol has a corresponding set of unit tests, so it makes sense to implement one when adding support for a new protocol. To add unit tests for your protocol, follow these steps: -1. Create a file named `test_.irtest` in the [assets](assets/unit_tests/infrared) directory. +1. Create a file named `test_.irtest` in the [assets](/assets/unit_tests/infrared) directory. 2. Fill it with the test data (more on it below). -3. Add the test code to [infrared_test.c](applications/debug/unit_tests/infrared/infrared_test.c). -4. Update the [assets](assets/unit_tests/infrared) on your Flipper Zero and run the tests to see if they pass. +3. Add the test code to [infrared_test.c](/applications/debug/unit_tests/infrared/infrared_test.c). +4. Update the [assets](/assets/unit_tests/infrared) on your Flipper Zero and run the tests to see if they pass. ##### Test data format