diff --git a/application.fam b/application.fam index 8c2a105..81a5a27 100644 --- a/application.fam +++ b/application.fam @@ -7,7 +7,7 @@ App( requires=["gui"], stack_size=4 * 1024, fap_description="Decode car key fob signals from Sub-GHz", - fap_version="1.2", + fap_version="1.3", fap_icon="images/protopirate_10px.png", fap_category="Sub-GHz", fap_icon_assets="images", diff --git a/dist/proto_pirate.fap b/dist/proto_pirate.fap index 33a2d28..3dd53fb 100644 Binary files a/dist/proto_pirate.fap and b/dist/proto_pirate.fap differ diff --git a/helpers/protopirate_storage.c b/helpers/protopirate_storage.c index ee3785d..d3bc419 100644 --- a/helpers/protopirate_storage.c +++ b/helpers/protopirate_storage.c @@ -4,6 +4,13 @@ #include #define TAG "ProtoPirateStorage" +#define MAX_FILES_TO_DISPLAY 50 + +// Structure to hold file info for sorting +typedef struct { + char name[64]; + uint32_t timestamp; +} FileEntry; bool protopirate_storage_init() { @@ -17,7 +24,6 @@ bool protopirate_storage_get_next_filename( const char *protocol_name, FuriString *out_filename) { - Storage *storage = furi_record_open(RECORD_STORAGE); FuriString *temp_path = furi_string_alloc(); uint32_t index = 0; @@ -56,7 +62,6 @@ bool protopirate_storage_save_capture( const char *protocol_name, FuriString *out_path) { - if (!protopirate_storage_init()) { FURI_LOG_E(TAG, "Failed to create app folder"); @@ -87,7 +92,7 @@ bool protopirate_storage_save_capture( } // Write standard SubGhz file header - if (!flipper_format_write_header_cstr(save_file, "Flipper SubGhz", 1)) + if (!flipper_format_write_header_cstr(save_file, "Flipper SubGhz Key File", 1)) { FURI_LOG_E(TAG, "Failed to write header"); break; @@ -109,13 +114,13 @@ bool protopirate_storage_save_capture( } // Bit count + flipper_format_rewind(flipper_format); if (flipper_format_read_uint32(flipper_format, "Bit", &uint32_value, 1)) { flipper_format_write_uint32(save_file, "Bit", &uint32_value, 1); - FURI_LOG_I(TAG, "Saving Bit count: %lu", uint32_value); } - // Key data - could be hex string or uint32 array + // Key data flipper_format_rewind(flipper_format); if (flipper_format_read_string(flipper_format, "Key", string_value)) { @@ -123,9 +128,8 @@ bool protopirate_storage_save_capture( } else { - // Try reading as uint32 array for raw data flipper_format_rewind(flipper_format); - if (flipper_format_get_value_count(flipper_format, "Key", &uint32_array_size)) + if (flipper_format_get_value_count(flipper_format, "Key", &uint32_array_size) && uint32_array_size > 0) { uint32_array = malloc(sizeof(uint32_t) * uint32_array_size); if (flipper_format_read_uint32(flipper_format, "Key", uint32_array, uint32_array_size)) @@ -153,7 +157,7 @@ bool protopirate_storage_save_capture( // Custom preset data if exists flipper_format_rewind(flipper_format); - if (flipper_format_get_value_count(flipper_format, "Custom_preset_module", &uint32_array_size)) + if (flipper_format_get_value_count(flipper_format, "Custom_preset_module", &uint32_array_size) && uint32_array_size > 0) { if (flipper_format_read_string(flipper_format, "Custom_preset_module", string_value)) { @@ -162,7 +166,7 @@ bool protopirate_storage_save_capture( } flipper_format_rewind(flipper_format); - if (flipper_format_get_value_count(flipper_format, "Custom_preset_data", &uint32_array_size)) + if (flipper_format_get_value_count(flipper_format, "Custom_preset_data", &uint32_array_size) && uint32_array_size > 0) { uint8_t *custom_data = malloc(uint32_array_size); if (flipper_format_read_hex(flipper_format, "Custom_preset_data", custom_data, uint32_array_size)) @@ -172,9 +176,7 @@ bool protopirate_storage_save_capture( free(custom_data); } - // Protocol-specific fields - - // TE (timing) if exists + // Protocol-specific fields - TE flipper_format_rewind(flipper_format); if (flipper_format_read_uint32(flipper_format, "TE", &uint32_value, 1)) { @@ -202,7 +204,7 @@ bool protopirate_storage_save_capture( flipper_format_write_uint32(save_file, "Cnt", &uint32_value, 1); } - // CRC if exists + // CRC flipper_format_rewind(flipper_format); if (flipper_format_read_uint32(flipper_format, "CRC", &uint32_value, 1)) { @@ -223,9 +225,9 @@ bool protopirate_storage_save_capture( flipper_format_write_uint32(save_file, "Check", &uint32_value, 1); } - // Raw data arrays if exist + // Raw data arrays flipper_format_rewind(flipper_format); - if (flipper_format_get_value_count(flipper_format, "RAW_Data", &uint32_array_size)) + if (flipper_format_get_value_count(flipper_format, "RAW_Data", &uint32_array_size) && uint32_array_size > 0) { uint32_array = malloc(sizeof(uint32_t) * uint32_array_size); if (flipper_format_read_uint32(flipper_format, "RAW_Data", uint32_array, uint32_array_size)) @@ -235,7 +237,7 @@ bool protopirate_storage_save_capture( free(uint32_array); } - // DataHi/DataLo for protocols with complex encoding + // DataHi/DataLo flipper_format_rewind(flipper_format); if (flipper_format_read_uint32(flipper_format, "DataHi", &uint32_value, 1)) { @@ -282,22 +284,6 @@ bool protopirate_storage_save_capture( flipper_format_write_uint32(save_file, "BS", &uint32_value, 1); } - // Debug: Log what we saved - flipper_format_rewind(save_file); - FuriString *debug_str = furi_string_alloc(); - uint32_t debug_bits; - uint32_t debug_version; - flipper_format_read_header(save_file, debug_str, &debug_version); - if (flipper_format_read_string(save_file, "Protocol", debug_str)) { - FURI_LOG_I(TAG, "Saved Protocol: %s", furi_string_get_cstr(debug_str)); - } - flipper_format_rewind(save_file); - flipper_format_read_header(save_file, debug_str, &debug_version); - if (flipper_format_read_uint32(save_file, "Bit", &debug_bits, 1)) { - FURI_LOG_I(TAG, "Saved Bit count: %lu", debug_bits); - } - furi_string_free(debug_str); - furi_string_free(string_value); if (out_path) @@ -317,31 +303,109 @@ bool protopirate_storage_save_capture( return result; } -uint32_t protopirate_storage_get_file_count() +// Static array to hold sorted file entries +static FileEntry *g_file_entries = NULL; +static uint32_t g_file_count = 0; + +// Build the sorted file list +static void protopirate_storage_build_file_list(void) { + // Free previous list + if (g_file_entries) + { + free(g_file_entries); + g_file_entries = NULL; + } + g_file_count = 0; + Storage *storage = furi_record_open(RECORD_STORAGE); File *dir = storage_file_alloc(storage); FileInfo file_info; - uint32_t count = 0; + // First pass: count files + uint32_t total_count = 0; if (storage_dir_open(dir, PROTOPIRATE_APP_FOLDER)) { char name[256]; while (storage_dir_read(dir, &file_info, name, sizeof(name))) { - if (!file_info_is_dir(&file_info) && - strstr(name, PROTOPIRATE_APP_EXTENSION)) + if (!file_info_is_dir(&file_info) && strstr(name, PROTOPIRATE_APP_EXTENSION)) { - count++; + total_count++; } } + storage_dir_close(dir); + } + + if (total_count == 0) + { + storage_file_free(dir); + furi_record_close(RECORD_STORAGE); + return; + } + + // Allocate array + g_file_entries = malloc(sizeof(FileEntry) * total_count); + if (!g_file_entries) + { + storage_file_free(dir); + furi_record_close(RECORD_STORAGE); + return; + } + + // Second pass: collect file info + FuriString *full_path = furi_string_alloc(); + if (storage_dir_open(dir, PROTOPIRATE_APP_FOLDER)) + { + char name[256]; + while (storage_dir_read(dir, &file_info, name, sizeof(name)) && g_file_count < total_count) + { + if (!file_info_is_dir(&file_info) && strstr(name, PROTOPIRATE_APP_EXTENSION)) + { + // Copy name (without extension for display) + strncpy(g_file_entries[g_file_count].name, name, sizeof(g_file_entries[g_file_count].name) - 1); + g_file_entries[g_file_count].name[sizeof(g_file_entries[g_file_count].name) - 1] = '\0'; + + // Remove extension + char *dot = strrchr(g_file_entries[g_file_count].name, '.'); + if (dot) *dot = '\0'; + + // Get file timestamp + furi_string_printf(full_path, "%s/%s", PROTOPIRATE_APP_FOLDER, name); + + // File Count + g_file_entries[g_file_count].timestamp = total_count - g_file_count; + + g_file_count++; + } + } + storage_dir_close(dir); + } + furi_string_free(full_path); + + // Reverse file count so newest files are first + if (g_file_count > 1) + { + // Reverse the array so newest (last found) is first + for (uint32_t i = 0; i < g_file_count / 2; i++) + { + FileEntry temp = g_file_entries[i]; + g_file_entries[i] = g_file_entries[g_file_count - 1 - i]; + g_file_entries[g_file_count - 1 - i] = temp; + } } - storage_dir_close(dir); storage_file_free(dir); furi_record_close(RECORD_STORAGE); - return count; + FURI_LOG_I(TAG, "Built file list with %lu entries", g_file_count); +} + +uint32_t protopirate_storage_get_file_count() +{ + // Rebuild the file list each time we're asked for count + protopirate_storage_build_file_list(); + return g_file_count; } bool protopirate_storage_get_file_by_index( @@ -349,54 +413,31 @@ bool protopirate_storage_get_file_by_index( FuriString *out_path, FuriString *out_name) { - - Storage *storage = furi_record_open(RECORD_STORAGE); - File *dir = storage_file_alloc(storage); - FileInfo file_info; - uint32_t current_index = 0; - bool found = false; - - if (storage_dir_open(dir, PROTOPIRATE_APP_FOLDER)) + if (!g_file_entries || index >= g_file_count) { - char name[256]; - while (storage_dir_read(dir, &file_info, name, sizeof(name))) - { - if (!file_info_is_dir(&file_info) && - strstr(name, PROTOPIRATE_APP_EXTENSION)) - { - if (current_index == index) - { - if (out_path) - { - furi_string_printf(out_path, "%s/%s", PROTOPIRATE_APP_FOLDER, name); - } - if (out_name) - { - // Remove extension for display - char *dot = strrchr(name, '.'); - if (dot) - *dot = '\0'; - furi_string_set_str(out_name, name); - } - found = true; - break; - } - current_index++; - } - } + return false; } - storage_dir_close(dir); - storage_file_free(dir); - furi_record_close(RECORD_STORAGE); + if (out_path) + { + furi_string_printf(out_path, "%s/%s%s", + PROTOPIRATE_APP_FOLDER, + g_file_entries[index].name, + PROTOPIRATE_APP_EXTENSION); + } + if (out_name) + { + furi_string_set_str(out_name, g_file_entries[index].name); + } - return found; + return true; } bool protopirate_storage_delete_file(const char *file_path) { Storage *storage = furi_record_open(RECORD_STORAGE); bool result = storage_simply_remove(storage, file_path); + FURI_LOG_I(TAG, "Delete file %s: %s", file_path, result ? "OK" : "FAILED"); furi_record_close(RECORD_STORAGE); return result; } @@ -414,6 +455,16 @@ FlipperFormat *protopirate_storage_load_file(const char *file_path) return NULL; } - furi_record_close(RECORD_STORAGE); return flipper_format; +} + +// Call this when exiting the app to free memory +void protopirate_storage_free_file_list(void) +{ + if (g_file_entries) + { + free(g_file_entries); + g_file_entries = NULL; + } + g_file_count = 0; } \ No newline at end of file diff --git a/helpers/protopirate_storage.h b/helpers/protopirate_storage.h index 9d5f025..1fc1db0 100644 --- a/helpers/protopirate_storage.h +++ b/helpers/protopirate_storage.h @@ -20,4 +20,5 @@ bool protopirate_storage_get_next_filename( uint32_t protopirate_storage_get_file_count(); bool protopirate_storage_get_file_by_index(uint32_t index, FuriString *out_path, FuriString *out_name); bool protopirate_storage_delete_file(const char *file_path); -FlipperFormat *protopirate_storage_load_file(const char *file_path); \ No newline at end of file +FlipperFormat *protopirate_storage_load_file(const char *file_path); +void protopirate_storage_free_file_list(void); \ No newline at end of file diff --git a/protopirate_app.c b/protopirate_app.c index aff570a..274a167 100644 --- a/protopirate_app.c +++ b/protopirate_app.c @@ -5,6 +5,7 @@ #include #include "protocols/protocol_items.h" #include "helpers/protopirate_settings.h" +#include "helpers/protopirate_storage.h" #define TAG "ProtoPirateApp" @@ -218,6 +219,9 @@ void protopirate_app_free(ProtoPirateApp *app) FURI_LOG_I(TAG, "Freeing ProtoPirate Decoder App"); + // Free the storage file list cache + protopirate_storage_free_file_list(); + // Save settings before exiting ProtoPirateSettings settings; settings.frequency = app->txrx->preset->frequency; diff --git a/scenes/protopirate_scene_saved.c b/scenes/protopirate_scene_saved.c index c154740..af0c2c6 100644 --- a/scenes/protopirate_scene_saved.c +++ b/scenes/protopirate_scene_saved.c @@ -2,6 +2,8 @@ #include "../protopirate_app_i.h" #include "../helpers/protopirate_storage.h" +#define TAG "ProtoPirateSceneSaved" + typedef enum { SubmenuIndexBack = 0xFF, @@ -20,7 +22,10 @@ void protopirate_scene_saved_on_enter(void *context) submenu_reset(app->submenu); submenu_set_header(app->submenu, "Saved Captures"); + FURI_LOG_I(TAG, "Entering saved captures scene"); + uint32_t file_count = protopirate_storage_get_file_count(); + FURI_LOG_I(TAG, "File count: %lu", file_count); if (file_count == 0) { @@ -40,6 +45,7 @@ void protopirate_scene_saved_on_enter(void *context) { if (protopirate_storage_get_file_by_index(i, path, name)) { + FURI_LOG_D(TAG, "Adding menu item: %s", furi_string_get_cstr(name)); submenu_add_item( app->submenu, furi_string_get_cstr(name), @@ -76,6 +82,8 @@ bool protopirate_scene_saved_on_event(void *context, SceneManagerEvent event) if (protopirate_storage_get_file_by_index(event.event, path, name)) { + FURI_LOG_I(TAG, "Loading file: %s", furi_string_get_cstr(path)); + // Store path for the info scene to use if (app->loaded_file_path) { diff --git a/scenes/protopirate_scene_sub_decode.c b/scenes/protopirate_scene_sub_decode.c index 41ea1ac..f9d2c20 100644 --- a/scenes/protopirate_scene_sub_decode.c +++ b/scenes/protopirate_scene_sub_decode.c @@ -491,6 +491,36 @@ static bool protopirate_process_raw_chunk(ProtoPirateApp* app, SubDecodeContext* ctx->decode_success = true; ctx->can_save = true; + // Serialize the decoded data BEFORE freeing the decoder + ctx->save_data = flipper_format_string_alloc(); + if(ctx->current_protocol->decoder->serialize) { + // Create a temporary preset for serialization + SubGhzRadioPreset temp_preset; + temp_preset.frequency = ctx->frequency; + temp_preset.name = furi_string_alloc_set("AM650"); + temp_preset.data = NULL; + temp_preset.data_size = 0; + + SubGhzProtocolStatus status = ctx->current_protocol->decoder->serialize( + ctx->current_decoder, ctx->save_data, &temp_preset); + + if(status != SubGhzProtocolStatusOk) { + FURI_LOG_W(TAG, "RAW serialize failed: %d", status); + flipper_format_free(ctx->save_data); + ctx->save_data = NULL; + ctx->can_save = false; + } else { + FURI_LOG_I(TAG, "RAW serialize success for %s", ctx->current_protocol->name); + } + + furi_string_free(temp_preset.name); + } else { + FURI_LOG_W(TAG, "Protocol %s has no serialize function", ctx->current_protocol->name); + flipper_format_free(ctx->save_data); + ctx->save_data = NULL; + ctx->can_save = false; + } + ctx->current_protocol->decoder->free(ctx->current_decoder); ctx->current_decoder = NULL; return true; @@ -588,27 +618,32 @@ bool protopirate_scene_sub_decode_on_event(void* context, SceneManagerEvent even if(ctx->save_data) { FuriString* protocol = furi_string_alloc(); flipper_format_rewind(ctx->save_data); + if(!flipper_format_read_string(ctx->save_data, "Protocol", protocol)) { furi_string_set_str(protocol, "Unknown"); + FURI_LOG_W(TAG, "Could not read Protocol from save_data"); } // Clean protocol name for filename furi_string_replace_all(protocol, "/", "_"); furi_string_replace_all(protocol, " ", "_"); + FURI_LOG_I(TAG, "Saving as protocol: %s", furi_string_get_cstr(protocol)); + FuriString* saved_path = furi_string_alloc(); if(protopirate_storage_save_capture( ctx->save_data, furi_string_get_cstr(protocol), saved_path)) { - FURI_LOG_I(TAG, "Saved: %s", furi_string_get_cstr(saved_path)); + FURI_LOG_I(TAG, "Saved to: %s", furi_string_get_cstr(saved_path)); notification_message(app->notifications, &sequence_success); } else { - FURI_LOG_E(TAG, "Save failed"); + FURI_LOG_E(TAG, "Save failed!"); notification_message(app->notifications, &sequence_error); } furi_string_free(protocol); furi_string_free(saved_path); } else { + FURI_LOG_E(TAG, "save_data is NULL, cannot save"); notification_message(app->notifications, &sequence_error); } consumed = true;