diff --git a/.gitmodules b/.gitmodules index a97e0933a..56368cd58 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 ca1b2b572..6b22aed76 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 81ff67790..609b36af4 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.GetProjectDirName() +dist_dir_name = distenv.GetProjectDirName() +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/ReadMe.md b/applications/ReadMe.md index ddbe5d65b..598c8b2ad 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/applications/debug/usb_mouse/application.fam b/applications/debug/usb_mouse/application.fam index bf555c671..38ba55425 100644 --- a/applications/debug/usb_mouse/application.fam +++ b/applications/debug/usb_mouse/application.fam @@ -7,6 +7,6 @@ App( requires=["gui"], stack_size=1 * 1024, order=60, - fap_icon="../../plugins/mousejacker/mouse_10px.png", + fap_icon="../../external/mousejacker/mouse_10px.png", fap_category="Debug", ) diff --git a/applications/examples/application.fam b/applications/examples/application.fam index 8556714c9..347411fac 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 000000000..a6e3c2078 --- /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 000000000..acc5903ad --- /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 000000000..12eba01c1 --- /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 000000000..156219353 --- /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 000000000..0b774dad2 --- /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 000000000..e24bc47bf --- /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 000000000..42b3a1860 --- /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 000000000..7035b79f5 --- /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 000000000..d0db44c4a --- /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 000000000..aacfb8c18 --- /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 000000000..17cc8be5f --- /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 000000000..d40c0dde2 --- /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 000000000..f27b0a084 --- /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 000000000..bf0ab50b4 --- /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 000000000..f0b2f726d --- /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 000000000..e8b5a22d6 --- /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 index 5d5824e29..12dc1cc1a 100644 --- a/applications/external/application.fam +++ b/applications/external/application.fam @@ -1,10 +1,6 @@ +# Placeholder App( - appid="basic_plugins", - name="Basic applications for plug-in menu", + appid="external_apps", + name="External apps bundle", apptype=FlipperAppType.METAPACKAGE, - provides=[ - "music_player", - "music_beeper", - "snake_game", - ], ) diff --git a/applications/external/arkanoid/application.fam b/applications/external/arkanoid/application.fam index bc202ff00..8440f3a89 100644 --- a/applications/external/arkanoid/application.fam +++ b/applications/external/arkanoid/application.fam @@ -3,7 +3,6 @@ App( name="Arkanoid", apptype=FlipperAppType.EXTERNAL, entry_point="arkanoid_game_app", - cdefines=["APP_ARKANOID_GAME"], requires=["gui"], stack_size=1 * 1024, order=20, diff --git a/applications/external/blackjack/application.fam b/applications/external/blackjack/application.fam index 6ca8add37..04b214e3c 100644 --- a/applications/external/blackjack/application.fam +++ b/applications/external/blackjack/application.fam @@ -3,7 +3,6 @@ App( name="BlackJack", apptype=FlipperAppType.EXTERNAL, entry_point="blackjack_app", - cdefines=["APP_BLACKJACK"], requires=["gui", "storage", "canvas"], stack_size=2 * 1024, order=30, 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/external/doom/application.fam b/applications/external/doom/application.fam index d9ae4d67d..4ff7b12f3 100644 --- a/applications/external/doom/application.fam +++ b/applications/external/doom/application.fam @@ -3,7 +3,6 @@ App( name="DOOM", apptype=FlipperAppType.EXTERNAL, entry_point="doom_app", - cdefines=["APP_DOOM_GAME"], requires=[ "gui", "music_player", diff --git a/applications/external/dtmf_dolphin/application.fam b/applications/external/dtmf_dolphin/application.fam index 0727f5f52..460f6ded6 100644 --- a/applications/external/dtmf_dolphin/application.fam +++ b/applications/external/dtmf_dolphin/application.fam @@ -3,7 +3,6 @@ App( name="DTMF Dolphin", apptype=FlipperAppType.EXTERNAL, entry_point="dtmf_dolphin_app", - cdefines=["DTMF_DOLPHIN"], requires=[ "storage", "gui", diff --git a/applications/external/esp8266_deauth/application.fam b/applications/external/esp8266_deauth/application.fam index b289bc88e..7718e1458 100644 --- a/applications/external/esp8266_deauth/application.fam +++ b/applications/external/esp8266_deauth/application.fam @@ -3,7 +3,6 @@ App( name="[ESP8266] Deauther", apptype=FlipperAppType.EXTERNAL, entry_point="esp8266_deauth_app", - cdefines=["APP_ESP8266_deauth"], requires=["gui"], stack_size=2 * 1024, order=20, diff --git a/applications/external/esp8266_deauth/esp8266_deauth.c b/applications/external/esp8266_deauth/esp8266_deauth.c index d32ca4c18..fc165b932 100644 --- a/applications/external/esp8266_deauth/esp8266_deauth.c +++ b/applications/external/esp8266_deauth/esp8266_deauth.c @@ -352,7 +352,12 @@ int32_t esp8266_deauth_app(void* p) { #else #if ENABLE_MODULE_POWER app->m_context = Initializing; - furi_hal_power_enable_otg(); + uint8_t attempts = 0; + while(!furi_hal_power_is_otg_enabled() && attempts++ < 5) { + furi_hal_power_enable_otg(); + furi_delay_ms(10); + } + furi_delay_ms(200); #else app->m_context = ModuleActive; #endif @@ -409,7 +414,11 @@ int32_t esp8266_deauth_app(void* p) { app->m_wifiDeauthModuleAttached = true; #if ENABLE_MODULE_POWER app->m_context = Initializing; - furi_hal_power_enable_otg(); + uint8_t attempts2 = 0; + while(!furi_hal_power_is_otg_enabled() && attempts2++ < 3) { + furi_hal_power_enable_otg(); + furi_delay_ms(10); + } #else app->m_context = ModuleActive; #endif @@ -533,7 +542,9 @@ int32_t esp8266_deauth_app(void* p) { DEAUTH_APP_LOG_I("App freed"); #if ENABLE_MODULE_POWER - furi_hal_power_disable_otg(); + if(furi_hal_power_is_otg_enabled()) { + furi_hal_power_disable_otg(); + } #endif return 0; diff --git a/applications/external/flappy_bird/application.fam b/applications/external/flappy_bird/application.fam index 0912178ca..f9fe85fa2 100644 --- a/applications/external/flappy_bird/application.fam +++ b/applications/external/flappy_bird/application.fam @@ -3,7 +3,6 @@ App( name="Flappy Bird", apptype=FlipperAppType.EXTERNAL, entry_point="flappy_game_app", - cdefines=["APP_FLAPPY_GAME"], requires=["gui"], stack_size=4 * 1024, order=100, diff --git a/applications/external/flipfrid/application.fam b/applications/external/flipfrid/application.fam index fc089de48..e2482e0d1 100644 --- a/applications/external/flipfrid/application.fam +++ b/applications/external/flipfrid/application.fam @@ -3,7 +3,6 @@ App( name="RFID Fuzzer", apptype=FlipperAppType.EXTERNAL, entry_point="flipfrid_start", - cdefines=["APP_FLIP_FRID"], requires=["gui", "storage", "dialogs", "input", "notification"], stack_size=1 * 1024, order=180, diff --git a/applications/external/flipper_i2ctools/application.fam b/applications/external/flipper_i2ctools/application.fam index 44138691d..3090244b5 100644 --- a/applications/external/flipper_i2ctools/application.fam +++ b/applications/external/flipper_i2ctools/application.fam @@ -3,7 +3,6 @@ App( name="[GPIO] i2c Tools", apptype=FlipperAppType.EXTERNAL, entry_point="i2ctools_app", - cdefines=["APP_I2CTOOLS"], requires=["gui"], stack_size=2 * 1024, order=175, diff --git a/applications/external/game15/application.fam b/applications/external/game15/application.fam index ab00316c1..969cb536a 100644 --- a/applications/external/game15/application.fam +++ b/applications/external/game15/application.fam @@ -3,7 +3,6 @@ App( name="Game 15", apptype=FlipperAppType.EXTERNAL, entry_point="game15_app", - cdefines=["APP_GAME15"], requires=["gui"], stack_size=1 * 1024, fap_icon="game15_10px.png", diff --git a/applications/external/game_2048/application.fam b/applications/external/game_2048/application.fam index 0440d0864..89210ca23 100644 --- a/applications/external/game_2048/application.fam +++ b/applications/external/game_2048/application.fam @@ -4,7 +4,6 @@ App( name="2048", apptype=FlipperAppType.EXTERNAL, entry_point="game_2048_app", - cdefines=["APP_GAME_2048"], requires=[ "gui", ], diff --git a/applications/external/gps_nmea_uart/application.fam b/applications/external/gps_nmea_uart/application.fam index a5fdb4360..e02785744 100644 --- a/applications/external/gps_nmea_uart/application.fam +++ b/applications/external/gps_nmea_uart/application.fam @@ -3,7 +3,6 @@ App( name="[NMEA] GPS", apptype=FlipperAppType.EXTERNAL, entry_point="gps_app", - cdefines=["APP_GPS"], requires=["gui"], stack_size=1 * 1024, order=35, diff --git a/applications/external/hc_sr04/application.fam b/applications/external/hc_sr04/application.fam index 85b6da2df..351f4e03d 100644 --- a/applications/external/hc_sr04/application.fam +++ b/applications/external/hc_sr04/application.fam @@ -3,7 +3,6 @@ App( name="[HC-SR] Dist. Sensor", apptype=FlipperAppType.EXTERNAL, entry_point="hc_sr04_app", - cdefines=["APP_HC_SR04"], requires=[ "gui", ], diff --git a/applications/external/hex_viewer/application.fam b/applications/external/hex_viewer/application.fam index 30c31ba76..96bf1a387 100644 --- a/applications/external/hex_viewer/application.fam +++ b/applications/external/hex_viewer/application.fam @@ -3,7 +3,6 @@ App( name="Hex Viewer", apptype=FlipperAppType.EXTERNAL, entry_point="hex_viewer_app", - cdefines=["APP_HEX_VIEWER"], requires=[ "gui", "dialogs", diff --git a/applications/external/ibtn_fuzzer/application.fam b/applications/external/ibtn_fuzzer/application.fam index b27f47ba9..00c244c41 100644 --- a/applications/external/ibtn_fuzzer/application.fam +++ b/applications/external/ibtn_fuzzer/application.fam @@ -3,7 +3,6 @@ App( name="iButton Fuzzer", apptype=FlipperAppType.EXTERNAL, entry_point="ibtnfuzzer_start", - cdefines=["APP_IBTN_FUZZ"], requires=["gui", "storage", "dialogs", "input", "notification"], stack_size=1 * 1024, order=15, diff --git a/applications/external/lightmeter/application.fam b/applications/external/lightmeter/application.fam index 8cd90ee26..7df664517 100644 --- a/applications/external/lightmeter/application.fam +++ b/applications/external/lightmeter/application.fam @@ -3,7 +3,6 @@ App( name="[BH1750] Lightmeter", apptype=FlipperAppType.EXTERNAL, entry_point="lightmeter_app", - cdefines=["APP_LIGHTMETER"], requires=[ "gui", ], diff --git a/applications/external/metronome/application.fam b/applications/external/metronome/application.fam index 32588d06e..1a99825a8 100644 --- a/applications/external/metronome/application.fam +++ b/applications/external/metronome/application.fam @@ -3,7 +3,6 @@ App( name="Metronome", apptype=FlipperAppType.EXTERNAL, entry_point="metronome_app", - cdefines=["APP_METRONOME"], requires=[ "gui", ], diff --git a/applications/external/minesweeper/application.fam b/applications/external/minesweeper/application.fam index 405cb0d1c..c0211282c 100644 --- a/applications/external/minesweeper/application.fam +++ b/applications/external/minesweeper/application.fam @@ -3,7 +3,6 @@ App( name="Minesweeper", apptype=FlipperAppType.EXTERNAL, entry_point="minesweeper_app", - cdefines=["APP_MINESWEEPER"], requires=["gui"], stack_size=8 * 1024, fap_category="Games", diff --git a/applications/external/morse_code/application.fam b/applications/external/morse_code/application.fam index d5a2e6c9a..d6898100d 100644 --- a/applications/external/morse_code/application.fam +++ b/applications/external/morse_code/application.fam @@ -3,7 +3,6 @@ App( name="Morse Code", apptype=FlipperAppType.EXTERNAL, entry_point="morse_code_app", - cdefines=["APP_MORSE_CODE"], requires=[ "gui", ], diff --git a/applications/external/mousejacker/application.fam b/applications/external/mousejacker/application.fam index 725e81ae8..f65b06cc2 100644 --- a/applications/external/mousejacker/application.fam +++ b/applications/external/mousejacker/application.fam @@ -3,7 +3,6 @@ App( name="[NRF24] Mouse Jacker", apptype=FlipperAppType.EXTERNAL, entry_point="mousejacker_app", - cdefines=["APP_MOUSEJACKER"], requires=[ "gui", "dialogs", diff --git a/applications/external/multi_converter/application.fam b/applications/external/multi_converter/application.fam index e0abe3ed4..106ddb127 100644 --- a/applications/external/multi_converter/application.fam +++ b/applications/external/multi_converter/application.fam @@ -3,7 +3,6 @@ App( name="Multi Converter", apptype=FlipperAppType.EXTERNAL, entry_point="multi_converter_app", - cdefines=["APP_DEC_HEX_CONVERTER"], requires=["gui"], stack_size=1 * 1024, order=160, diff --git a/applications/external/music_player/application.fam b/applications/external/music_player/application.fam index 1072f1524..1a8f5505b 100644 --- a/applications/external/music_player/application.fam +++ b/applications/external/music_player/application.fam @@ -3,7 +3,6 @@ App( name="Music Player", apptype=FlipperAppType.EXTERNAL, entry_point="music_player_app", - cdefines=["APP_MUSIC_PLAYER"], requires=[ "gui", "dialogs", @@ -12,8 +11,8 @@ App( stack_size=2 * 1024, order=45, fap_icon="icons/music_10px.png", - fap_icon_assets="icons", fap_category="Music", + fap_icon_assets="icons", ) App( diff --git a/applications/external/nrfsniff/application.fam b/applications/external/nrfsniff/application.fam index e55ab3e9c..0330ab11a 100644 --- a/applications/external/nrfsniff/application.fam +++ b/applications/external/nrfsniff/application.fam @@ -3,7 +3,6 @@ App( name="[NRF24] Sniffer", apptype=FlipperAppType.EXTERNAL, entry_point="nrfsniff_app", - cdefines=["APP_NRFSNIFF"], requires=["gui"], stack_size=2 * 1024, order=60, diff --git a/applications/external/picopass/helpers/iclass_elite_dict.c b/applications/external/picopass/helpers/iclass_elite_dict.c index e8c13dd1d..f92dce0aa 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 e5ec8dfcb..150cd1b76 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 d141bdf6b..2f7aba35f 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/applications/external/pocsag_pager/application.fam b/applications/external/pocsag_pager/application.fam index b0d1bedd1..3cef05374 100644 --- a/applications/external/pocsag_pager/application.fam +++ b/applications/external/pocsag_pager/application.fam @@ -3,7 +3,6 @@ App( name="POCSAG Pager", apptype=FlipperAppType.EXTERNAL, entry_point="pocsag_pager_app", - cdefines=["APP_POCSAG_PAGER"], requires=["gui"], stack_size=4 * 1024, order=50, diff --git a/applications/external/pocsag_pager/pocsag_pager_app.c b/applications/external/pocsag_pager/pocsag_pager_app.c index 123b3ee9d..f5e989fba 100644 --- a/applications/external/pocsag_pager/pocsag_pager_app.c +++ b/applications/external/pocsag_pager/pocsag_pager_app.c @@ -124,6 +124,11 @@ POCSAGPagerApp* pocsag_pager_app_alloc() { // Enable power for External CC1101 if it is connected furi_hal_subghz_enable_ext_power(); + // Auto switch to internal radio if external radio is not available + furi_delay_ms(15); + if(!furi_hal_subghz_check_radio()) { + furi_hal_subghz_set_radio_type(SubGhzRadioInternal); + } furi_hal_power_suppress_charge_enter(); diff --git a/applications/external/protoview/app.c b/applications/external/protoview/app.c index a4dab9f40..678c9d75f 100644 --- a/applications/external/protoview/app.c +++ b/applications/external/protoview/app.c @@ -169,6 +169,11 @@ ProtoViewApp* protoview_app_alloc() { // Enable power for External CC1101 if it is connected furi_hal_subghz_enable_ext_power(); + // Auto switch to internal radio if external radio is not available + furi_delay_ms(15); + if(!furi_hal_subghz_check_radio()) { + furi_hal_subghz_set_radio_type(SubGhzRadioInternal); + } furi_hal_power_suppress_charge_enter(); app->running = 1; diff --git a/applications/external/protoview/application.fam b/applications/external/protoview/application.fam index 22ad2d628..329a95c8a 100644 --- a/applications/external/protoview/application.fam +++ b/applications/external/protoview/application.fam @@ -3,7 +3,6 @@ App( name="ProtoView", apptype=FlipperAppType.EXTERNAL, entry_point="protoview_app_entry", - cdefines=["APP_PROTOVIEW"], requires=["gui"], stack_size=8 * 1024, order=50, diff --git a/applications/external/sentry_safe/application.fam b/applications/external/sentry_safe/application.fam index 2eb43f4aa..28d1da04f 100644 --- a/applications/external/sentry_safe/application.fam +++ b/applications/external/sentry_safe/application.fam @@ -3,7 +3,6 @@ App( name="[GPIO] Sentry Safe", apptype=FlipperAppType.EXTERNAL, entry_point="sentry_safe_app", - cdefines=["APP_SENTRY_SAFE"], requires=["gui"], stack_size=1 * 1024, order=40, diff --git a/applications/external/signal_generator/application.fam b/applications/external/signal_generator/application.fam index 133319cf4..df2dd2559 100644 --- a/applications/external/signal_generator/application.fam +++ b/applications/external/signal_generator/application.fam @@ -3,7 +3,6 @@ App( name="[GPIO] Signal Generator", apptype=FlipperAppType.EXTERNAL, entry_point="signal_gen_app", - cdefines=["APP_SIGNAL_GEN"], requires=["gui"], stack_size=1 * 1024, order=50, diff --git a/applications/external/snake_game/application.fam b/applications/external/snake_game/application.fam index 573f5c0b3..021ab4900 100644 --- a/applications/external/snake_game/application.fam +++ b/applications/external/snake_game/application.fam @@ -3,7 +3,6 @@ App( name="Snake Game", apptype=FlipperAppType.EXTERNAL, entry_point="snake_game_app", - cdefines=["APP_SNAKE_GAME"], requires=["gui"], stack_size=1 * 1024, order=210, diff --git a/applications/external/solitaire/application.fam b/applications/external/solitaire/application.fam index 4011c343e..299b4a2dc 100644 --- a/applications/external/solitaire/application.fam +++ b/applications/external/solitaire/application.fam @@ -3,7 +3,6 @@ App( name="Solitaire", apptype=FlipperAppType.EXTERNAL, entry_point="solitaire_app", - cdefines=["APP_SOLITAIRE"], requires=["gui", "storage", "canvas"], stack_size=2 * 1024, order=30, diff --git a/applications/external/spectrum_analyzer/application.fam b/applications/external/spectrum_analyzer/application.fam index 344c2244f..286aa64ba 100644 --- a/applications/external/spectrum_analyzer/application.fam +++ b/applications/external/spectrum_analyzer/application.fam @@ -3,7 +3,6 @@ App( name="Spectrum Analyzer", apptype=FlipperAppType.EXTERNAL, entry_point="spectrum_analyzer_app", - cdefines=["APP_SPECTRUM_ANALYZER"], requires=["gui"], stack_size=2 * 1024, order=12, diff --git a/applications/external/spectrum_analyzer/spectrum_analyzer.c b/applications/external/spectrum_analyzer/spectrum_analyzer.c index 99c12adf7..d38c6bf23 100644 --- a/applications/external/spectrum_analyzer/spectrum_analyzer.c +++ b/applications/external/spectrum_analyzer/spectrum_analyzer.c @@ -405,6 +405,11 @@ int32_t spectrum_analyzer_app(void* p) { // Enable power for External CC1101 if it is connected furi_hal_subghz_enable_ext_power(); + // Auto switch to internal radio if external radio is not available + furi_delay_ms(15); + if(!furi_hal_subghz_check_radio()) { + furi_hal_subghz_set_radio_type(SubGhzRadioInternal); + } furi_hal_power_suppress_charge_enter(); diff --git a/applications/external/subbrute b/applications/external/subbrute new file mode 160000 index 000000000..f9a4508e5 --- /dev/null +++ b/applications/external/subbrute @@ -0,0 +1 @@ +Subproject commit f9a4508e508393ce064179bc83b09e4516843400 diff --git a/applications/external/subghz_playlist/application.fam b/applications/external/subghz_playlist/application.fam index 06357e24b..e137cdb39 100644 --- a/applications/external/subghz_playlist/application.fam +++ b/applications/external/subghz_playlist/application.fam @@ -3,7 +3,6 @@ App( name="Sub-GHz Playlist", apptype=FlipperAppType.EXTERNAL, entry_point="playlist_app", - cdefines=["APP_PLAYLIST"], requires=["storage", "gui", "dialogs", "subghz"], stack_size=2 * 1024, order=14, diff --git a/applications/external/subghz_playlist/playlist.c b/applications/external/subghz_playlist/playlist.c index 1b16b1749..e17526b84 100644 --- a/applications/external/subghz_playlist/playlist.c +++ b/applications/external/subghz_playlist/playlist.c @@ -713,6 +713,11 @@ int32_t playlist_app(void* p) { // Enable power for External CC1101 if it is connected furi_hal_subghz_enable_ext_power(); + // Auto switch to internal radio if external radio is not available + furi_delay_ms(15); + if(!furi_hal_subghz_check_radio()) { + furi_hal_subghz_set_radio_type(SubGhzRadioInternal); + } furi_hal_power_suppress_charge_enter(); diff --git a/applications/external/subghz_remote/subghz_remote_app.c b/applications/external/subghz_remote/subghz_remote_app.c index 1ee4b53ed..120b7d4ed 100644 --- a/applications/external/subghz_remote/subghz_remote_app.c +++ b/applications/external/subghz_remote/subghz_remote_app.c @@ -22,6 +22,7 @@ #include #include #include +#include #define SUBREMOTEMAP_FOLDER "/ext/subghz/remote" #define SUBREMOTEMAP_EXTENSION ".txt" @@ -491,6 +492,7 @@ void subghz_remote_tx_stop(SubGHzRemote* app) { alutech_reset_original_btn(); nice_flors_reset_original_btn(); somfy_telis_reset_original_btn(); + secplus2_reset_original_btn(); star_line_reset_mfname(); star_line_reset_kl_type(); } @@ -738,6 +740,11 @@ SubGHzRemote* subghz_remote_alloc(void) { // Enable power for External CC1101 if it is connected furi_hal_subghz_enable_ext_power(); + // Auto switch to internal radio if external radio is not available + furi_delay_ms(15); + if(!furi_hal_subghz_check_radio()) { + furi_hal_subghz_set_radio_type(SubGhzRadioInternal); + } furi_hal_power_suppress_charge_enter(); diff --git a/applications/external/swd_probe/application.fam b/applications/external/swd_probe/application.fam index 2a66f0054..065ee8769 100644 --- a/applications/external/swd_probe/application.fam +++ b/applications/external/swd_probe/application.fam @@ -3,7 +3,6 @@ App( name="[SWD] Probe", apptype=FlipperAppType.EXTERNAL, entry_point="swd_probe_app_main", - cdefines=["APP_SWD_PROBE"], requires=["notification", "gui", "storage", "dialogs", "cli"], stack_size=2 * 1024, order=10, diff --git a/applications/external/tetris_game/application.fam b/applications/external/tetris_game/application.fam index 52f5588cb..21c829e99 100644 --- a/applications/external/tetris_game/application.fam +++ b/applications/external/tetris_game/application.fam @@ -3,7 +3,6 @@ App( name="Tetris", apptype=FlipperAppType.EXTERNAL, entry_point="tetris_game_app", - cdefines=["APP_TETRIS_GAME"], requires=["gui"], stack_size=2 * 1024, order=240, diff --git a/applications/external/text_viewer/application.fam b/applications/external/text_viewer/application.fam index dcd573c9d..518626f41 100644 --- a/applications/external/text_viewer/application.fam +++ b/applications/external/text_viewer/application.fam @@ -3,7 +3,6 @@ App( name="Text Viewer", apptype=FlipperAppType.EXTERNAL, entry_point="text_viewer_app", - cdefines=["APP_TEXT_VIEWER"], requires=[ "gui", "dialogs", diff --git a/applications/external/tictactoe_game/application.fam b/applications/external/tictactoe_game/application.fam index 2ad09f11b..09ee2eba5 100644 --- a/applications/external/tictactoe_game/application.fam +++ b/applications/external/tictactoe_game/application.fam @@ -3,7 +3,6 @@ App( name="Tic Tac Toe", apptype=FlipperAppType.EXTERNAL, entry_point="tictactoe_game_app", - cdefines=["APP_TICTACTOE_GAME"], requires=["gui"], stack_size=1 * 1024, order=250, diff --git a/applications/external/totp/application.fam b/applications/external/totp/application.fam index f6a61f119..432f202c6 100644 --- a/applications/external/totp/application.fam +++ b/applications/external/totp/application.fam @@ -3,7 +3,6 @@ App( name="Authenticator", apptype=FlipperAppType.EXTERNAL, entry_point="totp_app", - cdefines=["APP_TOTP"], requires=["gui", "cli", "dialogs", "storage", "input", "notification"], stack_size=2 * 1024, order=20, diff --git a/applications/external/uart_terminal/application.fam b/applications/external/uart_terminal/application.fam index a36960ea4..530baf2fc 100644 --- a/applications/external/uart_terminal/application.fam +++ b/applications/external/uart_terminal/application.fam @@ -3,7 +3,6 @@ App( name="[GPIO] UART Terminal", apptype=FlipperAppType.EXTERNAL, entry_point="uart_terminal_app", - cdefines=["APP_UART_TERMINAL"], requires=["gui"], stack_size=1 * 1024, order=90, diff --git a/applications/external/unitemp/application.fam b/applications/external/unitemp/application.fam index d975da5ff..b0e0aa6ee 100644 --- a/applications/external/unitemp/application.fam +++ b/applications/external/unitemp/application.fam @@ -3,7 +3,6 @@ App( name="[GPIO] Unitemp", apptype=FlipperAppType.EXTERNAL, entry_point="unitemp_app", - cdefines=["UNITEMP_APP"], requires=[ "gui", ], diff --git a/applications/external/wav_player/application.fam b/applications/external/wav_player/application.fam index 4040ed159..007a47694 100644 --- a/applications/external/wav_player/application.fam +++ b/applications/external/wav_player/application.fam @@ -3,7 +3,6 @@ App( name="WAV Player", apptype=FlipperAppType.EXTERNAL, entry_point="wav_player_app", - cdefines=["APP_WAV_PLAYER"], stack_size=4 * 1024, order=46, fap_icon="wav_10px.png", diff --git a/applications/external/weather_station/application.fam b/applications/external/weather_station/application.fam index 28a425bdc..8dcaa1259 100644 --- a/applications/external/weather_station/application.fam +++ b/applications/external/weather_station/application.fam @@ -4,7 +4,6 @@ App( 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/external/weather_station/weather_station_app.c b/applications/external/weather_station/weather_station_app.c index ffa569f20..a3135a6b0 100644 --- a/applications/external/weather_station/weather_station_app.c +++ b/applications/external/weather_station/weather_station_app.c @@ -107,6 +107,11 @@ WeatherStationApp* weather_station_app_alloc() { // Enable power for External CC1101 if it is connected furi_hal_subghz_enable_ext_power(); + // Auto switch to internal radio if external radio is not available + furi_delay_ms(15); + if(!furi_hal_subghz_check_radio()) { + furi_hal_subghz_set_radio_type(SubGhzRadioInternal); + } furi_hal_power_suppress_charge_enter(); diff --git a/applications/external/wifi_marauder_companion/application.fam b/applications/external/wifi_marauder_companion/application.fam index 049d66045..5d6c068a9 100644 --- a/applications/external/wifi_marauder_companion/application.fam +++ b/applications/external/wifi_marauder_companion/application.fam @@ -3,7 +3,6 @@ App( name="[ESP32] WiFi Marauder", apptype=FlipperAppType.EXTERNAL, entry_point="wifi_marauder_app", - cdefines=["APP_WIFI_MARAUDER"], requires=["gui"], stack_size=1 * 1024, order=90, diff --git a/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_console_output.c b/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_console_output.c index 8b9984dbf..77beb733c 100644 --- a/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_console_output.c +++ b/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_console_output.c @@ -14,10 +14,26 @@ void wifi_marauder_console_output_handle_rx_data_cb(uint8_t* buf, size_t len, vo // Null-terminate buf and append to text box store buf[len] = '\0'; furi_string_cat_printf(app->text_box_store, "%s", buf); - view_dispatcher_send_custom_event(app->view_dispatcher, WifiMarauderEventRefreshConsoleOutput); } +void wifi_marauder_console_output_handle_rx_packets_cb(uint8_t* buf, size_t len, void* context) { + furi_assert(context); + WifiMarauderApp* app = context; + + // If it is a sniff function, open the pcap file for recording + if(strncmp("sniff", app->selected_tx_string, strlen("sniff")) == 0 && !app->is_writing) { + app->is_writing = true; + if(!app->capture_file || !storage_file_is_open(app->capture_file)) { + wifi_marauder_create_pcap_file(app); + } + } + + if(app->is_writing) { + storage_file_write(app->capture_file, buf, len); + } +} + void wifi_marauder_scene_console_output_on_enter(void* context) { WifiMarauderApp* app = context; @@ -33,8 +49,8 @@ void wifi_marauder_scene_console_output_on_enter(void* context) { furi_string_reset(app->text_box_store); app->text_box_store_strlen = 0; if(0 == strncmp("help", app->selected_tx_string, strlen("help"))) { - const char* help_msg = - "Marauder companion v0.3.0\nFor app support/feedback,\nreach out to me:\n@cococode#6011 (discord)\n0xchocolate (github)\n"; + const char* help_msg = "Marauder companion " WIFI_MARAUDER_APP_VERSION + "\nby @0xchocolate\nmodified by @tcpassos\n"; furi_string_cat_str(app->text_box_store, help_msg); app->text_box_store_strlen += strlen(help_msg); } @@ -54,7 +70,11 @@ void wifi_marauder_scene_console_output_on_enter(void* context) { // Register callback to receive data wifi_marauder_uart_set_handle_rx_data_cb( - app->uart, wifi_marauder_console_output_handle_rx_data_cb); // setup callback for rx thread + app->uart, + wifi_marauder_console_output_handle_rx_data_cb); // setup callback for general log rx thread + wifi_marauder_uart_set_handle_rx_data_cb( + app->lp_uart, + wifi_marauder_console_output_handle_rx_packets_cb); // setup callback for packets rx thread // Send command with newline '\n' if(app->is_command && app->selected_tx_string) { @@ -84,9 +104,15 @@ void wifi_marauder_scene_console_output_on_exit(void* context) { // Unregister rx callback wifi_marauder_uart_set_handle_rx_data_cb(app->uart, NULL); + wifi_marauder_uart_set_handle_rx_data_cb(app->lp_uart, NULL); // Automatically stop the scan when exiting view if(app->is_command) { wifi_marauder_uart_tx((uint8_t*)("stopscan\n"), strlen("stopscan\n")); } -} \ No newline at end of file + + app->is_writing = false; + if(app->capture_file && storage_file_is_open(app->capture_file)) { + storage_file_close(app->capture_file); + } +} diff --git a/applications/external/wifi_marauder_companion/wifi_marauder_app.c b/applications/external/wifi_marauder_companion/wifi_marauder_app.c index 1deb4e6f2..7563d927a 100644 --- a/applications/external/wifi_marauder_companion/wifi_marauder_app.c +++ b/applications/external/wifi_marauder_companion/wifi_marauder_app.c @@ -25,6 +25,9 @@ WifiMarauderApp* wifi_marauder_app_alloc() { WifiMarauderApp* app = malloc(sizeof(WifiMarauderApp)); app->gui = furi_record_open(RECORD_GUI); + app->dialogs = furi_record_open(RECORD_DIALOGS); + app->storage = furi_record_open(RECORD_STORAGE); + app->capture_file = storage_file_alloc(app->storage); app->view_dispatcher = view_dispatcher_alloc(); app->scene_manager = scene_manager_alloc(&wifi_marauder_scene_handlers, app); @@ -67,6 +70,14 @@ WifiMarauderApp* wifi_marauder_app_alloc() { return app; } +void wifi_marauder_make_app_folder(WifiMarauderApp* app) { + furi_assert(app); + + if(!storage_simply_mkdir(app->storage, MARAUDER_APP_FOLDER)) { + dialog_message_show_storage_error(app->dialogs, "Cannot create\napp folder"); + } +} + void wifi_marauder_app_free(WifiMarauderApp* app) { furi_assert(app); @@ -77,33 +88,47 @@ void wifi_marauder_app_free(WifiMarauderApp* app) { text_box_free(app->text_box); furi_string_free(app->text_box_store); text_input_free(app->text_input); + storage_file_free(app->capture_file); // View dispatcher view_dispatcher_free(app->view_dispatcher); scene_manager_free(app->scene_manager); wifi_marauder_uart_free(app->uart); + wifi_marauder_uart_free(app->lp_uart); // Close records furi_record_close(RECORD_GUI); + furi_record_close(RECORD_STORAGE); + furi_record_close(RECORD_DIALOGS); free(app); } int32_t wifi_marauder_app(void* p) { UNUSED(p); - furi_hal_power_enable_otg(); - furi_delay_ms(300); + + uint8_t attempts = 0; + while(!furi_hal_power_is_otg_enabled() && attempts++ < 5) { + furi_hal_power_enable_otg(); + furi_delay_ms(10); + } + furi_delay_ms(200); WifiMarauderApp* wifi_marauder_app = wifi_marauder_app_alloc(); - wifi_marauder_app->uart = wifi_marauder_uart_init(wifi_marauder_app); + wifi_marauder_make_app_folder(wifi_marauder_app); + + wifi_marauder_app->uart = wifi_marauder_usart_init(wifi_marauder_app); + wifi_marauder_app->lp_uart = wifi_marauder_lp_uart_init(wifi_marauder_app); view_dispatcher_run(wifi_marauder_app->view_dispatcher); wifi_marauder_app_free(wifi_marauder_app); - furi_hal_power_disable_otg(); + if(furi_hal_power_is_otg_enabled()) { + furi_hal_power_disable_otg(); + } return 0; } diff --git a/applications/external/wifi_marauder_companion/wifi_marauder_app.h b/applications/external/wifi_marauder_companion/wifi_marauder_app.h index 2d41daa55..92dd1dbd9 100644 --- a/applications/external/wifi_marauder_companion/wifi_marauder_app.h +++ b/applications/external/wifi_marauder_companion/wifi_marauder_app.h @@ -4,6 +4,8 @@ extern "C" { #endif +#define WIFI_MARAUDER_APP_VERSION "v0.3.1" + typedef struct WifiMarauderApp WifiMarauderApp; #ifdef __cplusplus diff --git a/applications/external/wifi_marauder_companion/wifi_marauder_app_i.h b/applications/external/wifi_marauder_companion/wifi_marauder_app_i.h index 4df9e326b..1165c6d9d 100644 --- a/applications/external/wifi_marauder_companion/wifi_marauder_app_i.h +++ b/applications/external/wifi_marauder_companion/wifi_marauder_app_i.h @@ -6,6 +6,7 @@ #include "scenes/wifi_marauder_scene.h" #include "wifi_marauder_custom_event.h" #include "wifi_marauder_uart.h" +#include "wifi_marauder_pcap.h" #include #include @@ -14,11 +15,16 @@ #include #include +#include +#include + #define NUM_MENU_ITEMS (16) #define WIFI_MARAUDER_TEXT_BOX_STORE_SIZE (4096) #define WIFI_MARAUDER_TEXT_INPUT_STORE_SIZE (512) +#define MARAUDER_APP_FOLDER EXT_PATH("apps_data/marauder") + struct WifiMarauderApp { Gui* gui; ViewDispatcher* view_dispatcher; @@ -29,11 +35,14 @@ struct WifiMarauderApp { size_t text_box_store_strlen; TextBox* text_box; TextInput* text_input; - //Widget* widget; + Storage* storage; + File* capture_file; + DialogsApp* dialogs; VariableItemList* var_item_list; WifiMarauderUart* uart; + WifiMarauderUart* lp_uart; int selected_menu_index; int selected_option_index[NUM_MENU_ITEMS]; const char* selected_tx_string; @@ -41,6 +50,7 @@ struct WifiMarauderApp { bool is_custom_tx_string; bool focus_console_start; bool show_stopscan_tip; + bool is_writing; // For input source and destination MAC in targeted deauth attack int special_case_input_step; diff --git a/applications/external/wifi_marauder_companion/wifi_marauder_pcap.c b/applications/external/wifi_marauder_companion/wifi_marauder_pcap.c new file mode 100644 index 000000000..fc5f39022 --- /dev/null +++ b/applications/external/wifi_marauder_companion/wifi_marauder_pcap.c @@ -0,0 +1,33 @@ +#include "wifi_marauder_app_i.h" +#include "wifi_marauder_pcap.h" + +void wifi_marauder_get_prefix_from_cmd(char* dest, const char* command) { + int start, end, delta; + start = strlen("sniff"); + end = strcspn(command, " "); + delta = end - start; + strncpy(dest, command + start, end - start); + dest[delta] = '\0'; +} + +void wifi_marauder_create_pcap_file(WifiMarauderApp* app) { + char prefix[10]; + char capture_file_path[100]; + wifi_marauder_get_prefix_from_cmd(prefix, app->selected_tx_string); + + int i = 0; + do { + snprintf( + capture_file_path, + sizeof(capture_file_path), + "%s/%s_%d.pcap", + MARAUDER_APP_FOLDER, + prefix, + i); + i++; + } while(storage_file_exists(app->storage, capture_file_path)); + + if(!storage_file_open(app->capture_file, capture_file_path, FSAM_WRITE, FSOM_CREATE_ALWAYS)) { + dialog_message_show_storage_error(app->dialogs, "Cannot open pcap file"); + } +} \ No newline at end of file diff --git a/applications/external/wifi_marauder_companion/wifi_marauder_pcap.h b/applications/external/wifi_marauder_companion/wifi_marauder_pcap.h new file mode 100644 index 000000000..29f8fcf8a --- /dev/null +++ b/applications/external/wifi_marauder_companion/wifi_marauder_pcap.h @@ -0,0 +1,11 @@ +#pragma once + +#include "furi_hal.h" + +/** + * Creates a PCAP file to store incoming packets. + * The file name will have a prefix according to the type of scan being performed by the application (Eg: raw_0.pcap) + * + * @param app Application context + */ +void wifi_marauder_create_pcap_file(WifiMarauderApp* app); \ No newline at end of file diff --git a/applications/external/wifi_marauder_companion/wifi_marauder_uart.c b/applications/external/wifi_marauder_companion/wifi_marauder_uart.c index 228b0f83d..5ce6480f2 100644 --- a/applications/external/wifi_marauder_companion/wifi_marauder_uart.c +++ b/applications/external/wifi_marauder_companion/wifi_marauder_uart.c @@ -2,10 +2,12 @@ #include "wifi_marauder_uart.h" #define UART_CH (FuriHalUartIdUSART1) +#define LP_UART_CH (FuriHalUartIdLPUART1) #define BAUDRATE (115200) struct WifiMarauderUart { WifiMarauderApp* app; + FuriHalUartId channel; FuriThread* rx_thread; FuriStreamBuffer* rx_stream; uint8_t rx_buf[RX_BUF_SIZE + 1]; @@ -60,25 +62,42 @@ void wifi_marauder_uart_tx(uint8_t* data, size_t len) { furi_hal_uart_tx(UART_CH, data, len); } -WifiMarauderUart* wifi_marauder_uart_init(WifiMarauderApp* app) { +void wifi_marauder_lp_uart_tx(uint8_t* data, size_t len) { + furi_hal_uart_tx(LP_UART_CH, data, len); +} + +WifiMarauderUart* + wifi_marauder_uart_init(WifiMarauderApp* app, FuriHalUartId channel, const char* thread_name) { WifiMarauderUart* uart = malloc(sizeof(WifiMarauderUart)); uart->app = app; + uart->channel = channel; uart->rx_stream = furi_stream_buffer_alloc(RX_BUF_SIZE, 1); uart->rx_thread = furi_thread_alloc(); - furi_thread_set_name(uart->rx_thread, "WifiMarauderUartRxThread"); + furi_thread_set_name(uart->rx_thread, thread_name); furi_thread_set_stack_size(uart->rx_thread, 1024); furi_thread_set_context(uart->rx_thread, uart); furi_thread_set_callback(uart->rx_thread, uart_worker); furi_thread_start(uart->rx_thread); - - furi_hal_console_disable(); - furi_hal_uart_set_br(UART_CH, BAUDRATE); - furi_hal_uart_set_irq_cb(UART_CH, wifi_marauder_uart_on_irq_cb, uart); + if(channel == FuriHalUartIdUSART1) { + furi_hal_console_disable(); + } else if(channel == FuriHalUartIdLPUART1) { + furi_hal_uart_init(channel, BAUDRATE); + } + furi_hal_uart_set_br(channel, BAUDRATE); + furi_hal_uart_set_irq_cb(channel, wifi_marauder_uart_on_irq_cb, uart); return uart; } +WifiMarauderUart* wifi_marauder_usart_init(WifiMarauderApp* app) { + return wifi_marauder_uart_init(app, UART_CH, "WifiMarauderUartRxThread"); +} + +WifiMarauderUart* wifi_marauder_lp_uart_init(WifiMarauderApp* app) { + return wifi_marauder_uart_init(app, LP_UART_CH, "WifiMarauderLPUartRxThread"); +} + void wifi_marauder_uart_free(WifiMarauderUart* uart) { furi_assert(uart); @@ -86,7 +105,7 @@ void wifi_marauder_uart_free(WifiMarauderUart* uart) { furi_thread_join(uart->rx_thread); furi_thread_free(uart->rx_thread); - furi_hal_uart_set_irq_cb(UART_CH, NULL, NULL); + furi_hal_uart_set_irq_cb(uart->channel, NULL, NULL); furi_hal_console_enable(); free(uart); diff --git a/applications/external/wifi_marauder_companion/wifi_marauder_uart.h b/applications/external/wifi_marauder_companion/wifi_marauder_uart.h index e38c18dd2..4835d52ce 100644 --- a/applications/external/wifi_marauder_companion/wifi_marauder_uart.h +++ b/applications/external/wifi_marauder_companion/wifi_marauder_uart.h @@ -10,5 +10,7 @@ void wifi_marauder_uart_set_handle_rx_data_cb( WifiMarauderUart* uart, void (*handle_rx_data_cb)(uint8_t* buf, size_t len, void* context)); void wifi_marauder_uart_tx(uint8_t* data, size_t len); -WifiMarauderUart* wifi_marauder_uart_init(WifiMarauderApp* app); +void wifi_marauder_lp_uart_tx(uint8_t* data, size_t len); +WifiMarauderUart* wifi_marauder_usart_init(WifiMarauderApp* app); +WifiMarauderUart* wifi_marauder_lp_uart_init(WifiMarauderApp* app); void wifi_marauder_uart_free(WifiMarauderUart* uart); diff --git a/applications/external/wifi_scanner/application.fam b/applications/external/wifi_scanner/application.fam index a46ecf3fb..a8ce99794 100644 --- a/applications/external/wifi_scanner/application.fam +++ b/applications/external/wifi_scanner/application.fam @@ -3,7 +3,6 @@ App( name="[WiFi] Scanner", apptype=FlipperAppType.EXTERNAL, entry_point="wifi_scanner_app", - cdefines=["APP_WIFI_SCANNER"], requires=["gui"], stack_size=2 * 1024, order=70, diff --git a/applications/external/wifi_scanner/wifi_scanner.c b/applications/external/wifi_scanner/wifi_scanner.c index 3a79ce16e..341287b54 100644 --- a/applications/external/wifi_scanner/wifi_scanner.c +++ b/applications/external/wifi_scanner/wifi_scanner.c @@ -663,7 +663,12 @@ int32_t wifi_scanner_app(void* p) { #else app->m_context = Initializing; #if ENABLE_MODULE_POWER - furi_hal_power_enable_otg(); + uint8_t attempts = 0; + while(!furi_hal_power_is_otg_enabled() && attempts++ < 5) { + furi_hal_power_enable_otg(); + furi_delay_ms(10); + } + furi_delay_ms(200); #endif // ENABLE_MODULE_POWER #endif // ENABLE_MODULE_DETECTION @@ -722,7 +727,12 @@ int32_t wifi_scanner_app(void* p) { app->m_wifiModuleAttached = true; app->m_context = Initializing; #if ENABLE_MODULE_POWER - furi_hal_power_enable_otg(); + uint8_t attempts2 = 0; + while(!furi_hal_power_is_otg_enabled() && attempts2++ < 3) { + furi_hal_power_enable_otg(); + furi_delay_ms(10); + } + #endif } } @@ -851,7 +861,9 @@ int32_t wifi_scanner_app(void* p) { WIFI_APP_LOG_I("App freed"); #if ENABLE_MODULE_POWER - furi_hal_power_disable_otg(); + if(furi_hal_power_is_otg_enabled()) { + furi_hal_power_disable_otg(); + } #endif return 0; diff --git a/applications/external/zombiez/application.fam b/applications/external/zombiez/application.fam index 3245187d2..069e591bc 100644 --- a/applications/external/zombiez/application.fam +++ b/applications/external/zombiez/application.fam @@ -3,7 +3,6 @@ App( name="Zombiez", apptype=FlipperAppType.EXTERNAL, entry_point="zombiez_game_app", - cdefines=["APP_ZOMBIEZ_GAME"], requires=["gui"], stack_size=2 * 1024, order=280, diff --git a/applications/main/fap_loader/application.fam b/applications/main/fap_loader/application.fam index 5e9376643..827f3d8ec 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 e845ed008..000000000 --- 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 e574f1169..000000000 --- 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 61ee80e91..000000000 --- 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 7b540fba6..000000000 --- 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 559463988..9b97d8530 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/main/nfc/nfc_cli.c b/applications/main/nfc/nfc_cli.c index 23335e299..6e6e04ca9 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); 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 b82bf5521..cb2f3a82d 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 a539e514b..8f4bd063e 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 2839534a7..73b98a1b8 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); diff --git a/applications/main/subghz/helpers/subghz_custom_event.h b/applications/main/subghz/helpers/subghz_custom_event.h index 4475045ee..69f946a9a 100644 --- a/applications/main/subghz/helpers/subghz_custom_event.h +++ b/applications/main/subghz/helpers/subghz_custom_event.h @@ -11,7 +11,14 @@ typedef enum { SubmenuIndexBFTClone, SubmenuIndexBFTMitto, SubmenuIndexSomfyTelis, - SubmenuIndexPricenton, + SubmenuIndexBeninca433, + SubmenuIndexBeninca868, + SubmenuIndexIronLogic, + SubmenuIndexElmesElectronic, + SubmenuIndexSommer_FM_434, + SubmenuIndexSommer_FM_868, + SubmenuIndexDTMNeo433, + SubmenuIndexGibidi433, SubmenuIndexNiceFlo12bit, SubmenuIndexNiceFlo24bit, SubmenuIndexNiceFlorS_433_92, @@ -19,14 +26,19 @@ typedef enum { SubmenuIndexNiceSmilo_433_92, SubmenuIndexCAME12bit, SubmenuIndexCAME24bit, - SubmenuIndexBETT_433, + SubmenuIndexCAME12bit868, + SubmenuIndexCAME24bit868, SubmenuIndexCAMETwee, + SubmenuIndexCAMESpace, + SubmenuIndexPricenton, + SubmenuIndexPricenton315, + SubmenuIndexBETT_433, + SubmenuIndexLinear_300_00, SubmenuIndexNeroSketch, SubmenuIndexNeroRadio, SubmenuIndexGateTX, SubmenuIndexDoorHan_315_00, SubmenuIndexDoorHan_433_92, - SubmenuIndexLinear_300_00, SubmenuIndexLiftMaster_315_00, SubmenuIndexLiftMaster_390_00, SubmenuIndexLiftMaster_433_00, diff --git a/applications/main/subghz/scenes/subghz_scene_ext_module_settings.c b/applications/main/subghz/scenes/subghz_scene_ext_module_settings.c index c58661332..a96208074 100644 --- a/applications/main/subghz/scenes/subghz_scene_ext_module_settings.c +++ b/applications/main/subghz/scenes/subghz_scene_ext_module_settings.c @@ -37,10 +37,13 @@ const char* const debug_counter_text[DEBUG_COUNTER_COUNT] = { static void subghz_scene_ext_module_changed(VariableItem* item) { SubGhz* subghz = variable_item_get_context(item); value_index_exm = variable_item_get_current_value_index(item); - UNUSED(subghz); variable_item_set_current_value_text(item, radio_modules_variables_text[value_index_exm]); + + subghz->last_settings->external_module_enabled = value_index_exm == 1; + subghz_last_settings_save(subghz->last_settings); } + static void subghz_ext_module_start_var_list_enter_callback(void* context, uint32_t index) { SubGhz* subghz = context; view_dispatcher_send_custom_event(subghz->view_dispatcher, index); @@ -85,6 +88,7 @@ static void subghz_scene_receiver_config_set_debug_counter(VariableItem* item) { } static void subghz_scene_receiver_config_set_ext_mod_power(VariableItem* item) { + SubGhz* subghz = variable_item_get_context(item); uint8_t index = variable_item_get_current_value_index(item); variable_item_set_current_value_text(item, ext_mod_power_text[index]); @@ -95,6 +99,9 @@ static void subghz_scene_receiver_config_set_ext_mod_power(VariableItem* item) { } else { furi_hal_subghz_enable_ext_power(); } + + subghz->last_settings->external_module_power_5v_disable = index == 1; + subghz_last_settings_save(subghz->last_settings); } void subghz_scene_ext_module_settings_on_enter(void* context) { @@ -181,7 +188,7 @@ bool subghz_scene_ext_module_settings_on_event(void* context, SceneManagerEvent // Check if module is present, if no -> show error if(!furi_hal_subghz_check_radio()) { value_index_exm = 0; - furi_hal_subghz_set_radio_type(value_index_exm); + furi_hal_subghz_set_radio_type(SubGhzRadioInternal); furi_string_set(subghz->error_str, "Please connect\nexternal radio"); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowErrorSub); } diff --git a/applications/main/subghz/scenes/subghz_scene_receiver_info.c b/applications/main/subghz/scenes/subghz_scene_receiver_info.c index 90046eb9d..978954181 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver_info.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver_info.c @@ -5,6 +5,7 @@ #include #include #include +#include void subghz_scene_receiver_info_callback(GuiButtonType result, InputType type, void* context) { furi_assert(context); @@ -240,6 +241,7 @@ void subghz_scene_receiver_info_on_exit(void* context) { alutech_reset_original_btn(); nice_flors_reset_original_btn(); somfy_telis_reset_original_btn(); + secplus2_reset_original_btn(); star_line_reset_mfname(); star_line_reset_kl_type(); } diff --git a/applications/main/subghz/scenes/subghz_scene_rpc.c b/applications/main/subghz/scenes/subghz_scene_rpc.c index b9585ec36..3e9ac11bc 100644 --- a/applications/main/subghz/scenes/subghz_scene_rpc.c +++ b/applications/main/subghz/scenes/subghz_scene_rpc.c @@ -4,6 +4,7 @@ #include #include #include +#include #include "xtreme/assets.h" @@ -118,6 +119,7 @@ void subghz_scene_rpc_on_exit(void* context) { alutech_reset_original_btn(); nice_flors_reset_original_btn(); somfy_telis_reset_original_btn(); + secplus2_reset_original_btn(); star_line_reset_mfname(); star_line_reset_kl_type(); } diff --git a/applications/main/subghz/scenes/subghz_scene_set_type.c b/applications/main/subghz/scenes/subghz_scene_set_type.c index bc76ee146..50af12669 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_type.c +++ b/applications/main/subghz/scenes/subghz_scene_set_type.c @@ -100,25 +100,67 @@ void subghz_scene_set_type_on_enter(void* context) { subghz); submenu_add_item( subghz->submenu, - "Princeton 433MHz", - SubmenuIndexPricenton, + "KL: DoorHan 315MHz", + SubmenuIndexDoorHan_315_00, subghz_scene_set_type_submenu_callback, subghz); submenu_add_item( subghz->submenu, - "Nice Flo 12bit 433MHz", - SubmenuIndexNiceFlo12bit, + "KL: DoorHan 433MHz", + SubmenuIndexDoorHan_433_92, subghz_scene_set_type_submenu_callback, subghz); submenu_add_item( subghz->submenu, - "Nice Flo 24bit 433MHz", - SubmenuIndexNiceFlo24bit, + "KL: Beninca 433MHz", + SubmenuIndexBeninca433, subghz_scene_set_type_submenu_callback, subghz); submenu_add_item( subghz->submenu, - "Nice Smilo 433MHz", + "KL: Beninca 868MHz", + SubmenuIndexBeninca868, + subghz_scene_set_type_submenu_callback, + subghz); + submenu_add_item( + subghz->submenu, + "KL: Sommer 434MHz", + SubmenuIndexSommer_FM_434, + subghz_scene_set_type_submenu_callback, + subghz); + submenu_add_item( + subghz->submenu, + "KL: Sommer 868MHz", + SubmenuIndexSommer_FM_868, + subghz_scene_set_type_submenu_callback, + subghz); + submenu_add_item( + subghz->submenu, + "KL: IronLogic 433MHz", + SubmenuIndexIronLogic, + subghz_scene_set_type_submenu_callback, + subghz); + submenu_add_item( + subghz->submenu, + "KL: DTM Neo 433MHz", + SubmenuIndexDTMNeo433, + subghz_scene_set_type_submenu_callback, + subghz); + submenu_add_item( + subghz->submenu, + "KL: Gibidi 433MHz", + SubmenuIndexGibidi433, + subghz_scene_set_type_submenu_callback, + subghz); + submenu_add_item( + subghz->submenu, + "KL: Elmes (PL) 433MHz", + SubmenuIndexElmesElectronic, + subghz_scene_set_type_submenu_callback, + subghz); + submenu_add_item( + subghz->submenu, + "KL: Nice Smilo 433MHz", SubmenuIndexNiceSmilo_433_92, subghz_scene_set_type_submenu_callback, subghz); @@ -134,6 +176,18 @@ void subghz_scene_set_type_on_enter(void* context) { SubmenuIndexNiceOne_433_92, subghz_scene_set_type_submenu_callback, subghz); + submenu_add_item( + subghz->submenu, + "Nice Flo 12bit 433MHz", + SubmenuIndexNiceFlo12bit, + subghz_scene_set_type_submenu_callback, + subghz); + submenu_add_item( + subghz->submenu, + "Nice Flo 24bit 433MHz", + SubmenuIndexNiceFlo24bit, + subghz_scene_set_type_submenu_callback, + subghz); submenu_add_item( subghz->submenu, "CAME 12bit 433MHz", @@ -146,6 +200,42 @@ void subghz_scene_set_type_on_enter(void* context) { SubmenuIndexCAME24bit, subghz_scene_set_type_submenu_callback, subghz); + submenu_add_item( + subghz->submenu, + "CAME 12bit 868MHz", + SubmenuIndexCAME12bit868, + subghz_scene_set_type_submenu_callback, + subghz); + submenu_add_item( + subghz->submenu, + "CAME 24bit 868MHz", + SubmenuIndexCAME24bit868, + subghz_scene_set_type_submenu_callback, + subghz); + submenu_add_item( + subghz->submenu, + "CAME TWEE 433MHz", + SubmenuIndexCAMETwee, + subghz_scene_set_type_submenu_callback, + subghz); + submenu_add_item( + subghz->submenu, + "KL: CAME Space 433MHz", + SubmenuIndexCAMESpace, + subghz_scene_set_type_submenu_callback, + subghz); + submenu_add_item( + subghz->submenu, + "Princeton 315MHz", + SubmenuIndexPricenton315, + subghz_scene_set_type_submenu_callback, + subghz); + submenu_add_item( + subghz->submenu, + "Princeton 433MHz", + SubmenuIndexPricenton, + subghz_scene_set_type_submenu_callback, + subghz); submenu_add_item( subghz->submenu, "BETT 433MHz", @@ -158,12 +248,6 @@ void subghz_scene_set_type_on_enter(void* context) { SubmenuIndexLinear_300_00, subghz_scene_set_type_submenu_callback, subghz); - submenu_add_item( - subghz->submenu, - "CAME TWEE 433MHz", - SubmenuIndexCAMETwee, - subghz_scene_set_type_submenu_callback, - subghz); // submenu_add_item( // subghz->submenu, "Nero Sketch", SubmenuIndexNeroSketch, subghz_scene_set_type_submenu_callback, subghz); // submenu_add_item( @@ -174,18 +258,6 @@ void subghz_scene_set_type_on_enter(void* context) { SubmenuIndexGateTX, subghz_scene_set_type_submenu_callback, subghz); - submenu_add_item( - subghz->submenu, - "DoorHan 315MHz", - SubmenuIndexDoorHan_315_00, - subghz_scene_set_type_submenu_callback, - subghz); - submenu_add_item( - subghz->submenu, - "DoorHan 433MHz", - SubmenuIndexDoorHan_433_92, - subghz_scene_set_type_submenu_callback, - subghz); submenu_add_item( subghz->submenu, "Security+1.0 315MHz", @@ -261,6 +333,15 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { generated_protocol = true; } break; + case SubmenuIndexPricenton315: + key = (key & 0x00FFFFF0) | 0x4; //btn 0x1, 0x2, 0x4, 0x8 + if(subghz_scene_set_type_submenu_gen_data_protocol( + subghz, SUBGHZ_PROTOCOL_PRINCETON_NAME, key, 24, 315000000, "AM650")) { + uint32_t te = 400; + flipper_format_update_uint32(subghz->txrx->fff_data, "TE", (uint32_t*)&te, 1); + generated_protocol = true; + } + break; case SubmenuIndexNiceFlo12bit: key = (key & 0x0000FFF0) | 0x1; //btn 0x1, 0x2, 0x4 if(subghz_scene_set_type_submenu_gen_data_protocol( @@ -289,6 +370,20 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { generated_protocol = true; } break; + case SubmenuIndexCAME12bit868: + key = (key & 0x0000FFF0) | 0x1; //btn 0x1, 0x2, 0x4 + if(subghz_scene_set_type_submenu_gen_data_protocol( + subghz, SUBGHZ_PROTOCOL_CAME_NAME, key, 12, 868350000, "AM650")) { + generated_protocol = true; + } + break; + case SubmenuIndexCAME24bit868: + key = (key & 0x00FFFFF0) | 0x4; //btn 0x1, 0x2, 0x4, 0x8 + if(subghz_scene_set_type_submenu_gen_data_protocol( + subghz, SUBGHZ_PROTOCOL_CAME_NAME, key, 24, 868350000, "AM650")) { + generated_protocol = true; + } + break; case SubmenuIndexLinear_300_00: key = (key & 0x3FF); if(subghz_scene_set_type_submenu_gen_data_protocol( @@ -325,6 +420,236 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { generated_protocol = true; } break; + case SubmenuIndexBeninca433: + subghz->txrx->transmitter = subghz_transmitter_alloc_init( + subghz->txrx->environment, SUBGHZ_PROTOCOL_KEELOQ_NAME); + subghz_preset_init(subghz, "AM650", 433920000, NULL, 0); + if(subghz->txrx->transmitter) { + subghz_protocol_keeloq_create_data( + subghz_transmitter_get_protocol_instance(subghz->txrx->transmitter), + subghz->txrx->fff_data, + (key & 0x000FFF00) | 0x00800080, + 0x1, + 0x0005, + "Beninca", + subghz->txrx->preset); + flipper_format_write_string_cstr(subghz->txrx->fff_data, "Manufacture", "Beninca"); + generated_protocol = true; + } else { + generated_protocol = false; + } + subghz_transmitter_free(subghz->txrx->transmitter); + if(!generated_protocol) { + furi_string_set( + subghz->error_str, "Function requires\nan SD card with\nfresh databases."); + scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError); + } + break; + case SubmenuIndexBeninca868: + subghz->txrx->transmitter = subghz_transmitter_alloc_init( + subghz->txrx->environment, SUBGHZ_PROTOCOL_KEELOQ_NAME); + subghz_preset_init(subghz, "AM650", 868350000, NULL, 0); + if(subghz->txrx->transmitter) { + subghz_protocol_keeloq_create_data( + subghz_transmitter_get_protocol_instance(subghz->txrx->transmitter), + subghz->txrx->fff_data, + (key & 0x000FFF00) | 0x00800080, + 0x1, + 0x0005, + "Beninca", + subghz->txrx->preset); + flipper_format_write_string_cstr(subghz->txrx->fff_data, "Manufacture", "Beninca"); + generated_protocol = true; + } else { + generated_protocol = false; + } + subghz_transmitter_free(subghz->txrx->transmitter); + if(!generated_protocol) { + furi_string_set( + subghz->error_str, "Function requires\nan SD card with\nfresh databases."); + scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError); + } + break; + case SubmenuIndexElmesElectronic: + subghz->txrx->transmitter = subghz_transmitter_alloc_init( + subghz->txrx->environment, SUBGHZ_PROTOCOL_KEELOQ_NAME); + subghz_preset_init(subghz, "AM650", 433920000, NULL, 0); + if(subghz->txrx->transmitter) { + subghz_protocol_keeloq_create_data( + subghz_transmitter_get_protocol_instance(subghz->txrx->transmitter), + subghz->txrx->fff_data, + (key & 0x00FFFFFF) | 0x02000000, + 0x2, + 0x0003, + "Elmes_Poland", + subghz->txrx->preset); + flipper_format_write_string_cstr( + subghz->txrx->fff_data, "Manufacture", "Elmes_Poland"); + generated_protocol = true; + } else { + generated_protocol = false; + } + subghz_transmitter_free(subghz->txrx->transmitter); + if(!generated_protocol) { + furi_string_set( + subghz->error_str, "Function requires\nan SD card with\nfresh databases."); + scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError); + } + break; + case SubmenuIndexGibidi433: + subghz->txrx->transmitter = subghz_transmitter_alloc_init( + subghz->txrx->environment, SUBGHZ_PROTOCOL_KEELOQ_NAME); + subghz_preset_init(subghz, "AM650", 433920000, NULL, 0); + if(subghz->txrx->transmitter) { + subghz_protocol_keeloq_create_data( + subghz_transmitter_get_protocol_instance(subghz->txrx->transmitter), + subghz->txrx->fff_data, + key & 0x00FFFFFF, + 0x2, + 0x0003, + "Gibidi", + subghz->txrx->preset); + flipper_format_write_string_cstr(subghz->txrx->fff_data, "Manufacture", "Gibidi"); + generated_protocol = true; + } else { + generated_protocol = false; + } + subghz_transmitter_free(subghz->txrx->transmitter); + if(!generated_protocol) { + furi_string_set( + subghz->error_str, "Function requires\nan SD card with\nfresh databases."); + scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError); + } + break; + case SubmenuIndexIronLogic: + subghz->txrx->transmitter = subghz_transmitter_alloc_init( + subghz->txrx->environment, SUBGHZ_PROTOCOL_KEELOQ_NAME); + subghz_preset_init(subghz, "AM650", 433920000, NULL, 0); + if(subghz->txrx->transmitter) { + subghz_protocol_keeloq_create_data( + subghz_transmitter_get_protocol_instance(subghz->txrx->transmitter), + subghz->txrx->fff_data, + key & 0x00FFFFF0, + 0x4, + 0x0005, + "IronLogic", + subghz->txrx->preset); + flipper_format_write_string_cstr( + subghz->txrx->fff_data, "Manufacture", "IronLogic"); + generated_protocol = true; + } else { + generated_protocol = false; + } + subghz_transmitter_free(subghz->txrx->transmitter); + if(!generated_protocol) { + furi_string_set( + subghz->error_str, "Function requires\nan SD card with\nfresh databases."); + scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError); + } + break; + case SubmenuIndexSommer_FM_434: + subghz->txrx->transmitter = subghz_transmitter_alloc_init( + subghz->txrx->environment, SUBGHZ_PROTOCOL_KEELOQ_NAME); + subghz_preset_init(subghz, "FM476", 434420000, NULL, 0); + if(subghz->txrx->transmitter) { + subghz_protocol_keeloq_create_data( + subghz_transmitter_get_protocol_instance(subghz->txrx->transmitter), + subghz->txrx->fff_data, + key & 0x0FFFFFFF, + 0x4, + 0x0003, + "Sommer(fsk476)", + subghz->txrx->preset); + flipper_format_write_string_cstr( + subghz->txrx->fff_data, "Manufacture", "Sommer(fsk476)"); + generated_protocol = true; + } else { + generated_protocol = false; + } + subghz_transmitter_free(subghz->txrx->transmitter); + if(!generated_protocol) { + furi_string_set( + subghz->error_str, "Function requires\nan SD card with\nfresh databases."); + scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError); + } + break; + case SubmenuIndexSommer_FM_868: + subghz->txrx->transmitter = subghz_transmitter_alloc_init( + subghz->txrx->environment, SUBGHZ_PROTOCOL_KEELOQ_NAME); + subghz_preset_init(subghz, "FM476", 868800000, NULL, 0); + if(subghz->txrx->transmitter) { + subghz_protocol_keeloq_create_data( + subghz_transmitter_get_protocol_instance(subghz->txrx->transmitter), + subghz->txrx->fff_data, + key & 0x0FFFFFFF, + 0x4, + 0x0003, + "Sommer(fsk476)", + subghz->txrx->preset); + flipper_format_write_string_cstr( + subghz->txrx->fff_data, "Manufacture", "Sommer(fsk476)"); + generated_protocol = true; + } else { + generated_protocol = false; + } + subghz_transmitter_free(subghz->txrx->transmitter); + if(!generated_protocol) { + furi_string_set( + subghz->error_str, "Function requires\nan SD card with\nfresh databases."); + scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError); + } + break; + case SubmenuIndexDTMNeo433: + subghz->txrx->transmitter = subghz_transmitter_alloc_init( + subghz->txrx->environment, SUBGHZ_PROTOCOL_KEELOQ_NAME); + subghz_preset_init(subghz, "AM650", 433920000, NULL, 0); + if(subghz->txrx->transmitter) { + subghz_protocol_keeloq_create_data( + subghz_transmitter_get_protocol_instance(subghz->txrx->transmitter), + subghz->txrx->fff_data, + key & 0x000FFFFF, + 0x2, + 0x0005, + "DTM_Neo", + subghz->txrx->preset); + flipper_format_write_string_cstr(subghz->txrx->fff_data, "Manufacture", "DTM_Neo"); + generated_protocol = true; + } else { + generated_protocol = false; + } + subghz_transmitter_free(subghz->txrx->transmitter); + if(!generated_protocol) { + furi_string_set( + subghz->error_str, "Function requires\nan SD card with\nfresh databases."); + scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError); + } + break; + case SubmenuIndexCAMESpace: + subghz->txrx->transmitter = subghz_transmitter_alloc_init( + subghz->txrx->environment, SUBGHZ_PROTOCOL_KEELOQ_NAME); + subghz_preset_init(subghz, "AM650", 433920000, NULL, 0); + if(subghz->txrx->transmitter) { + subghz_protocol_keeloq_create_data( + subghz_transmitter_get_protocol_instance(subghz->txrx->transmitter), + subghz->txrx->fff_data, + key & 0x00FFFFFF, + 0x2, + 0x0003, + "Came_Space", + subghz->txrx->preset); + flipper_format_write_string_cstr( + subghz->txrx->fff_data, "Manufacture", "Came_Space"); + generated_protocol = true; + } else { + generated_protocol = false; + } + subghz_transmitter_free(subghz->txrx->transmitter); + if(!generated_protocol) { + furi_string_set( + subghz->error_str, "Function requires\nan SD card with\nfresh databases."); + scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError); + } + break; case SubmenuIndexBFTMitto: subghz->txrx->transmitter = subghz_transmitter_alloc_init( subghz->txrx->environment, SUBGHZ_PROTOCOL_KEELOQ_NAME); @@ -397,6 +722,7 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { 0x0003, "DoorHan", subghz->txrx->preset); + flipper_format_write_string_cstr(subghz->txrx->fff_data, "Manufacture", "DoorHan"); generated_protocol = true; } else { generated_protocol = false; @@ -421,6 +747,7 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { 0x0003, "DoorHan", subghz->txrx->preset); + flipper_format_write_string_cstr(subghz->txrx->fff_data, "Manufacture", "DoorHan"); generated_protocol = true; } else { generated_protocol = false; @@ -493,6 +820,8 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { 0x0003, "NICE_Smilo", subghz->txrx->preset); + flipper_format_write_string_cstr( + subghz->txrx->fff_data, "Manufacture", "NICE_Smilo"); generated_protocol = true; } else { generated_protocol = false; diff --git a/applications/main/subghz/scenes/subghz_scene_start.c b/applications/main/subghz/scenes/subghz_scene_start.c index fd31353e0..eb14315a8 100644 --- a/applications/main/subghz/scenes/subghz_scene_start.c +++ b/applications/main/subghz/scenes/subghz_scene_start.c @@ -84,6 +84,8 @@ bool subghz_scene_start_on_event(void* context, SceneManagerEvent event) { furi_hal_subghz_enable_ext_power(); if(!furi_hal_subghz_check_radio()) { + furi_hal_subghz_set_radio_type(SubGhzRadioInternal); + subghz->last_settings->external_module_enabled = false; furi_string_set(subghz->error_str, "Please connect\nexternal radio"); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowErrorSub); return true; diff --git a/applications/main/subghz/scenes/subghz_scene_transmitter.c b/applications/main/subghz/scenes/subghz_scene_transmitter.c index 34685a8e5..e32e2b7b2 100644 --- a/applications/main/subghz/scenes/subghz_scene_transmitter.c +++ b/applications/main/subghz/scenes/subghz_scene_transmitter.c @@ -6,6 +6,7 @@ #include #include #include +#include void subghz_scene_transmitter_callback(SubGhzCustomEvent event, void* context) { furi_assert(context); @@ -95,6 +96,7 @@ bool subghz_scene_transmitter_on_event(void* context, SceneManagerEvent event) { alutech_set_btn(0); nice_flors_set_btn(0); somfy_telis_set_btn(0); + secplus2_set_btn(0); uint8_t tmp_counter = furi_hal_subghz_get_rolling_counter_mult(); furi_hal_subghz_set_rolling_counter_mult(0); // Calling restore! @@ -139,6 +141,7 @@ void subghz_scene_transmitter_on_exit(void* context) { alutech_reset_original_btn(); nice_flors_reset_original_btn(); somfy_telis_reset_original_btn(); + secplus2_reset_original_btn(); star_line_reset_mfname(); star_line_reset_kl_type(); } diff --git a/applications/main/subghz/subghz.c b/applications/main/subghz/subghz.c index e2b484120..75c6e627a 100644 --- a/applications/main/subghz/subghz.c +++ b/applications/main/subghz/subghz.c @@ -186,66 +186,66 @@ SubGhz* subghz_alloc(bool alloc_for_tx_only) { subghz_setting_load(subghz->setting, EXT_PATH("subghz/assets/setting_user")); // Custom Presets load without using config file + if(!alloc_for_tx_only) { + FlipperFormat* temp_fm_preset = flipper_format_string_alloc(); + flipper_format_write_string_cstr( + temp_fm_preset, + (const char*)"Custom_preset_data", + (const char*)"02 0D 0B 06 08 32 07 04 14 00 13 02 12 04 11 83 10 67 15 24 18 18 19 16 1D 91 1C 00 1B 07 20 FB 22 10 21 56 00 00 C0 00 00 00 00 00 00 00"); + flipper_format_rewind(temp_fm_preset); + subghz_setting_load_custom_preset(subghz->setting, (const char*)"FM95", temp_fm_preset); - FlipperFormat* temp_fm_preset = flipper_format_string_alloc(); - flipper_format_write_string_cstr( - temp_fm_preset, - (const char*)"Custom_preset_data", - (const char*)"02 0D 0B 06 08 32 07 04 14 00 13 02 12 04 11 83 10 67 15 24 18 18 19 16 1D 91 1C 00 1B 07 20 FB 22 10 21 56 00 00 C0 00 00 00 00 00 00 00"); - flipper_format_rewind(temp_fm_preset); - subghz_setting_load_custom_preset(subghz->setting, (const char*)"FM95", temp_fm_preset); + flipper_format_free(temp_fm_preset); - flipper_format_free(temp_fm_preset); + // #2-FSK 200khz BW / 135kHz Filter/ 15.86Khz Deviation + Ramping + FlipperFormat* temp_fm_preset2 = flipper_format_string_alloc(); + flipper_format_write_string_cstr( + temp_fm_preset2, + (const char*)"Custom_preset_data", + (const char*)"02 0D 03 47 08 32 0B 06 15 32 14 00 13 00 12 00 11 32 10 A7 18 18 19 1D 1D 92 1C 00 1B 04 20 FB 22 17 21 B6 00 00 00 12 0E 34 60 C5 C1 C0"); + flipper_format_rewind(temp_fm_preset2); + subghz_setting_load_custom_preset(subghz->setting, (const char*)"FM15k", temp_fm_preset2); - // #2-FSK 200khz BW / 135kHz Filter/ 15.86Khz Deviation + Ramping - FlipperFormat* temp_fm_preset2 = flipper_format_string_alloc(); - flipper_format_write_string_cstr( - temp_fm_preset2, - (const char*)"Custom_preset_data", - (const char*)"02 0D 03 47 08 32 0B 06 15 32 14 00 13 00 12 00 11 32 10 A7 18 18 19 1D 1D 92 1C 00 1B 04 20 FB 22 17 21 B6 00 00 00 12 0E 34 60 C5 C1 C0"); - flipper_format_rewind(temp_fm_preset2); - subghz_setting_load_custom_preset(subghz->setting, (const char*)"FM15k", temp_fm_preset2); + flipper_format_free(temp_fm_preset2); - flipper_format_free(temp_fm_preset2); + // Pagers + FlipperFormat* temp_fm_preset3 = flipper_format_string_alloc(); + flipper_format_write_string_cstr( + temp_fm_preset3, + (const char*)"Custom_preset_data", + (const char*)"02 0D 07 04 08 32 0B 06 10 64 11 93 12 0C 13 02 14 00 15 15 18 18 19 16 1B 07 1C 00 1D 91 20 FB 21 56 22 10 00 00 C0 00 00 00 00 00 00 00"); + flipper_format_rewind(temp_fm_preset3); + subghz_setting_load_custom_preset(subghz->setting, (const char*)"Pagers", temp_fm_preset3); - // Pagers - FlipperFormat* temp_fm_preset3 = flipper_format_string_alloc(); - flipper_format_write_string_cstr( - temp_fm_preset3, - (const char*)"Custom_preset_data", - (const char*)"02 0D 07 04 08 32 0B 06 10 64 11 93 12 0C 13 02 14 00 15 15 18 18 19 16 1B 07 1C 00 1D 91 20 FB 21 56 22 10 00 00 C0 00 00 00 00 00 00 00"); - flipper_format_rewind(temp_fm_preset3); - subghz_setting_load_custom_preset(subghz->setting, (const char*)"Pagers", temp_fm_preset3); + flipper_format_free(temp_fm_preset3); - flipper_format_free(temp_fm_preset3); + // # HND - FM presets + FlipperFormat* temp_fm_preset4 = flipper_format_string_alloc(); + flipper_format_write_string_cstr( + temp_fm_preset4, + (const char*)"Custom_preset_data", + (const char*)"02 0D 0B 06 08 32 07 04 14 00 13 02 12 04 11 36 10 69 15 32 18 18 19 16 1D 91 1C 00 1B 07 20 FB 22 10 21 56 00 00 C0 00 00 00 00 00 00 00"); + flipper_format_rewind(temp_fm_preset4); + subghz_setting_load_custom_preset(subghz->setting, (const char*)"HND_1", temp_fm_preset4); - // # HND - FM presets - FlipperFormat* temp_fm_preset4 = flipper_format_string_alloc(); - flipper_format_write_string_cstr( - temp_fm_preset4, - (const char*)"Custom_preset_data", - (const char*)"02 0D 0B 06 08 32 07 04 14 00 13 02 12 04 11 36 10 69 15 32 18 18 19 16 1D 91 1C 00 1B 07 20 FB 22 10 21 56 00 00 C0 00 00 00 00 00 00 00"); - flipper_format_rewind(temp_fm_preset4); - subghz_setting_load_custom_preset(subghz->setting, (const char*)"HND_1", temp_fm_preset4); + flipper_format_free(temp_fm_preset4); - flipper_format_free(temp_fm_preset4); - - FlipperFormat* temp_fm_preset5 = flipper_format_string_alloc(); - flipper_format_write_string_cstr( - temp_fm_preset5, - (const char*)"Custom_preset_data", - (const char*)"02 0D 0B 06 08 32 07 04 14 00 13 02 12 07 11 36 10 E9 15 32 18 18 19 16 1D 92 1C 40 1B 03 20 FB 22 10 21 56 00 00 C0 00 00 00 00 00 00 00"); - flipper_format_rewind(temp_fm_preset5); - subghz_setting_load_custom_preset(subghz->setting, (const char*)"HND_2", temp_fm_preset5); - - flipper_format_free(temp_fm_preset5); + FlipperFormat* temp_fm_preset5 = flipper_format_string_alloc(); + flipper_format_write_string_cstr( + temp_fm_preset5, + (const char*)"Custom_preset_data", + (const char*)"02 0D 0B 06 08 32 07 04 14 00 13 02 12 07 11 36 10 E9 15 32 18 18 19 16 1D 92 1C 40 1B 03 20 FB 22 10 21 56 00 00 C0 00 00 00 00 00 00 00"); + flipper_format_rewind(temp_fm_preset5); + subghz_setting_load_custom_preset(subghz->setting, (const char*)"HND_2", temp_fm_preset5); + flipper_format_free(temp_fm_preset5); + } // custom presets loading - end // Load last used values for Read, Read RAW, etc. or default + subghz->last_settings = subghz_last_settings_alloc(); + subghz_last_settings_load(subghz->last_settings, 0); if(!alloc_for_tx_only) { - subghz->last_settings = subghz_last_settings_alloc(); - subghz_last_settings_load(subghz->last_settings, 0); #if FURI_DEBUG FURI_LOG_D( TAG, @@ -421,9 +421,6 @@ void subghz_free(SubGhz* subghz, bool alloc_for_tx_only) { // The rest free(subghz); - - // Disable power for External CC1101 if it was enabled and module is connected - furi_hal_subghz_disable_ext_power(); } int32_t subghz_app(void* p) { @@ -447,9 +444,19 @@ int32_t subghz_app(void* p) { subghz->txrx->environment, EXT_PATH("subghz/assets/keeloq_mfcodes")); subghz_environment_load_keystore( subghz->txrx->environment, EXT_PATH("subghz/assets/keeloq_mfcodes_user")); + + // Call enable power for external module + furi_hal_subghz_enable_ext_power(); + + // Auto switch to internal radio if external radio is not available + if(!furi_hal_subghz_check_radio()) { + subghz->last_settings->external_module_enabled = false; + furi_hal_subghz_set_radio_type(SubGhzRadioInternal); + } // Check argument and run corresponding scene if(p && strlen(p)) { uint32_t rpc_ctx = 0; + if(sscanf(p, "RPC %lX", &rpc_ctx) == 1) { subghz->rpc_ctx = (void*)rpc_ctx; rpc_system_app_set_callback(subghz->rpc_ctx, subghz_rpc_command_callback, subghz); @@ -498,6 +505,8 @@ int32_t subghz_app(void* p) { view_dispatcher_run(subghz->view_dispatcher); furi_hal_power_suppress_charge_exit(); + // Disable power for External CC1101 if it was enabled and module is connected + furi_hal_subghz_disable_ext_power(); subghz_free(subghz, alloc_for_tx); diff --git a/applications/main/subghz/subghz_last_settings.c b/applications/main/subghz/subghz_last_settings.c index 8e7016df7..6fc51554d 100644 --- a/applications/main/subghz/subghz_last_settings.c +++ b/applications/main/subghz/subghz_last_settings.c @@ -17,6 +17,8 @@ //#define SUBGHZ_LAST_SETTING_FIELD_PRESET "Preset" #define SUBGHZ_LAST_SETTING_FIELD_FREQUENCY_ANALYZER_FEEDBACK_LEVEL "FeedbackLevel" #define SUBGHZ_LAST_SETTING_FIELD_FREQUENCY_ANALYZER_TRIGGER "FATrigger" +#define SUBGHZ_LAST_SETTING_FIELD_EXTERNAL_MODULE_ENABLED "External" +#define SUBGHZ_LAST_SETTING_FIELD_EXTERNAL_MODULE_POWER "ExtPower" SubGhzLastSettings* subghz_last_settings_alloc(void) { SubGhzLastSettings* instance = malloc(sizeof(SubGhzLastSettings)); @@ -41,6 +43,8 @@ void subghz_last_settings_load(SubGhzLastSettings* instance, size_t preset_count uint32_t temp_frequency = 0; uint32_t temp_frequency_analyzer_feedback_level = 0; float temp_frequency_analyzer_trigger = 0; + bool temp_external_module_enabled = false; + bool temp_external_module_power_5v_disable = false; //int32_t temp_preset = 0; bool frequency_analyzer_feedback_level_was_read = false; bool frequency_analyzer_trigger_was_read = false; @@ -62,6 +66,16 @@ void subghz_last_settings_load(SubGhzLastSettings* instance, size_t preset_count SUBGHZ_LAST_SETTING_FIELD_FREQUENCY_ANALYZER_TRIGGER, (float*)&temp_frequency_analyzer_trigger, 1); + flipper_format_read_bool( + fff_data_file, + SUBGHZ_LAST_SETTING_FIELD_EXTERNAL_MODULE_ENABLED, + (bool*)&temp_external_module_enabled, + 1); + flipper_format_read_bool( + fff_data_file, + SUBGHZ_LAST_SETTING_FIELD_EXTERNAL_MODULE_POWER, + (bool*)&temp_external_module_power_5v_disable, + 1); } else { FURI_LOG_E(TAG, "Error open file %s", SUBGHZ_LAST_SETTINGS_PATH); @@ -74,6 +88,7 @@ void subghz_last_settings_load(SubGhzLastSettings* instance, size_t preset_count instance->frequency_analyzer_feedback_level = SUBGHZ_LAST_SETTING_FREQUENCY_ANALYZER_FEEDBACK_LEVEL; instance->frequency_analyzer_trigger = SUBGHZ_LAST_SETTING_FREQUENCY_ANALYZER_TRIGGER; + instance->external_module_enabled = false; } else { instance->frequency = temp_frequency; @@ -89,6 +104,21 @@ void subghz_last_settings_load(SubGhzLastSettings* instance, size_t preset_count /*if(temp_preset > (int32_t)preset_count - 1 || temp_preset < 0) { FURI_LOG_W(TAG, "Last used preset no found");*/ instance->preset = SUBGHZ_LAST_SETTING_DEFAULT_PRESET; + + instance->external_module_enabled = temp_external_module_enabled; + + instance->external_module_power_5v_disable = temp_external_module_power_5v_disable; + + if(instance->external_module_power_5v_disable) { + furi_hal_subghz_set_external_power_disable(true); + furi_hal_subghz_disable_ext_power(); + } + + // Set selected radio module + if(instance->external_module_enabled) { + furi_hal_subghz_set_radio_type(SubGhzRadioExternal); + } + /*/} else { instance->preset = temp_preset; }*/ @@ -145,6 +175,20 @@ bool subghz_last_settings_save(SubGhzLastSettings* instance) { 1)) { break; } + if(!flipper_format_insert_or_update_bool( + file, + SUBGHZ_LAST_SETTING_FIELD_EXTERNAL_MODULE_ENABLED, + &instance->external_module_enabled, + 1)) { + break; + } + if(!flipper_format_insert_or_update_bool( + file, + SUBGHZ_LAST_SETTING_FIELD_EXTERNAL_MODULE_POWER, + &instance->external_module_power_5v_disable, + 1)) { + break; + } saved = true; } while(0); diff --git a/applications/main/subghz/subghz_last_settings.h b/applications/main/subghz/subghz_last_settings.h index f08d99c81..5e3630468 100644 --- a/applications/main/subghz/subghz_last_settings.h +++ b/applications/main/subghz/subghz_last_settings.h @@ -10,6 +10,8 @@ typedef struct { int32_t preset; uint32_t frequency_analyzer_feedback_level; float frequency_analyzer_trigger; + bool external_module_enabled; + bool external_module_power_5v_disable; } SubGhzLastSettings; SubGhzLastSettings* subghz_last_settings_alloc(void); diff --git a/applications/main/subghz/views/transmitter.c b/applications/main/subghz/views/transmitter.c index e6ed16d81..7de0e6495 100644 --- a/applications/main/subghz/views/transmitter.c +++ b/applications/main/subghz/views/transmitter.c @@ -8,6 +8,7 @@ #include #include #include +#include struct SubGhzViewTransmitter { View* view; @@ -163,6 +164,7 @@ bool subghz_view_transmitter_input(InputEvent* event, void* context) { alutech_set_btn(1); nice_flors_set_btn(1); somfy_telis_set_btn(1); + secplus2_set_btn(1); with_view_model( subghz_transmitter->view, SubGhzViewTransmitterModel * model, @@ -199,6 +201,7 @@ bool subghz_view_transmitter_input(InputEvent* event, void* context) { alutech_set_btn(2); nice_flors_set_btn(2); somfy_telis_set_btn(2); + secplus2_set_btn(2); with_view_model( subghz_transmitter->view, SubGhzViewTransmitterModel * model, @@ -235,6 +238,7 @@ bool subghz_view_transmitter_input(InputEvent* event, void* context) { alutech_set_btn(3); nice_flors_set_btn(3); somfy_telis_set_btn(3); + secplus2_set_btn(3); with_view_model( subghz_transmitter->view, SubGhzViewTransmitterModel * model, diff --git a/applications/services/applications.h b/applications/services/applications.h index 871e9af54..85f736742 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/gui/modules/submenu.c b/applications/services/gui/modules/submenu.c index 17dc8aa29..31ca24581 100644 --- a/applications/services/gui/modules/submenu.c +++ b/applications/services/gui/modules/submenu.c @@ -104,7 +104,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 - 11); canvas_draw_str( canvas, diff --git a/applications/services/loader/application.fam b/applications/services/loader/application.fam index 91103e46e..49f3c4148 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 000000000..814dd82c9 --- /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 000000000..c73ae8960 --- /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 868d86358..219a649a9 100644 --- a/applications/services/loader/loader.c +++ b/applications/services/loader/loader.c @@ -66,10 +66,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); @@ -78,10 +74,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; } @@ -184,18 +176,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) { @@ -365,22 +345,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); @@ -409,10 +373,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); @@ -465,24 +425,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", @@ -539,29 +481,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 db91f806c..00028cd6b 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/applications/services/storage/storage_processing.c b/applications/services/storage/storage_processing.c index 2a335e366..a3076f271 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,13 +37,19 @@ 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) > 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) { @@ -43,7 +60,6 @@ static StorageType storage_get_type_by_path(FuriString* path) { 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) { diff --git a/assets/.gitignore b/assets/.gitignore index 2ecf4ed66..8dadaf6c8 100644 --- a/assets/.gitignore +++ b/assets/.gitignore @@ -1,3 +1,4 @@ /core2_firmware /resources/Manifest /resources/apps/* +/resources/apps_data/**/*.fal 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 5da2a2fa8..d11892372 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 000000000..46808ef60 --- /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 diff --git a/fbt b/fbt index f80e802b6..efe625f03 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 92c734860..6e839c778 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 ) ) diff --git a/fbt_options.py b/fbt_options.py index 8c7bdbe29..8f7d9c2d4 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 07c323a1b..61195aba6 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 a08f2ed4f..e12bbf3c5 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/main/fap_loader/fap_loader_app.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -31,6 +31,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,, @@ -117,7 +118,11 @@ 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/STM32CubeWB/Middlewares/Third_Party/FreeRTOS/Source/include/FreeRTOS.h,, Header,+,lib/STM32CubeWB/Middlewares/Third_Party/FreeRTOS/Source/include/stream_buffer.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,, @@ -717,6 +722,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" @@ -839,6 +848,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* @@ -925,14 +935,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* @@ -2233,6 +2245,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* @@ -2549,6 +2568,10 @@ Function,+,scene_manager_search_and_switch_to_previous_scene_one_of,_Bool,"Scene Function,+,scene_manager_set_scene_state,void,"SceneManager*, uint32_t, uint32_t" Function,+,scene_manager_stop,void,SceneManager* Function,+,sd_api_get_fs_type_text,const char*,SDFsType +Function,-,secplus2_get_custom_btn,uint8_t, +Function,-,secplus2_get_original_btn,uint8_t, +Function,-,secplus2_reset_original_btn,void, +Function,-,secplus2_set_btn,void,uint8_t Function,-,secure_getenv,char*,const char* Function,-,seed48,unsigned short*,unsigned short[3] Function,-,select,int,"int, fd_set*, fd_set*, fd_set*, timeval*" @@ -4788,6 +4811,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/furi_hal/furi_hal_subghz.c b/firmware/targets/f7/furi_hal/furi_hal_subghz.c index a219303b7..366a30498 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_subghz.c +++ b/firmware/targets/f7/furi_hal/furi_hal_subghz.c @@ -93,10 +93,10 @@ bool furi_hal_subghz_enable_ext_power(void) { } if(furi_hal_subghz.radio_type != SubGhzRadioInternal) { uint8_t attempts = 0; - while(!furi_hal_power_is_otg_enabled() && attempts++ < 2) { + while(!furi_hal_power_is_otg_enabled() && attempts++ < 5) { furi_hal_power_enable_otg(); //CC1101 power-up time - furi_delay_ms(5); + furi_delay_ms(10); } } return furi_hal_power_is_otg_enabled(); diff --git a/furi/flipper.c b/furi/flipper.c index f0147c060..5c2ad8138 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 9fbbf95d1..d253cc82c 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 000000000..022792dce --- /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 000000000..7e4b4aba1 --- /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 746611697..9737fd022 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 ca31fc483..f07df4edb 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 58e315333..146afccb5 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 f371cdb22..631fe122f 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 9b38540b7..af9a1d9b4 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 6e20c0809..ca917cf1a 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 b3e5996bb..519cc3971 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 000000000..1402c3ad0 --- /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 000000000..a2d4bab25 --- /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 000000000..101471dc5 --- /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 000000000..d94c25db9 --- /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/lib/nfc/nfc_worker.c b/lib/nfc/nfc_worker.c index d77029b61..23d462590 100644 --- a/lib/nfc/nfc_worker.c +++ b/lib/nfc/nfc_worker.c @@ -1072,7 +1072,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; @@ -1086,24 +1086,33 @@ 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); deactivated = true; } 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; } 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/parsers/plantain_4k_parser.c b/lib/nfc/parsers/plantain_4k_parser.c index aed41965c..19da0b5eb 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 3a1d17732..2e4091dda 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 d87b4eba7..1f1b85a5c 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 9c16296f3..bfd22364b 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 0e2ed5690..d6d4279dd 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, diff --git a/lib/nfc/protocols/mifare_classic.c b/lib/nfc/protocols/mifare_classic.c index dc8d2b9c4..8c7603767 100644 --- a/lib/nfc/protocols/mifare_classic.c +++ b/lib/nfc/protocols/mifare_classic.c @@ -651,6 +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)) { + 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_auth(tx_rx, i, key, MfClassicKeyA, &crypto, false, 0)) continue; @@ -660,7 +666,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++; @@ -681,7 +691,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_auth(tx_rx, i, key, MfClassicKeyB, &crypto, false, 0)) continue; @@ -691,7 +706,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++; @@ -1524,4 +1543,4 @@ bool mf_classic_write_sector( } return write_success; -} \ No newline at end of file +} diff --git a/lib/one_wire/ibutton/protocols/dallas/dallas_common.c b/lib/one_wire/ibutton/protocols/dallas/dallas_common.c index 57a873b1d..22b25db84 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 7ad13eb2c..7991a1f8b 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 000000000..eb5b330b7 --- /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 000000000..522b612da --- /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 131bc634a..17d631259 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 e69145c58..74a5792c6 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 e54c3125d..b4dd51ce7 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 ba74c0c23..2ba1dd39a 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 */ diff --git a/lib/subghz/protocols/keeloq.c b/lib/subghz/protocols/keeloq.c index 4bd978c8f..d08352e78 100644 --- a/lib/subghz/protocols/keeloq.c +++ b/lib/subghz/protocols/keeloq.c @@ -198,10 +198,12 @@ static bool subghz_protocol_keeloq_gen_data( (instance->generic.serial & 0x3FF) << 16 | //ToDo in some protocols the discriminator is 0 instance->generic.cnt; - // DTM Neo uses 12bit -> simple learning -- FAAC_RC,XT , Mutanco_Mutancode -> 12bit normal learning + // DTM Neo, Came_Space uses 12bit -> simple learning -- FAAC_RC,XT , Mutanco_Mutancode, Stilmatic(Schellenberg) -> 12bit normal learning if((strcmp(instance->manufacture_name, "DTM_Neo") == 0) || (strcmp(instance->manufacture_name, "FAAC_RC,XT") == 0) || - (strcmp(instance->manufacture_name, "Mutanco_Mutancode") == 0)) { + (strcmp(instance->manufacture_name, "Mutanco_Mutancode") == 0) || + (strcmp(instance->manufacture_name, "Stilmatic") == 0) || + (strcmp(instance->manufacture_name, "Came_Space") == 0)) { decrypt = btn << 28 | (instance->generic.serial & 0xFFF) << 16 | instance->generic.cnt; } @@ -567,6 +569,10 @@ SubGhzProtocolStatus instance->generic.seed = seed_data[0] << 24 | seed_data[1] << 16 | seed_data[2] << 8 | seed_data[3]; + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } // Read manufacturer from file if(flipper_format_read_string( flipper_format, "Manufacture", instance->manufacture_from_file)) { @@ -1247,6 +1253,10 @@ SubGhzProtocolStatus instance->generic.seed = seed_data[0] << 24 | seed_data[1] << 16 | seed_data[2] << 8 | seed_data[3]; + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } // Read manufacturer from file if(flipper_format_read_string( flipper_format, "Manufacture", instance->manufacture_from_file)) { diff --git a/lib/subghz/protocols/secplus_v2.c b/lib/subghz/protocols/secplus_v2.c index a55930552..a0bc2b9f4 100644 --- a/lib/subghz/protocols/secplus_v2.c +++ b/lib/subghz/protocols/secplus_v2.c @@ -83,6 +83,25 @@ const SubGhzProtocol subghz_protocol_secplus_v2 = { .encoder = &subghz_protocol_secplus_v2_encoder, }; +static uint8_t sc_btn_temp_id; +static uint8_t sc_btn_temp_id_original; + +void secplus2_set_btn(uint8_t b) { + sc_btn_temp_id = b; +} + +uint8_t secplus2_get_original_btn() { + return sc_btn_temp_id_original; +} + +uint8_t secplus2_get_custom_btn() { + return sc_btn_temp_id; +} + +void secplus2_reset_original_btn() { + sc_btn_temp_id_original = 0; +} + void* subghz_protocol_encoder_secplus_v2_alloc(SubGhzEnvironment* environment) { UNUSED(environment); SubGhzProtocolEncoderSecPlus_v2* instance = malloc(sizeof(SubGhzProtocolEncoderSecPlus_v2)); @@ -338,6 +357,11 @@ static void instance->btn = 0; instance->serial = 0; } + + // Save original button for later use + if(sc_btn_temp_id_original == 0) { + sc_btn_temp_id_original = instance->btn; + } } /** @@ -373,6 +397,72 @@ static uint64_t subghz_protocol_secplus_v2_encode_half(uint8_t roll_array[], uin */ static void subghz_protocol_secplus_v2_encode(SubGhzProtocolEncoderSecPlus_v2* instance) { + // Save original button for later use + if(sc_btn_temp_id_original == 0) { + sc_btn_temp_id_original = instance->generic.btn; + } + + // Set custom button + if(sc_btn_temp_id == 1) { + switch(sc_btn_temp_id_original) { + case 0x68: + instance->generic.btn = 0x80; + break; + case 0x80: + instance->generic.btn = 0x68; + break; + case 0x81: + instance->generic.btn = 0x80; + break; + case 0xE2: + instance->generic.btn = 0x80; + break; + + default: + break; + } + } + if(sc_btn_temp_id == 2) { + switch(sc_btn_temp_id_original) { + case 0x68: + instance->generic.btn = 0x81; + break; + case 0x80: + instance->generic.btn = 0x81; + break; + case 0x81: + instance->generic.btn = 0x68; + break; + case 0xE2: + instance->generic.btn = 0x81; + break; + + default: + break; + } + } + if(sc_btn_temp_id == 3) { + switch(sc_btn_temp_id_original) { + case 0x68: + instance->generic.btn = 0xE2; + break; + case 0x80: + instance->generic.btn = 0xE2; + break; + case 0x81: + instance->generic.btn = 0xE2; + break; + case 0xE2: + instance->generic.btn = 0x68; + break; + + default: + break; + } + } + if((sc_btn_temp_id == 0) && (sc_btn_temp_id_original != 0)) { + instance->generic.btn = sc_btn_temp_id_original; + } uint32_t fixed_1[1] = {instance->generic.btn << 12 | instance->generic.serial >> 20}; uint32_t fixed_2[1] = {instance->generic.serial & 0xFFFFF}; uint8_t rolling_digits[18] = {0}; @@ -611,7 +701,7 @@ bool subghz_protocol_secplus_v2_create_data( 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 = SubGhzProtocolStatusError; + res = SubGhzProtocolStatusErrorParserOthers; } return res == SubGhzProtocolStatusOk; } diff --git a/lib/subghz/protocols/secplus_v2.h b/lib/subghz/protocols/secplus_v2.h index 0eea732af..155d943b2 100644 --- a/lib/subghz/protocols/secplus_v2.h +++ b/lib/subghz/protocols/secplus_v2.h @@ -10,6 +10,14 @@ extern const SubGhzProtocolDecoder subghz_protocol_secplus_v2_decoder; extern const SubGhzProtocolEncoder subghz_protocol_secplus_v2_encoder; extern const SubGhzProtocol subghz_protocol_secplus_v2; +// Custom buttons +void secplus2_set_btn(uint8_t b); + +uint8_t secplus2_get_original_btn(); +uint8_t secplus2_get_custom_btn(); + +void secplus2_reset_original_btn(); + /** * Allocate SubGhzProtocolEncoderSecPlus_v2. * @param environment Pointer to a SubGhzEnvironment instance diff --git a/lib/subghz/subghz_setting.c b/lib/subghz/subghz_setting.c index 0b3c8939e..d406eff6f 100644 --- a/lib/subghz/subghz_setting.c +++ b/lib/subghz/subghz_setting.c @@ -73,9 +73,8 @@ static const uint32_t subghz_frequency_list[] = { }; static const uint32_t subghz_hopper_frequency_list[] = { + 310000000, 315000000, - 330000000, - 390000000, 433420000, 433920000, 868350000, diff --git a/scripts/distfap.py b/scripts/distfap.py new file mode 100644 index 000000000..060fe26ff --- /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 7ccbd5529..380d2e7de 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, @@ -261,6 +290,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: @@ -302,7 +343,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 000000000..0649f03ef --- /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 3a37eacc9..324819818 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 9c9f52958..47e11236d 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 5b6fac5f7..000000000 --- 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 1d537c58d..397238c69 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'"Apps" {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 "Apps" {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 1c16c5ca6..9bfbfefa3 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 ee5dabd43..84c01021a 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/scripts/version.py b/scripts/version.py index 55de1df06..ff57e6910 100644 --- a/scripts/version.py +++ b/scripts/version.py @@ -10,11 +10,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: diff --git a/site_scons/commandline.scons b/site_scons/commandline.scons index e38f58ec2..5610478cb 100644 --- a/site_scons/commandline.scons +++ b/site_scons/commandline.scons @@ -204,10 +204,6 @@ vars.AddVariables( "system_apps", # Settings "settings_apps", - # Plugins - # "basic_plugins", - # Debug - # "debug_apps", ), }, ), @@ -232,7 +228,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 abe1a4534..208b75775 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