hex-editor/src/main.c

625 lines
19 KiB
C

#include "source.h"
#include "heditor.h"
#include "hlist.h"
#include "util.h"
#include <gint/display.h>
#include <gint/keyboard.h>
#include <gint/gint.h>
#include <justui/jscene.h>
#include <justui/jlabel.h>
#include <justui/jfkeys.h>
#include <justui/jinput.h>
#include <justui/jfileselect.h>
#include <justui/jpainted.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define ENABLE_USB 0
#ifdef ENABLE_USB
#include <gint/usb.h>
#include <gint/usb-ff-bulk.h>
#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 : "<unnamed source>",
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;
}