This commit is contained in:
neauoire 2022-03-26 17:23:52 -07:00
commit b88531dd75
18 changed files with 1849 additions and 0 deletions

21
.clang-format Normal file
View File

@ -0,0 +1,21 @@
AlignAfterOpenBracket: DontAlign
AlignEscapedNewlines: DontAlign
AlignOperands: DontAlign
AllowShortBlocksOnASingleLine: Always
AllowShortCaseLabelsOnASingleLine: true
AllowShortEnumsOnASingleLine: true
AllowShortIfStatementsOnASingleLine: true
AllowShortLoopsOnASingleLine: true
AlwaysBreakAfterDefinitionReturnType: TopLevel
BinPackArguments: false
BinPackParameters: false
BreakBeforeBraces: WebKit
IndentCaseLabels: false
TabWidth: 4
IndentWidth: 4
ContinuationIndentWidth: 4
UseTab: ForContinuationAndIndentation
ColumnLimit: 0
ReflowComments: false
SortIncludes: false
SpaceBeforeParens: false

15
.gitignore vendored Normal file
View File

@ -0,0 +1,15 @@
.DS*
*jpg~
*png~
*gif~
*bmp~
/bin
*io.bit
*.bak
/*-test
/screenshot-*.bmp
*snarf
*theme
*.[o0125678vqki]

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) Devine Lu Linvega
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

9
README.md Normal file
View File

@ -0,0 +1,9 @@
# Uxn
An assembler and emulator for the [Uxn stack-machine](https://wiki.xxiivv.com/site/uxn.html), written in ANSI C.
More docs coming soon.
## 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).

13
build.sh Executable file
View File

@ -0,0 +1,13 @@
#!/bin/sh -e
clang-format -i uxn11.c
echo "Cleaning.."
rm -f ./bin/*
echo "Building.."
mkdir -p bin
gcc uxn.c devices/ppu.c uxn11.c -o bin/uxn11 -lX11
echo "Running.."
bin/uxn11 etc/screen.rom

97
devices/apu.c Normal file
View File

@ -0,0 +1,97 @@
#include "../uxn.h"
#include "apu.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 NOTE_PERIOD (SAMPLE_FREQUENCY * 0x4000 / 11025)
#define ADSR_STEP (SAMPLE_FREQUENCY / 0xf)
/* clang-format off */
static Uint32 advances[12] = {
0x80000, 0x879c8, 0x8facd, 0x9837f, 0xa1451, 0xaadc1,
0xb504f, 0xbfc88, 0xcb2ff, 0xd7450, 0xe411f, 0xf1a1c
};
/* clang-format on */
static Sint32
envelope(Apu *c, Uint32 age)
{
if(!c->r) return 0x0888;
if(age < c->a) return 0x0888 * age / c->a;
if(age < c->d) return 0x0444 * (2 * c->d - c->a - age) / (c->d - c->a);
if(age < c->s) return 0x0444;
if(age < c->r) return 0x0444 * (c->r - age) / (c->r - c->s);
c->advance = 0;
return 0x0000;
}
int
apu_render(Apu *c, Sint16 *sample, Sint16 *end)
{
Sint32 s;
if(!c->advance || !c->period) return 0;
while(sample < end) {
c->count += c->advance;
c->i += c->count / c->period;
c->count %= c->period;
if(c->i >= c->len) {
if(!c->repeat) {
c->advance = 0;
break;
}
c->i %= c->len;
}
s = (Sint8)(c->addr[c->i] + 0x80) * envelope(c, c->age++);
*sample++ += s * c->volume[0] / 0x180;
*sample++ += s * c->volume[1] / 0x180;
}
if(!c->advance) apu_finished_handler(c);
return 1;
}
void
apu_start(Apu *c, Uint16 adsr, Uint8 pitch)
{
if(pitch < 108 && c->len)
c->advance = advances[pitch % 12] >> (8 - pitch / 12);
else {
c->advance = 0;
return;
}
c->a = ADSR_STEP * (adsr >> 12);
c->d = ADSR_STEP * (adsr >> 8 & 0xf) + c->a;
c->s = ADSR_STEP * (adsr >> 4 & 0xf) + c->d;
c->r = ADSR_STEP * (adsr >> 0 & 0xf) + c->s;
c->age = 0;
c->i = 0;
if(c->len <= 0x100) /* single cycle mode */
c->period = NOTE_PERIOD * 337 / 2 / c->len;
else /* sample repeat mode */
c->period = NOTE_PERIOD;
}
Uint8
apu_get_vu(Apu *c)
{
int i;
Sint32 sum[2] = {0, 0};
if(!c->advance || !c->period) return 0;
for(i = 0; i < 2; ++i) {
if(!c->volume[i]) continue;
sum[i] = 1 + envelope(c, c->age) * c->volume[i] / 0x800;
if(sum[i] > 0xf) sum[i] = 0xf;
}
return (sum[0] << 4) | sum[1];
}

