commit e7d38ed

pita  ·  2026-07-24 13:09:51 +0000 UTC
parent 2195006
added -t option to not carry .theme file arround all the time, (by pnthr)
3 files changed,  +1862, -9
+1, -0
1@@ -47,6 +47,7 @@ archive:
2 	cp etc/tests/perifs.tal ../oscean/etc/varvara.perifs.tal.txt
3 	cp etc/tests/file.tal ../oscean/etc/varvara.file.tal.txt
4 	cp etc/tests/datetime.tal ../oscean/etc/varvara.datetime.tal.txt
5+	cp etc/tests/audio.tal ../oscean/etc/varvara.audio.tal.txt
6 
7 .PHONY: run test clean format install uninstall grab archive
8 
+15, -9
 1@@ -641,9 +641,6 @@ audio_start(int instance, Uint8 *d)
 2 		c->period = NOTE_PERIOD * 337 / 2 / c->len;
 3 	else
 4 		c->period = NOTE_PERIOD;
 5-  	/* Debug Print */
 6-	fprintf(stderr, "[APU %d] PLAY: addr=0x%04x len=%u adsr=0x%04x vol=%d/%d pitch=%d repeat=%d\n",
 7-	        instance, addr, c->len, adsr, c->volume[0], c->volume[1], pitch, c->repeat);
 8 
 9 }
