forked from Lephenixnoir/hex-editor
basic start with memory viewer and data source logic
This commit is contained in:
parent
b7798d7ef2
commit
1a318f1daf
|
@ -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
|
||||
)
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
#include "buffer.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
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);
|
||||
}
|
|
@ -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 <stdbool.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
// 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);
|
211
src/main.c
211
src/main.c
|
@ -1,12 +1,211 @@
|
|||
#include <gint/display.h>
|
||||
#include <gint/keyboard.h>
|
||||
#include <gint/exc.h>
|
||||
|
||||
#include <justui/jscene.h>
|
||||
#include <justui/jlabel.h>
|
||||
#include <justui/jfkeys.h>
|
||||
#include <justui/jinput.h>
|
||||
#include <justui/jpainted.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
// TODO
|
|
@ -0,0 +1,106 @@
|
|||
#include "source.h"
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
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;
|
||||
}
|
|
@ -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 <stddef.h>
|
||||
|
||||
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);
|
|
@ -0,0 +1 @@
|
|||
// TODO
|
|
@ -0,0 +1,27 @@
|
|||
#include "source.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
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);
|
||||
}
|
|
@ -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 <stdbool.h>
|
||||
#include <sys/types.h>
|
||||
#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"
|
Loading…
Reference in New Issue