diff --git a/CMakeLists.txt b/CMakeLists.txt index 477a00c..a7cbeff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,7 +10,10 @@ find_package(Gint 2.8 REQUIRED) find_package(JustUI 1.0 REQUIRED) set(SOURCES + src/buffer.c src/main.c + src/source.c + src/source-loaded-file.c ) set(ASSETS ) diff --git a/src/buffer.c b/src/buffer.c new file mode 100644 index 0000000..7367ebb --- /dev/null +++ b/src/buffer.c @@ -0,0 +1,74 @@ +#include "buffer.h" +#include +#include + +static inline size_t round_up_to_multiple(size_t n, int multiple) +{ + n += multiple - 1; + return n - (n % multiple); +} + +buffer_t *buffer_create(size_t initial_size, int block_size) +{ + buffer_t *b = calloc(1, sizeof *b); + if(!b || block_size <= 0) goto fail; + + /* Round initial size to the next multiple of the block size */ + b->data_size = initial_size; + b->mem_size = round_up_to_multiple(initial_size, block_size); + b->block_size = block_size; + b->mem = malloc(b->mem_size); + if(!b->mem) goto fail; + + return b; + +fail: + buffer_free(b); + return NULL; +} + +bool buffer_resize(buffer_t *b, size_t new_mem_size) +{ + /* Determine whether we need to reallocate */ + new_mem_size = round_up_to_multiple(new_mem_size, b->block_size); + bool expanding = new_mem_size > b->mem_size; + bool shrinking = new_mem_size + b->block_size < b->mem_size; + + if(expanding || shrinking) { + void *new_mem = realloc(b->mem, new_mem_size); + if(!new_mem) + return false; + b->mem = new_mem; + b->mem_size = new_mem_size; + } + return true; +} + +bool buffer_write(buffer_t *b, void *data, size_t data_size, off_t offset, + size_t segment_size) +{ + if(!b || offset < 0 || offset + segment_size > b->data_size) + return false; + + if(data_size == segment_size) { + memcpy(b->mem + offset, data, segment_size); + return true; + } + + size_t new_data_size = b->data_size - segment_size + data_size; + buffer_resize(b, new_data_size); + + /* Move existing data out of the way, then perform the copy */ + memmove(b->mem + offset + data_size, b->mem + offset + segment_size, + b->data_size - (offset + segment_size)); + memcpy(b->mem + offset, data, data_size); + b->data_size = new_data_size; + return true; +} + +void buffer_free(buffer_t *b) +{ + if(b) + free(b->mem); + free(b); +} diff --git a/src/buffer.h b/src/buffer.h new file mode 100644 index 0000000..da9706b --- /dev/null +++ b/src/buffer.h @@ -0,0 +1,44 @@ +// buffer: A resizable buffer +// +// This utility provides an interface for a RAM buffer that can grow and +// shrink, allocates its size in multiples of a reasonably-large block size, +// and avoids reallocation when possible. + +#pragma once +#include +#include + +// Buffer type // + +typedef struct { + /* Pointer to memory. The buffer is of size `mem_size`. The first + `data_size` bytes hold the buffer data; the rest has undefined value. */ + void *mem; + size_t mem_size; + size_t data_size; + /* Allocation parameter: block size */ + int block_size; +} buffer_t; + +// Buffer API // + +/* Create a new buffer of size `initial_size`. Memory is allocated in blocks of + size `block_size`, to avoid reallocating all the time. The buffer memory is + not initialized. */ +buffer_t *buffer_create(size_t initial_size, int block_size); + +/* Resize the buffer's data to a given size. This can cause the mem array to + expand or shrink depending on the block size. When expanding, the data is + kept unchanged. When shrinking, data beyond the new size is lost. */ +bool buffer_resize(buffer_t *b, size_t new_mem_size); + +/* Write into the buffer. For simple writes, the `mem` attribute can be used + directly. This function allows a segment of the buffer to be replaced by a + segment of different size, resulting in data movement and possibly a + reallocation. */ +bool buffer_write(buffer_t *b, + void *data, size_t data_size, /* Source */ + off_t offset, size_t segment_size); /* Destination */ + +/* Free a buffer, including `b` itself. */ +void buffer_free(buffer_t *b); diff --git a/src/main.c b/src/main.c index 1a9e9b4..2776352 100644 --- a/src/main.c +++ b/src/main.c @@ -1,12 +1,211 @@ #include #include +#include + +#include +#include +#include +#include +#include + +#include +#include + +struct view { + uint32_t base; + bool ascii; + int lines; +}; + +/* Code of exception that occurs during a memory access */ +static uint32_t exception = 0; +/* Exception-catching function */ +static int catch_exc(uint32_t code) +{ + if(code == 0x040 || code == 0x0e0) { + exception = code; + gint_exc_skip(1); + return 0; + } + return 1; +} + +int line(uint8_t *mem, char *header, char *bytes, char *ascii, int n) +{ + /* First do a naive access to the first byte, and record possible + exceptions - TLB miss read and CPU read error. + I use a volatile asm statement so that the read can't be optimized + away or moved by the compiler. */ + exception = 0; + gint_exc_catch(catch_exc); + uint8_t z; + __asm__ volatile("mov.l %1, %0" : "=r"(z) : "m"(*mem)); + gint_exc_catch(NULL); + + sprintf(header, "%08X:", (uint32_t)mem); + + if(exception == 0x040) { + sprintf(bytes, "TLB error"); + memset(ascii, 0, n); + return 1; + } + if(exception == 0x0e0) { + sprintf(bytes, "Read error"); + memset(ascii, 0, n); + return 1; + } + + /* Read single bytes when possible, but longwords when in SPU memory */ + for(int k = 0; k < n; k++) { + uint32_t addr = (uint32_t)mem; + int c = 0x11; + + /* XRAM, YRAM, PRAM */ + if((addr & 0xffc00000) == 0xfe000000) { + uint32_t l = *(uint32_t *)(addr & ~3); + c = (l << (addr & 3) * 8) >> 24; + } + else c = mem[k]; + + ascii[k] = (c >= 0x20 && c < 0x7f) ? c : '.'; + } + ascii[n] = 0; + + for(int k = 0; 2 * k < n; k++) + sprintf(bytes + 5 * k, "%02X%02X ", mem[2*k], mem[2*k+1]); + + return 0; +} + +static void paint_mem(int x, int y, struct view *v) +{ + char header[12], bytes[48], ascii[24]; + uint32_t addr = v->base; + uint8_t *mem = (void *)addr; + + for(int i = 0; i < v->lines; i++, mem += 8, addr += 8) { + int status = line(mem, header, bytes, ascii, 8); + + dtext(x, y + 12*i, C_BLACK, header); + dtext(x + 85, y + 12*i, status ? C_RED : C_BLACK, bytes); + + for(int k = 7; k >= 0; k--) { + ascii[k+1] = 0; + dtext(x + 250 + 9*k, y + 12*i, C_BLACK, ascii+k); + } + } +} + +static void update_status_bar(jlabel *status, struct view *v) +{ + jlabel_asprintf(status, "%X/%X", v->base, 8*v->lines); +} + +void hex_view(void) +{ + struct view v = { .base = 0x88000000, .ascii = false, .lines = 13 }; + + jscene *scene = jscene_create_fullscreen(NULL); + jlabel *title = jlabel_create("Hex Editor", scene); + jwidget *stack = jwidget_create(scene); + jfkeys *fkeys = jfkeys_create("@JUMP;;#ROM;#RAM;#ILRAM;#ADDIN", scene); + + if(!scene || !title || !stack || !fkeys) { + jwidget_destroy(scene); + return; + } + + 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 = jwidget_create(NULL); + jpainted *mem = jpainted_create(paint_mem, &v, 321, 12*v.lines-3, tab); + jlabel *status = jlabel_create("", tab); + jinput *input = jinput_create("Go to: ", 12, tab); + + jwidget_set_margin(mem, 4, 0, 4, 0); + jwidget_set_stretch(mem, 1, 0, false); + + jwidget_set_stretch(status, 1, 0, false); + + jwidget_set_margin(input, 0, 0, 0, 4); + jwidget_set_stretch(input, 1, 0, false); + jwidget_set_visible(input, false); + + jlayout_set_vbox(tab)->spacing = 4; + jwidget_add_child(stack, tab); + jwidget_set_stretch(tab, 1, 1, false); + + update_status_bar(status, &v); + + // Event handling // + + int key = 0; + while(key != KEY_EXIT) + { + bool input_focus = (jscene_focused_widget(scene) == input); + 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 */ + uint32_t target = 0; + char const *str = jinput_value(input); + + for(int k = 0; k < str[k]; k++) + { + target <<= 4; + if(str[k] <= '9') target += (str[k] - '0'); + else target += ((str[k]|0x20) - 'a' + 10); + } + v.base = target & ~7; + } + if(e.type == JINPUT_VALIDATED || e.type == JINPUT_CANCELED) { + jwidget_set_visible(input, false); + jwidget_set_visible(fkeys, true); + jscene_set_focused_widget(scene, NULL); + } + + if(e.type != JSCENE_KEY || e.key.type == KEYEV_UP) continue; + key = e.key.key; + + int move_speed = (e.key.shift ? 8 : 1); + if(key == KEY_UP) v.base -= move_speed * 8 * v.lines; + if(key == KEY_DOWN) v.base += move_speed * 8 * v.lines; + + if(key == KEY_F1 && !input_focus) { + jinput_clear(input); + jwidget_set_visible(input, true); + jwidget_set_visible(fkeys, false); + jscene_set_focused_widget(scene, input); + } + + if(key == KEY_F3 && !input_focus) v.base = 0x80000000; + if(key == KEY_F4 && !input_focus) v.base = 0x88000000; + if(key == KEY_F5 && !input_focus) v.base = 0xe5200000; + if(key == KEY_F6 && !input_focus) v.base = 0x00300000; + mem->widget.update = 1; + + update_status_bar(status, &v); + } + jwidget_destroy(scene); +} int main(void) { - dclear(C_WHITE); - dtext(1, 1, C_BLACK, "Sample fxSDK add-in."); - dupdate(); - - getkey(); - return 1; + hex_view(); + return 1; } diff --git a/src/source-lazy-file.h b/src/source-lazy-file.h new file mode 100644 index 0000000..70b786d --- /dev/null +++ b/src/source-lazy-file.h @@ -0,0 +1 @@ +// TODO diff --git a/src/source-loaded-file.c b/src/source-loaded-file.c new file mode 100644 index 0000000..f074534 --- /dev/null +++ b/src/source-loaded-file.c @@ -0,0 +1,106 @@ +#include "source.h" +#include +#include +#include +#include + +static void lf_free(loaded_file_t *lf) +{ + if(lf) { + free(lf->path); + buffer_free(lf->buf); + } + free(lf); +} + +static bool lf_read(void *_cookie, void *buf, off_t offset, size_t size) +{ + loaded_file_t *lf = _cookie; + if(offset < 0 || offset + size > lf->buf->data_size) + return false; + + memcpy(buf, lf->buf->mem + offset, size); + return true; +} + +static bool lf_write(void *_cookie, void *data, size_t data_size, off_t offset, + size_t segment_size) +{ + loaded_file_t *lf = _cookie; + return buffer_write(lf->buf, data, data_size, offset, segment_size); +} + +static bool lf_save(void *_cookie) +{ + loaded_file_t *lf = _cookie; + + /* If the new file is larger than the original, we can just overwrite it + without truncating, which is much faster with Fugue */ + int flags = O_WRONLY | O_TRUNC; + if(lf->buf->data_size >= lf->original_size) + flags = O_WRONLY; + int fd = open(lf->path, flags); + if(!fd) goto fail; + + ssize_t rc = write(fd, lf->buf->mem, lf->buf->data_size); + if(rc < 0 || (size_t)rc != lf->buf->data_size) goto fail; + + close(fd); + return true; + +fail: + close(fd); + return false; +} + +static void lf_close(void *_cookie) +{ + loaded_file_t *lf = _cookie; + lf_free(lf); +} + +static source_intf_t lf_intf = { + .cap = SOURCE_WRITE | SOURCE_EXPAND | SOURCE_SHRINK, + .read = lf_read, + .write = lf_write, + .save = lf_save, + .close = lf_close, +}; + +source_t *source_loaded_file_open(char const *path) +{ + int fd = -1; + source_t *source = NULL; + loaded_file_t *lf = NULL; + + lf = calloc(1, sizeof *lf); + if(!lf) goto fail; + + lf->path = strdup(path); + if(!lf->path) goto fail; + + fd = open(path, O_RDONLY); + if(fd < 0) goto fail; + + int size = lseek(fd, 0, SEEK_END); + if(size < 0) goto fail; + + lf->original_size = size; + lf->buf = buffer_create(size, 4096); + if(!lf->buf) goto fail; + + ssize_t rc = pread(fd, lf->buf->mem, size, 0); + if(rc != size) goto fail; + + source = source_open(&lf_intf, lf); + if(!source) goto fail; + + close(fd); + return source; + +fail: + free(source); + close(fd); + lf_free(lf); + return NULL; +} diff --git a/src/source-loaded-file.h b/src/source-loaded-file.h new file mode 100644 index 0000000..153c0fa --- /dev/null +++ b/src/source-loaded-file.h @@ -0,0 +1,22 @@ +// source-loaded-file: A file fully loaded to memory +// +// This data source is a normal file entirely loaded to RAM. The flexibility of +// RAM accesses allow us to implement all of the capabilities. + +#pragma once +#include "buffer.h" +#include + +typedef struct { + /* Path to file (owned by this structure) */ + char *path; + /* Original file size. This is used to determine whether we can save the + file by a normal overwrite (with expansion) or we need to delete and + re-create the file to shrink it */ + size_t original_size; + /* The entire file, as a RAM buffer */ + buffer_t *buf; + +} loaded_file_t; + +source_t *source_loaded_file_open(char const *path); diff --git a/src/source-memory.h b/src/source-memory.h new file mode 100644 index 0000000..70b786d --- /dev/null +++ b/src/source-memory.h @@ -0,0 +1 @@ +// TODO diff --git a/src/source.c b/src/source.c new file mode 100644 index 0000000..ae0b177 --- /dev/null +++ b/src/source.c @@ -0,0 +1,27 @@ +#include "source.h" +#include + +source_t *source_open(source_intf_t *intf, void *cookie) +{ + source_t *source = calloc(1, sizeof *source); + if(!source) return NULL; + + source->buf = buffer_create(1024, 1024); + if(!source->buf) { + free(source); + return NULL; + } + + source->intf = intf; + source->cookie = cookie; + return source; +} + +bool source_load(source_t *s, off_t offset, size_t segment_size) +{ + if(!buffer_resize(s->buf, segment_size)) + return false; + + s->buf_offset = offset; + return s->intf->read(s->cookie, s->buf->mem, offset, segment_size); +} diff --git a/src/source.h b/src/source.h new file mode 100644 index 0000000..b418da2 --- /dev/null +++ b/src/source.h @@ -0,0 +1,64 @@ +// source: A data source to read and edit +// +// This header provides the main definitions for data sources, which back the +// data that is shown and edited in the application. Data sources implement a +// base interface for reading and writing chunks, with some optional features +// being defined by capabilities. + +#pragma once +#include +#include +#include "buffer.h" + +// Data source capabilities // + +/* The data source is writable and can be modified by API. */ +#define SOURCE_WRITE 0x01 +/* The data source can expand, adding new bytes at the end. */ +#define SOURCE_EXPAND 0x02 +/* The data source can shrink, removing bytes at the end. */ +#define SOURCE_SHRINK 0x03 + +// Interface to data sources // + +typedef struct { + /* Source capabilities */ + int cap; + /* Load a chunk from the data source into a buffer. */ + bool (*read)(void *_cookie, void *buf, off_t offset, size_t size); + /* Write from a buffer into a segment of the data source. If the interface + has the SOURCE_EXPAND capabilitiy, the buffer might be larger than the + segment. Similarly, if the interfadce has the SOURCE_SHRINK capability, + the buffer might be smaller than the segment. */ + bool (*write)(void *_cookie, void *buf, size_t buf_size, off_t offset, + size_t segment_size); + /* Save the data to its original medium. */ + bool (*save)(void *_cookie); + /* Close the data source. */ + void (*close)(void *_cookie); +} source_intf_t; + +// An open data source // + +typedef struct { + /* Interface identifier, and opaque interface data */ + source_intf_t *intf; + void *cookie; + /* Front buffer for edition */ + buffer_t *buf; + /* Address of the front buffer in the source */ + off_t buf_offset; +} source_t; + +/* Open a data source; this is a low-level function used by source-specific + opening functions. */ +source_t *source_open(source_intf_t *intf, void *cookie); + +/* Replace the source's buffer's contents with data read from the source. */ +bool source_load(source_t *s, off_t offset, size_t segment_size); + +// Supported data sources // + +#include "source-loaded-file.h" +#include "source-lazy-file.h" +#include "source-memory.h"