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
This commit is contained in:
Leeroy
2026-09-25 18:26:33 +10:00
parent 48522ec4f6
commit fcd49fcfdd
8 changed files with 505 additions and 366 deletions
+16
View File
@@ -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,
)
+2
View File
@@ -31,11 +31,13 @@ typedef enum {
ProtoPirateCustomEventReceiverInfoUpdate,
ProtoPirateCustomEventReceiverInfoEmulate,
ProtoPirateCustomEventSavedInfoDelete,
ProtoPirateCustomEventSavedInfoExit,
//Bruteforcing PSA & Renault
ProtoPirateCustomEventBruteforceStart,
ProtoPirateCustomEventBruteforceComplete,
// Emulator
ProtoPirateCustomEventSavedInfoEmulate,
ProtoPirateCustomEventSavedInfoEmulateDelayedStart,
ProtoPirateCustomEventEmulateTransmit,
ProtoPirateCustomEventEmulateStop,
ProtoPirateCustomEventEmulateExit,
+50 -39
View File
@@ -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;
}
+6 -5
View File
@@ -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 <loader/firmware_api/firmware_api.h>
#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);
@@ -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;
}
@@ -0,0 +1,23 @@
#pragma once
#include <gui/scene_manager.h>
#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;
+3 -3
View File
@@ -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);
}
+28 -319
View File
@@ -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);
}