Skip to content

Commit a442509

Browse files
committed
upd protopirate
1 parent d41c766 commit a442509

7 files changed

Lines changed: 171 additions & 16 deletions

File tree

base_pack/protopirate/helpers/protopirate_storage.c

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,52 @@ static bool protopirate_storage_write_capture_data(
276276
return status;
277277
}
278278

279+
bool protopirate_storage_save_capture_to_path(
280+
FlipperFormat* flipper_format,
281+
const char* full_path) {
282+
furi_check(flipper_format);
283+
furi_check(full_path);
284+
285+
if(!protopirate_storage_init()) {
286+
FURI_LOG_E(TAG, "Failed to create app folder");
287+
return false;
288+
}
289+
290+
Storage* storage = furi_record_open(RECORD_STORAGE);
291+
FlipperFormat* save_file = flipper_format_file_alloc(storage);
292+
bool result = false;
293+
294+
do {
295+
// Remove if it already exists (overwrite)
296+
if(storage_file_exists(storage, full_path)) {
297+
storage_simply_remove(storage, full_path);
298+
}
299+
300+
if(!flipper_format_file_open_new(save_file, full_path)) {
301+
FURI_LOG_E(TAG, "Failed to create file: %s", full_path);
302+
break;
303+
}
304+
305+
if(!flipper_format_write_header_cstr(save_file, "Flipper SubGhz Key File", 1)) {
306+
FURI_LOG_E(TAG, "Failed to write header");
307+
break;
308+
}
309+
310+
if(!protopirate_storage_write_capture_data(save_file, flipper_format)) {
311+
FURI_LOG_E(TAG, "Failed to write capture data");
312+
break;
313+
}
314+
315+
result = true;
316+
FURI_LOG_I(TAG, "Saved capture to %s", full_path);
317+
318+
} while(false);
319+
320+
flipper_format_free(save_file);
321+
furi_record_close(RECORD_STORAGE);
322+
return result;
323+
}
324+
279325
bool protopirate_storage_save_temp(FlipperFormat* flipper_format) {
280326
if(!protopirate_storage_init()) {
281327
FURI_LOG_E(TAG, "Failed to create app folder");
@@ -415,4 +461,4 @@ bool protopirate_storage_file_exists(const char* file_path) {
415461
furi_record_close(RECORD_STORAGE);
416462

417463
return exists;
418-
}
464+
}

base_pack/protopirate/helpers/protopirate_storage.h

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,12 +77,17 @@
7777
// Initialize storage (create folder if needed)
7878
bool protopirate_storage_init(void);
7979

80-
// Save a capture to a new file
80+
// Save a capture to a new file (auto-generated name)
8181
bool protopirate_storage_save_capture(
8282
FlipperFormat* flipper_format,
8383
const char* protocol_name,
8484
FuriString* out_path);
8585

86+
// Save a capture to a specific file path (user-chosen name)
87+
bool protopirate_storage_save_capture_to_path(
88+
FlipperFormat* flipper_format,
89+
const char* full_path);
90+
8691
// Save to temp file for emulation
8792
bool protopirate_storage_save_temp(FlipperFormat* flipper_format);
8893

@@ -102,4 +107,4 @@ FlipperFormat* protopirate_storage_load_file(const char* file_path);
102107
void protopirate_storage_close_file(FlipperFormat* flipper_format);
103108

104109
// Check if file exists
105-
bool protopirate_storage_file_exists(const char* file_path);
110+
bool protopirate_storage_file_exists(const char* file_path);

base_pack/protopirate/helpers/protopirate_types.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ typedef enum {
1212
ProtoPirateViewReceiverInfo,
1313
ProtoPirateViewAbout,
1414
ProtoPirateViewFileBrowser,
15+
ProtoPirateViewTextInput,
1516
} ProtoPirateView;
1617

1718
typedef enum {
@@ -25,6 +26,7 @@ typedef enum {
2526
ProtoPirateCustomEventSceneSettingLock,
2627
// File management
2728
ProtoPirateCustomEventReceiverInfoSave,
29+
ProtoPirateCustomEventReceiverInfoSaveConfirm,
2830
ProtoPirateCustomEventReceiverInfoEmulate,
2931
ProtoPirateCustomEventReceiverInfoBruteforceStart,
3032
ProtoPirateCustomEventReceiverInfoBruteforceCancel,
@@ -70,4 +72,4 @@ typedef enum {
7072
ProtoPirateRxKeyStateBack,
7173
ProtoPirateRxKeyStateStart,
7274
ProtoPirateRxKeyStateAddKey,
73-
} ProtoPirateRxKeyState;
75+
} ProtoPirateRxKeyState;

base_pack/protopirate/protocols/kia_v0.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ static const SubGhzBlockConst subghz_protocol_kia_const = {
1313
// Multi-burst configuration
1414
#define KIA_TOTAL_BURSTS 2
1515
#define KIA_INTER_BURST_GAP_US 25000
16+
#define KIA_HEADER_PULSES 64
1617

1718
struct SubGhzProtocolDecoderKIA {
1819
SubGhzProtocolDecoderBase base;
@@ -139,7 +140,7 @@ void* subghz_protocol_encoder_kia_alloc(SubGhzEnvironment* environment) {
139140
instance->button = 0;
140141
instance->counter = 0;
141142

142-
instance->encoder.size_upload = (32 + 2 + 118 + 1) * KIA_TOTAL_BURSTS + (KIA_TOTAL_BURSTS - 1);
143+
instance->encoder.size_upload = (KIA_HEADER_PULSES + 2 + 118 + 1) * KIA_TOTAL_BURSTS + (KIA_TOTAL_BURSTS - 1);
143144
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
144145
instance->encoder.repeat =
145146
10; // High repeat count for continuous transmission while button is held
@@ -202,7 +203,7 @@ static void subghz_protocol_encoder_kia_get_upload(SubGhzProtocolEncoderKIA* ins
202203
instance->encoder.upload[index++] = level_duration_make(false, KIA_INTER_BURST_GAP_US);
203204
}
204205

205-
for(int i = 0; i < 32; i++) {
206+
for(int i = 0; i < KIA_HEADER_PULSES; i++) {
206207
bool is_high = (i % 2) == 0;
207208
instance->encoder.upload[index++] =
208209
level_duration_make(is_high, subghz_protocol_kia_const.te_short);

base_pack/protopirate/protopirate_app.c

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,15 @@ ProtoPirateApp* protopirate_app_alloc() {
8686
view_dispatcher_add_view(
8787
app->view_dispatcher, ProtoPirateViewWidget, widget_get_view(app->widget));
8888

89+
// Text Input
90+
app->text_input = text_input_alloc();
91+
view_dispatcher_add_view(
92+
app->view_dispatcher, ProtoPirateViewTextInput, text_input_get_view(app->text_input));
93+
app->save_protocol = NULL;
94+
app->save_from_saved_info = false;
95+
app->save_history_idx = 0;
96+
memset(app->save_filename, 0, sizeof(app->save_filename));
97+
8998
// File Browser path
9099
app->file_path = furi_string_alloc();
91100
furi_string_set(app->file_path, PROTOPIRATE_APP_FOLDER);
@@ -442,6 +451,15 @@ void protopirate_app_free(ProtoPirateApp* app) {
442451
view_dispatcher_remove_view(app->view_dispatcher, ProtoPirateViewWidget);
443452
widget_free(app->widget);
444453

454+
// Text Input
455+
FURI_LOG_D(TAG, "Removing text_input view");
456+
view_dispatcher_remove_view(app->view_dispatcher, ProtoPirateViewTextInput);
457+
text_input_free(app->text_input);
458+
if(app->save_protocol) {
459+
furi_string_free(app->save_protocol);
460+
app->save_protocol = NULL;
461+
}
462+
445463
// Receiver
446464
FURI_LOG_D(TAG, "Removing receiver view");
447465
view_dispatcher_remove_view(app->view_dispatcher, ProtoPirateViewReceiver);
@@ -537,4 +555,4 @@ int32_t protopirate_app(char* p) {
537555
furi_hal_power_suppress_charge_exit();
538556

539557
return 0;
540-
}
558+
}

base_pack/protopirate/protopirate_app_i.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include <gui/modules/submenu.h>
1616
#include <gui/modules/variable_item_list.h>
1717
#include <gui/modules/widget.h>
18+
#include <gui/modules/text_input.h>
1819
#include <notification/notification_messages.h>
1920
#include <lib/subghz/subghz_setting.h>
2021
#include <lib/subghz/subghz_worker.h>
@@ -54,6 +55,7 @@ struct ProtoPirateApp {
5455
VariableItemList* variable_item_list;
5556
Submenu* submenu;
5657
Widget* widget;
58+
TextInput* text_input;
5759
View* view_about;
5860
FuriString* file_path;
5961
ProtoPirateReceiver* protopirate_receiver;
@@ -69,6 +71,10 @@ struct ProtoPirateApp {
6971
uint8_t tx_power;
7072
PsaBfState* psa_bf_state;
7173
FuriThread* psa_bf_thread;
74+
char save_filename[64];
75+
FuriString* save_protocol;
76+
uint16_t save_history_idx;
77+
bool save_from_saved_info;
7278
};
7379

7480
typedef enum {
@@ -114,4 +120,4 @@ static const NotificationSequence sequence_tx = {
114120
&message_delay_25,
115121
&message_sound_off,
116122
NULL,
117-
};
123+
};

base_pack/protopirate/scenes/protopirate_scene_receiver_info.c

Lines changed: 85 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ static void protopirate_scene_receiver_info_widget_callback(
1212
InputType type,
1313
void* context);
1414

15+
static void protopirate_scene_receiver_info_text_input_callback(void* context) {
16+
ProtoPirateApp* app = context;
17+
view_dispatcher_send_custom_event(
18+
app->view_dispatcher, ProtoPirateCustomEventReceiverInfoSaveConfirm);
19+
}
20+
1521
static void psa_bf_done_cb_receiver_info(void* context) {
1622
ProtoPirateApp* app = context;
1723
view_dispatcher_send_custom_event(
@@ -416,27 +422,98 @@ bool protopirate_scene_receiver_info_on_event(void* context, SceneManagerEvent e
416422
FlipperFormat* ff =
417423
protopirate_history_get_raw_data(app->txrx->history, app->txrx->idx_menu_chosen);
418424
if(ff) {
425+
// Read protocol name for default filename
419426
FuriString* protocol = furi_string_alloc();
420427
flipper_format_rewind(ff);
421428
if(!flipper_format_read_string(ff, "Protocol", protocol)) {
422429
furi_string_set_str(protocol, "Unknown");
423430
}
424-
FuriString* saved_path = furi_string_alloc();
425-
if(protopirate_storage_save_capture(
426-
ff, furi_string_get_cstr(protocol), saved_path)) {
431+
432+
// Clean protocol name for filename
433+
furi_string_replace_all(protocol, "/", "_");
434+
furi_string_replace_all(protocol, " ", "_");
435+
436+
// Get the next auto-generated filename (just the name part)
437+
FuriString* auto_path = furi_string_alloc();
438+
if(protopirate_storage_get_next_filename(
439+
furi_string_get_cstr(protocol), auto_path)) {
440+
// Extract just the filename without folder and extension
441+
const char* full = furi_string_get_cstr(auto_path);
442+
const char* slash = strrchr(full, '/');
443+
const char* name_start = slash ? slash + 1 : full;
444+
445+
// Copy without extension
446+
size_t name_len = strlen(name_start);
447+
const char* dot = strrchr(name_start, '.');
448+
if(dot) name_len = dot - name_start;
449+
if(name_len >= sizeof(app->save_filename))
450+
name_len = sizeof(app->save_filename) - 1;
451+
452+
memcpy(app->save_filename, name_start, name_len);
453+
app->save_filename[name_len] = '\0';
454+
} else {
455+
snprintf(app->save_filename, sizeof(app->save_filename), "capture");
456+
}
457+
furi_string_free(auto_path);
458+
459+
// Store context for when text input confirms
460+
if(app->save_protocol) furi_string_free(app->save_protocol);
461+
app->save_protocol = protocol; // transfer ownership
462+
app->save_history_idx = app->txrx->idx_menu_chosen;
463+
app->save_from_saved_info = false;
464+
465+
// Configure and show text input
466+
text_input_reset(app->text_input);
467+
text_input_set_header_text(app->text_input, "Save filename:");
468+
text_input_set_result_callback(
469+
app->text_input,
470+
protopirate_scene_receiver_info_text_input_callback,
471+
app,
472+
app->save_filename,
473+
sizeof(app->save_filename),
474+
false); // don't clear default text
475+
476+
view_dispatcher_switch_to_view(app->view_dispatcher, ProtoPirateViewTextInput);
477+
}
478+
consumed = true;
479+
}
480+
481+
if(event.event == ProtoPirateCustomEventReceiverInfoSaveConfirm) {
482+
// User confirmed the filename in text input
483+
FlipperFormat* ff =
484+
protopirate_history_get_raw_data(app->txrx->history, app->save_history_idx);
485+
if(ff) {
486+
// Build full path: folder/filename.psf
487+
FuriString* save_path = furi_string_alloc_printf(
488+
"%s/%s%s",
489+
PROTOPIRATE_APP_FOLDER,
490+
app->save_filename,
491+
PROTOPIRATE_APP_EXTENSION);
492+
493+
if(protopirate_storage_save_capture_to_path(
494+
ff, furi_string_get_cstr(save_path))) {
427495
notification_message(app->notifications, &sequence_success);
428-
FURI_LOG_I(TAG, "Saved to: %s", furi_string_get_cstr(saved_path));
496+
FURI_LOG_I(TAG, "Saved to: %s", furi_string_get_cstr(save_path));
429497
} else {
430498
notification_message(app->notifications, &sequence_error);
431499
FURI_LOG_E(TAG, "Save failed");
432500
}
433-
furi_string_free(protocol);
434-
furi_string_free(saved_path);
501+
furi_string_free(save_path);
435502
}
503+
504+
// Clean up save protocol string
505+
if(app->save_protocol) {
506+
furi_string_free(app->save_protocol);
507+
app->save_protocol = NULL;
508+
}
509+
510+
// Return to the receiver info widget
511+
view_dispatcher_switch_to_view(app->view_dispatcher, ProtoPirateViewWidget);
436512
consumed = true;
437513
}
514+
438515
#ifdef ENABLE_EMULATE_FEATURE
439-
else if(event.event == ProtoPirateCustomEventReceiverInfoEmulate && !is_emu_off) {
516+
if(event.event == ProtoPirateCustomEventReceiverInfoEmulate && !is_emu_off) {
440517
FlipperFormat* ff =
441518
protopirate_history_get_raw_data(app->txrx->history, app->txrx->idx_menu_chosen);
442519
if(ff) {
@@ -464,4 +541,4 @@ void protopirate_scene_receiver_info_on_exit(void* context) {
464541
furi_check(context);
465542
ProtoPirateApp* app = context;
466543
widget_reset(app->widget);
467-
}
544+
}

0 commit comments

Comments
 (0)