diff --git a/README.md b/README.md index 4cd6a84..c6baf4f 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,12 @@ An assembler and emulator for the [Uxn stack-machine](https://wiki.xxiivv.com/si More docs coming soon. +If you wish to build the emulator without graphics mode: + +```sh +cc src/devices/datetime.c src/devices/system.c src/devices/file.c src/uxn.c -DNDEBUG -Os -g0 -s src/uxncli.c -o bin/uxncli +``` + ## Contributing Submit patches using [`git send-email`](https://git-send-email.io/) to the [~rabbits/public-inbox mailing list](https://lists.sr.ht/~rabbits/public-inbox). diff --git a/build.sh b/build.sh index 15ada32..4d3e5fb 100755 --- a/build.sh +++ b/build.sh @@ -1,13 +1,13 @@ #!/bin/sh -e -clang-format -i uxn11.c +clang-format -i src/uxn11.c echo "Cleaning.." rm -f ./bin/* echo "Building.." mkdir -p bin -gcc uxn.c devices/ppu.c uxn11.c -o bin/uxn11 -lX11 +gcc -std=c89 -DDEBUG -Wall -Wno-unknown-pragmas -Wpedantic -Wshadow -Wextra -Werror=implicit-int -Werror=incompatible-pointer-types -Werror=int-conversion -Wvla -g -Og -fsanitize=address -fsanitize=undefined src/uxn.c src/devices/system.c src/devices/screen.c src/uxn11.c -o bin/uxn11 -lX11 echo "Running.." bin/uxn11 etc/screen.rom diff --git a/devices/file.c b/devices/file.c deleted file mode 100644 index e607420..0000000 --- a/devices/file.c +++ /dev/null @@ -1,139 +0,0 @@ -#include "../uxn.h" -#include "file.h" - -/* -Copyright (c) 2021 Devine Lu Linvega -Copyright (c) 2021 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 _POSIX_C_SOURCE 200809L - -#include -#include -#include -#include -#include - -static FILE *f; -static DIR *d; -static char *current_filename = ""; -static enum { IDLE, - FILE_READ, - FILE_WRITE, - DIR_READ } state; -static struct dirent *de; - -static void -reset(void) -{ - if(f != NULL) { - fclose(f); - f = NULL; - } - if(d != NULL) { - closedir(d); - d = NULL; - } - de = NULL; - state = IDLE; -} - -static Uint16 -get_entry(char *p, Uint16 len, const char *pathname, const char *basename, int fail_nonzero) -{ - struct stat st; - if(len < strlen(basename) + 7) - 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", (Uint16)st.st_size, basename); - else - return snprintf(p, len, "???? %s\n", basename); -} - -static Uint16 -file_read_dir(char *dest, Uint16 len) -{ - static char pathname[4096]; - char *p = dest; - if(de == NULL) de = readdir(d); - for(; de != NULL; de = readdir(d)) { - Uint16 n; - if(de->d_name[0] == '.' && de->d_name[1] == '\0') - continue; - snprintf(pathname, sizeof(pathname), "%s/%s", current_filename, de->d_name); - n = get_entry(p, len, pathname, de->d_name, 1); - if(!n) break; - p += n; - len -= n; - } - return p - dest; -} - -Uint16 -file_init(void *filename) -{ - reset(); - current_filename = filename; - return 0; -} - -Uint16 -file_read(void *dest, Uint16 len) -{ - if(state != FILE_READ && state != DIR_READ) { - reset(); - if((d = opendir(current_filename)) != NULL) - state = DIR_READ; - else if((f = fopen(current_filename, "rb")) != NULL) - state = FILE_READ; - } - if(state == FILE_READ) - return fread(dest, 1, len, f); - if(state == DIR_READ) - return file_read_dir(dest, len); - return 0; -} - -Uint16 -file_write(void *src, Uint16 len, Uint8 flags) -{ - Uint16 ret = 0; - if(state != FILE_WRITE) { - reset(); - if((f = fopen(current_filename, (flags & 0x01) ? "ab" : "wb")) != NULL) - state = FILE_WRITE; - } - if(state == FILE_WRITE) { - if((ret = fwrite(src, 1, len, f)) > 0 && fflush(f) != 0) - ret = 0; - } - return ret; -} - -Uint16 -file_stat(void *dest, Uint16 len) -{ - char *basename = strrchr(current_filename, '/'); - if(basename != NULL) - ++basename; - else - basename = current_filename; - return get_entry(dest, len, current_filename, basename, 0); -} - -Uint16 -file_delete(void) -{ - return unlink(current_filename); -} diff --git a/devices/ppu_aarch64.c b/devices/ppu_aarch64.c deleted file mode 100644 index fb7470e..0000000 --- a/devices/ppu_aarch64.c +++ /dev/null @@ -1,37 +0,0 @@ -#ifdef __aarch64__ -#include -#include "ppu.h" - -void -ppu_redraw(Ppu *p, Uint32 *screen) -{ - uint8x16x4_t pal = vld4q_u8((Uint8*)p->palette); - Uint8 *fg = p->fg.pixels; - Uint8 *bg = p->bg.pixels; - int i; - - p->fg.changed = p->bg.changed = 0; - -#ifdef __has_builtin -#if __has_builtin(__builtin_assume) - __builtin_assume(p->width > 0 && p->height > 0); -#endif -#endif - - for(i = 0; i < (p->width * p->height & ~15); i += 16, fg += 16, bg += 16, screen += 16) { - uint8x16_t fg8 = vld1q_u8(fg); - uint8x16_t bg8 = vld1q_u8(bg); - uint8x16_t px8 = vbslq_u8(vceqzq_u8(fg8), bg8, fg8); - uint8x16x4_t px = { - vqtbl1q_u8(pal.val[0], px8), - vqtbl1q_u8(pal.val[1], px8), - vqtbl1q_u8(pal.val[2], px8), - vdupq_n_u8(0xff), - }; - vst4q_u8((uint8_t*)screen, px); - } - - for(; i < p->width * p->height; i++) - screen[i] = p->palette[*fg ? *fg : *bg]; -} -#endif diff --git a/etc/screen.rom b/etc/screen.rom index 2f7b2eb..d80d426 100644 Binary files a/etc/screen.rom and b/etc/screen.rom differ diff --git a/devices/apu.c b/src/devices/apu.c similarity index 100% rename from devices/apu.c rename to src/devices/apu.c diff --git a/devices/apu.h b/src/devices/apu.h similarity index 100% rename from devices/apu.h rename to src/devices/apu.h diff --git a/src/devices/datetime.c b/src/devices/datetime.c new file mode 100644 index 0000000..b933377 --- /dev/null +++ b/src/devices/datetime.c @@ -0,0 +1,40 @@ +#include + +#include "../uxn.h" +#include "datetime.h" + +/* +Copyright (c) 2021 Devine Lu Linvega +Copyright (c) 2021 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. +*/ + +Uint8 +datetime_dei(Device *d, Uint8 port) +{ + time_t seconds = time(NULL); + struct tm zt = {0}; + struct tm *t = localtime(&seconds); + if(t == NULL) + t = &zt; + switch(port) { + case 0x0: return (t->tm_year + 1900) >> 8; + case 0x1: return (t->tm_year + 1900); + case 0x2: return t->tm_mon; + case 0x3: return t->tm_mday; + case 0x4: return t->tm_hour; + case 0x5: return t->tm_min; + case 0x6: return t->tm_sec; + case 0x7: return t->tm_wday; + case 0x8: return t->tm_yday >> 8; + case 0x9: return t->tm_yday; + case 0xa: return t->tm_isdst; + default: return d->dat[port]; + } +} diff --git a/src/devices/datetime.h b/src/devices/datetime.h new file mode 100644 index 0000000..95a3739 --- /dev/null +++ b/src/devices/datetime.h @@ -0,0 +1,13 @@ +/* +Copyright (c) 2021 Devine Lu Linvega +Copyright (c) 2021 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. +*/ + +Uint8 datetime_dei(Device *d, Uint8 port); diff --git a/src/devices/file.c b/src/devices/file.c new file mode 100644 index 0000000..d31d88e --- /dev/null +++ b/src/devices/file.c @@ -0,0 +1,211 @@ +#include "../uxn.h" +#include "file.h" + +/* +Copyright (c) 2021 Devine Lu Linvega +Copyright (c) 2021 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. +*/ + +#include +#include +#include +#include +#include + +typedef struct { + FILE *f; + DIR *dir; + char current_filename[4096]; + struct dirent *de; + enum { IDLE, + FILE_READ, + FILE_WRITE, + DIR_READ } state; +} 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; +} + +static Uint16 +get_entry(char *p, Uint16 len, const char *pathname, const char *basename, int fail_nonzero) +{ + struct stat st; + if(len < strlen(basename) + 7) + return 0; + if(stat(pathname, &st)) + return fail_nonzero ? sprintf(p, "!!!! %s\n", basename) : 0; + else if(S_ISDIR(st.st_mode)) + return sprintf(p, "---- %s\n", basename); + else if(st.st_size < 0x10000) + return sprintf(p, "%04x %s\n", (unsigned int)st.st_size, basename); + else + return sprintf(p, "???? %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(strlen(c->current_filename) + 1 + strlen(c->de->d_name) < sizeof(pathname)) + sprintf(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 Uint16 +file_init(UxnFile *c, char *filename, size_t max_len) +{ + 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') + return 0; + len--; + } + c->current_filename[0] = '\0'; + return 0; +} + +static Uint16 +file_read(UxnFile *c, void *dest, Uint16 len) +{ + 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->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, '/'); + 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 unlink(c->current_filename); +} + +/* IO */ + +void +file_i_deo(int instance, Device *d, Uint8 port) +{ + UxnFile *c = &uxn_file[instance]; + Uint16 addr, len, res; + switch(port) { + case 0x5: + DEVPEEK16(addr, 0x4); + DEVPEEK16(len, 0xa); + if(len > 0x10000 - addr) + len = 0x10000 - addr; + res = file_stat(c, &d->u->ram[addr], len); + DEVPOKE16(0x2, res); + break; + case 0x6: + res = file_delete(c); + DEVPOKE16(0x2, res); + break; + case 0x9: + DEVPEEK16(addr, 0x8); + res = file_init(c, (char *)&d->u->ram[addr], 0x10000 - addr); + DEVPOKE16(0x2, res); + break; + case 0xd: + DEVPEEK16(addr, 0xc); + DEVPEEK16(len, 0xa); + if(len > 0x10000 - addr) + len = 0x10000 - addr; + res = file_read(c, &d->u->ram[addr], len); + DEVPOKE16(0x2, res); + break; + case 0xf: + DEVPEEK16(addr, 0xe); + DEVPEEK16(len, 0xa); + if(len > 0x10000 - addr) + len = 0x10000 - addr; + res = file_write(c, &d->u->ram[addr], len, d->dat[0x7]); + DEVPOKE16(0x2, res); + break; + } +} + +Uint8 +file_i_dei(int instance, Device *d, Uint8 port) +{ + UxnFile *c = &uxn_file[instance]; + Uint16 res; + switch(port) { + case 0xc: + case 0xd: + res = file_read(c, &d->dat[port], 1); + DEVPOKE16(0x2, res); + break; + } + return d->dat[port]; +} diff --git a/devices/file.h b/src/devices/file.h similarity index 66% rename from devices/file.h rename to src/devices/file.h index affd28c..09c2cdc 100644 --- a/devices/file.h +++ b/src/devices/file.h @@ -10,8 +10,7 @@ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE. */ -Uint16 file_init(void *filename); -Uint16 file_read(void *dest, Uint16 len); -Uint16 file_write(void *src, Uint16 len, Uint8 flags); -Uint16 file_stat(void *dest, Uint16 len); -Uint16 file_delete(void); +#define POLYFILEY 2 + +void file_i_deo(int instance, Device *d, Uint8 port); +Uint8 file_i_dei(int instance, Device *d, Uint8 port); diff --git a/devices/ppu.c b/src/devices/ppu.c similarity index 100% rename from devices/ppu.c rename to src/devices/ppu.c diff --git a/devices/ppu.h b/src/devices/ppu.h similarity index 100% rename from devices/ppu.h rename to src/devices/ppu.h diff --git a/src/devices/screen.c b/src/devices/screen.c new file mode 100644 index 0000000..a261051 --- /dev/null +++ b/src/devices/screen.c @@ -0,0 +1,181 @@ +#include + +#include "../uxn.h" +#include "screen.h" + +/* +Copyright (c) 2021 Devine Lu Linvega +Copyright (c) 2021 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. +*/ + +UxnScreen uxn_screen; + +static Uint8 blending[5][16] = { + {0, 0, 0, 0, 1, 0, 1, 1, 2, 2, 0, 2, 3, 3, 3, 0}, + {0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3}, + {1, 2, 3, 1, 1, 2, 3, 1, 1, 2, 3, 1, 1, 2, 3, 1}, + {2, 3, 1, 2, 2, 3, 1, 2, 2, 3, 1, 2, 2, 3, 1, 2}, + {1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0}}; + +static void +screen_write(UxnScreen *p, Layer *layer, Uint16 x, Uint16 y, Uint8 color) +{ + if(x < p->width && y < p->height) { + Uint32 i = x + y * p->width; + if(color != layer->pixels[i]) { + layer->pixels[i] = color; + layer->changed = 1; + } + } +} + +static void +screen_blit(UxnScreen *p, Layer *layer, Uint16 x, Uint16 y, Uint8 *sprite, Uint8 color, Uint8 flipx, Uint8 flipy, Uint8 twobpp) +{ + int v, h, opaque = blending[4][color]; + for(v = 0; v < 8; v++) { + Uint16 c = sprite[v] | (twobpp ? sprite[v + 8] : 0) << 8; + for(h = 7; h >= 0; --h, c >>= 1) { + Uint8 ch = (c & 1) | ((c >> 7) & 2); + if(opaque || ch) + screen_write(p, + layer, + x + (flipx ? 7 - h : h), + y + (flipy ? 7 - v : v), + blending[ch][color]); + } + } +} + +void +screen_palette(UxnScreen *p, Uint8 *addr) +{ + int i, shift; + for(i = 0, shift = 4; i < 4; ++i, shift ^= 4) { + Uint8 + r = (addr[0 + i / 2] >> shift) & 0x0f, + g = (addr[2 + i / 2] >> shift) & 0x0f, + b = (addr[4 + i / 2] >> shift) & 0x0f; + p->palette[i] = 0x0f000000 | r << 16 | g << 8 | b; + p->palette[i] |= p->palette[i] << 4; + } + p->fg.changed = p->bg.changed = 1; +} + +void +screen_resize(UxnScreen *p, Uint16 width, Uint16 height) +{ + Uint8 + *bg = realloc(p->bg.pixels, width * height), + *fg = realloc(p->fg.pixels, width * height); + Uint32 + *pixels = realloc(p->pixels, width * height * sizeof(Uint32)); + if(bg) p->bg.pixels = bg; + if(fg) p->fg.pixels = fg; + if(pixels) p->pixels = pixels; + if(bg && fg && pixels) { + p->width = width; + p->height = height; + screen_clear(p, &p->bg); + screen_clear(p, &p->fg); + } +} + +void +screen_clear(UxnScreen *p, Layer *layer) +{ + Uint32 i, size = p->width * p->height; + for(i = 0; i < size; i++) + layer->pixels[i] = 0x00; + layer->changed = 1; +} + +void +screen_redraw(UxnScreen *p, Uint32 *pixels) +{ + Uint32 i, size = p->width * p->height, palette[16]; + for(i = 0; i < 16; i++) + palette[i] = p->palette[(i >> 2) ? (i >> 2) : (i & 3)]; + for(i = 0; i < size; i++) + pixels[i] = palette[p->fg.pixels[i] << 2 | p->bg.pixels[i]]; + p->fg.changed = p->bg.changed = 0; +} + +int +clamp(int val, int min, int max) +{ + return (val >= min) ? (val <= max) ? val : max : min; +} + +/* IO */ + +Uint8 +screen_dei(Device *d, Uint8 port) +{ + switch(port) { + case 0x2: return uxn_screen.width >> 8; + case 0x3: return uxn_screen.width; + case 0x4: return uxn_screen.height >> 8; + case 0x5: return uxn_screen.height; + default: return d->dat[port]; + } +} + +void +screen_deo(Device *d, Uint8 port) +{ + switch(port) { + case 0x3: + if(!FIXED_SIZE) { + Uint16 w; + DEVPEEK16(w, 0x2); + screen_resize(&uxn_screen, clamp(w, 1, 1024), uxn_screen.height); + } + break; + case 0x5: + if(!FIXED_SIZE) { + Uint16 h; + DEVPEEK16(h, 0x4); + screen_resize(&uxn_screen, uxn_screen.width, clamp(h, 1, 1024)); + } + break; + case 0xe: { + Uint16 x, y; + Uint8 layer = d->dat[0xe] & 0x40; + DEVPEEK16(x, 0x8); + DEVPEEK16(y, 0xa); + screen_write(&uxn_screen, layer ? &uxn_screen.fg : &uxn_screen.bg, x, y, d->dat[0xe] & 0x3); + if(d->dat[0x6] & 0x01) DEVPOKE16(0x8, x + 1); /* auto x+1 */ + if(d->dat[0x6] & 0x02) DEVPOKE16(0xa, y + 1); /* auto y+1 */ + break; + } + case 0xf: { + Uint16 x, y, dx, dy, addr; + Uint8 i, n, twobpp = !!(d->dat[0xf] & 0x80); + Layer *layer = (d->dat[0xf] & 0x40) ? &uxn_screen.fg : &uxn_screen.bg; + DEVPEEK16(x, 0x8); + DEVPEEK16(y, 0xa); + DEVPEEK16(addr, 0xc); + n = d->dat[0x6] >> 4; + dx = (d->dat[0x6] & 0x01) << 3; + dy = (d->dat[0x6] & 0x02) << 2; + if(addr > 0x10000 - ((n + 1) << (3 + twobpp))) + return; + for(i = 0; i <= n; i++) { + screen_blit(&uxn_screen, layer, x + dy * i, y + dx * i, &d->u->ram[addr], d->dat[0xf] & 0xf, d->dat[0xf] & 0x10, d->dat[0xf] & 0x20, twobpp); + addr += (d->dat[0x6] & 0x04) << (1 + twobpp); + } + DEVPOKE16(0xc, addr); /* auto addr+length */ + DEVPOKE16(0x8, x + dx); /* auto x+8 */ + DEVPOKE16(0xa, y + dy); /* auto y+8 */ + break; + } + } +} diff --git a/src/devices/screen.h b/src/devices/screen.h new file mode 100644 index 0000000..8a19abb --- /dev/null +++ b/src/devices/screen.h @@ -0,0 +1,34 @@ +/* +Copyright (c) 2021 Devine Lu Linvega +Copyright (c) 2021 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 FIXED_SIZE 0 + +typedef struct Layer { + Uint8 *pixels, changed; +} Layer; + +typedef struct UxnScreen { + Uint32 palette[4], *pixels; + Uint16 width, height; + Layer fg, bg; +} UxnScreen; + +extern UxnScreen uxn_screen; + +void screen_palette(UxnScreen *p, Uint8 *addr); +void screen_resize(UxnScreen *p, Uint16 width, Uint16 height); +void screen_clear(UxnScreen *p, Layer *layer); +void screen_redraw(UxnScreen *p, Uint32 *pixels); + +Uint8 screen_dei(Device *d, Uint8 port); +void screen_deo(Device *d, Uint8 port); +int clamp(int val, int min, int max); diff --git a/src/devices/system.c b/src/devices/system.c new file mode 100644 index 0000000..413773d --- /dev/null +++ b/src/devices/system.c @@ -0,0 +1,73 @@ +#include "../uxn.h" +#include "system.h" + +#include + +/* +Copyright (c) 2022 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. +*/ + +static const char *errors[] = { + "Working-stack underflow", + "Return-stack underflow", + "Working-stack overflow", + "Return-stack overflow", + "Working-stack division by zero", + "Return-stack division by zero"}; + +static void +system_print(Stack *s, char *name) +{ + Uint8 i; + fprintf(stderr, "<%s>", name); + for(i = 0; i < s->ptr; i++) + fprintf(stderr, " %02x", s->dat[i]); + if(!i) + fprintf(stderr, " empty"); + fprintf(stderr, "\n"); +} + +void +system_inspect(Uxn *u) +{ + system_print(&u->wst, "wst"); + system_print(&u->rst, "rst"); +} + +int +uxn_halt(Uxn *u, Uint8 error, Uint16 addr) +{ + system_inspect(u); + fprintf(stderr, "Halted: %s#%04x, at 0x%04x\n", errors[error], u->ram[addr], addr); + return 0; +} + +/* IO */ + +Uint8 +system_dei(Device *d, Uint8 port) +{ + switch(port) { + case 0x2: return d->u->wst.ptr; + case 0x3: return d->u->rst.ptr; + default: return d->dat[port]; + } +} + +void +system_deo(Device *d, Uint8 port) +{ + switch(port) { + case 0x2: d->u->wst.ptr = d->dat[port]; break; + case 0x3: d->u->rst.ptr = d->dat[port]; break; + case 0xe: system_inspect(d->u); break; + default: system_deo_special(d, port); + } +} diff --git a/src/devices/system.h b/src/devices/system.h new file mode 100644 index 0000000..6bdc847 --- /dev/null +++ b/src/devices/system.h @@ -0,0 +1,21 @@ +/* +Copyright (c) 2022 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 SystemDevice { + Device device; + struct UxnScreen *screen; +} SystemDevice; + +void system_inspect(Uxn *u); + +Uint8 system_dei(Device *d, Uint8 port); +void system_deo(Device *d, Uint8 port); +void system_deo_special(Device *d, Uint8 port); diff --git a/src/uxn.c b/src/uxn.c new file mode 100644 index 0000000..8ec2d9a --- /dev/null +++ b/src/uxn.c @@ -0,0 +1,124 @@ +#include "uxn.h" + +/* +Copyright (u) 2022 Devine Lu Linvega, Andrew Alderwick, Andrew Richards + +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. +*/ + +/* clang-format off */ + +#define PUSH8(s, x) { if(s->ptr == 0xff) { errcode = 2; goto err; } s->dat[s->ptr++] = (x); } +#define PUSH16(s, x) { if((j = s->ptr) >= 0xfe) { errcode = 2; goto err; } k = (x); s->dat[j] = k >> 8; s->dat[j + 1] = k; s->ptr = j + 2; } +#define PUSH(s, x) { if(bs) { PUSH16(s, (x)) } else { PUSH8(s, (x)) } } +#define POP8(o) { if(!(j = *sp)) { errcode = 0; goto err; } o = (Uint16)src->dat[--j]; *sp = j; } +#define POP16(o) { if((j = *sp) <= 1) { errcode = 0; goto err; } o = src->dat[j - 1]; o += src->dat[j - 2] << 8; *sp = j - 2; } +#define POP(o) { if(bs) { POP16(o) } else { POP8(o) } } +#define POKE(x, y) { if(bs) { u->ram[(x)] = (y) >> 8; u->ram[(x) + 1] = (y); } else { u->ram[(x)] = y; } } +#define PEEK16(o, x) { o = (u->ram[(x)] << 8) + u->ram[(x) + 1]; } +#define PEEK(o, x) { if(bs) { PEEK16(o, x) } else { o = u->ram[(x)]; } } +#define DEVR(o, d, x) { dev = (d); o = dev->dei(dev, (x) & 0x0f); if(bs) { o = (o << 8) + dev->dei(dev, ((x) + 1) & 0x0f); } } +#define DEVW8(x, y) { dev->dat[(x) & 0xf] = y; dev->deo(dev, (x) & 0x0f); } +#define DEVW(d, x, y) { dev = (d); if(bs) { DEVW8((x), (y) >> 8); DEVW8((x) + 1, (y)); } else { DEVW8((x), (y)) } } +#define WARP(x) { if(bs) pc = (x); else pc += (Sint8)(x); } + +int +uxn_eval(Uxn *u, Uint16 pc) +{ + unsigned int a, b, c, j, k, bs, instr, errcode; + Uint8 kptr, *sp; + Stack *src, *dst; + Device *dev; + if(!pc || u->dev[0].dat[0xf]) return 0; + while((instr = u->ram[pc++])) { + /* Return Mode */ + if(instr & 0x40) { + src = &u->rst; dst = &u->wst; + } else { + src = &u->wst; dst = &u->rst; + } + /* Keep Mode */ + if(instr & 0x80) { + kptr = src->ptr; + sp = &kptr; + } else { + sp = &src->ptr; + } + /* Short Mode */ + bs = instr & 0x20 ? 1 : 0; + switch(instr & 0x1f) { + /* Stack */ + case 0x00: /* LIT */ if(bs) { PEEK16(a, pc) PUSH16(src, a) pc += 2; } + else { a = u->ram[pc]; PUSH8(src, a) pc++; } break; + case 0x01: /* INC */ POP(a) PUSH(src, a + 1) break; + case 0x02: /* POP */ POP(a) break; + case 0x03: /* DUP */ POP(a) PUSH(src, a) PUSH(src, a) break; + case 0x04: /* NIP */ POP(a) POP(b) PUSH(src, a) break; + case 0x05: /* SWP */ POP(a) POP(b) PUSH(src, a) PUSH(src, b) break; + case 0x06: /* OVR */ POP(a) POP(b) PUSH(src, b) PUSH(src, a) PUSH(src, b) break; + case 0x07: /* ROT */ POP(a) POP(b) POP(c) PUSH(src, b) PUSH(src, a) PUSH(src, c) break; + /* Logic */ + case 0x08: /* EQU */ POP(a) POP(b) PUSH8(src, b == a) break; + case 0x09: /* NEQ */ POP(a) POP(b) PUSH8(src, b != a) break; + case 0x0a: /* GTH */ POP(a) POP(b) PUSH8(src, b > a) break; + case 0x0b: /* LTH */ POP(a) POP(b) PUSH8(src, b < a) break; + case 0x0c: /* JMP */ POP(a) WARP(a) break; + case 0x0d: /* JCN */ POP(a) POP8(b) if(b) WARP(a) break; + case 0x0e: /* JSR */ POP(a) PUSH16(dst, pc) WARP(a) break; + case 0x0f: /* STH */ POP(a) PUSH(dst, a) break; + /* Memory */ + case 0x10: /* LDZ */ POP8(a) PEEK(b, a) PUSH(src, b) break; + case 0x11: /* STZ */ POP8(a) POP(b) POKE(a, b) break; + case 0x12: /* LDR */ POP8(a) PEEK(b, pc + (Sint8)a) PUSH(src, b) break; + case 0x13: /* STR */ POP8(a) POP(b) c = pc + (Sint8)a; POKE(c, b) break; + case 0x14: /* LDA */ POP16(a) PEEK(b, a) PUSH(src, b) break; + case 0x15: /* STA */ POP16(a) POP(b) POKE(a, b) break; + case 0x16: /* DEI */ POP8(a) DEVR(b, &u->dev[a >> 4], a) PUSH(src, b) break; + case 0x17: /* DEO */ POP8(a) POP(b) DEVW(&u->dev[a >> 4], a, b) break; + /* Arithmetic */ + case 0x18: /* ADD */ POP(a) POP(b) PUSH(src, b + a) break; + case 0x19: /* SUB */ POP(a) POP(b) PUSH(src, b - a) break; + case 0x1a: /* MUL */ POP(a) POP(b) PUSH(src, (Uint32)b * a) break; + case 0x1b: /* DIV */ POP(a) POP(b) if(a == 0) { errcode = 4; goto err; } PUSH(src, b / a) break; + case 0x1c: /* AND */ POP(a) POP(b) PUSH(src, b & a) break; + case 0x1d: /* ORA */ POP(a) POP(b) PUSH(src, b | a) break; + case 0x1e: /* EOR */ POP(a) POP(b) PUSH(src, b ^ a) break; + case 0x1f: /* SFT */ POP8(a) POP(b) c = b >> (a & 0x0f) << ((a & 0xf0) >> 4); PUSH(src, c) break; + } + } + return 1; + +err: + /* set 1 in errcode if it involved the return stack instead of the working stack */ + /* (stack overflow & ( opcode was STH / JSR )) ^ Return Mode */ + errcode |= ((errcode >> 1 & ((instr & 0x1e) == 0x0e)) ^ instr >> 6) & 1; + return uxn_halt(u, errcode, pc - 1); +} + +/* clang-format on */ + +int +uxn_boot(Uxn *u, Uint8 *ram) +{ + Uint32 i; + char *cptr = (char *)u; + for(i = 0; i < sizeof(*u); i++) + cptr[i] = 0x00; + u->ram = ram; + return 1; +} + +Device * +uxn_port(Uxn *u, Uint8 id, Uint8 (*deifn)(Device *d, Uint8 port), void (*deofn)(Device *d, Uint8 port)) +{ + Device *d = &u->dev[id]; + d->u = u; + d->dei = deifn; + d->deo = deofn; + return d; +} diff --git a/uxn.h b/src/uxn.h similarity index 63% rename from uxn.h rename to src/uxn.h index 5ad45db..4580be5 100644 --- a/uxn.h +++ b/src/uxn.h @@ -1,3 +1,6 @@ +#ifndef UXN_UXN_H +#define UXN_UXN_H + /* Copyright (c) 2021 Devine Lu Linvega @@ -17,34 +20,33 @@ typedef unsigned int Uint32; #define PAGE_PROGRAM 0x0100 -typedef struct { - Uint8 ptr, kptr, error; - Uint8 dat[256]; -} Stack; +/* clang-format off */ + +#define DEVPEEK16(o, x) { (o) = (d->dat[(x)] << 8) + d->dat[(x) + 1]; } +#define DEVPOKE16(x, y) { d->dat[(x)] = (y) >> 8; d->dat[(x) + 1] = (y); } +#define GETVECTOR(d) ((d)->dat[0] << 8 | (d)->dat[1]) + +/* clang-format on */ typedef struct { - Uint16 ptr; - Uint8 dat[65536]; -} Memory; + Uint8 ptr, dat[255]; +} Stack; typedef struct Device { struct Uxn *u; - Uint8 addr, dat[16], *mem; - Uint16 vector; + Uint8 dat[16]; Uint8 (*dei)(struct Device *d, Uint8); void (*deo)(struct Device *d, Uint8); } Device; typedef struct Uxn { - Stack wst, rst, *src, *dst; - Memory ram; + Uint8 *ram; + Stack wst, rst; Device dev[16]; } Uxn; -void poke16(Uint8 *m, Uint16 a, Uint16 b); -Uint16 peek16(Uint8 *m, Uint16 a); - -int uxn_boot(Uxn *c); -int uxn_eval(Uxn *u, Uint16 vec); -int uxn_halt(Uxn *u, Uint8 error, char *name, int id); +int uxn_boot(Uxn *u, Uint8 *ram); +int uxn_eval(Uxn *u, Uint16 pc); +int uxn_halt(Uxn *u, Uint8 error, Uint16 addr); Device *uxn_port(Uxn *u, Uint8 id, Uint8 (*deifn)(Device *, Uint8), void (*deofn)(Device *, Uint8)); +#endif /* UXN_UXN_H */ diff --git a/src/uxn11.c b/src/uxn11.c new file mode 100644 index 0000000..6c36ed5 --- /dev/null +++ b/src/uxn11.c @@ -0,0 +1,151 @@ +#include +#include +#include + +#include +#include +#include + +#define WIDTH 64 * 8 +#define HEIGHT 40 * 8 + +#include "uxn.h" +#include "devices/system.h" +#include "devices/screen.h" + +static Device *devsystem, *devconsole, *devscreen; + +static int +error(char *msg, const char *err) +{ + fprintf(stderr, "Error %s: %s\n", msg, err); + return 0; +} + +static int +set_size(Uint16 width, Uint16 height, int is_resize) +{ + screen_resize(&uxn_screen, width, height); + return 1; +} + +void +system_deo_special(Device *d, Uint8 port) +{ + if(port > 0x7 && port < 0xe) + screen_palette(&uxn_screen, &d->dat[0x8]); +} + +static void +console_deo(Device *d, Uint8 port) +{ + FILE *fd = port == 0x8 ? stdout : port == 0x9 ? stderr + : 0; + if(fd) { + fputc(d->dat[port], fd); + fflush(fd); + } +} + +static Uint8 +nil_dei(Device *d, Uint8 port) +{ + return d->dat[port]; +} + +static void +nil_deo(Device *d, Uint8 port) +{ + (void)d; + (void)port; +} + +static int +load(Uxn *u, char *filepath) +{ + FILE *f; + int r; + if(!(f = fopen(filepath, "rb"))) return 0; + r = fread(u->ram + PAGE_PROGRAM, 1, 0x10000 - PAGE_PROGRAM, f); + fclose(f); + if(r < 1) return 0; + fprintf(stderr, "Loaded %s\n", filepath); + return 1; +} + +void +processEvent(Display *display, Window window, XImage *ximage, int width, int height) +{ + XEvent ev; + XNextEvent(display, &ev); + switch(ev.type) { + case Expose: + XPutImage(display, window, DefaultGC(display, 0), ximage, 0, 0, 0, 0, width, height); + break; + case ButtonPress: + exit(0); + } +} + +static int +start(Uxn *u) +{ + if(!uxn_boot(u, (Uint8 *)calloc(0x10000, sizeof(Uint8)))) + return error("Boot", "Failed"); + /* system */ devsystem = uxn_port(u, 0x0, system_dei, system_deo); + /* console */ devconsole = uxn_port(u, 0x1, nil_dei, console_deo); + /* screen */ devscreen = uxn_port(u, 0x2, screen_dei, screen_deo); + /* empty */ uxn_port(u, 0x3, nil_dei, nil_deo); + /* empty */ uxn_port(u, 0x4, nil_dei, nil_deo); + /* empty */ uxn_port(u, 0x5, nil_dei, nil_deo); + /* empty */ uxn_port(u, 0x6, nil_dei, nil_deo); + /* empty */ uxn_port(u, 0x7, nil_dei, nil_deo); + /* empty */ uxn_port(u, 0x8, nil_dei, nil_deo); + /* empty */ uxn_port(u, 0x9, nil_dei, nil_deo); + /* file */ uxn_port(u, 0xa, nil_dei, nil_deo); + /* datetime */ uxn_port(u, 0xb, nil_dei, nil_deo); + /* empty */ uxn_port(u, 0xc, nil_dei, nil_deo); + /* empty */ uxn_port(u, 0xd, nil_dei, nil_deo); + /* empty */ uxn_port(u, 0xe, nil_dei, nil_deo); + /* empty */ uxn_port(u, 0xf, nil_dei, nil_deo); + return 1; +} + +int +main(int argc, char **argv) +{ + Uxn u; + + if(argc < 2) + return error("Usage", "uxncli game.rom args"); + if(!start(&u)) + return error("Start", "Failed"); + if(!load(&u, argv[1])) + return error("Load", "Failed"); + + if(!set_size(WIDTH, HEIGHT, 0)) + return error("Window", "Failed to set window size."); + + if(!uxn_eval(&u, PAGE_PROGRAM)) + return error("Boot", "Failed to start rom."); + + screen_redraw(&uxn_screen, uxn_screen.pixels); + + XImage *ximage; + Display *display = XOpenDisplay(NULL); + Visual *visual = DefaultVisual(display, 0); + Window window = XCreateSimpleWindow(display, RootWindow(display, 0), 0, 0, uxn_screen.width, uxn_screen.height, 1, 0, 0); + if(visual->class != TrueColor) { + fprintf(stderr, "Cannot handle non true color visual ...\n"); + exit(1); + } + + ximage = XCreateImage(display, visual, DefaultDepth(display, DefaultScreen(display)), ZPixmap, 0, (char *)uxn_screen.pixels, uxn_screen.width, uxn_screen.height, 32, 0); + + XSelectInput(display, window, ButtonPressMask | ExposureMask); + XMapWindow(display, window); + while(1) { + processEvent(display, window, ximage, uxn_screen.width, uxn_screen.height); + } + return 0; +} \ No newline at end of file diff --git a/src/uxncli.c b/src/uxncli.c new file mode 100644 index 0000000..c6da9b2 --- /dev/null +++ b/src/uxncli.c @@ -0,0 +1,148 @@ +#include +#include + +#include "uxn.h" +#include "devices/system.h" +#include "devices/file.h" +#include "devices/datetime.h" + +/* +Copyright (c) 2021 Devine Lu Linvega + +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. +*/ + +static Device *devfile0; + +static int +error(char *msg, const char *err) +{ + fprintf(stderr, "Error %s: %s\n", msg, err); + return 0; +} + +void +system_deo_special(Device *d, Uint8 port) +{ + (void)d; + (void)port; +} + +static void +console_deo(Device *d, Uint8 port) +{ + FILE *fd = port == 0x8 ? stdout : port == 0x9 ? stderr + : 0; + if(fd) { + fputc(d->dat[port], fd); + fflush(fd); + } +} + +static void +file_deo(Device *d, Uint8 port) +{ + file_i_deo(d - devfile0, d, port); +} + +static Uint8 +file_dei(Device *d, Uint8 port) +{ + return file_i_dei(d - devfile0, d, port); +} + +static Uint8 +nil_dei(Device *d, Uint8 port) +{ + return d->dat[port]; +} + +static void +nil_deo(Device *d, Uint8 port) +{ + (void)d; + (void)port; +} + +static int +console_input(Uxn *u, char c) +{ + Device *d = &u->dev[1]; + d->dat[0x2] = c; + return uxn_eval(u, GETVECTOR(d)); +} + +static void +run(Uxn *u) +{ + Device *d = &u->dev[0]; + while(!d->dat[0xf]) { + int c = fgetc(stdin); + if(c != EOF) + console_input(u, (Uint8)c); + } +} + +static int +load(Uxn *u, char *filepath) +{ + FILE *f; + int r; + if(!(f = fopen(filepath, "rb"))) return 0; + r = fread(u->ram + PAGE_PROGRAM, 1, 0x10000 - PAGE_PROGRAM, f); + fclose(f); + if(r < 1) return 0; + fprintf(stderr, "Loaded %s\n", filepath); + return 1; +} + +static int +start(Uxn *u) +{ + if(!uxn_boot(u, (Uint8 *)calloc(0x10000, sizeof(Uint8)))) + return error("Boot", "Failed"); + /* system */ uxn_port(u, 0x0, system_dei, system_deo); + /* console */ uxn_port(u, 0x1, nil_dei, console_deo); + /* empty */ uxn_port(u, 0x2, nil_dei, nil_deo); + /* empty */ uxn_port(u, 0x3, nil_dei, nil_deo); + /* empty */ uxn_port(u, 0x4, nil_dei, nil_deo); + /* empty */ uxn_port(u, 0x5, nil_dei, nil_deo); + /* empty */ uxn_port(u, 0x6, nil_dei, nil_deo); + /* empty */ uxn_port(u, 0x7, nil_dei, nil_deo); + /* empty */ uxn_port(u, 0x8, nil_dei, nil_deo); + /* empty */ uxn_port(u, 0x9, nil_dei, nil_deo); + /* file0 */ devfile0 = uxn_port(u, 0xa, file_dei, file_deo); + /* file1 */ uxn_port(u, 0xb, file_dei, file_deo); + /* datetime */ uxn_port(u, 0xc, datetime_dei, nil_deo); + /* empty */ uxn_port(u, 0xd, nil_dei, nil_deo); + /* empty */ uxn_port(u, 0xe, nil_dei, nil_deo); + /* empty */ uxn_port(u, 0xf, nil_dei, nil_deo); + return 1; +} + +int +main(int argc, char **argv) +{ + Uxn u; + int i; + if(argc < 2) + return error("Usage", "uxncli game.rom args"); + if(!start(&u)) + return error("Start", "Failed"); + if(!load(&u, argv[1])) + return error("Load", "Failed"); + if(!uxn_eval(&u, PAGE_PROGRAM)) + return error("Init", "Failed"); + for(i = 2; i < argc; i++) { + char *p = argv[i]; + while(*p) console_input(&u, *p++); + console_input(&u, '\n'); + } + run(&u); + return 0; +} diff --git a/uxn.c b/uxn.c deleted file mode 100644 index 5d251e0..0000000 --- a/uxn.c +++ /dev/null @@ -1,158 +0,0 @@ -#include "uxn.h" - -/* -Copyright (u) 2021 Devine Lu Linvega -Copyright (u) 2021 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 MODE_SHORT 0x20 -#define MODE_RETURN 0x40 -#define MODE_KEEP 0x80 - -#pragma mark - Operations - -/* clang-format off */ - -/* Utilities */ -static void (*push)(Stack *s, Uint16 a); -static Uint16 (*pop8)(Stack *s); -static Uint16 (*pop)(Stack *s); -static void (*poke)(Uint8 *m, Uint16 a, Uint16 b); -static Uint16 (*peek)(Uint8 *m, Uint16 a); -static void (*devw)(Device *d, Uint8 a, Uint16 b); -static Uint16 (*devr)(Device *d, Uint8 a); -static void (*warp)(Uxn *u, Uint16 a); -static void (*pull)(Uxn *u); -/* byte mode */ -static void push8(Stack *s, Uint16 a) { if(s->ptr == 0xff) { s->error = 2; return; } s->dat[s->ptr++] = a; } -static Uint16 pop8k(Stack *s) { if(s->kptr == 0) { s->error = 1; return 0; } return s->dat[--s->kptr]; } -static Uint16 pop8d(Stack *s) { if(s->ptr == 0) { s->error = 1; return 0; } return s->dat[--s->ptr]; } -static void poke8(Uint8 *m, Uint16 a, Uint16 b) { m[a] = b; } -static Uint16 peek8(Uint8 *m, Uint16 a) { return m[a]; } -static void devw8(Device *d, Uint8 a, Uint16 b) { d->dat[a & 0xf] = b; d->deo(d, a & 0x0f); } -static Uint16 devr8(Device *d, Uint8 a) { return d->dei(d, a & 0x0f); } -static void warp8(Uxn *u, Uint16 a){ u->ram.ptr += (Sint8)a; } -static void pull8(Uxn *u){ push8(u->src, peek8(u->ram.dat, u->ram.ptr++)); } -/* short mode */ -static void push16(Stack *s, Uint16 a) { push8(s, a >> 8); push8(s, a); } -static Uint16 pop16(Stack *s) { Uint8 a = pop8(s), b = pop8(s); return a + (b << 8); } - void poke16(Uint8 *m, Uint16 a, Uint16 b) { poke8(m, a, b >> 8); poke8(m, a + 1, b); } - Uint16 peek16(Uint8 *m, Uint16 a) { return (peek8(m, a) << 8) + peek8(m, a + 1); } -static void devw16(Device *d, Uint8 a, Uint16 b) { devw8(d, a, b >> 8); devw8(d, a + 1, b); } -static Uint16 devr16(Device *d, Uint8 a) { return (devr8(d, a) << 8) + devr8(d, a + 1); } -static void warp16(Uxn *u, Uint16 a){ u->ram.ptr = a; } -static void pull16(Uxn *u){ push16(u->src, peek16(u->ram.dat, u->ram.ptr++)); u->ram.ptr++; } - -#pragma mark - Core - -int -uxn_eval(Uxn *u, Uint16 vec) -{ - Uint8 instr; - Uint16 a,b,c; - if(!vec || u->dev[0].dat[0xf]) - return 0; - u->ram.ptr = vec; - if(u->wst.ptr > 0xf8) u->wst.ptr = 0xf8; - while((instr = u->ram.dat[u->ram.ptr++])) { - /* Return Mode */ - if(instr & MODE_RETURN) { - u->src = &u->rst; - u->dst = &u->wst; - } else { - u->src = &u->wst; - u->dst = &u->rst; - } - /* Keep Mode */ - if(instr & MODE_KEEP) { - pop8 = pop8k; - u->src->kptr = u->src->ptr; - } else { - pop8 = pop8d; - } - /* Short Mode */ - if(instr & MODE_SHORT) { - push = push16; pop = pop16; - poke = poke16; peek = peek16; - devw = devw16; devr = devr16; - warp = warp16; pull = pull16; - } else { - push = push8; pop = pop8; - poke = poke8; peek = peek8; - devw = devw8; devr = devr8; - warp = warp8; pull = pull8; - } - switch(instr & 0x1f){ - /* Stack */ - case 0x00: /* LIT */ pull(u); break; - case 0x01: /* INC */ a = pop(u->src); push(u->src, a + 1); break; - case 0x02: /* POP */ pop(u->src); break; - case 0x03: /* DUP */ a = pop(u->src); push(u->src, a); push(u->src, a); break; - case 0x04: /* NIP */ a = pop(u->src); pop(u->src); push(u->src, a); break; - case 0x05: /* SWP */ a = pop(u->src), b = pop(u->src); push(u->src, a); push(u->src, b); break; - case 0x06: /* OVR */ a = pop(u->src), b = pop(u->src); push(u->src, b); push(u->src, a); push(u->src, b); break; - case 0x07: /* ROT */ a = pop(u->src), b = pop(u->src), c = pop(u->src); push(u->src, b); push(u->src, a); push(u->src, c); break; - /* Logic */ - case 0x08: /* EQU */ a = pop(u->src), b = pop(u->src); push8(u->src, b == a); break; - case 0x09: /* NEQ */ a = pop(u->src), b = pop(u->src); push8(u->src, b != a); break; - case 0x0a: /* GTH */ a = pop(u->src), b = pop(u->src); push8(u->src, b > a); break; - case 0x0b: /* LTH */ a = pop(u->src), b = pop(u->src); push8(u->src, b < a); break; - case 0x0c: /* JMP */ a = pop(u->src); warp(u, a); break; - case 0x0d: /* JCN */ a = pop(u->src); if(pop8(u->src)) warp(u, a); break; - case 0x0e: /* JSR */ a = pop(u->src); push16(u->dst, u->ram.ptr); warp(u, a); break; - case 0x0f: /* STH */ a = pop(u->src); push(u->dst, a); break; - /* Memory */ - case 0x10: /* LDZ */ a = pop8(u->src); push(u->src, peek(u->ram.dat, a)); break; - case 0x11: /* STZ */ a = pop8(u->src); b = pop(u->src); poke(u->ram.dat, a, b); break; - case 0x12: /* LDR */ a = pop8(u->src); push(u->src, peek(u->ram.dat, u->ram.ptr + (Sint8)a)); break; - case 0x13: /* STR */ a = pop8(u->src); b = pop(u->src); poke(u->ram.dat, u->ram.ptr + (Sint8)a, b); break; - case 0x14: /* LDA */ a = pop16(u->src); push(u->src, peek(u->ram.dat, a)); break; - case 0x15: /* STA */ a = pop16(u->src); b = pop(u->src); poke(u->ram.dat, a, b); break; - case 0x16: /* DEI */ a = pop8(u->src); push(u->src, devr(&u->dev[a >> 4], a)); break; - case 0x17: /* DEO */ a = pop8(u->src); b = pop(u->src); devw(&u->dev[a >> 4], a, b); break; - /* Arithmetic */ - case 0x18: /* ADD */ a = pop(u->src), b = pop(u->src); push(u->src, b + a); break; - case 0x19: /* SUB */ a = pop(u->src), b = pop(u->src); push(u->src, b - a); break; - case 0x1a: /* MUL */ a = pop(u->src), b = pop(u->src); push(u->src, (Uint32)b * a); break; - case 0x1b: /* DIV */ a = pop(u->src), b = pop(u->src); if(a == 0) { u->src->error = 3; a = 1; } push(u->src, b / a); break; - case 0x1c: /* AND */ a = pop(u->src), b = pop(u->src); push(u->src, b & a); break; - case 0x1d: /* ORA */ a = pop(u->src), b = pop(u->src); push(u->src, b | a); break; - case 0x1e: /* EOR */ a = pop(u->src), b = pop(u->src); push(u->src, b ^ a); break; - case 0x1f: /* SFT */ a = pop8(u->src), b = pop(u->src); push(u->src, b >> (a & 0x0f) << ((a & 0xf0) >> 4)); break; - } - if(u->wst.error) return uxn_halt(u, u->wst.error, "Working-stack", instr); - if(u->rst.error) return uxn_halt(u, u->rst.error, "Return-stack", instr); - } - return 1; -} - -/* clang-format on */ - -int -uxn_boot(Uxn *u) -{ - Uint32 i; - char *cptr = (char *)u; - for(i = 0; i < sizeof(*u); ++i) - cptr[i] = 0x00; - return 1; -} - -Device * -uxn_port(Uxn *u, Uint8 id, Uint8 (*deifn)(Device *d, Uint8 port), void (*deofn)(Device *d, Uint8 port)) -{ - Device *d = &u->dev[id]; - d->addr = id * 0x10; - d->u = u; - d->mem = u->ram.dat; - d->dei = deifn; - d->deo = deofn; - return d; -} diff --git a/uxn11.c b/uxn11.c deleted file mode 100644 index 0b53863..0000000 --- a/uxn11.c +++ /dev/null @@ -1,266 +0,0 @@ -#include -#include -#include - -#include -#include -#include - -#define WIDTH 64 * 8 -#define HEIGHT 40 * 8 -#define FIXED_SIZE 0 - -#include "uxn.h" - -#include "devices/ppu.h" -static Ppu ppu; - -static Device *devsystem, *devconsole, *devscreen; -static Uint32 *ppu_screen; - -static int -error(char *msg, const char *err) -{ - fprintf(stderr, "Error %s: %s\n", msg, err); - return 0; -} - -static int -set_size(Uint16 width, Uint16 height, int is_resize) -{ - ppu_resize(&ppu, width, height); - if(!(ppu_screen = - realloc(ppu_screen, ppu.width * ppu.height * sizeof(Uint32)))) - return error("ppu_screen", "Memory failure"); - memset(ppu_screen, 0, ppu.width * ppu.height * sizeof(Uint32)); - printf("%d x %d(%d)\n", width, height, is_resize); - return 1; -} - -static Uint8 -system_dei(Device *d, Uint8 port) -{ - switch(port) { - case 0x2: - return d->u->wst.ptr; - case 0x3: - return d->u->rst.ptr; - default: - return d->dat[port]; - } -} - -static void -system_deo(Device *d, Uint8 port) -{ - switch(port) { - case 0x2: - d->u->wst.ptr = d->dat[port]; - break; - case 0x3: - d->u->rst.ptr = d->dat[port]; - break; - } - if(port > 0x7 && port < 0xe) - ppu_palette(&ppu, &d->dat[0x8]); -} - -static void -console_deo(Device *d, Uint8 port) -{ - if(port == 0x1) - d->vector = peek16(d->dat, 0x0); - if(port > 0x7) - write(port - 0x7, (char *)&d->dat[port], 1); -} - -static Uint8 -screen_dei(Device *d, Uint8 port) -{ - switch(port) { - case 0x2: - return ppu.width >> 8; - case 0x3: - return ppu.width; - case 0x4: - return ppu.height >> 8; - case 0x5: - return ppu.height; - default: - return d->dat[port]; - } -} - -static void -screen_deo(Device *d, Uint8 port) -{ - switch(port) { - case 0x1: - d->vector = peek16(d->dat, 0x0); - break; - case 0x5: - if(!FIXED_SIZE) - set_size(peek16(d->dat, 0x2), peek16(d->dat, 0x4), 1); - break; - case 0xe: { - Uint16 x = peek16(d->dat, 0x8); - Uint16 y = peek16(d->dat, 0xa); - Uint8 layer = d->dat[0xe] & 0x40; - ppu_write(&ppu, layer ? &ppu.fg : &ppu.bg, x, y, d->dat[0xe] & 0x3); - if(d->dat[0x6] & 0x01) - poke16(d->dat, 0x8, x + 1); /* auto x+1 */ - if(d->dat[0x6] & 0x02) - poke16(d->dat, 0xa, y + 1); /* auto y+1 */ - break; - } - case 0xf: { - Uint16 x = peek16(d->dat, 0x8); - Uint16 y = peek16(d->dat, 0xa); - Layer *layer = (d->dat[0xf] & 0x40) ? &ppu.fg : &ppu.bg; - Uint8 *addr = &d->mem[peek16(d->dat, 0xc)]; - Uint8 twobpp = !!(d->dat[0xf] & 0x80); - ppu_blit(&ppu, layer, x, y, addr, d->dat[0xf] & 0xf, d->dat[0xf] & 0x10, d->dat[0xf] & 0x20, twobpp); - if(d->dat[0x6] & 0x04) - poke16(d->dat, 0xc, peek16(d->dat, 0xc) + 8 + twobpp * 8); /* auto addr+8 / auto addr+16 */ - if(d->dat[0x6] & 0x01) - poke16(d->dat, 0x8, x + 8); /* auto x+8 */ - if(d->dat[0x6] & 0x02) - poke16(d->dat, 0xa, y + 8); /* auto y+8 */ - break; - } - } -} - -static Uint8 -nil_dei(Device *d, Uint8 port) -{ - return d->dat[port]; -} - -static void -nil_deo(Device *d, Uint8 port) -{ - if(port == 0x1) - d->vector = peek16(d->dat, 0x0); -} - -static const char *errors[] = {"underflow", "overflow", "division by zero"}; - -int -uxn_halt(Uxn *u, Uint8 error, char *name, int id) -{ - fprintf(stderr, "Halted: %s %s#%04x, at 0x%04x\n", name, errors[error - 1], id, u->ram.ptr); - return 0; -} - -static int -load(Uxn *u, char *filepath) -{ - FILE *f; - int r; - if(!(f = fopen(filepath, "rb"))) - return 0; - r = fread(u->ram.dat + PAGE_PROGRAM, 1, sizeof(u->ram.dat) - PAGE_PROGRAM, f); - fclose(f); - if(r < 1) - return 0; - fprintf(stderr, "Loaded %s\n", filepath); - return 1; -} - -// --------------------------- - -void -processEvent(Display *display, Window window, XImage *ximage, int width, int height) -{ - static char *tir = "This is red"; - static char *tig = "This is green"; - static char *tib = "This is blue"; - XEvent ev; - XNextEvent(display, &ev); - switch(ev.type) { - case Expose: - XPutImage(display, window, DefaultGC(display, 0), ximage, 0, 0, 0, 0, width, height); - XSetForeground(display, DefaultGC(display, 0), 0x00ff0000); // red - XDrawString(display, window, DefaultGC(display, 0), 32, 32, tir, strlen(tir)); - XDrawString(display, window, DefaultGC(display, 0), 32 + 256, 32, tir, strlen(tir)); - XDrawString(display, window, DefaultGC(display, 0), 32 + 256, 32 + 256, tir, strlen(tir)); - XDrawString(display, window, DefaultGC(display, 0), 32, 32 + 256, tir, strlen(tir)); - XSetForeground(display, DefaultGC(display, 0), 0x0000ff00); // green - XDrawString(display, window, DefaultGC(display, 0), 32, 52, tig, strlen(tig)); - XDrawString(display, window, DefaultGC(display, 0), 32 + 256, 52, tig, strlen(tig)); - XDrawString(display, window, DefaultGC(display, 0), 32 + 256, 52 + 256, tig, strlen(tig)); - XDrawString(display, window, DefaultGC(display, 0), 32, 52 + 256, tig, strlen(tig)); - XSetForeground(display, DefaultGC(display, 0), 0x000000ff); // blue - XDrawString(display, window, DefaultGC(display, 0), 32, 72, tib, strlen(tib)); - XDrawString(display, window, DefaultGC(display, 0), 32 + 256, 72, tib, strlen(tib)); - XDrawString(display, window, DefaultGC(display, 0), 32 + 256, 72 + 256, tib, strlen(tib)); - XDrawString(display, window, DefaultGC(display, 0), 32, 72 + 256, tib, strlen(tib)); - break; - case ButtonPress: - exit(0); - } -} - -int -main(int argc, char **argv) -{ - - Uxn u; - int i, loaded = 0; - - if(!uxn_boot(&u)) - return error("Boot", "Failed"); - - /* system */ devsystem = uxn_port(&u, 0x0, system_dei, system_deo); - /* console */ devconsole = uxn_port(&u, 0x1, nil_dei, console_deo); - /* screen */ devscreen = uxn_port(&u, 0x2, screen_dei, screen_deo); - /* empty */ uxn_port(&u, 0x3, nil_dei, nil_deo); - /* empty */ uxn_port(&u, 0x4, nil_dei, nil_deo); - /* empty */ uxn_port(&u, 0x5, nil_dei, nil_deo); - /* empty */ uxn_port(&u, 0x6, nil_dei, nil_deo); - /* empty */ uxn_port(&u, 0x7, nil_dei, nil_deo); - /* empty */ uxn_port(&u, 0x8, nil_dei, nil_deo); - /* empty */ uxn_port(&u, 0x9, nil_dei, nil_deo); - /* file */ uxn_port(&u, 0xa, nil_dei, nil_deo); - /* datetime */ uxn_port(&u, 0xb, nil_dei, nil_deo); - /* empty */ uxn_port(&u, 0xc, nil_dei, nil_deo); - /* empty */ uxn_port(&u, 0xd, nil_dei, nil_deo); - /* empty */ uxn_port(&u, 0xe, nil_dei, nil_deo); - /* empty */ uxn_port(&u, 0xf, nil_dei, nil_deo); - - for(i = 1; i < argc; ++i) { - if(!loaded++) { - if(!load(&u, argv[i])) - return error("Load", "Failed"); - if(!uxn_eval(&u, PAGE_PROGRAM)) - return error("Init", "Failed"); - } - } - if(!loaded) - return error("Input", "Missing"); - - if(!set_size(WIDTH, HEIGHT, 0)) - return error("Window", "Failed to set window size."); - - if(!uxn_eval(&u, PAGE_PROGRAM)) - return error("Boot", "Failed to start rom."); - ppu_redraw(&ppu, ppu_screen); - - XImage *ximage; - Display *display = XOpenDisplay(NULL); - Visual *visual = DefaultVisual(display, 0); - Window window = XCreateSimpleWindow(display, RootWindow(display, 0), 0, 0, ppu.width, ppu.height, 1, 0, 0); - if(visual->class != TrueColor) { - fprintf(stderr, "Cannot handle non true color visual ...\n"); - exit(1); - } - - ximage = XCreateImage(display, visual, DefaultDepth(display, DefaultScreen(display)), ZPixmap, 0, (Uint8 *)ppu_screen, ppu.width, ppu.height, 32, 0); - - XSelectInput(display, window, ButtonPressMask | ExposureMask); - XMapWindow(display, window); - while(1) { - processEvent(display, window, ximage, ppu.width, ppu.height); - } -} \ No newline at end of file diff --git a/uxncli.c b/uxncli.c deleted file mode 100644 index a8180d7..0000000 --- a/uxncli.c +++ /dev/null @@ -1,214 +0,0 @@ -#include -#include -#include -#include "uxn.h" -#include "devices/file.h" - -/* -Copyright (c) 2021 Devine Lu Linvega - -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. -*/ - -#pragma mark - Core - -static Device *devsystem, *devconsole; - -static int -error(char *msg, const char *err) -{ - fprintf(stderr, "Error %s: %s\n", msg, err); - return 0; -} - -static void -inspect(Stack *s, char *name) -{ - Uint8 x, y; - fprintf(stderr, "\n%s\n", name); - for(y = 0; y < 0x04; ++y) { - for(x = 0; x < 0x08; ++x) { - Uint8 p = y * 0x08 + x; - fprintf(stderr, - p == s->ptr ? "[%02x]" : " %02x ", - s->dat[p]); - } - fprintf(stderr, "\n"); - } -} - -#pragma mark - Devices - -static Uint8 -system_dei(Device *d, Uint8 port) -{ - switch(port) { - case 0x2: return d->u->wst.ptr; - case 0x3: return d->u->rst.ptr; - default: return d->dat[port]; - } -} - -static void -system_deo(Device *d, Uint8 port) -{ - switch(port) { - case 0x2: d->u->wst.ptr = d->dat[port]; break; - case 0x3: d->u->rst.ptr = d->dat[port]; break; - case 0xe: - inspect(&d->u->wst, "Working-stack"); - inspect(&d->u->rst, "Return-stack"); - break; - } -} - -static void -console_deo(Device *d, Uint8 port) -{ - if(port == 0x1) - d->vector = peek16(d->dat, 0x0); - if(port > 0x7) - write(port - 0x7, (char *)&d->dat[port], 1); -} - -static void -file_deo(Device *d, Uint8 port) -{ - switch(port) { - case 0x1: d->vector = peek16(d->dat, 0x0); break; - case 0x9: poke16(d->dat, 0x2, file_init(&d->mem[peek16(d->dat, 0x8)])); break; - case 0xd: poke16(d->dat, 0x2, file_read(&d->mem[peek16(d->dat, 0xc)], peek16(d->dat, 0xa))); break; - case 0xf: poke16(d->dat, 0x2, file_write(&d->mem[peek16(d->dat, 0xe)], peek16(d->dat, 0xa), d->dat[0x7])); break; - case 0x5: poke16(d->dat, 0x2, file_stat(&d->mem[peek16(d->dat, 0x4)], peek16(d->dat, 0xa))); break; - case 0x6: poke16(d->dat, 0x2, file_delete()); break; - } -} - -static Uint8 -datetime_dei(Device *d, Uint8 port) -{ - time_t seconds = time(NULL); - struct tm zt = {0}; - struct tm *t = localtime(&seconds); - if(t == NULL) - t = &zt; - switch(port) { - case 0x0: return (t->tm_year + 1900) >> 8; - case 0x1: return (t->tm_year + 1900); - case 0x2: return t->tm_mon; - case 0x3: return t->tm_mday; - case 0x4: return t->tm_hour; - case 0x5: return t->tm_min; - case 0x6: return t->tm_sec; - case 0x7: return t->tm_wday; - case 0x8: return t->tm_yday >> 8; - case 0x9: return t->tm_yday; - case 0xa: return t->tm_isdst; - default: return d->dat[port]; - } -} - -static Uint8 -nil_dei(Device *d, Uint8 port) -{ - return d->dat[port]; -} - -static void -nil_deo(Device *d, Uint8 port) -{ - if(port == 0x1) d->vector = peek16(d->dat, 0x0); -} - -#pragma mark - Generics - -static const char *errors[] = {"underflow", "overflow", "division by zero"}; - -int -uxn_halt(Uxn *u, Uint8 error, char *name, int id) -{ - fprintf(stderr, "Halted: %s %s#%04x, at 0x%04x\n", name, errors[error - 1], id, u->ram.ptr); - return 0; -} - -static int -console_input(Uxn *u, char c) -{ - devconsole->dat[0x2] = c; - return uxn_eval(u, devconsole->vector); -} - -static void -run(Uxn *u) -{ - Uint16 vec; - while((!u->dev[0].dat[0xf]) && (read(0, &devconsole->dat[0x2], 1) > 0)) { - vec = peek16(devconsole->dat, 0); - if(!vec) vec = u->ram.ptr; /* continue after last BRK */ - uxn_eval(u, vec); - } -} - -static int -load(Uxn *u, char *filepath) -{ - FILE *f; - int r; - if(!(f = fopen(filepath, "rb"))) return 0; - r = fread(u->ram.dat + PAGE_PROGRAM, 1, sizeof(u->ram.dat) - PAGE_PROGRAM, f); - fclose(f); - if(r < 1) return 0; - fprintf(stderr, "Loaded %s\n", filepath); - return 1; -} - -int -main(int argc, char **argv) -{ - Uxn u; - int i, loaded = 0; - - if(!uxn_boot(&u)) - return error("Boot", "Failed"); - - /* system */ devsystem = uxn_port(&u, 0x0, system_dei, system_deo); - /* console */ devconsole = uxn_port(&u, 0x1, nil_dei, console_deo); - /* empty */ uxn_port(&u, 0x2, nil_dei, nil_deo); - /* empty */ uxn_port(&u, 0x3, nil_dei, nil_deo); - /* empty */ uxn_port(&u, 0x4, nil_dei, nil_deo); - /* empty */ uxn_port(&u, 0x5, nil_dei, nil_deo); - /* empty */ uxn_port(&u, 0x6, nil_dei, nil_deo); - /* empty */ uxn_port(&u, 0x7, nil_dei, nil_deo); - /* empty */ uxn_port(&u, 0x8, nil_dei, nil_deo); - /* empty */ uxn_port(&u, 0x9, nil_dei, nil_deo); - /* file */ uxn_port(&u, 0xa, nil_dei, file_deo); - /* datetime */ uxn_port(&u, 0xb, datetime_dei, nil_deo); - /* empty */ uxn_port(&u, 0xc, nil_dei, nil_deo); - /* empty */ uxn_port(&u, 0xd, nil_dei, nil_deo); - /* empty */ uxn_port(&u, 0xe, nil_dei, nil_deo); - /* empty */ uxn_port(&u, 0xf, nil_dei, nil_deo); - - for(i = 1; i < argc; ++i) { - if(!loaded++) { - if(!load(&u, argv[i])) - return error("Load", "Failed"); - if(!uxn_eval(&u, PAGE_PROGRAM)) - return error("Init", "Failed"); - } else { - char *p = argv[i]; - while(*p) console_input(&u, *p++); - console_input(&u, '\n'); - } - } - if(!loaded) - return error("Input", "Missing"); - - run(&u); - - return 0; -} diff --git a/uxnemu.c b/uxnemu.c deleted file mode 100644 index 6979c2c..0000000 --- a/uxnemu.c +++ /dev/null @@ -1,577 +0,0 @@ -#include -#include -#include -#include "uxn.h" - -#pragma GCC diagnostic push -#pragma clang diagnostic push -#pragma GCC diagnostic ignored "-Wpedantic" -#pragma clang diagnostic ignored "-Wtypedef-redefinition" -#include -#include "devices/ppu.h" -#include "devices/apu.h" -#include "devices/file.h" -#pragma GCC diagnostic pop -#pragma clang diagnostic pop - -/* -Copyright (c) 2021 Devine Lu Linvega - -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 WIDTH 64 * 8 -#define HEIGHT 40 * 8 -#define PAD 4 -#define FIXED_SIZE 0 -#define POLYPHONY 4 -#define BENCH 0 - -static SDL_Window *gWindow; -static SDL_Texture *gTexture; -static SDL_Renderer *gRenderer; -static SDL_AudioDeviceID audio_id; -static SDL_Rect gRect; -/* devices */ -static Ppu ppu; -static Apu apu[POLYPHONY]; -static Device *devsystem, *devscreen, *devmouse, *devctrl, *devaudio0, *devconsole; -static Uint8 zoom = 1; -static Uint32 *ppu_screen, stdin_event, audio0_event; - -static int -error(char *msg, const char *err) -{ - fprintf(stderr, "%s: %s\n", msg, err); - return 0; -} - -#pragma mark - Generics - -static void -audio_callback(void *u, Uint8 *stream, int len) -{ - int i, running = 0; - Sint16 *samples = (Sint16 *)stream; - SDL_memset(stream, 0, len); - for(i = 0; i < POLYPHONY; ++i) - running += apu_render(&apu[i], samples, samples + len / 2); - if(!running) - SDL_PauseAudioDevice(audio_id, 1); - (void)u; -} - -void -apu_finished_handler(Apu *c) -{ - SDL_Event event; - event.type = audio0_event + (c - apu); - SDL_PushEvent(&event); -} - -static int -stdin_handler(void *p) -{ - SDL_Event event; - event.type = stdin_event; - while(read(0, &event.cbutton.button, 1) > 0) - SDL_PushEvent(&event); - return 0; - (void)p; -} - -static void -set_window_size(SDL_Window *window, int w, int h) -{ - SDL_Point win, win_old; - SDL_GetWindowPosition(window, &win.x, &win.y); - SDL_GetWindowSize(window, &win_old.x, &win_old.y); - SDL_SetWindowPosition(window, (win.x + win_old.x / 2) - w / 2, (win.y + win_old.y / 2) - h / 2); - SDL_SetWindowSize(window, w, h); -} - -static void -set_zoom(Uint8 scale) -{ - zoom = SDL_clamp(scale, 1, 3); - set_window_size(gWindow, (ppu.width + PAD * 2) * zoom, (ppu.height + PAD * 2) * zoom); -} - -static int -set_size(Uint16 width, Uint16 height, int is_resize) -{ - ppu_resize(&ppu, width, height); - gRect.x = PAD; - gRect.y = PAD; - gRect.w = ppu.width; - gRect.h = ppu.height; - if(!(ppu_screen = realloc(ppu_screen, ppu.width * ppu.height * sizeof(Uint32)))) - return error("ppu_screen", "Memory failure"); - memset(ppu_screen, 0, ppu.width * ppu.height * sizeof(Uint32)); - if(gTexture != NULL) SDL_DestroyTexture(gTexture); - SDL_RenderSetLogicalSize(gRenderer, ppu.width + PAD * 2, ppu.height + PAD * 2); - gTexture = SDL_CreateTexture(gRenderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STATIC, ppu.width + PAD * 2, ppu.height + PAD * 2); - if(gTexture == NULL || SDL_SetTextureBlendMode(gTexture, SDL_BLENDMODE_NONE)) - return error("gTexture", SDL_GetError()); - if(SDL_UpdateTexture(gTexture, NULL, ppu_screen, sizeof(Uint32)) != 0) - return error("SDL_UpdateTexture", SDL_GetError()); - if(is_resize) - set_window_size(gWindow, (ppu.width + PAD * 2) * zoom, (ppu.height + PAD * 2) * zoom); - return 1; -} - -static void -capture_screen(void) -{ - const Uint32 format = SDL_PIXELFORMAT_RGB24; - time_t t = time(NULL); - char fname[64]; - int w, h; - SDL_Surface *surface; - SDL_GetRendererOutputSize(gRenderer, &w, &h); - surface = SDL_CreateRGBSurfaceWithFormat(0, w, h, 24, format); - SDL_RenderReadPixels(gRenderer, NULL, format, surface->pixels, surface->pitch); - strftime(fname, sizeof(fname), "screenshot-%Y%m%d-%H%M%S.bmp", localtime(&t)); - SDL_SaveBMP(surface, fname); - SDL_FreeSurface(surface); - fprintf(stderr, "Saved %s\n", fname); -} - -static void -redraw(Uxn *u) -{ - if(devsystem->dat[0xe]) - ppu_debug(&ppu, u->wst.dat, u->wst.ptr, u->rst.ptr, u->ram.dat); - ppu_redraw(&ppu, ppu_screen); - if(SDL_UpdateTexture(gTexture, &gRect, ppu_screen, ppu.width * sizeof(Uint32)) != 0) - error("SDL_UpdateTexture", SDL_GetError()); - SDL_RenderClear(gRenderer); - SDL_RenderCopy(gRenderer, gTexture, NULL, NULL); - SDL_RenderPresent(gRenderer); -} - -static int -init(void) -{ - SDL_AudioSpec as; - SDL_zero(as); - as.freq = SAMPLE_FREQUENCY; - as.format = AUDIO_S16; - as.channels = 2; - as.callback = audio_callback; - as.samples = 512; - as.userdata = NULL; - if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) { - error("sdl", SDL_GetError()); - if(SDL_Init(SDL_INIT_VIDEO) < 0) - return error("sdl", SDL_GetError()); - } else { - audio_id = SDL_OpenAudioDevice(NULL, 0, &as, NULL, 0); - if(!audio_id) - error("sdl_audio", SDL_GetError()); - } - gWindow = SDL_CreateWindow("Uxn", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, (WIDTH + PAD * 2) * zoom, (HEIGHT + PAD * 2) * zoom, SDL_WINDOW_SHOWN); - if(gWindow == NULL) - return error("sdl_window", SDL_GetError()); - gRenderer = SDL_CreateRenderer(gWindow, -1, 0); - if(gRenderer == NULL) - return error("sdl_renderer", SDL_GetError()); - stdin_event = SDL_RegisterEvents(1); - audio0_event = SDL_RegisterEvents(POLYPHONY); - SDL_CreateThread(stdin_handler, "stdin", NULL); - SDL_StartTextInput(); - SDL_ShowCursor(SDL_DISABLE); - SDL_EventState(SDL_DROPFILE, SDL_ENABLE); - return 1; -} - -static void -domouse(SDL_Event *event) -{ - Uint8 flag = 0x00; - Uint16 x = SDL_clamp(event->motion.x - PAD, 0, ppu.width - 1); - Uint16 y = SDL_clamp(event->motion.y - PAD, 0, ppu.height - 1); - if(event->type == SDL_MOUSEWHEEL) { - devmouse->dat[7] = event->wheel.y; - return; - } - poke16(devmouse->dat, 0x2, x); - poke16(devmouse->dat, 0x4, y); - devmouse->dat[7] = 0x00; - switch(event->button.button) { - case SDL_BUTTON_LEFT: flag = 0x01; break; - case SDL_BUTTON_RIGHT: flag = 0x10; break; - } - switch(event->type) { - case SDL_MOUSEBUTTONDOWN: - devmouse->dat[6] |= flag; - break; - case SDL_MOUSEBUTTONUP: - devmouse->dat[6] &= (~flag); - break; - } -} - -#pragma mark - Devices - -static Uint8 -system_dei(Device *d, Uint8 port) -{ - switch(port) { - case 0x2: return d->u->wst.ptr; - case 0x3: return d->u->rst.ptr; - default: return d->dat[port]; - } -} - -static void -system_deo(Device *d, Uint8 port) -{ - switch(port) { - case 0x2: d->u->wst.ptr = d->dat[port]; break; - case 0x3: d->u->rst.ptr = d->dat[port]; break; - } - if(port > 0x7 && port < 0xe) - ppu_palette(&ppu, &d->dat[0x8]); -} - -static void -console_deo(Device *d, Uint8 port) -{ - if(port == 0x1) - d->vector = peek16(d->dat, 0x0); - if(port > 0x7) - write(port - 0x7, (char *)&d->dat[port], 1); -} - -static Uint8 -screen_dei(Device *d, Uint8 port) -{ - switch(port) { - case 0x2: return ppu.width >> 8; - case 0x3: return ppu.width; - case 0x4: return ppu.height >> 8; - case 0x5: return ppu.height; - default: return d->dat[port]; - } -} - -static void -screen_deo(Device *d, Uint8 port) -{ - switch(port) { - case 0x1: d->vector = peek16(d->dat, 0x0); break; - case 0x5: - if(!FIXED_SIZE) set_size(peek16(d->dat, 0x2), peek16(d->dat, 0x4), 1); - break; - case 0xe: { - Uint16 x = peek16(d->dat, 0x8); - Uint16 y = peek16(d->dat, 0xa); - Uint8 layer = d->dat[0xe] & 0x40; - ppu_write(&ppu, layer ? &ppu.fg : &ppu.bg, x, y, d->dat[0xe] & 0x3); - if(d->dat[0x6] & 0x01) poke16(d->dat, 0x8, x + 1); /* auto x+1 */ - if(d->dat[0x6] & 0x02) poke16(d->dat, 0xa, y + 1); /* auto y+1 */ - break; - } - case 0xf: { - Uint16 x = peek16(d->dat, 0x8); - Uint16 y = peek16(d->dat, 0xa); - Layer *layer = (d->dat[0xf] & 0x40) ? &ppu.fg : &ppu.bg; - Uint8 *addr = &d->mem[peek16(d->dat, 0xc)]; - Uint8 twobpp = !!(d->dat[0xf] & 0x80); - ppu_blit(&ppu, layer, x, y, addr, d->dat[0xf] & 0xf, d->dat[0xf] & 0x10, d->dat[0xf] & 0x20, twobpp); - if(d->dat[0x6] & 0x04) poke16(d->dat, 0xc, peek16(d->dat, 0xc) + 8 + twobpp*8); /* auto addr+8 / auto addr+16 */ - if(d->dat[0x6] & 0x01) poke16(d->dat, 0x8, x + 8); /* auto x+8 */ - if(d->dat[0x6] & 0x02) poke16(d->dat, 0xa, y + 8); /* auto y+8 */ - break; - } - } -} - -static void -file_deo(Device *d, Uint8 port) -{ - switch(port) { - case 0x1: d->vector = peek16(d->dat, 0x0); break; - case 0x9: poke16(d->dat, 0x2, file_init(&d->mem[peek16(d->dat, 0x8)])); break; - case 0xd: poke16(d->dat, 0x2, file_read(&d->mem[peek16(d->dat, 0xc)], peek16(d->dat, 0xa))); break; - case 0xf: poke16(d->dat, 0x2, file_write(&d->mem[peek16(d->dat, 0xe)], peek16(d->dat, 0xa), d->dat[0x7])); break; - case 0x5: poke16(d->dat, 0x2, file_stat(&d->mem[peek16(d->dat, 0x4)], peek16(d->dat, 0xa))); break; - case 0x6: poke16(d->dat, 0x2, file_delete()); break; - } -} - -static Uint8 -audio_dei(Device *d, Uint8 port) -{ - Apu *c = &apu[d - devaudio0]; - if(!audio_id) return d->dat[port]; - switch(port) { - case 0x4: return apu_get_vu(c); - case 0x2: poke16(d->dat, 0x2, c->i); /* fall through */ - default: return d->dat[port]; - } -} - -static void -audio_deo(Device *d, Uint8 port) -{ - Apu *c = &apu[d - devaudio0]; - if(!audio_id) return; - if(port == 0xf) { - SDL_LockAudioDevice(audio_id); - c->len = peek16(d->dat, 0xa); - c->addr = &d->mem[peek16(d->dat, 0xc)]; - c->volume[0] = d->dat[0xe] >> 4; - c->volume[1] = d->dat[0xe] & 0xf; - c->repeat = !(d->dat[0xf] & 0x80); - apu_start(c, peek16(d->dat, 0x8), d->dat[0xf] & 0x7f); - SDL_UnlockAudioDevice(audio_id); - SDL_PauseAudioDevice(audio_id, 0); - } -} - -static Uint8 -datetime_dei(Device *d, Uint8 port) -{ - time_t seconds = time(NULL); - struct tm zt = {0}; - struct tm *t = localtime(&seconds); - if(t == NULL) - t = &zt; - switch(port) { - case 0x0: return (t->tm_year + 1900) >> 8; - case 0x1: return (t->tm_year + 1900); - case 0x2: return t->tm_mon; - case 0x3: return t->tm_mday; - case 0x4: return t->tm_hour; - case 0x5: return t->tm_min; - case 0x6: return t->tm_sec; - case 0x7: return t->tm_wday; - case 0x8: return t->tm_yday >> 8; - case 0x9: return t->tm_yday; - case 0xa: return t->tm_isdst; - default: return d->dat[port]; - } -} - -static Uint8 -nil_dei(Device *d, Uint8 port) -{ - return d->dat[port]; -} - -static void -nil_deo(Device *d, Uint8 port) -{ - if(port == 0x1) d->vector = peek16(d->dat, 0x0); -} - -/* Boot */ - -static int -load(Uxn *u, char *rom) -{ - SDL_RWops *f; - int r; - if(!(f = SDL_RWFromFile(rom, "rb"))) return 0; - r = f->read(f, u->ram.dat + PAGE_PROGRAM, 1, sizeof(u->ram.dat) - PAGE_PROGRAM); - f->close(f); - if(r < 1) return 0; - fprintf(stderr, "Loaded %s\n", rom); - SDL_SetWindowTitle(gWindow, rom); - return 1; -} - -static int -start(Uxn *u, char *rom) -{ - if(!uxn_boot(u)) - return error("Boot", "Failed to start uxn."); - if(!load(u, rom)) - return error("Boot", "Failed to load rom."); - - /* system */ devsystem = uxn_port(u, 0x0, system_dei, system_deo); - /* console */ devconsole = uxn_port(u, 0x1, nil_dei, console_deo); - /* screen */ devscreen = uxn_port(u, 0x2, screen_dei, screen_deo); - /* audio0 */ devaudio0 = uxn_port(u, 0x3, audio_dei, audio_deo); - /* audio1 */ uxn_port(u, 0x4, audio_dei, audio_deo); - /* audio2 */ uxn_port(u, 0x5, audio_dei, audio_deo); - /* audio3 */ uxn_port(u, 0x6, audio_dei, audio_deo); - /* unused */ uxn_port(u, 0x7, nil_dei, nil_deo); - /* control */ devctrl = uxn_port(u, 0x8, nil_dei, nil_deo); - /* mouse */ devmouse = uxn_port(u, 0x9, nil_dei, nil_deo); - /* file */ uxn_port(u, 0xa, nil_dei, file_deo); - /* datetime */ uxn_port(u, 0xb, datetime_dei, nil_deo); - /* unused */ uxn_port(u, 0xc, nil_dei, nil_deo); - /* unused */ uxn_port(u, 0xd, nil_dei, nil_deo); - /* unused */ uxn_port(u, 0xe, nil_dei, nil_deo); - /* unused */ uxn_port(u, 0xf, nil_dei, nil_deo); - - if(!uxn_eval(u, PAGE_PROGRAM)) - return error("Boot", "Failed to start rom."); - - return 1; -} - -static void -restart(Uxn *u) -{ - set_size(WIDTH, HEIGHT, 1); - start(u, "boot.rom"); -} - -static void -doctrl(Uxn *u, SDL_Event *event, int z) -{ - Uint8 flag = 0x00; - SDL_Keymod mods = SDL_GetModState(); - devctrl->dat[2] &= 0xf8; - if(mods & KMOD_CTRL) devctrl->dat[2] |= 0x01; - if(mods & KMOD_ALT) devctrl->dat[2] |= 0x02; - if(mods & KMOD_SHIFT) devctrl->dat[2] |= 0x04; - /* clang-format off */ - switch(event->key.keysym.sym) { - case SDLK_ESCAPE: flag = 0x08; break; - case SDLK_UP: flag = 0x10; break; - case SDLK_DOWN: flag = 0x20; break; - case SDLK_LEFT: flag = 0x40; break; - case SDLK_RIGHT: flag = 0x80; break; - case SDLK_F1: if(z) set_zoom(zoom > 2 ? 1 : zoom + 1); break; - case SDLK_F2: if(z) devsystem->dat[0xe] = !devsystem->dat[0xe]; ppu_clear(&ppu, &ppu.fg); break; - case SDLK_F3: if(z) capture_screen(); break; - case SDLK_AC_BACK: - case SDLK_F4: if(z) restart(u); break; - } - /* clang-format on */ - if(z) { - devctrl->dat[2] |= flag; - if(event->key.keysym.sym < 0x20 || event->key.keysym.sym == SDLK_DELETE) - devctrl->dat[3] = event->key.keysym.sym; - else if((mods & KMOD_CTRL) && event->key.keysym.sym >= SDLK_a && event->key.keysym.sym <= SDLK_z) - devctrl->dat[3] = event->key.keysym.sym - (mods & KMOD_SHIFT) * 0x20; - } else - devctrl->dat[2] &= ~flag; -} - -static const char *errors[] = {"underflow", "overflow", "division by zero"}; - -int -uxn_halt(Uxn *u, Uint8 error, char *name, int id) -{ - fprintf(stderr, "Halted: %s %s#%04x, at 0x%04x\n", name, errors[error - 1], id, u->ram.ptr); - return 0; -} - -static int -console_input(Uxn *u, char c) -{ - devconsole->dat[0x2] = c; - return uxn_eval(u, devconsole->vector); -} - -static int -run(Uxn *u) -{ - redraw(u); - while(!devsystem->dat[0xf]) { - SDL_Event event; - double elapsed, begin = 0; - int ksym; - if(!BENCH) - begin = SDL_GetPerformanceCounter(); - while(SDL_PollEvent(&event) != 0) { - switch(event.type) { - case SDL_DROPFILE: - set_size(WIDTH, HEIGHT, 0); - start(u, event.drop.file); - SDL_free(event.drop.file); - break; - case SDL_QUIT: - return error("Run", "Quit."); - case SDL_TEXTINPUT: - devctrl->dat[3] = event.text.text[0]; /* fall-thru */ - case SDL_KEYDOWN: - case SDL_KEYUP: - doctrl(u, &event, event.type == SDL_KEYDOWN); - uxn_eval(u, devctrl->vector); - devctrl->dat[3] = 0; - - if(event.type == SDL_KEYDOWN) { - ksym = event.key.keysym.sym; - if(SDL_PeepEvents(&event, 1, SDL_PEEKEVENT, SDL_KEYUP, SDL_KEYUP) == 1 && ksym == event.key.keysym.sym) - goto breakout; - } - break; - case SDL_MOUSEWHEEL: - case SDL_MOUSEBUTTONUP: - case SDL_MOUSEBUTTONDOWN: - case SDL_MOUSEMOTION: - domouse(&event); - uxn_eval(u, devmouse->vector); - break; - case SDL_WINDOWEVENT: - if(event.window.event == SDL_WINDOWEVENT_EXPOSED) - redraw(u); - break; - default: - if(event.type == stdin_event) { - console_input(u, event.cbutton.button); - } else if(event.type >= audio0_event && event.type < audio0_event + POLYPHONY) - uxn_eval(u, peek16((devaudio0 + (event.type - audio0_event))->dat, 0)); - } - } - breakout: - uxn_eval(u, devscreen->vector); - if(ppu.fg.changed || ppu.bg.changed || devsystem->dat[0xe]) - redraw(u); - if(!BENCH) { - elapsed = (SDL_GetPerformanceCounter() - begin) / (double)SDL_GetPerformanceFrequency() * 1000.0f; - SDL_Delay(SDL_clamp(16.666f - elapsed, 0, 1000)); - } - } - return error("Run", "Ended."); -} - -int -main(int argc, char **argv) -{ - SDL_DisplayMode DM; - Uxn u; - int i, loaded = 0; - - if(!init()) - return error("Init", "Failed to initialize emulator."); - if(!set_size(WIDTH, HEIGHT, 0)) - return error("Window", "Failed to set window size."); - - /* set default zoom */ - if(SDL_GetCurrentDisplayMode(0, &DM) == 0) - set_zoom(DM.w / 1280); - for(i = 1; i < argc; ++i) { - /* get default zoom from flags */ - if(strcmp(argv[i], "-s") == 0) { - if(i < argc - 1) - set_zoom(atoi(argv[++i])); - else - return error("Opt", "-s No scale provided."); - } else if(!loaded++) { - if(!start(&u, argv[i])) - return error("Boot", "Failed to boot."); - } else { - char *p = argv[i]; - while(*p) console_input(&u, *p++); - console_input(&u, '\n'); - } - } - if(!loaded && !start(&u, "boot.rom")) - return error("usage", "uxnemu [-s scale] file.rom"); - run(&u); - SDL_Quit(); - return 0; -}