#include "source.h" #include "heditor.h" #include "hlist.h" #include "util.h" #include #include #include #include #include #include #include #include #include #include #include #include #define ENABLE_USB 0 #ifdef ENABLE_USB #include #include #endif // Global application data // struct { /* Title widget, status bar widget, f-keys widget. */ jlabel *title; jlabel *status; jfkeys *fkeys; /* Main editor. */ heditor *editor; /* Source info widget. */ jlabel *source_info; /* Source stats widget and histogram. */ jlabel *source_stats; jpainted *source_histogram; /* Current data source. */ source_t *source; /* Current edition mode. */ bool insert; /* Current view. */ int view; /* Current position in the menus. */ int fkmenu; /* Histogram data. */ int32_t histogram[256]; } app; enum { VIEW_EDITOR, VIEW_INFO, VIEW_STATS, }; enum { FK_EDITOR, FK_EDITOR_SOURCE, FK_EDITOR_OPEN, FK_INFO, FK_STATS, FK_VIEW, }; // General UI update functions // static void fkeys_sprintf(jfkeys *fk, char const *fmt, ...) { static char str[128]; va_list args; va_start(args, fmt); vsnprintf(str, sizeof str, fmt, args); va_end(args); jfkeys_set(fk, str); } static void update_fkeys(void) { if(app.fkmenu == FK_EDITOR) { if(!app.source) jfkeys_set(app.fkeys, "/OPEN;;;;;"); else { bool can_insert = (app.source->cap & SOURCE_RESIZE) != 0; fkeys_sprintf(app.fkeys, "/OPEN;/SOURCE;%s%s;@GOTO;;#VIEW", can_insert ? "@" : "#", app.insert ? "INSERT" : "OVERW"); } } else if(app.fkmenu == FK_EDITOR_SOURCE) { fkeys_sprintf(app.fkeys, "%s;%s;;;;", !app.source->no_origin ? "@SAVE" : "", app.source->cap & SOURCE_SAVEAS ? "@SAVE AS" : ""); } else if(app.fkmenu == FK_EDITOR_OPEN) { jfkeys_set(app.fkeys, "@FILE;@LAZY;@MEMORY;@NEW"); } else if(app.fkmenu == FK_INFO) { jfkeys_set(app.fkeys, ";;;;;#VIEW"); } else if(app.fkmenu == FK_STATS) { jfkeys_set(app.fkeys, ";;;;;#VIEW"); } else if(app.fkmenu == FK_VIEW) { fkeys_sprintf(app.fkeys, ";;;%sEDITOR;%sINFO;%sSTATS", app.view == VIEW_EDITOR ? "#" : "@", app.view == VIEW_INFO ? "#" : "@", app.view == VIEW_STATS ? "#" : "@"); } } static bool has_unsaved_changes(void) { return app.editor->front_buffer_dirty || source_dirty(app.source); } static void update_status(void) { source_t *s = app.source; if(!s) { jwidget_set_visible(app.status, false); jlabel_set_text(app.title, "Hex Editor"); return; } size_t size = source_size(s); jwidget_set_visible(app.status, true); if(s->origin) { char const *dirty = ""; if(app.editor->front_buffer_dirty) dirty = " **"; else if(source_dirty(app.source)) dirty = " *"; jlabel_asprintf(app.title, "%s%s (%s) - Hex Editor", s->origin, dirty, human_size(size, NULL)); } else jlabel_set_text(app.title, "(nil) - Hex Editor"); jlabel_asprintf(app.status, "Buffer: @%X %d/%d", s->buf_offset, s->buf->data_size, s->buf->mem_size); } static void update_source_info(void) { source_t *s = app.source; if(!s) { jlabel_set_text(app.source_info, "No source."); return; } char caps[48]; memset(caps, 0, sizeof caps); if(s->cap & SOURCE_WRITE) strcat(caps, " WRITE"); if(s->cap & SOURCE_RESIZE) strcat(caps, " RESIZE"); if(s->cap & SOURCE_SAVEAS) strcat(caps, " SAVEAS"); jlabel_asprintf(app.source_info, "%s\n" "Size: %d bytes\n" "Front buffer: %p, size %d/%d (x%d)\n" "Front buffer maps to: @%d, size %d\n" "Source type: %s\n" "Capabilities: %s", s->origin ? s->origin : "", source_size(s), s->buf->mem, s->buf->data_size, s->buf->mem_size, s->buf->block_size, s->buf_offset, s->buf_segment_size, s->intf->name, caps + 1); } #define HIST_W 256 #define HIST_H 150 static void update_stats_label(char const *message) { jlabel_set_text(app.source_stats, message); } static void update_stats(void) { memset(app.histogram, 0, sizeof app.histogram); source_t *s = app.source; if(!s) return update_stats_label("No source."); bool dirty = app.editor->front_buffer_dirty; if(dirty) update_stats_label("Warning: unsaved changes ignored!"); int const BUFFER_SIZE = 4096; uint8_t *buf = malloc(BUFFER_SIZE); if(!buf) return update_stats_label("Out of memory!"); int size = source_size(s); for(off_t offset = 0; offset < size;) { int rc = s->intf->read(s->cookie, buf, offset, BUFFER_SIZE); if(rc <= 0) { if(!dirty) update_stats_label("Read error!"); return; } for(int i = 0; i < rc; i++) app.histogram[buf[i]]++; offset += rc; } app.source_histogram->widget.update = true; free(buf); if(!dirty) jlabel_asprintf(app.source_stats, "Bytes processed: %d", size); } static void render_stats_histogram(int x, int y) { int M = 0; for(int i = 0; i < 256; i++) M = max(M, app.histogram[i]); int bars_h = HIST_H - 20; int bars_y = y + bars_h; dline(x, bars_y+1, x + HIST_W - 1, bars_y+1, C_BLACK); dtext_opt(x, bars_y+4, C_BLACK, C_NONE, DTEXT_LEFT, DTEXT_TOP, "0x00", -1); dtext_opt(x + HIST_W - 1, bars_y+4, C_BLACK, C_NONE, DTEXT_RIGHT, DTEXT_TOP, "0xff", -1); if(!M) return; for(int i = 0; i < 256; i++) { if(app.histogram[i] == 0) continue; int bar_height = max(1, app.histogram[i] * bars_h / M); int color = (i & 1) ? C_RGB(4, 4, 4) : C_RGB(16, 16, 16); dline(x+i, bars_y-1, x+i, bars_y-bar_height, color); } } static void open_source(source_t *source, bool insert) { app.source = source; app.fkmenu = FK_EDITOR; app.insert = insert; if(app.source) source_load(app.source, 0, 2048); heditor_set_source(app.editor, app.source); heditor_set_insert_mode(app.editor, app.insert); update_fkeys(); update_status(); } static void render_memory_entry(int x, int y, int w, int h, memory_region_t **r_ptr, bool selected) { memory_region_t *r = *r_ptr; int fg = C_BLACK; if(selected) { drect(x, y, x+w-1, y+h-1, C_BLACK); fg = C_WHITE; } dprint(x+2, y+2, fg, "%08X %s", r->start, r->name); dprint(x+3*w/4, y+2, fg, "(%s)", r->writable ? "rw" : "r"); dtext_opt(x+w-3, y+2, fg, C_NONE, DTEXT_RIGHT, DTEXT_TOP, human_isize(r->end - r->start, NULL), -1); } void hex_view(void) { memset(&app, 0, sizeof app); app.source = NULL; app.insert = false; app.view = VIEW_EDITOR; app.fkmenu = FK_EDITOR; enum { OPEN_LF, OPEN_LZ, SAVE_AS } browser_mode = OPEN_LF; // jscene *scene = jscene_create_fullscreen(NULL); jlabel *title = jlabel_create("Hex Editor", scene); jwidget *stack = jwidget_create(scene); jfkeys *fkeys = jfkeys_create(";;;;;",scene); if(!scene || !title || !stack || !fkeys) { jwidget_destroy(scene); return; } app.title = title; app.fkeys = fkeys; jscene_set_mainmenu(scene, false); jwidget_set_background(title, C_BLACK); jlabel_set_text_color(title, C_WHITE); jwidget_set_stretch(title, 1, 0, false); jwidget_set_padding(title, 3, 6, 3, 6); jlayout_set_vbox(scene)->spacing = 3; jlayout_set_stack(stack); jwidget_set_padding(stack, 0, 6, 0, 6); jwidget_set_stretch(stack, 1, 1, false); // Main tab // jwidget *tab_editor = jwidget_create(NULL); heditor *mem = heditor_create(tab_editor); /* previously 321x153 */ jinput *goto_input = jinput_create("Goto: ", 12, tab_editor); jlabel *status = jlabel_create("", tab_editor); app.editor = mem; app.status = status; jwidget_set_margin(mem, 2, 0, 0, 0); jwidget_set_stretch(mem, 1, 1, false); jwidget_set_stretch(status, 1, 0, false); jwidget_set_padding(status, 2, 0, 0, 0); jwidget_set_borders(status, J_BORDER_SOLID, C_BLACK, 1, 0, 0, 0); jwidget_set_stretch(goto_input, 1, 0, false); jwidget_set_visible(goto_input, false); jlayout_set_vbox(tab_editor)->spacing = 3; jwidget_add_child(stack, tab_editor); jwidget_set_stretch(tab_editor, 1, 1, false); // File selection tab // jfileselect *fileselect = jfileselect_create(stack); jfileselect_set_show_file_size(fileselect, true); jwidget_set_stretch(fileselect, 1, 1, false); // Memory region selection tab // int memory_region_count = 0; while(memory_all_regions[memory_region_count]) memory_region_count++; hlist *memoryselect = hlist_create(stack); jwidget_set_stretch(memoryselect, 1, 1, false); hlist_set_entries(memoryselect, memory_all_regions, sizeof *memory_all_regions, memory_region_count); hlist_set_renderer(memoryselect, 13, render_memory_entry); // Info tab // jwidget *tab_info = jwidget_create(stack); jwidget_set_stretch(tab_info, 1, 1, false); jlayout_set_vbox(tab_info)->spacing = 3; jlabel *source_info = jlabel_create("", tab_info); jlabel_set_wrap_mode(source_info, J_WRAP_WORD); jlabel_set_line_spacing(source_info, 4); jlabel_set_block_alignment(source_info, J_ALIGN_LEFT, J_ALIGN_TOP); jwidget_set_stretch(source_info, 1, 1, false); app.source_info = source_info; // Stats tab // jwidget *tab_stats = jwidget_create(stack); jwidget_set_stretch(tab_stats, 1, 1, false); jlayout_set_vbox(tab_stats)->spacing = 3; jlabel *source_stats = jlabel_create("", tab_stats); jlabel_set_block_alignment(source_stats, J_ALIGN_LEFT, J_ALIGN_TOP); jwidget_set_stretch(source_stats, 1, 1, false); app.source_stats = source_stats; jpainted *source_histogram = jpainted_create(render_stats_histogram, NULL, HIST_W, HIST_H, tab_stats); jwidget_set_margin(source_histogram, 8, 8, 8, 8); app.source_histogram = source_histogram; // Initial state // update_status(); update_fkeys(); jscene_show_and_focus(scene, mem); // Event handling // int key = 0; while(1) { /* TODO: Data can be discarded when going back to the main menu */ jevent e = jscene_run(scene); if(e.type == JSCENE_PAINT) { dclear(C_WHITE); jscene_render(scene); dupdate(); } if(e.type == JINPUT_VALIDATED) { /* Parse string into hexa */ char const *str = jinput_value(goto_input); int mode = (*str == '+') ? 1 : (*str == '-') ? -1 : 0; int target = strtoul(str + (mode != 0), NULL, 16); target -= app.source->address_base; heditor_move_to(mem, mode ? mem->cursor + target * mode : target); } if(e.type == JINPUT_VALIDATED || e.type == JINPUT_CANCELED) { jwidget_set_visible(goto_input, false); jscene_show_and_focus(scene, mem); } if(e.type == JFILESELECT_LOADED) { char const *path = jfileselect_current_folder(fileselect); jlabel_asprintf(title, "Browsing: %s", path ? path : "(nil)"); } if(e.type == JFILESELECT_CANCELED || e.type == HLIST_CANCELED) { jscene_show_and_focus(scene, mem); update_status(); app.fkmenu = FK_EDITOR; update_fkeys(); } if(e.type == JFILESELECT_VALIDATED) { char const *file = jfileselect_selected_file(fileselect); if(file) { if(browser_mode == OPEN_LF || browser_mode == OPEN_LZ) { if(app.source) source_free(app.source); open_source(browser_mode == OPEN_LZ ? source_lazy_file_open(file) : source_loaded_file_open(file), false); jscene_show_and_focus(scene, mem); } else if(browser_mode == SAVE_AS) { if(heditor_save(mem, file)) { app.fkmenu = FK_EDITOR; update_fkeys(); } jscene_show_and_focus(scene, mem); update_status(); } } } if(e.type == HEDITOR_CHANGED) { update_status(); } if(e.type == HLIST_VALIDATED) { int i = hlist_selected_index(memoryselect); if(i >= 0) { if(app.source) source_free(app.source); open_source(source_memory_open(memory_all_regions[i]), false); jscene_show_and_focus(scene, mem); } } // Fkey menu navigation // if(e.type != JSCENE_KEY || e.key.type == KEYEV_UP) continue; key = e.key.key; if(key == KEY_MENU) { if(app.source && app.source->intf->close_on_return_to_menu) { source_free(app.source); app.source = NULL; app.fkmenu = FK_EDITOR; heditor_set_source(mem, NULL); update_fkeys(); } gint_osmenu(); jscene_queue_event(scene, (jevent){ .type = JSCENE_PAINT }); } #if ENABLE_USB if(key == KEY_OPTN && e.key.shift && e.key.alpha) { if(!usb_is_open()) { usb_interface_t const *intf[] = { &usb_ff_bulk, NULL }; usb_open(intf, GINT_CALL_NULL); usb_open_wait(); } usb_fxlink_screenshot(true); } #endif if(jscene_focused_widget(scene) == goto_input) continue; if(app.fkmenu == FK_EDITOR) { if(key == KEY_F1) { app.fkmenu = FK_EDITOR_OPEN; update_fkeys(); } else if(key == KEY_F2 && app.source) { app.fkmenu = FK_EDITOR_SOURCE; update_fkeys(); } else if(key == KEY_F3 && app.source) { if(app.source->cap & SOURCE_RESIZE) { app.insert = !app.insert; heditor_set_insert_mode(mem, app.insert); update_fkeys(); } } else if(key == KEY_F4 && app.source) { jinput_clear(goto_input); jscene_show_and_focus(scene, goto_input); } else if(key == KEY_F6 && app.source) { app.fkmenu = FK_VIEW; update_fkeys(); } } else if(app.fkmenu == FK_EDITOR_SOURCE) { if(key == KEY_F1 && app.source && !app.source->no_origin) { if(heditor_save(mem, NULL)) { app.fkmenu = FK_EDITOR; update_fkeys(); } } else if(key == KEY_F2 && app.source) { if(app.source->cap & SOURCE_SAVEAS) { browser_mode = SAVE_AS; jfileselect_set_saveas(fileselect, true); jfileselect_browse(fileselect, "/"); jscene_show_and_focus(scene, fileselect); } } else if(key == KEY_EXIT) { app.fkmenu = FK_EDITOR; update_fkeys(); } } else if(app.fkmenu == FK_EDITOR_OPEN) { if(key == KEY_F1) { if(!has_unsaved_changes() || confirm_discard()) { browser_mode = OPEN_LF; jfileselect_set_saveas(fileselect, false); jfileselect_browse(fileselect, "/"); jscene_show_and_focus(scene, fileselect); } scene->widget.update = true; } else if(key == KEY_F2) { if(!has_unsaved_changes() || confirm_discard()) { browser_mode = OPEN_LZ; jfileselect_set_saveas(fileselect, false); jfileselect_browse(fileselect, "/"); jscene_show_and_focus(scene, fileselect); } scene->widget.update = true; } else if(key == KEY_F3) { if(!has_unsaved_changes() || confirm_discard()) { jscene_show_and_focus(scene, memoryselect); jlabel_set_text(title, "Browsing: memory"); } scene->widget.update = true; } else if(key == KEY_F4) { if(!has_unsaved_changes() || confirm_discard()) { if(app.source) source_free(app.source); open_source(source_loaded_file_new(), true); jscene_show_and_focus(scene, mem); } } else if(key == KEY_EXIT) { app.fkmenu = FK_EDITOR; update_fkeys(); } } else if(app.fkmenu == FK_INFO) { if(key == KEY_F6 && app.source) { app.fkmenu = FK_VIEW; update_fkeys(); } } else if(app.fkmenu == FK_STATS) { if(key == KEY_F6 && app.source) { app.fkmenu = FK_VIEW; update_fkeys(); } } else if(app.fkmenu == FK_VIEW) { if(key == KEY_F4) { jscene_show_and_focus(scene, mem); app.fkmenu = FK_EDITOR; app.view = VIEW_EDITOR; update_fkeys(); } else if(key == KEY_F5) { jscene_show_and_focus(scene, source_info); app.fkmenu = FK_INFO; app.view = VIEW_INFO; update_source_info(); update_fkeys(); } else if(key == KEY_F6) { jscene_show_and_focus(scene, source_stats); app.fkmenu = FK_STATS; app.view = VIEW_STATS; update_stats(); update_fkeys(); } else if(key == KEY_EXIT) { if(app.view == VIEW_EDITOR) app.fkmenu = FK_EDITOR; else if(app.view == VIEW_INFO) app.fkmenu = FK_INFO; else if(app.view == VIEW_STATS) app.fkmenu = FK_STATS; update_fkeys(); } } } jwidget_destroy(scene); } int main(void) { hex_view(); return 1; }