From 64730bbfb24462926cc09aa0cecdcb03429d201f Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Sun, 18 Jun 2023 00:53:11 +0100 Subject: [PATCH] Add hex editor --- .../external/hex_editor/application.fam | 16 + applications/external/hex_editor/hex_editor.c | 357 ++++++++++++++++++ .../hex_editor/icons/Pin_arrow_up_7x9.png | Bin 0 -> 3603 bytes .../external/hex_editor/icons/edit_10px.png | Bin 0 -> 150 bytes 4 files changed, 373 insertions(+) create mode 100644 applications/external/hex_editor/application.fam create mode 100644 applications/external/hex_editor/hex_editor.c create mode 100644 applications/external/hex_editor/icons/Pin_arrow_up_7x9.png create mode 100644 applications/external/hex_editor/icons/edit_10px.png diff --git a/applications/external/hex_editor/application.fam b/applications/external/hex_editor/application.fam new file mode 100644 index 000000000..cb7f878f1 --- /dev/null +++ b/applications/external/hex_editor/application.fam @@ -0,0 +1,16 @@ +App( + appid="hex_editor", + name="HEX Editor", + apptype=FlipperAppType.EXTERNAL, + entry_point="hex_editor_app", + cdefines=["APP_HEX_EDITOR"], + requires=[ + "gui", + "dialogs", + ], + stack_size=2 * 1024, + order=20, + fap_icon="icons/edit_10px.png", + fap_category="Tools", + fap_icon_assets="icons", +) diff --git a/applications/external/hex_editor/hex_editor.c b/applications/external/hex_editor/hex_editor.c new file mode 100644 index 000000000..5e5dc807c --- /dev/null +++ b/applications/external/hex_editor/hex_editor.c @@ -0,0 +1,357 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +// #include + +#define TAG "HexEditor" + +typedef struct { + // uint8_t file_bytes[HEX_editor_LINES_ON_SCREEN][HEX_editor_BYTES_PER_LINE]; + uint32_t file_offset; + uint32_t file_read_bytes; + uint32_t file_size; + uint8_t string_offset; + char editable_char; + Stream* stream; + bool mode; // Print address or content +} HexEditorModel; + +typedef struct { + HexEditorModel* model; + FuriMutex** mutex; + + FuriMessageQueue* input_queue; + + ViewPort* view_port; + Gui* gui; + Storage* storage; + + FuriString* buffer; +} HexEditor; + +static void draw_callback(Canvas* canvas, void* ctx) { + // UNUSED(ctx); + HexEditor* hex_editor = ctx; + + canvas_clear(canvas); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 0, 10, "Line and mode:"); + // elements_button_right(canvas, "Info"); + + // // elements_string_fit_width(canvas, buffer, 100); + canvas_set_font(canvas, FontSecondary); + + canvas_draw_str_aligned( + canvas, + 0, + 20, + AlignLeft, + AlignBottom, + furi_string_get_cstr(hex_editor->buffer) + hex_editor->model->string_offset); + // elements_scrollable_text_line( + // canvas, 0, 20, 128, hex_editor->buffer, hex_editor->model->string_offset, false); + + // canvas_draw_line(canvas, 3, 20, 5, 30); + + canvas_draw_icon(canvas, 0, 20, &I_Pin_arrow_up_7x9); + + if(hex_editor->model->mode) { + elements_button_left(canvas, "ASCII -"); + elements_button_right(canvas, "ASCII +"); + } else { + elements_button_left(canvas, ""); + elements_button_right(canvas, ""); + } + + canvas_set_font(canvas, FontPrimary); + canvas_draw_glyph(canvas, 0, 45, '0' + hex_editor->model->mode); + canvas_draw_glyph(canvas, 30, 45, hex_editor->model->editable_char); +} + +static void input_callback(InputEvent* input_event, void* ctx) { + // Проверяем, что контекст не нулевой + furi_assert(ctx); + HexEditor* hex_editor = ctx; + + furi_message_queue_put(hex_editor->input_queue, input_event, 100); +} + +static HexEditor* hex_editor_alloc() { + HexEditor* instance = malloc(sizeof(HexEditor)); + + instance->model = malloc(sizeof(HexEditorModel)); + memset(instance->model, 0x0, sizeof(HexEditorModel)); + + instance->model->editable_char = ' '; + + instance->mutex = furi_mutex_alloc(FuriMutexTypeNormal); + + instance->input_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); + + instance->view_port = view_port_alloc(); + view_port_draw_callback_set(instance->view_port, draw_callback, instance); + view_port_input_callback_set(instance->view_port, input_callback, instance); + + instance->gui = furi_record_open(RECORD_GUI); + gui_add_view_port(instance->gui, instance->view_port, GuiLayerFullscreen); + + instance->storage = furi_record_open(RECORD_STORAGE); + + instance->buffer = furi_string_alloc(); + + return instance; +} + +static void hex_editor_free(HexEditor* instance) { + furi_record_close(RECORD_STORAGE); + + gui_remove_view_port(instance->gui, instance->view_port); + furi_record_close(RECORD_GUI); + view_port_free(instance->view_port); + + furi_message_queue_free(instance->input_queue); + + furi_mutex_free(instance->mutex); + + if(instance->model->stream) buffered_file_stream_close(instance->model->stream); + + furi_string_free(instance->buffer); + + free(instance->model); + free(instance); +} + +static bool hex_editor_open_file(HexEditor* hex_editor, const char* file_path) { + furi_assert(hex_editor); + furi_assert(file_path); + + hex_editor->model->stream = buffered_file_stream_alloc(hex_editor->storage); + bool isOk = true; + + do { + if(!buffered_file_stream_open( + hex_editor->model->stream, file_path, FSAM_READ_WRITE, FSOM_OPEN_EXISTING)) { + FURI_LOG_E(TAG, "Unable to open stream: %s", file_path); + isOk = false; + break; + }; + + hex_editor->model->file_size = stream_size(hex_editor->model->stream); + } while(false); + + return isOk; +} + +// static bool hex_editor_read_file(HexEditor* hex_editor) { +// furi_assert(hex_editor); +// furi_assert(hex_editor->model->stream); +// // furi_assert(hex_editor->model->file_offset % hex_editor_BYTES_PER_LINE == 0); + +// memset(hex_editor->model->file_bytes, 0x0, hex_editor_BUF_SIZE); +// bool isOk = true; + +// do { +// uint32_t offset = hex_editor->model->file_offset; +// if(!stream_seek(hex_editor->model->stream, offset, true)) { +// FURI_LOG_E(TAG, "Unable to seek stream"); +// isOk = false; +// break; +// } + +// hex_editor->model->file_read_bytes = stream_read( +// hex_editor->model->stream, +// (uint8_t*)hex_editor->model->file_bytes, +// hex_editor_BUF_SIZE); +// } while(false); + +// return isOk; +// } + +int32_t hex_editor_app(void* p) { + UNUSED(p); + + HexEditor* hex_editor = hex_editor_alloc(); + + FuriString* file_path; + file_path = furi_string_alloc(); + + // furi_string_printf( + // hex_editor->buffer, + // "qqqqq1231231232343454565676urtfgsdfascesc\nasdqwe\new ra sssssssssssssssssssssssssqqqqqqqqqqq1231231232343454565676urtfgsdfascesc\nq2e"); + + do { + if(p && strlen(p)) { + furi_string_set(file_path, (const char*)p); + } else { + furi_string_set(file_path, "/any"); + + DialogsFileBrowserOptions browser_options; + dialog_file_browser_set_basic_options(&browser_options, "*", &I_edit_10px); + browser_options.hide_ext = false; + + DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); + bool res = dialog_file_browser_show(dialogs, file_path, file_path, &browser_options); + + furi_record_close(RECORD_DIALOGS); + if(!res) { + FURI_LOG_I(TAG, "No file selected"); + break; + } + } + + FURI_LOG_I(TAG, "File selected: %s", furi_string_get_cstr(file_path)); + + if(!hex_editor_open_file(hex_editor, furi_string_get_cstr(file_path))) break; + + if(!stream_read_line(hex_editor->model->stream, hex_editor->buffer)) { + FURI_LOG_T(TAG, "No keys left in dict"); + break; + } + + InputEvent event; + int8_t off; + while(1) { + // Выбираем событие из очереди в переменную event (ждем бесконечно долго, если очередь пуста) + // и проверяем, что у нас получилось это сделать + furi_check( + furi_message_queue_get(hex_editor->input_queue, &event, FuriWaitForever) == + FuriStatusOk); + + // Если нажата кнопка "назад", то выходим из цикла, а следовательно и из приложения + if(event.type == InputTypeShort || event.type == InputTypeRepeat) { + if(!hex_editor->model->mode) { + off = 1; + if(event.type == InputTypeRepeat) { + off = 2; + } + if(event.key == InputKeyRight) { + hex_editor->model->string_offset += off; + if(hex_editor->model->string_offset >= + furi_string_size(hex_editor->buffer)) { + // dengeros + hex_editor->model->string_offset -= + furi_string_size(hex_editor->buffer); + } + } + if(event.key == InputKeyLeft) { + if(hex_editor->model->string_offset - off < 0) { + // dengeros + hex_editor->model->string_offset += + furi_string_size(hex_editor->buffer); + } + hex_editor->model->string_offset -= off; + } + if(event.key == InputKeyDown) { + hex_editor->model->string_offset = 0; + if(!stream_read_line(hex_editor->model->stream, hex_editor->buffer)) { + FURI_LOG_T(TAG, "No keys left in dict"); + } + } + if(event.key == InputKeyUp) { + hex_editor->model->string_offset = 0; + // TODO asert + if(!stream_seek(hex_editor->model->stream, -1, StreamOffsetFromCurrent)) { + FURI_LOG_E(TAG, "Unable to seek stream"); + break; + } + // NOT work on first line + stream_seek_to_char( + hex_editor->model->stream, '\n', StreamDirectionBackward); + + // if(!stream_seek(hex_editor->model->stream, -1, StreamOffsetFromCurrent)) { + // FURI_LOG_E(TAG, "Unable to seek stream"); + // break; + // } + if(!stream_seek_to_char( + hex_editor->model->stream, '\n', StreamDirectionBackward)) { + stream_rewind(hex_editor->model->stream); + } else { + if(!stream_seek( + hex_editor->model->stream, 1, StreamOffsetFromCurrent)) { + FURI_LOG_E(TAG, "Unable to seek stream"); + break; + } + } + + if(!stream_read_line(hex_editor->model->stream, hex_editor->buffer)) { + FURI_LOG_T(TAG, "No keys left in dict"); + break; + } + } + + if(event.key == InputKeyOk) { + hex_editor->model->editable_char = furi_string_get_char( + hex_editor->buffer, hex_editor->model->string_offset); + + hex_editor->model->mode = 1; + } + } else { + off = 1; + if(event.type == InputTypeRepeat) { + off = 4; + } + if(event.key == InputKeyRight) { + hex_editor->model->editable_char += off; + } + if(event.key == InputKeyLeft) { + hex_editor->model->editable_char -= off; + } + + if(event.key == InputKeyOk) { + if(!stream_seek(hex_editor->model->stream, -1, StreamOffsetFromCurrent)) { + FURI_LOG_E(TAG, "Unable to seek stream"); + break; + } + stream_seek_to_char( + hex_editor->model->stream, '\n', StreamDirectionBackward); + stream_seek( + hex_editor->model->stream, + hex_editor->model->string_offset + 1, + StreamOffsetFromCurrent); + + stream_write_char( + hex_editor->model->stream, hex_editor->model->editable_char); + + hex_editor->model->editable_char = ' '; + + hex_editor->model->mode = 0; + + stream_seek_to_char( + hex_editor->model->stream, '\n', StreamDirectionBackward); + + if(!stream_seek(hex_editor->model->stream, 1, StreamOffsetFromCurrent)) { + FURI_LOG_E(TAG, "Unable to seek stream"); + break; + } + + if(!stream_read_line(hex_editor->model->stream, hex_editor->buffer)) { + FURI_LOG_T(TAG, "No keys left in dict"); + break; + } + } + } + } + if(event.key == InputKeyBack) { + break; + } + // ? + view_port_update(hex_editor->view_port); + } + } while(false); + + furi_string_free(file_path); + hex_editor_free(hex_editor); + + return 0; +} \ No newline at end of file diff --git a/applications/external/hex_editor/icons/Pin_arrow_up_7x9.png b/applications/external/hex_editor/icons/Pin_arrow_up_7x9.png new file mode 100644 index 0000000000000000000000000000000000000000..a91a6fd5e99a72112e28865cd8a004c7d1933fff GIT binary patch literal 3603 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 literal 0 HcmV?d00001