commit b88531d

neauoire  ·  2022-03-27 00:23:52 +0000 UTC
parent b88531d
Init
18 files changed,  +1849, -0
A uxn.c
A uxn.h
+21, -0
 1@@ -0,0 +1,21 @@
 2+AlignAfterOpenBracket: DontAlign
 3+AlignEscapedNewlines: DontAlign
 4+AlignOperands: DontAlign
 5+AllowShortBlocksOnASingleLine: Always
 6+AllowShortCaseLabelsOnASingleLine: true
 7+AllowShortEnumsOnASingleLine: true
 8+AllowShortIfStatementsOnASingleLine: true
 9+AllowShortLoopsOnASingleLine: true
10+AlwaysBreakAfterDefinitionReturnType: TopLevel
11+BinPackArguments: false
12+BinPackParameters: false
13+BreakBeforeBraces: WebKit
14+IndentCaseLabels: false
15+TabWidth: 4
16+IndentWidth: 4
17+ContinuationIndentWidth: 4
18+UseTab: ForContinuationAndIndentation
19+ColumnLimit: 0
20+ReflowComments: false
21+SortIncludes: false
22+SpaceBeforeParens: false
+15, -0
 1@@ -0,0 +1,15 @@
 2+.DS*
 3+*jpg~
 4+*png~
 5+*gif~
 6+*bmp~
 7+/bin
 8+*io.bit
 9+*.bak