10 
11@@ -730,10 +727,6 @@ audio_finished_handler(int instance)
12 {
13 	Uint8 *port_value = &dev[0x30 + 0x10 * instance];
14 	Uint16 vector = port_value[0] << 8 | port_value[1];
15-
16-	/* Debug Print */
17-	fprintf(stderr, "[APU %d] FINISHED: vector=0x%04x\n", instance, vector);
18-
19 	if(vector)
20 		uxn_eval(vector);
21 }
22@@ -877,6 +870,8 @@ mouse_deo_vector(void)
23 #include <sys/stat.h>
24 #include <string.h>
25 
26+static char *theme_file = NULL;
27+
28 typedef struct {
29 	union {
30 		FILE *f;
31@@ -1060,7 +1055,11 @@ static unsigned int
32 file_init(unsigned int id, Uint16 addr)
33 {
34 	file_reset(id);
35-	ufs[id].filepath = (char *)&ram[addr];
36+	char *path = (char *)&ram[addr];
37+	if(theme_file && strcmp(path, ".theme") == 0)
38+		ufs[id].filepath = theme_file;
39+	else
40+		ufs[id].filepath = path;
41 	return 0;
42 }
43 
44@@ -1804,8 +1803,15 @@ main(int argc, char **argv)
45 {
46 	int i = 1;
47 	if(argc == 1)
48-		return !fprintf(stdout, "usage: %s [-v] file.rom [args..]\n", argv[0]);
49+		return !fprintf(stdout, "usage: %s [-v] [-t path/to/.theme] [-l x y] file.rom [args..]\n", argv[0]);
50 	while(i < argc && argv[i][0] == '-') {
51+		if(!strcmp(argv[i], "-t")) {
52+			if(i + 1 >= argc)
53+				return !fprintf(stdout, "usage: %s [-v] [-t theme] [-l x y] file.rom [args..]\n", argv[0]);
54+			theme_file = argv[++i];
55+			i++;
56+			continue;
57+		}
58 		if(!strcmp(argv[i], "-v"))
59 			return !fprintf(stdout, "%s - Varvara Emulator(79K), 10 Jul 2026.\n", argv[0]);
60 		if(!strcmp(argv[i], "--")){ i++; break; }
+1846, -0
   1@@ -0,0 +1,1846 @@
   2+#include <stdio.h>
   3+#include <stdlib.h>
   4+#include <unistd.h>
   5+#include <string.h>
   6+#include <sys/mman.h>
   7+#include <poll.h>
   8+#include <time.h>
   9+#include <wayland-client.h>
  10+#include <xkbcommon/xkbcommon.h>
  11+#include <linux/input-event-codes.h>
  12+#include "xdg-shell-client-protocol.h"
  13+#include "wlr-layer-shell-client-protocol.h"
  14+
  15+/*
  16+Copyright (c) 2021-2026 Devine Lu Linvega, Andrew Alderwick,
  17+Andrew Richards, Eiríkr Åsheim, Sigrid Solveig Haflínudóttir
  18+
  19+Permission to use, copy, modify, and distribute this software for any
  20+purpose with or without fee is hereby granted, provided that the above
  21+copyright notice and this permission notice appear in all copies.
  22+
  23+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  24+WITH REGARD TO THIS SOFTWARE.
  25+
  26+cc -DNDEBUG -O2 -g0 -s uxn11.c -lX11 -lutil -o uxn11
  27+*/
  28+
  29+extern int mkstemp(char *template);
  30+
  31+typedef signed char Sint8;
  32+typedef unsigned char Uint8;
  33+typedef signed short Sint16;
  34+typedef unsigned short Uint16;
  35+typedef signed int Sint32;
  36+typedef unsigned int Uint32;
  37+typedef void (*deo_handler)(void);
  38+typedef Uint8 (*dei_handler)(void);
  39+
  40+static unsigned int uxn_eval(Uint16 pc);
  41+static Uint8 *ram, dev[0x100], stk[2][0x100], ptr[2];
  42+static int console_vector, screen_vector, controller_vector, mouse_vector;
  43+static int rX, rY, rA, rMX, rMY, rMA, rML, rDX, rDY, rL1, rL2;
  44+
  45+#define SAMPLE_FREQUENCY 44100
  46+#define POLYPHONY 4
  47+#define NOTE_PERIOD (SAMPLE_FREQUENCY * 0x4000 / 11025)
  48+#define ADSR_STEP (SAMPLE_FREQUENCY / 0xf)
  49+
  50+typedef struct {
  51+	Uint8 *addr;
  52+	Uint32 count, advance, period, age, a, d, s, r;
  53+	Uint16 i, len;
  54+	Sint8 volume[2];
  55+	Uint8 pitch, repeat;
  56+}	UxnAudio;
  57+
  58+static UxnAudio uxn_audio[POLYPHONY];
  59+
  60+static Uint32 advances[12] = {
  61+	0x80000, 0x879c8, 0x8facd, 0x9837f, 0xa1451, 0xaadc1,
  62+	0xb504f, 0xbfc88, 0xcb2ff, 0xd7450, 0xe411f, 0xf1a1c
  63+};
  64+
  65+/* clang-format off */
  66+
  67+#define BANKS 0x10
  68+#define BANKS_CAP BANKS * 0x10000
  69+#define WIDTH (64 * 8)
  70+#define HEIGHT (40 * 8)
  71+
  72+static inline Uint16 peek2(const Uint8 *d) { return ((Uint16)d[0] << 8) | d[1]; }
  73+static inline void poke2(Uint8 *d, Uint16 v) { d[0] = v >> 8, d[1] = v; }
  74+
  75+/* clang-format on */
  76+
  77+/*
  78+@|System ------------------------------------------------------------ */
  79+
  80+static char *system_boot_path;
  81+static void emu_screenshot(void);
  82+
  83+static void
  84+system_print(char *name, int r)
  85+{
  86+	Uint8 i;
  87+	fprintf(stderr, "%s%c", name, ptr[r] - 8 ? ' ' : '|');
  88+	for(i = ptr[r] - 8; i != ptr[r]; i++)
  89+		fprintf(stderr, "%02x%c", stk[r][i], i == 0xff ? '|' : ' ');
  90+	fprintf(stderr, "<%02x\n", ptr[r]);
  91+}
  92+
  93+static unsigned int
  94+system_load(const char *rom_path)
  95+{
  96+	FILE *f = fopen(rom_path, "rb");
  97+	if(f) {
  98+		unsigned int i = 0, l = fread(ram + 0x100, 0x10000 - 0x100, 1, f);
  99+		while(l && ++i < BANKS)
 100+			l = fread(ram + i * 0x10000, 0x10000, 1, f);
 101+		fclose(f);
 102+	}
 103+	return !!f;
 104+}
 105+
 106+static unsigned int
 107+system_boot(char *rom_path, const unsigned int has_args)
 108+{
 109+	ram = (Uint8 *)calloc(BANKS_CAP, sizeof(Uint8));
 110+	system_boot_path = rom_path;
 111+	dev[0x17] = has_args;
 112+	return ram && system_load(rom_path);
 113+}
 114+
 115+static unsigned int
 116+system_reboot(const unsigned int soft)
 117+{
 118+	memset(dev, 0, 0x100);
 119+	memset(stk[0], 0, 0x100);
 120+	memset(stk[1], 0, 0x100);
 121+	if(soft)
 122+		memset(ram + 0x100, 0, 0xff00);
 123+	else
 124+		memset(ram, 0, 0x10000);
 125+	ptr[0] = ptr[1] = 0;
 126+	console_vector = screen_vector = controller_vector = mouse_vector = 0;
 127+	rX = rY = rA = rMX = rMY = rMA = rML = rDX = rDY = rL1 = rL2 = 0;
 128+	return system_load(system_boot_path);
 129+}
 130+
 131+static void
 132+system_deo_expansion(void)
 133+{
 134+	const Uint16 exp = peek2(dev + 2);
 135+	Uint8 *aptr = ram + exp;
 136+	Uint16 length = peek2(aptr + 1);
 137+	unsigned int bank = peek2(aptr + 3) * 0x10000;
 138+	unsigned int addr = peek2(aptr + 5);
 139+	if(ram[exp] == 0x0) {
 140+		if(bank < BANKS_CAP)
 141+			memset(ram + bank + addr, ram[exp + 7], length);
 142+	} else if(ram[exp] == 1 || ram[exp] == 2) {
 143+		unsigned int dst_bank = peek2(aptr + 7) * 0x10000;
 144+		unsigned int dst_addr = peek2(aptr + 9);
 145+		if(bank < BANKS_CAP && dst_bank < BANKS_CAP)
 146+			memmove(ram + dst_bank + dst_addr, ram + bank + addr, length);
 147+	} else
 148+		fprintf(stderr, "Unknown command: %02x\n", ram[exp]);
 149+}
 150+
 151+static void
 152+system_deo_print(void)
 153+{
 154+	if(dev[0xe] & 0x1)
 155+		system_print("WST", 0), system_print("RST", 1);
 156+	if(dev[0xe] & 0x2)
 157+		emu_screenshot();
 158+}
 159+
 160+/* clang-format off */
 161+
 162+static Uint8 system_dei_wst(void) { return ptr[0]; }
 163+static Uint8 system_dei_rst(void) { return ptr[1]; }
 164+static void system_deo_wst(void) { ptr[0] = dev[4]; }
 165+static void system_deo_rst(void) { ptr[1] = dev[5]; }
 166+
 167+/* clang-format on */
 168+
 169+/*
 170+@|Console ----------------------------------------------------------- */
 171+
 172+#define CONSOLE_STD 0x1
 173+#define CONSOLE_ARG 0x2
 174+#define CONSOLE_EOA 0x3
 175+#define CONSOLE_END 0x4
 176+
 177+static void
 178+console_input(int c, unsigned int type)
 179+{
 180+	if(c == EOF) c = 0, type = CONSOLE_END;
 181+	dev[0x12] = c, dev[0x17] = type;
 182+	if(console_vector)
 183+		uxn_eval(console_vector);
 184+}
 185+
 186+#include <signal.h>
 187+#include <sys/wait.h>
 188+
 189+#ifdef __linux
 190+#include <pty.h>
 191+#include <sys/prctl.h>
 192+#endif
 193+
 194+#ifdef __NetBSD__
 195+#include <sys/ioctl.h>
 196+#include <util.h>
 197+#endif
 198+
 199+/* subprocess support */
 200+static char *fork_args[4] = {"/bin/sh", "-c", "", NULL};
 201+static int child_mode;
 202+static int to_child_fd[2];
 203+static int from_child_fd[2];
 204+static int saved_in;
 205+static int saved_out;
 206+static pid_t child_pid;
 207+
 208+/* child_mode:
 209+* 0x01: writes to child's stdin
 210+* 0x02: reads from child's stdout
 211+* 0x04: reads from child's stderr
 212+* 0x08: kill previous process (if any) but do not start
 213+* (other bits ignored for now )
 214+*/
 215+
 216+#define CMD_LIVE 0x15 /* 0x00 not started, 0x01 running, 0xff dead */
 217+#define CMD_EXIT 0x16 /* if dead, exit code of process */
 218+#define CMD_ADDR 0x1c /* address to read command args from */
 219+#define CMD_MODE 0x1e /* mode to execute, 0x00 to 0x07 */
 220+
 221+/* call after we're sure the process has exited */
 222+
 223+static void
 224+clean_after_child(void)
 225+{
 226+	child_pid = 0;
 227+	if(child_mode & 0x01) {
 228+		close(to_child_fd[1]);
 229+		dup2(saved_out, 1);
 230+	}
 231+	if(child_mode & (0x04 | 0x02)) {
 232+		close(from_child_fd[0]);
 233+		dup2(saved_in, 0);
 234+	}
 235+	child_mode = 0;
 236+	saved_in = -1;
 237+	saved_out = -1;
 238+}
 239+
 240+static void
 241+start_fork_pipe(void)
 242+{
 243+	pid_t pid;
 244+	pid_t parent_pid = getpid();
 245+	int addr = peek2(&dev[CMD_ADDR]);
 246+	fflush(stdout);
 247+	if(child_mode & 0x08) {
 248+		dev[CMD_EXIT] = dev[CMD_LIVE] = 0x00;
 249+		return;
 250+	}
 251+	if(child_mode & 0x01) {
 252+		/* parent writes to child's stdin */
 253+		if(pipe(to_child_fd) == -1) {
 254+			dev[CMD_EXIT] = dev[CMD_LIVE] = 0xff;
 255+			fprintf(stderr, "Pipe error to child.\n");
 256+			return;
 257+		}
 258+	}
 259+	if(child_mode & (0x04 | 0x02)) {
 260+		/* parent reads from child's stdout and/or stderr */
 261+		if(pipe(from_child_fd) == -1) {
 262+			dev[CMD_EXIT] = dev[CMD_LIVE] = 0xff;
 263+			fprintf(stderr, "Pipe error from child.\n");
 264+			return;
 265+		}
 266+	}
 267+
 268+	fork_args[2] = (char *)&ram[addr];
 269+	pid = fork();
 270+	if(pid < 0) { /* failure */
 271+		dev[CMD_EXIT] = dev[CMD_LIVE] = 0xff;
 272+		fprintf(stderr, "Fork failure.\n");
 273+	} else if(pid == 0) { /* child */
 274+
 275+#ifdef __linux__
 276+		int r = prctl(PR_SET_PDEATHSIG, SIGTERM);
 277+		if(r == -1) {
 278+			perror(0);
 279+			exit(6);
 280+		}
 281+		if(getppid() != parent_pid) exit(13);
 282+#endif
 283+
 284+		if(child_mode & 0x01) {
 285+			dup2(to_child_fd[0], 0);
 286+			close(to_child_fd[1]);
 287+		}
 288+		if(child_mode & (0x04 | 0x02)) {
 289+			if(child_mode & 0x02) dup2(from_child_fd[1], 1);
 290+			if(child_mode & 0x04) dup2(from_child_fd[1], 2);
 291+			close(from_child_fd[0]);
 292+		}
 293+		fflush(stdout);
 294+		execvp(fork_args[0], fork_args);
 295+		exit(1);
 296+	} else { /*parent*/
 297+		child_pid = pid;
 298+		dev[CMD_LIVE] = 0x01;
 299+		dev[CMD_EXIT] = 0x00;
 300+		if(child_mode & 0x01) {
 301+			saved_out = dup(1);
 302+			dup2(to_child_fd[1], 1);
 303+			close(to_child_fd[0]);
 304+		}
 305+		if(child_mode & (0x04 | 0x02)) {
 306+			saved_in = dup(0);
 307+			dup2(from_child_fd[0], 0);
 308+			close(from_child_fd[1]);
 309+		}
 310+	}
 311+}
 312+
 313+static void
 314+check_child(void)
 315+{
 316+	int wstatus;
 317+	if(child_pid) {
 318+		if(waitpid(child_pid, &wstatus, WNOHANG)) {
 319+			dev[CMD_LIVE] = 0xff;
 320+			dev[CMD_EXIT] = WEXITSTATUS(wstatus);
 321+			clean_after_child();
 322+		} else {
 323+			dev[CMD_LIVE] = 0x01;
 324+			dev[CMD_EXIT] = 0x00;
 325+		}
 326+	}
 327+}
 328+
 329+static void
 330+kill_child(void)
 331+{
 332+	int wstatus;
 333+	if(child_pid) {
 334+		kill(child_pid, 9);
 335+		if(waitpid(child_pid, &wstatus, WNOHANG)) {
 336+			dev[CMD_LIVE] = 0xff;
 337+			dev[CMD_EXIT] = WEXITSTATUS(wstatus);
 338+			clean_after_child();
 339+		}
 340+	}
 341+}
 342+
 343+static void
 344+console_deo_fork(void)
 345+{
 346+	fflush(stderr);
 347+	kill_child();
 348+	child_mode = dev[CMD_MODE];
 349+	start_fork_pipe();
 350+}
 351+
 352+static void
 353+console_close(void)
 354+{
 355+	kill_child();
 356+}
 357+
 358+/* clang-format off */
 359+
 360+static Uint8 console_dei_childlive(void) { check_child(); return dev[CMD_LIVE]; }
 361+static Uint8 console_dei_childexit(void) { check_child(); return dev[CMD_EXIT]; }
 362+static void console_deo_vector(void) { console_vector = peek2(&dev[0x10]); }
 363+static void console_deo_stdout(void) { fputc(dev[0x18], stdout), fflush(stdout); }
 364+static void console_deo_stderr(void) { fputc(dev[0x19], stderr), fflush(stderr); }
 365+static void console_deo_debug1(void) { fprintf(stderr, "%02x", dev[0x1a]); }
 366+static void console_deo_debug2(void) { fprintf(stderr, "%02x%02x", dev[0x1a], dev[0x1b]); }
 367+
 368+/* clang-format on */
 369+
 370+/*
 371+@|Screen ------------------------------------------------------------ */
 372+
 373+static int screen_zoom = 1, screen_reqsize, screen_reqdraw;
 374+static int screen_width, screen_height, screen_wmar2, screen_hmar2;
 375+static int *screen_pixels, screen_palette[16];
 376+static Uint8 *screen_layers;
 377+
 378+void emu_redraw(void), emu_resize(void);
 379+
 380+static const Uint8 blend_lut[16][2][4] = {
 381+	{{0, 0, 1, 2}, {0, 0, 4, 8}},
 382+	{{0, 1, 2, 3}, {0, 4, 8, 12}},
 383+	{{0, 2, 3, 1}, {0, 8, 12, 4}},
 384+	{{0, 3, 1, 2}, {0, 12, 4, 8}},
 385+	{{1, 0, 1, 2}, {4, 0, 4, 8}},
 386+	{{1, 1, 2, 3}, {4, 4, 8, 12}},
 387+	{{1, 2, 3, 1}, {4, 8, 12, 4}},
 388+	{{1, 3, 1, 2}, {4, 12, 4, 8}},
 389+	{{2, 0, 1, 2}, {8, 0, 4, 8}},
 390+	{{2, 1, 2, 3}, {8, 4, 8, 12}},
 391+	{{2, 2, 3, 1}, {8, 8, 12, 4}},
 392+	{{2, 3, 1, 2}, {8, 12, 4, 8}},
 393+	{{3, 0, 1, 2}, {12, 0, 4, 8}},
 394+	{{3, 1, 2, 3}, {12, 4, 8, 12}},
 395+	{{3, 2, 3, 1}, {12, 8, 12, 4}},
 396+	{{3, 3, 1, 2}, {12, 12, 4, 8}}};
 397+
 398+
 399+static void
 400+system_deo_colorize(void)
 401+{
 402+	unsigned int i, shift, colors[4];
 403+	for(i = 0, shift = 4; i < 4; ++i, shift ^= 4) {
 404+		Uint8
 405+			r = dev[0x8 + i / 2] >> shift & 0xf,
 406+			g = dev[0xa + i / 2] >> shift & 0xf,
 407+			b = dev[0xc + i / 2] >> shift & 0xf;
 408+		colors[i] = 0x0f000000 | r << 16 | g << 8 | b;
 409+		colors[i] |= colors[i] << 4;
 410+	}
 411+	for(i = 0; i < 16; i++)
 412+		screen_palette[i] = colors[i < 4 ? i : i >> 2];
 413+	screen_reqdraw = 1;
 414+}
 415+
 416+static void
 417+screen_resize(int width, int height)
 418+{
 419+	if(width != screen_width || height != screen_height) {
 420+		int length;
 421+		screen_width = width, screen_wmar2 = width + 0x10;
 422+		poke2(&dev[0x22], screen_width);
 423+		screen_height = height, screen_hmar2 = height + 0x10;
 424+		poke2(&dev[0x24], screen_height);
 425+		length = screen_wmar2 * screen_hmar2;
 426+		screen_layers = realloc(screen_layers, length);
 427+		memset(screen_layers, 0, length);
 428+		screen_reqsize = screen_reqdraw = 1;
 429+	}
 430+}
 431+
 432+static void
 433+screen_redraw(void)
 434+{
 435+	if(screen_zoom == 1) {
 436+		int y, *dst_ptr = screen_pixels;
 437+		Uint8 *src_ptr = &screen_layers[8 * screen_wmar2 + 8];
 438+		for(y = 0; y < screen_height; y++) {
 439+			int *dst_end = dst_ptr + screen_width;
 440+			while(dst_ptr < dst_end)
 441+				*dst_ptr++ = screen_palette[*src_ptr++];
 442+			src_ptr += 0x10;
 443+		}
 444+	} else {
 445+		const int stride = screen_width * screen_zoom;
 446+		const int zoom_stride = screen_zoom * stride;
 447+		int x, y, k, l, row_base = 0, i_base = 8 * screen_wmar2 + 8;
 448+		for(y = 0; y < screen_height; y++, row_base += zoom_stride, i_base += screen_wmar2) {
 449+			int i = i_base, xs = 0;
 450+			for(x = 0; x < screen_width; x++, i++, xs += screen_zoom) {
 451+				const int c = screen_palette[screen_layers[i]];
 452+				int oo = row_base + xs;
 453+				for(k = 0; k < screen_zoom; k++, oo += stride)
 454+					for(l = 0; l < screen_zoom; l++)
 455+						screen_pixels[oo + l] = c;
 456+			}
 457+		}
 458+	}
 459+	screen_reqdraw = 0;
 460+}
 461+
 462+static void
 463+screen_update(void)
 464+{
 465+	if(screen_vector)
 466+		uxn_eval(screen_vector);
 467+	if(screen_reqsize) {
 468+		screen_reqsize = 0;
 469+		emu_resize();
 470+	}
 471+	if(screen_reqdraw)
 472+		screen_redraw();
 473+}
 474+
 475+static void
 476+screen_deo_pixel(void)
 477+{
 478+	const int ctrl = dev[0x2e];
 479+	const int hi = ctrl & 0x40;
 480+	const Uint8 mask = hi ? 0x3 : 0xc;
 481+	const Uint8 color = hi ? ((ctrl & 0x3) << 2) : (ctrl & 0x3);
 482+	const Uint16 x = rX, y = rY;
 483+	if(x < screen_width && y < screen_height) {
 484+		/* fill mode */
 485+		if(ctrl & 0x80) {
 486+			const int x1 = (ctrl & 0x10) ? 8 : x + 8;
 487+			const int x2 = (ctrl & 0x10) ? x : screen_width;
 488+			const int y1 = (ctrl & 0x20) ? 8 : y + 8;
 489+			const int y2 = (ctrl & 0x20) ? y : screen_height;
 490+			const int hor = (x2 + 8) - x1;
 491+			const int ver = (y2 + 8) - y1;
 492+			Uint8 *row = &screen_layers[y1 * screen_wmar2 + x1];
 493+			Uint8 *end = row + ver * screen_wmar2;
 494+			for(; row < end; row += screen_wmar2) {
 495+				int px;
 496+				Uint8 *dst = row;
 497+				for(px = 0; px < hor; px++)
 498+					dst[px] = (dst[px] & mask) | color;
 499+			}
 500+		}
 501+		/* pixel mode */
 502+		else {
 503+			Uint8 *dst = &screen_layers[(y + 8) * screen_wmar2 + (x + 8)];
 504+			*dst = (*dst & mask) | color;
 505+			if(rMX) rX += ctrl & 0x10 ? -1 : 1;
 506+			if(rMY) rY += ctrl & 0x20 ? -1 : 1;
 507+		}
 508+		screen_reqdraw = 1;
 509+	}
 510+}
 511+
 512+static void
 513+screen_deo_sprite(void)
 514+{
 515+	const int ctrl = dev[0x2f];
 516+	const int flipx = ctrl & 0x10, flipy = ctrl & 0x20;
 517+	const int dx = flipx ? -rDY : rDY, dy = flipy ? -rDX : rDX;
 518+	const int row_start = flipx ? 0 : 7, row_delta = flipx ? 1 : -1;
 519+	const int col_start = flipy ? 7 : 0, col_delta = flipy ? -1 : 1;
 520+	const int layer = ctrl & 0x40, layer_mask = layer ? 0x3 : 0xc;
 521+	const int is_2bpp = ctrl >> 7;
 522+	const int addr_incr = rMA << is_2bpp;
 523+	const int stride = screen_wmar2;
 524+	const int blend = ctrl & 0xf;
 525+	const int row_step = dy * stride;
 526+	const Uint8 opaque_mask = blend % 5;
 527+	const Uint8 *table = blend_lut[blend][layer >> 6];
 528+	int i, j, x = rX, y = rY;
 529+	int row_base = (y + 8) * stride;
 530+	for(i = 0; i <= rML; i++, x += dx, y += dy, rA += addr_incr, row_base += row_step) {
 531+		const Uint16 x0 = x + 8, y0 = y + 8;
 532+		if(x0 + 8 >= stride || y0 + 8 >= screen_hmar2) continue;
 533+		Uint8 *dst = screen_layers + row_base + x0;
 534+		const Uint8 *col = &ram[rA + col_start];
 535+		for(j = 0; j < 8; j++, dst += stride, col += col_delta) {
 536+			Uint8 *d = dst;
 537+			const int ch1 = *col;
 538+			const int ch2 = is_2bpp ? (col[8] << 1) : 0;
 539+			int row = row_start;
 540+			for(int k = 0; k < 8; k++, d++, row += row_delta) {
 541+				const int color = ((ch1 >> row) & 1) | ((ch2 >> row) & 2);
 542+				if(opaque_mask || color)
 543+					*d = (*d & layer_mask) | table[color];
 544+			}
 545+		}
 546+	}
 547+	screen_reqdraw = 1;
 548+	if(rMX) rX += flipx ? -rDX : rDX;
 549+	if(rMY) rY += flipy ? -rDY : rDY;
 550+}
 551+
 552+/* clang-format off */
 553+
 554+static Uint8 screen_dei_rx(void) { dev[0x29] = rX; return rX >> 8; }
 555+static Uint8 screen_dei_ry(void) { dev[0x2b] = rY; return rY >> 8; }
 556+static Uint8 screen_dei_ra(void) { dev[0x2d] = rA; return rA >> 8; }
 557+static void screen_deo_vector(void) { screen_vector = peek2(&dev[0x20]); }
 558+static void screen_deo_width(void) { screen_resize(peek2(&dev[0x22]) & 0xfff, screen_height & 0xfff); }
 559+static void screen_deo_height(void) { screen_resize(screen_width & 0xfff, peek2(&dev[0x24]) & 0xfff); }
 560+static void screen_deo_auto(void) { rMX = dev[0x26] & 0x1, rMY = dev[0x26] & 0x2, rMA = (dev[0x26] & 0x4) << 1, rML = dev[0x26] >> 4, rDX = rMX << 3, rDY = rMY << 2; }
 561+static void screen_deo_x(void) { rX = (Sint16)peek2(&dev[0x28]); }
 562+static void screen_deo_y(void) { rY = (Sint16)peek2(&dev[0x2a]); }
 563+static void screen_deo_addr(void) { rA = peek2(&dev[0x2c]); }
 564+
 565+/* clang-format on */
 566+
 567+/*
 568+@|Audio ------------------------------------------------------------- */
 569+#define MINIAUDIO_IMPLEMENTATION
 570+#include "miniaudio.h"
 571+
 572+static unsigned audio_rate;
 573+
 574+static Sint32
 575+envelope(UxnAudio *c, Uint32 age)
 576+{
 577+	if(!c->r)
 578+		return 0x0888;
 579+	if(age < c->a)
 580+		return 0x0888 * age / c->a;
 581+	if(age < c->d)
 582+		return 0x0444 * (2 * c->d - c->a - age) / (c->d - c->a);
 583+	if(age < c->s)
 584+		return 0x0444;
 585+	if(age < c->r)
 586+		return 0x0444 * (c->r - age) / (c->r - c->s);
 587+	c->advance = 0;
 588+	return 0x0000;
 589+}
 590+
 591+static Uint8
 592+audio_get_vu(int instance)
 593+{
 594+	int i;
 595+	UxnAudio *c = &uxn_audio[instance];
 596+	Sint32 sum[2] = {0, 0};
 597+	if(!c->advance || !c->period)
 598+		return 0;
 599+	for(i = 0; i < 2; i++) {
 600+		if(!c->volume[i])
 601+			continue;
 602+		sum[i] = 1 + envelope(c, c->age) * c->volume[i] / 0x800;
 603+		if(sum[i] > 0xf)
 604+			sum[i] = 0xf;
 605+	}
 606+	return (sum[0] << 4) | sum[1];
 607+}
 608+
 609+static Uint16
 610+audio_get_position(int instance)
 611+{
 612+	return uxn_audio[instance].i;
 613+}
 614+
 615+static void
 616+audio_start(int instance, Uint8 *d)
 617+{
 618+	UxnAudio *c = &uxn_audio[instance];
 619+	Uint8 pitch = d[0xf] & 0x7f;
 620+	Uint16 addr = peek2(d + 0xc);
 621+	Uint16 adsr = peek2(d + 0x8);
 622+	c->len = peek2(d + 0xa);
 623+	if(c->len > 0x10000 - addr)
 624+		c->len = 0x10000 - addr;
 625+	c->addr = &ram[addr];
 626+	c->volume[0] = d[0xe] >> 4;
 627+	c->volume[1] = d[0xe] & 0xf;
 628+	c->repeat = !(d[0xf] & 0x80);
 629+	if(pitch < 108 && c->len)
 630+		c->advance = advances[pitch % 12] >> (8 - pitch / 12);
 631+	else {
 632+		c->advance = 0;
 633+		return;
 634+	}
 635+	c->a = ADSR_STEP * (adsr >> 12);
 636+	c->d = ADSR_STEP * (adsr >> 8 & 0xf) + c->a;
 637+	c->s = ADSR_STEP * (adsr >> 4 & 0xf) + c->d;
 638+	c->r = ADSR_STEP * (adsr >> 0 & 0xf) + c->s;
 639+	c->age = 0;
 640+	c->i = 0;
 641+	if(c->len <= 0x100)
 642+		c->period = NOTE_PERIOD * 337 / 2 / c->len;
 643+	else
 644+		c->period = NOTE_PERIOD;
 645+  	/* Debug Print */
 646+	fprintf(stderr, "[APU %d] PLAY: addr=0x%04x len=%u adsr=0x%04x vol=%d/%d pitch=%d repeat=%d\n",
 647+	        instance, addr, c->len, adsr, c->volume[0], c->volume[1], pitch, c->repeat);
 648+
 649+}
 650+
 651+#define AUDIO_CHAN(n, base)							\
 652+static void audio##n##_deo_vector      (void) {}				\
 653+static void audio##n##_deo_volume      (void) {}				\
 654+static void audio##n##_deo_pitch       (void) {					\
 655+	       audio_start(n, &dev[base]);					\
 656+}										\
 657+static Uint8 audio##n##_dei_position   (void) {					\
 658+	return audio_get_position(n) >> 8;					\
 659+}										\
 660+static Uint8 audio##n##_dei_position_lo(void) {					\
 661+	return (Uint8)audio_get_position(n);					\
 662+}										\
 663+static Uint8 audio##n##_dei_output     (void) {					\
 664+	return audio_get_vu(n);							\
 665+}
 666+
 667+AUDIO_CHAN(0, 0x30)
 668+AUDIO_CHAN(1, 0x40)
 669+AUDIO_CHAN(2, 0x50)
 670+AUDIO_CHAN(3, 0x60)
 671+
 672+
 673+static ma_device audio_device;
 674+#define RING_SIZE 8192
 675+static Sint16 audio_ring[RING_SIZE];
 676+static volatile int audio_ring_w = 0;
 677+static volatile int audio_ring_r = 0;
 678+
 679+static void
 680+data_callback(ma_device *device, void *output, const void *input, ma_uint32 framecount)
 681+{
 682+	Sint16 *out = (Sint16*)output;
 683+	ma_uint32 i;
 684+	int w = audio_ring_w;
 685+	int r = audio_ring_r;
 686+	(void)device; (void)input;
 687+	for(i = 0; i < framecount * 2; i++) {
 688+		if(r != w) {
 689+			out[i] = audio_ring[r];
 690+			r = (r + 1) % RING_SIZE;
 691+		} else
 692+			out[i] = 0;
 693+	}
 694+	audio_ring_r = r;
 695+}
 696+
 697+static int audio_initialized = 0;
 698+
 699+void
 700+audio_init(void)
 701+{
 702+	ma_device_config config = ma_device_config_init(ma_device_type_playback);
 703+	config.playback.format = ma_format_s16;
 704+	config.playback.channels = 2;
 705+	config.sampleRate = SAMPLE_FREQUENCY;
 706+	config.dataCallback = data_callback;
 707+	if(ma_device_init(NULL, &config, &audio_device) == MA_SUCCESS) {
 708+		ma_device_start(&audio_device);
 709+		audio_initialized = 1;
 710+	}
 711+	audio_rate = SAMPLE_FREQUENCY;
 712+}
 713+
 714+void
 715+audio_write(Sint16 *buf, unsigned count)
 716+{
 717+	(void)buf;
 718+	(void)count;
 719+	// that white cat thing presenting his hand in a confused matter
 720+	unsigned i;
 721+	int w = audio_ring_w;
 722+	for(i = 0; i < count * 2; i++) {
 723+		audio_ring[w] = buf[i];
 724+		w = (w + 1) % RING_SIZE;
 725+	}
 726+	audio_ring_w = w;
 727+}
 728+
 729+static void
 730+audio_finished_handler(int instance)
 731+{
 732+	Uint8 *port_value = &dev[0x30 + 0x10 * instance];
 733+	Uint16 vector = port_value[0] << 8 | port_value[1];
 734+
 735+	/* Debug Print */
 736+	fprintf(stderr, "[APU %d] FINISHED: vector=0x%04x\n", instance, vector);
 737+
 738+	if(vector)
 739+		uxn_eval(vector);
 740+}
 741+
 742+static int
 743+audio_render(int instance, Sint16 *sample, Sint16 *end)
 744+{
 745+	UxnAudio *c = &uxn_audio[instance];
 746+	Sint32 s;
 747+	if(!c->advance || !c->period)
 748+		return 0;
 749+	while(sample < end) {
 750+		c->count += c->advance;
 751+		c->i += c->count / c->period;
 752+		c->count %= c->period;
 753+		if(c->i >= c->len) {
 754+			if(!c->repeat) {
 755+				c->advance = 0;
 756+				break;
 757+			}
 758+			c->i %= c->len;
 759+		}
 760+		s = (Sint8)(*(c->addr + c->i) + 0x80) * envelope(c, c->age++);
 761+		*sample++ += s * c->volume[0] / 0x180;
 762+		*sample++ += s * c->volume[1] / 0x180;
 763+	}
 764+	if(!c->advance)
 765+		audio_finished_handler(instance);
 766+	return 1;
 767+}
 768+
 769+static void
 770+audio_output(void)
 771+{
 772+	int c;
 773+	Sint16 mix[2048];
 774+	int r = audio_ring_r;
 775+	int w = audio_ring_w;
 776+	int fill = (w - r + RING_SIZE) % RING_SIZE;
 777+	if(fill >= 4096)
 778+		return;
 779+	unsigned count = (4096 - fill) / 2;
 780+	if(count > 1024)
 781+		count = 1024;
 782+	memset(mix, 0, count * 2 * sizeof(Sint16));
 783+	for(c = 0; c < POLYPHONY; c++) {
 784+		audio_render(c, mix, mix + count * 2);
 785+	}
 786+	audio_write(mix, count);
 787+}
 788+
 789+/*
 790+@|Controller -------------------------------------------------------- */
 791+
 792+static void
 793+controller_down(Uint8 mask)
 794+{
 795+	if(mask) {
 796+		dev[0x82] |= mask;
 797+		if(controller_vector)
 798+			uxn_eval(controller_vector);
 799+	}
 800+}
 801+
 802+static void
 803+controller_up(Uint8 mask)
 804+{
 805+	if(mask) {
 806+		dev[0x82] &= (~mask);
 807+		if(controller_vector)
 808+			uxn_eval(controller_vector);
 809+	}
 810+}
 811+
 812+static void
 813+controller_key(Uint8 key)
 814+{
 815+	if(key) {
 816+		dev[0x83] = key;
 817+		if(controller_vector)
 818+			uxn_eval(controller_vector);
 819+		dev[0x83] = 0;
 820+	}
 821+}
 822+
 823+void
 824+controller_deo_vector(void)
 825+{
 826+	controller_vector = peek2(&dev[0x80]);
 827+}
 828+
 829+/*
 830+@|Mouse ------------------------------------------------------------- */
 831+
 832+static void
 833+mouse_down(Uint8 mask)
 834+{
 835+	dev[0x96] |= mask;
 836+	if(mouse_vector)
 837+		uxn_eval(mouse_vector);
 838+}
 839+
 840+static void
 841+mouse_up(Uint8 mask)
 842+{
 843+	dev[0x96] &= (~mask);
 844+	if(mouse_vector)
 845+		uxn_eval(mouse_vector);
 846+}
 847+
 848+static void
 849+mouse_pos(Uint16 x, Uint16 y)
 850+{
 851+	poke2(&dev[0x92], x);
 852+	poke2(&dev[0x94], y);
 853+	if(mouse_vector)
 854+		uxn_eval(mouse_vector);
 855+}
 856+
 857+static void
 858+mouse_scroll(Uint16 x, Uint16 y)
 859+{
 860+	poke2(&dev[0x9a], x);
 861+	poke2(&dev[0x9c], -y);
 862+	if(mouse_vector)
 863+		uxn_eval(mouse_vector);
 864+	poke2(&dev[0x9a], 0);
 865+	poke2(&dev[0x9c], 0);
 866+}
 867+
 868+void
 869+mouse_deo_vector(void)
 870+{
 871+	mouse_vector = peek2(&dev[0x90]);
 872+}
 873+
 874+/*
 875+@|File -------------------------------------------------------------- */
 876+
 877+#include <dirent.h>
 878+#include <sys/stat.h>
 879+#include <string.h>
 880+
 881+typedef struct {
 882+	union {
 883+		FILE *f;
 884+		DIR *dir;
 885+	} h;
 886+	char *filepath;
 887+	enum { IDLE,
 888+		FILE_READ,
 889+		FILE_WRITE,
 890+		DIR_READ,
 891+		DIR_WRITE } state;
 892+} UxnFile;
 893+
 894+static UxnFile ufs[2];
 895+
 896+static inline unsigned int
 897+put_text(Uint8 *dest, unsigned int *length, const char *text)
 898+{
 899+	Uint8 *anchor = dest;
 900+	while(*text && *length) {
 901+		*dest = *text++, dest++;
 902+		*length = *length - 1;
 903+	}
 904+	*dest = 0;
 905+	return dest - anchor;
 906+}
 907+
 908+static inline unsigned int
 909+put_size(Uint8 *dest, unsigned int *length, unsigned int len, unsigned int size)
 910+{
 911+	unsigned int i;
 912+	for(i = 0, dest += len; i < len && *length; i++) {
 913+		*(--dest) = "0123456789abcdef"[(Uint8)(size & 0xf)];
 914+		size >>= 4;
 915+		*length = *length - 1;
 916+	}
 917+	return len;
 918+}
 919+
 920+static unsigned int
 921+put_fill(Uint8 *dest, unsigned int *length, unsigned int len, char c)
 922+{
 923+	unsigned int i;
 924+	for(i = 0; i < len && *length; i++) {
 925+		*dest++ = c;
 926+		*length = *length - 1;
 927+	}
 928+	return len;
 929+}
 930+
 931+static unsigned int
 932+put_stat(Uint8 *dest, unsigned int *length, unsigned int len, unsigned int size, unsigned int err, unsigned int dir)
 933+{
 934+	unsigned int max = 1 << (len << 2);
 935+	if(dir)
 936+		return put_fill(dest, length, len, '-');
 937+	else if(err)
 938+		return put_fill(dest, length, len, '!');
 939+	else if(size < max)
 940+		return put_size(dest, length, len, size);
 941+	return put_fill(dest, length, len, '?');
 942+}
 943+
 944+static unsigned int
 945+put_statfile(Uint8 *dest, unsigned int *length, char *pathbuf, unsigned int prefix_len, unsigned int cap, const char *basename)
 946+{
 947+	unsigned int err, dir;
 948+	struct stat st;
 949+	Uint8 *anchor = dest;
 950+	char *p = pathbuf + prefix_len, *end = pathbuf + cap - 1;
 951+	const char *b = basename;
 952+	while(*b && p < end)
 953+		*p++ = *b++;
 954+	*p = 0;
 955+	err = stat(pathbuf, &st);
 956+	dir = !err && S_ISDIR(st.st_mode);
 957+	dest += put_stat(dest, length, 4, st.st_size, err, dir);
 958+	dest += put_text(dest, length, "\t");
 959+	dest += put_text(dest, length, basename);
 960+	dest += put_text(dest, length, dir ? "/\n" : "\n");
 961+	return dest - anchor;
 962+}
 963+
 964+static unsigned int
 965+put_fdir(Uint8 *dest, unsigned int *length, const char *filepath, DIR *dir)
 966+{
 967+	Uint8 *p = dest, *end = dest + *length - 1;
 968+	struct dirent *de;
 969+	char pathbuf[0x200];
 970+	unsigned int prefix_len = 0;
 971+	char c = '/';
 972+	const char *fp = filepath;
 973+	while(*fp && prefix_len < sizeof pathbuf - 1)
 974+		c = *fp++, pathbuf[prefix_len++] = c;
 975+	if(c != '/' && prefix_len < sizeof pathbuf - 1)
 976+		pathbuf[prefix_len++] = '/';
 977+	while((de = readdir(dir)) && p < end) {
 978+		const char *name = de->d_name;
 979+		if(name[0] == '.' && (!name[1] || (name[1] == '.' && !name[2])))
 980+			continue;
 981+		p += put_statfile(p, length, pathbuf, prefix_len, sizeof pathbuf, name);
 982+	}
 983+	*p = 0;
 984+	return p - dest;
 985+}
 986+
 987+static unsigned int
 988+is_dir_path(const char *p)
 989+{
 990+	const char *end = p;
 991+	while(*end) end++;
 992+	return end > p && end[-1] == '/';
 993+}
 994+
 995+static unsigned int
 996+is_dir_real(char *p)
 997+{
 998+	struct stat st;
 999+	return stat(p, &st) == 0 && S_ISDIR(st.st_mode);
1000+}
1001+
1002+static unsigned int
1003+file_write_dir(const char *p)
1004+{
1005+	char buf[0x200];
1006+	char *s = buf, *end = buf + sizeof buf - 1;
1007+	unsigned int ok = 1;
1008+	while(*p && ok && s < end) {
1009+		*s++ = *p;
1010+		if(*p++ == '/') {
1011+			s[-1] = '\0';
1012+			ok = is_dir_real(buf) || mkdir(buf, 0755) == 0;
1013+			s[-1] = '/';
1014+		}
1015+	}
1016+	return ok && !*p;
1017+}
1018+
1019+static void
1020+file_reset(unsigned int id)
1021+{
1022+	switch(ufs[id].state) {
1023+	case FILE_READ:
1024+	case FILE_WRITE: fclose(ufs[id].h.f); break;
1025+	case DIR_READ: closedir(ufs[id].h.dir); break;
1026+	default: break;
1027+	}
1028+	ufs[id].state = IDLE;
1029+}
1030+
1031+static unsigned int
1032+file_open(unsigned int id, unsigned int want_write, Uint8 flags)
1033+{
1034+	UxnFile *u = &ufs[id];
1035+	struct stat st;
1036+	if(want_write) {
1037+		if(u->state == FILE_WRITE || u->state == DIR_WRITE)
1038+			return 1;
1039+		file_reset(id);
1040+		file_write_dir(u->filepath);
1041+		if(is_dir_path(u->filepath)) {
1042+			u->state = is_dir_real(u->filepath) ? DIR_WRITE : IDLE;
1043+		} else if((u->h.f = fopen(u->filepath, (flags & 0x01) ? "ab" : "wb"))) {
1044+			u->state = FILE_WRITE;
1045+		}
1046+	} else {
1047+		if(u->state == FILE_READ || u->state == DIR_READ)
1048+			return 1;
1049+		file_reset(id);
1050+		if(stat(u->filepath, &st) == 0 && S_ISDIR(st.st_mode)) {
1051+			if((u->h.dir = opendir(u->filepath)))
1052+				u->state = DIR_READ;
1053+		} else if((u->h.f = fopen(u->filepath, "rb"))) {
1054+			u->state = FILE_READ;
1055+		}
1056+	}
1057+	return u->state != IDLE;
1058+}
1059+
1060+static unsigned int
1061+file_init(unsigned int id, Uint16 addr)
1062+{
1063+	file_reset(id);
1064+	ufs[id].filepath = (char *)&ram[addr];
1065+	return 0;
1066+}
1067+
1068+static unsigned int
1069+file_read(unsigned int id, Uint16 addr, unsigned int len)
1070+{
1071+	void *dest = &ram[addr];
1072+	unsigned int n, length = len;
1073+	if(ufs[id].filepath == 0)
1074+		return 0;
1075+	if(addr + len > 0x10000)
1076+		length = 0x10000 - addr;
1077+	if(!file_open(id, 0, 0))
1078+		return 0;
1079+	if(ufs[id].state == FILE_READ)
1080+		n = fread(dest, 1, length, ufs[id].h.f);
1081+	else
1082+		n = put_fdir(dest, &length, ufs[id].filepath, ufs[id].h.dir);
1083+	if(len > 0 && n == 0) file_reset(id);
1084+	return n;
1085+}
1086+
1087+static unsigned int
1088+file_write(unsigned int id, Uint16 addr, unsigned int len, Uint8 flags)
1089+{
1090+	if(ufs[id].filepath == 0)
1091+		return 0;
1092+	if(addr + len > 0x10000)
1093+		len = 0x10000 - addr;
1094+	if(!file_open(id, 1, flags))
1095+		return 0;
1096+	if(ufs[id].state == FILE_WRITE) {
1097+		unsigned int ret = fwrite(&ram[addr], 1, len, ufs[id].h.f);
1098+		return (ret > 0 && fflush(ufs[id].h.f) != 0) ? 0 : ret;
1099+	}
1100+	return is_dir_real(ufs[id].filepath);
1101+}
1102+
1103+static unsigned int
1104+file_stat(unsigned int id, Uint16 addr, unsigned int len)
1105+{
1106+	unsigned int err, dir;
1107+	unsigned int length = len;
1108+	struct stat st;
1109+	if(ufs[id].filepath == 0)
1110+		return 0;
1111+	if(addr + len > 0x10000)
1112+		len = 0x10000 - addr;
1113+	err = stat(ufs[id].filepath, &st);
1114+	dir = !err && S_ISDIR(st.st_mode);
1115+	return put_stat(&ram[addr], &length, len, st.st_size, err, dir);
1116+}
1117+
1118+static unsigned int
1119+file_delete(unsigned int id)
1120+{
1121+	if(ufs[id].filepath == 0)
1122+		return 0;
1123+	return !unlink(ufs[id].filepath);
1124+}
1125+
1126+/* clang-format off */
1127+
1128+static void filea_deo_stat(void) { poke2(&dev[0xa2], file_stat(0, peek2(&dev[0xa4]), rL1)); }
1129+static void filea_deo_delete(void) { poke2(&dev[0xa2], file_delete(0)); }
1130+static void filea_deo_name(void) { poke2(&dev[0xa2], file_init(0, peek2(&dev[0xa8]))); }
1131+static void filea_deo_length(void) { rL1 = peek2(&dev[0xaa]); }
1132+static void filea_deo_read(void) { poke2(&dev[0xa2], file_read(0, peek2(&dev[0xac]), rL1)); }
1133+static void filea_deo_write(void) { poke2(&dev[0xa2], file_write(0, peek2(&dev[0xae]), rL1, dev[0xa7])); }
1134+static void fileb_deo_stat(void) { poke2(&dev[0xb2], file_stat(1, peek2(&dev[0xb4]), rL2)); }
1135+static void fileb_deo_delete(void) { poke2(&dev[0xb2], file_delete(1)); }
1136+static void fileb_deo_name(void) { poke2(&dev[0xb2], file_init(1, peek2(&dev[0xb8]))); }
1137+static void fileb_deo_length(void) { rL2 = peek2(&dev[0xba]); }
1138+static void fileb_deo_read(void) { poke2(&dev[0xb2], file_read(1, peek2(&dev[0xbc]), rL2)); }
1139+static void fileb_deo_write(void) { poke2(&dev[0xb2], file_write(1, peek2(&dev[0xbe]), rL2, dev[0xb7])); }
1140+
1141+/* clang-format on */
1142+
1143+/*
1144+@|Datetime ---------------------------------------------------------- */
1145+
1146+int datetime_busy;
1147+time_t datetime_seconds;
1148+struct tm *datetime_t, datetime_zt = {0};
1149+
1150+void
1151+datetime_update(void)
1152+{
1153+	if(!datetime_busy) {
1154+		datetime_seconds = time(NULL);
1155+		datetime_t = localtime(&datetime_seconds);
1156+		if(datetime_t == NULL)
1157+			datetime_t = &datetime_zt;
1158+		datetime_busy = 1;
1159+	}
1160+}
1161+
1162+/* clang-format off */
1163+
1164+static Uint8 datetime_dei_y(void) {
1165+	datetime_update();
1166+	const int v = (datetime_t->tm_year + 1900);
1167+	dev[0xc1] = v;
1168+	return v >> 8; }
1169+static Uint8 datetime_dei_mon(void) { datetime_update(); return datetime_t->tm_mon; }
1170+static Uint8 datetime_dei_day(void) { datetime_update(); return datetime_t->tm_mday; }
1171+static Uint8 datetime_dei_hou(void) { datetime_update(); return datetime_t->tm_hour; }
1172+static Uint8 datetime_dei_min(void) { datetime_update(); return datetime_t->tm_min; }
1173+static Uint8 datetime_dei_sec(void) { datetime_update(); return datetime_t->tm_sec; }
1174+static Uint8 datetime_dei_wday(void) { datetime_update(); return datetime_t->tm_wday; }
1175+static Uint8 datetime_dei_yday(void) {
1176+	datetime_update();
1177+	const int v = datetime_t->tm_yday;
1178+	dev[0xc9] = v;
1179+	return v >> 8; }
1180+static Uint8 datetime_dei_dst(void) { datetime_update(); return datetime_t->tm_isdst; }
1181+
1182+/* clang-format on */
1183+
1184+/*
1185+@|Core -------------------------------------------------------------- */
1186+
1187+static const dei_handler dei_handlers[256] = {
1188+	[0x04] = system_dei_wst,
1189+	[0x05] = system_dei_rst,
1190+	[0x15] = console_dei_childlive,
1191+	[0x16] = console_dei_childexit,
1192+	[0x28] = screen_dei_rx,
1193+	[0x2a] = screen_dei_ry,
1194+	[0x2c] = screen_dei_ra,
1195+	[0x32] = audio0_dei_position,
1196+	[0x33] = audio0_dei_position_lo,
1197+	[0x34] = audio0_dei_output,
1198+	[0x42] = audio1_dei_position,
1199+	[0x43] = audio1_dei_position_lo,
1200+	[0x44] = audio1_dei_output,
1201+	[0x52] = audio2_dei_position,
1202+	[0x53] = audio2_dei_position_lo,
1203+	[0x54] = audio2_dei_output,
1204+	[0x62] = audio3_dei_position,
1205+	[0x63] = audio3_dei_position_lo,
1206+	[0x64] = audio3_dei_output,
1207+	[0xc0] = datetime_dei_y,
1208+	[0xc2] = datetime_dei_mon,
1209+	[0xc3] = datetime_dei_day,
1210+	[0xc4] = datetime_dei_hou,
1211+	[0xc5] = datetime_dei_min,
1212+	[0xc6] = datetime_dei_sec,
1213+	[0xc7] = datetime_dei_wday,
1214+	[0xc8] = datetime_dei_yday,
1215+	[0xca] = datetime_dei_dst};
1216+
1217+static const deo_handler deo_handlers[256] = {
1218+	[0x03] = system_deo_expansion,
1219+	[0x04] = system_deo_wst,
1220+	[0x05] = system_deo_rst,
1221+	[0x09] = system_deo_colorize,
1222+	[0x0b] = system_deo_colorize,
1223+	[0x0d] = system_deo_colorize,
1224+	[0x0e] = system_deo_print,
1225+	[0x11] = console_deo_vector,
1226+	[0x18] = console_deo_stdout,
1227+	[0x19] = console_deo_stderr,
1228+	[0x1a] = console_deo_debug1,
1229+	[0x1b] = console_deo_debug2,
1230+	[0x1f] = console_deo_fork,
1231+	[0x21] = screen_deo_vector,
1232+	[0x23] = screen_deo_width,
1233+	[0x25] = screen_deo_height,
1234+	[0x26] = screen_deo_auto,
1235+	[0x29] = screen_deo_x,
1236+	[0x2b] = screen_deo_y,
1237+	[0x2d] = screen_deo_addr,
1238+	[0x2e] = screen_deo_pixel,
1239+	[0x2f] = screen_deo_sprite,
1240+	[0x30] = audio0_deo_vector,
1241+	[0x3e] = audio0_deo_volume,
1242+	[0x3f] = audio0_deo_pitch,
1243+	[0x40] = audio1_deo_vector,
1244+	[0x4e] = audio1_deo_volume,
1245+	[0x4f] = audio1_deo_pitch,
1246+	[0x50] = audio2_deo_vector,
1247+	[0x5e] = audio2_deo_volume,
1248+	[0x5f] = audio2_deo_pitch,
1249+	[0x60] = audio3_deo_vector,
1250+	[0x6e] = audio3_deo_volume,
1251+	[0x6f] = audio3_deo_pitch,
1252+	[0x81] = controller_deo_vector,
1253+	[0x91] = mouse_deo_vector,
1254+	[0xa5] = filea_deo_stat,
1255+	[0xa6] = filea_deo_delete,
1256+	[0xa9] = filea_deo_name,
1257+	[0xab] = filea_deo_length,
1258+	[0xad] = filea_deo_read,
1259+	[0xaf] = filea_deo_write,
1260+	[0xb5] = fileb_deo_stat,
1261+	[0xb6] = fileb_deo_delete,
1262+	[0xb9] = fileb_deo_name,
1263+	[0xbb] = fileb_deo_length,
1264+	[0xbd] = fileb_deo_read,
1265+	[0xbf] = fileb_deo_write};
1266+
1267+static inline Uint8
1268+emu_dei(const Uint8 port)
1269+{
1270+	dei_handler h = dei_handlers[port];
1271+	return h ? h() : dev[port];
1272+}
1273+
1274+static inline void
1275+emu_deo(const Uint8 port, const Uint8 value)
1276+{
1277+	deo_handler h = deo_handlers[port];
1278+	dev[port] = value;
1279+	if(h) h();
1280+}
1281+
1282+/* clang-format off */
1283+
1284+#define Ld1(o)      stk[r][(Uint8)(ptr[r]-(o))]
1285+#define Ld2(o)      (Ld1(o) << 8 | stk[r][(Uint8)(ptr[r]-(o)+1)])
1286+#define Ldx(o8,o16) d ? Ld2(o16) : Ld1(o8)
1287+#define Re1(m)      ptr[r] -= m;
1288+#define Rex(m1,m2)  ptr[r] -= d ? m2 : m1;
1289+#define Pu1(s,v)    stk[s][ptr[s]++] = v;
1290+#define Pu2(s,v)    Pu1(s,v>>8) Pu1(s,v)
1291+#define Pux(s,v)    if(d) Pu1(s,(v)>>8) Pu1(s,v)
1292+#define Lda(s,o)    Pu1(r,ram[o]) if(d) Pu1(r,ram[(s)(o+1)])
1293+#define Sta(s,o,u)  if(d) ram[o]=u>>8, ram[(s)(o+1)]=u; else ram[o]=u;
1294+#define Dei         Pu1(r,emu_dei(x)) if(d) Pu1(r,dev[(x+1)&0xff])
1295+#define Deo         if(d) dev[x]=y>>8, emu_deo(x+1,y); else emu_deo(x,y);
1296+
1297+#define MUTE (void)x;(void)y;(void)z;(void)r;(void)d;
1298+
1299+#define OP(opc,X,Y,Z,K,P) \
1300+	case (opc)|0x00: { const int d=0,r=0,x=X,y=Y,z=Z; K P MUTE } break; \
1301+	case (opc)|0x20: { const int d=1,r=0,x=X,y=Y,z=Z; K P MUTE } break; \
1302+	case (opc)|0x40: { const int d=0,r=1,x=X,y=Y,z=Z; K P MUTE } break; \
1303+	case (opc)|0x60: { const int d=1,r=1,x=X,y=Y,z=Z; K P MUTE } break; \
1304+	case (opc)|0x80: { const int d=0,r=0,x=X,y=Y,z=Z; P MUTE } break; \
1305+	case (opc)|0xa0: { const int d=1,r=0,x=X,y=Y,z=Z; P MUTE } break; \
1306+	case (opc)|0xc0: { const int d=0,r=1,x=X,y=Y,z=Z; P MUTE } break; \
1307+	case (opc)|0xe0: { const int d=1,r=1,x=X,y=Y,z=Z; P MUTE } break;
1308+
1309+static unsigned int
1310+uxn_eval(Uint16 start_pc)
1311+{
1312+	Uint16 pc = start_pc;
1313+	for(;;)
1314+	switch(ram[pc++]) {
1315+	/* BRK */ case 0x00: return 1;
1316+	/* JCI */ case 0x20: { Uint16 a=ram[pc]<<8|ram[pc+1]; pc+=2; if(stk[0][--ptr[0]]) pc+=a; } break;
1317+	/* JMI */ case 0x40: { Uint16 a=ram[pc]<<8|ram[pc+1]; pc+=2+a; } break;
1318+	/* JSI */ case 0x60: { Uint16 a=ram[pc]<<8|ram[pc+1]; const Uint8 d=1; pc+=2; Pux(1,pc); pc+=a; } break;
1319+	/* LIT */ case 0x80: { const Uint8 r=0,d=0; Lda(Uint16,pc); pc+=1; } break;
1320+	/* LI2 */ case 0xa0: { const Uint8 r=0,d=1; Lda(Uint16,pc); pc+=2; } break;
1321+	/* LIr */ case 0xc0: { const Uint8 r=1,d=0; Lda(Uint16,pc); pc+=1; } break;
1322+	/* L2r */ case 0xe0: { const Uint8 r=1,d=1; Lda(Uint16,pc); pc+=2; } break;
1323+	/* INC */ OP(0x01, Ldx(1,2),0,       0,       Rex(1,2),Pux(r,x+1))
1324+	/* POP */ OP(0x02, 0,       0,       0,       Rex(1,2),{})
1325+	/* NIP */ OP(0x03, Ldx(1,2),0,       0,       Rex(2,4),Pux(r,x))
1326+	/* SWP */ OP(0x04, Ldx(1,2),Ldx(2,4),0,       Rex(2,4),Pux(r,x) Pux(r,y))
1327+	/* ROT */ OP(0x05, Ldx(1,2),Ldx(2,4),Ldx(3,6),Rex(3,6),Pux(r,y) Pux(r,x) Pux(r,z))
1328+	/* DUP */ OP(0x06, Ldx(1,2),0,       0,       Rex(1,2),Pux(r,x) Pux(r,x))
1329+	/* OVR */ OP(0x07, Ldx(1,2),Ldx(2,4),0,       Rex(2,4),Pux(r,y) Pux(r,x) Pux(r,y))
1330+	/* EQU */ OP(0x08, Ldx(1,2),Ldx(2,4),0,       Rex(2,4),Pu1(r,x==y))
1331+	/* NEQ */ OP(0x09, Ldx(1,2),Ldx(2,4),0,       Rex(2,4),Pu1(r,y!=x))
1332+	/* GTH */ OP(0x0a, Ldx(1,2),Ldx(2,4),0,       Rex(2,4),Pu1(r,y>x))
1333+	/* LTH */ OP(0x0b, Ldx(1,2),Ldx(2,4),0,       Rex(2,4),Pu1(r,y<x))
1334+	/* JMP */ OP(0x0c, Ldx(1,2),0,       0,       Rex(1,2),pc=d?x:(pc+(Sint8)x);)
1335+	/* JCN */ OP(0x0d, Ldx(1,2),Ld1(d+2),0,       Rex(2,3),if(y) pc=d?x:(pc+(Sint8)x);)
1336+	/* JSR */ OP(0x0e, Ldx(1,2),0,       0,       Rex(1,2),Pu2(!r,pc) pc=d?x:(pc+(Sint8)x);)
1337+	/* STH */ OP(0x0f, Ldx(1,2),0,       0,       Rex(1,2),Pux(!r,x))
1338+	/* LDZ */ OP(0x10, Ld1(1),  0,       0,       Re1(1),  Lda(Uint8,x))
1339+	/* STZ */ OP(0x11, Ld1(1),  Ldx(2,3),0,       Rex(2,3),Sta(Uint8,x,y))
1340+	/* LDR */ OP(0x12, Ld1(1),  0,       0,       Re1(1),  Uint16 t=pc+(Sint8)x; Lda(Uint16,t))
1341+	/* STR */ OP(0x13, Ld1(1),  Ldx(2,3),0,       Rex(2,3),Uint16 t=pc+(Sint8)x; Sta(Uint16,t,y))
1342+	/* LDA */ OP(0x14, Ld2(2),  0,       0,       Re1(2),  Lda(Uint16,x))
1343+	/* STA */ OP(0x15, Ld2(2),  Ldx(3,4),0,       Rex(3,4),Sta(Uint16,x,y))
1344+	/* DEI */ OP(0x16, Ld1(1),  0,       0,       Re1(1),  Dei)
1345+	/* DEO */ OP(0x17, Ld1(1),  Ldx(2,3),0,       Rex(2,3),Deo)
1346+	/* ADD */ OP(0x18, Ldx(1,2),Ldx(2,4),0,       Rex(2,4),Pux(r,y+x))
1347+	/* SUB */ OP(0x19, Ldx(1,2),Ldx(2,4),0,       Rex(2,4),Pux(r,y-x))
1348+	/* MUL */ OP(0x1a, Ldx(1,2),Ldx(2,4),0,       Rex(2,4),Pux(r,y*x))
1349+	/* DIV */ OP(0x1b, Ldx(1,2),Ldx(2,4),0,       Rex(2,4),Pux(r,x?y/x:0))
1350+	/* AND */ OP(0x1c, Ldx(1,2),Ldx(2,4),0,       Rex(2,4),Pux(r,y&x))
1351+	/* ORA */ OP(0x1d, Ldx(1,2),Ldx(2,4),0,       Rex(2,4),Pux(r,y|x))
1352+	/* EOR */ OP(0x1e, Ldx(1,2),Ldx(2,4),0,       Rex(2,4),Pux(r,y^x))
1353+	/* SFT */ OP(0x1f, Ld1(1)  ,Ldx(2,3),0,       Rex(2,3),Pux(r,(y>>(x&0xf))<<(x>>4)))
1354+	}
1355+	return 0;
1356+}
1357+
1358+/* clang-format on */
1359+
1360+static struct wl_display *emu_dpy;
1361+static struct wl_registry *emu_reg;
1362+static struct wl_compositor *emu_comp;
1363+static struct wl_shm *emu_shm;
1364+static struct wl_seat *emu_seat;
1365+static struct wl_pointer *emu_ptr;
1366+static struct wl_keyboard *emu_kbd;
1367+static struct xdg_wm_base *emu_wm;
1368+static struct wl_surface *emu_surf;
1369+static struct xdg_surface *emu_xdgsurf;
1370+static struct xdg_toplevel *emu_top;
1371+static struct wl_buffer *emu_bufs[2];
1372+static struct zwlr_layer_shell_v1 *emu_layer_shell;
1373+static struct zwlr_layer_surface_v1 *emu_layer_surf;
1374+static int use_layer_shell = 0;
1375+static int layer_shell_x = 0;
1376+static int layer_shell_y = 0;
1377+static int emu_configured;
1378+static int active_buf;
1379+
1380+static void *shm_data;
1381+static int shm_size, shm_bufsize, shm_width, shm_height;
1382+
1383+static struct xkb_context *xkb_ctx;
1384+static struct xkb_keymap *xkb_map;
1385+static struct xkb_state *xkb_state;
1386+
1387+#define CONINBUFSIZE 256
1388+
1389+void
1390+emu_resize(void)
1391+{
1392+	const int width = screen_width * screen_zoom;
1393+	const int height = screen_height * screen_zoom;
1394+	if(width != shm_width || height != shm_height) {
1395+		const int stride = width * 4;
1396+		const int size = stride * height;
1397+		struct wl_shm_pool *pool;
1398+		int fd;
1399+		if(shm_data)
1400+			munmap(shm_data, shm_size);
1401+		if(emu_bufs[0])
1402+			wl_buffer_destroy(emu_bufs[0]);
1403+		if(emu_bufs[1])
1404+			wl_buffer_destroy(emu_bufs[1]);
1405+		char tmpl[] = "/tmp/uxn-wl-XXXXXX";
1406+		fd = mkstemp(tmpl);
1407+		unlink(tmpl);
1408+		ftruncate(fd, size * 2);
1409+		shm_data = mmap(NULL, size * 2, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
1410+		pool = wl_shm_create_pool(emu_shm, fd, size * 2);
1411+		emu_bufs[0] = wl_shm_pool_create_buffer(pool, 0, width, height, stride, WL_SHM_FORMAT_XRGB8888);
1412+		emu_bufs[1] = wl_shm_pool_create_buffer(pool, size, width, height, stride, WL_SHM_FORMAT_XRGB8888);
1413+		wl_shm_pool_destroy(pool);
1414+		close(fd);
1415+		shm_size = size * 2, shm_bufsize = size, shm_width = width, shm_height = height;
1416+	}
1417+	screen_pixels = (int *)((Uint8 *)shm_data + active_buf * shm_bufsize);
1418+	active_buf = 0;
1419+}
1420+
1421+static void frame_callback(void *, struct wl_callback *, uint32_t);
1422+
1423+static const struct wl_callback_listener frame_listener = {
1424+	.done = frame_callback,
1425+};
1426+
1427+void
1428+emu_redraw(void)
1429+{
1430+	if(!emu_configured)
1431+		return;
1432+	wl_callback_add_listener(wl_surface_frame(emu_surf), &frame_listener, NULL);
1433+	wl_surface_attach(emu_surf, emu_bufs[active_buf], 0, 0);
1434+	wl_surface_damage_buffer(emu_surf, 0, 0, screen_width * screen_zoom, screen_height * screen_zoom);
1435+	wl_surface_commit(emu_surf);
1436+}
1437+
1438+static void
1439+frame_callback(void *data, struct wl_callback *cb, uint32_t time)
1440+{
1441+	wl_callback_destroy(cb);
1442+	int old = active_buf;
1443+	active_buf ^= 1;
1444+	memcpy((Uint8 *)shm_data + active_buf * shm_bufsize,
1445+	       (Uint8 *)shm_data + old * shm_bufsize, shm_bufsize);
1446+	screen_pixels = (int *)((Uint8 *)shm_data + active_buf * shm_bufsize);
1447+}
1448+
1449+static void
1450+emu_restart(unsigned int soft)
1451+{
1452+	system_reboot(soft);
1453+	screen_width = screen_height = 0;
1454+	screen_resize(WIDTH, HEIGHT);
1455+	uxn_eval(0x100);
1456+}
1457+
1458+static void
1459+emu_screenshot(void)
1460+{
1461+	FILE *f;
1462+	char filename[] = "_00x00.chr";
1463+	unsigned int length = 4;
1464+	int tx, ty, row, col, tw = screen_width / 8, th = screen_height / 8;
1465+	put_size((Uint8 *)filename + 1, &length, 2, tw);
1466+	put_size((Uint8 *)filename + 4, &length, 2, th);
1467+	f = fopen(filename, "wb");
1468+	if(f) {
1469+		for(ty = 0; ty < th; ty++) {
1470+			for(tx = 0; tx < tw; tx++) {
1471+				Uint8 tile[16] = {0};
1472+				for(row = 0; row < 8; row++) {
1473+					const Uint8 *src = &screen_layers[(ty * 8 + row + 8) * screen_wmar2 + (tx * 8 + 8)];
1474+					Uint8 b1 = 0, b2 = 0;
1475+					for(col = 0; col < 8; col++) {
1476+						Uint8 px = src[col];
1477+						b1 |= ((px & 0x1) << (7 - col)), b2 |= (((px >> 2) & 0x1) << (7 - col));
1478+					}
1479+					tile[row] = b1, tile[row + 8] = b2;
1480+				}
1481+				fwrite(tile, 1, 16, f);
1482+			}
1483+		}
1484+		fclose(f);
1485+	}
1486+}
1487+
1488+static Uint8
1489+get_button(xkb_keysym_t sym)
1490+{
1491+	switch(sym) {
1492+	case XKB_KEY_Up: return 0x10;
1493+	case XKB_KEY_Down: return 0x20;
1494+	case XKB_KEY_Left: return 0x40;
1495+	case XKB_KEY_Right: return 0x80;
1496+	case XKB_KEY_Control_L: return 0x01;
1497+	case XKB_KEY_Alt_R:
1498+	case XKB_KEY_Alt_L: return 0x02;
1499+	case XKB_KEY_Shift_L: return 0x04;
1500+	case XKB_KEY_Home: return 0x08;
1501+	case XKB_KEY_Super_L: return 0x02;
1502+	}
1503+	return 0x00;
1504+}
1505+
1506+static void
1507+kbd_keymap(void *d, struct wl_keyboard *k, uint32_t fmt, int32_t fd, uint32_t size)
1508+{
1509+	char *map;
1510+	if(fmt != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) {
1511+		close(fd);
1512+		return;
1513+	}
1514+	map = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
1515+	if(map != MAP_FAILED) {
1516+		xkb_map = xkb_keymap_new_from_string(xkb_ctx, map, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS);
1517+		munmap(map, size);
1518+		if(xkb_state)
1519+			xkb_state_unref(xkb_state);
1520+		xkb_state = xkb_state_new(xkb_map);
1521+	}
1522+	close(fd);
1523+}
1524+
1525+static void kbd_enter(void *d, struct wl_keyboard *k, uint32_t serial, struct wl_surface *surf, struct wl_array *keys) { }
1526+static void kbd_leave(void *d, struct wl_keyboard *k, uint32_t serial, struct wl_surface *surf) { }
1527+
1528+static void
1529+kbd_key(void *d, struct wl_keyboard *k, uint32_t serial, uint32_t time, uint32_t key, uint32_t state)
1530+{
1531+	xkb_keysym_t sym;
1532+	if(!xkb_state)
1533+		return;
1534+	sym = xkb_state_key_get_one_sym(xkb_state, key + 8);
1535+	if(state == WL_KEYBOARD_KEY_STATE_PRESSED) {
1536+		char buf[8] = {0};
1537+		switch(sym) {
1538+		case XKB_KEY_F1: screen_zoom = (screen_zoom % 3) + 1, screen_reqsize = screen_reqdraw = 1; break;
1539+		case XKB_KEY_F2: emu_deo(0xe, 0x1); break;
1540+		case XKB_KEY_F4: console_close(), emu_restart(0); break;
1541+		case XKB_KEY_F5: console_close(), emu_restart(1); break;
1542+		case XKB_KEY_F6: emu_deo(0xe, 0x2); break;
1543+		}
1544+		controller_down(get_button(sym));
1545+		xkb_state_key_get_utf8(xkb_state, key + 8, buf, sizeof buf);
1546+		if(sym == XKB_KEY_Tab)
1547+			buf[0] = 0x9;
1548+		controller_key(sym < 0x80 ? (Uint8)sym : (Uint8)buf[0]);
1549+	} else
1550+		controller_up(get_button(sym));
1551+}
1552+
1553+static void
1554+kbd_modifiers(void *d, struct wl_keyboard *k, uint32_t serial, uint32_t depressed, uint32_t latched, uint32_t locked, uint32_t group)
1555+{
1556+	if(xkb_state)
1557+		xkb_state_update_mask(xkb_state, depressed, latched, locked, 0, 0, group);
1558+}
1559+
1560+static const struct wl_keyboard_listener kbd_listener = {
1561+	.keymap = kbd_keymap,
1562+	.enter = kbd_enter,
1563+	.leave = kbd_leave,
1564+	.key = kbd_key,
1565+	.modifiers = kbd_modifiers,
1566+};
1567+
1568+static void
1569+ptr_enter(void *d, struct wl_pointer *p, uint32_t serial, struct wl_surface *surf, wl_fixed_t sx, wl_fixed_t sy)
1570+{
1571+	wl_pointer_set_cursor(p, serial, NULL, 0, 0);
1572+	mouse_pos(wl_fixed_to_int(sx) / screen_zoom, wl_fixed_to_int(sy) / screen_zoom);
1573+}
1574+
1575+static void ptr_leave(void *d, struct wl_pointer *p, uint32_t serial, struct wl_surface *surf) { }
1576+
1577+static void
1578+ptr_motion(void *d, struct wl_pointer *p, uint32_t time, wl_fixed_t sx, wl_fixed_t sy)
1579+{
1580+	mouse_pos(wl_fixed_to_int(sx) / screen_zoom, wl_fixed_to_int(sy) / screen_zoom);
1581+}
1582+
1583+static void
1584+ptr_button(void *d, struct wl_pointer *p, uint32_t serial, uint32_t time, uint32_t button, uint32_t state)
1585+{
1586+	Uint8 mask = 0;
1587+	switch(button) {
1588+	case BTN_LEFT: mask = 0x01; break;
1589+	case BTN_MIDDLE: mask = 0x02; break;
1590+	case BTN_RIGHT: mask = 0x04; break;
1591+	}
1592+	if(state == WL_POINTER_BUTTON_STATE_PRESSED)
1593+		mouse_down(mask);
1594+	else
1595+		mouse_up(mask);
1596+}
1597+
1598+static void
1599+ptr_axis(void *d, struct wl_pointer *p, uint32_t time, uint32_t axis, wl_fixed_t value)
1600+{
1601+	const int v = wl_fixed_to_int(value) / 10;
1602+	if(axis == WL_POINTER_AXIS_VERTICAL_SCROLL)
1603+		mouse_scroll(0, v);
1604+	else if(axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL)
1605+		mouse_scroll(v, 0);
1606+}
1607+
1608+static const struct wl_pointer_listener ptr_listener = {
1609+	.enter = ptr_enter,
1610+	.leave = ptr_leave,
1611+	.motion = ptr_motion,
1612+	.button = ptr_button,
1613+	.axis = ptr_axis,
1614+};
1615+
1616+static void
1617+seat_caps(void *d, struct wl_seat *seat, uint32_t caps)
1618+{
1619+	if((caps & WL_SEAT_CAPABILITY_POINTER) && !emu_ptr) {
1620+		emu_ptr = wl_seat_get_pointer(seat);
1621+		wl_pointer_add_listener(emu_ptr, &ptr_listener, NULL);
1622+	}
1623+	if((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !emu_kbd) {
1624+		emu_kbd = wl_seat_get_keyboard(seat);
1625+		wl_keyboard_add_listener(emu_kbd, &kbd_listener, NULL);
1626+	}
1627+}
1628+
1629+static const struct wl_seat_listener seat_listener = {
1630+	.capabilities = seat_caps,
1631+};
1632+
1633+static void wm_ping(void *d, struct xdg_wm_base *wm, uint32_t serial) { xdg_wm_base_pong(wm, serial); }
1634+static const struct xdg_wm_base_listener wm_listener = { .ping = wm_ping };
1635+
1636+static void
1637+xs_configure(void *d, struct xdg_surface *xs, uint32_t serial)
1638+{
1639+	xdg_surface_ack_configure(xs, serial);
1640+	emu_configured = 1;
1641+	screen_reqdraw = 1;
1642+}
1643+
1644+static const struct xdg_surface_listener xs_listener = {
1645+	.configure = xs_configure,
1646+};
1647+
1648+static void xt_configure(void *d, struct xdg_toplevel *xt, int32_t w, int32_t h, struct wl_array *st) { }
1649+/* xt_close takes over X11's ClientMessage/WM_DELETE_WINDOW case. */
1650+static void xt_close(void *d, struct xdg_toplevel *xt) { dev[0x0f] = 0x80; }
1651+
1652+static const struct xdg_toplevel_listener xt_listener = {
1653+	.configure = xt_configure,
1654+	.close = xt_close,
1655+};
1656+
1657+static void
1658+ls_configure(void *d, struct zwlr_layer_surface_v1 *ls, uint32_t serial, uint32_t w, uint32_t h)
1659+{
1660+	zwlr_layer_surface_v1_ack_configure(ls, serial);
1661+	emu_configured = 1;
1662+	screen_reqdraw = 1;
1663+}
1664+
1665+static void
1666+ls_closed(void *d, struct zwlr_layer_surface_v1 *ls) { dev[0x0f] = 0x80; }
1667+
1668+static const struct zwlr_layer_surface_v1_listener ls_listener = {
1669+	.configure = ls_configure,
1670+	.closed = ls_closed,
1671+};
1672+
1673+static void
1674+reg_global(void *d, struct wl_registry *reg, uint32_t name, const char *iface, uint32_t ver)
1675+{
1676+	if(!strcmp(iface, "wl_compositor"))
1677+		emu_comp = wl_registry_bind(reg, name, &wl_compositor_interface, 4);
1678+	else if(!strcmp(iface, "wl_shm"))
1679+		emu_shm = wl_registry_bind(reg, name, &wl_shm_interface, 1);
1680+	else if(!strcmp(iface, "xdg_wm_base"))
1681+		emu_wm = wl_registry_bind(reg, name, &xdg_wm_base_interface, 1);
1682+	else if(!strcmp(iface, "zwlr_layer_shell_v1"))
1683+		emu_layer_shell = wl_registry_bind(reg, name, &zwlr_layer_shell_v1_interface, 4);
1684+	else if(!strcmp(iface, "wl_seat")) {
1685+		emu_seat = wl_registry_bind(reg, name, &wl_seat_interface, 1);
1686+		wl_seat_add_listener(emu_seat, &seat_listener, NULL);
1687+	}
1688+}
1689+
1690+static const struct wl_registry_listener reg_listener = {
1691+	.global = reg_global,
1692+};
1693+
1694+
1695+static int
1696+emu_init(void)
1697+{
1698+	xkb_ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
1699+	emu_dpy = wl_display_connect(NULL);
1700+	if(!emu_dpy)
1701+		return !fprintf(stderr, "Display: failed\n");
1702+	emu_reg = wl_display_get_registry(emu_dpy);
1703+	wl_registry_add_listener(emu_reg, &reg_listener, NULL);
1704+	wl_display_roundtrip(emu_dpy);
1705+	if(!emu_comp || !emu_shm || (!emu_wm && !use_layer_shell ))
1706+		return !fprintf(stderr, "Wayland: missing globals\n");
1707+	
1708+	emu_surf = wl_compositor_create_surface(emu_comp);
1709+	if(use_layer_shell && emu_layer_shell) {
1710+		emu_layer_surf = zwlr_layer_shell_v1_get_layer_surface(
1711+			emu_layer_shell, emu_surf, NULL,
1712+			ZWLR_LAYER_SHELL_V1_LAYER_TOP, "uxn-wl");
1713+		zwlr_layer_surface_v1_add_listener(emu_layer_surf, &ls_listener, NULL);
1714+		zwlr_layer_surface_v1_set_size(emu_layer_surf, screen_width, screen_height);
1715+		zwlr_layer_surface_v1_set_anchor(emu_layer_surf,
1716+			ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT);
1717+		zwlr_layer_surface_v1_set_margin(emu_layer_surf, layer_shell_y, 0, 0, layer_shell_x);
1718+		zwlr_layer_surface_v1_set_keyboard_interactivity(emu_layer_surf, 2); 
1719+		wl_surface_commit(emu_surf);
1720+	} else if(emu_wm) {
1721+		xdg_wm_base_add_listener(emu_wm, &wm_listener, NULL);
1722+		emu_xdgsurf = xdg_wm_base_get_xdg_surface(emu_wm, emu_surf);
1723+		xdg_surface_add_listener(emu_xdgsurf, &xs_listener, NULL);
1724+		emu_top = xdg_surface_get_toplevel(emu_xdgsurf);
1725+		xdg_toplevel_add_listener(emu_top, &xt_listener, NULL);
1726+		xdg_toplevel_set_title(emu_top, "uxn-wl");
1727+		xdg_toplevel_set_app_id(emu_top, "uxn-wl");
1728+		wl_surface_commit(emu_surf);
1729+	}
1730+	wl_display_roundtrip(emu_dpy);
1731+	return 1;
1732+}
1733+
1734+static void
1735+emu_run(void)
1736+{
1737+	int has_input, stdin_active = 1;
1738+	char coninp[CONINBUFSIZE];
1739+	struct pollfd fds[2];
1740+	struct timespec next_refresh;
1741+	fds[0].fd = wl_display_get_fd(emu_dpy), fds[0].events = POLLIN;
1742+	fds[1].fd = STDIN_FILENO, fds[1].events = POLLIN | POLLHUP;
1743+	clock_gettime(CLOCK_MONOTONIC, &next_refresh);
1744+	while(!dev[0x0f]) {
1745+		wl_display_dispatch_pending(emu_dpy);
1746+		wl_display_flush(emu_dpy);
1747+		struct timespec now;
1748+		clock_gettime(CLOCK_MONOTONIC, &now);
1749+		long long now_ms = (long long)now.tv_sec * 1000 + now.tv_nsec / 1000000;
1750+		long long next_ms = (long long)next_refresh.tv_sec * 1000 + next_refresh.tv_nsec / 1000000;
1751+		if(now_ms >= next_ms) {
1752+			audio_output();
1753+			screen_update();
1754+			emu_redraw();
1755+			next_ms += 16;
1756+			if(now_ms > next_ms + 100)
1757+				next_ms = now_ms +16;
1758+			next_refresh.tv_sec = next_ms / 1000;
1759+			next_refresh.tv_nsec = (next_ms % 1000) * 1000000;
1760+		}
1761+		clock_gettime(CLOCK_MONOTONIC, &now);
1762+		now_ms = (long long)now.tv_sec * 1000 + now.tv_nsec / 1000000;
1763+		int timeout = next_ms - now_ms;
1764+		if(timeout < 0)
1765+			timeout = 0;
1766+		/* only pull stdin while it's still active */
1767+		if(poll(fds, stdin_active ? 2 : 1, timeout) <= 0)
1768+			continue;
1769+		if(fds[0].revents & POLLIN)
1770+			wl_display_dispatch(emu_dpy);
1771+		has_input = stdin_active && (fds[1].revents & POLLIN);
1772+		if(has_input || (stdin_active && (fds[1].revents & POLLHUP))) {
1773+			int i, n = read(STDIN_FILENO, coninp, CONINBUFSIZE - 1);
1774+			if(n > 0) {
1775+				for(i = 0; i < n; i++)
1776+					console_input(coninp[i], CONSOLE_STD);
1777+			} else {
1778+				console_input('\n', CONSOLE_END);
1779+				stdin_active = 0;
1780+			}
1781+		}
1782+		datetime_busy = 0;
1783+	}
1784+	if(emu_bufs[0])
1785+		wl_buffer_destroy(emu_bufs[0]);
1786+	if(emu_bufs[1])
1787+		wl_buffer_destroy(emu_bufs[1]);
1788+	if(emu_top)
1789+		xdg_toplevel_destroy(emu_top);
1790+	if(emu_xdgsurf)
1791+		xdg_surface_destroy(emu_xdgsurf);
1792+	if(emu_layer_surf)
1793+		zwlr_layer_surface_v1_destroy(emu_layer_surf);
1794+	if(emu_surf)
1795+		wl_surface_destroy(emu_surf);
1796+	if(audio_initialized)
1797+		ma_device_uninit(&audio_device);
1798+	if(emu_dpy)
1799+		wl_display_disconnect(emu_dpy);
1800+	console_close();
1801+}
1802+
1803+int
1804+main(int argc, char **argv)
1805+{
1806+	int i = 1;
1807+	if(argc == 1)
1808+		return !fprintf(stdout, "usage: %s [-v] file.rom [args..]\n", argv[0]);
1809+	while(i < argc && argv[i][0] == '-') {
1810+		if(!strcmp(argv[i], "-v"))
1811+			return !fprintf(stdout, "%s - Varvara Emulator(79K), 10 Jul 2026.\n", argv[0]);
1812+		if(!strcmp(argv[i], "--")){ i++; break; }
1813+		if(!strcmp(argv[i], "-l")) {
1814+			if(i + 3 >= argc)
1815+				return !fprintf(stdout, "usage: %s [-l x y] file.rom [args..]\n", argv[0]);
1816+			use_layer_shell = 1;
1817+			layer_shell_x = atoi(argv[++i]);
1818+			layer_shell_y = atoi(argv[++i]);
1819+			i++;
1820+			continue;
1821+		}
1822+		return !fprintf(stdout, "unknown flag: %s\n", argv[i]);
1823+	}
1824+	if(i >= argc)
1825+		return !fprintf(stdout, "usage: %s [-v] file.rom [args..]\n", argv[0]);
1826+	if(!system_boot(argv[i], argc > i + 1))
1827+		return !fprintf(stdout, "Could not load %s.\n", argv[i]);
1828+	i++;
1829+	screen_resize(WIDTH, HEIGHT);
1830+	if(uxn_eval(0x100) && console_vector) {
1831+		for(; i < argc; i++) {
1832+			char *p = argv[i];
1833+			while(*p)
1834+				console_input(*p++, CONSOLE_ARG);
1835+			console_input('\n', i == argc - 1 ? CONSOLE_END : CONSOLE_EOA);
1836+		}
1837+	}
1838+	if(!dev[0x0f]) {
1839+		if(!emu_init())
1840+			return !fprintf(stdout, "Could not initialize %s.\n", argv[0]);
1841+		screen_update();
1842+		emu_redraw();
1843+		audio_init();
1844+		emu_run();
1845+	}
1846+	return dev[0x0f] & 0x7f;
1847+}