28
devices/apu.h Normal file
View File

@ -0,0 +1,28 @@
/*
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.
*/
typedef signed int Sint32;
#define SAMPLE_FREQUENCY 44100
typedef struct {
Uint8 *addr;
Uint32 count, advance, period, age, a, d, s, r;
Uint16 i, len;
Sint8 volume[2];
Uint8 pitch, repeat;
} Apu;
int apu_render(Apu *c, Sint16 *sample, Sint16 *end);
void apu_start(Apu *c, Uint16 adsr, Uint8 pitch);
Uint8 apu_get_vu(Apu *c);
void apu_finished_handler(Apu *c);

139
devices/file.c Normal file
View File

@ -0,0 +1,139 @@
#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 <stdio.h>
#include <dirent.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
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);
}

17
devices/file.h Normal file
View File

@ -0,0 +1,17 @@
/*
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.
*/
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);

150
devices/ppu.c Normal file
View File

@ -0,0 +1,150 @@
#include "ppu.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.
*/
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 Uint8 font[][8] = {
{0x00, 0x7c, 0x82, 0x82, 0x82, 0x82, 0x82, 0x7c},
{0x00, 0x30, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10},
{0x00, 0x7c, 0x82, 0x02, 0x7c, 0x80, 0x80, 0xfe},
{0x00, 0x7c, 0x82, 0x02, 0x1c, 0x02, 0x82, 0x7c},
{0x00, 0x0c, 0x14, 0x24, 0x44, 0x84, 0xfe, 0x04},
{0x00, 0xfe, 0x80, 0x80, 0x7c, 0x02, 0x82, 0x7c},
{0x00, 0x7c, 0x82, 0x80, 0xfc, 0x82, 0x82, 0x7c},
{0x00, 0x7c, 0x82, 0x02, 0x1e, 0x02, 0x02, 0x02},
{0x00, 0x7c, 0x82, 0x82, 0x7c, 0x82, 0x82, 0x7c},
{0x00, 0x7c, 0x82, 0x82, 0x7e, 0x02, 0x82, 0x7c},
{0x00, 0x7c, 0x82, 0x02, 0x7e, 0x82, 0x82, 0x7e},
{0x00, 0xfc, 0x82, 0x82, 0xfc, 0x82, 0x82, 0xfc},
{0x00, 0x7c, 0x82, 0x80, 0x80, 0x80, 0x82, 0x7c},
{0x00, 0xfc, 0x82, 0x82, 0x82, 0x82, 0x82, 0xfc},
{0x00, 0x7c, 0x82, 0x80, 0xf0, 0x80, 0x82, 0x7c},
{0x00, 0x7c, 0x82, 0x80, 0xf0, 0x80, 0x80, 0x80}};
void
ppu_palette(Ppu *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
ppu_resize(Ppu *p, Uint16 width, Uint16 height)
{
Uint8
*bg = realloc(p->bg.pixels, width * height),
*fg = realloc(p->fg.pixels, width * height);
if(bg) p->bg.pixels = bg;
if(fg) p->fg.pixels = fg;
if(bg && fg) {
p->width = width;
p->height = height;
ppu_clear(p, &p->bg);
ppu_clear(p, &p->fg);
}
}
void
ppu_clear(Ppu *p, Layer *layer)
{
Uint32 i, size = p->width * p->height;
for(i = 0; i < size; ++i)
layer->pixels[i] = 0x00;
layer->changed = 1;
}
#pragma weak ppu_redraw
void
ppu_redraw(Ppu *p, Uint32 *screen)
{
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)
screen[i] = palette[p->fg.pixels[i] << 2 | p->bg.pixels[i]];
p->fg.changed = p->bg.changed = 0;
}
void
ppu_write(Ppu *p, Layer *layer, Uint16 x, Uint16 y, Uint8 color)
{
if(x < p->width && y < p->height) {
Uint32 i = x + y * p->width;
Uint8 prev = layer->pixels[i];
if(color != prev) {
layer->pixels[i] = color;
layer->changed = 1;
}
}
}
void
ppu_blit(Ppu *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)
ppu_write(p,
layer,
x + (flipx ? 7 - h : h),
y + (flipy ? 7 - v : v),
blending[ch][color]);
}
}
}
void
ppu_debug(Ppu *p, Uint8 *stack, Uint8 wptr, Uint8 rptr, Uint8 *memory)
{
Uint8 i, x, y, b;
for(i = 0; i < 0x20; ++i) {
x = ((i % 8) * 3 + 1) * 8, y = (i / 8 + 1) * 8, b = stack[i];
/* working stack */
ppu_blit(p, &p->fg, x, y, font[(b >> 4) & 0xf], 1 + (wptr == i) * 0x7, 0, 0, 0);
ppu_blit(p, &p->fg, x + 8, y, font[b & 0xf], 1 + (wptr == i) * 0x7, 0, 0, 0);
y = 0x28 + (i / 8 + 1) * 8;
b = memory[i];
/* return stack */
ppu_blit(p, &p->fg, x, y, font[(b >> 4) & 0xf], 3, 0, 0, 0);
ppu_blit(p, &p->fg, x + 8, y, font[b & 0xf], 3, 0, 0, 0);
}
/* return pointer */
ppu_blit(p, &p->fg, 0x8, y + 0x10, font[(rptr >> 4) & 0xf], 0x2, 0, 0, 0);
ppu_blit(p, &p->fg, 0x10, y + 0x10, font[rptr & 0xf], 0x2, 0, 0, 0);
/* guides */
for(x = 0; x < 0x10; ++x) {
ppu_write(p, &p->fg, x, p->height / 2, 2);
ppu_write(p, &p->fg, p->width - x, p->height / 2, 2);
ppu_write(p, &p->fg, p->width / 2, p->height - x, 2);
ppu_write(p, &p->fg, p->width / 2, x, 2);
ppu_write(p, &p->fg, p->width / 2 - 0x10 / 2 + x, p->height / 2, 2);
ppu_write(p, &p->fg, p->width / 2, p->height / 2 - 0x10 / 2 + x, 2);
}
}