10+/*-test
11+/screenshot-*.bmp
12+
13+*snarf
14+*theme
15+
16+*.[o0125678vqki]
+21, -0
 1@@ -0,0 +1,21 @@
 2+MIT License
 3+
 4+Copyright (c) Devine Lu Linvega
 5+
 6+Permission is hereby granted, free of charge, to any person obtaining a copy
 7+of this software and associated documentation files (the "Software"), to deal
 8+in the Software without restriction, including without limitation the rights
 9+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+copies of the Software, and to permit persons to whom the Software is
11+furnished to do so, subject to the following conditions:
12+
13+The above copyright notice and this permission notice shall be included in all
14+copies or substantial portions of the Software.
15+
16+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+SOFTWARE.
+9, -0
 1@@ -0,0 +1,9 @@
 2+# Uxn
 3+
 4+An assembler and emulator for the [Uxn stack-machine](https://wiki.xxiivv.com/site/uxn.html), written in ANSI C. 
 5+
 6+More docs coming soon.
 7+
 8+## Contributing
 9+
10+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, -0
 1@@ -0,0 +1,13 @@
 2+#!/bin/sh -e
 3+
 4+clang-format -i uxn11.c
 5+
 6+echo "Cleaning.."
 7+rm -f ./bin/*
 8+
 9+echo "Building.."
10+mkdir -p bin
11+gcc uxn.c devices/ppu.c uxn11.c -o bin/uxn11 -lX11
12+
13+echo "Running.."
14+bin/uxn11 etc/screen.rom
+97, -0
 1@@ -0,0 +1,97 @@
 2+#include "../uxn.h"
 3+#include "apu.h"
 4+
 5+/*
 6+Copyright (c) 2021 Devine Lu Linvega
 7+Copyright (c) 2021 Andrew Alderwick
 8+
 9+Permission to use, copy, modify, and distribute this software for any
10+purpose with or without fee is hereby granted, provided that the above
11+copyright notice and this permission notice appear in all copies.
12+
13+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
14+WITH REGARD TO THIS SOFTWARE.
15+*/
16+
17+#define NOTE_PERIOD (SAMPLE_FREQUENCY * 0x4000 / 11025)
18+#define ADSR_STEP (SAMPLE_FREQUENCY / 0xf)
19+
20+/* clang-format off */
21+
22+static Uint32 advances[12] = {
23+	0x80000, 0x879c8, 0x8facd, 0x9837f, 0xa1451, 0xaadc1,
24+	0xb504f, 0xbfc88, 0xcb2ff, 0xd7450, 0xe411f, 0xf1a1c
25+};
26+
27+/* clang-format on */
28+
29+static Sint32
30+envelope(Apu *c, Uint32 age)
31+{
32+	if(!c->r) return 0x0888;
33+	if(age < c->a) return 0x0888 * age / c->a;
34+	if(age < c->d) return 0x0444 * (2 * c->d - c->a - age) / (c->d - c->a);
35+	if(age < c->s) return 0x0444;
36+	if(age < c->r) return 0x0444 * (c->r - age) / (c->r - c->s);
37+	c->advance = 0;
38+	return 0x0000;
39+}
40+
41+int
42+apu_render(Apu *c, Sint16 *sample, Sint16 *end)
43+{
44+	Sint32 s;
45+	if(!c->advance || !c->period) return 0;
46+	while(sample < end) {
47+		c->count += c->advance;
48+		c->i += c->count / c->period;
49+		c->count %= c->period;
50+		if(c->i >= c->len) {
51+			if(!c->repeat) {
52+				c->advance = 0;
53+				break;
54+			}
55+			c->i %= c->len;
56+		}
57+		s = (Sint8)(c->addr[c->i] + 0x80) * envelope(c, c->age++);
58+		*sample++ += s * c->volume[0] / 0x180;
59+		*sample++ += s * c->volume[1] / 0x180;
60+	}
61+	if(!c->advance) apu_finished_handler(c);
62+	return 1;
63+}
64+
65+void
66+apu_start(Apu *c, Uint16 adsr, Uint8 pitch)
67+{
68+	if(pitch < 108 && c->len)
69+		c->advance = advances[pitch % 12] >> (8 - pitch / 12);
70+	else {
71+		c->advance = 0;
72+		return;
73+	}
74+	c->a = ADSR_STEP * (adsr >> 12);
75+	c->d = ADSR_STEP * (adsr >> 8 & 0xf) + c->a;
76+	c->s = ADSR_STEP * (adsr >> 4 & 0xf) + c->d;
77+	c->r = ADSR_STEP * (adsr >> 0 & 0xf) + c->s;
78+	c->age = 0;
79+	c->i = 0;
80+	if(c->len <= 0x100) /* single cycle mode */
81+		c->period = NOTE_PERIOD * 337 / 2 / c->len;
82+	else /* sample repeat mode */
83+		c->period = NOTE_PERIOD;
84+}
85+
86+Uint8
87+apu_get_vu(Apu *c)
88+{
89+	int i;
90+	Sint32 sum[2] = {0, 0};
91+	if(!c->advance || !c->period) return 0;
92+	for(i = 0; i < 2; ++i) {
93+		if(!c->volume[i]) continue;
94+		sum[i] = 1 + envelope(c, c->age) * c->volume[i] / 0x800;
95+		if(sum[i] > 0xf) sum[i] = 0xf;
96+	}
97+	return (sum[0] << 4) | sum[1];
98+}
+28, -0
 1@@ -0,0 +1,28 @@
 2+/*
 3+Copyright (c) 2021 Devine Lu Linvega
 4+Copyright (c) 2021 Andrew Alderwick
 5+
 6+Permission to use, copy, modify, and distribute this software for any
 7+purpose with or without fee is hereby granted, provided that the above
 8+copyright notice and this permission notice appear in all copies.
 9+
10+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11+WITH REGARD TO THIS SOFTWARE.
12+*/
13+
14+typedef signed int Sint32;
15+
16+#define SAMPLE_FREQUENCY 44100
17+
18+typedef struct {
19+	Uint8 *addr;
20+	Uint32 count, advance, period, age, a, d, s, r;
21+	Uint16 i, len;
22+	Sint8 volume[2];
23+	Uint8 pitch, repeat;
24+} Apu;
25+
26+int apu_render(Apu *c, Sint16 *sample, Sint16 *end);
27+void apu_start(Apu *c, Uint16 adsr, Uint8 pitch);
28+Uint8 apu_get_vu(Apu *c);
29+void apu_finished_handler(Apu *c);
+139, -0
  1@@ -0,0 +1,139 @@
  2+#include "../uxn.h"
  3+#include "file.h"
  4+
  5+/*
  6+Copyright (c) 2021 Devine Lu Linvega
  7+Copyright (c) 2021 Andrew Alderwick
  8+
  9+Permission to use, copy, modify, and distribute this software for any
 10+purpose with or without fee is hereby granted, provided that the above
 11+copyright notice and this permission notice appear in all copies.
 12+
 13+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 14+WITH REGARD TO THIS SOFTWARE.
 15+*/
 16+
 17+#define _POSIX_C_SOURCE 200809L
 18+
 19+#include <stdio.h>
 20+#include <dirent.h>
 21+#include <string.h>
 22+#include <sys/stat.h>
 23+#include <unistd.h>
 24+
 25+static FILE *f;
 26+static DIR *d;
 27+static char *current_filename = "";
 28+static enum { IDLE,
 29+	FILE_READ,
 30+	FILE_WRITE,
 31+	DIR_READ } state;
 32+static struct dirent *de;
 33+
 34+static void
 35+reset(void)
 36+{
 37+	if(f != NULL) {
 38+		fclose(f);
 39+		f = NULL;
 40+	}
 41+	if(d != NULL) {
 42+		closedir(d);
 43+		d = NULL;
 44+	}
 45+	de = NULL;
 46+	state = IDLE;
 47+}
 48+
 49+static Uint16
 50+get_entry(char *p, Uint16 len, const char *pathname, const char *basename, int fail_nonzero)
 51+{
 52+	struct stat st;
 53+	if(len < strlen(basename) + 7)
 54+		return 0;
 55+	if(stat(pathname, &st))
 56+		return fail_nonzero ? snprintf(p, len, "!!!! %s\n", basename) : 0;
 57+	else if(S_ISDIR(st.st_mode))
 58+		return snprintf(p, len, "---- %s\n", basename);
 59+	else if(st.st_size < 0x10000)
 60+		return snprintf(p, len, "%04x %s\n", (Uint16)st.st_size, basename);
 61+	else
 62+		return snprintf(p, len, "???? %s\n", basename);
 63+}
 64+
 65+static Uint16
 66+file_read_dir(char *dest, Uint16 len)
 67+{
 68+	static char pathname[4096];
 69+	char *p = dest;
 70+	if(de == NULL) de = readdir(d);
 71+	for(; de != NULL; de = readdir(d)) {
 72+		Uint16 n;
 73+		if(de->d_name[0] == '.' && de->d_name[1] == '\0')
 74+			continue;
 75+		snprintf(pathname, sizeof(pathname), "%s/%s", current_filename, de->d_name);
 76+		n = get_entry(p, len, pathname, de->d_name, 1);
 77+		if(!n) break;
 78+		p += n;
 79+		len -= n;
 80+	}
 81+	return p - dest;
 82+}
 83+
 84+Uint16
 85+file_init(void *filename)
 86+{
 87+	reset();
 88+	current_filename = filename;
 89+	return 0;
 90+}
 91+
 92+Uint16
 93+file_read(void *dest, Uint16 len)
 94+{
 95+	if(state != FILE_READ && state != DIR_READ) {
 96+		reset();
 97+		if((d = opendir(current_filename)) != NULL)
 98+			state = DIR_READ;
 99+		else if((f = fopen(current_filename, "rb")) != NULL)
100+			state = FILE_READ;
101+	}
102+	if(state == FILE_READ)
103+		return fread(dest, 1, len, f);
104+	if(state == DIR_READ)
105+		return file_read_dir(dest, len);
106+	return 0;
107+}
108+
109+Uint16
110+file_write(void *src, Uint16 len, Uint8 flags)
111+{
112+	Uint16 ret = 0;
113+	if(state != FILE_WRITE) {
114+		reset();
115+		if((f = fopen(current_filename, (flags & 0x01) ? "ab" : "wb")) != NULL)
116+			state = FILE_WRITE;
117+	}
118+	if(state == FILE_WRITE) {
119+		if((ret = fwrite(src, 1, len, f)) > 0 && fflush(f) != 0)
120+			ret = 0;
121+	}
122+	return ret;
123+}
124+
125+Uint16
126+file_stat(void *dest, Uint16 len)
127+{
128+	char *basename = strrchr(current_filename, '/');
129+	if(basename != NULL)
130+		++basename;
131+	else
132+		basename = current_filename;
133+	return get_entry(dest, len, current_filename, basename, 0);
134+}
135+
136+Uint16
137+file_delete(void)
138+{
139+	return unlink(current_filename);
140+}
+17, -0
 1@@ -0,0 +1,17 @@
 2+/*
 3+Copyright (c) 2021 Devine Lu Linvega
 4+Copyright (c) 2021 Andrew Alderwick
 5+
 6+Permission to use, copy, modify, and distribute this software for any
 7+purpose with or without fee is hereby granted, provided that the above
 8+copyright notice and this permission notice appear in all copies.
 9+
10+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11+WITH REGARD TO THIS SOFTWARE.
12+*/
13+
14+Uint16 file_init(void *filename);
15+Uint16 file_read(void *dest, Uint16 len);
16+Uint16 file_write(void *src, Uint16 len, Uint8 flags);
17+Uint16 file_stat(void *dest, Uint16 len);
18+Uint16 file_delete(void);
+150, -0
  1@@ -0,0 +1,150 @@
  2+#include "ppu.h"
  3+
  4+/*
  5+Copyright (c) 2021 Devine Lu Linvega
  6+Copyright (c) 2021 Andrew Alderwick
  7+
  8+Permission to use, copy, modify, and distribute this software for any
  9+purpose with or without fee is hereby granted, provided that the above
 10+copyright notice and this permission notice appear in all copies.
 11+
 12+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 13+WITH REGARD TO THIS SOFTWARE.
 14+*/
 15+
 16+static Uint8 blending[5][16] = {
 17+	{0, 0, 0, 0, 1, 0, 1, 1, 2, 2, 0, 2, 3, 3, 3, 0},
 18+	{0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3},
 19+	{1, 2, 3, 1, 1, 2, 3, 1, 1, 2, 3, 1, 1, 2, 3, 1},
 20+	{2, 3, 1, 2, 2, 3, 1, 2, 2, 3, 1, 2, 2, 3, 1, 2},
 21+	{1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0}};
 22+
 23+static Uint8 font[][8] = {
 24+	{0x00, 0x7c, 0x82, 0x82, 0x82, 0x82, 0x82, 0x7c},
 25+	{0x00, 0x30, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10},
 26+	{0x00, 0x7c, 0x82, 0x02, 0x7c, 0x80, 0x80, 0xfe},
 27+	{0x00, 0x7c, 0x82, 0x02, 0x1c, 0x02, 0x82, 0x7c},
 28+	{0x00, 0x0c, 0x14, 0x24, 0x44, 0x84, 0xfe, 0x04},
 29+	{0x00, 0xfe, 0x80, 0x80, 0x7c, 0x02, 0x82, 0x7c},
 30+	{0x00, 0x7c, 0x82, 0x80, 0xfc, 0x82, 0x82, 0x7c},
 31+	{0x00, 0x7c, 0x82, 0x02, 0x1e, 0x02, 0x02, 0x02},
 32+	{0x00, 0x7c, 0x82, 0x82, 0x7c, 0x82, 0x82, 0x7c},
 33+	{0x00, 0x7c, 0x82, 0x82, 0x7e, 0x02, 0x82, 0x7c},
 34+	{0x00, 0x7c, 0x82, 0x02, 0x7e, 0x82, 0x82, 0x7e},
 35+	{0x00, 0xfc, 0x82, 0x82, 0xfc, 0x82, 0x82, 0xfc},
 36+	{0x00, 0x7c, 0x82, 0x80, 0x80, 0x80, 0x82, 0x7c},
 37+	{0x00, 0xfc, 0x82, 0x82, 0x82, 0x82, 0x82, 0xfc},
 38+	{0x00, 0x7c, 0x82, 0x80, 0xf0, 0x80, 0x82, 0x7c},
 39+	{0x00, 0x7c, 0x82, 0x80, 0xf0, 0x80, 0x80, 0x80}};
 40+
 41+void
 42+ppu_palette(Ppu *p, Uint8 *addr)
 43+{
 44+	int i, shift;
 45+	for(i = 0, shift = 4; i < 4; ++i, shift ^= 4) {
 46+		Uint8
 47+			r = (addr[0 + i / 2] >> shift) & 0x0f,
 48+			g = (addr[2 + i / 2] >> shift) & 0x0f,
 49+			b = (addr[4 + i / 2] >> shift) & 0x0f;
 50+		p->palette[i] = 0x0f000000 | r << 16 | g << 8 | b;
 51+		p->palette[i] |= p->palette[i] << 4;
 52+	}
 53+	p->fg.changed = p->bg.changed = 1;
 54+}
 55+
 56+void
 57+ppu_resize(Ppu *p, Uint16 width, Uint16 height)
 58+{
 59+	Uint8
 60+		*bg = realloc(p->bg.pixels, width * height),
 61+		*fg = realloc(p->fg.pixels, width * height);
 62+	if(bg) p->bg.pixels = bg;
 63+	if(fg) p->fg.pixels = fg;
 64+	if(bg && fg) {
 65+		p->width = width;
 66+		p->height = height;
 67+		ppu_clear(p, &p->bg);
 68+		ppu_clear(p, &p->fg);
 69+	}
 70+}
 71+
 72+void
 73+ppu_clear(Ppu *p, Layer *layer)
 74+{
 75+	Uint32 i, size = p->width * p->height;
 76+	for(i = 0; i < size; ++i)
 77+		layer->pixels[i] = 0x00;
 78+	layer->changed = 1;
 79+}
 80+
 81+#pragma weak ppu_redraw
 82+void
 83+ppu_redraw(Ppu *p, Uint32 *screen)
 84+{
 85+	Uint32 i, size = p->width * p->height, palette[16];
 86+	for(i = 0; i < 16; ++i)
 87+		palette[i] = p->palette[(i >> 2) ? (i >> 2) : (i & 3)];
 88+	for(i = 0; i < size; ++i)
 89+		screen[i] = palette[p->fg.pixels[i] << 2 | p->bg.pixels[i]];
 90+	p->fg.changed = p->bg.changed = 0;
 91+}
 92+
 93+void
 94+ppu_write(Ppu *p, Layer *layer, Uint16 x, Uint16 y, Uint8 color)
 95+{
 96+	if(x < p->width && y < p->height) {
 97+		Uint32 i = x + y * p->width;
 98+		Uint8 prev = layer->pixels[i];
 99+		if(color != prev) {
100+			layer->pixels[i] = color;
101+			layer->changed = 1;
102+		}
103+	}
104+}
105+
106+void
107+ppu_blit(Ppu *p, Layer *layer, Uint16 x, Uint16 y, Uint8 *sprite, Uint8 color, Uint8 flipx, Uint8 flipy, Uint8 twobpp)
108+{
109+	int v, h, opaque = blending[4][color];
110+	for(v = 0; v < 8; ++v) {
111+		Uint16 c = sprite[v] | (twobpp ? sprite[v + 8] : 0) << 8;
112+		for(h = 7; h >= 0; --h, c >>= 1) {
113+			Uint8 ch = (c & 1) | ((c >> 7) & 2);
114+			if(opaque || ch)
115+				ppu_write(p,
116+					layer,
117+					x + (flipx ? 7 - h : h),
118+					y + (flipy ? 7 - v : v),
119+					blending[ch][color]);
120+		}
121+	}
122+}
123+
124+void
125+ppu_debug(Ppu *p, Uint8 *stack, Uint8 wptr, Uint8 rptr, Uint8 *memory)
126+{
127+	Uint8 i, x, y, b;
128+	for(i = 0; i < 0x20; ++i) {
129+		x = ((i % 8) * 3 + 1) * 8, y = (i / 8 + 1) * 8, b = stack[i];
130+		/* working stack */
131+		ppu_blit(p, &p->fg, x, y, font[(b >> 4) & 0xf], 1 + (wptr == i) * 0x7, 0, 0, 0);
132+		ppu_blit(p, &p->fg, x + 8, y, font[b & 0xf], 1 + (wptr == i) * 0x7, 0, 0, 0);
133+		y = 0x28 + (i / 8 + 1) * 8;
134+		b = memory[i];
135+		/* return stack */
136+		ppu_blit(p, &p->fg, x, y, font[(b >> 4) & 0xf], 3, 0, 0, 0);
137+		ppu_blit(p, &p->fg, x + 8, y, font[b & 0xf], 3, 0, 0, 0);
138+	}
139+	/* return pointer */
140+	ppu_blit(p, &p->fg, 0x8, y + 0x10, font[(rptr >> 4) & 0xf], 0x2, 0, 0, 0);
141+	ppu_blit(p, &p->fg, 0x10, y + 0x10, font[rptr & 0xf], 0x2, 0, 0, 0);
142+	/* guides */
143+	for(x = 0; x < 0x10; ++x) {
144+		ppu_write(p, &p->fg, x, p->height / 2, 2);
145+		ppu_write(p, &p->fg, p->width - x, p->height / 2, 2);
146+		ppu_write(p, &p->fg, p->width / 2, p->height - x, 2);
147+		ppu_write(p, &p->fg, p->width / 2, x, 2);
148+		ppu_write(p, &p->fg, p->width / 2 - 0x10 / 2 + x, p->height / 2, 2);
149+		ppu_write(p, &p->fg, p->width / 2, p->height / 2 - 0x10 / 2 + x, 2);
150+	}
151+}
+37, -0
 1@@ -0,0 +1,37 @@
 2+#include <stdlib.h>
 3+
 4+/*
 5+Copyright (c) 2021 Devine Lu Linvega
 6+Copyright (c) 2021 Andrew Alderwick
 7+
 8+Permission to use, copy, modify, and distribute this software for any
 9+purpose with or without fee is hereby granted, provided that the above
10+copyright notice and this permission notice appear in all copies.
11+
12+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
13+WITH REGARD TO THIS SOFTWARE.
14+*/
15+
16+typedef unsigned char Uint8;
17+typedef unsigned short Uint16;
18+typedef unsigned int Uint32;
19+
20+typedef struct Layer {
21+	Uint8 *pixels;
22+	Uint8 changed;
23+} Layer;
24+
25+typedef struct Ppu {
26+	Uint32 palette[4];
27+	Uint16 width, height;
28+	Layer fg, bg;
29+} Ppu;
30+
31+void ppu_palette(Ppu *p, Uint8 *addr);
32+void ppu_resize(Ppu *p, Uint16 width, Uint16 height);
33+void ppu_clear(Ppu *p, Layer *layer);
34+void ppu_redraw(Ppu *p, Uint32 *screen);
35+
36+void ppu_write(Ppu *p, Layer *layer, Uint16 x, Uint16 y, Uint8 color);
37+void ppu_blit(Ppu *p, Layer *layer, Uint16 x, Uint16 y, Uint8 *sprite, Uint8 color, Uint8 flipx, Uint8 flipy, Uint8 twobpp);
38+void ppu_debug(Ppu *p, Uint8 *stack, Uint8 wptr, Uint8 rptr, Uint8 *memory);
+37, -0
 1@@ -0,0 +1,37 @@
 2+#ifdef __aarch64__
 3+#include <arm_neon.h>
 4+#include "ppu.h"
 5+
 6+void
 7+ppu_redraw(Ppu *p, Uint32 *screen)
 8+{
 9+	uint8x16x4_t pal = vld4q_u8((Uint8*)p->palette);
10+	Uint8 *fg = p->fg.pixels;
11+	Uint8 *bg = p->bg.pixels;
12+	int i;
13+
14+	p->fg.changed = p->bg.changed = 0;
15+
16+#ifdef __has_builtin
17+#if __has_builtin(__builtin_assume)
18+	__builtin_assume(p->width > 0 && p->height > 0);
19+#endif
20+#endif
21+
22+	for(i = 0; i < (p->width * p->height & ~15); i += 16, fg += 16, bg += 16, screen += 16) {
23+		uint8x16_t fg8 = vld1q_u8(fg);
24+		uint8x16_t bg8 = vld1q_u8(bg);
25+		uint8x16_t px8 = vbslq_u8(vceqzq_u8(fg8), bg8, fg8);
26+		uint8x16x4_t px = {
27+			vqtbl1q_u8(pal.val[0], px8),
28+			vqtbl1q_u8(pal.val[1], px8),
29+			vqtbl1q_u8(pal.val[2], px8),
30+			vdupq_n_u8(0xff),
31+		};
32+		vst4q_u8((uint8_t*)screen, px);
33+	}
34+
35+	for(; i < p->width * p->height; i++)
36+		screen[i] = p->palette[*fg ? *fg : *bg];
37+}
38+#endif
+0, -0
A uxn.c
+158, -0
  1@@ -0,0 +1,158 @@
  2+#include "uxn.h"
  3+
  4+/*
  5+Copyright (u) 2021 Devine Lu Linvega
  6+Copyright (u) 2021 Andrew Alderwick
  7+
  8+Permission to use, copy, modify, and distribute this software for any
  9+purpose with or without fee is hereby granted, provided that the above
 10+copyright notice and this permission notice appear in all copies.
 11+
 12+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 13+WITH REGARD TO THIS SOFTWARE.
 14+*/
 15+
 16+#define MODE_SHORT 0x20
 17+#define MODE_RETURN 0x40
 18+#define MODE_KEEP 0x80
 19+
 20+#pragma mark - Operations
 21+
 22+/* clang-format off */
 23+
 24+/* Utilities */
 25+static void   (*push)(Stack *s, Uint16 a);
 26+static Uint16 (*pop8)(Stack *s);
 27+static Uint16 (*pop)(Stack *s);
 28+static void   (*poke)(Uint8 *m, Uint16 a, Uint16 b);
 29+static Uint16 (*peek)(Uint8 *m, Uint16 a);
 30+static void   (*devw)(Device *d, Uint8 a, Uint16 b);
 31+static Uint16 (*devr)(Device *d, Uint8 a);
 32+static void   (*warp)(Uxn *u, Uint16 a);
 33+static void   (*pull)(Uxn *u);
 34+/* byte mode */
 35+static void   push8(Stack *s, Uint16 a) { if(s->ptr == 0xff) { s->error = 2; return; } s->dat[s->ptr++] = a; }
 36+static Uint16 pop8k(Stack *s) { if(s->kptr == 0) { s->error = 1; return 0; } return s->dat[--s->kptr]; }
 37+static Uint16 pop8d(Stack *s) { if(s->ptr == 0) { s->error = 1; return 0; } return s->dat[--s->ptr]; }
 38+static void   poke8(Uint8 *m, Uint16 a, Uint16 b) { m[a] = b; }
 39+static Uint16 peek8(Uint8 *m, Uint16 a) { return m[a]; }
 40+static void   devw8(Device *d, Uint8 a, Uint16 b) { d->dat[a & 0xf] = b; d->deo(d, a & 0x0f); }
 41+static Uint16 devr8(Device *d, Uint8 a) { return d->dei(d, a & 0x0f); }
 42+static void   warp8(Uxn *u, Uint16 a){ u->ram.ptr += (Sint8)a; }
 43+static void   pull8(Uxn *u){ push8(u->src, peek8(u->ram.dat, u->ram.ptr++)); }
 44+/* short mode */
 45+static void   push16(Stack *s, Uint16 a) { push8(s, a >> 8); push8(s, a); }
 46+static Uint16 pop16(Stack *s) { Uint8 a = pop8(s), b = pop8(s); return a + (b << 8); }
 47+	   void   poke16(Uint8 *m, Uint16 a, Uint16 b) { poke8(m, a, b >> 8); poke8(m, a + 1, b); }
 48+	   Uint16 peek16(Uint8 *m, Uint16 a) { return (peek8(m, a) << 8) + peek8(m, a + 1); }
 49+static void   devw16(Device *d, Uint8 a, Uint16 b) { devw8(d, a, b >> 8); devw8(d, a + 1, b); }
 50+static Uint16 devr16(Device *d, Uint8 a) { return (devr8(d, a) << 8) + devr8(d, a + 1); }
 51+static void   warp16(Uxn *u, Uint16 a){ u->ram.ptr = a; }
 52+static void   pull16(Uxn *u){ push16(u->src, peek16(u->ram.dat, u->ram.ptr++)); u->ram.ptr++; }
 53+
 54+#pragma mark - Core
 55+
 56+int
 57+uxn_eval(Uxn *u, Uint16 vec)
 58+{
 59+	Uint8 instr;
 60+	Uint16 a,b,c;
 61+	if(!vec || u->dev[0].dat[0xf])
 62+		return 0;
 63+	u->ram.ptr = vec;
 64+	if(u->wst.ptr > 0xf8) u->wst.ptr = 0xf8;
 65+	while((instr = u->ram.dat[u->ram.ptr++])) {
 66+		/* Return Mode */
 67+		if(instr & MODE_RETURN) {
 68+			u->src = &u->rst; 
 69+			u->dst = &u->wst;
 70+		} else {
 71+			u->src = &u->wst; 
 72+			u->dst = &u->rst;
 73+		}
 74+		/* Keep Mode */
 75+		if(instr & MODE_KEEP) {
 76+			pop8 = pop8k;
 77+			u->src->kptr = u->src->ptr;
 78+		} else {
 79+			pop8 = pop8d;
 80+		}
 81+		/* Short Mode */
 82+		if(instr & MODE_SHORT) {
 83+			push = push16; pop = pop16;
 84+			poke = poke16; peek = peek16;
 85+			devw = devw16; devr = devr16;
 86+			warp = warp16; pull = pull16;
 87+		} else {
 88+			push = push8; pop = pop8;
 89+			poke = poke8; peek = peek8;
 90+			devw = devw8; devr = devr8;
 91+			warp = warp8; pull = pull8;
 92+		}
 93+		switch(instr & 0x1f){
 94+			/* Stack */
 95+			case 0x00: /* LIT */ pull(u); break;
 96+			case 0x01: /* INC */ a = pop(u->src); push(u->src, a + 1); break;
 97+			case 0x02: /* POP */ pop(u->src); break;
 98+			case 0x03: /* DUP */ a = pop(u->src); push(u->src, a); push(u->src, a); break;
 99+			case 0x04: /* NIP */ a = pop(u->src); pop(u->src); push(u->src, a); break;
100+			case 0x05: /* SWP */ a = pop(u->src), b = pop(u->src); push(u->src, a); push(u->src, b); break;
101+			case 0x06: /* OVR */ a = pop(u->src), b = pop(u->src); push(u->src, b); push(u->src, a); push(u->src, b); break;
102+			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;
103+			/* Logic */
104+			case 0x08: /* EQU */ a = pop(u->src), b = pop(u->src); push8(u->src, b == a); break;
105+			case 0x09: /* NEQ */ a = pop(u->src), b = pop(u->src); push8(u->src, b != a); break;
106+			case 0x0a: /* GTH */ a = pop(u->src), b = pop(u->src); push8(u->src, b > a); break;
107+			case 0x0b: /* LTH */ a = pop(u->src), b = pop(u->src); push8(u->src, b < a); break;
108+			case 0x0c: /* JMP */ a = pop(u->src); warp(u, a); break;
109+			case 0x0d: /* JCN */ a = pop(u->src); if(pop8(u->src)) warp(u, a); break;
110+			case 0x0e: /* JSR */ a = pop(u->src); push16(u->dst, u->ram.ptr); warp(u, a); break;
111+			case 0x0f: /* STH */ a = pop(u->src); push(u->dst, a); break;
112+			/* Memory */
113+			case 0x10: /* LDZ */ a = pop8(u->src); push(u->src, peek(u->ram.dat, a)); break;
114+			case 0x11: /* STZ */ a = pop8(u->src); b = pop(u->src); poke(u->ram.dat, a, b); break;
115+			case 0x12: /* LDR */ a = pop8(u->src); push(u->src, peek(u->ram.dat, u->ram.ptr + (Sint8)a)); break;
116+			case 0x13: /* STR */ a = pop8(u->src); b = pop(u->src); poke(u->ram.dat, u->ram.ptr + (Sint8)a, b); break;
117+			case 0x14: /* LDA */ a = pop16(u->src); push(u->src, peek(u->ram.dat, a)); break;
118+			case 0x15: /* STA */ a = pop16(u->src); b = pop(u->src); poke(u->ram.dat, a, b); break;
119+			case 0x16: /* DEI */ a = pop8(u->src); push(u->src, devr(&u->dev[a >> 4], a)); break;
120+			case 0x17: /* DEO */ a = pop8(u->src); b = pop(u->src); devw(&u->dev[a >> 4], a, b); break;
121+			/* Arithmetic */
122+			case 0x18: /* ADD */ a = pop(u->src), b = pop(u->src); push(u->src, b + a); break;
123+			case 0x19: /* SUB */ a = pop(u->src), b = pop(u->src); push(u->src, b - a); break;
124+			case 0x1a: /* MUL */ a = pop(u->src), b = pop(u->src); push(u->src, (Uint32)b * a); break;
125+			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;
126+			case 0x1c: /* AND */ a = pop(u->src), b = pop(u->src); push(u->src, b & a); break;
127+			case 0x1d: /* ORA */ a = pop(u->src), b = pop(u->src); push(u->src, b | a); break;
128+			case 0x1e: /* EOR */ a = pop(u->src), b = pop(u->src); push(u->src, b ^ a); break;
129+			case 0x1f: /* SFT */ a = pop8(u->src), b = pop(u->src); push(u->src, b >> (a & 0x0f) << ((a & 0xf0) >> 4)); break;
130+		}
131+		if(u->wst.error) return uxn_halt(u, u->wst.error, "Working-stack", instr);
132+		if(u->rst.error) return uxn_halt(u, u->rst.error, "Return-stack", instr);
133+	}
134+	return 1;
135+}
136+
137+/* clang-format on */
138+
139+int
140+uxn_boot(Uxn *u)
141+{
142+	Uint32 i;
143+	char *cptr = (char *)u;
144+	for(i = 0; i < sizeof(*u); ++i)
145+		cptr[i] = 0x00;
146+	return 1;
147+}
148+
149+Device *
150+uxn_port(Uxn *u, Uint8 id, Uint8 (*deifn)(Device *d, Uint8 port), void (*deofn)(Device *d, Uint8 port))
151+{
152+	Device *d = &u->dev[id];
153+	d->addr = id * 0x10;
154+	d->u = u;
155+	d->mem = u->ram.dat;
156+	d->dei = deifn;
157+	d->deo = deofn;
158+	return d;
159+}
A uxn.h
+50, -0
 1@@ -0,0 +1,50 @@
 2+/*
 3+Copyright (c) 2021 Devine Lu Linvega
 4+
 5+Permission to use, copy, modify, and distribute this software for any
 6+purpose with or without fee is hereby granted, provided that the above
 7+copyright notice and this permission notice appear in all copies.
 8+
 9+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10+WITH REGARD TO THIS SOFTWARE.
11+*/
12+
13+typedef unsigned char Uint8;
14+typedef signed char Sint8;
15+typedef unsigned short Uint16;
16+typedef signed short Sint16;
17+typedef unsigned int Uint32;
18+
19+#define PAGE_PROGRAM 0x0100
20+
21+typedef struct {
22+	Uint8 ptr, kptr, error;
23+	Uint8 dat[256];
24+} Stack;
25+
26+typedef struct {
27+	Uint16 ptr;
28+	Uint8 dat[65536];
29+} Memory;
30+
31+typedef struct Device {
32+	struct Uxn *u;
33+	Uint8 addr, dat[16], *mem;
34+	Uint16 vector;
35+	Uint8 (*dei)(struct Device *d, Uint8);
36+	void (*deo)(struct Device *d, Uint8);
37+} Device;
38+
39+typedef struct Uxn {
40+	Stack wst, rst, *src, *dst;
41+	Memory ram;
42+	Device dev[16];
43+} Uxn;
44+
45+void poke16(Uint8 *m, Uint16 a, Uint16 b);
46+Uint16 peek16(Uint8 *m, Uint16 a);
47+
48+int uxn_boot(Uxn *c);
49+int uxn_eval(Uxn *u, Uint16 vec);
50+int uxn_halt(Uxn *u, Uint8 error, char *name, int id);
51+Device *uxn_port(Uxn *u, Uint8 id, Uint8 (*deifn)(Device *, Uint8), void (*deofn)(Device *, Uint8));
+266, -0
  1@@ -0,0 +1,266 @@
  2+#include <stdio.h>
  3+#include <time.h>
  4+#include <unistd.h>
  5+
  6+#include <X11/Xlib.h>
  7+#include <stdlib.h>
  8+#include <string.h>
  9+
 10+#define WIDTH 64 * 8
 11+#define HEIGHT 40 * 8
 12+#define FIXED_SIZE 0
 13+
 14+#include "uxn.h"
 15+
 16+#include "devices/ppu.h"
 17+static Ppu ppu;
 18+
 19+static Device *devsystem, *devconsole, *devscreen;
 20+static Uint32 *ppu_screen;
 21+
 22+static int
 23+error(char *msg, const char *err)
 24+{
 25+	fprintf(stderr, "Error %s: %s\n", msg, err);
 26+	return 0;
 27+}
 28+
 29+static int
 30+set_size(Uint16 width, Uint16 height, int is_resize)
 31+{
 32+	ppu_resize(&ppu, width, height);
 33+	if(!(ppu_screen =
 34+			   realloc(ppu_screen, ppu.width * ppu.height * sizeof(Uint32))))
 35+		return error("ppu_screen", "Memory failure");
 36+	memset(ppu_screen, 0, ppu.width * ppu.height * sizeof(Uint32));
 37+	printf("%d x %d(%d)\n", width, height, is_resize);
 38+	return 1;
 39+}
 40+
 41+static Uint8
 42+system_dei(Device *d, Uint8 port)
 43+{
 44+	switch(port) {
 45+	case 0x2:
 46+		return d->u->wst.ptr;
 47+	case 0x3:
 48+		return d->u->rst.ptr;
 49+	default:
 50+		return d->dat[port];
 51+	}
 52+}
 53+
 54+static void
 55+system_deo(Device *d, Uint8 port)
 56+{
 57+	switch(port) {
 58+	case 0x2:
 59+		d->u->wst.ptr = d->dat[port];
 60+		break;
 61+	case 0x3:
 62+		d->u->rst.ptr = d->dat[port];
 63+		break;
 64+	}
 65+	if(port > 0x7 && port < 0xe)
 66+		ppu_palette(&ppu, &d->dat[0x8]);
 67+}
 68+
 69+static void
 70+console_deo(Device *d, Uint8 port)
 71+{
 72+	if(port == 0x1)
 73+		d->vector = peek16(d->dat, 0x0);
 74+	if(port > 0x7)
 75+		write(port - 0x7, (char *)&d->dat[port], 1);
 76+}
 77+
 78+static Uint8
 79+screen_dei(Device *d, Uint8 port)
 80+{
 81+	switch(port) {
 82+	case 0x2:
 83+		return ppu.width >> 8;
 84+	case 0x3:
 85+		return ppu.width;
 86+	case 0x4:
 87+		return ppu.height >> 8;
 88+	case 0x5:
 89+		return ppu.height;
 90+	default:
 91+		return d->dat[port];
 92+	}
 93+}
 94+
 95+static void
 96+screen_deo(Device *d, Uint8 port)
 97+{
 98+	switch(port) {
 99+	case 0x1:
100+		d->vector = peek16(d->dat, 0x0);
101+		break;
102+	case 0x5:
103+		if(!FIXED_SIZE)
104+			set_size(peek16(d->dat, 0x2), peek16(d->dat, 0x4), 1);
105+		break;
106+	case 0xe: {
107+		Uint16 x = peek16(d->dat, 0x8);
108+		Uint16 y = peek16(d->dat, 0xa);
109+		Uint8 layer = d->dat[0xe] & 0x40;
110+		ppu_write(&ppu, layer ? &ppu.fg : &ppu.bg, x, y, d->dat[0xe] & 0x3);
111+		if(d->dat[0x6] & 0x01)
112+			poke16(d->dat, 0x8, x + 1); /* auto x+1 */
113+		if(d->dat[0x6] & 0x02)
114+			poke16(d->dat, 0xa, y + 1); /* auto y+1 */
115+		break;
116+	}
117+	case 0xf: {
118+		Uint16 x = peek16(d->dat, 0x8);
119+		Uint16 y = peek16(d->dat, 0xa);
120+		Layer *layer = (d->dat[0xf] & 0x40) ? &ppu.fg : &ppu.bg;
121+		Uint8 *addr = &d->mem[peek16(d->dat, 0xc)];
122+		Uint8 twobpp = !!(d->dat[0xf] & 0x80);
123+		ppu_blit(&ppu, layer, x, y, addr, d->dat[0xf] & 0xf, d->dat[0xf] & 0x10, d->dat[0xf] & 0x20, twobpp);
124+		if(d->dat[0x6] & 0x04)
125+			poke16(d->dat, 0xc, peek16(d->dat, 0xc) + 8 + twobpp * 8); /* auto addr+8 / auto addr+16 */
126+		if(d->dat[0x6] & 0x01)
127+			poke16(d->dat, 0x8, x + 8); /* auto x+8 */
128+		if(d->dat[0x6] & 0x02)
129+			poke16(d->dat, 0xa, y + 8); /* auto y+8 */
130+		break;
131+	}
132+	}
133+}
134+
135+static Uint8
136+nil_dei(Device *d, Uint8 port)
137+{
138+	return d->dat[port];
139+}
140+
141+static void
142+nil_deo(Device *d, Uint8 port)
143+{
144+	if(port == 0x1)
145+		d->vector = peek16(d->dat, 0x0);
146+}
147+
148+static const char *errors[] = {"underflow", "overflow", "division by zero"};
149+
150+int
151+uxn_halt(Uxn *u, Uint8 error, char *name, int id)
152+{
153+	fprintf(stderr, "Halted: %s %s#%04x, at 0x%04x\n", name, errors[error - 1], id, u->ram.ptr);
154+	return 0;
155+}
156+
157+static int
158+load(Uxn *u, char *filepath)
159+{
160+	FILE *f;
161+	int r;
162+	if(!(f = fopen(filepath, "rb")))
163+		return 0;
164+	r = fread(u->ram.dat + PAGE_PROGRAM, 1, sizeof(u->ram.dat) - PAGE_PROGRAM, f);
165+	fclose(f);
166+	if(r < 1)
167+		return 0;
168+	fprintf(stderr, "Loaded %s\n", filepath);
169+	return 1;
170+}
171+
172+// ---------------------------
173+
174+void
175+processEvent(Display *display, Window window, XImage *ximage, int width, int height)
176+{
177+	static char *tir = "This is red";
178+	static char *tig = "This is green";
179+	static char *tib = "This is blue";
180+	XEvent ev;
181+	XNextEvent(display, &ev);
182+	switch(ev.type) {
183+	case Expose:
184+		XPutImage(display, window, DefaultGC(display, 0), ximage, 0, 0, 0, 0, width, height);
185+		XSetForeground(display, DefaultGC(display, 0), 0x00ff0000); // red
186+		XDrawString(display, window, DefaultGC(display, 0), 32, 32, tir, strlen(tir));
187+		XDrawString(display, window, DefaultGC(display, 0), 32 + 256, 32, tir, strlen(tir));
188+		XDrawString(display, window, DefaultGC(display, 0), 32 + 256, 32 + 256, tir, strlen(tir));
189+		XDrawString(display, window, DefaultGC(display, 0), 32, 32 + 256, tir, strlen(tir));
190+		XSetForeground(display, DefaultGC(display, 0), 0x0000ff00); // green
191+		XDrawString(display, window, DefaultGC(display, 0), 32, 52, tig, strlen(tig));
192+		XDrawString(display, window, DefaultGC(display, 0), 32 + 256, 52, tig, strlen(tig));
193+		XDrawString(display, window, DefaultGC(display, 0), 32 + 256, 52 + 256, tig, strlen(tig));
194+		XDrawString(display, window, DefaultGC(display, 0), 32, 52 + 256, tig, strlen(tig));
195+		XSetForeground(display, DefaultGC(display, 0), 0x000000ff); // blue
196+		XDrawString(display, window, DefaultGC(display, 0), 32, 72, tib, strlen(tib));
197+		XDrawString(display, window, DefaultGC(display, 0), 32 + 256, 72, tib, strlen(tib));
198+		XDrawString(display, window, DefaultGC(display, 0), 32 + 256, 72 + 256, tib, strlen(tib));
199+		XDrawString(display, window, DefaultGC(display, 0), 32, 72 + 256, tib, strlen(tib));
200+		break;
201+	case ButtonPress:
202+		exit(0);
203+	}
204+}
205+
206+int
207+main(int argc, char **argv)
208+{
209+
210+	Uxn u;
211+	int i, loaded = 0;
212+
213+	if(!uxn_boot(&u))
214+		return error("Boot", "Failed");
215+
216+	/* system   */ devsystem = uxn_port(&u, 0x0, system_dei, system_deo);
217+	/* console  */ devconsole = uxn_port(&u, 0x1, nil_dei, console_deo);
218+	/* screen   */ devscreen = uxn_port(&u, 0x2, screen_dei, screen_deo);
219+	/* empty    */ uxn_port(&u, 0x3, nil_dei, nil_deo);
220+	/* empty    */ uxn_port(&u, 0x4, nil_dei, nil_deo);
221+	/* empty    */ uxn_port(&u, 0x5, nil_dei, nil_deo);
222+	/* empty    */ uxn_port(&u, 0x6, nil_dei, nil_deo);
223+	/* empty    */ uxn_port(&u, 0x7, nil_dei, nil_deo);
224+	/* empty    */ uxn_port(&u, 0x8, nil_dei, nil_deo);
225+	/* empty    */ uxn_port(&u, 0x9, nil_dei, nil_deo);
226+	/* file     */ uxn_port(&u, 0xa, nil_dei, nil_deo);
227+	/* datetime */ uxn_port(&u, 0xb, nil_dei, nil_deo);
228+	/* empty    */ uxn_port(&u, 0xc, nil_dei, nil_deo);
229+	/* empty    */ uxn_port(&u, 0xd, nil_dei, nil_deo);
230+	/* empty    */ uxn_port(&u, 0xe, nil_dei, nil_deo);
231+	/* empty    */ uxn_port(&u, 0xf, nil_dei, nil_deo);
232+
233+	for(i = 1; i < argc; ++i) {
234+		if(!loaded++) {
235+			if(!load(&u, argv[i]))
236+				return error("Load", "Failed");
237+			if(!uxn_eval(&u, PAGE_PROGRAM))
238+				return error("Init", "Failed");
239+		}
240+	}
241+	if(!loaded)
242+		return error("Input", "Missing");
243+
244+	if(!set_size(WIDTH, HEIGHT, 0))
245+		return error("Window", "Failed to set window size.");
246+
247+	if(!uxn_eval(&u, PAGE_PROGRAM))
248+		return error("Boot", "Failed to start rom.");
249+	ppu_redraw(&ppu, ppu_screen);
250+
251+	XImage *ximage;
252+	Display *display = XOpenDisplay(NULL);
253+	Visual *visual = DefaultVisual(display, 0);
254+	Window window = XCreateSimpleWindow(display, RootWindow(display, 0), 0, 0, ppu.width, ppu.height, 1, 0, 0);
255+	if(visual->class != TrueColor) {
256+		fprintf(stderr, "Cannot handle non true color visual ...\n");
257+		exit(1);
258+	}
259+
260+	ximage = XCreateImage(display, visual, DefaultDepth(display, DefaultScreen(display)), ZPixmap, 0, (Uint8 *)ppu_screen, ppu.width, ppu.height, 32, 0);
261+
262+	XSelectInput(display, window, ButtonPressMask | ExposureMask);
263+	XMapWindow(display, window);
264+	while(1) {
265+		processEvent(display, window, ximage, ppu.width, ppu.height);
266+	}
267+}
+214, -0
  1@@ -0,0 +1,214 @@
  2+#include <stdio.h>
  3+#include <unistd.h>
  4+#include <time.h>
  5+#include "uxn.h"
  6+#include "devices/file.h"
  7+
  8+/*
  9+Copyright (c) 2021 Devine Lu Linvega
 10+
 11+Permission to use, copy, modify, and distribute this software for any
 12+purpose with or without fee is hereby granted, provided that the above
 13+copyright notice and this permission notice appear in all copies.
 14+
 15+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 16+WITH REGARD TO THIS SOFTWARE.
 17+*/
 18+
 19+#pragma mark - Core
 20+
 21+static Device *devsystem, *devconsole;
 22+
 23+static int
 24+error(char *msg, const char *err)
 25+{
 26+	fprintf(stderr, "Error %s: %s\n", msg, err);
 27+	return 0;
 28+}
 29+
 30+static void
 31+inspect(Stack *s, char *name)
 32+{
 33+	Uint8 x, y;
 34+	fprintf(stderr, "\n%s\n", name);
 35+	for(y = 0; y < 0x04; ++y) {
 36+		for(x = 0; x < 0x08; ++x) {
 37+			Uint8 p = y * 0x08 + x;
 38+			fprintf(stderr,
 39+				p == s->ptr ? "[%02x]" : " %02x ",
 40+				s->dat[p]);
 41+		}
 42+		fprintf(stderr, "\n");
 43+	}
 44+}
 45+
 46+#pragma mark - Devices
 47+
 48+static Uint8
 49+system_dei(Device *d, Uint8 port)
 50+{
 51+	switch(port) {
 52+	case 0x2: return d->u->wst.ptr;
 53+	case 0x3: return d->u->rst.ptr;
 54+	default: return d->dat[port];
 55+	}
 56+}
 57+
 58+static void
 59+system_deo(Device *d, Uint8 port)
 60+{
 61+	switch(port) {
 62+	case 0x2: d->u->wst.ptr = d->dat[port]; break;
 63+	case 0x3: d->u->rst.ptr = d->dat[port]; break;
 64+	case 0xe:
 65+		inspect(&d->u->wst, "Working-stack");
 66+		inspect(&d->u->rst, "Return-stack");
 67+		break;
 68+	}
 69+}
 70+
 71+static void
 72+console_deo(Device *d, Uint8 port)
 73+{
 74+	if(port == 0x1)
 75+		d->vector = peek16(d->dat, 0x0);
 76+	if(port > 0x7)
 77+		write(port - 0x7, (char *)&d->dat[port], 1);
 78+}
 79+
 80+static void
 81+file_deo(Device *d, Uint8 port)
 82+{
 83+	switch(port) {
 84+	case 0x1: d->vector = peek16(d->dat, 0x0); break;
 85+	case 0x9: poke16(d->dat, 0x2, file_init(&d->mem[peek16(d->dat, 0x8)])); break;
 86+	case 0xd: poke16(d->dat, 0x2, file_read(&d->mem[peek16(d->dat, 0xc)], peek16(d->dat, 0xa))); break;
 87+	case 0xf: poke16(d->dat, 0x2, file_write(&d->mem[peek16(d->dat, 0xe)], peek16(d->dat, 0xa), d->dat[0x7])); break;
 88+	case 0x5: poke16(d->dat, 0x2, file_stat(&d->mem[peek16(d->dat, 0x4)], peek16(d->dat, 0xa))); break;
 89+	case 0x6: poke16(d->dat, 0x2, file_delete()); break;
 90+	}
 91+}
 92+
 93+static Uint8
 94+datetime_dei(Device *d, Uint8 port)
 95+{
 96+	time_t seconds = time(NULL);
 97+	struct tm zt = {0};
 98+	struct tm *t = localtime(&seconds);
 99+	if(t == NULL)
100+		t = &zt;
101+	switch(port) {
102+	case 0x0: return (t->tm_year + 1900) >> 8;
103+	case 0x1: return (t->tm_year + 1900);
104+	case 0x2: return t->tm_mon;
105+	case 0x3: return t->tm_mday;
106+	case 0x4: return t->tm_hour;
107+	case 0x5: return t->tm_min;
108+	case 0x6: return t->tm_sec;
109+	case 0x7: return t->tm_wday;
110+	case 0x8: return t->tm_yday >> 8;
111+	case 0x9: return t->tm_yday;
112+	case 0xa: return t->tm_isdst;
113+	default: return d->dat[port];
114+	}
115+}
116+
117+static Uint8
118+nil_dei(Device *d, Uint8 port)
119+{
120+	return d->dat[port];
121+}
122+
123+static void
124+nil_deo(Device *d, Uint8 port)
125+{
126+	if(port == 0x1) d->vector = peek16(d->dat, 0x0);
127+}
128+
129+#pragma mark - Generics
130+
131+static const char *errors[] = {"underflow", "overflow", "division by zero"};
132+
133+int
134+uxn_halt(Uxn *u, Uint8 error, char *name, int id)
135+{
136+	fprintf(stderr, "Halted: %s %s#%04x, at 0x%04x\n", name, errors[error - 1], id, u->ram.ptr);
137+	return 0;
138+}
139+
140+static int
141+console_input(Uxn *u, char c)
142+{
143+	devconsole->dat[0x2] = c;
144+	return uxn_eval(u, devconsole->vector);
145+}
146+
147+static void
148+run(Uxn *u)
149+{
150+	Uint16 vec;
151+	while((!u->dev[0].dat[0xf]) && (read(0, &devconsole->dat[0x2], 1) > 0)) {
152+		vec = peek16(devconsole->dat, 0);
153+		if(!vec) vec = u->ram.ptr; /* continue after last BRK */
154+		uxn_eval(u, vec);
155+	}
156+}
157+
158+static int
159+load(Uxn *u, char *filepath)
160+{
161+	FILE *f;
162+	int r;
163+	if(!(f = fopen(filepath, "rb"))) return 0;
164+	r = fread(u->ram.dat + PAGE_PROGRAM, 1, sizeof(u->ram.dat) - PAGE_PROGRAM, f);
165+	fclose(f);
166+	if(r < 1) return 0;
167+	fprintf(stderr, "Loaded %s\n", filepath);
168+	return 1;
169+}
170+
171+int
172+main(int argc, char **argv)
173+{
174+	Uxn u;
175+	int i, loaded = 0;
176+
177+	if(!uxn_boot(&u))
178+		return error("Boot", "Failed");
179+
180+	/* system   */ devsystem = uxn_port(&u, 0x0, system_dei, system_deo);
181+	/* console  */ devconsole = uxn_port(&u, 0x1, nil_dei, console_deo);
182+	/* empty    */ uxn_port(&u, 0x2, nil_dei, nil_deo);
183+	/* empty    */ uxn_port(&u, 0x3, nil_dei, nil_deo);
184+	/* empty    */ uxn_port(&u, 0x4, nil_dei, nil_deo);
185+	/* empty    */ uxn_port(&u, 0x5, nil_dei, nil_deo);
186+	/* empty    */ uxn_port(&u, 0x6, nil_dei, nil_deo);
187+	/* empty    */ uxn_port(&u, 0x7, nil_dei, nil_deo);
188+	/* empty    */ uxn_port(&u, 0x8, nil_dei, nil_deo);
189+	/* empty    */ uxn_port(&u, 0x9, nil_dei, nil_deo);
190+	/* file     */ uxn_port(&u, 0xa, nil_dei, file_deo);
191+	/* datetime */ uxn_port(&u, 0xb, datetime_dei, nil_deo);
192+	/* empty    */ uxn_port(&u, 0xc, nil_dei, nil_deo);
193+	/* empty    */ uxn_port(&u, 0xd, nil_dei, nil_deo);
194+	/* empty    */ uxn_port(&u, 0xe, nil_dei, nil_deo);
195+	/* empty    */ uxn_port(&u, 0xf, nil_dei, nil_deo);
196+
197+	for(i = 1; i < argc; ++i) {
198+		if(!loaded++) {
199+			if(!load(&u, argv[i]))
200+				return error("Load", "Failed");
201+			if(!uxn_eval(&u, PAGE_PROGRAM))
202+				return error("Init", "Failed");
203+		} else {
204+			char *p = argv[i];
205+			while(*p) console_input(&u, *p++);
206+			console_input(&u, '\n');
207+		}
208+	}
209+	if(!loaded)
210+		return error("Input", "Missing");
211+
212+	run(&u);
213+
214+	return 0;
215+}
+577, -0
  1@@ -0,0 +1,577 @@
  2+#include <stdio.h>
  3+#include <unistd.h>
  4+#include <time.h>
  5+#include "uxn.h"
  6+
  7+#pragma GCC diagnostic push
  8+#pragma clang diagnostic push
  9+#pragma GCC diagnostic ignored "-Wpedantic"
 10+#pragma clang diagnostic ignored "-Wtypedef-redefinition"
 11+#include <SDL.h>
 12+#include "devices/ppu.h"
 13+#include "devices/apu.h"
 14+#include "devices/file.h"
 15+#pragma GCC diagnostic pop
 16+#pragma clang diagnostic pop
 17+
 18+/*
 19+Copyright (c) 2021 Devine Lu Linvega
 20+
 21+Permission to use, copy, modify, and distribute this software for any
 22+purpose with or without fee is hereby granted, provided that the above
 23+copyright notice and this permission notice appear in all copies.
 24+
 25+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 26+WITH REGARD TO THIS SOFTWARE.
 27+*/
 28+
 29+#define WIDTH 64 * 8
 30+#define HEIGHT 40 * 8
 31+#define PAD 4
 32+#define FIXED_SIZE 0
 33+#define POLYPHONY 4
 34+#define BENCH 0
 35+
 36+static SDL_Window *gWindow;
 37+static SDL_Texture *gTexture;
 38+static SDL_Renderer *gRenderer;
 39+static SDL_AudioDeviceID audio_id;
 40+static SDL_Rect gRect;
 41+/* devices */
 42+static Ppu ppu;
 43+static Apu apu[POLYPHONY];
 44+static Device *devsystem, *devscreen, *devmouse, *devctrl, *devaudio0, *devconsole;
 45+static Uint8 zoom = 1;
 46+static Uint32 *ppu_screen, stdin_event, audio0_event;
 47+
 48+static int
 49+error(char *msg, const char *err)
 50+{
 51+	fprintf(stderr, "%s: %s\n", msg, err);
 52+	return 0;
 53+}
 54+
 55+#pragma mark - Generics
 56+
 57+static void
 58+audio_callback(void *u, Uint8 *stream, int len)
 59+{
 60+	int i, running = 0;
 61+	Sint16 *samples = (Sint16 *)stream;
 62+	SDL_memset(stream, 0, len);
 63+	for(i = 0; i < POLYPHONY; ++i)
 64+		running += apu_render(&apu[i], samples, samples + len / 2);
 65+	if(!running)
 66+		SDL_PauseAudioDevice(audio_id, 1);
 67+	(void)u;
 68+}
 69+
 70+void
 71+apu_finished_handler(Apu *c)
 72+{
 73+	SDL_Event event;
 74+	event.type = audio0_event + (c - apu);
 75+	SDL_PushEvent(&event);
 76+}
 77+
 78+static int
 79+stdin_handler(void *p)
 80+{
 81+	SDL_Event event;
 82+	event.type = stdin_event;
 83+	while(read(0, &event.cbutton.button, 1) > 0)
 84+		SDL_PushEvent(&event);
 85+	return 0;
 86+	(void)p;
 87+}
 88+
 89+static void
 90+set_window_size(SDL_Window *window, int w, int h)
 91+{
 92+	SDL_Point win, win_old;
 93+	SDL_GetWindowPosition(window, &win.x, &win.y);
 94+	SDL_GetWindowSize(window, &win_old.x, &win_old.y);
 95+	SDL_SetWindowPosition(window, (win.x + win_old.x / 2) - w / 2, (win.y + win_old.y / 2) - h / 2);
 96+	SDL_SetWindowSize(window, w, h);
 97+}
 98+
 99+static void
