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 [](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))