diff --git a/build.sh b/build.sh index 4f80a48..64dffc1 100755 --- a/build.sh +++ b/build.sh @@ -3,7 +3,7 @@ # format code # clang-format -i src/porporo.c src/devices/* -SRC="src/uxn.c src/devices/system.c src/devices/screen.c src/devices/controller.c src/devices/mouse.c src/devices/datetime.c src/porporo.c" +SRC="src/uxn.c src/devices/system.c src/devices/screen.c src/devices/controller.c src/devices/mouse.c src/devices/file.c src/devices/datetime.c src/porporo.c" # remove old rm -f bin/porporo diff --git a/src/devices/file.c b/src/devices/file.c new file mode 100644 index 0000000..cd83bde --- /dev/null +++ b/src/devices/file.c @@ -0,0 +1,287 @@ +#define _XOPEN_SOURCE 500 +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#define realpath(s, dummy) lrealpath(s) +#define DIR_SEP_CHAR '\\' +#define DIR_SEP_STR "\\" +#define pathcmp(path1, path2, length) strncasecmp(path1, path2, length) /* strncasecmp provided by libiberty */ +#define notdriveroot(file_name) (file_name[0] != DIR_SEP_CHAR && ((strlen(file_name) > 2 && file_name[1] != ':') || strlen(file_name) <= 2)) +#else +#define DIR_SEP_CHAR '/' +#define DIR_SEP_STR "/" +#define pathcmp(path1, path2, length) strncmp(path1, path2, length) +#define notdriveroot(file_name) (file_name[0] != DIR_SEP_CHAR) +#endif + +#ifndef PATH_MAX +#define PATH_MAX 4096 +#endif + +#include "../uxn.h" +#include "file.h" + +/* +Copyright (c) 2021-2023 Devine Lu Linvega, Andrew Alderwick + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE. +*/ + +typedef struct { + FILE *f; + DIR *dir; + char current_filename[4096]; + struct dirent *de; + enum { IDLE, + FILE_READ, + FILE_WRITE, + DIR_READ } state; + int outside_sandbox; +} UxnFile; + +static UxnFile uxn_file[POLYFILEY]; + +static void +reset(UxnFile *c) +{ + if(c->f != NULL) { + fclose(c->f); + c->f = NULL; + } + if(c->dir != NULL) { + closedir(c->dir); + c->dir = NULL; + } + c->de = NULL; + c->state = IDLE; + c->outside_sandbox = 0; +} + +static Uint16 +get_entry(char *p, Uint16 len, const char *pathname, const char *basename, int fail_nonzero) +{ + struct stat st; + if(len < strlen(basename) + 8) + return 0; + if(stat(pathname, &st)) + return fail_nonzero ? snprintf(p, len, "!!!! %s\n", basename) : 0; + else if(S_ISDIR(st.st_mode)) + return snprintf(p, len, "---- %s/\n", basename); + else if(st.st_size < 0x10000) + return snprintf(p, len, "%04x %s\n", (unsigned int)st.st_size, basename); + else + return snprintf(p, len, "???? %s\n", basename); +} + +static Uint16 +file_read_dir(UxnFile *c, char *dest, Uint16 len) +{ + static char pathname[4352]; + char *p = dest; + if(c->de == NULL) c->de = readdir(c->dir); + for(; c->de != NULL; c->de = readdir(c->dir)) { + Uint16 n; + if(c->de->d_name[0] == '.' && c->de->d_name[1] == '\0') + continue; + if(strcmp(c->de->d_name, "..") == 0) { + /* hide "sandbox/.." */ + char cwd[PATH_MAX] = {'\0'}, *t; + /* Note there's [currently] no way of chdir()ing from uxn, so $PWD + * is always the sandbox top level. */ + getcwd(cwd, sizeof(cwd)); + /* We already checked that c->current_filename exists so don't need a wrapper. */ + t = realpath(c->current_filename, NULL); + if(strcmp(cwd, t) == 0) { + free(t); + continue; + } + free(t); + } + if(strlen(c->current_filename) + 1 + strlen(c->de->d_name) < sizeof(pathname)) + snprintf(pathname, sizeof(pathname), "%s/%s", c->current_filename, c->de->d_name); + else + pathname[0] = '\0'; + n = get_entry(p, len, pathname, c->de->d_name, 1); + if(!n) break; + p += n; + len -= n; + } + return p - dest; +} + +static char * +retry_realpath(const char *file_name) +{ + char *r, p[PATH_MAX] = {'\0'}, *x; + int fnlen; + if(file_name == NULL) { + errno = EINVAL; + return NULL; + } else if((fnlen = strlen(file_name)) >= PATH_MAX) { + errno = ENAMETOOLONG; + return NULL; + } + if(notdriveroot(file_name)) { + /* TODO: use a macro instead of '/' for absolute path first character so that other systems can work */ + /* if a relative path, prepend cwd */ + getcwd(p, sizeof(p)); + if(strlen(p) + strlen(DIR_SEP_STR) + fnlen >= PATH_MAX) { + errno = ENAMETOOLONG; + return NULL; + } + strcat(p, DIR_SEP_STR); /* TODO: use a macro instead of '/' for the path delimiter */ + } + strcat(p, file_name); + while((r = realpath(p, NULL)) == NULL) { + if(errno != ENOENT) + return NULL; + x = strrchr(p, DIR_SEP_CHAR); /* TODO: path delimiter macro */ + if(x) + *x = '\0'; + else + return NULL; + } + return r; +} + +static void +file_check_sandbox(UxnFile *c) +{ + char *x, *rp, cwd[PATH_MAX] = {'\0'}; + x = getcwd(cwd, sizeof(cwd)); + rp = retry_realpath(c->current_filename); + if(rp == NULL || (x && pathcmp(cwd, rp, strlen(cwd)) != 0)) { + c->outside_sandbox = 1; + fprintf(stderr, "file warning: blocked attempt to access %s outside of sandbox\n", c->current_filename); + } + free(rp); +} + +static Uint16 +file_init(UxnFile *c, char *filename, size_t max_len, int override_sandbox) +{ + char *p = c->current_filename; + size_t len = sizeof(c->current_filename); + reset(c); + if(len > max_len) len = max_len; + while(len) { + if((*p++ = *filename++) == '\0') { + if(!override_sandbox) /* override sandbox for loading roms */ + file_check_sandbox(c); + return 0; + } + len--; + } + c->current_filename[0] = '\0'; + return 0; +} + +static Uint16 +file_read(UxnFile *c, void *dest, int len) +{ + if(c->outside_sandbox) return 0; + if(c->state != FILE_READ && c->state != DIR_READ) { + reset(c); + if((c->dir = opendir(c->current_filename)) != NULL) + c->state = DIR_READ; + else if((c->f = fopen(c->current_filename, "rb")) != NULL) + c->state = FILE_READ; + } + if(c->state == FILE_READ) + return fread(dest, 1, len, c->f); + if(c->state == DIR_READ) + return file_read_dir(c, dest, len); + return 0; +} + +static Uint16 +file_write(UxnFile *c, void *src, Uint16 len, Uint8 flags) +{ + Uint16 ret = 0; + if(c->outside_sandbox) return 0; + if(c->state != FILE_WRITE) { + reset(c); + if((c->f = fopen(c->current_filename, (flags & 0x01) ? "ab" : "wb")) != NULL) + c->state = FILE_WRITE; + } + if(c->state == FILE_WRITE) { + if((ret = fwrite(src, 1, len, c->f)) > 0 && fflush(c->f) != 0) + ret = 0; + } + return ret; +} + +static Uint16 +file_stat(UxnFile *c, void *dest, Uint16 len) +{ + char *basename = strrchr(c->current_filename, DIR_SEP_CHAR); + if(c->outside_sandbox) return 0; + if(basename != NULL) + basename++; + else + basename = c->current_filename; + return get_entry(dest, len, c->current_filename, basename, 0); +} + +static Uint16 +file_delete(UxnFile *c) +{ + return c->outside_sandbox ? 0 : unlink(c->current_filename); +} + +/* IO */ + +void +file_deo(Program *prg, Uint8 id, Uint8 *ram, Uint8 *d, Uint8 port) +{ + UxnFile *c = &uxn_file[id]; + Uint16 addr, len, res; + switch(port) { + case 0x5: + addr = PEEK2(d + 0x4); + len = PEEK2(d + 0xa); + if(len > 0x10000 - addr) + len = 0x10000 - addr; + res = file_stat(c, &ram[addr], len); + POKE2(d + 0x2, res); + break; + case 0x6: + res = file_delete(c); + POKE2(d + 0x2, res); + break; + case 0x9: + addr = PEEK2(d + 0x8); + res = file_init(c, (char *)&ram[addr], 0x10000 - addr, 0); + POKE2(d + 0x2, res); + break; + case 0xd: + addr = PEEK2(d + 0xc); + len = PEEK2(d + 0xa); + if(len > 0x10000 - addr) + len = 0x10000 - addr; + res = file_read(c, &ram[addr], len); + POKE2(d + 0x2, res); + break; + case 0xf: + addr = PEEK2(d + 0xe); + len = PEEK2(d + 0xa); + if(len > 0x10000 - addr) + len = 0x10000 - addr; + res = file_write(c, &ram[addr], len, d[0x7]); + POKE2(d + 0x2, res); + break; + } +} diff --git a/src/devices/file.h b/src/devices/file.h new file mode 100644 index 0000000..9ed2efd --- /dev/null +++ b/src/devices/file.h @@ -0,0 +1,19 @@ +/* +Copyright (c) 2021 Devine Lu Linvega, Andrew Alderwick + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE. +*/ + +#define FILE_VERSION 1 +#define FILE_DEIMASK 0x0000 +#define FILE_DEOMASK 0xa260 + +#define POLYFILEY 2 +#define DEV_FILE0 0xa + +void file_deo(Program *prg, Uint8 id, Uint8 *ram, Uint8 *d, Uint8 port); diff --git a/src/porporo.c b/src/porporo.c index 32b7011..3cd6536 100644 --- a/src/porporo.c +++ b/src/porporo.c @@ -6,6 +6,7 @@ #include "devices/screen.h" #include "devices/controller.h" #include "devices/mouse.h" +#include "devices/file.h" #include "devices/datetime.h" /* @@ -31,7 +32,7 @@ static Uint8 *ram; static int plen; static int reqdraw; -static int dragx, dragy, movemode = 1; +static int dragx, dragy, movemode; static int camerax, cameray; static SDL_Window *gWindow = NULL; @@ -353,6 +354,10 @@ static void handle_mouse(SDL_Event *event) { int i, desk = 1, x = event->motion.x - camerax, y = event->motion.y - cameray; + if(event->type == SDL_MOUSEWHEEL) { + mouse_scroll(&focused->u, &focused->u.dev[0x90], event->wheel.x, event->wheel.y); + return; + } for(i = plen - 1; i; i--) { Program *p = &programs[i]; if(withinprogram(p, x, y)) { @@ -373,8 +378,6 @@ handle_mouse(SDL_Event *event) mouse_down(&p->u, &p->u.dev[0x90], SDL_BUTTON(event->button.button)); else if(event->type == SDL_MOUSEBUTTONUP) mouse_up(&p->u, &p->u.dev[0x90], SDL_BUTTON(event->button.button)); - else if(event->type == SDL_MOUSEWHEEL) - mouse_scroll(&p->u, &p->u.dev[0x90], event->wheel.x, event->wheel.y); return; } } @@ -395,6 +398,7 @@ handle_mouse(SDL_Event *event) static void domouse(SDL_Event *event) { + switch(event->type) { case SDL_MOUSEBUTTONDOWN: handle_mouse(event); break; case SDL_MOUSEMOTION: handle_mouse(event); break; @@ -529,6 +533,8 @@ emu_deo(Uxn *u, Uint8 addr, Uint8 value) case 0x20: screen_deo(prg, u->ram, &u->dev[d], p); break; + case 0xa0: file_deo(prg, 0, u->ram, &u->dev[d], p); break; + case 0xb0: file_deo(prg, 1, u->ram, &u->dev[d], p); break; } } @@ -550,7 +556,6 @@ porporo_key(char c) switch(c) { case 'd': movemode = !movemode; - printf("%d\n", movemode); break; } } @@ -577,6 +582,7 @@ main(int argc, char **argv) addprogram(20, 30, "bin/screen.pixel.rom"); addprogram(150, 90, "bin/oekaki.rom"); addprogram(650, 160, "bin/catclock.rom"); + addprogram(750, 300, "bin/left.rom"); connectports(prg_hello, prg_listen, 0x12, 0x18); connectports(prg_listen, porporo, 0x12, 0x18); @@ -600,9 +606,12 @@ main(int argc, char **argv) while(SDL_PollEvent(&event) != 0) { switch(event.type) { case SDL_QUIT: quit(); break; + case SDL_MOUSEWHEEL: case SDL_MOUSEBUTTONUP: case SDL_MOUSEBUTTONDOWN: - case SDL_MOUSEMOTION: domouse(&event); break; + case SDL_MOUSEMOTION: + domouse(&event); + break; case SDL_TEXTINPUT: if(focused == porporo) { porporo_key(event.text.text[0]);