From fcd49fcfdd2dfd8753d55fb995205ba5b026831c Mon Sep 17 00:00:00 2001 From: Leeroy <135471162+LeeroysHub@users.noreply.github.com> Date: Wed, 23 Sep 2026 20:46:14 +1000 Subject: [PATCH] SAVED INFO PLUGIN: Moved Saved Info into its own plugin. * Has full host API, no FW included functions. * Emulate and BF working well. * Delete not crashing * Share Load and Unload functons for Config and Saved Info plugin (Smaller code) * Get rid of Saved Info Screen Flash when Emulating from favorites --- application.fam | 16 + helpers/protopirate_types.h | 2 + protopirate_app.c | 89 +++-- protopirate_app_i.h | 11 +- .../plugins/protopirate_saved_info_plugin.c | 377 ++++++++++++++++++ .../plugins/protopirate_saved_info_plugin.h | 23 ++ scenes/protopirate_scene_receiver_config.c | 6 +- scenes/protopirate_scene_saved_info.c | 347 ++-------------- 8 files changed, 505 insertions(+), 366 deletions(-) create mode 100644 scenes/plugins/protopirate_saved_info_plugin.c create mode 100644 scenes/plugins/protopirate_saved_info_plugin.h diff --git a/application.fam b/application.fam index 717ec57..ec42fbc 100644 --- a/application.fam +++ b/application.fam @@ -24,6 +24,8 @@ _MAIN_APP_SOURCES = [ "!protocols", "!protopirate_emulate_plugin.c", "!protopirate_psa_bf_plugin.c", + "!protopirate_saved_info_plugin.c", + "!protopirate_config_plugin.c", "protocols/protocol_items.c", "protocols/protocols_common.c", "!raw_file_reader.c", @@ -435,4 +437,18 @@ App( ], fap_icon_assets="images", fal_embedded=True, +) + +App( + appid="protopirate_saved_info_plugin", + apptype=FlipperAppType.PLUGIN, + entry_point="protopirate_saved_info_plugin_ep", + requires=["proto_pirate"], + cdefines=["PROTOPIRATE_SAVED_INFO_PLUGIN_BUILD"], + sources=[ + "scenes/plugins/protopirate_saved_info_plugin.c", + "protocols/protocols_common.c", + ], + fap_icon_assets="images", + fal_embedded=True, ) \ No newline at end of file diff --git a/helpers/protopirate_types.h b/helpers/protopirate_types.h index 86fa921..5156038 100644 --- a/helpers/protopirate_types.h +++ b/helpers/protopirate_types.h @@ -31,11 +31,13 @@ typedef enum { ProtoPirateCustomEventReceiverInfoUpdate, ProtoPirateCustomEventReceiverInfoEmulate, ProtoPirateCustomEventSavedInfoDelete, + ProtoPirateCustomEventSavedInfoExit, //Bruteforcing PSA & Renault ProtoPirateCustomEventBruteforceStart, ProtoPirateCustomEventBruteforceComplete, // Emulator ProtoPirateCustomEventSavedInfoEmulate, + ProtoPirateCustomEventSavedInfoEmulateDelayedStart, ProtoPirateCustomEventEmulateTransmit, ProtoPirateCustomEventEmulateStop, ProtoPirateCustomEventEmulateExit, diff --git a/protopirate_app.c b/protopirate_app.c index 0ab3b3a..36943c1 100644 --- a/protopirate_app.c +++ b/protopirate_app.c @@ -15,10 +15,13 @@ // ----------------------------------------------------------------------------- // Plugin load / unload // ----------------------------------------------------------------------------- -void config_plugin_unload(ProtoPirateApp* app) { +void config_or_saved_plugin_unload(ProtoPirateApp* app, bool unload_config) { furi_check(app); - app->config_plugin = NULL; + if(unload_config) + app->config_plugin = NULL; + else + app->saved_info_plugin = NULL; if(app->plugin_manager) { plugin_manager_free(app->plugin_manager); @@ -31,15 +34,17 @@ void config_plugin_unload(ProtoPirateApp* app) { } } -bool config_plugin_load( - ProtoPirateApp* app, - const ProtoPirateConfigSceneHostApi* protopirate_config_scene_host_api) { +bool config_or_saved_plugin_load(ProtoPirateApp* app, bool load_config) { furi_check(app); - if(app->config_plugin) return true; + if(load_config) { + if(app->config_plugin) return true; + } else { + if(app->saved_info_plugin) return true; + } if(app->plugin_manager || app->plugin_resolver) { - config_plugin_unload(app); + config_or_saved_plugin_unload(app, load_config); } CompositeApiResolver* resolver = composite_api_resolver_alloc(); @@ -50,16 +55,18 @@ bool config_plugin_load( composite_api_resolver_add(resolver, firmware_api_interface); PluginManager* manager = plugin_manager_alloc( - PROTOPIRATE_CONFIG_PLUGIN_APP_ID, - PROTOPIRATE_CONFIG_PLUGIN_API_VERSION, + (load_config) ? PROTOPIRATE_CONFIG_PLUGIN_APP_ID : PROTOPIRATE_SAVED_INFO_PLUGIN_APP_ID, + (load_config) ? PROTOPIRATE_CONFIG_PLUGIN_API_VERSION : + PROTOPIRATE_SAVED_INFO_PLUGIN_API_VERSION, composite_api_resolver_get(resolver)); if(!manager) { - FURI_LOG_E(TAG, "Failed to allocate config plugin manager"); + FURI_LOG_E(TAG, "Failed to allocate plugin manager"); composite_api_resolver_free(resolver); return false; } - PluginManagerError error = plugin_manager_load_single(manager, CONFIG_PLUGIN_PATH); + PluginManagerError error = plugin_manager_load_single( + manager, (load_config) ? CONFIG_PLUGIN_PATH : SAVED_INFO_PLUGIN_PATH); if(error != PluginManagerErrorNone) { FURI_LOG_E(TAG, "Failed to load config plugin %s: %d", CONFIG_PLUGIN_PATH, (int)error); plugin_manager_free(manager); @@ -67,18 +74,28 @@ bool config_plugin_load( return false; } - const ProtoPirateConfigPlugin* plugin = plugin_manager_get_ep(manager, 0U); - if(!plugin || !plugin->on_enter) { - FURI_LOG_E(TAG, "Config plugin entry point is invalid"); - plugin_manager_free(manager); - composite_api_resolver_free(resolver); - return false; - } + if(load_config) { + const ProtoPirateConfigPlugin* plugin_config = plugin_manager_get_ep(manager, 0U); + if(!plugin_config || !plugin_config->on_enter) { + FURI_LOG_E(TAG, "Config plugin entry point is invalid"); + plugin_manager_free(manager); + composite_api_resolver_free(resolver); + return false; + } + app->config_plugin = plugin_config; + } else { + const ProtoPirateSavedInfoPlugin* plugin_saved_info = plugin_manager_get_ep(manager, 0U); + if(!plugin_saved_info || !plugin_saved_info->on_enter) { + FURI_LOG_E(TAG, "Saved Info plugin entry point is invalid"); + plugin_manager_free(manager); + composite_api_resolver_free(resolver); + return false; + } + app->saved_info_plugin = plugin_saved_info; + }; app->plugin_resolver = resolver; app->plugin_manager = manager; - app->config_plugin = plugin; - plugin->set_host_api(protopirate_config_scene_host_api); return true; } @@ -225,7 +242,7 @@ ProtoPirateApp* protopirate_app_alloc() { settings.auto_save, settings.hopping_enabled); - config_plugin_load(app, NULL); + config_or_saved_plugin_load(app, true); app->car_models_count = app->config_plugin->car_model_get_count(); app->selected_model = malloc(sizeof(ProtoPirateCarModel)); app->selected_model->name = furi_string_alloc(); @@ -251,7 +268,7 @@ ProtoPirateApp* protopirate_app_alloc() { protopirate_preset_init(app, preset_name, frequency, preset_data, preset_data_size); } - config_plugin_unload(app); + config_or_saved_plugin_unload(app, true); // Apply hopping state from settings app->txrx->hopper_state = settings.hopping_enabled ? ProtoPirateHopperStateRunning : @@ -397,6 +414,7 @@ void protopirate_app_free(ProtoPirateApp* app) { } int32_t protopirate_app(char* p) { + //Stop charging while running the app. furi_hal_power_suppress_charge_enter(); ProtoPirateApp* protopirate_app = protopirate_app_alloc(); @@ -408,31 +426,24 @@ int32_t protopirate_app(char* p) { // Handle Command line PSF that may have been passed to us bool load_saved = (p && strlen(p)); if(load_saved) protopirate_app->loaded_file_path = furi_string_alloc_set(p); + + //We now jump straight to emulate scene from Browser. scene_manager_next_scene( protopirate_app->scene_manager, - (load_saved) ? ProtoPirateSceneSavedInfo : ProtoPirateSceneStart); + (load_saved) ? ((protopirate_app->emulate_feature_enabled) ? ProtoPirateSceneEmulate : + ProtoPirateSceneSavedInfo) : + ProtoPirateSceneStart); - //We now jump straight to emulate scene from Browser. If the user wanted the key to look at, just click back. - if(load_saved) { -#ifdef ENABLE_EMULATE_FEATURE - if(protopirate_app->emulate_feature_enabled) { - view_dispatcher_send_custom_event( - protopirate_app->view_dispatcher, ProtoPirateCustomEventSavedInfoEmulate); - notification_message(protopirate_app->notifications, &sequence_success); - } else { -#endif - view_dispatcher_send_custom_event( - protopirate_app->view_dispatcher, ProtoPirateCustomEventReceiverInfoSave); -#ifdef ENABLE_EMULATE_FEATURE - } -#endif + //Pop up the beep if we are startng emulate. + if(load_saved && protopirate_app->emulate_feature_enabled) { + notification_message(protopirate_app->notifications, &sequence_success); } + //Run the App view_dispatcher_run(protopirate_app->view_dispatcher); + //Free the App and allow chargin again. protopirate_app_free(protopirate_app); - furi_hal_power_suppress_charge_exit(); - return 0; } diff --git a/protopirate_app_i.h b/protopirate_app_i.h index 2450738..5d02d0c 100644 --- a/protopirate_app_i.h +++ b/protopirate_app_i.h @@ -33,6 +33,7 @@ #include "scenes/plugins/protopirate_emulate_plugin.h" #endif #include "scenes/plugins/protopirate_config_plugin.h" +#include "scenes/plugins/protopirate_saved_info_plugin.h" #include "scenes/plugins/protopirate_psa_bf_plugin.h" #include "scenes/plugins/protopirate_tool_scene_plugin.h" #include "helpers/protopirate_views.h" @@ -43,7 +44,8 @@ #include #include "helpers/protopirate_settings.h" -#define CONFIG_PLUGIN_PATH APP_ASSETS_PATH("plugins/protopirate_config_plugin.fal") +#define CONFIG_PLUGIN_PATH APP_ASSETS_PATH("plugins/protopirate_config_plugin.fal") +#define SAVED_INFO_PLUGIN_PATH APP_ASSETS_PATH("plugins/protopirate_saved_info_plugin.fal") #define PROTOPIRATE_KEYSTORE_DIR_NAME APP_ASSETS_PATH("encrypted") @@ -111,6 +113,7 @@ struct ProtoPirateApp { uint8_t emulate_nav_pending; #endif const ProtoPirateConfigPlugin* config_plugin; + const ProtoPirateSavedInfoPlugin* saved_info_plugin; CompositeApiResolver* psa_bf_plugin_resolver; PluginManager* psa_bf_plugin_manager; const ProtoPiratePsaBfPlugin* psa_bf_plugin; @@ -143,10 +146,8 @@ bool protopirate_tool_scene_on_event(void* app, SceneManagerEvent event); void protopirate_tool_scene_on_exit(void* app); void protopirate_tool_scene_plugin_release(ProtoPirateApp* app); -bool config_plugin_load( - ProtoPirateApp* app, - const ProtoPirateConfigSceneHostApi* protopirate_config_scene_host_api); -void config_plugin_unload(ProtoPirateApp* app); +bool config_or_saved_plugin_load(ProtoPirateApp* app, bool load_config); +void config_or_saved_plugin_unload(ProtoPirateApp* app, bool unload_config); void protopirate_app_free(ProtoPirateApp* app); diff --git a/scenes/plugins/protopirate_saved_info_plugin.c b/scenes/plugins/protopirate_saved_info_plugin.c new file mode 100644 index 0000000..abe24c6 --- /dev/null +++ b/scenes/plugins/protopirate_saved_info_plugin.c @@ -0,0 +1,377 @@ +#include "protopirate_saved_info_plugin.h" +#include "../../protopirate_app_i.h" +#include "../../helpers/protopirate_storage.h" +#include "../../protocols/protocols_common.h" +#include "../../protocols/protocol_items.h" +#include "protopirate_saved_info_plugin_icons.h" + +static const ProtoPirateSavedInfoSceneHostApi* g_saved_info_scene_host_api = NULL; + +#define TAG "ProtoPirateSavedInfoPlugin" + +#define STATE_EMULATE 0 +#define STATE_BF 1 + +static void plugin_protopirate_scene_saved_info_widget_callback( + GuiButtonType result, + InputType type, + void* context) { + ProtoPirateApp* app = context; + + if((result == GuiButtonTypeLeft) && (type == InputTypeShort)) { + const uint32_t left_event = + (scene_manager_get_scene_state(app->scene_manager, ProtoPirateSceneSavedInfo) == + STATE_BF) ? + ProtoPirateCustomEventBruteforceStart : + ProtoPirateCustomEventSavedInfoEmulate; + view_dispatcher_send_custom_event(app->view_dispatcher, left_event); + } else if(result == GuiButtonTypeRight && (type == InputTypeShort)) { + //Send delete event and get user confirmation to delete. + view_dispatcher_send_custom_event( + app->view_dispatcher, ProtoPirateCustomEventSavedInfoDelete); + } +} + +void plugin_protopirate_scene_saved_info_on_enter(void* context) { + furi_check(context); + ProtoPirateApp* app = context; + Storage* storage = NULL; + FlipperFormat* ff = NULL; + FuriString* info_str = NULL; + FuriString* temp_str = NULL; + bool success = false; + + FURI_LOG_I(TAG, "=== ENTER START ==="); + + //protopirate_release_shared_radio_state(app); + + if(!g_saved_info_scene_host_api->ensure_widget(app)) { + notification_message(app->notifications, &sequence_error); + view_dispatcher_send_custom_event( + app->view_dispatcher, ProtoPirateCustomEventSavedInfoExit); + return; + } + + // Reset widget first + widget_reset(app->widget); + + // Validate file path + if(!app->loaded_file_path || furi_string_empty(app->loaded_file_path)) { + FURI_LOG_E(TAG, "No file path"); + widget_add_string_element( + app->widget, 64, 32, AlignCenter, AlignCenter, FontPrimary, "No file selected"); + goto switch_view; + } + + FURI_LOG_I(TAG, "Path: %s", furi_string_get_cstr(app->loaded_file_path)); + + // Allocate strings first (no I/O) + info_str = furi_string_alloc(); + temp_str = furi_string_alloc(); + if(!info_str || !temp_str) { + FURI_LOG_E(TAG, "String alloc failed"); + widget_add_string_element( + app->widget, 64, 32, AlignCenter, AlignCenter, FontPrimary, "Memory error"); + goto cleanup; + } + + FURI_LOG_I(TAG, "Strings allocated"); + + // Open storage + FURI_LOG_I(TAG, "Opening storage..."); + storage = furi_record_open(RECORD_STORAGE); + if(!storage) { + FURI_LOG_E(TAG, "Storage open failed"); + widget_add_string_element( + app->widget, 64, 32, AlignCenter, AlignCenter, FontPrimary, "Storage error"); + goto cleanup; + } + + FURI_LOG_I(TAG, "Storage opened"); + + // Allocate flipper format + FURI_LOG_I(TAG, "Allocating FF..."); + ff = flipper_format_file_alloc(storage); + if(!ff) { + FURI_LOG_E(TAG, "FF alloc failed"); + widget_add_string_element( + app->widget, 64, 32, AlignCenter, AlignCenter, FontPrimary, "Memory error"); + goto cleanup; + } + + FURI_LOG_I(TAG, "FF allocated"); + + // Open file + FURI_LOG_I(TAG, "Opening file..."); + if(!flipper_format_file_open_existing(ff, furi_string_get_cstr(app->loaded_file_path))) { + FURI_LOG_E(TAG, "File open failed"); + widget_add_string_element( + app->widget, 64, 32, AlignCenter, AlignCenter, FontPrimary, "File open failed"); + goto cleanup; + } + + FURI_LOG_I(TAG, "File opened, reading..."); + bool offers_bf = false; + + // Read fields + uint32_t temp_data = 0; + app->emulate_disabled_for_loaded = true; + + flipper_format_rewind(ff); + if(flipper_format_read_string(ff, FF_PROTOCOL, temp_str)) { + const char* protocol_name = furi_string_get_cstr(temp_str); + furi_string_cat_printf(info_str, "Protocol: %s\n", protocol_name); + + //Can we emulate this type? + app->emulate_disabled_for_loaded = + !g_saved_info_scene_host_api->protocol_catalog_can_tx(protocol_name); + + //Do we need to offer a Brute Force Option? + offers_bf = g_saved_info_scene_host_api->protocol_catalog_offers_bruteforce(protocol_name); + } + + flipper_format_rewind(ff); + if(flipper_format_read_uint32(ff, FF_FREQUENCY, &temp_data, 1)) { + furi_string_cat_printf( + info_str, "Freq: %lu.%02lu MHz\n", temp_data / 1000000, (temp_data % 1000000) / 10000); + } + + flipper_format_rewind(ff); + if(flipper_format_read_string(ff, FF_PRESET, temp_str)) { + // Convert full preset name to short name + const char* preset_name = pp_get_short_preset_name(furi_string_get_cstr(temp_str)); + furi_string_cat_printf(info_str, "Modulation: %s\n", preset_name); + } + + flipper_format_rewind(ff); + if(flipper_format_read_uint32(ff, FF_SERIAL, &temp_data, 1)) { + furi_string_cat_printf(info_str, "Serial: %08lX\n", temp_data); + } + + flipper_format_rewind(ff); + if(flipper_format_read_uint32(ff, FF_BTN, &temp_data, 1)) { + furi_string_cat_printf(info_str, "Button: %02X\n", (uint8_t)temp_data); + } + + flipper_format_rewind(ff); + if(flipper_format_read_uint32(ff, FF_CNT, &temp_data, 1)) { + furi_string_cat_printf(info_str, "Counter: %04lX\n", temp_data); + } + + flipper_format_rewind(ff); + if(flipper_format_read_uint32(ff, "Checksum", &temp_data, 1)) { + furi_string_cat_printf(info_str, "Checksum: %02X\n", (uint8_t)temp_data); + } + + flipper_format_rewind(ff); + if(flipper_format_read_uint32(ff, "CRC", &temp_data, 1)) { + furi_string_cat_printf(info_str, "CRC: %02X\n", (uint8_t)temp_data); + } + + flipper_format_rewind(ff); + if(flipper_format_read_uint32(ff, FF_TYPE, &temp_data, 1)) { + furi_string_cat_printf(info_str, "Type: %02X\n", (uint8_t)temp_data); + } + + flipper_format_rewind(ff); + if(flipper_format_read_uint32(ff, "KeyIdx", &temp_data, 1)) { + furi_string_cat_printf(info_str, "KeyIdx: %d\n", (uint8_t)temp_data); + } + + flipper_format_rewind(ff); + if(flipper_format_read_string(ff, FF_KEY, temp_str)) { + furi_string_cat_printf(info_str, "Key1: %s\n", furi_string_get_cstr(temp_str)); + } + + flipper_format_rewind(ff); + if(flipper_format_read_string(ff, "Key_2", temp_str)) { + furi_string_cat_printf(info_str, "Key2: %s\n", furi_string_get_cstr(temp_str)); + } + + flipper_format_rewind(ff); + if(flipper_format_read_uint32(ff, "ValidationField", &temp_data, 1)) { + furi_string_cat_printf(info_str, "ValField: %04X\n", (uint16_t)temp_data); + } + + flipper_format_rewind(ff); + if(flipper_format_read_uint32(ff, "Seed", &temp_data, 1)) { + furi_string_cat_printf(info_str, "Seed: %02X\n", (uint8_t)temp_data); + } + + flipper_format_rewind(ff); + if(flipper_format_read_string(ff, FF_MANUFACTURE, temp_str)) { + furi_string_cat_printf(info_str, "Manufacture: %s\n", furi_string_get_cstr(temp_str)); + } + + FURI_LOG_I(TAG, "Read complete, len=%u", furi_string_size(info_str)); + success = true; + +cleanup: + // Now do widget operations + if(success && info_str && furi_string_size(info_str) > 0) { + FURI_LOG_I(TAG, "Adding scroll element"); + widget_add_text_scroll_element(app->widget, 0, 0, 128, 50, furi_string_get_cstr(info_str)); + + bool needs_bf = false; + if(offers_bf && g_saved_info_scene_host_api->psa_bf_plugin_ensure_loaded(app) && + app->psa_bf_plugin) { + needs_bf = app->psa_bf_plugin->widget_left_should_bruteforce(app, ff); + } + + g_saved_info_scene_host_api->psa_bf_plugin_unload_if_idle(app); + if(needs_bf) { + scene_manager_set_scene_state(app->scene_manager, ProtoPirateSceneSavedInfo, STATE_BF); + widget_add_button_element( + app->widget, + GuiButtonTypeLeft, + "BF", + plugin_protopirate_scene_saved_info_widget_callback, + app); + } else { + scene_manager_set_scene_state( + app->scene_manager, ProtoPirateSceneSavedInfo, STATE_EMULATE); +#ifdef ENABLE_EMULATE_FEATURE + if(app->emulate_feature_enabled && !app->emulate_disabled_for_loaded) { + widget_add_button_element( + app->widget, + GuiButtonTypeLeft, + "Emulate", + plugin_protopirate_scene_saved_info_widget_callback, + app); + } +#endif + } + + widget_add_button_element( + app->widget, + GuiButtonTypeRight, + "Delete", + plugin_protopirate_scene_saved_info_widget_callback, + app); + } + + // Close file and storage BEFORE widget operations + if(ff) { + flipper_format_free(ff); + ff = NULL; + } + if(storage) { + furi_record_close(RECORD_STORAGE); + storage = NULL; + } + + FURI_LOG_I(TAG, "Storage closed"); + + // Free strings + if(temp_str) furi_string_free(temp_str); + if(info_str) furi_string_free(info_str); + + FURI_LOG_I(TAG, "Cleanup done"); + +switch_view: + FURI_LOG_I(TAG, "Switching to widget"); + view_dispatcher_switch_to_view(app->view_dispatcher, ProtoPirateViewWidget); + FURI_LOG_I(TAG, "=== ENTER DONE ==="); +} + +bool plugin_protopirate_scene_saved_info_on_event(void* context, SceneManagerEvent event) { + ProtoPirateApp* app = context; + bool consumed = false; + + //load_emu* = false; + if(event.type == SceneManagerEventTypeTick) { + if(app->psa_bf_plugin && app->psa_bf_plugin->is_running(app)) { + app->psa_bf_plugin->on_scene_event(app, ProtoPiratePsaBfContextSavedInfo, event); + consumed = true; + } + } else if(event.type == SceneManagerEventTypeBack) { + return ( + app->psa_bf_plugin && app->psa_bf_plugin->is_running && + app->psa_bf_plugin->on_scene_event(app, ProtoPiratePsaBfContextReceiverInfo, event)); + } else if(event.type == SceneManagerEventTypeCustom) { + if(event.event == ProtoPirateCustomEventSavedInfoDelete) { + FURI_LOG_I(TAG, "Delete requested"); + if(app->loaded_file_path && !furi_string_empty(app->loaded_file_path)) { + DialogMessage* message = dialog_message_alloc(); + dialog_message_set_buttons(message, "Delete", NULL, "Keep"); + dialog_message_set_icon(message, &I_WarningDolphin_45x42, 0, 12); + dialog_message_set_header( + message, "Confirm Delete Action", 64, 0, AlignCenter, AlignTop); + dialog_message_set_text( + message, + "Are you sure you\nwant to delete\nthis file?", + 50, + 14, + AlignLeft, + AlignTop); + DialogMessageButton dialog_result = dialog_message_show(app->dialogs, message); + dialog_message_free(message); + + //Delete if the user said yes. + if(dialog_result == DialogMessageButtonLeft) { + notification_message(app->notifications, &sequence_semi_success); + g_saved_info_scene_host_api->storage_delete_file( + furi_string_get_cstr(app->loaded_file_path)); + + view_dispatcher_send_custom_event( + app->view_dispatcher, ProtoPirateCustomEventSavedInfoExit); + } + } + consumed = true; + } + if(event.event == ProtoPirateCustomEventBruteforceStart || + event.event == ProtoPirateCustomEventBruteforceComplete) { + if(g_saved_info_scene_host_api->psa_bf_plugin_ensure_loaded(app) && + app->psa_bf_plugin && + app->psa_bf_plugin->on_scene_event(app, ProtoPiratePsaBfContextSavedInfo, event)) { + } + if(event.event == ProtoPirateCustomEventBruteforceComplete) + plugin_protopirate_scene_saved_info_on_enter(app); + consumed = true; + } + +#ifdef ENABLE_EMULATE_FEATURE + if(event.event == ProtoPirateCustomEventSavedInfoEmulate && app->emulate_feature_enabled && + !app->emulate_disabled_for_loaded) { + FURI_LOG_I(TAG, "Emulate requested"); + + //Send custom event back to the scene, so it can start emulate for us and avoid the crashes. + view_dispatcher_send_custom_event( + app->view_dispatcher, ProtoPirateCustomEventSavedInfoEmulateDelayedStart); + + consumed = true; + } +#endif + } + + return consumed; +} + +void plugin_protopirate_scene_saved_info_on_exit(void* context) { + ProtoPirateApp* app = context; + UNUSED(app); + FURI_LOG_I(TAG, "Exiting SavedInfo scene"); + widget_reset(app->widget); +} + +void saved_info_plugin_set_host_api(const ProtoPirateSavedInfoSceneHostApi* host_api) { + g_saved_info_scene_host_api = host_api; +} + +static const ProtoPirateSavedInfoPlugin protopirate_saved_info_plugin = { + .plugin_name = "ProtoPirate Saved Info", + .on_enter = plugin_protopirate_scene_saved_info_on_enter, + .on_event = plugin_protopirate_scene_saved_info_on_event, + .on_exit = plugin_protopirate_scene_saved_info_on_exit, + .set_host_api = saved_info_plugin_set_host_api, +}; + +static const FlipperAppPluginDescriptor protopirate_saved_info_plugin_descriptor = { + .appid = PROTOPIRATE_SAVED_INFO_PLUGIN_APP_ID, + .ep_api_version = PROTOPIRATE_SAVED_INFO_PLUGIN_API_VERSION, + .entry_point = &protopirate_saved_info_plugin, +}; + +const FlipperAppPluginDescriptor* protopirate_saved_info_plugin_ep(void) { + return &protopirate_saved_info_plugin_descriptor; +} diff --git a/scenes/plugins/protopirate_saved_info_plugin.h b/scenes/plugins/protopirate_saved_info_plugin.h new file mode 100644 index 0000000..4a115c1 --- /dev/null +++ b/scenes/plugins/protopirate_saved_info_plugin.h @@ -0,0 +1,23 @@ +#pragma once +#include + +#define PROTOPIRATE_SAVED_INFO_PLUGIN_APP_ID "protopirate_saved_info_plugin" +#define PROTOPIRATE_SAVED_INFO_PLUGIN_API_VERSION 1U + +typedef struct ProtoPirateApp ProtoPirateApp; +typedef struct ProtoPirateSavedInfoSceneHostApi { + bool (*ensure_widget)(ProtoPirateApp* app); + bool (*psa_bf_plugin_ensure_loaded)(ProtoPirateApp* app); + void (*psa_bf_plugin_unload_if_idle)(ProtoPirateApp* app); + bool (*protocol_catalog_can_tx)(const char* protocol_name); + bool (*storage_delete_file)(const char* file_path); + bool (*protocol_catalog_offers_bruteforce)(const char* protocol_name); +} ProtoPirateSavedInfoSceneHostApi; + +typedef struct ProtoPirateSavedInfoPlugin { + const char* plugin_name; + void (*on_enter)(void* app); + void (*on_exit)(void* app); + bool (*on_event)(void* app, SceneManagerEvent event); + void (*set_host_api)(const ProtoPirateSavedInfoSceneHostApi* host_api); +} ProtoPirateSavedInfoPlugin; diff --git a/scenes/protopirate_scene_receiver_config.c b/scenes/protopirate_scene_receiver_config.c index 7dc79b2..ef3582f 100644 --- a/scenes/protopirate_scene_receiver_config.c +++ b/scenes/protopirate_scene_receiver_config.c @@ -11,12 +11,12 @@ static const ProtoPirateConfigSceneHostApi protopirate_config_scene_host_api = { void protopirate_scene_receiver_config_on_enter(void* context) { ProtoPirateApp* app = context; - if(!config_plugin_load(app, &protopirate_config_scene_host_api)) { + if(!config_or_saved_plugin_load(app, true)) { notification_message(app->notifications, &sequence_error); scene_manager_previous_scene(app->scene_manager); return; } - + app->config_plugin->set_host_api(&protopirate_config_scene_host_api); app->config_plugin->on_enter(app); } @@ -37,5 +37,5 @@ bool protopirate_scene_receiver_config_on_event(void* context, SceneManagerEvent void protopirate_scene_receiver_config_on_exit(void* context) { ProtoPirateApp* app = context; - config_plugin_unload(app); + config_or_saved_plugin_unload(app, true); } diff --git a/scenes/protopirate_scene_saved_info.c b/scenes/protopirate_scene_saved_info.c index 87257b7..39c884c 100644 --- a/scenes/protopirate_scene_saved_info.c +++ b/scenes/protopirate_scene_saved_info.c @@ -1,343 +1,52 @@ // scenes/protopirate_scene_saved_info.c #include "../protopirate_app_i.h" -#include "../helpers/protopirate_storage.h" +#include "plugins/protopirate_saved_info_plugin.h" #include "../helpers/protopirate_psa_bf_host.h" -#include "../protocols/protocol_items.h" -#include "../protocols/protocols_common.h" +#include "../helpers/protopirate_storage.h" -#include "proto_pirate_icons.h" - -#define TAG "ProtoPirateSceneSavedInfo" - -#define STATE_EMULATE 0 -#define STATE_BF 1 - -static void protopirate_scene_saved_info_widget_callback( - GuiButtonType result, - InputType type, - void* context) { - ProtoPirateApp* app = context; - - if((result == GuiButtonTypeLeft) && (type == InputTypeShort)) { - const uint32_t left_event = - (scene_manager_get_scene_state(app->scene_manager, ProtoPirateSceneSavedInfo) == - STATE_BF) ? - ProtoPirateCustomEventBruteforceStart : - ProtoPirateCustomEventSavedInfoEmulate; - view_dispatcher_send_custom_event(app->view_dispatcher, left_event); - } else if(result == GuiButtonTypeRight && (type == InputTypeShort)) { - //Send delete event and get user confirmation to delete. - view_dispatcher_send_custom_event( - app->view_dispatcher, ProtoPirateCustomEventSavedInfoDelete); - } -} +static const ProtoPirateSavedInfoSceneHostApi protopirate_saved_info_scene_host_api = { + .ensure_widget = protopirate_ensure_widget, + .psa_bf_plugin_ensure_loaded = protopirate_psa_bf_plugin_ensure_loaded, + .psa_bf_plugin_unload_if_idle = protopirate_psa_bf_plugin_unload_if_idle, + .protocol_catalog_can_tx = protopirate_protocol_catalog_can_tx, + .storage_delete_file = protopirate_storage_delete_file, + .protocol_catalog_offers_bruteforce = protopirate_protocol_catalog_offers_bruteforce, +}; void protopirate_scene_saved_info_on_enter(void* context) { - furi_check(context); ProtoPirateApp* app = context; - Storage* storage = NULL; - FlipperFormat* ff = NULL; - FuriString* info_str = NULL; - FuriString* temp_str = NULL; - bool success = false; - FURI_LOG_I(TAG, "=== ENTER START ==="); - - protopirate_release_shared_radio_state(app); - - if(!protopirate_ensure_widget(app)) { + if(!config_or_saved_plugin_load(app, false)) { notification_message(app->notifications, &sequence_error); scene_manager_previous_scene(app->scene_manager); return; } - // Reset widget first - widget_reset(app->widget); - - // Validate file path - if(!app->loaded_file_path || furi_string_empty(app->loaded_file_path)) { - FURI_LOG_E(TAG, "No file path"); - widget_add_string_element( - app->widget, 64, 32, AlignCenter, AlignCenter, FontPrimary, "No file selected"); - goto switch_view; - } - - FURI_LOG_I(TAG, "Path: %s", furi_string_get_cstr(app->loaded_file_path)); - - // Allocate strings first (no I/O) - info_str = furi_string_alloc(); - temp_str = furi_string_alloc(); - if(!info_str || !temp_str) { - FURI_LOG_E(TAG, "String alloc failed"); - widget_add_string_element( - app->widget, 64, 32, AlignCenter, AlignCenter, FontPrimary, "Memory error"); - goto cleanup; - } - - FURI_LOG_I(TAG, "Strings allocated"); - - // Open storage - FURI_LOG_I(TAG, "Opening storage..."); - storage = furi_record_open(RECORD_STORAGE); - if(!storage) { - FURI_LOG_E(TAG, "Storage open failed"); - widget_add_string_element( - app->widget, 64, 32, AlignCenter, AlignCenter, FontPrimary, "Storage error"); - goto cleanup; - } - - FURI_LOG_I(TAG, "Storage opened"); - - // Allocate flipper format - FURI_LOG_I(TAG, "Allocating FF..."); - ff = flipper_format_file_alloc(storage); - if(!ff) { - FURI_LOG_E(TAG, "FF alloc failed"); - widget_add_string_element( - app->widget, 64, 32, AlignCenter, AlignCenter, FontPrimary, "Memory error"); - goto cleanup; - } - - FURI_LOG_I(TAG, "FF allocated"); - - // Open file - FURI_LOG_I(TAG, "Opening file..."); - if(!flipper_format_file_open_existing(ff, furi_string_get_cstr(app->loaded_file_path))) { - FURI_LOG_E(TAG, "File open failed"); - widget_add_string_element( - app->widget, 64, 32, AlignCenter, AlignCenter, FontPrimary, "File open failed"); - goto cleanup; - } - - FURI_LOG_I(TAG, "File opened, reading..."); - bool offers_bf = false; - - // Read fields - uint32_t temp_data = 0; - app->emulate_disabled_for_loaded = true; - - flipper_format_rewind(ff); - if(flipper_format_read_string(ff, FF_PROTOCOL, temp_str)) { - const char* protocol_name = furi_string_get_cstr(temp_str); - furi_string_cat_printf(info_str, "Protocol: %s\n", protocol_name); - - //Can we emulate this type? - app->emulate_disabled_for_loaded = !protopirate_protocol_catalog_can_tx(protocol_name); - - //Do we need to offer a Brute Force Option? - offers_bf = protopirate_protocol_catalog_offers_bruteforce(protocol_name); - } - - flipper_format_rewind(ff); - if(flipper_format_read_uint32(ff, FF_FREQUENCY, &temp_data, 1)) { - furi_string_cat_printf( - info_str, "Freq: %lu.%02lu MHz\n", temp_data / 1000000, (temp_data % 1000000) / 10000); - } - - flipper_format_rewind(ff); - if(flipper_format_read_string(ff, FF_PRESET, temp_str)) { - // Convert full preset name to short name - const char* preset_name = pp_get_short_preset_name(furi_string_get_cstr(temp_str)); - furi_string_cat_printf(info_str, "Modulation: %s\n", preset_name); - } - - flipper_format_rewind(ff); - if(flipper_format_read_uint32(ff, FF_SERIAL, &temp_data, 1)) { - furi_string_cat_printf(info_str, "Serial: %08lX\n", temp_data); - } - - flipper_format_rewind(ff); - if(flipper_format_read_uint32(ff, FF_BTN, &temp_data, 1)) { - furi_string_cat_printf(info_str, "Button: %02X\n", (uint8_t)temp_data); - } - - flipper_format_rewind(ff); - if(flipper_format_read_uint32(ff, FF_CNT, &temp_data, 1)) { - furi_string_cat_printf(info_str, "Counter: %04lX\n", temp_data); - } - - flipper_format_rewind(ff); - if(flipper_format_read_uint32(ff, "Checksum", &temp_data, 1)) { - furi_string_cat_printf(info_str, "Checksum: %02X\n", (uint8_t)temp_data); - } - - flipper_format_rewind(ff); - if(flipper_format_read_uint32(ff, "CRC", &temp_data, 1)) { - furi_string_cat_printf(info_str, "CRC: %02X\n", (uint8_t)temp_data); - } - - flipper_format_rewind(ff); - if(flipper_format_read_uint32(ff, FF_TYPE, &temp_data, 1)) { - furi_string_cat_printf(info_str, "Type: %02X\n", (uint8_t)temp_data); - } - - flipper_format_rewind(ff); - if(flipper_format_read_uint32(ff, "KeyIdx", &temp_data, 1)) { - furi_string_cat_printf(info_str, "KeyIdx: %d\n", (uint8_t)temp_data); - } - - flipper_format_rewind(ff); - if(flipper_format_read_string(ff, FF_KEY, temp_str)) { - furi_string_cat_printf(info_str, "Key1: %s\n", furi_string_get_cstr(temp_str)); - } - - flipper_format_rewind(ff); - if(flipper_format_read_string(ff, "Key_2", temp_str)) { - furi_string_cat_printf(info_str, "Key2: %s\n", furi_string_get_cstr(temp_str)); - } - - flipper_format_rewind(ff); - if(flipper_format_read_uint32(ff, "ValidationField", &temp_data, 1)) { - furi_string_cat_printf(info_str, "ValField: %04X\n", (uint16_t)temp_data); - } - - flipper_format_rewind(ff); - if(flipper_format_read_uint32(ff, "Seed", &temp_data, 1)) { - furi_string_cat_printf(info_str, "Seed: %02X\n", (uint8_t)temp_data); - } - - flipper_format_rewind(ff); - if(flipper_format_read_string(ff, FF_MANUFACTURE, temp_str)) { - furi_string_cat_printf(info_str, "Manufacture: %s\n", furi_string_get_cstr(temp_str)); - } - - FURI_LOG_I(TAG, "Read complete, len=%u", furi_string_size(info_str)); - success = true; - -cleanup: - // Now do widget operations - if(success && info_str && furi_string_size(info_str) > 0) { - FURI_LOG_I(TAG, "Adding scroll element"); - widget_add_text_scroll_element(app->widget, 0, 0, 128, 50, furi_string_get_cstr(info_str)); - - bool needs_bf = false; - if(offers_bf && protopirate_psa_bf_plugin_ensure_loaded(app) && app->psa_bf_plugin) { - needs_bf = app->psa_bf_plugin->widget_left_should_bruteforce(app, ff); - } - - protopirate_psa_bf_plugin_unload_if_idle(app); - if(needs_bf) { - scene_manager_set_scene_state(app->scene_manager, ProtoPirateSceneSavedInfo, STATE_BF); - widget_add_button_element( - app->widget, - GuiButtonTypeLeft, - "BF", - protopirate_scene_saved_info_widget_callback, - app); - } else { - scene_manager_set_scene_state( - app->scene_manager, ProtoPirateSceneSavedInfo, STATE_EMULATE); -#ifdef ENABLE_EMULATE_FEATURE - if(app->emulate_feature_enabled && !app->emulate_disabled_for_loaded) { - widget_add_button_element( - app->widget, - GuiButtonTypeLeft, - "Emulate", - protopirate_scene_saved_info_widget_callback, - app); - } -#endif - } - - widget_add_button_element( - app->widget, - GuiButtonTypeRight, - "Delete", - protopirate_scene_saved_info_widget_callback, - app); - } - - // Close file and storage BEFORE widget operations - if(ff) { - flipper_format_free(ff); - ff = NULL; - } - if(storage) { - furi_record_close(RECORD_STORAGE); - storage = NULL; - } - - FURI_LOG_I(TAG, "Storage closed"); - - // Free strings - if(temp_str) furi_string_free(temp_str); - if(info_str) furi_string_free(info_str); - - FURI_LOG_I(TAG, "Cleanup done"); - -switch_view: - FURI_LOG_I(TAG, "Switching to widget"); - view_dispatcher_switch_to_view(app->view_dispatcher, ProtoPirateViewWidget); - FURI_LOG_I(TAG, "=== ENTER DONE ==="); + app->saved_info_plugin->set_host_api(&protopirate_saved_info_scene_host_api); + app->saved_info_plugin->on_enter(app); } bool protopirate_scene_saved_info_on_event(void* context, SceneManagerEvent event) { - ProtoPirateApp* app = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeTick) { - if(app->psa_bf_plugin && app->psa_bf_plugin->is_running(app)) { - app->psa_bf_plugin->on_scene_event(app, ProtoPiratePsaBfContextSavedInfo, event); - consumed = true; + //I can't set the next scene from inside the plugin, or it causes crazy crashes. + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == ProtoPirateCustomEventSavedInfoEmulateDelayedStart) { + //Start Emulate Scene. + scene_manager_next_scene( + ((ProtoPirateApp*)context)->scene_manager, ProtoPirateSceneEmulate); + return true; + } else if(event.event == ProtoPirateCustomEventSavedInfoExit) { + scene_manager_previous_scene(((ProtoPirateApp*)context)->scene_manager); + return true; } - - } else if(event.type == SceneManagerEventTypeBack) { - return ( - app->psa_bf_plugin && app->psa_bf_plugin->is_running && - app->psa_bf_plugin->on_scene_event(app, ProtoPiratePsaBfContextReceiverInfo, event)); - } else if(event.type == SceneManagerEventTypeCustom) { - if(event.event == ProtoPirateCustomEventSavedInfoDelete) { - FURI_LOG_I(TAG, "Delete requested"); - if(app->loaded_file_path && !furi_string_empty(app->loaded_file_path)) { - DialogMessage* message = dialog_message_alloc(); - dialog_message_set_buttons(message, "Delete", NULL, "Keep"); - dialog_message_set_icon(message, &I_WarningDolphin_45x42, 0, 12); - dialog_message_set_header( - message, "Confirm Delete Action", 64, 0, AlignCenter, AlignTop); - dialog_message_set_text( - message, - "Are you sure you\nwant to delete\nthis file?", - 50, - 14, - AlignLeft, - AlignTop); - DialogMessageButton dialog_result = dialog_message_show(app->dialogs, message); - dialog_message_free(message); - - //Delete if the user said yes. - if(dialog_result == DialogMessageButtonLeft) { - notification_message(app->notifications, &sequence_semi_success); - protopirate_storage_delete_file(furi_string_get_cstr(app->loaded_file_path)); - scene_manager_previous_scene(app->scene_manager); - } - } - consumed = true; - } - if(event.event == ProtoPirateCustomEventBruteforceStart || - event.event == ProtoPirateCustomEventBruteforceComplete) { - if(protopirate_psa_bf_plugin_ensure_loaded(app) && app->psa_bf_plugin && - app->psa_bf_plugin->on_scene_event(app, ProtoPiratePsaBfContextSavedInfo, event)) { - } - if(event.event == ProtoPirateCustomEventBruteforceComplete) - protopirate_scene_saved_info_on_enter(app); - consumed = true; - } - -#ifdef ENABLE_EMULATE_FEATURE - if(event.event == ProtoPirateCustomEventSavedInfoEmulate && app->emulate_feature_enabled && - !app->emulate_disabled_for_loaded) { - FURI_LOG_I(TAG, "Emulate requested"); - scene_manager_next_scene(app->scene_manager, ProtoPirateSceneEmulate); - consumed = true; - } -#endif } - return consumed; + //Handle Saved Info event in plugin. + return ((ProtoPirateApp*)context)->saved_info_plugin->on_event(context, event); } void protopirate_scene_saved_info_on_exit(void* context) { ProtoPirateApp* app = context; - FURI_LOG_I(TAG, "Exiting SavedInfo scene"); - widget_reset(app->widget); + UNUSED(app); + + config_or_saved_plugin_unload(app, false); }