basic start with memory viewer and data source logic

This commit is contained in:
Lephenixnoir 2022-06-18 18:17:31 +01:00
parent b7798d7ef2
commit 1a318f1daf
Signed by untrusted user: Lephenixnoir
GPG Key ID: 1BBA026E13FC0495
10 changed files with 547 additions and 6 deletions

View File

@ -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
)

74
src/buffer.c Normal file
View File

@ -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);
}

44
src/buffer.h Normal file
View File

@ -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);

View File

@ -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;
}

1
src/source-lazy-file.h Normal file
View File

@ -0,0 +1 @@
// TODO

106
src/source-loaded-file.c Normal file
View File

@ -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;
}

22
src/source-loaded-file.h Normal file
View File

@ -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);

1
src/source-memory.h Normal file
View File

@ -0,0 +1 @@
// TODO

27
src/source.c Normal file
View File

@ -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);
}

64
src/source.h Normal file
View File

@ -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"