100+set_zoom(Uint8 scale)
101+{
102+	zoom = SDL_clamp(scale, 1, 3);
103+	set_window_size(gWindow, (ppu.width + PAD * 2) * zoom, (ppu.height + PAD * 2) * zoom);
104+}
105+
106+static int
107+set_size(Uint16 width, Uint16 height, int is_resize)
108+{
109+	ppu_resize(&ppu, width, height);
110+	gRect.x = PAD;
111+	gRect.y = PAD;
112+	gRect.w = ppu.width;
113+	gRect.h = ppu.height;
114+	if(!(ppu_screen = realloc(ppu_screen, ppu.width * ppu.height * sizeof(Uint32))))
115+		return error("ppu_screen", "Memory failure");
116+	memset(ppu_screen, 0, ppu.width * ppu.height * sizeof(Uint32));
117+	if(gTexture != NULL) SDL_DestroyTexture(gTexture);
118+	SDL_RenderSetLogicalSize(gRenderer, ppu.width + PAD * 2, ppu.height + PAD * 2);
119+	gTexture = SDL_CreateTexture(gRenderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STATIC, ppu.width + PAD * 2, ppu.height + PAD * 2);
120+	if(gTexture == NULL || SDL_SetTextureBlendMode(gTexture, SDL_BLENDMODE_NONE))
121+		return error("gTexture", SDL_GetError());
122+	if(SDL_UpdateTexture(gTexture, NULL, ppu_screen, sizeof(Uint32)) != 0)
123+		return error("SDL_UpdateTexture", SDL_GetError());
124+	if(is_resize)
125+		set_window_size(gWindow, (ppu.width + PAD * 2) * zoom, (ppu.height + PAD * 2) * zoom);
126+	return 1;
127+}
128+
129+static void
130+capture_screen(void)
131+{
132+	const Uint32 format = SDL_PIXELFORMAT_RGB24;
133+	time_t t = time(NULL);
134+	char fname[64];
135+	int w, h;
136+	SDL_Surface *surface;
137+	SDL_GetRendererOutputSize(gRenderer, &w, &h);
138+	surface = SDL_CreateRGBSurfaceWithFormat(0, w, h, 24, format);
139+	SDL_RenderReadPixels(gRenderer, NULL, format, surface->pixels, surface->pitch);
140+	strftime(fname, sizeof(fname), "screenshot-%Y%m%d-%H%M%S.bmp", localtime(&t));
141+	SDL_SaveBMP(surface, fname);
142+	SDL_FreeSurface(surface);
143+	fprintf(stderr, "Saved %s\n", fname);
144+}
145+
146+static void
147+redraw(Uxn *u)
148+{
149+	if(devsystem->dat[0xe])
150+		ppu_debug(&ppu, u->wst.dat, u->wst.ptr, u->rst.ptr, u->ram.dat);
151+	ppu_redraw(&ppu, ppu_screen);
152+	if(SDL_UpdateTexture(gTexture, &gRect, ppu_screen, ppu.width * sizeof(Uint32)) != 0)
153+		error("SDL_UpdateTexture", SDL_GetError());
154+	SDL_RenderClear(gRenderer);
155+	SDL_RenderCopy(gRenderer, gTexture, NULL, NULL);
156+	SDL_RenderPresent(gRenderer);
157+}
158+
159+static int
160+init(void)
161+{
162+	SDL_AudioSpec as;
163+	SDL_zero(as);
164+	as.freq = SAMPLE_FREQUENCY;
165+	as.format = AUDIO_S16;
166+	as.channels = 2;
167+	as.callback = audio_callback;
168+	as.samples = 512;
169+	as.userdata = NULL;
170+	if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) {
171+		error("sdl", SDL_GetError());
172+		if(SDL_Init(SDL_INIT_VIDEO) < 0)
173+			return error("sdl", SDL_GetError());
174+	} else {
175+		audio_id = SDL_OpenAudioDevice(NULL, 0, &as, NULL, 0);
176+		if(!audio_id)
177+			error("sdl_audio", SDL_GetError());
178+	}
179+	gWindow = SDL_CreateWindow("Uxn", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, (WIDTH + PAD * 2) * zoom, (HEIGHT + PAD * 2) * zoom, SDL_WINDOW_SHOWN);
180+	if(gWindow == NULL)
181+		return error("sdl_window", SDL_GetError());
182+	gRenderer = SDL_CreateRenderer(gWindow, -1, 0);
183+	if(gRenderer == NULL)
184+		return error("sdl_renderer", SDL_GetError());
185+	stdin_event = SDL_RegisterEvents(1);
186+	audio0_event = SDL_RegisterEvents(POLYPHONY);
187+	SDL_CreateThread(stdin_handler, "stdin", NULL);
188+	SDL_StartTextInput();
189+	SDL_ShowCursor(SDL_DISABLE);
190+	SDL_EventState(SDL_DROPFILE, SDL_ENABLE);
191+	return 1;
192+}
193+
194+static void
195+domouse(SDL_Event *event)
196+{
197+	Uint8 flag = 0x00;
198+	Uint16 x = SDL_clamp(event->motion.x - PAD, 0, ppu.width - 1);
199+	Uint16 y = SDL_clamp(event->motion.y - PAD, 0, ppu.height - 1);
200+	if(event->type == SDL_MOUSEWHEEL) {
201+		devmouse->dat[7] = event->wheel.y;
202+		return;
203+	}
204+	poke16(devmouse->dat, 0x2, x);
205+	poke16(devmouse->dat, 0x4, y);
206+	devmouse->dat[7] = 0x00;
207+	switch(event->button.button) {
208+	case SDL_BUTTON_LEFT: flag = 0x01; break;
209+	case SDL_BUTTON_RIGHT: flag = 0x10; break;
210+	}
211+	switch(event->type) {
212+	case SDL_MOUSEBUTTONDOWN:
213+		devmouse->dat[6] |= flag;
214+		break;
215+	case SDL_MOUSEBUTTONUP:
216+		devmouse->dat[6] &= (~flag);
217+		break;
218+	}
219+}
220+
221+#pragma mark - Devices
222+
223+static Uint8
224+system_dei(Device *d, Uint8 port)
225+{
226+	switch(port) {
227+	case 0x2: return d->u->wst.ptr;
228+	case 0x3: return d->u->rst.ptr;
229+	default: return d->dat[port];
230+	}
231+}
232+
233+static void
234+system_deo(Device *d, Uint8 port)
235+{
236+	switch(port) {
237+	case 0x2: d->u->wst.ptr = d->dat[port]; break;
238+	case 0x3: d->u->rst.ptr = d->dat[port]; break;
239+	}
240+	if(port > 0x7 && port < 0xe)
241+		ppu_palette(&ppu, &d->dat[0x8]);
242+}
243+
244+static void
245+console_deo(Device *d, Uint8 port)
246+{
247+	if(port == 0x1)
248+		d->vector = peek16(d->dat, 0x0);
249+	if(port > 0x7)
250+		write(port - 0x7, (char *)&d->dat[port], 1);
251+}
252+
253+static Uint8
254+screen_dei(Device *d, Uint8 port)
255+{
256+	switch(port) {
257+	case 0x2: return ppu.width >> 8;
258+	case 0x3: return ppu.width;
259+	case 0x4: return ppu.height >> 8;
260+	case 0x5: return ppu.height;
261+	default: return d->dat[port];
262+	}
263+}
264+
265+static void
266+screen_deo(Device *d, Uint8 port)
267+{
268+	switch(port) {
269+	case 0x1: d->vector = peek16(d->dat, 0x0); break;
270+	case 0x5:
271+		if(!FIXED_SIZE) set_size(peek16(d->dat, 0x2), peek16(d->dat, 0x4), 1);
272+		break;
273+	case 0xe: {
274+		Uint16 x = peek16(d->dat, 0x8);
275+		Uint16 y = peek16(d->dat, 0xa);
276+		Uint8 layer = d->dat[0xe] & 0x40;
277+		ppu_write(&ppu, layer ? &ppu.fg : &ppu.bg, x, y, d->dat[0xe] & 0x3);
278+		if(d->dat[0x6] & 0x01) poke16(d->dat, 0x8, x + 1); /* auto x+1 */
279+		if(d->dat[0x6] & 0x02) poke16(d->dat, 0xa, y + 1); /* auto y+1 */
280+		break;
281+	}
282+	case 0xf: {
283+		Uint16 x = peek16(d->dat, 0x8);
284+		Uint16 y = peek16(d->dat, 0xa);
285+		Layer *layer = (d->dat[0xf] & 0x40) ? &ppu.fg : &ppu.bg;
286+		Uint8 *addr = &d->mem[peek16(d->dat, 0xc)];
287+		Uint8 twobpp = !!(d->dat[0xf] & 0x80);
288+		ppu_blit(&ppu, layer, x, y, addr, d->dat[0xf] & 0xf, d->dat[0xf] & 0x10, d->dat[0xf] & 0x20, twobpp);
289+		if(d->dat[0x6] & 0x04) poke16(d->dat, 0xc, peek16(d->dat, 0xc) + 8 + twobpp*8); /* auto addr+8 / auto addr+16 */
290+		if(d->dat[0x6] & 0x01) poke16(d->dat, 0x8, x + 8); /* auto x+8 */
291+		if(d->dat[0x6] & 0x02) poke16(d->dat, 0xa, y + 8); /* auto y+8 */
292+		break;
293+	}
294+	}
295+}
296+
297+static void
298+file_deo(Device *d, Uint8 port)
299+{
300+	switch(port) {
301+	case 0x1: d->vector = peek16(d->dat, 0x0); break;
302+	case 0x9: poke16(d->dat, 0x2, file_init(&d->mem[peek16(d->dat, 0x8)])); break;
303+	case 0xd: poke16(d->dat, 0x2, file_read(&d->mem[peek16(d->dat, 0xc)], peek16(d->dat, 0xa))); break;
304+	case 0xf: poke16(d->dat, 0x2, file_write(&d->mem[peek16(d->dat, 0xe)], peek16(d->dat, 0xa), d->dat[0x7])); break;
305+	case 0x5: poke16(d->dat, 0x2, file_stat(&d->mem[peek16(d->dat, 0x4)], peek16(d->dat, 0xa))); break;
306+	case 0x6: poke16(d->dat, 0x2, file_delete()); break;
307+	}
308+}
309+
310+static Uint8
311+audio_dei(Device *d, Uint8 port)
312+{
313+	Apu *c = &apu[d - devaudio0];
314+	if(!audio_id) return d->dat[port];
315+	switch(port) {
316+	case 0x4: return apu_get_vu(c);
317+	case 0x2: poke16(d->dat, 0x2, c->i); /* fall through */
318+	default: return d->dat[port];
319+	}
320+}
321+
322+static void
323+audio_deo(Device *d, Uint8 port)
324+{
325+	Apu *c = &apu[d - devaudio0];
326+	if(!audio_id) return;
327+	if(port == 0xf) {
328+		SDL_LockAudioDevice(audio_id);
329+		c->len = peek16(d->dat, 0xa);
330+		c->addr = &d->mem[peek16(d->dat, 0xc)];
331+		c->volume[0] = d->dat[0xe] >> 4;
332+		c->volume[1] = d->dat[0xe] & 0xf;
333+		c->repeat = !(d->dat[0xf] & 0x80);
334+		apu_start(c, peek16(d->dat, 0x8), d->dat[0xf] & 0x7f);
335+		SDL_UnlockAudioDevice(audio_id);
336+		SDL_PauseAudioDevice(audio_id, 0);
337+	}
338+}
339+
340+static Uint8
341+datetime_dei(Device *d, Uint8 port)
342+{
343+	time_t seconds = time(NULL);
344+	struct tm zt = {0};
345+	struct tm *t = localtime(&seconds);
346+	if(t == NULL)
347+		t = &zt;
348+	switch(port) {
349+	case 0x0: return (t->tm_year + 1900) >> 8;
350+	case 0x1: return (t->tm_year + 1900);
351+	case 0x2: return t->tm_mon;
352+	case 0x3: return t->tm_mday;
353+	case 0x4: return t->tm_hour;
354+	case 0x5: return t->tm_min;
355+	case 0x6: return t->tm_sec;
356+	case 0x7: return t->tm_wday;
357+	case 0x8: return t->tm_yday >> 8;
358+	case 0x9: return t->tm_yday;
359+	case 0xa: return t->tm_isdst;
360+	default: return d->dat[port];
361+	}
362+}
363+
364+static Uint8
365+nil_dei(Device *d, Uint8 port)
366+{
367+	return d->dat[port];
368+}
369+
370+static void
371+nil_deo(Device *d, Uint8 port)
372+{
373+	if(port == 0x1) d->vector = peek16(d->dat, 0x0);
374+}
375+
376+/* Boot */
377+
378+static int
379+load(Uxn *u, char *rom)
380+{
381+	SDL_RWops *f;
382+	int r;
383+	if(!(f = SDL_RWFromFile(rom, "rb"))) return 0;
384+	r = f->read(f, u->ram.dat + PAGE_PROGRAM, 1, sizeof(u->ram.dat) - PAGE_PROGRAM);
385+	f->close(f);
386+	if(r < 1) return 0;
387+	fprintf(stderr, "Loaded %s\n", rom);
388+	SDL_SetWindowTitle(gWindow, rom);
389+	return 1;
390+}
391+
392+static int
393+start(Uxn *u, char *rom)
394+{
395+	if(!uxn_boot(u))
396+		return error("Boot", "Failed to start uxn.");
397+	if(!load(u, rom))
398+		return error("Boot", "Failed to load rom.");
399+
400+	/* system   */ devsystem = uxn_port(u, 0x0, system_dei, system_deo);
401+	/* console  */ devconsole = uxn_port(u, 0x1, nil_dei, console_deo);
402+	/* screen   */ devscreen = uxn_port(u, 0x2, screen_dei, screen_deo);
403+	/* audio0   */ devaudio0 = uxn_port(u, 0x3, audio_dei, audio_deo);
404+	/* audio1   */ uxn_port(u, 0x4, audio_dei, audio_deo);
405+	/* audio2   */ uxn_port(u, 0x5, audio_dei, audio_deo);
406+	/* audio3   */ uxn_port(u, 0x6, audio_dei, audio_deo);
407+	/* unused   */ uxn_port(u, 0x7, nil_dei, nil_deo);
408+	/* control  */ devctrl = uxn_port(u, 0x8, nil_dei, nil_deo);
409+	/* mouse    */ devmouse = uxn_port(u, 0x9, nil_dei, nil_deo);
410+	/* file     */ uxn_port(u, 0xa, nil_dei, file_deo);
411+	/* datetime */ uxn_port(u, 0xb, datetime_dei, nil_deo);
412+	/* unused   */ uxn_port(u, 0xc, nil_dei, nil_deo);
413+	/* unused   */ uxn_port(u, 0xd, nil_dei, nil_deo);
414+	/* unused   */ uxn_port(u, 0xe, nil_dei, nil_deo);
415+	/* unused   */ uxn_port(u, 0xf, nil_dei, nil_deo);
416+
417+	if(!uxn_eval(u, PAGE_PROGRAM))
418+		return error("Boot", "Failed to start rom.");
419+
420+	return 1;
421+}
422+
423+static void
424+restart(Uxn *u)
425+{
426+	set_size(WIDTH, HEIGHT, 1);
427+	start(u, "boot.rom");
428+}
429+
430+static void
431+doctrl(Uxn *u, SDL_Event *event, int z)
432+{
433+	Uint8 flag = 0x00;
434+	SDL_Keymod mods = SDL_GetModState();
435+	devctrl->dat[2] &= 0xf8;
436+	if(mods & KMOD_CTRL) devctrl->dat[2] |= 0x01;
437+	if(mods & KMOD_ALT) devctrl->dat[2] |= 0x02;
438+	if(mods & KMOD_SHIFT) devctrl->dat[2] |= 0x04;
439+	/* clang-format off */
440+	switch(event->key.keysym.sym) {
441+	case SDLK_ESCAPE: flag = 0x08; break;
442+	case SDLK_UP: flag = 0x10; break;
443+	case SDLK_DOWN: flag = 0x20; break;
444+	case SDLK_LEFT: flag = 0x40; break;
445+	case SDLK_RIGHT: flag = 0x80; break;
446+	case SDLK_F1: if(z) set_zoom(zoom > 2 ? 1 : zoom + 1); break;
447+	case SDLK_F2: if(z) devsystem->dat[0xe] = !devsystem->dat[0xe]; ppu_clear(&ppu, &ppu.fg); break;
448+	case SDLK_F3: if(z) capture_screen(); break;
449+	case SDLK_AC_BACK:
450+	case SDLK_F4: if(z) restart(u); break;
451+	}
452+	/* clang-format on */
453+	if(z) {
454+		devctrl->dat[2] |= flag;
455+		if(event->key.keysym.sym < 0x20 || event->key.keysym.sym == SDLK_DELETE)
456+			devctrl->dat[3] = event->key.keysym.sym;
457+		else if((mods & KMOD_CTRL) && event->key.keysym.sym >= SDLK_a && event->key.keysym.sym <= SDLK_z)
458+			devctrl->dat[3] = event->key.keysym.sym - (mods & KMOD_SHIFT) * 0x20;
459+	} else
460+		devctrl->dat[2] &= ~flag;
461+}
462+
463+static const char *errors[] = {"underflow", "overflow", "division by zero"};
464+
465+int
466+uxn_halt(Uxn *u, Uint8 error, char *name, int id)
467+{
468+	fprintf(stderr, "Halted: %s %s#%04x, at 0x%04x\n", name, errors[error - 1], id, u->ram.ptr);
469+	return 0;
470+}
471+
472+static int
473+console_input(Uxn *u, char c)
474+{
475+	devconsole->dat[0x2] = c;
476+	return uxn_eval(u, devconsole->vector);
477+}
478+
479+static int
480+run(Uxn *u)
481+{
482+	redraw(u);
483+	while(!devsystem->dat[0xf]) {
484+		SDL_Event event;
485+		double elapsed, begin = 0;
486+		int ksym;
487+		if(!BENCH)
488+			begin = SDL_GetPerformanceCounter();
489+		while(SDL_PollEvent(&event) != 0) {
490+			switch(event.type) {
491+			case SDL_DROPFILE:
492+				set_size(WIDTH, HEIGHT, 0);
493+				start(u, event.drop.file);
494+				SDL_free(event.drop.file);
495+				break;
496+			case SDL_QUIT:
497+				return error("Run", "Quit.");
498+			case SDL_TEXTINPUT:
499+				devctrl->dat[3] = event.text.text[0]; /* fall-thru */
500+			case SDL_KEYDOWN:
501+			case SDL_KEYUP:
502+				doctrl(u, &event, event.type == SDL_KEYDOWN);
503+				uxn_eval(u, devctrl->vector);
504+				devctrl->dat[3] = 0;
505+
506+				if(event.type == SDL_KEYDOWN) {
507+					ksym = event.key.keysym.sym;
508+					if(SDL_PeepEvents(&event, 1, SDL_PEEKEVENT, SDL_KEYUP, SDL_KEYUP) == 1 && ksym == event.key.keysym.sym)
509+						goto breakout;
510+				}
511+				break;
512+			case SDL_MOUSEWHEEL:
513+			case SDL_MOUSEBUTTONUP:
514+			case SDL_MOUSEBUTTONDOWN:
515+			case SDL_MOUSEMOTION:
516+				domouse(&event);
517+				uxn_eval(u, devmouse->vector);
518+				break;
519+			case SDL_WINDOWEVENT:
520+				if(event.window.event == SDL_WINDOWEVENT_EXPOSED)
521+					redraw(u);
522+				break;
523+			default:
524+				if(event.type == stdin_event) {
525+					console_input(u, event.cbutton.button);
526+				} else if(event.type >= audio0_event && event.type < audio0_event + POLYPHONY)
527+					uxn_eval(u, peek16((devaudio0 + (event.type - audio0_event))->dat, 0));
528+			}
529+		}
530+	breakout:
531+		uxn_eval(u, devscreen->vector);
532+		if(ppu.fg.changed || ppu.bg.changed || devsystem->dat[0xe])
533+			redraw(u);
534+		if(!BENCH) {
535+			elapsed = (SDL_GetPerformanceCounter() - begin) / (double)SDL_GetPerformanceFrequency() * 1000.0f;
536+			SDL_Delay(SDL_clamp(16.666f - elapsed, 0, 1000));
537+		}
538+	}
539+	return error("Run", "Ended.");
540+}
541+
542+int
543+main(int argc, char **argv)
544+{
545+	SDL_DisplayMode DM;
546+	Uxn u;
547+	int i, loaded = 0;
548+
549+	if(!init())
550+		return error("Init", "Failed to initialize emulator.");
551+	if(!set_size(WIDTH, HEIGHT, 0))
552+		return error("Window", "Failed to set window size.");
553+
554+	/* set default zoom */
555+	if(SDL_GetCurrentDisplayMode(0, &DM) == 0)
556+		set_zoom(DM.w / 1280);
557+	for(i = 1; i < argc; ++i) {
558+		/* get default zoom from flags */
559+		if(strcmp(argv[i], "-s") == 0) {
560+			if(i < argc - 1)
561+				set_zoom(atoi(argv[++i]));
562+			else
563+				return error("Opt", "-s No scale provided.");
564+		} else if(!loaded++) {
565+			if(!start(&u, argv[i]))
566+				return error("Boot", "Failed to boot.");
567+		} else {
568+			char *p = argv[i];
569+			while(*p) console_input(&u, *p++);
570+			console_input(&u, '\n');
571+		}
572+	}
573+	if(!loaded && !start(&u, "boot.rom"))
574+		return error("usage", "uxnemu [-s scale] file.rom");
575+	run(&u);
576+	SDL_Quit();
577+	return 0;
578+}