commit 6169107

Devine Lu Linvega  ·  2025-04-19 17:11:26 +0000 UTC
parent 3b40bc1
Made uxn11 single-file
3 files changed,  +1076, -25
+5, -7
 1@@ -1,5 +1,3 @@
 2-CLI_src=src/uxn.c src/devices/system.c src/devices/console.c src/devices/file.c src/devices/datetime.c
 3-EMU_src=${CLI_src} src/devices/screen.c src/devices/controller.c src/devices/mouse.c
 4 RELEASE_flags=-DNDEBUG -O2 -g0 -s
 5 DEBUG_flags=-std=c89 -D_POSIX_C_SOURCE=199309L -DDEBUG -Wall -Wno-unknown-pragmas -Wpedantic -Wshadow -Wextra -Werror=implicit-int -Werror=incompatible-pointer-types -Werror=int-conversion -Wvla -g -Og -fsanitize=address -fsanitize=undefined
 6 
 7@@ -34,6 +32,7 @@ grab:
 8 
 9 archive:
10 	@ cp src/uxncli.c ../oscean/etc/uxncli.c.txt
11+	@ cp src/uxn11.c ../oscean/etc/uxn11.c.txt
12 
13 clean:
14 	@ rm -fr bin
15@@ -62,13 +61,12 @@ bin/screen.rom: bin/uxncli bin/asm.rom etc/examples/screen.tal
16 bin/uxncli: src/uxncli.c
17 	@ mkdir -p bin
18 	@ cc ${RELEASE_flags} ${CFLAGS} src/uxncli.c -lutil -o bin/uxncli
19-bin/uxn11: ${EMU_src} src/uxn11.c
20+bin/uxn11: src/uxn11.c
21 	@ mkdir -p bin
22-	@ cc ${RELEASE_flags} ${CFLAGS} ${EMU_src} src/uxn11.c -lX11 -lutil -o bin/uxn11
23+	@ cc ${RELEASE_flags} ${CFLAGS} src/uxn11.c -lX11 -lutil -o bin/uxn11
24 bin/uxncli-debug: src/uxncli.c
25 	@ mkdir -p bin
26 	@ cc ${DEBUG_flags} ${CFLAGS} src/uxncli.c -lutil -o bin/uxncli-debug
27-bin/uxn11-debug: ${EMU_src} src/uxn11.c
28+bin/uxn11-debug: src/uxn11.c
29 	@ mkdir -p bin
30-	@ cc ${DEBUG_flags} ${CFLAGS} ${EMU_src} src/uxn11.c -lX11 -lutil -o bin/uxn11-debug
31-
32+	@ cc ${DEBUG_flags} ${CFLAGS} src/uxn11.c -lX11 -lutil -o bin/uxn11-debug
+1070, -17
   1@@ -1,24 +1,18 @@
   2 #include <stdio.h>
   3 #include <stdlib.h>
   4+#include <unistd.h>
   5+
   6 #include <X11/Xlib.h>
   7 #include <X11/Xutil.h>
   8 #include <X11/keysymdef.h>
   9 #include <sys/timerfd.h>
  10-#include <unistd.h>
  11 #include <poll.h>
  12 
  13-#include "uxn.h"
  14-
  15-#include "devices/system.h"
  16-#include "devices/console.h"
  17-#include "devices/screen.h"
  18-#include "devices/controller.h"
  19-#include "devices/mouse.h"
  20-#include "devices/file.h"
  21-#include "devices/datetime.h"
  22-
  23-Uxn uxn;
  24-int console_vector;
  25+#define STEP_MAX 0x80000000
  26+#define PAGE_PROGRAM 0x100
  27+#define RAM_PAGES 0x10
  28+#define WIDTH (64 * 8)
  29+#define HEIGHT (40 * 8)
  30 
  31 /*
  32 Copyright (c) 2022-2025 Devine Lu Linvega
  33@@ -29,14 +23,1073 @@ copyright notice and this permission notice appear in all copies.
  34 
  35 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  36 WITH REGARD TO THIS SOFTWARE.
  37+
  38+cc -DNDEBUG -O2 -g0 -s src/uxn11.c -lX11 -lutil -o bin/uxn11
  39 */
  40 
  41 static XImage *ximage;
  42 static Display *display;
  43 static Window window;
  44 
  45-#define WIDTH (64 * 8)
  46-#define HEIGHT (40 * 8)
  47+/*
  48+@|Uxn --------------------------------------------------------------- */
  49+
  50+typedef unsigned char Uint8;
  51+typedef signed char Sint8;
  52+typedef unsigned short Uint16;
  53+typedef signed short Sint16;
  54+typedef unsigned int Uint32;
  55+
  56+typedef struct {
  57+	Uint8 dat[0x100], ptr;
  58+} Stack;
  59+
  60+typedef struct Uxn {
  61+	Uint8 *ram, dev[0x100];
  62+	Stack wst, rst;
  63+} Uxn;
  64+
  65+Uint8 emu_dei(Uint8 addr);
  66+void emu_deo(Uint8 addr, Uint8 value);
  67+static Uxn uxn;
  68+
  69+/* clang-format off */
  70+
  71+#define PEEK2(d) (*(d) << 8 | (d)[1])
  72+#define POKE2(d, v) { *(d) = (v) >> 8; (d)[1] = (v); }
  73+
  74+#define OPC(opc, init, body) {\
  75+	case 0x00|opc: {const int _2=0,_r=0;init body;} break;\
  76+	case 0x20|opc: {const int _2=1,_r=0;init body;} break;\
  77+	case 0x40|opc: {const int _2=0,_r=1;init body;} break;\
  78+	case 0x60|opc: {const int _2=1,_r=1;init body;} break;\
  79+	case 0x80|opc: {const int _2=0,_r=0,k=uxn.wst.ptr;init uxn.wst.ptr=k;body;} break;\
  80+	case 0xa0|opc: {const int _2=1,_r=0,k=uxn.wst.ptr;init uxn.wst.ptr=k;body;} break;\
  81+	case 0xc0|opc: {const int _2=0,_r=1,k=uxn.rst.ptr;init uxn.rst.ptr=k;body;} break;\
  82+	case 0xe0|opc: {const int _2=1,_r=1,k=uxn.rst.ptr;init uxn.rst.ptr=k;body;} break;\
  83+}
  84+
  85+/* Microcode */
  86+
  87+#define JMI a = uxn.ram[pc] << 8 | uxn.ram[pc + 1], pc += a + 2;
  88+#define REM if(_r) uxn.rst.ptr -= 1 + _2; else uxn.wst.ptr -= 1 + _2;
  89+#define INC(s) uxn.s.dat[uxn.s.ptr++]
  90+#define DEC(s) uxn.s.dat[--uxn.s.ptr]
  91+#define JMP(x) { if(_2) pc = x; else pc += (Sint8)x; }
  92+#define PO1(o) { o = _r ? DEC(rst) : DEC(wst);}
  93+#define PO2(o) { if(_r) o = DEC(rst), o |= DEC(rst) << 8; else o = DEC(wst), o |= DEC(wst) << 8; }
  94+#define POx(o) { if(_2) PO2(o) else PO1(o) }
  95+#define PU1(i) { if(_r) INC(rst) = i; else INC(wst) = i; }
  96+#define RP1(i) { if(_r) INC(wst) = i; else INC(rst) = i; }
  97+#define PUx(i) { if(_2) { c = (i); PU1(c >> 8) PU1(c) } else PU1(i) }
  98+#define GET(o) { if(_2) PO1(o[1]) PO1(o[0]) }
  99+#define PUT(i) { PU1(i[0]) if(_2) PU1(i[1]) }
 100+#define DEI(i,o) o[0] = emu_dei(i); if(_2) o[1] = emu_dei(i + 1); PUT(o)
 101+#define DEO(i,j) emu_deo(i, j[0]); if(_2) emu_deo(i + 1, j[1]);
 102+#define PEK(i,o,m) o[0] = uxn.ram[i]; if(_2) o[1] = uxn.ram[(i + 1) & m]; PUT(o)
 103+#define POK(i,j,m) uxn.ram[i] = j[0]; if(_2) uxn.ram[(i + 1) & m] = j[1];
 104+
 105+int
 106+uxn_eval(Uint16 pc)
 107+{
 108+	Uint32 a, b, c, x[2], y[2], z[2], step;
 109+	if(!pc || uxn.dev[0x0f]) return 0;
 110+	for(step = STEP_MAX; step; step--) {
 111+		switch(uxn.ram[pc++]) {
 112+		/* BRK */ case 0x00: return 1;
 113+		/* JCI */ case 0x20: if(DEC(wst)) { JMI break; } pc += 2; break;
 114+		/* JMI */ case 0x40: JMI break;
 115+		/* JSI */ case 0x60: c = pc + 2; INC(rst) = c >> 8; INC(rst) = c; JMI break;
 116+		/* LI2 */ case 0xa0: INC(wst) = uxn.ram[pc++]; /* fall-through */
 117+		/* LIT */ case 0x80: INC(wst) = uxn.ram[pc++]; break;
 118+		/* L2r */ case 0xe0: INC(rst) = uxn.ram[pc++]; /* fall-through */
 119+		/* LIr */ case 0xc0: INC(rst) = uxn.ram[pc++]; break;
 120+		/* INC */ OPC(0x01,POx(a),PUx(a + 1))
 121+		/* POP */ OPC(0x02,REM ,{})
 122+		/* NIP */ OPC(0x03,GET(x) REM ,PUT(x))
 123+		/* SWP */ OPC(0x04,GET(x) GET(y),PUT(x) PUT(y))
 124+		/* ROT */ OPC(0x05,GET(x) GET(y) GET(z),PUT(y) PUT(x) PUT(z))
 125+		/* DUP */ OPC(0x06,GET(x),PUT(x) PUT(x))
 126+		/* OVR */ OPC(0x07,GET(x) GET(y),PUT(y) PUT(x) PUT(y))
 127+		/* EQU */ OPC(0x08,POx(a) POx(b),PU1(b == a))
 128+		/* NEQ */ OPC(0x09,POx(a) POx(b),PU1(b != a))
 129+		/* GTH */ OPC(0x0a,POx(a) POx(b),PU1(b > a))
 130+		/* LTH */ OPC(0x0b,POx(a) POx(b),PU1(b < a))
 131+		/* JMP */ OPC(0x0c,POx(a),JMP(a))
 132+		/* JCN */ OPC(0x0d,POx(a) PO1(b),if(b) JMP(a))
 133+		/* JSR */ OPC(0x0e,POx(a),RP1(pc >> 8) RP1(pc) JMP(a))
 134+		/* STH */ OPC(0x0f,GET(x),RP1(x[0]) if(_2) RP1(x[1]))
 135+		/* LDZ */ OPC(0x10,PO1(a),PEK(a, x, 0xff))
 136+		/* STZ */ OPC(0x11,PO1(a) GET(y),POK(a, y, 0xff))
 137+		/* LDR */ OPC(0x12,PO1(a),PEK(pc + (Sint8)a, x, 0xffff))
 138+		/* STR */ OPC(0x13,PO1(a) GET(y),POK(pc + (Sint8)a, y, 0xffff))
 139+		/* LDA */ OPC(0x14,PO2(a),PEK(a, x, 0xffff))
 140+		/* STA */ OPC(0x15,PO2(a) GET(y),POK(a, y, 0xffff))
 141+		/* DEI */ OPC(0x16,PO1(a),DEI(a, x))
 142+		/* DEO */ OPC(0x17,PO1(a) GET(y),DEO(a, y))
 143+		/* ADD */ OPC(0x18,POx(a) POx(b),PUx(b + a))
 144+		/* SUB */ OPC(0x19,POx(a) POx(b),PUx(b - a))
 145+		/* MUL */ OPC(0x1a,POx(a) POx(b),PUx(b * a))
 146+		/* DIV */ OPC(0x1b,POx(a) POx(b),PUx(a ? b / a : 0))
 147+		/* AND */ OPC(0x1c,POx(a) POx(b),PUx(b & a))
 148+		/* ORA */ OPC(0x1d,POx(a) POx(b),PUx(b | a))
 149+		/* EOR */ OPC(0x1e,POx(a) POx(b),PUx(b ^ a))
 150+		/* SFT */ OPC(0x1f,PO1(a) POx(b),PUx(b >> (a & 0xf) << (a >> 4)))
 151+		}
 152+	}
 153+	return 0;
 154+}
 155+
 156+/* clang-format on */
 157+
 158+/*
 159+@|System ------------------------------------------------------------ */
 160+
 161+static char *system_boot_path;
 162+
 163+static void
 164+system_print(char *name, Stack *s)
 165+{
 166+	Uint8 i;
 167+	fprintf(stderr, "%s ", name);
 168+	for(i = s->ptr - 8; i != (Uint8)(s->ptr); i++)
 169+		fprintf(stderr, "%02x%c", s->dat[i], i == 0xff ? '|' : ' ');
 170+	fprintf(stderr, "<%02x\n", s->ptr);
 171+}
 172+
 173+static int
 174+system_load(Uint8 *ram, char *rom_path)
 175+{
 176+	FILE *f = fopen(rom_path, "rb");
 177+	if(f) {
 178+		int i = 0, l = fread(ram, 0x10000 - PAGE_PROGRAM, 1, f);
 179+		while(l && ++i < RAM_PAGES)
 180+			l = fread(ram + 0x10000 * i - PAGE_PROGRAM, 0x10000, 1, f);
 181+		fclose(f);
 182+	}
 183+	return !!f;
 184+}
 185+
 186+static int
 187+system_boot(Uint8 *ram, char *rom_path, int has_args)
 188+{
 189+	uxn.ram = ram;
 190+	system_boot_path = rom_path;
 191+	uxn.dev[0x17] = has_args;
 192+	if(ram && system_load(uxn.ram + PAGE_PROGRAM, rom_path))
 193+		return uxn_eval(PAGE_PROGRAM);
 194+	return 0;
 195+}
 196+
 197+static int
 198+system_reboot(int soft)
 199+{
 200+	int i;
 201+	for(i = 0x0; i < 0x100; i++) uxn.dev[i] = 0;
 202+	for(i = soft ? 0x100 : 0; i < 0x10000; i++) uxn.ram[i] = 0;
 203+	uxn.wst.ptr = uxn.rst.ptr = 0;
 204+	return system_boot(uxn.ram, system_boot_path, 0);
 205+}
 206+
 207+static Uint8
 208+system_dei(Uint8 addr)
 209+{
 210+	switch(addr) {
 211+	case 0x4: return uxn.wst.ptr;
 212+	case 0x5: return uxn.rst.ptr;
 213+	default: return uxn.dev[addr];
 214+	}
 215+}
 216+
 217+static void
 218+system_deo(Uint8 port)
 219+{
 220+	switch(port) {
 221+	case 0x3: {
 222+		Uint16 value;
 223+		Uint16 addr = PEEK2(uxn.dev + 2);
 224+		Uint8 *aptr = uxn.ram + addr;
 225+		Uint16 length = PEEK2(aptr + 1);
 226+		if(uxn.ram[addr] == 0x0) {
 227+			Uint32 a = PEEK2(aptr + 3) * 0x10000 + PEEK2(aptr + 5);
 228+			Uint32 b = a + length;
 229+			value = uxn.ram[addr + 7];
 230+			for(; a < b; uxn.ram[a++] = value);
 231+		} else if(uxn.ram[addr] == 0x1) {
 232+			Uint32 a = PEEK2(aptr + 3) * 0x10000 + PEEK2(aptr + 5);
 233+			Uint32 b = a + length;
 234+			Uint32 c = PEEK2(aptr + 7) * 0x10000 + PEEK2(aptr + 9);
 235+			for(; a < b; uxn.ram[c++] = uxn.ram[a++]);
 236+		} else if(uxn.ram[addr] == 0x2) {
 237+			Uint32 a = PEEK2(aptr + 3) * 0x10000 + PEEK2(aptr + 5);
 238+			Uint32 b = a + length;
 239+			Uint32 c = PEEK2(aptr + 7) * 0x10000 + PEEK2(aptr + 9);
 240+			Uint32 d = c + length;
 241+			for(; b >= a; uxn.ram[--d] = uxn.ram[--b]);
 242+		} else
 243+			fprintf(stderr, "Unknown command: %s\n", &uxn.ram[addr]);
 244+		break;
 245+	}
 246+	case 0x4:
 247+		uxn.wst.ptr = uxn.dev[4];
 248+		break;
 249+	case 0x5:
 250+		uxn.rst.ptr = uxn.dev[5];
 251+		break;
 252+	case 0xe:
 253+		system_print("WST", &uxn.wst);
 254+		system_print("RST", &uxn.rst);
 255+		break;
 256+	}
 257+}
 258+
 259+/*
 260+@|Console ----------------------------------------------------------- */
 261+
 262+#define CONSOLE_STD 0x1
 263+#define CONSOLE_ARG 0x2
 264+#define CONSOLE_EOA 0x3
 265+#define CONSOLE_END 0x4
 266+
 267+static int console_vector;
 268+
 269+#undef _POSIX_C_SOURCE
 270+#define _POSIX_C_SOURCE 200112L
 271+
 272+#include <signal.h>
 273+#include <sys/select.h>
 274+#include <sys/wait.h>
 275+
 276+#ifdef __linux
 277+#include <pty.h>
 278+#include <sys/prctl.h>
 279+#endif
 280+
 281+#ifdef __NetBSD__
 282+#include <sys/ioctl.h>
 283+#include <util.h>
 284+#endif
 285+
 286+/* subprocess support */
 287+static char *fork_args[4] = {"/bin/sh", "-c", "", NULL};
 288+static int child_mode;
 289+static int to_child_fd[2];
 290+static int from_child_fd[2];
 291+static int saved_in;
 292+static int saved_out;
 293+static pid_t child_pid;
 294+
 295+/* child_mode:
 296+* 0x01: writes to child's stdin
 297+* 0x02: reads from child's stdout
 298+* 0x04: reads from child's stderr
 299+* 0x08: kill previous process (if any) but do not start
 300+* (other bits ignored for now )
 301+*/
 302+
 303+#define CMD_LIVE 0x15 /* 0x00 not started, 0x01 running, 0xff dead */
 304+#define CMD_EXIT 0x16 /* if dead, exit code of process */
 305+#define CMD_ADDR 0x1c /* address to read command args from */
 306+#define CMD_MODE 0x1e /* mode to execute, 0x00 to 0x07 */
 307+#define CMD_EXEC 0x1f /* write to execute programs, etc */
 308+
 309+/* call after we're sure the process has exited */
 310+
 311+static void
 312+clean_after_child(void)
 313+{
 314+	child_pid = 0;
 315+	if(child_mode & 0x01) {
 316+		close(to_child_fd[1]);
 317+		dup2(saved_out, 1);
 318+	}
 319+	if(child_mode & (0x04 | 0x02)) {
 320+		close(from_child_fd[0]);
 321+		dup2(saved_in, 0);
 322+	}
 323+	child_mode = 0;
 324+	saved_in = -1;
 325+	saved_out = -1;
 326+}
 327+
 328+static void
 329+start_fork_pipe(void)
 330+{
 331+	pid_t pid;
 332+	pid_t parent_pid = getpid();
 333+	int addr = PEEK2(&uxn.dev[CMD_ADDR]);
 334+	fflush(stdout);
 335+	if(child_mode & 0x08) {
 336+		uxn.dev[CMD_EXIT] = uxn.dev[CMD_LIVE] = 0x00;
 337+		return;
 338+	}
 339+	if(child_mode & 0x01) {
 340+		/* parent writes to child's stdin */
 341+		if(pipe(to_child_fd) == -1) {
 342+			uxn.dev[CMD_EXIT] = uxn.dev[CMD_LIVE] = 0xff;
 343+			fprintf(stderr, "Pipe error to child.\n");
 344+			return;
 345+		}
 346+	}
 347+	if(child_mode & (0x04 | 0x02)) {
 348+		/* parent reads from child's stdout and/or stderr */
 349+		if(pipe(from_child_fd) == -1) {
 350+			uxn.dev[CMD_EXIT] = uxn.dev[CMD_LIVE] = 0xff;
 351+			fprintf(stderr, "Pipe error from child.\n");
 352+			return;
 353+		}
 354+	}
 355+
 356+	fork_args[2] = (char *)&uxn.ram[addr];
 357+	pid = fork();
 358+	if(pid < 0) { /* failure */
 359+		uxn.dev[CMD_EXIT] = uxn.dev[CMD_LIVE] = 0xff;
 360+		fprintf(stderr, "Fork failure.\n");
 361+	} else if(pid == 0) { /* child */
 362+
 363+#ifdef __linux__
 364+		int r = prctl(PR_SET_PDEATHSIG, SIGTERM);
 365+		if(r == -1) {
 366+			perror(0);
 367+			exit(6);
 368+		}
 369+		if(getppid() != parent_pid) exit(13);
 370+#endif
 371+
 372+		if(child_mode & 0x01) {
 373+			dup2(to_child_fd[0], 0);
 374+			close(to_child_fd[1]);
 375+		}
 376+		if(child_mode & (0x04 | 0x02)) {
 377+			if(child_mode & 0x02) dup2(from_child_fd[1], 1);
 378+			if(child_mode & 0x04) dup2(from_child_fd[1], 2);
 379+			close(from_child_fd[0]);
 380+		}
 381+		fflush(stdout);
 382+		execvp(fork_args[0], fork_args);
 383+		exit(1);
 384+	} else { /*parent*/
 385+		child_pid = pid;
 386+		uxn.dev[CMD_LIVE] = 0x01;
 387+		uxn.dev[CMD_EXIT] = 0x00;
 388+		if(child_mode & 0x01) {
 389+			saved_out = dup(1);
 390+			dup2(to_child_fd[1], 1);
 391+			close(to_child_fd[0]);
 392+		}
 393+		if(child_mode & (0x04 | 0x02)) {
 394+			saved_in = dup(0);
 395+			dup2(from_child_fd[0], 0);
 396+			close(from_child_fd[1]);
 397+		}
 398+	}
 399+}
 400+
 401+static void
 402+check_child(void)
 403+{
 404+	int wstatus;
 405+	if(child_pid) {
 406+		if(waitpid(child_pid, &wstatus, WNOHANG)) {
 407+			uxn.dev[CMD_LIVE] = 0xff;
 408+			uxn.dev[CMD_EXIT] = WEXITSTATUS(wstatus);
 409+			clean_after_child();
 410+		} else {
 411+			uxn.dev[CMD_LIVE] = 0x01;
 412+			uxn.dev[CMD_EXIT] = 0x00;
 413+		}
 414+	}
 415+}
 416+
 417+static void
 418+kill_child(void)
 419+{
 420+	int wstatus;
 421+	if(child_pid) {
 422+		kill(child_pid, 9);
 423+		if(waitpid(child_pid, &wstatus, WNOHANG)) {
 424+			uxn.dev[CMD_LIVE] = 0xff;
 425+			uxn.dev[CMD_EXIT] = WEXITSTATUS(wstatus);
 426+			clean_after_child();
 427+		}
 428+	}
 429+}
 430+
 431+static void
 432+start_fork(void)
 433+{
 434+	fflush(stderr);
 435+	kill_child();
 436+	child_mode = uxn.dev[CMD_MODE];
 437+	start_fork_pipe();
 438+}
 439+
 440+void
 441+close_console(void)
 442+{
 443+	kill_child();
 444+}
 445+
 446+static int
 447+console_input(int c, int type)
 448+{
 449+	if(c == EOF) c = 0, type = 4;
 450+	uxn.dev[0x12] = c, uxn.dev[0x17] = type;
 451+	uxn_eval(console_vector);
 452+	return type != 4;
 453+}
 454+
 455+static void
 456+console_arguments(int i, int argc, char **argv)
 457+{
 458+	for(; i < argc; i++) {
 459+		char *p = argv[i];
 460+		while(*p)
 461+			console_input(*p++, CONSOLE_ARG);
 462+		console_input('\n', i == argc - 1 ? CONSOLE_END : CONSOLE_EOA);
 463+	}
 464+}
 465+
 466+static Uint8
 467+console_dei(Uint8 addr)
 468+{
 469+	switch(addr) {
 470+	case CMD_LIVE:
 471+	case CMD_EXIT: check_child(); break;
 472+	}
 473+	return uxn.dev[addr];
 474+}
 475+
 476+static void
 477+console_deo(Uint8 addr)
 478+{
 479+	FILE *fd;
 480+	switch(addr) {
 481+	case 0x11: console_vector = PEEK2(&uxn.dev[0x10]); return;
 482+	case 0x18: fd = stdout, fputc(uxn.dev[0x18], fd), fflush(fd); break;
 483+	case 0x19: fd = stderr, fputc(uxn.dev[0x19], fd), fflush(fd); break;
 484+	case CMD_EXEC: start_fork(); break;
 485+	}
 486+}
 487+
 488+/*
 489+@|Screen ------------------------------------------------------------ */
 490+
 491+typedef struct UxnScreen {
 492+	int width, height, vector, zoom, x1, y1, x2, y2;
 493+	Uint32 palette[16], *pixels;
 494+	Uint8 *fg, *bg;
 495+} UxnScreen;
 496+
 497+extern UxnScreen uxn_screen;
 498+extern void emu_resize(void);
 499+void screen_change(int x1, int y1, int x2, int y2);
 500+int screen_changed(void);
 501+void screen_palette(void);
 502+void screen_apply(int width, int height);
 503+void screen_redraw(void);
 504+
 505+Uint8 screen_dei(Uint8 addr);
 506+void screen_deo(Uint8 addr);
 507+
 508+/* clang-format off */
 509+
 510+#define clamp(v,a,b) { if(v < a) v = a; else if(v >= b) v = b; }
 511+#define twos(v) (v & 0x8000 ? (int)v - 0x10000 : (int)v)
 512+
 513+/* clang-format on */
 514+
 515+#include <stdlib.h>
 516+#include <string.h>
 517+
 518+UxnScreen uxn_screen;
 519+
 520+#define MAR(x) (x + 0x8)
 521+#define MAR2(x) (x + 0x10)
 522+
 523+/* c = !ch ? (color % 5 ? color >> 2 : 0) : color % 4 + ch == 1 ? 0 : (ch - 2 + (color & 3)) % 3 + 1; */
 524+
 525+static Uint8 blending[4][16] = {
 526+	{0, 0, 0, 0, 1, 0, 1, 1, 2, 2, 0, 2, 3, 3, 3, 0},
 527+	{0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3},
 528+	{1, 2, 3, 1, 1, 2, 3, 1, 1, 2, 3, 1, 1, 2, 3, 1},
 529+	{2, 3, 1, 2, 2, 3, 1, 2, 2, 3, 1, 2, 2, 3, 1, 2}};
 530+
 531+int
 532+screen_changed(void)
 533+{
 534+	clamp(uxn_screen.x1, 0, uxn_screen.width);
 535+	clamp(uxn_screen.y1, 0, uxn_screen.height);
 536+	clamp(uxn_screen.x2, 0, uxn_screen.width);
 537+	clamp(uxn_screen.y2, 0, uxn_screen.height);
 538+	return uxn_screen.x2 > uxn_screen.x1 &&
 539+		uxn_screen.y2 > uxn_screen.y1;
 540+}
 541+
 542+void
 543+screen_change(int x1, int y1, int x2, int y2)
 544+{
 545+	if(x1 < uxn_screen.x1) uxn_screen.x1 = x1;
 546+	if(y1 < uxn_screen.y1) uxn_screen.y1 = y1;
 547+	if(x2 > uxn_screen.x2) uxn_screen.x2 = x2;
 548+	if(y2 > uxn_screen.y2) uxn_screen.y2 = y2;
 549+}
 550+
 551+void
 552+screen_palette(void)
 553+{
 554+	int i, shift, colors[4];
 555+	for(i = 0, shift = 4; i < 4; ++i, shift ^= 4) {
 556+		Uint8
 557+			r = (uxn.dev[0x8 + i / 2] >> shift) & 0xf,
 558+			g = (uxn.dev[0xa + i / 2] >> shift) & 0xf,
 559+			b = (uxn.dev[0xc + i / 2] >> shift) & 0xf;
 560+		colors[i] = 0x0f000000 | r << 16 | g << 8 | b;
 561+		colors[i] |= colors[i] << 4;
 562+	}
 563+	for(i = 0; i < 16; i++)
 564+		uxn_screen.palette[i] = colors[(i >> 2) ? (i >> 2) : (i & 3)];
 565+	screen_change(0, 0, uxn_screen.width, uxn_screen.height);
 566+}
 567+
 568+void
 569+screen_apply(int width, int height)
 570+{
 571+	int length;
 572+	clamp(width, 8, 0x800);
 573+	clamp(height, 8, 0x800);
 574+	length = MAR2(width) * MAR2(height);
 575+	uxn_screen.bg = realloc(uxn_screen.bg, length), uxn_screen.fg = realloc(uxn_screen.fg, length);
 576+	memset(uxn_screen.bg, 0, length);
 577+	memset(uxn_screen.fg, 0, length);
 578+	uxn_screen.width = width, uxn_screen.height = height;
 579+	screen_change(0, 0, width, height);
 580+	emu_resize();
 581+}
 582+
 583+void
 584+screen_redraw(void)
 585+{
 586+	int i, x, y, k, l;
 587+	for(y = uxn_screen.y1; y < uxn_screen.y2; y++) {
 588+		int ys = y * uxn_screen.zoom;
 589+		for(x = uxn_screen.x1, i = MAR(x) + MAR(y) * MAR2(uxn_screen.width); x < uxn_screen.x2; x++, i++) {
 590+			int c = uxn_screen.palette[uxn_screen.fg[i] << 2 | uxn_screen.bg[i]];
 591+			for(k = 0; k < uxn_screen.zoom; k++) {
 592+				int oo = ((ys + k) * uxn_screen.width + x) * uxn_screen.zoom;
 593+				for(l = 0; l < uxn_screen.zoom; l++)
 594+					uxn_screen.pixels[oo + l] = c;
 595+			}
 596+		}
 597+	}
 598+	uxn_screen.x1 = uxn_screen.y1 = 9999;
 599+	uxn_screen.x2 = uxn_screen.y2 = 0;
 600+}
 601+
 602+/* screen registers */
 603+
 604+static int rX, rY, rA, rMX, rMY, rMA, rML, rDX, rDY;
 605+
 606+Uint8
 607+screen_dei(Uint8 addr)
 608+{
 609+	switch(addr) {
 610+	case 0x22: return uxn_screen.width >> 8;
 611+	case 0x23: return uxn_screen.width;
 612+	case 0x24: return uxn_screen.height >> 8;
 613+	case 0x25: return uxn_screen.height;
 614+	case 0x28: return rX >> 8;
 615+	case 0x29: return rX;
 616+	case 0x2a: return rY >> 8;
 617+	case 0x2b: return rY;
 618+	case 0x2c: return rA >> 8;
 619+	case 0x2d: return rA;
 620+	default: return uxn.dev[addr];
 621+	}
 622+}
 623+
 624+void
 625+screen_deo(Uint8 addr)
 626+{
 627+	switch(addr) {
 628+	case 0x21: uxn_screen.vector = PEEK2(&uxn.dev[0x20]); return;
 629+	case 0x23: screen_apply(PEEK2(&uxn.dev[0x22]), uxn_screen.height); return;
 630+	case 0x25: screen_apply(uxn_screen.width, PEEK2(&uxn.dev[0x24])); return;
 631+	case 0x26: rMX = uxn.dev[0x26] & 0x1, rMY = uxn.dev[0x26] & 0x2, rMA = uxn.dev[0x26] & 0x4, rML = uxn.dev[0x26] >> 4, rDX = rMX << 3, rDY = rMY << 2; return;
 632+	case 0x28:
 633+	case 0x29: rX = (uxn.dev[0x28] << 8) | uxn.dev[0x29], rX = twos(rX); return;
 634+	case 0x2a:
 635+	case 0x2b: rY = (uxn.dev[0x2a] << 8) | uxn.dev[0x2b], rY = twos(rY); return;
 636+	case 0x2c:
 637+	case 0x2d: rA = (uxn.dev[0x2c] << 8) | uxn.dev[0x2d]; return;
 638+	case 0x2e: {
 639+		int ctrl = uxn.dev[0x2e];
 640+		int color = ctrl & 0x3;
 641+		int len = MAR2(uxn_screen.width);
 642+		Uint8 *layer = ctrl & 0x40 ? uxn_screen.fg : uxn_screen.bg;
 643+		/* fill mode */
 644+		if(ctrl & 0x80) {
 645+			int x1, y1, x2, y2, ax, bx, ay, by, hor, ver;
 646+			if(ctrl & 0x10)
 647+				x1 = 0, x2 = rX;
 648+			else
 649+				x1 = rX, x2 = uxn_screen.width;
 650+			if(ctrl & 0x20)
 651+				y1 = 0, y2 = rY;
 652+			else
 653+				y1 = rY, y2 = uxn_screen.height;
 654+			screen_change(x1, y1, x2, y2);
 655+			x1 = MAR(x1), y1 = MAR(y1);
 656+			hor = MAR(x2) - x1, ver = MAR(y2) - y1;
 657+			for(ay = y1 * len, by = ay + ver * len; ay < by; ay += len)
 658+				for(ax = ay + x1, bx = ax + hor; ax < bx; ax++)
 659+					layer[ax] = color;
 660+		}
 661+		/* pixel mode */
 662+		else {
 663+			if(rX >= 0 && rY >= 0 && rX < len && rY < uxn_screen.height)
 664+				layer[MAR(rX) + MAR(rY) * len] = color;
 665+			screen_change(rX, rY, rX + 1, rY + 1);
 666+			if(rMX) rX++;
 667+			if(rMY) rY++;
 668+		}
 669+		return;
 670+	}
 671+	case 0x2f: {
 672+		int ctrl = uxn.dev[0x2f];
 673+		int blend = ctrl & 0xf, opaque = blend % 5;
 674+		int fx = ctrl & 0x10 ? -1 : 1, fy = ctrl & 0x20 ? -1 : 1;
 675+		int qfx = fx > 0 ? 7 : 0, qfy = fy < 0 ? 7 : 0;
 676+		int dxy = fy * rDX, dyx = fx * rDY;
 677+		int wmar = MAR(uxn_screen.width), wmar2 = MAR2(uxn_screen.width);
 678+		int hmar2 = MAR2(uxn_screen.height);
 679+		int i, x1, x2, y1, y2, ax, ay, qx, qy, x = rX, y = rY;
 680+		Uint8 *layer = ctrl & 0x40 ? uxn_screen.fg : uxn_screen.bg;
 681+		if(ctrl & 0x80) {
 682+			int addr_incr = rMA << 2;
 683+			for(i = 0; i <= rML; i++, x += dyx, y += dxy, rA += addr_incr) {
 684+				Uint16 xmar = MAR(x), ymar = MAR(y);
 685+				Uint16 xmar2 = MAR2(x), ymar2 = MAR2(y);
 686+				if(xmar < wmar && ymar2 < hmar2) {
 687+					Uint8 *sprite = &uxn.ram[rA];
 688+					int by = ymar2 * wmar2;
 689+					for(ay = ymar * wmar2, qy = qfy; ay < by; ay += wmar2, qy += fy) {
 690+						int ch1 = sprite[qy], ch2 = sprite[qy + 8] << 1, bx = xmar2 + ay;
 691+						for(ax = xmar + ay, qx = qfx; ax < bx; ax++, qx -= fx) {
 692+							int color = ((ch1 >> qx) & 1) | ((ch2 >> qx) & 2);
 693+							if(opaque || color) layer[ax] = blending[color][blend];
 694+						}
 695+					}
 696+				}
 697+			}
 698+		} else {
 699+			int addr_incr = rMA << 1;
 700+			for(i = 0; i <= rML; i++, x += dyx, y += dxy, rA += addr_incr) {
 701+				Uint16 xmar = MAR(x), ymar = MAR(y);
 702+				Uint16 xmar2 = MAR2(x), ymar2 = MAR2(y);
 703+				if(xmar < wmar && ymar2 < hmar2) {
 704+					Uint8 *sprite = &uxn.ram[rA];
 705+					int by = ymar2 * wmar2;
 706+					for(ay = ymar * wmar2, qy = qfy; ay < by; ay += wmar2, qy += fy) {
 707+						int ch1 = sprite[qy], bx = xmar2 + ay;
 708+						for(ax = xmar + ay, qx = qfx; ax < bx; ax++, qx -= fx) {
 709+							int color = (ch1 >> qx) & 1;
 710+							if(opaque || color) layer[ax] = blending[color][blend];
 711+						}
 712+					}
 713+				}
 714+			}
 715+		}
 716+		if(fx < 0)
 717+			x1 = x, x2 = rX;
 718+		else
 719+			x1 = rX, x2 = x;
 720+		if(fy < 0)
 721+			y1 = y, y2 = rY;
 722+		else
 723+			y1 = rY, y2 = y;
 724+		screen_change(x1 - 8, y1 - 8, x2 + 8, y2 + 8);
 725+		if(rMX) rX += rDX * fx;
 726+		if(rMY) rY += rDY * fy;
 727+		return;
 728+	}
 729+	}
 730+}
 731+
 732+/*
 733+@|Controller -------------------------------------------------------- */
 734+
 735+static int controller_vector;
 736+
 737+void
 738+controller_down(Uint8 mask)
 739+{
 740+	if(mask) {
 741+		uxn.dev[0x82] |= mask;
 742+		uxn_eval(controller_vector);
 743+	}
 744+}
 745+
 746+void
 747+controller_up(Uint8 mask)
 748+{
 749+	if(mask) {
 750+		uxn.dev[0x82] &= (~mask);
 751+		uxn_eval(controller_vector);
 752+	}
 753+}
 754+
 755+void
 756+controller_key(Uint8 key)
 757+{
 758+	if(key) {
 759+		uxn.dev[0x83] = key;
 760+		uxn_eval(controller_vector);
 761+		uxn.dev[0x83] = 0;
 762+	}
 763+}
 764+
 765+void
 766+controller_deo(Uint8 addr)
 767+{
 768+	switch(addr) {
 769+	case 0x81: controller_vector = PEEK2(&uxn.dev[0x80]); break;
 770+	}
 771+}
 772+
 773+/*
 774+@|Mouse ------------------------------------------------------------- */
 775+
 776+static int mouse_vector;
 777+
 778+void
 779+mouse_down(Uint8 mask)
 780+{
 781+	uxn.dev[0x96] |= mask;
 782+	uxn_eval(mouse_vector);
 783+}
 784+
 785+void
 786+mouse_up(Uint8 mask)
 787+{
 788+	uxn.dev[0x96] &= (~mask);
 789+	uxn_eval(mouse_vector);
 790+}
 791+
 792+void
 793+mouse_pos(Uint16 x, Uint16 y)
 794+{
 795+	uxn.dev[0x92] = x >> 8, uxn.dev[0x93] = x;
 796+	uxn.dev[0x94] = y >> 8, uxn.dev[0x95] = y;
 797+	uxn_eval(mouse_vector);
 798+}
 799+
 800+void
 801+mouse_scroll(Uint16 x, Uint16 y)
 802+{
 803+	uxn.dev[0x9a] = x >> 8, uxn.dev[0x9b] = x;
 804+	uxn.dev[0x9c] = -y >> 8, uxn.dev[0x9d] = -y;
 805+	uxn_eval(mouse_vector);
 806+	uxn.dev[0x9a] = 0, uxn.dev[0x9b] = 0;
 807+	uxn.dev[0x9c] = 0, uxn.dev[0x9d] = 0;
 808+}
 809+
 810+void
 811+mouse_deo(Uint8 addr)
 812+{
 813+	switch(addr) {
 814+	case 0x91: mouse_vector = PEEK2(&uxn.dev[0x90]); break;
 815+	}
 816+}
 817+
 818+/*
 819+@|File -------------------------------------------------------------- */
 820+
 821+#include <dirent.h>
 822+#include <sys/stat.h>
 823+#include <string.h>
 824+
 825+typedef struct {
 826+	FILE *f;
 827+	DIR *dir;
 828+	char *filepath;
 829+	enum { IDLE,
 830+		FILE_READ,
 831+		FILE_WRITE,
 832+		DIR_READ,
 833+		DIR_WRITE
 834+	} state;
 835+} UxnFile;
 836+
 837+static UxnFile ufs[2];
 838+static Uint8 dirbuf[0x10000], *_dirbuf = dirbuf;
 839+
 840+static void
 841+make_pathfile(char *pathbuf, const char *filepath, const char *basename)
 842+{
 843+	char c = '/';
 844+	while(*filepath)
 845+		c = *filepath++, *pathbuf = c, pathbuf++;
 846+	if(c != '/')
 847+		*pathbuf = '/', pathbuf++;
 848+	while(*basename)
 849+		*pathbuf = *basename++, pathbuf++;
 850+	*pathbuf = 0;
 851+}
 852+
 853+static int
 854+put_fill(Uint8 *dest, int len, char c)
 855+{
 856+	int i;
 857+	for(i = 0; i < len; i++)
 858+		*dest = c, dest++;
 859+	return len;
 860+}
 861+
 862+static int
 863+put_size(Uint8 *dest, int len, int size)
 864+{
 865+	int i;
 866+	for(i = 0, dest += len; i < len; i++, size >>= 4)
 867+		*(--dest) = "0123456789abcdef"[(Uint8)(size & 0xf)];
 868+	return len;
 869+}
 870+
 871+static int
 872+put_text(Uint8 *dest, const char *text)
 873+{
 874+	Uint8 *anchor = dest;
 875+	while(*text)
 876+		*dest = *text++, dest++;
 877+	*dest = 0;
 878+	return dest - anchor;
 879+}
 880+
 881+static int
 882+put_stat(Uint8 *dest, int len, int size, int err, int dir, int capsize)
 883+{
 884+	if(err) return put_fill(dest, len, '!');
 885+	if(dir) return put_fill(dest, len, '-');
 886+	if(capsize && size >= 0x10000) return put_fill(dest, len, '?');
 887+	return put_size(dest, len, size);
 888+}
 889+
 890+static int
 891+put_statfile(Uint8 *dest, const char *filepath, const char *basename)
 892+{
 893+	int err, dir;
 894+	struct stat st;
 895+	Uint8 *anchor = dest;
 896+	char pathbuf[0x2000];
 897+	make_pathfile(pathbuf, filepath, basename);
 898+	err = stat(pathbuf, &st);
 899+	dir = S_ISDIR(st.st_mode);
 900+	dest += put_stat(dest, 4, st.st_size, err, dir, 1);
 901+	dest += put_text(dest, " ");
 902+	dest += put_text(dest, basename);
 903+	dest += put_text(dest, dir ? "/\n" : "\n");
 904+	return dest - anchor;
 905+}
 906+
 907+static int
 908+put_fdir(Uint8 *dest, int len, const char *filepath, DIR *dir)
 909+{
 910+	int i;
 911+	struct dirent *de = readdir(dir);
 912+	for(_dirbuf = dirbuf; de != NULL; de = readdir(dir)) {
 913+		char *name = de->d_name;
 914+		if(name[0] == '.' && (name[1] == '.' || name[1] == '\0'))
 915+			continue;
 916+		else
 917+			_dirbuf += put_statfile(_dirbuf, filepath, name);
 918+	}
 919+	for(i = 0; i < len && dirbuf[i]; i++)
 920+		dest[i] = dirbuf[i];
 921+	dest[i] = 0;
 922+	return i;
 923+}
 924+
 925+static int
 926+is_dir_path(char *p)
 927+{
 928+	char c;
 929+	int saw_slash = 0;
 930+	while((c = *p++)) saw_slash = c == '/';
 931+	return saw_slash;
 932+}
 933+
 934+static int
 935+is_dir_real(char *p)
 936+{
 937+	struct stat st;
 938+	return stat(p, &st) == 0 && S_ISDIR(st.st_mode);
 939+}
 940+
 941+static int
 942+file_write_dir(char *p)
 943+{
 944+	int ok = 1;
 945+	char c, *s = p;
 946+	for(; ok && (c = *p); p++) {
 947+		if(c == '/') {
 948+			*p = '\0';
 949+			ok = is_dir_real(s) || (mkdir(s, 0755) == 0);
 950+			*p = c;
 951+		}
 952+	}
 953+	return ok;
 954+}
 955+
 956+static void
 957+file_reset(int id)
 958+{
 959+	if(ufs[id].f != NULL) fclose(ufs[id].f), ufs[id].f = NULL;
 960+	if(ufs[id].dir != NULL) closedir(ufs[id].dir), ufs[id].dir = NULL;
 961+	ufs[id].state = IDLE;
 962+}
 963+
 964+static int
 965+file_init(int id, Uint16 addr)
 966+{
 967+	file_reset(id);
 968+	ufs[id].filepath = (char *)&uxn.ram[addr];
 969+	return 0;
 970+}
 971+
 972+static int
 973+file_not_ready(int id)
 974+{
 975+	if(ufs[id].filepath == 0) {
 976+		fprintf(stderr, "File %d is uninitialized\n", id);
 977+		return 1;
 978+	} else
 979+		return 0;
 980+}
 981+
 982+static int
 983+file_read(int id, Uint16 addr, int len)
 984+{
 985+	void *dest = &uxn.ram[addr];
 986+	if(file_not_ready(id))
 987+		return 0;
 988+	if(ufs[id].state != FILE_READ && ufs[id].state != DIR_READ) {
 989+		file_reset(id);
 990+		if((ufs[id].dir = opendir(ufs[id].filepath)) != NULL)
 991+			ufs[id].state = DIR_READ;
 992+		else if((ufs[id].f = fopen(ufs[id].filepath, "rb")) != NULL)
 993+			ufs[id].state = FILE_READ;
 994+	}
 995+	if(ufs[id].state == FILE_READ)
 996+		return fread(dest, 1, len, ufs[id].f);
 997+	if(ufs[id].state == DIR_READ)
 998+		return put_fdir(dest, len, ufs[id].filepath, ufs[id].dir);
 999+	return 0;
1000+}
1001+
1002+static int
1003+file_write(int id, Uint16 addr, int len, Uint8 flags)
1004+{
1005+	int ret = 0;
1006+	if(file_not_ready(id))
1007+		return 0;
1008+	file_write_dir(ufs[id].filepath);
1009+	if(ufs[id].state != FILE_WRITE && ufs[id].state != DIR_WRITE) {
1010+		file_reset(id);
1011+		if(is_dir_path(ufs[id].filepath))
1012+			ufs[id].state = DIR_WRITE;
1013+		else if((ufs[id].f = fopen(ufs[id].filepath, (flags & 0x01) ? "ab" : "wb")) != NULL)
1014+			ufs[id].state = FILE_WRITE;
1015+	}
1016+	if(ufs[id].state == FILE_WRITE)
1017+		if((ret = fwrite(&uxn.ram[addr], 1, len, ufs[id].f)) > 0 && fflush(ufs[id].f) != 0)
1018+			ret = 0;
1019+	if(ufs[id].state == DIR_WRITE)
1020+		ret = is_dir_real(ufs[id].filepath);
1021+	return ret;
1022+}
1023+
1024+static int
1025+file_stat(int id, Uint16 addr, int len)
1026+{
1027+	int err, dir;
1028+	struct stat st;
1029+	if(file_not_ready(id))
1030+		return 0;
1031+	err = stat(ufs[id].filepath, &st);
1032+	dir = S_ISDIR(st.st_mode);
1033+	return put_stat(&uxn.ram[addr], len, st.st_size, err, dir, 0);
1034+}
1035+
1036+static int
1037+file_delete(int id)
1038+{
1039+	if(file_not_ready(id))
1040+		return -1;
1041+	return unlink(ufs[id].filepath);
1042+}
1043+
1044+static void
1045+file_success(int port, int value)
1046+{
1047+	POKE2(&uxn.dev[port], value);
1048+}
1049+
1050+/* file registers */
1051+
1052+static int rL1, rL2;
1053+
1054+static void
1055+file_deo(Uint8 port)
1056+{
1057+	switch(port) {
1058+	/* File 1 */
1059+	case 0xab: rL1 = PEEK2(&uxn.dev[0xaa]); break;
1060+	case 0xa5: file_success(0xa2, file_stat(0, PEEK2(&uxn.dev[0xa4]), rL1)); break;
1061+	case 0xa6: file_success(0xa2, file_delete(0)); break;
1062+	case 0xa9: file_success(0xa2, file_init(0, PEEK2(&uxn.dev[0xa8]))); break;
1063+	case 0xad: file_success(0xa2, file_read(0, PEEK2(&uxn.dev[0xac]), rL1)); break;
1064+	case 0xaf: file_success(0xa2, file_write(0, PEEK2(&uxn.dev[0xae]), rL1, uxn.dev[0xa7])); break;
1065+	/* File 2 */
1066+	case 0xbb: rL2 = PEEK2(&uxn.dev[0xba]); break;
1067+	case 0xb5: file_success(0xb2, file_stat(1, PEEK2(&uxn.dev[0xb4]), rL2)); break;
1068+	case 0xb6: file_success(0xb2, file_delete(1)); break;
1069+	case 0xb9: file_success(0xb2, file_init(1, PEEK2(&uxn.dev[0xb8]))); break;
1070+	case 0xbd: file_success(0xb2, file_read(1, PEEK2(&uxn.dev[0xbc]), rL2)); break;
1071+	case 0xbf: file_success(0xb2, file_write(1, PEEK2(&uxn.dev[0xbe]), rL2, uxn.dev[0xb7])); break;
1072+	}
1073+}
1074+
1075+/*
1076+@|Datetime ---------------------------------------------------------- */
1077+
1078+#include <time.h>
1079+
1080+static Uint8
1081+datetime_dei(Uint8 addr)
1082+{
1083+	time_t seconds = time(NULL);
1084+	struct tm zt = {0};
1085+	struct tm *t = localtime(&seconds);
1086+	if(t == NULL) t = &zt;
1087+	switch(addr) {
1088+	case 0xc0: return (t->tm_year + 1900) >> 8;
1089+	case 0xc1: return (t->tm_year + 1900);
1090+	case 0xc2: return t->tm_mon;
1091+	case 0xc3: return t->tm_mday;
1092+	case 0xc4: return t->tm_hour;
1093+	case 0xc5: return t->tm_min;
1094+	case 0xc6: return t->tm_sec;
1095+	case 0xc7: return t->tm_wday;
1096+	case 0xc8: return t->tm_yday >> 8;
1097+	case 0xc9: return t->tm_yday;
1098+	case 0xca: return t->tm_isdst;
1099+	default: return uxn.dev[addr];
1100+	}
1101+}
1102+
1103+/*
1104+@|Core -------------------------------------------------------------- */
1105+
1106 #define CONINBUFSIZE 256
1107 
1108 Uint8
1109@@ -214,11 +1267,11 @@ display_init(void)
1110 	/* start display */
1111 	display = XOpenDisplay(NULL);
1112 	if(!display)
1113-		return system_error("init", "Display failed");
1114+		return !fprintf(stderr, "Display: failed\n");
1115 	/* start window */
1116 	visual = DefaultVisual(display, 0);
1117 	if(visual->class != TrueColor)
1118-		return system_error("init", "True-color visual failed");
1119+		return !fprintf(stderr, "Display: True-color visual failed\n");
1120 	window = XCreateSimpleWindow(display, RootWindow(display, 0), 0, 0, WIDTH, HEIGHT, 1, 0, 0);
1121 	display_hidecursor();
1122 	display_floating(display, window);
+1, -1
1@@ -16,7 +16,7 @@ copyright notice and this permission notice appear in all copies.
2 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
3 WITH REGARD TO THIS SOFTWARE.
4 
5-cc src/uxncli.c -lutil -o bin/uxncli
6+cc -DNDEBUG -O2 -g0 -s src/uxncli.c -lutil -o bin/uxncli
7 */
8 
9 /*