diff --git a/helpers/hex_viewer_storage.c b/helpers/hex_viewer_storage.c index 47663e488c5..f12a40bb92c 100644 --- a/helpers/hex_viewer_storage.c +++ b/helpers/hex_viewer_storage.c @@ -117,4 +117,51 @@ void hex_viewer_read_settings(void* context) { hex_viewer_close_config_file(fff_file); hex_viewer_close_storage(); +} + + +bool hex_viewer_open_file(HexViewer* hex_viewer, const char* file_path) { + furi_assert(hex_viewer); + furi_assert(file_path); + + hex_viewer->model->stream = buffered_file_stream_alloc(hex_viewer->storage); + bool isOk = true; + + do { + if(!buffered_file_stream_open( + hex_viewer->model->stream, file_path, FSAM_READ, FSOM_OPEN_EXISTING)) { + FURI_LOG_E(TAG, "Unable to open stream: %s", file_path); + isOk = false; + break; + }; + + hex_viewer->model->file_size = stream_size(hex_viewer->model->stream); + } while(false); + + return isOk; +} + +bool hex_viewer_read_file(HexViewer* hex_viewer) { + furi_assert(hex_viewer); + furi_assert(hex_viewer->model->stream); + furi_assert(hex_viewer->model->file_offset % HEX_VIEWER_BYTES_PER_LINE == 0); + + memset(hex_viewer->model->file_bytes, 0x0, HEX_VIEWER_BUF_SIZE); + bool isOk = true; + + do { + uint32_t offset = hex_viewer->model->file_offset; + if(!stream_seek(hex_viewer->model->stream, offset, true)) { + FURI_LOG_E(TAG, "Unable to seek stream"); + isOk = false; + break; + } + + hex_viewer->model->file_read_bytes = stream_read( + hex_viewer->model->stream, + (uint8_t*)hex_viewer->model->file_bytes, + HEX_VIEWER_BUF_SIZE); + } while(false); + + return isOk; } \ No newline at end of file diff --git a/helpers/hex_viewer_storage.h b/helpers/hex_viewer_storage.h index 8727fd363a7..e2cac2ef3c5 100644 --- a/helpers/hex_viewer_storage.h +++ b/helpers/hex_viewer_storage.h @@ -15,4 +15,8 @@ #define HEX_VIEWER_SETTINGS_KEY_SAVE_SETTINGS "SaveSettings" void hex_viewer_save_settings(void* context); -void hex_viewer_read_settings(void* context); \ No newline at end of file +void hex_viewer_read_settings(void* context); + + +bool hex_viewer_open_file(HexViewer* hex_viewer, const char* file_path); +bool hex_viewer_read_file(HexViewer* hex_viewer); \ No newline at end of file diff --git a/hex_viewer.c b/hex_viewer.c index cf52cdc2f99..b31d4e38477 100644 --- a/hex_viewer.c +++ b/hex_viewer.c @@ -1,5 +1,6 @@ #include "hex_viewer.h" + bool hex_viewer_custom_event_callback(void* context, uint32_t event) { furi_assert(context); HexViewer* app = context; @@ -22,6 +23,7 @@ bool hex_viewer_navigation_event_callback(void* context) { HexViewer* hex_viewer_app_alloc() { HexViewer* app = malloc(sizeof(HexViewer)); app->gui = furi_record_open(RECORD_GUI); + app->storage = furi_record_open(RECORD_STORAGE); app->notification = furi_record_open(RECORD_NOTIFICATION); //Turn backlight on, believe me this makes testing your app easier @@ -71,6 +73,8 @@ HexViewer* hex_viewer_app_alloc() { void hex_viewer_app_free(HexViewer* app) { furi_assert(app); + + if(app->model->stream) buffered_file_stream_close(app->model->stream); // Scene manager scene_manager_free(app->scene_manager); @@ -83,8 +87,10 @@ void hex_viewer_app_free(HexViewer* app) { submenu_free(app->submenu); view_dispatcher_free(app->view_dispatcher); + furi_record_close(RECORD_STORAGE); furi_record_close(RECORD_GUI); + app->storage = NULL; app->gui = NULL; app->notification = NULL; diff --git a/hex_viewer.h b/hex_viewer.h index bac72da435a..aee30e74dde 100644 --- a/hex_viewer.h +++ b/hex_viewer.h @@ -20,13 +20,41 @@ #include "views/hex_viewer_scene_2.h" #include "helpers/hex_viewer_storage.h" +#include +#include +#include +#include + #define TAG "HexViewer" -#define SUBGHZ_APP_EXTENSION ".sub" -#define SUBGHZ_APP_FOLDER ANY_PATH("subghz") +// #define SUBGHZ_APP_EXTENSION ".sub" +// #define SUBGHZ_APP_FOLDER ANY_PATH("subghz") + +#define HEX_VIEWER_APP_PATH_FOLDER "/any" // TODO ANY_PATH +#define HEX_VIEWER_APP_EXTENSION "*" + +#define HEX_VIEWER_BYTES_PER_LINE 4u +#define HEX_VIEWER_LINES_ON_SCREEN 4u +#define HEX_VIEWER_BUF_SIZE (HEX_VIEWER_LINES_ON_SCREEN * HEX_VIEWER_BYTES_PER_LINE) + typedef struct { + uint8_t file_bytes[HEX_VIEWER_LINES_ON_SCREEN][HEX_VIEWER_BYTES_PER_LINE]; + uint32_t file_offset; + uint32_t file_read_bytes; + uint32_t file_size; + Stream* stream; + bool mode; // Print address or content +} HexViewerModel; + + +// TODO Clean +typedef struct { + HexViewerModel* model; + FuriMutex** mutex; // TODO Don't need? + Gui* gui; + Storage* storage; NotificationApp* notification; ViewDispatcher* view_dispatcher; Submenu* submenu; diff --git a/scenes/hex_viewer_scene_menu.c b/scenes/hex_viewer_scene_menu.c index 70c24a0b3e2..27a91a8e899 100644 --- a/scenes/hex_viewer_scene_menu.c +++ b/scenes/hex_viewer_scene_menu.c @@ -2,11 +2,11 @@ enum SubmenuIndex { SubmenuIndexScene1 = 10, - SubmenuIndexScene2, - SubmenuIndexScene3, + // SubmenuIndexScene2, + // SubmenuIndexScene3, SubmenuIndexScene4, - SubmenuIndexScene5, - SubmenuIndexSettings, + // SubmenuIndexScene5, + // SubmenuIndexSettings, }; void hex_viewer_scene_menu_submenu_callback(void* context, uint32_t index) { @@ -17,11 +17,11 @@ void hex_viewer_scene_menu_submenu_callback(void* context, uint32_t index) { void hex_viewer_scene_menu_on_enter(void* context) { HexViewer* app = context; - submenu_add_item(app->submenu, "Scene 1 (empty)", SubmenuIndexScene1, hex_viewer_scene_menu_submenu_callback, app); - submenu_add_item(app->submenu, "Scene 2 (Inputs/Effects)", SubmenuIndexScene2, hex_viewer_scene_menu_submenu_callback, app); - submenu_add_item(app->submenu, "Scene 3 (Buttonmenu)", SubmenuIndexScene3, hex_viewer_scene_menu_submenu_callback, app); - submenu_add_item(app->submenu, "Scene 4 (File Browser)", SubmenuIndexScene4, hex_viewer_scene_menu_submenu_callback, app); - submenu_add_item(app->submenu, "Settings", SubmenuIndexSettings, hex_viewer_scene_menu_submenu_callback, app); + submenu_add_item(app->submenu, "Open ...", SubmenuIndexScene4, hex_viewer_scene_menu_submenu_callback, app); + // submenu_add_item(app->submenu, "Scene 2 (Inputs/Effects)", SubmenuIndexScene2, hex_viewer_scene_menu_submenu_callback, app); + // submenu_add_item(app->submenu, "Scene 3 (Buttonmenu)", SubmenuIndexScene3, hex_viewer_scene_menu_submenu_callback, app); + submenu_add_item(app->submenu, "Go to ...", SubmenuIndexScene1, hex_viewer_scene_menu_submenu_callback, app); + // submenu_add_item(app->submenu, "Settings", SubmenuIndexSettings, hex_viewer_scene_menu_submenu_callback, app); submenu_set_selected_item(app->submenu, scene_manager_get_scene_state(app->scene_manager, HexViewerSceneMenu)); @@ -33,8 +33,10 @@ bool hex_viewer_scene_menu_on_event(void* context, SceneManagerEvent event) { UNUSED(app); if(event.type == SceneManagerEventTypeBack) { //exit app - scene_manager_stop(app->scene_manager); - view_dispatcher_stop(app->view_dispatcher); + // TODO Check Return to main view + // scene_manager_stop(app->scene_manager); + // view_dispatcher_stop(app->view_dispatcher); + scene_manager_previous_scene(app->scene_manager); return true; } else if(event.type == SceneManagerEventTypeCustom) { if(event.event == SubmenuIndexScene1) { @@ -42,24 +44,24 @@ bool hex_viewer_scene_menu_on_event(void* context, SceneManagerEvent event) { app->scene_manager, HexViewerSceneMenu, SubmenuIndexScene1); scene_manager_next_scene(app->scene_manager, HexViewerSceneScene_1); return true; - } else if (event.event == SubmenuIndexScene2) { - scene_manager_set_scene_state( - app->scene_manager, HexViewerSceneMenu, SubmenuIndexScene2); - scene_manager_next_scene(app->scene_manager, HexViewerSceneScene_2); - return true; - } else if (event.event == SubmenuIndexScene3) { - scene_manager_set_scene_state( - app->scene_manager, HexViewerSceneMenu, SubmenuIndexScene3); - scene_manager_next_scene(app->scene_manager, HexViewerSceneScene_3); + // } else if (event.event == SubmenuIndexScene2) { + // scene_manager_set_scene_state( + // app->scene_manager, HexViewerSceneMenu, SubmenuIndexScene2); + // scene_manager_next_scene(app->scene_manager, HexViewerSceneScene_2); + // return true; + // } else if (event.event == SubmenuIndexScene3) { + // scene_manager_set_scene_state( + // app->scene_manager, HexViewerSceneMenu, SubmenuIndexScene3); + // scene_manager_next_scene(app->scene_manager, HexViewerSceneScene_3); } else if (event.event == SubmenuIndexScene4) { scene_manager_set_scene_state( app->scene_manager, HexViewerSceneMenu, SubmenuIndexScene4); scene_manager_next_scene(app->scene_manager, HexViewerSceneScene_4); - } else if (event.event == SubmenuIndexSettings) { - scene_manager_set_scene_state( - app->scene_manager, HexViewerSceneMenu, SubmenuIndexSettings); - scene_manager_next_scene(app->scene_manager, HexViewerSceneSettings); - return true; + // } else if (event.event == SubmenuIndexSettings) { + // scene_manager_set_scene_state( + // app->scene_manager, HexViewerSceneMenu, SubmenuIndexSettings); + // scene_manager_next_scene(app->scene_manager, HexViewerSceneSettings); + // return true; } } return false; diff --git a/scenes/hex_viewer_scene_scene_4.c b/scenes/hex_viewer_scene_scene_4.c index 7916d95e8e3..12bda64a619 100644 --- a/scenes/hex_viewer_scene_scene_4.c +++ b/scenes/hex_viewer_scene_scene_4.c @@ -1,24 +1,38 @@ #include "../hex_viewer.h" + void hex_viewer_scene_scene_4_on_enter(void* context) { furi_assert(context); HexViewer* app = context; DialogsFileBrowserOptions browser_options; // This will filter the browser to only show one file type and also add an icon - dialog_file_browser_set_basic_options(&browser_options, SUBGHZ_APP_EXTENSION, &I_sub1_10px); - - //Get the Folder you want to browse - browser_options.base_path = SUBGHZ_APP_FOLDER; - FuriString* path; - path = furi_string_alloc(); - furi_string_set(path, SUBGHZ_APP_FOLDER); - bool success = dialog_file_browser_show( - app->dialogs, app->file_path, path, &browser_options); - furi_string_free(path); + // dialog_file_browser_set_basic_options(&browser_options, SUBGHZ_APP_EXTENSION, &I_sub1_10px); + // Get the Folder you want to browse + // browser_options.base_path = SUBGHZ_APP_FOLDER; + // FuriString* path; + // path = furi_string_alloc(); + // furi_string_set(path, SUBGHZ_APP_FOLDER); + // bool success = dialog_file_browser_show( + // app->dialogs, app->file_path, path, &browser_options); + // furi_string_free(path); - if(success) { - // Do something with the result in app->file_path + FuriString* initial_path; + initial_path = furi_string_alloc(); + furi_string_set(initial_path, HEX_VIEWER_APP_PATH_FOLDER); + + DialogsFileBrowserOptions browser_options; + dialog_file_browser_set_basic_options( + &browser_options, HEX_VIEWER_APP_EXTENSION, &I_hex_10px); + browser_options.hide_ext = false; + + bool success = dialog_file_browser_show(app->dialogs, app->file_path, initial_path, &browser_options); + furi_string_free(initial_path); + + if (success) { + success = hex_viewer_open_file(app, furi_string_get_cstr(app->file_path)); + if(success) + hex_viewer_read_file(app); } if(success) { diff --git a/scenes/hex_viewer_scene_startscreen.c b/scenes/hex_viewer_scene_startscreen.c index 283811aba23..acc99136351 100644 --- a/scenes/hex_viewer_scene_startscreen.c +++ b/scenes/hex_viewer_scene_startscreen.c @@ -22,16 +22,41 @@ bool hex_viewer_scene_startscreen_on_event(void* context, SceneManagerEvent even if(event.type == SceneManagerEventTypeCustom) { switch(event.event) { case HexViewerCustomEventStartscreenLeft: + app->model->mode = !hex_viewer->model->mode; + consumed = true; + break; case HexViewerCustomEventStartscreenRight: + // TODO Dialog + consumed = true; break; case HexViewerCustomEventStartscreenUp: + //furi_check(furi_mutex_acquire(hex_viewer->mutex, FuriWaitForever) == FuriStatusOk); + if(app->model->file_offset > 0) { + app->model->file_offset -= HEX_VIEWER_BYTES_PER_LINE; + if(!hex_viewer_read_file(app)) break; + } + consumed = true; + //furi_mutex_release(hex_viewer->mutex); + break; case HexViewerCustomEventStartscreenDown: + //furi_check(furi_mutex_acquire(hex_viewer->mutex, FuriWaitForever) == FuriStatusOk); + uint32_t last_byte_on_screen = + app->model->file_offset + app->model->file_read_bytes; + + if(app->model->file_size > last_byte_on_screen) { + app->model->file_offset += HEX_VIEWER_BYTES_PER_LINE; + if(!hex_viewer_read_file(app)) break; // TODO Do smth + } + consumed = true; + //furi_mutex_release(hex_viewer->mutex); break; case HexViewerCustomEventStartscreenOk: - scene_manager_next_scene(app->scene_manager, HexViewerSceneMenu); + if (!app->model->file_size) // TODO + scene_manager_next_scene(app->scene_manager, HexViewerSceneScene_4); + else scene_manager_next_scene(app->scene_manager, HexViewerSceneMenu); consumed = true; break; - case HexViewerCustomEventStartscreenBack: + case HexViewerCustomEventStartscreenBack: // TODO DElete notification_message(app->notification, &sequence_reset_red); notification_message(app->notification, &sequence_reset_green); notification_message(app->notification, &sequence_reset_blue); diff --git a/views/hex_viewer_startscreen.c b/views/hex_viewer_startscreen.c index 4044ccc2c8e..0d66383639d 100644 --- a/views/hex_viewer_startscreen.c +++ b/views/hex_viewer_startscreen.c @@ -28,13 +28,74 @@ void hex_viewer_startscreen_set_callback( void hex_viewer_startscreen_draw(Canvas* canvas, HexViewerStartscreenModel* model) { UNUSED(model); canvas_clear(canvas); - canvas_set_color(canvas, ColorBlack); - canvas_set_font(canvas, FontPrimary); - canvas_draw_str_aligned(canvas, 64, 10, AlignCenter, AlignTop, "Start Screen"); - canvas_set_font(canvas, FontSecondary); - canvas_draw_str_aligned(canvas, 64, 22, AlignCenter, AlignTop, "Explain your app"); - canvas_draw_str_aligned(canvas, 64, 32, AlignCenter, AlignTop, "on this screen"); - elements_button_center(canvas, "Start"); + + if (!app->model->file_size) { + canvas_set_color(canvas, ColorBlack); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 64, 10, AlignCenter, AlignTop, "HexViewer v2.0"); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned(canvas, 64, 22, AlignCenter, AlignTop, "Basic hex viewer"); + canvas_draw_str_aligned(canvas, 64, 32, AlignCenter, AlignTop, "for your Flipper"); + elements_button_center(canvas, "Open"); + } else { + canvas_set_color(canvas, ColorBlack); + + elements_button_left(canvas, app->model->mode ? "Addr" : "Text"); + elements_button_right(canvas, "Info"); + elements_button_center(canvas, "Menu"); + + int ROW_HEIGHT = 12; + int TOP_OFFSET = 10; + int LEFT_OFFSET = 3; + + uint32_t line_count = app->model->file_size / HEX_VIEWER_BYTES_PER_LINE; + if(app->model->file_size % HEX_VIEWER_BYTES_PER_LINE != 0) line_count += 1; + uint32_t first_line_on_screen = app->model->file_offset / HEX_VIEWER_BYTES_PER_LINE; + if(line_count > HEX_VIEWER_LINES_ON_SCREEN) { + uint8_t width = canvas_width(canvas); + elements_scrollbar_pos( + canvas, + width, + 0, + ROW_HEIGHT * HEX_VIEWER_LINES_ON_SCREEN, + first_line_on_screen, // TODO + line_count - (HEX_VIEWER_LINES_ON_SCREEN - 1)); + } + + char temp_buf[32]; + uint32_t row_iters = app->model->file_read_bytes / HEX_VIEWER_BYTES_PER_LINE; + if(app->model->file_read_bytes % HEX_VIEWER_BYTES_PER_LINE != 0) row_iters += 1; + + for(uint32_t i = 0; i < row_iters; ++i) { + uint32_t bytes_left_per_row = + app->model->file_read_bytes - i * HEX_VIEWER_BYTES_PER_LINE; + bytes_left_per_row = MIN(bytes_left_per_row, HEX_VIEWER_BYTES_PER_LINE); + + if(app->model->mode) { + memcpy(temp_buf, app->model->file_bytes[i], bytes_left_per_row); + temp_buf[bytes_left_per_row] = '\0'; + for(uint32_t j = 0; j < bytes_left_per_row; ++j) + if(!isprint((int)temp_buf[j])) temp_buf[j] = '.'; + + canvas_set_font(canvas, FontKeyboard); + canvas_draw_str(canvas, LEFT_OFFSET, TOP_OFFSET + i * ROW_HEIGHT, temp_buf); + } else { + uint32_t addr = app->model->file_offset + i * HEX_VIEWER_BYTES_PER_LINE; + snprintf(temp_buf, 32, "%04lX", addr); + + canvas_set_font(canvas, FontKeyboard); + canvas_draw_str(canvas, LEFT_OFFSET, TOP_OFFSET + i * ROW_HEIGHT, temp_buf); + } + + char* p = temp_buf; + for(uint32_t j = 0; j < bytes_left_per_row; ++j) + p += snprintf(p, 32, "%02X ", app->model->file_bytes[i][j]); + + canvas_set_font(canvas, FontKeyboard); + canvas_draw_str(canvas, LEFT_OFFSET + 41, TOP_OFFSET + i * ROW_HEIGHT, temp_buf); + } + } + } static void hex_viewer_startscreen_model_init(HexViewerStartscreenModel* const model) { @@ -57,9 +118,45 @@ bool hex_viewer_startscreen_input(InputEvent* event, void* context) { true); break; case InputKeyLeft: + with_view_model( + instance->view, + HexViewerStartscreenModel * model, + { + UNUSED(model); + instance->callback(HexViewerCustomEventStartscreenLeft, instance->context); + }, + true); + break; case InputKeyRight: + with_view_model( + instance->view, + HexViewerStartscreenModel * model, + { + UNUSED(model); + instance->callback(HexViewerCustomEventStartscreenRight, instance->context); + }, + true); + break; case InputKeyUp: + with_view_model( + instance->view, + HexViewerStartscreenModel * model, + { + UNUSED(model); + instance->callback(HexViewerCustomEventStartscreenUp, instance->context); + }, + true); + break; case InputKeyDown: + with_view_model( + instance->view, + HexViewerStartscreenModel * model, + { + UNUSED(model); + instance->callback(HexViewerCustomEventStartscreenDown, instance->context); + }, + true); + break; case InputKeyOk: with_view_model( instance->view,