37
devices/ppu.h Normal file
View File

@ -0,0 +1,37 @@
#include <stdlib.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.
*/
typedef unsigned char Uint8;
typedef unsigned short Uint16;
typedef unsigned int Uint32;
typedef struct Layer {
Uint8 *pixels;
Uint8 changed;
} Layer;
typedef struct Ppu {
Uint32 palette[4];
Uint16 width, height;
Layer fg, bg;
} Ppu;
void ppu_palette(Ppu *p, Uint8 *addr);
void ppu_resize(Ppu *p, Uint16 width, Uint16 height);
void ppu_clear(Ppu *p, Layer *layer);
void ppu_redraw(Ppu *p, Uint32 *screen);
void ppu_write(Ppu *p, Layer *layer, Uint16 x, Uint16 y, Uint8 color);
void ppu_blit(Ppu *p, Layer *layer, Uint16 x, Uint16 y, Uint8 *sprite, Uint8 color, Uint8 flipx, Uint8 flipy, Uint8 twobpp);
void ppu_debug(Ppu *p, Uint8 *stack, Uint8 wptr, Uint8 rptr, Uint8 *memory);

37
devices/ppu_aarch64.c Normal file
View File

@ -0,0 +1,37 @@
#ifdef __aarch64__
#include <arm_neon.h>
#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

BIN
etc/screen.rom Normal file

Binary file not shown.

158
uxn.c Normal file
View File

@ -0,0 +1,158 @@
#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;
}

50
uxn.h Normal file
View File

@ -0,0 +1,50 @@
/*
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.
*/
typedef unsigned char Uint8;
typedef signed char Sint8;
typedef unsigned short Uint16;
typedef signed short Sint16;
typedef unsigned int Uint32;
#define PAGE_PROGRAM 0x0100
typedef struct {
Uint8 ptr, kptr, error;
Uint8 dat[256];
} Stack;
typedef struct {
Uint16 ptr;
Uint8 dat[65536];
} Memory;
typedef struct Device {
struct Uxn *u;
Uint8 addr, dat[16], *mem;
Uint16 vector;
Uint8 (*dei)(struct Device *d, Uint8);
void (*deo)(struct Device *d, Uint8);
} Device;
typedef struct Uxn {
Stack wst, rst, *src, *dst;
Memory ram;
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);
Device *uxn_port(Uxn *u, Uint8 id, Uint8 (*deifn)(Device *, Uint8), void (*deofn)(Device *, Uint8));

266
uxn11.c Normal file
View File

@ -0,0 +1,266 @@
#include <stdio.h>
#include <time.h>
#include <unistd.h>
#include <X11/Xlib.h>
#include <stdlib.h>
#include <string.h>
#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);
}
}

214
uxncli.c Normal file
View File

@ -0,0 +1,214 @@
#include <stdio.h>
#include <unistd.h>
#include <time.h>
#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;
}

577
uxnemu.c Normal file
View File

@ -0,0 +1,577 @@
#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include "uxn.h"
#pragma GCC diagnostic push
#pragma clang diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic"
#pragma clang diagnostic ignored "-Wtypedef-redefinition"
#include <SDL.h>
#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;
}