commit 9f201b1

Devine Lu Linvega  ·  2026-01-09 20:37:45 +0000 UTC
parent 0500e2d
Added openBSD core by Lobo
4 files changed,  +1370, -2
+14, -0
 1@@ -27,6 +27,20 @@ If do not wish to build it yourself, you can download linux binaries for [uxn11]
 2 
 3 [![builds.sr.ht status](https://builds.sr.ht/~rabbits/uxn11.svg)](https://builds.sr.ht/~rabbits/uxn11?)
 4 
 5+### For OpenBSD
 6+
 7+To compile on OpenBSD, you need to install the "epoll-shim" package.
 8+
 9+```sh
10+pkg_add epoll-shim
11+```
12+
13+Then, to build:
14+
15+```makefile
16+cc -DNDEBUG -O2 -g0 -s `pkg-config --cflags --libs x11 epoll-shim` src/uxn11.c -lutil -o bin/uxn11
17+```
18+
19 ## Usage
20 
21 The first parameter is the rom file, the subsequent arguments will be accessible to the rom, via the [Console vector](https://wiki.xxiivv.com/site/varvara.html#console).
+1354, -0
   1@@ -0,0 +1,1354 @@
   2+#include <sys/types.h>
   3+#include <sys/event.h>
   4+#include <sys/time.h>
   5+#include <stdio.h>
   6+#include <stdlib.h>
   7+#include <unistd.h>
   8+#include <string.h>
   9+#include <X11/Xlib.h>
  10+#include <X11/Xutil.h>
  11+#include <X11/keysymdef.h>
  12+
  13+/*
  14+Copyright (c) 2021-2026 Devine Lu Linvega, Andrew Alderwick,
  15+Andrew Richards, Eiríkr Åsheim, Sigrid Solveig Haflínudóttir,
  16+Lobo Torres
  17+
  18+Permission to use, copy, modify, and distribute this software for any
  19+purpose with or without fee is hereby granted, provided that the above
  20+copyright notice and this permission notice appear in all copies.
  21+
  22+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  23+WITH REGARD TO THIS SOFTWARE.
  24+
  25+cc -DNDEBUG -O2 -g0 -s src/uxn11.c -L/usr/X11R6/lib -I/usr/X11R6/include -lX11 -lutil -o bin/uxn11
  26+*/
  27+
  28+typedef unsigned char Uint8;
  29+typedef unsigned short Uint16;
  30+typedef void (*deo_handler)(void);
  31+typedef Uint8 (*dei_handler)(void);
  32+
  33+static Display *emu_dpy;
  34+static Window emu_win;
  35+static XImage *emu_img;
  36+static int emu_x11;
  37+static int emu_timer;
  38+static Uint8 *ram, dev[0x100], ptr[2], stk[2][0x100];
  39+static Uint8 emu_dei(const Uint8 port);
  40+static void emu_deo(const Uint8 port, const Uint8 value);
  41+
  42+/* clang-format off */
  43+
  44+#define BANKS 0x10
  45+#define BANKS_CAP BANKS * 0x10000
  46+#define WIDTH (64 * 8)
  47+#define HEIGHT (40 * 8)
  48+
  49+#define CLAMP(v, a, b) { if(v < a) v = a; else if(v >= b) v = b; }
  50+#define TWOS(v) (v & 0x8000 ? (int)v - 0x10000 : (int)v)
  51+#define PEEK2(d) (*(d) << 8 | (d)[1])
  52+
  53+#define REM *p -= 1 + _2;
  54+#define DEC s[--(*p)]
  55+#define INC s[(*p)++]
  56+#define MOV { if(_2) pc = a; else pc += (signed char)a; }
  57+#define JMP(x) { c = ram[pc] << 8, c |= ram[pc + 1]; pc += x + 2; }
  58+#define STK(m) Uint8 *s = stk[m], *p = &ptr[m];
  59+#define PO1(o) o = DEC;
  60+#define PO2(o) { PO1(o) o |= DEC << 8; }
  61+#define POx(o) { PO1(o) if(_2) o |= DEC << 8; }
  62+#define POt(o) if(_2) PO1(o[1]) PO1(o[0])
  63+#define PU1(i) INC = i;
  64+#define PU2(i) PU1(i >> 8) PU1(i)
  65+#define PUx(i) c = (i); if(_2) PU1(c >> 8) PU1(c)
  66+#define PUt(i) PU1(i[0]) if(_2) PU1(i[1])
  67+#define PUr(i) stk[!_r][ptr[!_r]++] = i;
  68+#define DEO(o,r) emu_deo(o, r[0]); if(_2) emu_deo(o + 1, r[1]);
  69+#define DEI(i,r) r[0] = emu_dei(i); if(_2) r[1] = emu_dei(i + 1);
  70+#define POK(o,r,m) ram[o] = r[0]; if(_2) ram[(o + 1) & m] = r[1];
  71+#define PEK(i,r,m) r[0] = ram[i]; if(_2) r[1] = ram[(i + 1) & m];
  72+
  73+#define OPC(opc, A, B) {\
  74+	case 0x00|opc: {const int _2=0,_r=0;STK(_r)A B} goto step;\
  75+	case 0x20|opc: {const int _2=1,_r=0;STK(_r)A B} goto step;\
  76+	case 0x40|opc: {const int _2=0,_r=1;STK(_r)A B} goto step;\
  77+	case 0x60|opc: {const int _2=1,_r=1;STK(_r)A B} goto step;\
  78+	case 0x80|opc: {const int _2=0,_r=0;STK(_r)int k=*p; A *p=k;B} goto step;\
  79+	case 0xa0|opc: {const int _2=1,_r=0;STK(_r)int k=*p; A *p=k;B} goto step;\
  80+	case 0xc0|opc: {const int _2=0,_r=1;STK(_r)int k=*p; A *p=k;B} goto step;\
  81+	case 0xe0|opc: {const int _2=1,_r=1;STK(_r)int k=*p; A *p=k;B} goto step;}
  82+
  83+static unsigned int
  84+uxn_eval(Uint16 pc)
  85+{
  86+	Uint16 a, b, c, x[2], y[2], z[2];
  87+step:
  88+	switch(ram[pc++]) {
  89+	/* BRK */ case 0x00: return 1;
  90+	/* JCI */ case 0x20: if(stk[0][--ptr[0]]) JMP(c) else pc += 2; goto step;
  91+	/* JMI */ case 0x40: JMP(c) goto step;
  92+	/* JSI */ case 0x60: { STK(1) JMP(0) PU2(pc) pc += c; } goto step;
  93+	/* LI2 */ case 0xa0: { STK(0) INC = ram[pc++]; INC = ram[pc++]; } goto step;
  94+	/* LIT */ case 0x80: { stk[0][ptr[0]++] = ram[pc++]; } goto step;
  95+	/* L2r */ case 0xe0: { STK(1) INC = ram[pc++]; INC = ram[pc++]; } goto step;
  96+	/* LIr */ case 0xc0: { stk[1][ptr[1]++] = ram[pc++]; } goto step;
  97+	/* INC */ OPC(0x01,POx(a),PUx(a + 1))
  98+	/* POP */ OPC(0x02,REM,(void)s;)
  99+	/* NIP */ OPC(0x03,POt(x) REM,PUt(x))
 100+	/* SWP */ OPC(0x04,POt(x) POt(y),PUt(x) PUt(y))
 101+	/* ROT */ OPC(0x05,POt(x) POt(y) POt(z),PUt(y) PUt(x) PUt(z))
 102+	/* DUP */ OPC(0x06,POt(x),PUt(x) PUt(x))
 103+	/* OVR */ OPC(0x07,POt(x) POt(y),PUt(y) PUt(x) PUt(y))
 104+	/* EQU */ OPC(0x08,POx(a) POx(b),PU1(b == a))
 105+	/* NEQ */ OPC(0x09,POx(a) POx(b),PU1(b != a))
 106+	/* GTH */ OPC(0x0a,POx(a) POx(b),PU1(b > a))
 107+	/* LTH */ OPC(0x0b,POx(a) POx(b),PU1(b < a))
 108+	/* JMP */ OPC(0x0c,POx(a),MOV)
 109+	/* JCN */ OPC(0x0d,POx(a) PO1(b),if(b) MOV)
 110+	/* JSR */ OPC(0x0e,POx(a),PUr(pc >> 8) PUr(pc) MOV)
 111+	/* STH */ OPC(0x0f,POt(x),PUr(x[0]) if(_2) PUr(x[1]))
 112+	/* LDZ */ OPC(0x10,PO1(a),PEK(a, x, 0xff) PUt(x))
 113+	/* STZ */ OPC(0x11,PO1(a) POt(y),POK(a, y, 0xff))
 114+	/* LDR */ OPC(0x12,PO1(a),PEK(pc + (signed char)a, x, 0xffff) PUt(x))
 115+	/* STR */ OPC(0x13,PO1(a) POt(y),POK(pc + (signed char)a, y, 0xffff))
 116+	/* LDA */ OPC(0x14,PO2(a),PEK(a, x, 0xffff) PUt(x))
 117+	/* STA */ OPC(0x15,PO2(a) POt(y),POK(a, y, 0xffff))
 118+	/* DEI */ OPC(0x16,PO1(a),DEI(a, x) PUt(x))
 119+	/* DEO */ OPC(0x17,PO1(a) POt(y),DEO(a, y))
 120+	/* ADD */ OPC(0x18,POx(a) POx(b),PUx(b + a))
 121+	/* SUB */ OPC(0x19,POx(a) POx(b),PUx(b - a))
 122+	/* MUL */ OPC(0x1a,POx(a) POx(b),PUx(b * a))
 123+	/* DIV */ OPC(0x1b,POx(a) POx(b),PUx(a ? b / a : 0))
 124+	/* AND */ OPC(0x1c,POx(a) POx(b),PUx(b & a))
 125+	/* ORA */ OPC(0x1d,POx(a) POx(b),PUx(b | a))
 126+	/* EOR */ OPC(0x1e,POx(a) POx(b),PUx(b ^ a))
 127+	/* SFT */ OPC(0x1f,PO1(a) POx(b),PUx(b >> (a & 0xf) << (a >> 4)))
 128+	}
 129+	return 0;
 130+}
 131+
 132+/* clang-format on */
 133+
 134+/*
 135+@|System ------------------------------------------------------------ */
 136+
 137+static char *system_boot_path;
 138+
 139+static void
 140+system_print(char *name, int r)
 141+{
 142+	Uint8 i;
 143+	fprintf(stderr, "%s%c", name, ptr[r] - 8 ? ' ' : '|');
 144+	for(i = ptr[r] - 8; i != ptr[r]; i++)
 145+		fprintf(stderr, "%02x%c", stk[r][i], i == 0xff ? '|' : ' ');
 146+	fprintf(stderr, "<%02x\n", ptr[r]);
 147+}
 148+
 149+static unsigned int
 150+system_load(const char *rom_path)
 151+{
 152+	FILE *f = fopen(rom_path, "rb");
 153+	if(f) {
 154+		unsigned int i = 0, l = fread(ram + 0x100, 0x10000 - 0x100, 1, f);
 155+		while(l && ++i < BANKS)
 156+			l = fread(ram + i * 0x10000, 0x10000, 1, f);
 157+		fclose(f);
 158+	}
 159+	return !!f;
 160+}
 161+
 162+static unsigned int
 163+system_boot(char *rom_path, const unsigned int has_args)
 164+{
 165+	ram = (Uint8 *)calloc(BANKS_CAP, sizeof(Uint8));
 166+	system_boot_path = rom_path;
 167+	dev[0x17] = has_args;
 168+	return ram && system_load(rom_path);
 169+}
 170+
 171+static unsigned int
 172+system_reboot(const unsigned int soft)
 173+{
 174+	memset(dev, 0, 0x100);
 175+	memset(stk[0], 0, 0x100);
 176+	memset(stk[1], 0, 0x100);
 177+	if(soft)
 178+		memset(ram + 0x100, 0, 0xff00);
 179+	else
 180+		memset(ram, 0, 0x10000);
 181+	ptr[0] = ptr[1] = 0;
 182+	return system_load(system_boot_path);
 183+}
 184+
 185+static void
 186+system_deo_expansion(void)
 187+{
 188+	const Uint16 exp = PEEK2(dev + 2);
 189+	Uint8 *aptr = ram + exp;
 190+	Uint16 length = PEEK2(aptr + 1);
 191+	unsigned int bank = PEEK2(aptr + 3) * 0x10000;
 192+	unsigned int addr = PEEK2(aptr + 5);
 193+	if(ram[exp] == 0x0) {
 194+		unsigned int dst_value = ram[exp + 7];
 195+		if(bank < BANKS_CAP)
 196+			memset(ram + bank + addr, dst_value, length);
 197+	} else if(ram[exp] == 1 || ram[exp] == 2) {
 198+		unsigned int dst_bank = PEEK2(aptr + 7) * 0x10000;
 199+		unsigned int dst_addr = PEEK2(aptr + 9);
 200+		if(bank < BANKS_CAP && dst_bank < BANKS_CAP)
 201+			memmove(ram + dst_bank + dst_addr, ram + bank + addr, length);
 202+	} else
 203+		fprintf(stderr, "Unknown command: %s\n", &ram[exp]);
 204+}
 205+
 206+/* clang-format off */
 207+
 208+static Uint8 system_dei_wst(void) { return ptr[0]; }
 209+static Uint8 system_dei_rst(void) { return ptr[1]; }
 210+static void system_deo_wst(void) { ptr[0] = dev[4]; }
 211+static void system_deo_rst(void) { ptr[1] = dev[5]; }
 212+static void system_deo_print(void) { system_print("WST", 0), system_print("RST", 1); }
 213+
 214+/* clang-format on */
 215+
 216+/*
 217+@|Console ----------------------------------------------------------- */
 218+
 219+#define CONSOLE_STD 0x1
 220+#define CONSOLE_ARG 0x2
 221+#define CONSOLE_EOA 0x3
 222+#define CONSOLE_END 0x4
 223+
 224+static unsigned int console_vector;
 225+
 226+static unsigned int
 227+console_input(int c, unsigned int type)
 228+{
 229+	if(c == EOF) c = 0, type = CONSOLE_END;
 230+	dev[0x12] = c, dev[0x17] = type;
 231+	if(console_vector) uxn_eval(console_vector);
 232+	return type != CONSOLE_END;
 233+}
 234+
 235+#undef _POSIX_C_SOURCE
 236+#define _POSIX_C_SOURCE 200112L
 237+
 238+#include <signal.h>
 239+#include <sys/select.h>
 240+#include <sys/wait.h>
 241+
 242+#ifdef __linux
 243+#include <pty.h>
 244+#include <sys/prctl.h>
 245+#endif
 246+
 247+#if defined(__NetBSD__) || defined(__OpenBSD__)
 248+#include <sys/ioctl.h>
 249+#include <util.h>
 250+#endif
 251+
 252+/* subprocess support */
 253+static char *fork_args[4] = {"/bin/sh", "-c", "", NULL};
 254+static int child_mode;
 255+static int to_child_fd[2];
 256+static int from_child_fd[2];
 257+static int saved_in;
 258+static int saved_out;
 259+static pid_t child_pid;
 260+
 261+/* child_mode:
 262+* 0x01: writes to child's stdin
 263+* 0x02: reads from child's stdout
 264+* 0x04: reads from child's stderr
 265+* 0x08: kill previous process (if any) but do not start
 266+* (other bits ignored for now )
 267+*/
 268+
 269+#define CMD_LIVE 0x15 /* 0x00 not started, 0x01 running, 0xff dead */
 270+#define CMD_EXIT 0x16 /* if dead, exit code of process */
 271+#define CMD_ADDR 0x1c /* address to read command args from */
 272+#define CMD_MODE 0x1e /* mode to execute, 0x00 to 0x07 */
 273+
 274+/* call after we're sure the process has exited */
 275+
 276+static void
 277+clean_after_child(void)
 278+{
 279+	child_pid = 0;
 280+	if(child_mode & 0x01) {
 281+		close(to_child_fd[1]);
 282+		dup2(saved_out, 1);
 283+	}
 284+	if(child_mode & (0x04 | 0x02)) {
 285+		close(from_child_fd[0]);
 286+		dup2(saved_in, 0);
 287+	}
 288+	child_mode = 0;
 289+	saved_in = -1;
 290+	saved_out = -1;
 291+}
 292+
 293+static void
 294+start_fork_pipe(void)
 295+{
 296+	pid_t pid;
 297+	pid_t parent_pid = getpid();
 298+	int addr = PEEK2(&dev[CMD_ADDR]);
 299+	fflush(stdout);
 300+	if(child_mode & 0x08) {
 301+		dev[CMD_EXIT] = dev[CMD_LIVE] = 0x00;
 302+		return;
 303+	}
 304+	if(child_mode & 0x01) {
 305+		/* parent writes to child's stdin */
 306+		if(pipe(to_child_fd) == -1) {
 307+			dev[CMD_EXIT] = dev[CMD_LIVE] = 0xff;
 308+			fprintf(stderr, "Pipe error to child.\n");
 309+			return;
 310+		}
 311+	}
 312+	if(child_mode & (0x04 | 0x02)) {
 313+		/* parent reads from child's stdout and/or stderr */
 314+		if(pipe(from_child_fd) == -1) {
 315+			dev[CMD_EXIT] = dev[CMD_LIVE] = 0xff;
 316+			fprintf(stderr, "Pipe error from child.\n");
 317+			return;
 318+		}
 319+	}
 320+
 321+	fork_args[2] = (char *)&ram[addr];
 322+	pid = fork();
 323+	if(pid < 0) { /* failure */
 324+		dev[CMD_EXIT] = dev[CMD_LIVE] = 0xff;
 325+		fprintf(stderr, "Fork failure.\n");
 326+	} else if(pid == 0) { /* child */
 327+
 328+#ifdef __linux__
 329+		int r = prctl(PR_SET_PDEATHSIG, SIGTERM);
 330+		if(r == -1) {
 331+			perror(0);
 332+			exit(6);
 333+		}
 334+		if(getppid() != parent_pid) exit(13);
 335+#endif
 336+
 337+		if(child_mode & 0x01) {
 338+			dup2(to_child_fd[0], 0);
 339+			close(to_child_fd[1]);
 340+		}
 341+		if(child_mode & (0x04 | 0x02)) {
 342+			if(child_mode & 0x02) dup2(from_child_fd[1], 1);
 343+			if(child_mode & 0x04) dup2(from_child_fd[1], 2);
 344+			close(from_child_fd[0]);
 345+		}
 346+		fflush(stdout);
 347+		execvp(fork_args[0], fork_args);
 348+		exit(1);
 349+	} else { /*parent*/
 350+		child_pid = pid;
 351+		dev[CMD_LIVE] = 0x01;
 352+		dev[CMD_EXIT] = 0x00;
 353+		if(child_mode & 0x01) {
 354+			saved_out = dup(1);
 355+			dup2(to_child_fd[1], 1);
 356+			close(to_child_fd[0]);
 357+		}
 358+		if(child_mode & (0x04 | 0x02)) {
 359+			saved_in = dup(0);
 360+			dup2(from_child_fd[0], 0);
 361+			close(from_child_fd[1]);
 362+		}
 363+	}
 364+}
 365+
 366+static void
 367+check_child(void)
 368+{
 369+	int wstatus;
 370+	if(child_pid) {
 371+		if(waitpid(child_pid, &wstatus, WNOHANG)) {
 372+			dev[CMD_LIVE] = 0xff;
 373+			dev[CMD_EXIT] = WEXITSTATUS(wstatus);
 374+			clean_after_child();
 375+		} else {
 376+			dev[CMD_LIVE] = 0x01;
 377+			dev[CMD_EXIT] = 0x00;
 378+		}
 379+	}
 380+}
 381+
 382+static void
 383+kill_child(void)
 384+{
 385+	int wstatus;
 386+	if(child_pid) {
 387+		kill(child_pid, 9);
 388+		if(waitpid(child_pid, &wstatus, WNOHANG)) {
 389+			dev[CMD_LIVE] = 0xff;
 390+			dev[CMD_EXIT] = WEXITSTATUS(wstatus);
 391+			clean_after_child();
 392+		}
 393+	}
 394+}
 395+
 396+static void
 397+console_deo_fork(void)
 398+{
 399+	fflush(stderr);
 400+	kill_child();
 401+	child_mode = dev[CMD_MODE];
 402+	start_fork_pipe();
 403+}
 404+
 405+static void
 406+console_close(void)
 407+{
 408+	kill_child();
 409+}
 410+
 411+/* clang-format off */
 412+
 413+static Uint8 console_dei_childlive(void) { check_child(); return dev[CMD_LIVE]; }
 414+static Uint8 console_dei_childexit(void) { check_child(); return dev[CMD_EXIT]; }
 415+static void console_deo_vector(void) { console_vector = PEEK2(&dev[0x10]); }
 416+static void console_deo_stdout(void) { fputc(dev[0x18], stdout), fflush(stdout); }
 417+static void console_deo_stderr(void) { fputc(dev[0x19], stderr), fflush(stderr); }
 418+static void console_deo_hb(void) { fprintf(stderr, "%02x", dev[0x1a]); }
 419+static void console_deo_lb(void) { fprintf(stderr, "%02x", dev[0x1b]); }
 420+
 421+/* clang-format on */
 422+
 423+/*
 424+@|Screen ------------------------------------------------------------ */
 425+
 426+static int screen_zoom = 1;
 427+static int screen_width, screen_height, screen_wmar2, screen_hmar2;
 428+static int screen_reqsize, screen_reqdraw;
 429+static int screen_x1, screen_y1, screen_x2, screen_y2;
 430+static int screen_vector, *screen_pixels, screen_palette[16];
 431+static int rX, rY, rA, rMX, rMY, rMA, rML, rDX, rDY;
 432+static Uint8 *screen_layers;
 433+
 434+static const Uint8 blending[16][2][4] = {
 435+	{{0, 0, 1, 2}, {0, 0, 4, 8}},
 436+	{{0, 1, 2, 3}, {0, 4, 8, 12}},
 437+	{{0, 2, 3, 1}, {0, 8, 12, 4}},
 438+	{{0, 3, 1, 2}, {0, 12, 4, 8}},
 439+	{{1, 0, 1, 2}, {4, 0, 4, 8}},
 440+	{{0, 1, 2, 3}, {0, 4, 8, 12}},
 441+	{{1, 2, 3, 1}, {4, 8, 12, 4}},
 442+	{{1, 3, 1, 2}, {4, 12, 4, 8}},
 443+	{{2, 0, 1, 2}, {8, 0, 4, 8}},
 444+	{{2, 1, 2, 3}, {8, 4, 8, 12}},
 445+	{{0, 2, 3, 1}, {0, 8, 12, 4}},
 446+	{{2, 3, 1, 2}, {8, 12, 4, 8}},
 447+	{{3, 0, 1, 2}, {12, 0, 4, 8}},
 448+	{{3, 1, 2, 3}, {12, 4, 8, 12}},
 449+	{{3, 2, 3, 1}, {12, 8, 12, 4}},
 450+	{{0, 3, 1, 2}, {0, 12, 4, 8}}};
 451+
 452+void emu_redraw(void), emu_resize(void);
 453+
 454+static void
 455+screen_change(const int x1, const int y1, const int x2, const int y2)
 456+{
 457+	screen_x1 = x1 < screen_x1 ? x1 : screen_x1;
 458+	screen_y1 = y1 < screen_y1 ? y1 : screen_y1;
 459+	screen_x2 = x2 > screen_x2 ? x2 : screen_x2;
 460+	screen_y2 = y2 > screen_y2 ? y2 : screen_y2;
 461+}
 462+
 463+static void
 464+system_deo_colorize(void)
 465+{
 466+	unsigned int i, shift, colors[4];
 467+	for(i = 0, shift = 4; i < 4; ++i, shift ^= 4) {
 468+		Uint8
 469+			r = dev[0x8 + i / 2] >> shift & 0xf,
 470+			g = dev[0xa + i / 2] >> shift & 0xf,
 471+			b = dev[0xc + i / 2] >> shift & 0xf;
 472+		colors[i] = 0x0f000000 | r << 16 | g << 8 | b;
 473+		colors[i] |= colors[i] << 4;
 474+	}
 475+	for(i = 0; i < 16; i++)
 476+		screen_palette[i] = colors[i >> 2 ? i >> 2 : i & 3];
 477+	screen_reqdraw = 1;
 478+}
 479+
 480+static void
 481+screen_resize(int width, int height)
 482+{
 483+	if(width != screen_width || height != screen_height) {
 484+		int length;
 485+		screen_width = width, screen_wmar2 = width + 0x10;
 486+		dev[0x22] = screen_width >> 8, dev[0x23] = screen_width;
 487+		screen_height = height, screen_hmar2 = height + 0x10;
 488+		dev[0x24] = screen_height >> 8, dev[0x25] = screen_height;
 489+		length = screen_wmar2 * screen_hmar2;
 490+		screen_layers = realloc(screen_layers, length);
 491+		memset(screen_layers, 0, length);
 492+		screen_reqsize = screen_reqdraw = 1;
 493+	}
 494+}
 495+
 496+static void
 497+screen_redraw(void)
 498+{
 499+	if(screen_zoom == 1) {
 500+		const int colmar = (screen_y1 + 8) * screen_wmar2 + (screen_x1 + 8);
 501+		const int row_width = screen_x2 - screen_x1;
 502+		const int src_stride = screen_wmar2 - row_width;
 503+		const int dst_stride = screen_width - row_width;
 504+		int y, *dst_ptr = &screen_pixels[screen_y1 * screen_width + screen_x1];
 505+		Uint8 *src_ptr = &screen_layers[colmar];
 506+		for(y = screen_y1; y < screen_y2; y++) {
 507+			int *dst_end = dst_ptr + row_width;
 508+			while(dst_ptr < dst_end)
 509+				*dst_ptr++ = screen_palette[*src_ptr++];
 510+			dst_ptr += dst_stride;
 511+			src_ptr += src_stride;
 512+		}
 513+	} else {
 514+		int x, y, i, k, l;
 515+		const int stride = screen_width * screen_zoom;
 516+		for(y = screen_y1; y < screen_y2; y++) {
 517+			const int ys = y * screen_zoom;
 518+			for(x = screen_x1, i = (x + 8) + (y + 8) * screen_wmar2; x < screen_x2; x++, i++) {
 519+				const int c = screen_palette[screen_layers[i]];
 520+				for(k = 0; k < screen_zoom; k++) {
 521+					const int oo = (ys + k) * stride + x * screen_zoom;
 522+					for(l = 0; l < screen_zoom; l++)
 523+						screen_pixels[oo + l] = c;
 524+				}
 525+			}
 526+		}
 527+	}
 528+	emu_redraw();
 529+	screen_x1 = screen_y1 = screen_x2 = screen_y2 = screen_reqdraw = 0;
 530+}
 531+
 532+static void
 533+screen_update(void)
 534+{
 535+	if(screen_vector)
 536+		uxn_eval(screen_vector);
 537+	if(screen_reqsize) {
 538+		static int pixel_capacity;
 539+		int need = screen_width * screen_height * screen_zoom * screen_zoom;
 540+		if(need > pixel_capacity) {
 541+			pixel_capacity = need;
 542+			screen_pixels = realloc(screen_pixels, need * sizeof(unsigned int));
 543+		}
 544+		screen_reqsize = 0;
 545+		emu_resize();
 546+	}
 547+	if(screen_reqdraw) {
 548+		screen_x1 = screen_y1 = 0;
 549+		screen_x2 = screen_width;
 550+		screen_y2 = screen_height;
 551+		screen_redraw();
 552+	} else if(screen_x2 > screen_x1 && screen_y2 > screen_y1) {
 553+		CLAMP(screen_x1, 0, screen_width);
 554+		CLAMP(screen_y1, 0, screen_height);
 555+		CLAMP(screen_x2, 0, screen_width);
 556+		CLAMP(screen_y2, 0, screen_height);
 557+		screen_redraw();
 558+	}
 559+}
 560+
 561+static void
 562+screen_deo_pixel(void)
 563+{
 564+	const int ctrl = dev[0x2e];
 565+	const int layer_mask = (ctrl & 0x40) ? 0x3 : 0xc;
 566+	const int color = (ctrl & 0x40) ? ((ctrl & 0x3) << 2) : (ctrl & 0x3);
 567+	/* fill mode */
 568+	if(ctrl & 0x80) {
 569+		int x1, y1, x2, y2, ay, by, px;
 570+		if(ctrl & 0x10)
 571+			x1 = 0, x2 = rX;
 572+		else
 573+			x1 = rX, x2 = screen_width;
 574+		if(ctrl & 0x20)
 575+			y1 = 0, y2 = rY;
 576+		else
 577+			y1 = rY, y2 = screen_height;
 578+		screen_reqdraw = 1;
 579+		x1 = x1 + 8, y1 = y1 + 8;
 580+		const int hor = (x2 + 8) - x1, ver = (y2 + 8) - y1;
 581+		for(ay = y1 * screen_wmar2, by = ay + ver * screen_wmar2; ay < by; ay += screen_wmar2) {
 582+			Uint8 *dst = &screen_layers[ay + x1];
 583+			for(px = 0; px < hor; px++)
 584+				dst[px] = (dst[px] & layer_mask) | color;
 585+		}
 586+	}
 587+	/* pixel mode */
 588+	else {
 589+		const unsigned int x = rX, y = rY;
 590+		if(x < screen_width && y < screen_height) {
 591+			Uint8 *dst = &screen_layers[(x + 8) + (y + 8) * screen_wmar2];
 592+			*dst = (*dst & layer_mask) | color;
 593+		}
 594+		screen_reqdraw = 1;
 595+		if(rMX) rX++;
 596+		if(rMY) rY++;
 597+	}
 598+}
 599+
 600+static void
 601+screen_deo_sprite(void)
 602+{
 603+	int i, row, px, x = rX, y = rY;
 604+	const int ctrl = dev[0x2f];
 605+	const int blend = ctrl & 0xf;
 606+	const int opaque = blend % 5;
 607+	const int fx = (ctrl & 0x10) ? -1 : 1, dyx = fx * rDY;
 608+	const int fy = (ctrl & 0x20) ? -1 : 1, dxy = fy * rDX;
 609+	const int qfx = (ctrl & 0x10) ? 0 : 7;
 610+	const int qfy = (ctrl & 0x20) ? 7 : 0;
 611+	const int layer_mask = (ctrl & 0x40) ? 0x3 : 0xc;
 612+	const Uint8 *table = (ctrl & 0x40) ? blending[blend][1] : blending[blend][0];
 613+	const int is_2bpp = (ctrl & 0x80) != 0;
 614+	const int addr_incr = rMA << (is_2bpp ? 2 : 1);
 615+	const int stride = screen_wmar2;
 616+	for(i = 0; i <= rML; i++, x += dyx, y += dxy, rA += addr_incr) {
 617+		const Uint16 x0 = x + 8, y0 = y + 8;
 618+		if(x0 + 8 >= stride || y0 + 8 >= screen_hmar2) continue;
 619+		Uint8 *dst = &screen_layers[x0 + y0 * stride];
 620+		const Uint8 *src = &ram[rA + qfy];
 621+		for(row = 0; row < 8; row++, dst += stride, src += fy) {
 622+			int qx = qfx;
 623+			const int ch1 = *src, ch2 = is_2bpp ? (src[8] << 1) : 0;
 624+			for(px = 0; px < 8; px++, qx -= fx) {
 625+				int color = ((ch1 >> qx) & 1) | ((ch2 >> qx) & 2);
 626+				if(opaque || color)
 627+					dst[px] = (dst[px] & layer_mask) | table[color];
 628+			}
 629+		}
 630+	}
 631+	if(!screen_reqdraw) {
 632+		int x1 = (fx < 0) ? x : rX;
 633+		int x2 = (fx < 0) ? rX : x;
 634+		int y1 = (fy < 0) ? y : rY;
 635+		int y2 = (fy < 0) ? rY : y;
 636+		screen_change(x1 - 8, y1 - 8, x2 + 8, y2 + 8);
 637+	}
 638+	if(rMX) rX += rDX * fx;
 639+	if(rMY) rY += rDY * fy;
 640+}
 641+
 642+/* clang-format off */
 643+
 644+static Uint8 screen_dei_rxhb(void) { return rX >> 8; }
 645+static Uint8 screen_dei_rxlb(void) { return rX; }
 646+static Uint8 screen_dei_ryhb(void) { return rY >> 8; }
 647+static Uint8 screen_dei_rylb(void) { return rY; }
 648+static Uint8 screen_dei_rahb(void) { return rA >> 8; }
 649+static Uint8 screen_dei_ralb(void) { return rA; }
 650+static void screen_deo_vector(void) { screen_vector = PEEK2(&dev[0x20]); }
 651+static void screen_deo_width(void) { screen_resize(PEEK2(&dev[0x22]) & 0xfff, screen_height & 0xfff); }
 652+static void screen_deo_height(void) { screen_resize(screen_width & 0xfff, PEEK2(&dev[0x24]) & 0xfff); }
 653+static void screen_deo_auto(void) { rMX = dev[0x26] & 0x1, rMY = dev[0x26] & 0x2, rMA = dev[0x26] & 0x4, rML = dev[0x26] >> 4, rDX = rMX << 3, rDY = rMY << 2; }
 654+static void screen_deo_x(void) { rX = (dev[0x28] << 8) | dev[0x29], rX = TWOS(rX); }
 655+static void screen_deo_y(void) { rY = (dev[0x2a] << 8) | dev[0x2b], rY = TWOS(rY); }
 656+static void screen_deo_addr(void) { rA = (dev[0x2c] << 8) | dev[0x2d]; }
 657+
 658+/* clang-format on */
 659+
 660+/*
 661+@|Controller -------------------------------------------------------- */
 662+
 663+static unsigned int controller_vector;
 664+
 665+static void
 666+controller_down(Uint8 mask)
 667+{
 668+	if(mask) {
 669+		dev[0x82] |= mask;
 670+		if(controller_vector) uxn_eval(controller_vector);
 671+	}
 672+}
 673+
 674+static void
 675+controller_up(Uint8 mask)
 676+{
 677+	if(mask) {
 678+		dev[0x82] &= (~mask);
 679+		if(controller_vector) uxn_eval(controller_vector);
 680+	}
 681+}
 682+
 683+static void
 684+controller_key(Uint8 key)
 685+{
 686+	if(key) {
 687+		dev[0x83] = key;
 688+		if(controller_vector) uxn_eval(controller_vector);
 689+		dev[0x83] = 0;
 690+	}
 691+}
 692+
 693+void
 694+controller_deo_vector(void)
 695+{
 696+	controller_vector = PEEK2(&dev[0x80]);
 697+}
 698+
 699+/*
 700+@|Mouse ------------------------------------------------------------- */
 701+
 702+static unsigned int mouse_vector;
 703+
 704+static void
 705+mouse_down(Uint8 mask)
 706+{
 707+	dev[0x96] |= mask;
 708+	if(mouse_vector) uxn_eval(mouse_vector);
 709+}
 710+
 711+static void
 712+mouse_up(Uint8 mask)
 713+{
 714+	dev[0x96] &= (~mask);
 715+	if(mouse_vector) uxn_eval(mouse_vector);
 716+}
 717+
 718+static void
 719+mouse_pos(Uint16 x, Uint16 y)
 720+{
 721+	dev[0x92] = x >> 8, dev[0x93] = x;
 722+	dev[0x94] = y >> 8, dev[0x95] = y;
 723+	if(mouse_vector) uxn_eval(mouse_vector);
 724+}
 725+
 726+static void
 727+mouse_scroll(Uint16 x, Uint16 y)
 728+{
 729+	dev[0x9a] = x >> 8, dev[0x9b] = x;
 730+	dev[0x9c] = -y >> 8, dev[0x9d] = -y;
 731+	if(mouse_vector) uxn_eval(mouse_vector);
 732+	dev[0x9a] = 0, dev[0x9b] = 0;
 733+	dev[0x9c] = 0, dev[0x9d] = 0;
 734+}
 735+
 736+void
 737+mouse_deo_vector(void)
 738+{
 739+	mouse_vector = PEEK2(&dev[0x90]);
 740+}
 741+
 742+/*
 743+@|File -------------------------------------------------------------- */
 744+
 745+#include <dirent.h>
 746+#include <sys/stat.h>
 747+#include <string.h>
 748+
 749+typedef struct {
 750+	FILE *f;
 751+	DIR *dir;
 752+	char *filepath;
 753+	enum { IDLE,
 754+		FILE_READ,
 755+		FILE_WRITE,
 756+		DIR_READ,
 757+		DIR_WRITE
 758+	} state;
 759+} UxnFile;
 760+
 761+static UxnFile ufs[2];
 762+static unsigned int rL1, rL2;
 763+
 764+static void
 765+make_pathfile(char *pathbuf, const char *filepath, const char *basename)
 766+{
 767+	char c = '/';
 768+	while(*filepath)
 769+		c = *filepath++, *pathbuf = c, pathbuf++;
 770+	if(c != '/')
 771+		*pathbuf = '/', pathbuf++;
 772+	while(*basename)
 773+		*pathbuf = *basename++, pathbuf++;
 774+	*pathbuf = 0;
 775+}
 776+
 777+static unsigned int
 778+put_fill(Uint8 *dest, unsigned int len, char c)
 779+{
 780+	memset(dest, c, len);
 781+	return len;
 782+}
 783+
 784+static unsigned int
 785+put_size(Uint8 *dest, unsigned int len, unsigned int size)
 786+{
 787+	unsigned int i;
 788+	for(i = 0, dest += len; i < len; i++, size >>= 4)
 789+		*(--dest) = "0123456789abcdef"[(Uint8)(size & 0xf)];
 790+	return len;
 791+}
 792+
 793+static unsigned int
 794+put_text(Uint8 *dest, const char *text)
 795+{
 796+	Uint8 *anchor = dest;
 797+	while(*text)
 798+		*dest = *text++, dest++;
 799+	*dest = 0;
 800+	return dest - anchor;
 801+}
 802+
 803+static unsigned int
 804+put_stat(Uint8 *dest, unsigned int len, unsigned int size, unsigned int err, unsigned int dir, unsigned int capsize)
 805+{
 806+	if(err) return put_fill(dest, len, '!');
 807+	if(dir) return put_fill(dest, len, '-');
 808+	if(capsize && size >= 0x10000) return put_fill(dest, len, '?');
 809+	return put_size(dest, len, size);
 810+}
 811+
 812+static unsigned int
 813+put_statfile(Uint8 *dest, const char *filepath, const char *basename)
 814+{
 815+	unsigned int err, dir;
 816+	struct stat st;
 817+	Uint8 *anchor = dest;
 818+	char pathbuf[0x2000];
 819+	make_pathfile(pathbuf, filepath, basename);
 820+	err = stat(pathbuf, &st);
 821+	dir = S_ISDIR(st.st_mode);
 822+	dest += put_stat(dest, 4, st.st_size, err, dir, 1);
 823+	dest += put_text(dest, " ");
 824+	dest += put_text(dest, basename);
 825+	dest += put_text(dest, dir ? "/\n" : "\n");
 826+	return dest - anchor;
 827+}
 828+
 829+static unsigned int
 830+put_fdir(Uint8 *dest, unsigned int len, const char *filepath, DIR *dir)
 831+{
 832+	Uint8 *p = dest, *end = dest + len - 1;
 833+	struct dirent *de;
 834+	while((de = readdir(dir)) && p < end) {
 835+		const char *name = de->d_name;
 836+		if(name[0] == '.' && (!name[1] || (name[1] == '.' && !name[2])))
 837+			continue;
 838+		p += put_statfile(p, filepath, name);
 839+	}
 840+	*p = 0;
 841+	return p - dest;
 842+}
 843+
 844+static unsigned int
 845+is_dir_path(const char *p)
 846+{
 847+	const char *end = p;
 848+	while(*end) end++;
 849+	return end > p && end[-1] == '/';
 850+}
 851+
 852+static unsigned int
 853+is_dir_real(char *p)
 854+{
 855+	struct stat st;
 856+	return stat(p, &st) == 0 && S_ISDIR(st.st_mode);
 857+}
 858+
 859+static unsigned int
 860+file_write_dir(const char *p)
 861+{
 862+	char buf[0x1000];
 863+	char *s = buf;
 864+	unsigned int ok = 1;
 865+	while(*p && ok) {
 866+		*s++ = *p;
 867+		if(*p++ == '/') {
 868+			s[-1] = '\0';
 869+			ok = is_dir_real(buf) || mkdir(buf, 0755) == 0;
 870+			s[-1] = '/';
 871+		}
 872+	}
 873+	return ok;
 874+}
 875+
 876+static void
 877+file_reset(unsigned int id)
 878+{
 879+	if(ufs[id].f != NULL) fclose(ufs[id].f), ufs[id].f = NULL;
 880+	if(ufs[id].dir != NULL) closedir(ufs[id].dir), ufs[id].dir = NULL;
 881+	ufs[id].state = IDLE;
 882+}
 883+
 884+static unsigned int
 885+file_init(unsigned int id, Uint16 addr)
 886+{
 887+	file_reset(id);
 888+	ufs[id].filepath = (char *)&ram[addr];
 889+	return 0;
 890+}
 891+
 892+static unsigned int
 893+file_not_ready(unsigned int id)
 894+{
 895+	return ufs[id].filepath == 0;
 896+}
 897+
 898+static unsigned int
 899+file_read(unsigned int id, Uint16 addr, unsigned int len)
 900+{
 901+	void *dest = &ram[addr];
 902+	struct stat st;
 903+	unsigned int n;
 904+	if(file_not_ready(id))
 905+		return 0;
 906+	if(addr + len > 0x10000)
 907+		len = 0x10000 - addr;
 908+	if(ufs[id].state != FILE_READ && ufs[id].state != DIR_READ) {
 909+		file_reset(id);
 910+		if(stat(ufs[id].filepath, &st) == 0 && S_ISDIR(st.st_mode)) {
 911+			if((ufs[id].dir = opendir(ufs[id].filepath)) != NULL)
 912+				ufs[id].state = DIR_READ;
 913+		} else if((ufs[id].f = fopen(ufs[id].filepath, "rb")) != NULL) {
 914+			ufs[id].state = FILE_READ;
 915+		}
 916+	}
 917+	if(ufs[id].state == FILE_READ) {
 918+		n = fread(dest, 1, len, ufs[id].f);
 919+		if(n == 0) file_reset(id);
 920+		return n;
 921+	}
 922+	if(ufs[id].state == DIR_READ) {
 923+		n = put_fdir(dest, len, ufs[id].filepath, ufs[id].dir);
 924+		if(n == 0) file_reset(id);
 925+		return n;
 926+	}
 927+	return 0;
 928+}
 929+
 930+static unsigned int
 931+file_write(unsigned int id, Uint16 addr, unsigned int len, Uint8 flags)
 932+{
 933+	unsigned int ret = 0;
 934+	if(file_not_ready(id))
 935+		return 0;
 936+	if(addr + len > 0x10000)
 937+		len = 0x10000 - addr;
 938+	file_write_dir(ufs[id].filepath);
 939+	if(ufs[id].state != FILE_WRITE && ufs[id].state != DIR_WRITE) {
 940+		file_reset(id);
 941+		if(is_dir_path(ufs[id].filepath))
 942+			ufs[id].state = DIR_WRITE;
 943+		else if((ufs[id].f = fopen(ufs[id].filepath, (flags & 0x01) ? "ab" : "wb")) != NULL)
 944+			ufs[id].state = FILE_WRITE;
 945+	}
 946+	if(ufs[id].state == FILE_WRITE)
 947+		if((ret = fwrite(&ram[addr], 1, len, ufs[id].f)) > 0 && fflush(ufs[id].f) != 0)
 948+			ret = 0;
 949+	if(ufs[id].state == DIR_WRITE)
 950+		ret = is_dir_real(ufs[id].filepath);
 951+	return ret;
 952+}
 953+
 954+static unsigned int
 955+file_stat(unsigned int id, Uint16 addr, unsigned int len)
 956+{
 957+	unsigned int err, dir;
 958+	struct stat st;
 959+	if(file_not_ready(id))
 960+		return 0;
 961+	if(addr + len > 0x10000)
 962+		len = 0x10000 - addr;
 963+	err = stat(ufs[id].filepath, &st);
 964+	dir = S_ISDIR(st.st_mode);
 965+	return put_stat(&ram[addr], len, st.st_size, err, dir, 0);
 966+}
 967+
 968+static unsigned int
 969+file_delete(unsigned int id)
 970+{
 971+	if(file_not_ready(id))
 972+		return 0;
 973+	return !unlink(ufs[id].filepath);
 974+}
 975+
 976+static void
 977+file_success(unsigned int port, unsigned int value)
 978+{
 979+	dev[port] = value >> 8, dev[port + 1] = value;
 980+}
 981+
 982+/* clang-format off */
 983+
 984+static void filea_deo_vector(void) { rL1 = PEEK2(&dev[0xaa]); }
 985+static void filea_deo_stat(void) { file_success(0xa2, file_stat(0, PEEK2(&dev[0xa4]), rL1)); }
 986+static void filea_deo_delete(void) { file_success(0xa2, file_delete(0)); }
 987+static void filea_deo_name(void) { file_success(0xa2, file_init(0, PEEK2(&dev[0xa8]))); }
 988+static void filea_deo_read(void) { file_success(0xa2, file_read(0, PEEK2(&dev[0xac]), rL1)); }
 989+static void filea_deo_write(void) { file_success(0xa2, file_write(0, PEEK2(&dev[0xae]), rL1, dev[0xa7])); }
 990+static void fileb_deo_vector(void) { rL2 = PEEK2(&dev[0xba]); }
 991+static void fileb_deo_stat(void) { file_success(0xb2, file_stat(1, PEEK2(&dev[0xb4]), rL2)); }
 992+static void fileb_deo_delete(void) { file_success(0xb2, file_delete(1)); }
 993+static void fileb_deo_name(void) { file_success(0xb2, file_init(1, PEEK2(&dev[0xb8]))); }
 994+static void fileb_deo_read(void) { file_success(0xb2, file_read(1, PEEK2(&dev[0xbc]), rL2)); }
 995+static void fileb_deo_write(void) { file_success(0xb2, file_write(1, PEEK2(&dev[0xbe]), rL2, dev[0xb7])); }
 996+
 997+/* clang-format on */
 998+
 999+/*
1000+@|Datetime ---------------------------------------------------------- */
1001+
1002+#include <time.h>
1003+
1004+time_t datetime_seconds;
1005+struct tm *datetime_t, datetime_zt = {0};
1006+
1007+void
1008+datetime_update(void)
1009+{
1010+	datetime_seconds = time(NULL);
1011+	datetime_t = localtime(&datetime_seconds);
1012+	if(datetime_t == NULL)
1013+		datetime_t = &datetime_zt;
1014+}
1015+
1016+/* clang-format off */
1017+
1018+static Uint8 datetime_dei_yhb(void) { datetime_update(); return (datetime_t->tm_year + 1900) >> 8; }
1019+static Uint8 datetime_dei_ylb(void) { datetime_update(); return (datetime_t->tm_year + 1900); }
1020+static Uint8 datetime_dei_mon(void) { datetime_update(); return datetime_t->tm_mon; }
1021+static Uint8 datetime_dei_day(void) { datetime_update(); return datetime_t->tm_mday; }
1022+static Uint8 datetime_dei_hou(void) { datetime_update(); return datetime_t->tm_hour; }
1023+static Uint8 datetime_dei_min(void) { datetime_update(); return datetime_t->tm_min; }
1024+static Uint8 datetime_dei_sec(void) { datetime_update(); return datetime_t->tm_sec; }
1025+static Uint8 datetime_dei_wday(void) { datetime_update(); return datetime_t->tm_wday; }
1026+static Uint8 datetime_dei_ydayhb(void) { datetime_update(); return datetime_t->tm_yday >> 8; }
1027+static Uint8 datetime_dei_ydaylb(void) { datetime_update(); return datetime_t->tm_yday; }
1028+static Uint8 datetime_dei_dst(void) { datetime_update(); return datetime_t->tm_isdst; }
1029+
1030+/* clang-format on */
1031+
1032+/*
1033+@|Core -------------------------------------------------------------- */
1034+
1035+static const dei_handler dei_handlers[256] = {
1036+	[0x04] = system_dei_wst,
1037+	[0x05] = system_dei_rst,
1038+	[0x15] = console_dei_childlive,
1039+	[0x16] = console_dei_childexit,
1040+	[0x28] = screen_dei_rxhb,
1041+	[0x29] = screen_dei_rxlb,
1042+	[0x2a] = screen_dei_ryhb,
1043+	[0x2b] = screen_dei_rylb,
1044+	[0x2c] = screen_dei_rahb,
1045+	[0x2d] = screen_dei_ralb,
1046+	[0xc0] = datetime_dei_yhb,
1047+	[0xc1] = datetime_dei_ylb,
1048+	[0xc2] = datetime_dei_mon,
1049+	[0xc3] = datetime_dei_day,
1050+	[0xc4] = datetime_dei_hou,
1051+	[0xc5] = datetime_dei_min,
1052+	[0xc6] = datetime_dei_sec,
1053+	[0xc7] = datetime_dei_wday,
1054+	[0xc8] = datetime_dei_ydayhb,
1055+	[0xc9] = datetime_dei_ydaylb,
1056+	[0xca] = datetime_dei_dst,
1057+};
1058+
1059+static const deo_handler deo_handlers[256] = {
1060+	[0x03] = system_deo_expansion,
1061+	[0x04] = system_deo_wst,
1062+	[0x05] = system_deo_rst,
1063+	[0x08] = system_deo_colorize,
1064+	[0x09] = system_deo_colorize,
1065+	[0x0a] = system_deo_colorize,
1066+	[0x0b] = system_deo_colorize,
1067+	[0x0c] = system_deo_colorize,
1068+	[0x0d] = system_deo_colorize,
1069+	[0x0e] = system_deo_print,
1070+	[0x11] = console_deo_vector,
1071+	[0x18] = console_deo_stdout,
1072+	[0x19] = console_deo_stderr,
1073+	[0x1a] = console_deo_hb,
1074+	[0x1b] = console_deo_lb,
1075+	[0x1f] = console_deo_fork,
1076+	[0x21] = screen_deo_vector,
1077+	[0x23] = screen_deo_width,
1078+	[0x25] = screen_deo_height,
1079+	[0x26] = screen_deo_auto,
1080+	[0x28] = screen_deo_x,
1081+	[0x29] = screen_deo_x,
1082+	[0x2a] = screen_deo_y,
1083+	[0x2b] = screen_deo_y,
1084+	[0x2c] = screen_deo_addr,
1085+	[0x2d] = screen_deo_addr,
1086+	[0x2e] = screen_deo_pixel,
1087+	[0x2f] = screen_deo_sprite,
1088+	[0x81] = controller_deo_vector,
1089+	[0x91] = mouse_deo_vector,
1090+	[0xab] = filea_deo_vector,
1091+	[0xa5] = filea_deo_stat,
1092+	[0xa6] = filea_deo_delete,
1093+	[0xa9] = filea_deo_name,
1094+	[0xad] = filea_deo_read,
1095+	[0xaf] = filea_deo_write,
1096+	[0xbb] = fileb_deo_vector,
1097+	[0xb5] = fileb_deo_stat,
1098+	[0xb6] = fileb_deo_delete,
1099+	[0xb9] = fileb_deo_name,
1100+	[0xbd] = fileb_deo_read,
1101+	[0xbf] = fileb_deo_write};
1102+
1103+#define CONINBUFSIZE 256
1104+
1105+Uint8
1106+emu_dei(const Uint8 port)
1107+{
1108+	if(dei_handlers[port]) return dei_handlers[port]();
1109+	return dev[port];
1110+}
1111+
1112+void
1113+emu_deo(const Uint8 port, const Uint8 value)
1114+{
1115+	dev[port] = value;
1116+	if(deo_handlers[port]) deo_handlers[port]();
1117+}
1118+
1119+void
1120+emu_resize(void)
1121+{
1122+	const int width = screen_width * screen_zoom;
1123+	const int height = screen_height * screen_zoom;
1124+	if(emu_img) {
1125+		emu_img->data = NULL;
1126+		XDestroyImage(emu_img);
1127+	}
1128+	emu_img = XCreateImage(emu_dpy, DefaultVisual(emu_dpy, 0), DefaultDepth(emu_dpy, DefaultScreen(emu_dpy)), ZPixmap, 0, (char *)screen_pixels, width, height, 32, 0);
1129+	XResizeWindow(emu_dpy, emu_win, width, height);
1130+}
1131+
1132+void
1133+emu_redraw(void)
1134+{
1135+	const int ax = screen_x1 * screen_zoom;
1136+	const int ay = screen_y1 * screen_zoom;
1137+	const int aw = screen_x2 * screen_zoom - ax;
1138+	const int ah = screen_y2 * screen_zoom - ay;
1139+	XPutImage(emu_dpy, emu_win, DefaultGC(emu_dpy, 0), emu_img, ax, ay, ax, ay, aw, ah);
1140+}
1141+
1142+static void
1143+emu_restart(unsigned int soft)
1144+{
1145+	screen_resize(WIDTH, HEIGHT), system_reboot(soft), uxn_eval(0x100);
1146+}
1147+
1148+static Uint8
1149+get_button(KeySym sym)
1150+{
1151+	switch(sym) {
1152+	case XK_Up: return 0x10;
1153+	case XK_Down: return 0x20;
1154+	case XK_Left: return 0x40;
1155+	case XK_Right: return 0x80;
1156+	case XK_Control_L: return 0x01;
1157+	case XK_Alt_R:
1158+	case XK_Alt_L: return 0x02;
1159+	case XK_Shift_L: return 0x04;
1160+	case XK_Home: return 0x08;
1161+	case XK_Meta_L: return 0x02;
1162+	}
1163+	return 0x00;
1164+}
1165+
1166+static void
1167+display_center(void)
1168+{
1169+	Screen *screen = DefaultScreenOfDisplay(emu_dpy);
1170+	const int w = screen_width * screen_zoom;
1171+	const int h = screen_height * screen_zoom;
1172+	const int x = screen->width / 2 - w / 2;
1173+	const int y = screen->height / 2 - h / 2;
1174+	emu_win = XCreateSimpleWindow(emu_dpy, RootWindow(emu_dpy, 0), x, y, w, h, 1, 0, 0);
1175+}
1176+
1177+static void
1178+display_hidecursor(void)
1179+{
1180+	XColor black = {0};
1181+	static const char empty[1] = {0};
1182+	Pixmap bitmap = XCreateBitmapFromData(emu_dpy, emu_win, empty, 1, 1);
1183+	Cursor blank = XCreatePixmapCursor(emu_dpy, bitmap, bitmap, &black, &black, 0, 0);
1184+	XDefineCursor(emu_dpy, emu_win, blank);
1185+	XFreeCursor(emu_dpy, blank);
1186+	XFreePixmap(emu_dpy, bitmap);
1187+}
1188+
1189+static void
1190+display_floating(void)
1191+{
1192+	Atom wmDelete = XInternAtom(emu_dpy, "WM_DELETE_WINDOW", True);
1193+	Atom wmType = XInternAtom(emu_dpy, "_NET_WM_WINDOW_TYPE", False);
1194+	Atom wmDialog = XInternAtom(emu_dpy, "_NET_WM_WINDOW_TYPE_DIALOG", False);
1195+	XClassHint class = {"uxn11", "Uxn"};
1196+	XSetWMProtocols(emu_dpy, emu_win, &wmDelete, 1);
1197+	XChangeProperty(emu_dpy, emu_win, wmType, 4, 32, PropModeReplace, (Uint8 *)&wmDialog, 1);
1198+	XSelectInput(emu_dpy, emu_win, ButtonPressMask | ButtonReleaseMask | PointerMotionMask | ExposureMask | KeyPressMask | KeyReleaseMask | LeaveWindowMask);
1199+	XStoreName(emu_dpy, emu_win, "uxn11");
1200+	XSetClassHint(emu_dpy, emu_win, &class);
1201+}
1202+
1203+static void
1204+emu_event(void)
1205+{
1206+	char buf[7];
1207+	XEvent ev;
1208+	XNextEvent(emu_dpy, &ev);
1209+	switch(ev.type) {
1210+	case Expose:
1211+		screen_reqdraw = 1;
1212+		break;
1213+	case ClientMessage:
1214+		dev[0x0f] = 0x80;
1215+		break;
1216+	case KeyPress: {
1217+		KeySym sym;
1218+		XLookupString((XKeyPressedEvent *)&ev, buf, 7, &sym, 0);
1219+		switch(sym) {
1220+		case XK_F1: screen_zoom = (screen_zoom % 3) + 1, screen_reqsize = screen_reqdraw = 1; break;
1221+		case XK_F2: emu_deo(0xe, 0x1); break;
1222+		case XK_F4: console_close(), emu_restart(0); break;
1223+		case XK_F5: console_close(), emu_restart(1); break;
1224+		}
1225+		controller_down(get_button(sym));
1226+		if(sym == XK_Tab)
1227+			buf[0] = 0x9;
1228+		controller_key(sym < 0x80 ? sym : (Uint8)buf[0]);
1229+	} break;
1230+	case KeyRelease: {
1231+		KeySym sym;
1232+		XLookupString((XKeyPressedEvent *)&ev, buf, 7, &sym, 0);
1233+		controller_up(get_button(sym));
1234+	} break;
1235+	case ButtonPress: {
1236+		XButtonPressedEvent *e = (XButtonPressedEvent *)&ev;
1237+		switch(e->button) {
1238+		case 4: mouse_scroll(0, 1); break;
1239+		case 5: mouse_scroll(0, -1); break;
1240+		case 6: mouse_scroll(1, 0); break;
1241+		case 7: mouse_scroll(-1, 0); break;
1242+		default: mouse_down(0x1 << (e->button - 1));
1243+		}
1244+	} break;
1245+	case ButtonRelease: {
1246+		XButtonPressedEvent *e = (XButtonPressedEvent *)&ev;
1247+		mouse_up(0x1 << (e->button - 1));
1248+	} break;
1249+	case LeaveNotify:
1250+	case MotionNotify: {
1251+		XMotionEvent *e = (XMotionEvent *)&ev;
1252+		mouse_pos(e->x / screen_zoom, e->y / screen_zoom);
1253+	} break;
1254+	}
1255+}
1256+
1257+static int
1258+emu_init(void)
1259+{
1260+	emu_dpy = XOpenDisplay(NULL);
1261+	if(!emu_dpy)
1262+		return !fprintf(stderr, "Display: failed\n");
1263+	emu_x11 = ConnectionNumber(emu_dpy);
1264+	display_center();
1265+	display_hidecursor();
1266+	display_floating();
1267+	XMapWindow(emu_dpy, emu_win);
1268+	XFlush(emu_dpy);
1269+	return 1;
1270+}
1271+
1272+static void
1273+emu_run(void)
1274+{
1275+	char coninp[CONINBUFSIZE];
1276+	int kq, i, xfd;
1277+	ssize_t n;
1278+	struct kevent evlist[3], ev, timer;
1279+	static const struct timespec timeout = { 1, 0 };
1280+
1281+	if((kq = kqueue()) == -1) {
1282+		perror("kqueue");
1283+		exit(1);
1284+	}
1285+
1286+	xfd = XConnectionNumber(emu_dpy);
1287+	EV_SET(&evlist[0], -1, EVFILT_TIMER, EV_ADD, NOTE_NSECONDS, 16666666, NULL);
1288+	EV_SET(&evlist[1], xfd, EVFILT_READ, EV_ADD, 0, 0, NULL);
1289+	EV_SET(&evlist[2], STDIN_FILENO, EVFILT_READ, EV_ADD, 0, 0, NULL);
1290+
1291+	if(kevent(kq, evlist, 3, NULL, 0, NULL) < 0) {
1292+		perror("kevent");
1293+		exit(1);
1294+	}
1295+
1296+	while(!dev[0x0f]) {
1297+		memset(&ev, 0, sizeof(struct kevent));
1298+		if(kevent(kq, NULL, 0, &ev, 1, &timeout) < 0) {
1299+			perror("kevent");
1300+			exit(1);
1301+		}
1302+
1303+		while(XPending(emu_dpy))
1304+			emu_event();
1305+		
1306+		switch(ev.filter) {
1307+		case EVFILT_TIMER:
1308+			screen_update();
1309+			break;
1310+		case EVFILT_READ:
1311+			if(ev.ident == STDIN_FILENO) {
1312+				n = read(STDIN_FILENO, coninp, CONINBUFSIZE - 1);
1313+				for(i = 0, coninp[n] = 0; i < n; i++)
1314+					console_input(coninp[i], CONSOLE_STD);
1315+				if(n == 0)
1316+					console_input(0, CONSOLE_STD);
1317+			}
1318+			break;
1319+		}
1320+	}
1321+	
1322+	close(kq);
1323+	if(emu_win)
1324+		XDestroyWindow(emu_dpy, emu_win);
1325+	if(emu_dpy)
1326+		XCloseDisplay(emu_dpy);
1327+	console_close();
1328+}
1329+
1330+int
1331+main(int argc, char **argv)
1332+{
1333+	int i = 1;
1334+	if(argc == 2 && argv[1][0] == '-' && argv[1][1] == 'v')
1335+		return !fprintf(stdout, "%s - Varvara Emulator, 9 Jan 2026.\n", argv[0]);
1336+	else if(argc == 1)
1337+		return !fprintf(stdout, "usage: %s [-v] file.rom [args..]\n", argv[0]);
1338+	else if(!system_boot(argv[i++], argc > 2))
1339+		return !fprintf(stdout, "Could not load %s.\n", argv[i - 1]);
1340+	screen_resize(WIDTH, HEIGHT);
1341+	if(uxn_eval(0x100) && console_vector) {
1342+		for(; i < argc; i++) {
1343+			char *p = argv[i];
1344+			while(*p)
1345+				console_input(*p++, CONSOLE_ARG);
1346+			console_input('\n', i == argc - 1 ? CONSOLE_END : CONSOLE_EOA);
1347+		}
1348+	}
1349+	if(!dev[0x0f]) {
1350+		if(!emu_init())
1351+			return !fprintf(stdout, "Could not initialize %s.\n", argv[0]);
1352+		emu_run();
1353+	}
1354+	return dev[0x0f] & 0x7f;
1355+}
R src/uxncli11.c => etc/alt/uxncli11.c
+0, -0
+2, -2
 1@@ -205,7 +205,7 @@ system_deo_expansion(void)
 2 static Uint8 system_dei_wst(void) { return ptr[0]; }
 3 static Uint8 system_dei_rst(void) { return ptr[1]; }
 4 static void system_deo_wst(void) { ptr[0] = dev[4]; }
 5-static void system_deo_rst(void) { ptr[0] = dev[4]; }
 6+static void system_deo_rst(void) { ptr[1] = dev[5]; }
 7 static void system_deo_print(void) { system_print("WST", 0), system_print("RST", 1); }
 8 
 9 /* clang-format on */
10@@ -1321,7 +1321,7 @@ main(int argc, char **argv)
11 {
12 	int i = 1;
13 	if(argc == 2 && argv[1][0] == '-' && argv[1][1] == 'v')
14-		return !fprintf(stdout, "%s - Varvara Emulator, 8 Jan 2026.\n", argv[0]);
15+		return !fprintf(stdout, "%s - Varvara Emulator, 9 Jan 2026.\n", argv[0]);
16 	else if(argc == 1)
17 		return !fprintf(stdout, "usage: %s [-v] file.rom [args..]\n", argv[0]);
18 	else if(!system_boot(argv[i++], argc > 2))