mirror of
https://protopirate.net/ProtoPirate/ProtoPirate.git
synced 2026-10-23 18:22:23 +00:00
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:
1 parent
48522ec4f6
commit
fcd49fcfdd
8 files changed
+505
-366
No files matched your search
@@ -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;
|
||||
Reference in new issue
Block a user