├── .github_disabled_actions └── workflows │ └── build_dev.yml ├── .gitignore ├── LICENSE ├── README.md ├── application.fam ├── helpers ├── hex_viewer_custom_event.h ├── hex_viewer_haptic.c ├── hex_viewer_haptic.h ├── hex_viewer_led.c ├── hex_viewer_led.h ├── hex_viewer_speaker.c ├── hex_viewer_speaker.h ├── hex_viewer_storage.c └── hex_viewer_storage.h ├── hex_viewer.c ├── hex_viewer.h ├── icons ├── hex_10px.bmp └── hex_10px.png ├── img ├── 1.png └── 2.png ├── scenes ├── hex_viewer_scene.c ├── hex_viewer_scene.h ├── hex_viewer_scene_config.h ├── hex_viewer_scene_info.c ├── hex_viewer_scene_menu.c ├── hex_viewer_scene_open.c ├── hex_viewer_scene_scroll.c ├── hex_viewer_scene_settings.c └── hex_viewer_scene_startscreen.c └── views ├── hex_viewer_startscreen.c └── hex_viewer_startscreen.h /.github_disabled_actions/workflows/build_dev.yml: -------------------------------------------------------------------------------- 1 | name: Build master 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | build_dev: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v3 15 | 16 | - name: Build 17 | uses: oleksiikutuzov/flipperzero-ufbt-action@v1 18 | with: 19 | channel: dev -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist/* 2 | .vscode 3 | .clang-format 4 | .editorconfig -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Roman Shchekin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # #️⃣ Hex Viewer 2 | 3 | Hex Viewer application for Flipper Zero! 4 | The app allows you to view various files as HEX 5 | 6 | **Some facts**: 7 | - Written with pure C in a very simple and effective manner 8 | - Tested on files up to 16Mb 9 | - Very effective: calls `canvas_draw_str` 8 times during repaint and that's almost it 10 | - Can also view text representation of bytes (makes it kinda poor man's text viewer) 11 | - Has "Scroll to ..." feature which allows you to jump to any percent of file 12 | 13 | Feel free to send PRs! 14 | 15 | **Useful links**: 16 | 17 | - App URL: https://lab.flipper.net/apps/hex_viewer 18 | - Catalog's manifest: [flipper-application-catalog/applications/Tools/hex_viewer/manifest.yml](https://github.com/flipperdevices/flipper-application-catalog/blob/main/applications/Tools/hex_viewer/manifest.yml) 19 | -------------------------------------------------------------------------------- /application.fam: -------------------------------------------------------------------------------- 1 | App( 2 | appid="hex_viewer", 3 | name="Hex Viewer", 4 | apptype=FlipperAppType.EXTERNAL, 5 | entry_point="hex_viewer_app", 6 | requires=[ 7 | "gui", 8 | "dialogs", 9 | ], 10 | stack_size=2 * 1024, 11 | fap_icon="icons/hex_10px.bmp", 12 | fap_icon_assets="icons", 13 | fap_category="Tools", 14 | fap_author="@QtRoS", 15 | fap_version="2.0", 16 | fap_description="App allows to view various files as HEX", 17 | ) 18 | -------------------------------------------------------------------------------- /helpers/hex_viewer_custom_event.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | typedef enum { 4 | HexViewerCustomEventStartscreenUp, 5 | HexViewerCustomEventStartscreenDown, 6 | HexViewerCustomEventStartscreenLeft, 7 | HexViewerCustomEventStartscreenRight, 8 | HexViewerCustomEventStartscreenOk, 9 | HexViewerCustomEventStartscreenBack, 10 | HexViewerCustomEventScene1Up, 11 | HexViewerCustomEventScene1Down, 12 | HexViewerCustomEventScene1Left, 13 | HexViewerCustomEventScene1Right, 14 | HexViewerCustomEventScene1Ok, 15 | HexViewerCustomEventScene1Back, 16 | HexViewerCustomEventScene2Up, 17 | HexViewerCustomEventScene2Down, 18 | HexViewerCustomEventScene2Left, 19 | HexViewerCustomEventScene2Right, 20 | HexViewerCustomEventScene2Ok, 21 | HexViewerCustomEventScene2Back, 22 | } HexViewerCustomEvent; 23 | 24 | enum HexViewerCustomEventType { 25 | // Reserve first 100 events for button types and indexes, starting from 0 26 | HexViewerCustomEventMenuVoid, 27 | HexViewerCustomEventMenuSelected, 28 | HexViewerCustomEventMenuPercentEntered, 29 | }; 30 | 31 | #pragma pack(push, 1) 32 | typedef union { 33 | uint32_t packed_value; 34 | struct { 35 | uint16_t type; 36 | int16_t value; 37 | } content; 38 | } HexViewerCustomEventMenu; 39 | #pragma pack(pop) 40 | 41 | static inline uint32_t hex_viewer_custom_menu_event_pack(uint16_t type, int16_t value) { 42 | HexViewerCustomEventMenu event = {.content = {.type = type, .value = value}}; 43 | return event.packed_value; 44 | } 45 | static inline void 46 | hex_viewer_custom_menu_event_unpack(uint32_t packed_value, uint16_t* type, int16_t* value) { 47 | HexViewerCustomEventMenu event = {.packed_value = packed_value}; 48 | if(type) *type = event.content.type; 49 | if(value) *value = event.content.value; 50 | } 51 | 52 | static inline uint16_t hex_viewer_custom_menu_event_get_type(uint32_t packed_value) { 53 | uint16_t type; 54 | hex_viewer_custom_menu_event_unpack(packed_value, &type, NULL); 55 | return type; 56 | } 57 | 58 | static inline int16_t hex_viewer_custom_menu_event_get_value(uint32_t packed_value) { 59 | int16_t value; 60 | hex_viewer_custom_menu_event_unpack(packed_value, NULL, &value); 61 | return value; 62 | } -------------------------------------------------------------------------------- /helpers/hex_viewer_haptic.c: -------------------------------------------------------------------------------- 1 | #include "hex_viewer_haptic.h" 2 | #include "../hex_viewer.h" 3 | 4 | 5 | void hex_viewer_play_happy_bump(void* context) { 6 | HexViewer* app = context; 7 | if (app->haptic != 1) { 8 | return; 9 | } 10 | notification_message(app->notification, &sequence_set_vibro_on); 11 | furi_thread_flags_wait(0, FuriFlagWaitAny, 20); 12 | notification_message(app->notification, &sequence_reset_vibro); 13 | } 14 | 15 | void hex_viewer_play_bad_bump(void* context) { 16 | HexViewer* app = context; 17 | if (app->haptic != 1) { 18 | return; 19 | } 20 | notification_message(app->notification, &sequence_set_vibro_on); 21 | furi_thread_flags_wait(0, FuriFlagWaitAny, 100); 22 | notification_message(app->notification, &sequence_reset_vibro); 23 | } 24 | 25 | void hex_viewer_play_long_bump(void* context) { 26 | HexViewer* app = context; 27 | if (app->haptic != 1) { 28 | return; 29 | } 30 | for (int i = 0; i < 4; i++) { 31 | notification_message(app->notification, &sequence_set_vibro_on); 32 | furi_thread_flags_wait(0, FuriFlagWaitAny, 50); 33 | notification_message(app->notification, &sequence_reset_vibro); 34 | furi_thread_flags_wait(0, FuriFlagWaitAny, 100); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /helpers/hex_viewer_haptic.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void hex_viewer_play_happy_bump(void* context); 4 | 5 | void hex_viewer_play_bad_bump(void* context); 6 | 7 | void hex_viewer_play_long_bump(void* context); 8 | 9 | -------------------------------------------------------------------------------- /helpers/hex_viewer_led.c: -------------------------------------------------------------------------------- 1 | #include "hex_viewer_led.h" 2 | #include "../hex_viewer.h" 3 | 4 | 5 | 6 | void hex_viewer_led_set_rgb(void* context, int red, int green, int blue) { 7 | HexViewer* app = context; 8 | if (app->led != 1) { 9 | return; 10 | } 11 | NotificationMessage notification_led_message_1; 12 | notification_led_message_1.type = NotificationMessageTypeLedRed; 13 | NotificationMessage notification_led_message_2; 14 | notification_led_message_2.type = NotificationMessageTypeLedGreen; 15 | NotificationMessage notification_led_message_3; 16 | notification_led_message_3.type = NotificationMessageTypeLedBlue; 17 | 18 | notification_led_message_1.data.led.value = red; 19 | notification_led_message_2.data.led.value = green; 20 | notification_led_message_3.data.led.value = blue; 21 | const NotificationSequence notification_sequence = { 22 | ¬ification_led_message_1, 23 | ¬ification_led_message_2, 24 | ¬ification_led_message_3, 25 | &message_do_not_reset, 26 | NULL, 27 | }; 28 | notification_message(app->notification, ¬ification_sequence); 29 | furi_thread_flags_wait(0, FuriFlagWaitAny, 10); //Delay, prevent removal from RAM before LED value set 30 | } 31 | 32 | void hex_viewer_led_reset(void* context) { 33 | HexViewer* app = context; 34 | notification_message(app->notification, &sequence_reset_red); 35 | notification_message(app->notification, &sequence_reset_green); 36 | notification_message(app->notification, &sequence_reset_blue); 37 | 38 | furi_thread_flags_wait(0, FuriFlagWaitAny, 300); //Delay, prevent removal from RAM before LED value set 39 | } 40 | -------------------------------------------------------------------------------- /helpers/hex_viewer_led.h: -------------------------------------------------------------------------------- 1 | 2 | 3 | void hex_viewer_led_set_rgb(void* context, int red, int green, int blue); 4 | 5 | void hex_viewer_led_reset(void* context); 6 | 7 | -------------------------------------------------------------------------------- /helpers/hex_viewer_speaker.c: -------------------------------------------------------------------------------- 1 | #include "hex_viewer_speaker.h" 2 | #include "../hex_viewer.h" 3 | 4 | #define NOTE_INPUT 587.33f 5 | 6 | void hex_viewer_play_input_sound(void* context) { 7 | HexViewer* app = context; 8 | if (app->speaker != 1) { 9 | return; 10 | } 11 | float volume = 1.0f; 12 | if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) { 13 | furi_hal_speaker_start(NOTE_INPUT, volume); 14 | } 15 | 16 | } 17 | 18 | void hex_viewer_stop_all_sound(void* context) { 19 | HexViewer* app = context; 20 | if (app->speaker != 1) { 21 | return; 22 | } 23 | if(furi_hal_speaker_is_mine()) { 24 | furi_hal_speaker_stop(); 25 | furi_hal_speaker_release(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /helpers/hex_viewer_speaker.h: -------------------------------------------------------------------------------- 1 | #define NOTE_INPUT 587.33f 2 | 3 | void hex_viewer_play_input_sound(void* context); 4 | void hex_viewer_stop_all_sound(void* context); 5 | -------------------------------------------------------------------------------- /helpers/hex_viewer_storage.c: -------------------------------------------------------------------------------- 1 | #include "hex_viewer_storage.h" 2 | 3 | static Storage* hex_viewer_open_storage() { 4 | return furi_record_open(RECORD_STORAGE); 5 | } 6 | 7 | static void hex_viewer_close_storage() { 8 | furi_record_close(RECORD_STORAGE); 9 | } 10 | 11 | static void hex_viewer_close_config_file(FlipperFormat* file) { 12 | if(file == NULL) return; 13 | flipper_format_file_close(file); 14 | flipper_format_free(file); 15 | } 16 | 17 | void hex_viewer_save_settings(void* context) { 18 | HexViewer* app = context; 19 | if(app->save_settings == 0) { 20 | return; 21 | } 22 | 23 | FURI_LOG_D(TAG, "Saving Settings"); 24 | Storage* storage = hex_viewer_open_storage(); 25 | FlipperFormat* fff_file = flipper_format_file_alloc(storage); 26 | 27 | // Overwrite wont work, so delete first 28 | if(storage_file_exists(storage, HEX_VIEWER_SETTINGS_SAVE_PATH)) { 29 | storage_simply_remove(storage, HEX_VIEWER_SETTINGS_SAVE_PATH); 30 | } 31 | 32 | // Open File, create if not exists 33 | if(!storage_common_stat(storage, HEX_VIEWER_SETTINGS_SAVE_PATH, NULL) == FSE_OK) { 34 | FURI_LOG_D( 35 | TAG, "Config file %s is not found. Will create new.", HEX_VIEWER_SETTINGS_SAVE_PATH); 36 | if(storage_common_stat(storage, CONFIG_FILE_DIRECTORY_PATH, NULL) == FSE_NOT_EXIST) { 37 | FURI_LOG_D( 38 | TAG, "Directory %s doesn't exist. Will create new.", CONFIG_FILE_DIRECTORY_PATH); 39 | if(!storage_simply_mkdir(storage, CONFIG_FILE_DIRECTORY_PATH)) { 40 | FURI_LOG_E(TAG, "Error creating directory %s", CONFIG_FILE_DIRECTORY_PATH); 41 | } 42 | } 43 | } 44 | 45 | if(!flipper_format_file_open_new(fff_file, HEX_VIEWER_SETTINGS_SAVE_PATH)) { 46 | //totp_close_config_file(fff_file); 47 | FURI_LOG_E(TAG, "Error creating new file %s", HEX_VIEWER_SETTINGS_SAVE_PATH); 48 | hex_viewer_close_storage(); 49 | return; 50 | } 51 | 52 | // Store Settings 53 | flipper_format_write_header_cstr( 54 | fff_file, HEX_VIEWER_SETTINGS_HEADER, HEX_VIEWER_SETTINGS_FILE_VERSION); 55 | flipper_format_write_uint32(fff_file, HEX_VIEWER_SETTINGS_KEY_HAPTIC, &app->haptic, 1); 56 | flipper_format_write_uint32(fff_file, HEX_VIEWER_SETTINGS_KEY_SPEAKER, &app->speaker, 1); 57 | flipper_format_write_uint32(fff_file, HEX_VIEWER_SETTINGS_KEY_LED, &app->led, 1); 58 | flipper_format_write_uint32( 59 | fff_file, HEX_VIEWER_SETTINGS_KEY_SAVE_SETTINGS, &app->save_settings, 1); 60 | 61 | if(!flipper_format_rewind(fff_file)) { 62 | hex_viewer_close_config_file(fff_file); 63 | FURI_LOG_E(TAG, "Rewind error"); 64 | hex_viewer_close_storage(); 65 | return; 66 | } 67 | 68 | hex_viewer_close_config_file(fff_file); 69 | hex_viewer_close_storage(); 70 | } 71 | 72 | void hex_viewer_read_settings(void* context) { 73 | HexViewer* app = context; 74 | Storage* storage = hex_viewer_open_storage(); 75 | FlipperFormat* fff_file = flipper_format_file_alloc(storage); 76 | 77 | if(storage_common_stat(storage, HEX_VIEWER_SETTINGS_SAVE_PATH, NULL) != FSE_OK) { 78 | hex_viewer_close_config_file(fff_file); 79 | hex_viewer_close_storage(); 80 | return; 81 | } 82 | uint32_t file_version; 83 | FuriString* temp_str = furi_string_alloc(); 84 | 85 | if(!flipper_format_file_open_existing(fff_file, HEX_VIEWER_SETTINGS_SAVE_PATH)) { 86 | FURI_LOG_E(TAG, "Cannot open file %s", HEX_VIEWER_SETTINGS_SAVE_PATH); 87 | hex_viewer_close_config_file(fff_file); 88 | hex_viewer_close_storage(); 89 | return; 90 | } 91 | 92 | if(!flipper_format_read_header(fff_file, temp_str, &file_version)) { 93 | FURI_LOG_E(TAG, "Missing Header Data"); 94 | hex_viewer_close_config_file(fff_file); 95 | hex_viewer_close_storage(); 96 | return; 97 | } 98 | 99 | if(file_version < HEX_VIEWER_SETTINGS_FILE_VERSION) { 100 | FURI_LOG_I(TAG, "old config version, will be removed."); 101 | hex_viewer_close_config_file(fff_file); 102 | hex_viewer_close_storage(); 103 | return; 104 | } 105 | 106 | flipper_format_read_uint32(fff_file, HEX_VIEWER_SETTINGS_KEY_HAPTIC, &app->haptic, 1); 107 | flipper_format_read_uint32(fff_file, HEX_VIEWER_SETTINGS_KEY_SPEAKER, &app->speaker, 1); 108 | flipper_format_read_uint32(fff_file, HEX_VIEWER_SETTINGS_KEY_LED, &app->led, 1); 109 | flipper_format_read_uint32( 110 | fff_file, HEX_VIEWER_SETTINGS_KEY_SAVE_SETTINGS, &app->save_settings, 1); 111 | 112 | flipper_format_rewind(fff_file); 113 | 114 | hex_viewer_close_config_file(fff_file); 115 | hex_viewer_close_storage(); 116 | } 117 | 118 | bool hex_viewer_open_file(void* context, const char* file_path) { 119 | HexViewer* hex_viewer = context; 120 | furi_assert(hex_viewer); 121 | furi_assert(file_path); 122 | 123 | // TODO Separate function? 124 | if(hex_viewer->model->stream) { 125 | buffered_file_stream_close(hex_viewer->model->stream); 126 | stream_free(hex_viewer->model->stream); 127 | hex_viewer->model->file_offset = 0; 128 | } 129 | 130 | hex_viewer->model->stream = buffered_file_stream_alloc(hex_viewer->storage); 131 | bool isOk = true; 132 | 133 | do { 134 | if(!buffered_file_stream_open( 135 | hex_viewer->model->stream, file_path, FSAM_READ, FSOM_OPEN_EXISTING)) { 136 | FURI_LOG_E(TAG, "Unable to open stream: %s", file_path); 137 | isOk = false; 138 | break; 139 | }; 140 | 141 | hex_viewer->model->file_size = stream_size(hex_viewer->model->stream); 142 | } while(false); 143 | 144 | return isOk; 145 | } 146 | 147 | bool hex_viewer_read_file(void* context) { 148 | HexViewer* hex_viewer = context; 149 | furi_assert(hex_viewer); 150 | furi_assert(hex_viewer->model->stream); 151 | furi_assert(hex_viewer->model->file_offset % HEX_VIEWER_BYTES_PER_LINE == 0); 152 | 153 | memset(hex_viewer->model->file_bytes, 0x0, HEX_VIEWER_BUF_SIZE); 154 | bool isOk = true; 155 | 156 | do { 157 | uint32_t offset = hex_viewer->model->file_offset; 158 | if(!stream_seek(hex_viewer->model->stream, offset, true)) { 159 | FURI_LOG_E(TAG, "Unable to seek stream"); 160 | isOk = false; 161 | break; 162 | } 163 | 164 | hex_viewer->model->file_read_bytes = stream_read( 165 | hex_viewer->model->stream, 166 | (uint8_t*)hex_viewer->model->file_bytes, 167 | HEX_VIEWER_BUF_SIZE); 168 | } while(false); 169 | 170 | return isOk; 171 | } -------------------------------------------------------------------------------- /helpers/hex_viewer_storage.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "../hex_viewer.h" 8 | 9 | #define HEX_VIEWER_SETTINGS_FILE_VERSION 1 10 | #define CONFIG_FILE_DIRECTORY_PATH EXT_PATH("apps_data/hex_viewer") 11 | #define HEX_VIEWER_SETTINGS_SAVE_PATH CONFIG_FILE_DIRECTORY_PATH "/hex_viewer.conf" 12 | #define HEX_VIEWER_SETTINGS_SAVE_PATH_TMP HEX_VIEWER_SETTINGS_SAVE_PATH ".tmp" 13 | #define HEX_VIEWER_SETTINGS_HEADER "HexViewer Config File" 14 | #define HEX_VIEWER_SETTINGS_KEY_HAPTIC "Haptic" 15 | #define HEX_VIEWER_SETTINGS_KEY_LED "Led" 16 | #define HEX_VIEWER_SETTINGS_KEY_SPEAKER "Speaker" 17 | #define HEX_VIEWER_SETTINGS_KEY_SAVE_SETTINGS "SaveSettings" 18 | 19 | void hex_viewer_save_settings(void* context); 20 | void hex_viewer_read_settings(void* context); 21 | 22 | bool hex_viewer_open_file(void* context, const char* file_path); 23 | bool hex_viewer_read_file(void* context); -------------------------------------------------------------------------------- /hex_viewer.c: -------------------------------------------------------------------------------- 1 | #include "hex_viewer.h" 2 | 3 | bool hex_viewer_custom_event_callback(void* context, uint32_t event) { 4 | furi_assert(context); 5 | HexViewer* app = context; 6 | return scene_manager_handle_custom_event(app->scene_manager, event); 7 | } 8 | 9 | void hex_viewer_tick_event_callback(void* context) { 10 | furi_assert(context); 11 | HexViewer* app = context; 12 | scene_manager_handle_tick_event(app->scene_manager); 13 | } 14 | 15 | //leave app if back button pressed 16 | bool hex_viewer_navigation_event_callback(void* context) { 17 | furi_assert(context); 18 | HexViewer* app = context; 19 | return scene_manager_handle_back_event(app->scene_manager); 20 | } 21 | 22 | HexViewer* hex_viewer_app_alloc() { 23 | HexViewer* app = malloc(sizeof(HexViewer)); 24 | 25 | app->model = malloc(sizeof(HexViewerModel)); 26 | memset(app->model, 0, sizeof(HexViewerModel)); 27 | 28 | app->gui = furi_record_open(RECORD_GUI); 29 | app->storage = furi_record_open(RECORD_STORAGE); 30 | app->notification = furi_record_open(RECORD_NOTIFICATION); 31 | 32 | //Turn backlight on, believe me this makes testing your app easier 33 | notification_message(app->notification, &sequence_display_backlight_on); 34 | 35 | //Scene additions 36 | app->view_dispatcher = view_dispatcher_alloc(); 37 | view_dispatcher_enable_queue(app->view_dispatcher); 38 | 39 | app->scene_manager = scene_manager_alloc(&hex_viewer_scene_handlers, app); 40 | view_dispatcher_set_event_callback_context(app->view_dispatcher, app); 41 | view_dispatcher_set_navigation_event_callback( 42 | app->view_dispatcher, hex_viewer_navigation_event_callback); 43 | view_dispatcher_set_tick_event_callback( 44 | app->view_dispatcher, hex_viewer_tick_event_callback, 100); 45 | view_dispatcher_set_custom_event_callback( 46 | app->view_dispatcher, hex_viewer_custom_event_callback); 47 | 48 | app->submenu = submenu_alloc(); 49 | app->text_input = text_input_alloc(); 50 | 51 | // Set defaults, in case no config loaded 52 | app->haptic = 1; 53 | app->speaker = 1; 54 | app->led = 1; 55 | app->save_settings = 1; 56 | 57 | // Used for File Browser 58 | app->dialogs = furi_record_open(RECORD_DIALOGS); 59 | app->file_path = furi_string_alloc(); 60 | 61 | // Load configs 62 | hex_viewer_read_settings(app); 63 | 64 | view_dispatcher_add_view( 65 | app->view_dispatcher, HexViewerViewIdMenu, submenu_get_view(app->submenu)); 66 | 67 | app->hex_viewer_startscreen = hex_viewer_startscreen_alloc(); 68 | view_dispatcher_add_view( 69 | app->view_dispatcher, 70 | HexViewerViewIdStartscreen, 71 | hex_viewer_startscreen_get_view(app->hex_viewer_startscreen)); 72 | 73 | view_dispatcher_add_view( 74 | app->view_dispatcher, HexViewerViewIdScroll, text_input_get_view(app->text_input)); 75 | 76 | app->variable_item_list = variable_item_list_alloc(); 77 | view_dispatcher_add_view( 78 | app->view_dispatcher, 79 | HexViewerViewIdSettings, 80 | variable_item_list_get_view(app->variable_item_list)); 81 | 82 | //End Scene Additions 83 | 84 | return app; 85 | } 86 | 87 | void hex_viewer_app_free(HexViewer* app) { 88 | furi_assert(app); 89 | 90 | if(app->model->stream) { 91 | buffered_file_stream_close(app->model->stream); 92 | stream_free(app->model->stream); 93 | } 94 | 95 | // Scene manager 96 | scene_manager_free(app->scene_manager); 97 | 98 | // View Dispatcher 99 | view_dispatcher_remove_view(app->view_dispatcher, HexViewerViewIdMenu); 100 | view_dispatcher_remove_view(app->view_dispatcher, HexViewerViewIdStartscreen); 101 | view_dispatcher_remove_view(app->view_dispatcher, HexViewerViewIdScroll); 102 | view_dispatcher_remove_view(app->view_dispatcher, HexViewerViewIdSettings); 103 | 104 | submenu_free(app->submenu); 105 | text_input_free(app->text_input); 106 | 107 | view_dispatcher_free(app->view_dispatcher); 108 | furi_record_close(RECORD_STORAGE); 109 | furi_record_close(RECORD_GUI); 110 | 111 | app->storage = NULL; 112 | app->gui = NULL; 113 | app->notification = NULL; 114 | 115 | // Close File Browser 116 | furi_record_close(RECORD_DIALOGS); 117 | furi_string_free(app->file_path); 118 | 119 | free(app->model); 120 | 121 | //Remove whatever is left 122 | free(app); 123 | } 124 | 125 | int32_t hex_viewer_app(void* p) { 126 | UNUSED(p); 127 | HexViewer* app = hex_viewer_app_alloc(); 128 | 129 | view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); 130 | 131 | scene_manager_next_scene(app->scene_manager, HexViewerSceneStartscreen); 132 | 133 | furi_hal_power_suppress_charge_enter(); 134 | 135 | view_dispatcher_run(app->view_dispatcher); 136 | 137 | hex_viewer_save_settings(app); 138 | 139 | furi_hal_power_suppress_charge_exit(); 140 | hex_viewer_app_free(app); 141 | 142 | return 0; 143 | } 144 | -------------------------------------------------------------------------------- /hex_viewer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include "scenes/hex_viewer_scene.h" 19 | #include "views/hex_viewer_startscreen.h" 20 | #include "helpers/hex_viewer_storage.h" 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #define TAG "HexViewer" 28 | 29 | #define HEX_VIEWER_APP_PATH_FOLDER "/any" // TODO ANY_PATH 30 | #define HEX_VIEWER_APP_EXTENSION "*" 31 | #define HEX_VIEWER_PERCENT_INPUT 16 32 | 33 | #define HEX_VIEWER_BYTES_PER_LINE 4u 34 | #define HEX_VIEWER_LINES_ON_SCREEN 4u 35 | #define HEX_VIEWER_BUF_SIZE (HEX_VIEWER_LINES_ON_SCREEN * HEX_VIEWER_BYTES_PER_LINE) 36 | 37 | typedef struct { 38 | uint8_t file_bytes[HEX_VIEWER_LINES_ON_SCREEN][HEX_VIEWER_BYTES_PER_LINE]; 39 | uint32_t file_offset; 40 | uint32_t file_read_bytes; 41 | uint32_t file_size; 42 | 43 | Stream* stream; 44 | } HexViewerModel; 45 | 46 | typedef struct { 47 | HexViewerModel* model; 48 | 49 | Gui* gui; 50 | Storage* storage; 51 | NotificationApp* notification; 52 | ViewDispatcher* view_dispatcher; 53 | Submenu* submenu; 54 | TextInput* text_input; 55 | SceneManager* scene_manager; 56 | VariableItemList* variable_item_list; 57 | HexViewerStartscreen* hex_viewer_startscreen; 58 | DialogsApp* dialogs; // File Browser 59 | FuriString* file_path; // File Browser 60 | uint32_t haptic; 61 | uint32_t speaker; 62 | uint32_t led; 63 | uint32_t save_settings; 64 | char percent_buf[HEX_VIEWER_PERCENT_INPUT]; 65 | } HexViewer; 66 | 67 | typedef enum { 68 | HexViewerViewIdStartscreen, 69 | HexViewerViewIdMenu, 70 | HexViewerViewIdScroll, 71 | HexViewerViewIdSettings, 72 | } HexViewerViewId; 73 | 74 | typedef enum { 75 | HexViewerHapticOff, 76 | HexViewerHapticOn, 77 | } HexViewerHapticState; 78 | 79 | typedef enum { 80 | HexViewerSpeakerOff, 81 | HexViewerSpeakerOn, 82 | } HexViewerSpeakerState; 83 | 84 | typedef enum { 85 | HexViewerLedOff, 86 | HexViewerLedOn, 87 | } HexViewerLedState; 88 | 89 | typedef enum { 90 | HexViewerSettingsOff, 91 | HexViewerSettingsOn, 92 | } HexViewerSettingsStoreState; 93 | -------------------------------------------------------------------------------- /icons/hex_10px.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QtRoS/flipper-zero-hex-viewer/452d10061bbda3254866acb0a00bf0280862707d/icons/hex_10px.bmp -------------------------------------------------------------------------------- /icons/hex_10px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QtRoS/flipper-zero-hex-viewer/452d10061bbda3254866acb0a00bf0280862707d/icons/hex_10px.png -------------------------------------------------------------------------------- /img/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QtRoS/flipper-zero-hex-viewer/452d10061bbda3254866acb0a00bf0280862707d/img/1.png -------------------------------------------------------------------------------- /img/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QtRoS/flipper-zero-hex-viewer/452d10061bbda3254866acb0a00bf0280862707d/img/2.png -------------------------------------------------------------------------------- /scenes/hex_viewer_scene.c: -------------------------------------------------------------------------------- 1 | #include "hex_viewer_scene.h" 2 | 3 | // Generate scene on_enter handlers array 4 | #define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, 5 | void (*const hex_viewer_on_enter_handlers[])(void*) = { 6 | #include "hex_viewer_scene_config.h" 7 | }; 8 | #undef ADD_SCENE 9 | 10 | // Generate scene on_event handlers array 11 | #define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event, 12 | bool (*const hex_viewer_on_event_handlers[])(void* context, SceneManagerEvent event) = { 13 | #include "hex_viewer_scene_config.h" 14 | }; 15 | #undef ADD_SCENE 16 | 17 | // Generate scene on_exit handlers array 18 | #define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit, 19 | void (*const hex_viewer_on_exit_handlers[])(void* context) = { 20 | #include "hex_viewer_scene_config.h" 21 | }; 22 | #undef ADD_SCENE 23 | 24 | // Initialize scene handlers configuration structure 25 | const SceneManagerHandlers hex_viewer_scene_handlers = { 26 | .on_enter_handlers = hex_viewer_on_enter_handlers, 27 | .on_event_handlers = hex_viewer_on_event_handlers, 28 | .on_exit_handlers = hex_viewer_on_exit_handlers, 29 | .scene_num = HexViewerSceneNum, 30 | }; 31 | -------------------------------------------------------------------------------- /scenes/hex_viewer_scene.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | // Generate scene id and total number 6 | #define ADD_SCENE(prefix, name, id) HexViewerScene##id, 7 | typedef enum { 8 | #include "hex_viewer_scene_config.h" 9 | HexViewerSceneNum, 10 | } HexViewerScene; 11 | #undef ADD_SCENE 12 | 13 | extern const SceneManagerHandlers hex_viewer_scene_handlers; 14 | 15 | // Generate scene on_enter handlers declaration 16 | #define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); 17 | #include "hex_viewer_scene_config.h" 18 | #undef ADD_SCENE 19 | 20 | // Generate scene on_event handlers declaration 21 | #define ADD_SCENE(prefix, name, id) \ 22 | bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event); 23 | #include "hex_viewer_scene_config.h" 24 | #undef ADD_SCENE 25 | 26 | // Generate scene on_exit handlers declaration 27 | #define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context); 28 | #include "hex_viewer_scene_config.h" 29 | #undef ADD_SCENE 30 | -------------------------------------------------------------------------------- /scenes/hex_viewer_scene_config.h: -------------------------------------------------------------------------------- 1 | ADD_SCENE(hex_viewer, startscreen, Startscreen) 2 | ADD_SCENE(hex_viewer, menu, Menu) 3 | ADD_SCENE(hex_viewer, scroll, Scroll) 4 | ADD_SCENE(hex_viewer, info, Info) 5 | ADD_SCENE(hex_viewer, open, Open) 6 | ADD_SCENE(hex_viewer, settings, Settings) -------------------------------------------------------------------------------- /scenes/hex_viewer_scene_info.c: -------------------------------------------------------------------------------- 1 | #include "../hex_viewer.h" 2 | 3 | void hex_viewer_scene_info_on_enter(void* context) { 4 | furi_assert(context); 5 | HexViewer* app = context; 6 | 7 | FuriString* buffer; 8 | buffer = furi_string_alloc(); 9 | furi_string_printf( 10 | buffer, 11 | "File path: %s\nFile size: %lu (0x%lX)", 12 | furi_string_get_cstr(app->file_path), 13 | app->model->file_size, 14 | app->model->file_size); 15 | 16 | DialogMessage* message = dialog_message_alloc(); 17 | dialog_message_set_header(message, "Hex Viewer v2.0", 16, 2, AlignLeft, AlignTop); 18 | dialog_message_set_icon(message, &I_hex_10px, 3, 2); 19 | dialog_message_set_text(message, furi_string_get_cstr(buffer), 3, 16, AlignLeft, AlignTop); 20 | dialog_message_set_buttons(message, NULL, NULL, "Back"); 21 | dialog_message_show(app->dialogs, message); 22 | 23 | furi_string_free(buffer); 24 | dialog_message_free(message); 25 | 26 | scene_manager_search_and_switch_to_previous_scene( 27 | app->scene_manager, HexViewerViewIdStartscreen); 28 | } 29 | 30 | bool hex_viewer_scene_info_on_event(void* context, SceneManagerEvent event) { 31 | HexViewer* app = context; 32 | UNUSED(app); 33 | UNUSED(event); 34 | bool consumed = true; 35 | 36 | return consumed; 37 | } 38 | 39 | void hex_viewer_scene_info_on_exit(void* context) { 40 | HexViewer* app = context; 41 | UNUSED(app); 42 | } 43 | -------------------------------------------------------------------------------- /scenes/hex_viewer_scene_menu.c: -------------------------------------------------------------------------------- 1 | #include "../hex_viewer.h" 2 | 3 | enum SubmenuIndex { 4 | SubmenuIndexScroll = 10, 5 | SubmenuIndexInfo, 6 | SubmenuIndexOpen, 7 | // SubmenuIndexSettings, 8 | }; 9 | 10 | void hex_viewer_scene_menu_submenu_callback(void* context, uint32_t index) { 11 | HexViewer* app = context; 12 | view_dispatcher_send_custom_event(app->view_dispatcher, index); 13 | } 14 | 15 | void hex_viewer_scene_menu_on_enter(void* context) { 16 | HexViewer* app = context; 17 | 18 | submenu_set_header(app->submenu, "Select action"); 19 | submenu_add_item( 20 | app->submenu, 21 | "Open file ...", 22 | SubmenuIndexOpen, 23 | hex_viewer_scene_menu_submenu_callback, 24 | app); 25 | submenu_add_item( 26 | app->submenu, 27 | "Scroll to ...", 28 | SubmenuIndexScroll, 29 | hex_viewer_scene_menu_submenu_callback, 30 | app); 31 | submenu_add_item( 32 | app->submenu, 33 | "Show info ...", 34 | SubmenuIndexInfo, 35 | hex_viewer_scene_menu_submenu_callback, 36 | app); 37 | // submenu_add_item(app->submenu, "Settings", SubmenuIndexSettings, hex_viewer_scene_menu_submenu_callback, app); 38 | 39 | submenu_set_selected_item( 40 | app->submenu, scene_manager_get_scene_state(app->scene_manager, HexViewerSceneMenu)); 41 | 42 | view_dispatcher_switch_to_view(app->view_dispatcher, HexViewerViewIdMenu); 43 | } 44 | 45 | bool hex_viewer_scene_menu_on_event(void* context, SceneManagerEvent event) { 46 | HexViewer* app = context; 47 | 48 | if(event.type == SceneManagerEventTypeBack) { 49 | //exit app 50 | // scene_manager_stop(app->scene_manager); 51 | // view_dispatcher_stop(app->view_dispatcher); 52 | scene_manager_previous_scene(app->scene_manager); 53 | return true; 54 | } else if(event.type == SceneManagerEventTypeCustom) { 55 | if(event.event == SubmenuIndexScroll) { 56 | scene_manager_set_scene_state( 57 | app->scene_manager, HexViewerSceneMenu, SubmenuIndexScroll); 58 | scene_manager_next_scene(app->scene_manager, HexViewerSceneScroll); 59 | return true; 60 | } else if(event.event == SubmenuIndexInfo) { 61 | scene_manager_set_scene_state( 62 | app->scene_manager, HexViewerSceneMenu, SubmenuIndexInfo); 63 | scene_manager_next_scene(app->scene_manager, HexViewerSceneInfo); 64 | return true; 65 | } else if(event.event == SubmenuIndexOpen) { 66 | scene_manager_set_scene_state( 67 | app->scene_manager, HexViewerSceneMenu, SubmenuIndexOpen); 68 | scene_manager_next_scene(app->scene_manager, HexViewerSceneOpen); 69 | // } else if (event.event == SubmenuIndexSettings) { 70 | // scene_manager_set_scene_state( 71 | // app->scene_manager, HexViewerSceneMenu, SubmenuIndexSettings); 72 | // scene_manager_next_scene(app->scene_manager, HexViewerSceneSettings); 73 | // return true; 74 | } 75 | } 76 | 77 | return false; 78 | } 79 | 80 | void hex_viewer_scene_menu_on_exit(void* context) { 81 | HexViewer* app = context; 82 | submenu_reset(app->submenu); 83 | } -------------------------------------------------------------------------------- /scenes/hex_viewer_scene_open.c: -------------------------------------------------------------------------------- 1 | #include "../hex_viewer.h" 2 | 3 | void hex_viewer_scene_open_on_enter(void* context) { 4 | furi_assert(context); 5 | HexViewer* app = context; 6 | 7 | FuriString* initial_path; 8 | initial_path = furi_string_alloc(); 9 | furi_string_set(initial_path, HEX_VIEWER_APP_PATH_FOLDER); 10 | 11 | DialogsFileBrowserOptions browser_options; 12 | dialog_file_browser_set_basic_options(&browser_options, HEX_VIEWER_APP_EXTENSION, &I_hex_10px); 13 | browser_options.hide_ext = false; 14 | 15 | bool success = 16 | dialog_file_browser_show(app->dialogs, app->file_path, initial_path, &browser_options); 17 | furi_string_free(initial_path); 18 | 19 | if(success) { 20 | success = hex_viewer_open_file(app, furi_string_get_cstr(app->file_path)); 21 | if(success) hex_viewer_read_file(app); 22 | } 23 | 24 | if(success) { 25 | scene_manager_search_and_switch_to_previous_scene( 26 | app->scene_manager, HexViewerViewIdStartscreen); 27 | } else { 28 | scene_manager_previous_scene(app->scene_manager); 29 | } 30 | } 31 | 32 | bool hex_viewer_scene_open_on_event(void* context, SceneManagerEvent event) { 33 | UNUSED(context); 34 | UNUSED(event); 35 | bool consumed = true; 36 | 37 | return consumed; 38 | } 39 | 40 | void hex_viewer_scene_open_on_exit(void* context) { 41 | UNUSED(context); 42 | } 43 | -------------------------------------------------------------------------------- /scenes/hex_viewer_scene_scroll.c: -------------------------------------------------------------------------------- 1 | #include "../hex_viewer.h" 2 | #include "../helpers/hex_viewer_custom_event.h" 3 | 4 | void hex_viewer_scene_scroll_callback(void* context) { 5 | HexViewer* app = (HexViewer*)context; 6 | view_dispatcher_send_custom_event( 7 | app->view_dispatcher, HexViewerCustomEventMenuPercentEntered); 8 | } 9 | 10 | void hex_viewer_scene_scroll_on_enter(void* context) { 11 | furi_assert(context); 12 | HexViewer* app = context; 13 | 14 | TextInput* text_input = app->text_input; 15 | 16 | text_input_set_header_text(text_input, "Scroll to percentage (0..100)"); 17 | text_input_set_result_callback( 18 | text_input, 19 | hex_viewer_scene_scroll_callback, 20 | app, 21 | app->percent_buf, 22 | HEX_VIEWER_PERCENT_INPUT, 23 | false); 24 | 25 | view_dispatcher_switch_to_view(app->view_dispatcher, HexViewerSceneScroll); 26 | } 27 | 28 | bool hex_viewer_scene_scroll_on_event(void* context, SceneManagerEvent event) { 29 | HexViewer* app = (HexViewer*)context; 30 | bool consumed = false; 31 | 32 | if(event.type == SceneManagerEventTypeCustom) { 33 | if(event.event == HexViewerCustomEventMenuPercentEntered) { 34 | int ipercent = atoi(app->percent_buf); 35 | ipercent = MIN(ipercent, 100); 36 | ipercent = MAX(ipercent, 0); 37 | float percent = ipercent / 100.0; 38 | 39 | uint32_t line_count = app->model->file_size / HEX_VIEWER_BYTES_PER_LINE; 40 | if(app->model->file_size % HEX_VIEWER_BYTES_PER_LINE != 0) line_count += 1; 41 | uint32_t scrollable_lines = line_count - HEX_VIEWER_LINES_ON_SCREEN; 42 | uint32_t target_line = (uint32_t)(percent * scrollable_lines); 43 | 44 | uint32_t new_file_offset = target_line * HEX_VIEWER_BYTES_PER_LINE; 45 | if(app->model->file_size > new_file_offset) { 46 | app->model->file_offset = new_file_offset; 47 | if(!hex_viewer_read_file(app)) new_file_offset = new_file_offset; // TODO Do smth 48 | } 49 | 50 | scene_manager_search_and_switch_to_previous_scene( 51 | app->scene_manager, HexViewerViewIdStartscreen); 52 | 53 | consumed = true; 54 | } 55 | } 56 | return consumed; 57 | } 58 | 59 | void hex_viewer_scene_scroll_on_exit(void* context) { 60 | UNUSED(context); 61 | } 62 | -------------------------------------------------------------------------------- /scenes/hex_viewer_scene_settings.c: -------------------------------------------------------------------------------- 1 | #include "../hex_viewer.h" 2 | #include 3 | 4 | enum SettingsIndex { 5 | SettingsIndexHaptic = 10, 6 | SettingsIndexValue1, 7 | SettingsIndexValue2, 8 | }; 9 | 10 | const char* const haptic_text[2] = { 11 | "OFF", 12 | "ON", 13 | }; 14 | const uint32_t haptic_value[2] = { 15 | HexViewerHapticOff, 16 | HexViewerHapticOn, 17 | }; 18 | 19 | const char* const speaker_text[2] = { 20 | "OFF", 21 | "ON", 22 | }; 23 | const uint32_t speaker_value[2] = { 24 | HexViewerSpeakerOff, 25 | HexViewerSpeakerOn, 26 | }; 27 | 28 | const char* const led_text[2] = { 29 | "OFF", 30 | "ON", 31 | }; 32 | const uint32_t led_value[2] = { 33 | HexViewerLedOff, 34 | HexViewerLedOn, 35 | }; 36 | 37 | const char* const settings_text[2] = { 38 | "OFF", 39 | "ON", 40 | }; 41 | const uint32_t settings_value[2] = { 42 | HexViewerSettingsOff, 43 | HexViewerSettingsOn, 44 | }; 45 | 46 | 47 | static void hex_viewer_scene_settings_set_haptic(VariableItem* item) { 48 | HexViewer* app = variable_item_get_context(item); 49 | uint8_t index = variable_item_get_current_value_index(item); 50 | 51 | variable_item_set_current_value_text(item, haptic_text[index]); 52 | app->haptic = haptic_value[index]; 53 | } 54 | 55 | static void hex_viewer_scene_settings_set_speaker(VariableItem* item) { 56 | HexViewer* app = variable_item_get_context(item); 57 | uint8_t index = variable_item_get_current_value_index(item); 58 | variable_item_set_current_value_text(item, speaker_text[index]); 59 | app->speaker = speaker_value[index]; 60 | } 61 | 62 | static void hex_viewer_scene_settings_set_led(VariableItem* item) { 63 | HexViewer* app = variable_item_get_context(item); 64 | uint8_t index = variable_item_get_current_value_index(item); 65 | variable_item_set_current_value_text(item, led_text[index]); 66 | app->led = led_value[index]; 67 | } 68 | 69 | static void hex_viewer_scene_settings_set_save_settings(VariableItem* item) { 70 | HexViewer* app = variable_item_get_context(item); 71 | uint8_t index = variable_item_get_current_value_index(item); 72 | variable_item_set_current_value_text(item, settings_text[index]); 73 | app->save_settings = settings_value[index]; 74 | } 75 | 76 | void hex_viewer_scene_settings_submenu_callback(void* context, uint32_t index) { 77 | HexViewer* app = context; 78 | view_dispatcher_send_custom_event(app->view_dispatcher, index); 79 | } 80 | 81 | void hex_viewer_scene_settings_on_enter(void* context) { 82 | HexViewer* app = context; 83 | VariableItem* item; 84 | uint8_t value_index; 85 | 86 | // Vibro on/off 87 | item = variable_item_list_add( 88 | app->variable_item_list, 89 | "Vibro/Haptic:", 90 | 2, 91 | hex_viewer_scene_settings_set_haptic, 92 | app); 93 | value_index = value_index_uint32(app->haptic, haptic_value, 2); 94 | variable_item_set_current_value_index(item, value_index); 95 | variable_item_set_current_value_text(item, haptic_text[value_index]); 96 | 97 | // Sound on/off 98 | item = variable_item_list_add( 99 | app->variable_item_list, 100 | "Sound:", 101 | 2, 102 | hex_viewer_scene_settings_set_speaker, 103 | app); 104 | value_index = value_index_uint32(app->speaker, speaker_value, 2); 105 | variable_item_set_current_value_index(item, value_index); 106 | variable_item_set_current_value_text(item, speaker_text[value_index]); 107 | 108 | // LED Effects on/off 109 | item = variable_item_list_add( 110 | app->variable_item_list, 111 | "LED FX:", 112 | 2, 113 | hex_viewer_scene_settings_set_led, 114 | app); 115 | value_index = value_index_uint32(app->led, led_value, 2); 116 | variable_item_set_current_value_index(item, value_index); 117 | variable_item_set_current_value_text(item, led_text[value_index]); 118 | 119 | // Save Settings to File 120 | item = variable_item_list_add( 121 | app->variable_item_list, 122 | "Save Settings", 123 | 2, 124 | hex_viewer_scene_settings_set_save_settings, 125 | app); 126 | value_index = value_index_uint32(app->save_settings, settings_value, 2); 127 | variable_item_set_current_value_index(item, value_index); 128 | variable_item_set_current_value_text(item, settings_text[value_index]); 129 | 130 | view_dispatcher_switch_to_view(app->view_dispatcher, HexViewerViewIdSettings); 131 | } 132 | 133 | bool hex_viewer_scene_settings_on_event(void* context, SceneManagerEvent event) { 134 | HexViewer* app = context; 135 | UNUSED(app); 136 | bool consumed = false; 137 | if(event.type == SceneManagerEventTypeCustom) { 138 | 139 | } 140 | return consumed; 141 | } 142 | 143 | void hex_viewer_scene_settings_on_exit(void* context) { 144 | HexViewer* app = context; 145 | variable_item_list_set_selected_item(app->variable_item_list, 0); 146 | variable_item_list_reset(app->variable_item_list); 147 | } -------------------------------------------------------------------------------- /scenes/hex_viewer_scene_startscreen.c: -------------------------------------------------------------------------------- 1 | #include "../hex_viewer.h" 2 | #include "../helpers/hex_viewer_custom_event.h" 3 | #include "../views/hex_viewer_startscreen.h" 4 | 5 | void hex_viewer_scene_startscreen_callback(HexViewerCustomEvent event, void* context) { 6 | furi_assert(context); 7 | HexViewer* app = context; 8 | view_dispatcher_send_custom_event(app->view_dispatcher, event); 9 | } 10 | 11 | void hex_viewer_scene_startscreen_on_enter(void* context) { 12 | furi_assert(context); 13 | HexViewer* app = context; 14 | hex_viewer_startscreen_set_callback( 15 | app->hex_viewer_startscreen, hex_viewer_scene_startscreen_callback, app); 16 | view_dispatcher_switch_to_view(app->view_dispatcher, HexViewerViewIdStartscreen); 17 | } 18 | 19 | bool hex_viewer_scene_startscreen_on_event(void* context, SceneManagerEvent event) { 20 | HexViewer* app = context; 21 | bool consumed = false; 22 | 23 | if(event.type == SceneManagerEventTypeCustom) { 24 | switch(event.event) { 25 | case HexViewerCustomEventStartscreenLeft: 26 | //app->model->mode = !app->model->mode; 27 | consumed = true; 28 | break; 29 | case HexViewerCustomEventStartscreenRight: 30 | consumed = true; 31 | break; 32 | case HexViewerCustomEventStartscreenUp: 33 | consumed = true; 34 | break; 35 | case HexViewerCustomEventStartscreenDown: 36 | consumed = true; 37 | break; 38 | case HexViewerCustomEventStartscreenOk: 39 | if(!app->model->file_size) 40 | scene_manager_next_scene(app->scene_manager, HexViewerSceneOpen); 41 | else 42 | scene_manager_next_scene(app->scene_manager, HexViewerSceneMenu); 43 | consumed = true; 44 | break; 45 | case HexViewerCustomEventStartscreenBack: // TODO Delete 46 | notification_message(app->notification, &sequence_reset_red); 47 | notification_message(app->notification, &sequence_reset_green); 48 | notification_message(app->notification, &sequence_reset_blue); 49 | if(!scene_manager_search_and_switch_to_previous_scene( 50 | app->scene_manager, HexViewerSceneStartscreen)) { 51 | scene_manager_stop(app->scene_manager); 52 | view_dispatcher_stop(app->view_dispatcher); 53 | } 54 | consumed = true; 55 | break; 56 | } 57 | } 58 | 59 | return consumed; 60 | } 61 | 62 | void hex_viewer_scene_startscreen_on_exit(void* context) { 63 | HexViewer* app = context; 64 | UNUSED(app); 65 | } -------------------------------------------------------------------------------- /views/hex_viewer_startscreen.c: -------------------------------------------------------------------------------- 1 | #include "../hex_viewer.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | struct HexViewerStartscreen { 8 | View* view; 9 | HexViewerStartscreenCallback callback; 10 | void* context; 11 | }; 12 | 13 | typedef struct { 14 | uint8_t file_bytes[HEX_VIEWER_LINES_ON_SCREEN][HEX_VIEWER_BYTES_PER_LINE]; 15 | uint32_t file_offset; 16 | uint32_t file_read_bytes; 17 | uint32_t file_size; 18 | bool mode; 19 | uint32_t dbg; 20 | } HexViewerStartscreenModel; 21 | 22 | void hex_viewer_startscreen_set_callback( 23 | HexViewerStartscreen* instance, 24 | HexViewerStartscreenCallback callback, 25 | void* context) { 26 | furi_assert(instance); 27 | furi_assert(callback); 28 | instance->callback = callback; 29 | instance->context = context; 30 | } 31 | 32 | void hex_viewer_startscreen_draw(Canvas* canvas, HexViewerStartscreenModel* model) { 33 | canvas_clear(canvas); 34 | 35 | if(!model->file_size) { 36 | canvas_set_color(canvas, ColorBlack); 37 | canvas_set_font(canvas, FontPrimary); 38 | canvas_draw_str_aligned(canvas, 64, 10, AlignCenter, AlignTop, "HexViewer v2.0"); 39 | canvas_set_font(canvas, FontSecondary); 40 | canvas_draw_str_aligned(canvas, 64, 22, AlignCenter, AlignTop, "Basic hex viewer"); 41 | canvas_draw_str_aligned(canvas, 64, 32, AlignCenter, AlignTop, "for your Flipper"); 42 | elements_button_center(canvas, "Open"); 43 | } else { 44 | canvas_set_color(canvas, ColorBlack); 45 | 46 | elements_button_left(canvas, model->mode ? "Addr" : "Text"); 47 | //elements_button_right(canvas, "Info"); 48 | elements_button_center(canvas, "Menu"); 49 | 50 | int ROW_HEIGHT = 12; 51 | int TOP_OFFSET = 10; 52 | int LEFT_OFFSET = 3; 53 | 54 | uint32_t line_count = model->file_size / HEX_VIEWER_BYTES_PER_LINE; 55 | if(model->file_size % HEX_VIEWER_BYTES_PER_LINE != 0) line_count += 1; 56 | uint32_t first_line_on_screen = model->file_offset / HEX_VIEWER_BYTES_PER_LINE; 57 | if(line_count > HEX_VIEWER_LINES_ON_SCREEN) { 58 | uint8_t width = canvas_width(canvas); 59 | elements_scrollbar_pos( 60 | canvas, 61 | width, 62 | 0, 63 | ROW_HEIGHT * HEX_VIEWER_LINES_ON_SCREEN, 64 | first_line_on_screen, 65 | line_count - (HEX_VIEWER_LINES_ON_SCREEN - 1)); 66 | } 67 | 68 | char temp_buf[32]; 69 | uint32_t row_iters = model->file_read_bytes / HEX_VIEWER_BYTES_PER_LINE; 70 | if(model->file_read_bytes % HEX_VIEWER_BYTES_PER_LINE != 0) row_iters += 1; 71 | 72 | // For the rest of drawing. 73 | canvas_set_font(canvas, FontKeyboard); 74 | 75 | for(uint32_t i = 0; i < row_iters; ++i) { 76 | uint32_t bytes_left_per_row = model->file_read_bytes - i * HEX_VIEWER_BYTES_PER_LINE; 77 | bytes_left_per_row = MIN(bytes_left_per_row, HEX_VIEWER_BYTES_PER_LINE); 78 | 79 | if(model->mode) { 80 | memcpy(temp_buf, model->file_bytes[i], bytes_left_per_row); 81 | temp_buf[bytes_left_per_row] = '\0'; 82 | for(uint32_t j = 0; j < bytes_left_per_row; ++j) 83 | if(!isprint((int)temp_buf[j])) temp_buf[j] = '.'; 84 | 85 | //canvas_set_font(canvas, FontKeyboard); 86 | canvas_draw_str(canvas, LEFT_OFFSET, TOP_OFFSET + i * ROW_HEIGHT, temp_buf); 87 | } else { 88 | uint32_t addr = model->file_offset + i * HEX_VIEWER_BYTES_PER_LINE; 89 | snprintf(temp_buf, 32, "%04lX", addr); 90 | 91 | //canvas_set_font(canvas, FontKeyboard); 92 | canvas_draw_str(canvas, LEFT_OFFSET, TOP_OFFSET + i * ROW_HEIGHT, temp_buf); 93 | } 94 | 95 | char* p = temp_buf; 96 | for(uint32_t j = 0; j < bytes_left_per_row; ++j) 97 | p += snprintf(p, 32, "%02X ", model->file_bytes[i][j]); 98 | 99 | //canvas_set_font(canvas, FontKeyboard); 100 | canvas_draw_str(canvas, LEFT_OFFSET + 41, TOP_OFFSET + i * ROW_HEIGHT, temp_buf); 101 | } 102 | 103 | // Poor man's debug 104 | // snprintf(temp_buf, 32, "D %02lX", model->dbg); 105 | // elements_button_right(canvas, temp_buf); 106 | } 107 | } 108 | 109 | static void hex_viewer_startscreen_model_init(HexViewerStartscreenModel* const model) { 110 | memset(model->file_bytes, 0, sizeof(model->file_bytes)); 111 | model->file_offset = 0; 112 | model->file_read_bytes = 0; 113 | model->file_size = 0; 114 | model->mode = false; 115 | model->dbg = 0; 116 | } 117 | 118 | static void 119 | update_local_model_from_app(HexViewer* const app, HexViewerStartscreenModel* const model) { 120 | memcpy(model->file_bytes, app->model->file_bytes, sizeof(model->file_bytes)); 121 | model->file_offset = app->model->file_offset; 122 | model->file_read_bytes = app->model->file_read_bytes; 123 | model->file_size = app->model->file_size; 124 | //model->mode = app->model->mode; 125 | } 126 | 127 | bool hex_viewer_startscreen_input(InputEvent* event, void* context) { 128 | furi_assert(context); 129 | HexViewerStartscreen* instance = context; 130 | HexViewer* app = instance->context; // TO so good, but works 131 | 132 | if(event->type == InputTypeRelease || event->type == InputTypeRepeat) { 133 | switch(event->key) { 134 | case InputKeyBack: 135 | with_view_model( 136 | instance->view, 137 | HexViewerStartscreenModel * model, 138 | { 139 | instance->callback(HexViewerCustomEventStartscreenBack, instance->context); 140 | update_local_model_from_app(instance->context, model); 141 | }, 142 | true); 143 | break; 144 | case InputKeyLeft: 145 | with_view_model( 146 | instance->view, 147 | HexViewerStartscreenModel * model, 148 | { model->mode = !model->mode; }, 149 | true); 150 | break; 151 | case InputKeyRight: 152 | with_view_model( 153 | instance->view, HexViewerStartscreenModel * model, { model->dbg = 0; }, true); 154 | break; 155 | case InputKeyUp: 156 | with_view_model( 157 | instance->view, 158 | HexViewerStartscreenModel * model, 159 | { 160 | if(app->model->file_offset > 0) { 161 | app->model->file_offset -= HEX_VIEWER_BYTES_PER_LINE; 162 | if(!hex_viewer_read_file(app)) break; // TODO Do smth 163 | } 164 | 165 | update_local_model_from_app(instance->context, model); 166 | }, 167 | true); 168 | break; 169 | case InputKeyDown: 170 | with_view_model( 171 | instance->view, 172 | HexViewerStartscreenModel * model, 173 | { 174 | uint32_t last_byte_on_screen = 175 | app->model->file_offset + app->model->file_read_bytes; 176 | if(app->model->file_size > last_byte_on_screen) { 177 | app->model->file_offset += HEX_VIEWER_BYTES_PER_LINE; 178 | if(!hex_viewer_read_file(app)) break; // TODO Do smth 179 | } 180 | 181 | update_local_model_from_app(instance->context, model); 182 | }, 183 | true); 184 | break; 185 | case InputKeyOk: 186 | with_view_model( 187 | instance->view, 188 | HexViewerStartscreenModel * model, 189 | { 190 | instance->callback(HexViewerCustomEventStartscreenOk, instance->context); 191 | update_local_model_from_app(instance->context, model); 192 | }, 193 | true); 194 | break; 195 | case InputKeyMAX: 196 | break; 197 | } 198 | } 199 | 200 | return true; 201 | } 202 | 203 | void hex_viewer_startscreen_exit(void* context) { 204 | furi_assert(context); 205 | } 206 | 207 | void hex_viewer_startscreen_enter(void* context) { 208 | furi_assert(context); 209 | HexViewerStartscreen* instance = (HexViewerStartscreen*)context; 210 | with_view_model( 211 | instance->view, 212 | HexViewerStartscreenModel * model, 213 | { update_local_model_from_app(instance->context, model); }, 214 | true); 215 | } 216 | 217 | HexViewerStartscreen* hex_viewer_startscreen_alloc() { 218 | HexViewerStartscreen* instance = malloc(sizeof(HexViewerStartscreen)); 219 | instance->view = view_alloc(); 220 | view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(HexViewerStartscreenModel)); 221 | view_set_context(instance->view, instance); 222 | view_set_draw_callback(instance->view, (ViewDrawCallback)hex_viewer_startscreen_draw); 223 | view_set_input_callback(instance->view, hex_viewer_startscreen_input); 224 | view_set_enter_callback(instance->view, hex_viewer_startscreen_enter); 225 | view_set_exit_callback(instance->view, hex_viewer_startscreen_exit); 226 | 227 | with_view_model( 228 | instance->view, 229 | HexViewerStartscreenModel * model, 230 | { hex_viewer_startscreen_model_init(model); }, 231 | true); 232 | 233 | return instance; 234 | } 235 | 236 | void hex_viewer_startscreen_free(HexViewerStartscreen* instance) { 237 | furi_assert(instance); 238 | 239 | with_view_model( 240 | instance->view, HexViewerStartscreenModel * model, { UNUSED(model); }, true); 241 | view_free(instance->view); 242 | free(instance); 243 | } 244 | 245 | View* hex_viewer_startscreen_get_view(HexViewerStartscreen* instance) { 246 | furi_assert(instance); 247 | return instance->view; 248 | } 249 | -------------------------------------------------------------------------------- /views/hex_viewer_startscreen.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "../helpers/hex_viewer_custom_event.h" 5 | 6 | typedef struct HexViewerStartscreen HexViewerStartscreen; 7 | 8 | typedef void (*HexViewerStartscreenCallback)(HexViewerCustomEvent event, void* context); 9 | 10 | void hex_viewer_startscreen_set_callback( 11 | HexViewerStartscreen* hex_viewer_startscreen, 12 | HexViewerStartscreenCallback callback, 13 | void* context); 14 | 15 | View* hex_viewer_startscreen_get_view(HexViewerStartscreen* hex_viewer_static); 16 | 17 | HexViewerStartscreen* hex_viewer_startscreen_alloc(); 18 | 19 | void hex_viewer_startscreen_free(HexViewerStartscreen* hex_viewer_static); --------------------------------------------------------------------------------