hex_viewer.c 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. #include <furi.h>
  2. #include <furi_hal.h>
  3. #include <hex_viewer_icons.h>
  4. #include <gui/gui.h>
  5. #include <gui/elements.h>
  6. #include <dialogs/dialogs.h>
  7. #include <storage/storage.h>
  8. #include <stream/stream.h>
  9. #include <stream/buffered_file_stream.h>
  10. #include <toolbox/stream/file_stream.h>
  11. #define TAG "HexViewer"
  12. #define HEX_VIEWER_APP_PATH_FOLDER "/any"
  13. #define HEX_VIEWER_APP_EXTENSION "*"
  14. #define HEX_VIEWER_BYTES_PER_ROW 4
  15. #define HEX_VIEWER_ROW_COUNT 4
  16. #define HEX_VIEWER_BUF_SIZE (HEX_VIEWER_BYTES_PER_ROW * HEX_VIEWER_ROW_COUNT)
  17. typedef struct {
  18. uint8_t file_bytes[HEX_VIEWER_ROW_COUNT][HEX_VIEWER_ROW_COUNT];
  19. uint32_t line;
  20. uint32_t read_bytes;
  21. uint32_t file_size;
  22. Stream* stream;
  23. bool mode; // Print address or content
  24. } HexViewerModel;
  25. typedef struct {
  26. HexViewerModel* model;
  27. FuriMutex** mutex;
  28. FuriMessageQueue* input_queue;
  29. ViewPort* view_port;
  30. Gui* gui;
  31. Storage* storage;
  32. } HexViewer;
  33. static void render_callback(Canvas* canvas, void* ctx) {
  34. HexViewer* hex_viewer = ctx;
  35. furi_check(furi_mutex_acquire(hex_viewer->mutex, FuriWaitForever) == FuriStatusOk);
  36. canvas_clear(canvas);
  37. canvas_set_color(canvas, ColorBlack);
  38. elements_button_left(canvas, hex_viewer->model->mode ? "Addr" : "Text");
  39. elements_button_right(canvas, "Info");
  40. int ROW_HEIGHT = 12;
  41. int TOP_OFFSET = 10;
  42. int LEFT_OFFSET = 3;
  43. uint32_t line_count = hex_viewer->model->file_size / HEX_VIEWER_BYTES_PER_ROW;
  44. if(hex_viewer->model->file_size % HEX_VIEWER_BYTES_PER_ROW != 0) line_count += 1;
  45. if(line_count > HEX_VIEWER_ROW_COUNT) {
  46. uint8_t width = canvas_width(canvas);
  47. elements_scrollbar_pos(
  48. canvas,
  49. width,
  50. 0,
  51. ROW_HEIGHT * HEX_VIEWER_ROW_COUNT,
  52. hex_viewer->model->line,
  53. line_count - (HEX_VIEWER_ROW_COUNT - 1));
  54. }
  55. char temp_buf[32];
  56. uint32_t row_iters = hex_viewer->model->read_bytes / HEX_VIEWER_BYTES_PER_ROW;
  57. if(hex_viewer->model->read_bytes % HEX_VIEWER_BYTES_PER_ROW != 0) row_iters += 1;
  58. for(uint32_t i = 0; i < row_iters; ++i) {
  59. uint32_t bytes_left_per_row = hex_viewer->model->read_bytes - i * HEX_VIEWER_BYTES_PER_ROW;
  60. if(bytes_left_per_row > HEX_VIEWER_BYTES_PER_ROW)
  61. bytes_left_per_row = HEX_VIEWER_BYTES_PER_ROW;
  62. if(hex_viewer->model->mode) {
  63. memcpy(temp_buf, hex_viewer->model->file_bytes[i], bytes_left_per_row);
  64. temp_buf[bytes_left_per_row] = '\0';
  65. for(uint32_t j = 0; j < bytes_left_per_row; ++j)
  66. if(!isprint((int)temp_buf[j])) temp_buf[j] = '.';
  67. canvas_set_font(canvas, FontKeyboard);
  68. canvas_draw_str(canvas, LEFT_OFFSET, TOP_OFFSET + i * ROW_HEIGHT, temp_buf);
  69. } else {
  70. int addr = (i + hex_viewer->model->line) * HEX_VIEWER_BYTES_PER_ROW;
  71. snprintf(temp_buf, 32, "%04X", addr);
  72. canvas_set_font(canvas, FontKeyboard);
  73. canvas_draw_str(canvas, LEFT_OFFSET, TOP_OFFSET + i * ROW_HEIGHT, temp_buf);
  74. }
  75. char* p = temp_buf;
  76. for(uint32_t j = 0; j < bytes_left_per_row; ++j)
  77. p += snprintf(p, 32, "%02X ", hex_viewer->model->file_bytes[i][j]);
  78. canvas_set_font(canvas, FontKeyboard);
  79. canvas_draw_str(canvas, LEFT_OFFSET + 41, TOP_OFFSET + i * ROW_HEIGHT, temp_buf);
  80. }
  81. furi_mutex_release(hex_viewer->mutex);
  82. }
  83. static void input_callback(InputEvent* input_event, void* ctx) {
  84. HexViewer* hex_viewer = ctx;
  85. if(input_event->type == InputTypeShort) {
  86. furi_message_queue_put(hex_viewer->input_queue, input_event, 0);
  87. }
  88. }
  89. static HexViewer* hex_viewer_alloc() {
  90. HexViewer* instance = malloc(sizeof(HexViewer));
  91. instance->model = malloc(sizeof(HexViewerModel));
  92. memset(instance->model, 0x0, sizeof(HexViewerModel));
  93. instance->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
  94. instance->input_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
  95. instance->view_port = view_port_alloc();
  96. view_port_draw_callback_set(instance->view_port, render_callback, instance);
  97. view_port_input_callback_set(instance->view_port, input_callback, instance);
  98. instance->gui = furi_record_open(RECORD_GUI);
  99. gui_add_view_port(instance->gui, instance->view_port, GuiLayerFullscreen);
  100. instance->storage = furi_record_open(RECORD_STORAGE);
  101. return instance;
  102. }
  103. static void hex_viewer_free(HexViewer* instance) {
  104. furi_record_close(RECORD_STORAGE);
  105. gui_remove_view_port(instance->gui, instance->view_port);
  106. furi_record_close(RECORD_GUI);
  107. view_port_free(instance->view_port);
  108. furi_message_queue_free(instance->input_queue);
  109. furi_mutex_free(instance->mutex);
  110. if(instance->model->stream) buffered_file_stream_close(instance->model->stream);
  111. free(instance->model);
  112. free(instance);
  113. }
  114. static bool hex_viewer_open_file(HexViewer* hex_viewer, const char* file_path) {
  115. furi_assert(hex_viewer);
  116. furi_assert(file_path);
  117. hex_viewer->model->stream = buffered_file_stream_alloc(hex_viewer->storage);
  118. bool isOk = true;
  119. do {
  120. if(!buffered_file_stream_open(
  121. hex_viewer->model->stream, file_path, FSAM_READ, FSOM_OPEN_EXISTING)) {
  122. FURI_LOG_E(TAG, "Unable to open stream: %s", file_path);
  123. isOk = false;
  124. break;
  125. };
  126. hex_viewer->model->file_size = stream_size(hex_viewer->model->stream);
  127. } while(false);
  128. return isOk;
  129. }
  130. static bool hex_viewer_read_file(HexViewer* hex_viewer) {
  131. furi_assert(hex_viewer);
  132. furi_assert(hex_viewer->model->stream);
  133. memset(hex_viewer->model->file_bytes, 0x0, HEX_VIEWER_BUF_SIZE);
  134. bool isOk = true;
  135. do {
  136. uint32_t offset = hex_viewer->model->line * HEX_VIEWER_BYTES_PER_ROW;
  137. if(!stream_seek(hex_viewer->model->stream, offset, true)) {
  138. FURI_LOG_E(TAG, "Unable to seek stream");
  139. isOk = false;
  140. break;
  141. }
  142. hex_viewer->model->read_bytes = stream_read(
  143. hex_viewer->model->stream,
  144. (uint8_t*)hex_viewer->model->file_bytes,
  145. HEX_VIEWER_BUF_SIZE);
  146. } while(false);
  147. return isOk;
  148. }
  149. int32_t hex_viewer_app(void* p) {
  150. HexViewer* hex_viewer = hex_viewer_alloc();
  151. FuriString* file_path;
  152. file_path = furi_string_alloc();
  153. do {
  154. if(p && strlen(p)) {
  155. furi_string_set(file_path, (const char*)p);
  156. } else {
  157. furi_string_set(file_path, HEX_VIEWER_APP_PATH_FOLDER);
  158. DialogsFileBrowserOptions browser_options;
  159. dialog_file_browser_set_basic_options(
  160. &browser_options, HEX_VIEWER_APP_EXTENSION, &I_hex_10px);
  161. browser_options.hide_ext = false;
  162. DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
  163. bool res = dialog_file_browser_show(dialogs, file_path, file_path, &browser_options);
  164. furi_record_close(RECORD_DIALOGS);
  165. if(!res) {
  166. FURI_LOG_I(TAG, "No file selected");
  167. break;
  168. }
  169. }
  170. if(!hex_viewer_open_file(hex_viewer, furi_string_get_cstr(file_path))) break;
  171. hex_viewer_read_file(hex_viewer);
  172. InputEvent input;
  173. while(furi_message_queue_get(hex_viewer->input_queue, &input, FuriWaitForever) ==
  174. FuriStatusOk) {
  175. if(input.key == InputKeyBack) {
  176. break;
  177. } else if(input.key == InputKeyUp) {
  178. furi_check(furi_mutex_acquire(hex_viewer->mutex, FuriWaitForever) == FuriStatusOk);
  179. if(hex_viewer->model->line > 0) {
  180. hex_viewer->model->line--;
  181. if(!hex_viewer_read_file(hex_viewer)) break;
  182. }
  183. furi_mutex_release(hex_viewer->mutex);
  184. } else if(input.key == InputKeyDown) {
  185. furi_check(furi_mutex_acquire(hex_viewer->mutex, FuriWaitForever) == FuriStatusOk);
  186. uint32_t cur_pos = hex_viewer->model->line * HEX_VIEWER_BYTES_PER_ROW +
  187. hex_viewer->model->read_bytes;
  188. if(hex_viewer->model->file_size > cur_pos) {
  189. hex_viewer->model->line++;
  190. if(!hex_viewer_read_file(hex_viewer)) break;
  191. }
  192. furi_mutex_release(hex_viewer->mutex);
  193. } else if(input.key == InputKeyLeft) {
  194. furi_check(furi_mutex_acquire(hex_viewer->mutex, FuriWaitForever) == FuriStatusOk);
  195. hex_viewer->model->mode = !hex_viewer->model->mode;
  196. furi_mutex_release(hex_viewer->mutex);
  197. } else if(input.key == InputKeyRight) {
  198. FuriString* buffer;
  199. buffer = furi_string_alloc();
  200. furi_string_printf(
  201. buffer,
  202. "File path: %s\nFile size: %lu bytes",
  203. furi_string_get_cstr(file_path),
  204. hex_viewer->model->file_size);
  205. DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
  206. DialogMessage* message = dialog_message_alloc();
  207. dialog_message_set_header(message, "Hex Viewer", 16, 2, AlignLeft, AlignTop);
  208. dialog_message_set_icon(message, &I_hex_10px, 3, 2);
  209. dialog_message_set_text(
  210. message, furi_string_get_cstr(buffer), 3, 16, AlignLeft, AlignTop);
  211. dialog_message_set_buttons(message, NULL, NULL, "Back");
  212. dialog_message_show(dialogs, message);
  213. furi_string_free(buffer);
  214. dialog_message_free(message);
  215. furi_record_close(RECORD_DIALOGS);
  216. }
  217. view_port_update(hex_viewer->view_port);
  218. }
  219. } while(false);
  220. furi_string_free(file_path);
  221. hex_viewer_free(hex_viewer);
  222. return 0;
  223. }