cogs-mykey-public/scenes/cogs_mikai_scene_load_file.c

192 lines
7.8 KiB
C

#include "../cogs_mikai.h"
#include <dialogs/dialogs.h>
#include <storage/storage.h>
#include <toolbox/path.h>
#include <string.h>
// Manual hex parser (sscanf %X doesn't work reliably on Flipper)
static bool parse_hex64(const char* str, uint64_t* value) {
if(!str || !value) return false;
*value = 0;
for(size_t i = 0; str[i] != '\0' && i < 16; i++) {
char c = str[i];
uint8_t digit;
if(c >= '0' && c <= '9') digit = c - '0';
else if(c >= 'A' && c <= 'F') digit = c - 'A' + 10;
else if(c >= 'a' && c <= 'f') digit = c - 'a' + 10;
else break;
*value = (*value << 4) | digit;
}
return true;
}
static bool parse_hex32(const char* str, uint32_t* value) {
if(!str || !value) return false;
*value = 0;
for(size_t i = 0; str[i] != '\0' && i < 8; i++) {
char c = str[i];
uint8_t digit;
if(c >= '0' && c <= '9') digit = c - '0';
else if(c >= 'A' && c <= 'F') digit = c - 'A' + 10;
else if(c >= 'a' && c <= 'f') digit = c - 'a' + 10;
else break;
*value = (*value << 4) | digit;
}
return true;
}
static void cogs_mikai_scene_load_file_popup_callback(void* context) {
COGSMyKaiApp* app = context;
view_dispatcher_send_custom_event(app->view_dispatcher, 0);
}
void cogs_mikai_scene_load_file_on_enter(void* context) {
COGSMyKaiApp* app = context;
Popup* popup = app->popup;
// Show file browser starting in the app's data folder
FuriString* file_path = furi_string_alloc();
furi_string_set(file_path, "/ext/apps_data/cogs_mikai");
// Ensure directory exists
Storage* storage_mkdir = furi_record_open(RECORD_STORAGE);
storage_simply_mkdir(storage_mkdir, "/ext/apps_data/cogs_mikai");
furi_record_close(RECORD_STORAGE);
DialogsFileBrowserOptions browser_options;
dialog_file_browser_set_basic_options(&browser_options, ".myk", NULL);
browser_options.hide_ext = false;
if(dialog_file_browser_show(app->dialogs, file_path, file_path, &browser_options)) {
// User selected a file
Storage* storage = furi_record_open(RECORD_STORAGE);
File* file = storage_file_alloc(storage);
if(storage_file_open(file, furi_string_get_cstr(file_path), FSAM_READ, FSOM_OPEN_EXISTING)) {
// Read entire file
size_t file_size = storage_file_size(file);
char* file_buffer = malloc(file_size + 1);
bool success = false;
if(file_buffer) {
size_t bytes_read = storage_file_read(file, file_buffer, file_size);
file_buffer[bytes_read] = '\0';
// Parse the file - manual line parsing without strtok
if(bytes_read > 14 && strncmp(file_buffer, "COGES_MYKEY_V1", 14) == 0) {
char* ptr = file_buffer;
char line[128];
// Helper to read next line
auto bool read_line(char** p, char* buf, size_t max_len) {
size_t i = 0;
while(**p && **p != '\n' && i < max_len - 1) {
buf[i++] = *(*p)++;
}
buf[i] = '\0';
if(**p == '\n') (*p)++;
return i > 0;
};
// Skip header line
read_line(&ptr, line, sizeof(line));
// Read UID
if(read_line(&ptr, line, sizeof(line))) {
char* uid_str = strstr(line, "UID: ");
if(uid_str && parse_hex64(uid_str + 5, &app->mykey.uid)) {
FURI_LOG_I(TAG, "Loaded UID: %016llX", app->mykey.uid);
// Read encryption key
if(read_line(&ptr, line, sizeof(line))) {
char* key_str = strstr(line, "ENCRYPTION_KEY: ");
if(key_str && parse_hex32(key_str + 16, &app->mykey.encryption_key)) {
FURI_LOG_I(TAG, "Loaded key: %08lX", app->mykey.encryption_key);
success = true;
// Read blocks
for(size_t i = 0; i < SRIX4K_BLOCKS && success; i++) {
if(read_line(&ptr, line, sizeof(line))) {
char* block_str = strstr(line, ": ");
if(block_str && parse_hex32(block_str + 2, &app->mykey.eeprom[i])) {
// Success
} else {
FURI_LOG_E(TAG, "Failed to parse block %zu: %s", i, line);
success = false;
}
} else {
FURI_LOG_E(TAG, "Failed to read line for block %zu", i);
success = false;
}
}
} else {
FURI_LOG_E(TAG, "Failed to parse encryption key: %s", line);
}
}
} else {
FURI_LOG_E(TAG, "Failed to parse UID: %s", line);
}
}
}
free(file_buffer);
}
storage_file_close(file);
if(success) {
app->mykey.is_loaded = true;
app->mykey.is_modified = false; // Fresh load from file
app->mykey.is_reset = mykey_is_reset(&app->mykey);
app->mykey.current_credit = mykey_get_current_credit(&app->mykey);
popup_set_header(popup, "Success!", 64, 10, AlignCenter, AlignTop);
popup_set_text(popup, "Card loaded from file", 64, 25, AlignCenter, AlignTop);
notification_message(app->notifications, &sequence_success);
} else {
popup_set_header(popup, "Error", 64, 10, AlignCenter, AlignTop);
popup_set_text(popup, "Invalid file format", 64, 25, AlignCenter, AlignTop);
notification_message(app->notifications, &sequence_error);
}
} else {
popup_set_header(popup, "Error", 64, 10, AlignCenter, AlignTop);
popup_set_text(popup, "Failed to open file", 64, 25, AlignCenter, AlignTop);
notification_message(app->notifications, &sequence_error);
}
storage_file_free(file);
furi_record_close(RECORD_STORAGE);
} else {
// User cancelled - search back to start scene and switch (forces menu rebuild)
furi_string_free(file_path);
scene_manager_search_and_switch_to_previous_scene(app->scene_manager, COGSMyKaiSceneStart);
return;
}
furi_string_free(file_path);
popup_set_callback(popup, cogs_mikai_scene_load_file_popup_callback);
popup_set_context(popup, app);
popup_set_timeout(popup, 2000);
popup_enable_timeout(popup);
view_dispatcher_switch_to_view(app->view_dispatcher, COGSMyKaiViewPopup);
}
bool cogs_mikai_scene_load_file_on_event(void* context, SceneManagerEvent event) {
COGSMyKaiApp* app = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
// Popup timeout - search back to start scene and switch (forces menu rebuild)
scene_manager_search_and_switch_to_previous_scene(app->scene_manager, COGSMyKaiSceneStart);
consumed = true;
}
return consumed;
}
void cogs_mikai_scene_load_file_on_exit(void* context) {
COGSMyKaiApp* app = context;
popup_reset(app->popup);
}