commit 7fd4d2f
Devine Lu Linvega
·
2025-04-19 16:44:44 +0000 UTC
parent 83adebf
Made uxncli a single file
2 files changed,
+727,
-12
M
makefile
M
makefile
+7,
-4
1@@ -5,7 +5,7 @@ DEBUG_flags=-std=c89 -D_POSIX_C_SOURCE=199309L -DDEBUG -Wall -Wno-unknown-pragma
2
3 PREFIX=${HOME}/.local
4
5-.PHONY: all debug run test install uninstall format clean grab
6+.PHONY: all debug run test install uninstall format clean grab archive
7
8 all: bin/uxncli bin/uxn11
9
10@@ -32,6 +32,9 @@ grab:
11 @ cp ../uxn-utils/cli/opctest/src/opctest.tal etc/examples/opctest.tal
12 @ cp ../drifblim/etc/drifblim.rom.txt etc/utils/asm.rom.txt
13
14+archive:
15+ @ cp src/uxncli.c ../oscean/etc/uxncli.c.txt
16+
17 clean:
18 @ rm -fr bin
19
20@@ -58,13 +61,13 @@ bin/screen.rom: bin/uxncli bin/asm.rom etc/examples/screen.tal
21
22 bin/uxncli: src/uxncli.c
23 @ mkdir -p bin
24- @ cc ${RELEASE_flags} ${CFLAGS} ${CLI_src} src/uxncli.c -lutil -o bin/uxncli
25+ @ cc ${RELEASE_flags} ${CFLAGS} src/uxncli.c -lutil -o bin/uxncli
26 bin/uxn11: ${EMU_src} src/uxn11.c
27 @ mkdir -p bin
28 @ cc ${RELEASE_flags} ${CFLAGS} ${EMU_src} src/uxn11.c -lX11 -lutil -o bin/uxn11
29-bin/uxncli-debug: ${CLI_src} src/uxncli.c
30+bin/uxncli-debug: src/uxncli.c
31 @ mkdir -p bin
32- @ cc ${DEBUG_flags} ${CFLAGS} ${CLI_src} src/uxncli.c -lutil -o bin/uxncli-debug
33+ @ cc ${DEBUG_flags} ${CFLAGS} src/uxncli.c -lutil -o bin/uxncli-debug
34 bin/uxn11-debug: ${EMU_src} src/uxn11.c
35 @ mkdir -p bin
36 @ cc ${DEBUG_flags} ${CFLAGS} ${EMU_src} src/uxn11.c -lX11 -lutil -o bin/uxn11-debug
+720,
-8
1@@ -1,11 +1,10 @@
2 #include <stdio.h>
3 #include <stdlib.h>
4+#include <unistd.h>
5
6-#include "uxn.h"
7-#include "devices/system.h"
8-#include "devices/console.h"
9-#include "devices/file.h"
10-#include "devices/datetime.h"
11+#define STEP_MAX 0x80000000
12+#define PAGE_PROGRAM 0x100
13+#define RAM_PAGES 0x10
14
15 /*
16 Copyright (c) 2021-2025 Devine Lu Linvega, Andrew Alderwick
17@@ -16,16 +15,729 @@ copyright notice and this permission notice appear in all copies.
18
19 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
20 WITH REGARD TO THIS SOFTWARE.
21+
22+cc src/uxncli.c -lutil -o bin/uxncli
23 */
24
25-Uxn uxn;
26-int console_vector;
27+/*
28+@|Uxn --------------------------------------------------------------- */
29+
30+typedef unsigned char Uint8;
31+typedef signed char Sint8;
32+typedef unsigned short Uint16;
33+typedef signed short Sint16;
34+typedef unsigned int Uint32;
35+
36+typedef struct {
37+ Uint8 dat[0x100], ptr;
38+} Stack;
39+
40+typedef struct Uxn {
41+ Uint8 *ram, dev[0x100];
42+ Stack wst, rst;
43+} Uxn;
44+
45+Uint8 emu_dei(Uint8 addr);
46+void emu_deo(Uint8 addr, Uint8 value);
47+static Uxn uxn;
48+
49+/* clang-format off */
50+
51+#define PEEK2(d) (*(d) << 8 | (d)[1])
52+#define POKE2(d, v) { *(d) = (v) >> 8; (d)[1] = (v); }
53+
54+#define OPC(opc, init, body) {\
55+ case 0x00|opc: {const int _2=0,_r=0;init body;} break;\
56+ case 0x20|opc: {const int _2=1,_r=0;init body;} break;\
57+ case 0x40|opc: {const int _2=0,_r=1;init body;} break;\
58+ case 0x60|opc: {const int _2=1,_r=1;init body;} break;\
59+ case 0x80|opc: {const int _2=0,_r=0,k=uxn.wst.ptr;init uxn.wst.ptr=k;body;} break;\
60+ case 0xa0|opc: {const int _2=1,_r=0,k=uxn.wst.ptr;init uxn.wst.ptr=k;body;} break;\
61+ case 0xc0|opc: {const int _2=0,_r=1,k=uxn.rst.ptr;init uxn.rst.ptr=k;body;} break;\
62+ case 0xe0|opc: {const int _2=1,_r=1,k=uxn.rst.ptr;init uxn.rst.ptr=k;body;} break;\
63+}
64+
65+/* Microcode */
66+
67+#define JMI a = uxn.ram[pc] << 8 | uxn.ram[pc + 1], pc += a + 2;
68+#define REM if(_r) uxn.rst.ptr -= 1 + _2; else uxn.wst.ptr -= 1 + _2;
69+#define INC(s) uxn.s.dat[uxn.s.ptr++]
70+#define DEC(s) uxn.s.dat[--uxn.s.ptr]
71+#define JMP(x) { if(_2) pc = x; else pc += (Sint8)x; }
72+#define PO1(o) { o = _r ? DEC(rst) : DEC(wst);}
73+#define PO2(o) { if(_r) o = DEC(rst), o |= DEC(rst) << 8; else o = DEC(wst), o |= DEC(wst) << 8; }
74+#define POx(o) { if(_2) PO2(o) else PO1(o) }
75+#define PU1(i) { if(_r) INC(rst) = i; else INC(wst) = i; }
76+#define RP1(i) { if(_r) INC(wst) = i; else INC(rst) = i; }
77+#define PUx(i) { if(_2) { c = (i); PU1(c >> 8) PU1(c) } else PU1(i) }
78+#define GET(o) { if(_2) PO1(o[1]) PO1(o[0]) }
79+#define PUT(i) { PU1(i[0]) if(_2) PU1(i[1]) }
80+#define DEI(i,o) o[0] = emu_dei(i); if(_2) o[1] = emu_dei(i + 1); PUT(o)
81+#define DEO(i,j) emu_deo(i, j[0]); if(_2) emu_deo(i + 1, j[1]);
82+#define PEK(i,o,m) o[0] = uxn.ram[i]; if(_2) o[1] = uxn.ram[(i + 1) & m]; PUT(o)
83+#define POK(i,j,m) uxn.ram[i] = j[0]; if(_2) uxn.ram[(i + 1) & m] = j[1];
84+
85+int
86+uxn_eval(Uint16 pc)
87+{
88+ unsigned int a, b, c, x[2], y[2], z[2], step;
89+ if(!pc || uxn.dev[0x0f]) return 0;
90+ for(step = STEP_MAX; step; step--) {
91+ switch(uxn.ram[pc++]) {
92+ /* BRK */ case 0x00: return 1;
93+ /* JCI */ case 0x20: if(DEC(wst)) { JMI break; } pc += 2; break;
94+ /* JMI */ case 0x40: JMI break;
95+ /* JSI */ case 0x60: c = pc + 2; INC(rst) = c >> 8; INC(rst) = c; JMI break;
96+ /* LI2 */ case 0xa0: INC(wst) = uxn.ram[pc++]; /* fall-through */
97+ /* LIT */ case 0x80: INC(wst) = uxn.ram[pc++]; break;
98+ /* L2r */ case 0xe0: INC(rst) = uxn.ram[pc++]; /* fall-through */
99+ /* LIr */ case 0xc0: INC(rst) = uxn.ram[pc++]; break;
100+ /* INC */ OPC(0x01,POx(a),PUx(a + 1))
101+ /* POP */ OPC(0x02,REM ,{})
102+ /* NIP */ OPC(0x03,GET(x) REM ,PUT(x))
103+ /* SWP */ OPC(0x04,GET(x) GET(y),PUT(x) PUT(y))
104+ /* ROT */ OPC(0x05,GET(x) GET(y) GET(z),PUT(y) PUT(x) PUT(z))
105+ /* DUP */ OPC(0x06,GET(x),PUT(x) PUT(x))
106+ /* OVR */ OPC(0x07,GET(x) GET(y),PUT(y) PUT(x) PUT(y))
107+ /* EQU */ OPC(0x08,POx(a) POx(b),PU1(b == a))
108+ /* NEQ */ OPC(0x09,POx(a) POx(b),PU1(b != a))
109+ /* GTH */ OPC(0x0a,POx(a) POx(b),PU1(b > a))
110+ /* LTH */ OPC(0x0b,POx(a) POx(b),PU1(b < a))
111+ /* JMP */ OPC(0x0c,POx(a),JMP(a))
112+ /* JCN */ OPC(0x0d,POx(a) PO1(b),if(b) JMP(a))
113+ /* JSR */ OPC(0x0e,POx(a),RP1(pc >> 8) RP1(pc) JMP(a))
114+ /* STH */ OPC(0x0f,GET(x),RP1(x[0]) if(_2) RP1(x[1]))
115+ /* LDZ */ OPC(0x10,PO1(a),PEK(a, x, 0xff))
116+ /* STZ */ OPC(0x11,PO1(a) GET(y),POK(a, y, 0xff))
117+ /* LDR */ OPC(0x12,PO1(a),PEK(pc + (Sint8)a, x, 0xffff))
118+ /* STR */ OPC(0x13,PO1(a) GET(y),POK(pc + (Sint8)a, y, 0xffff))
119+ /* LDA */ OPC(0x14,PO2(a),PEK(a, x, 0xffff))
120+ /* STA */ OPC(0x15,PO2(a) GET(y),POK(a, y, 0xffff))
121+ /* DEI */ OPC(0x16,PO1(a),DEI(a, x))
122+ /* DEO */ OPC(0x17,PO1(a) GET(y),DEO(a, y))
123+ /* ADD */ OPC(0x18,POx(a) POx(b),PUx(b + a))
124+ /* SUB */ OPC(0x19,POx(a) POx(b),PUx(b - a))
125+ /* MUL */ OPC(0x1a,POx(a) POx(b),PUx(b * a))
126+ /* DIV */ OPC(0x1b,POx(a) POx(b),PUx(a ? b / a : 0))
127+ /* AND */ OPC(0x1c,POx(a) POx(b),PUx(b & a))
128+ /* ORA */ OPC(0x1d,POx(a) POx(b),PUx(b | a))
129+ /* EOR */ OPC(0x1e,POx(a) POx(b),PUx(b ^ a))
130+ /* SFT */ OPC(0x1f,PO1(a) POx(b),PUx(b >> (a & 0xf) << (a >> 4)))
131+ }
132+ }
133+ return 0;
134+}
135+
136+/* clang-format on */
137+
138+/*
139+@|System ------------------------------------------------------------ */
140+
141+static char *system_boot_path;
142+
143+static void
144+system_print(char *name, Stack *s)
145+{
146+ Uint8 i;
147+ fprintf(stderr, "%s ", name);
148+ for(i = s->ptr - 8; i != (Uint8)(s->ptr); i++)
149+ fprintf(stderr, "%02x%c", s->dat[i], i == 0xff ? '|' : ' ');
150+ fprintf(stderr, "<%02x\n", s->ptr);
151+}
152+
153+static int
154+system_load(Uint8 *ram, char *rom_path)
155+{
156+ FILE *f = fopen(rom_path, "rb");
157+ if(f) {
158+ int i = 0, l = fread(ram, 0x10000 - PAGE_PROGRAM, 1, f);
159+ while(l && ++i < RAM_PAGES)
160+ l = fread(ram + 0x10000 * i - PAGE_PROGRAM, 0x10000, 1, f);
161+ fclose(f);
162+ }
163+ return !!f;
164+}
165+
166+static int
167+system_boot(Uint8 *ram, char *rom_path, int has_args)
168+{
169+ uxn.ram = ram;
170+ system_boot_path = rom_path;
171+ uxn.dev[0x17] = has_args;
172+ if(ram && system_load(uxn.ram + PAGE_PROGRAM, rom_path))
173+ return uxn_eval(PAGE_PROGRAM);
174+ return 0;
175+}
176+
177+static Uint8
178+system_dei(Uint8 addr)
179+{
180+ switch(addr) {
181+ case 0x4: return uxn.wst.ptr;
182+ case 0x5: return uxn.rst.ptr;
183+ default: return uxn.dev[addr];
184+ }
185+}
186+
187+static void
188+system_deo(Uint8 port)
189+{
190+ switch(port) {
191+ case 0x3: {
192+ Uint16 value;
193+ Uint16 addr = PEEK2(uxn.dev + 2);
194+ Uint8 *aptr = uxn.ram + addr;
195+ Uint16 length = PEEK2(aptr + 1);
196+ if(uxn.ram[addr] == 0x0) {
197+ unsigned int a = PEEK2(aptr + 3) * 0x10000 + PEEK2(aptr + 5);
198+ unsigned int b = a + length;
199+ value = uxn.ram[addr + 7];
200+ for(; a < b; uxn.ram[a++] = value);
201+ } else if(uxn.ram[addr] == 0x1) {
202+ unsigned int a = PEEK2(aptr + 3) * 0x10000 + PEEK2(aptr + 5);
203+ unsigned int b = a + length;
204+ unsigned int c = PEEK2(aptr + 7) * 0x10000 + PEEK2(aptr + 9);
205+ for(; a < b; uxn.ram[c++] = uxn.ram[a++]);
206+ } else if(uxn.ram[addr] == 0x2) {
207+ unsigned int a = PEEK2(aptr + 3) * 0x10000 + PEEK2(aptr + 5);
208+ unsigned int b = a + length;
209+ unsigned int c = PEEK2(aptr + 7) * 0x10000 + PEEK2(aptr + 9);
210+ unsigned int d = c + length;
211+ for(; b >= a; uxn.ram[--d] = uxn.ram[--b]);
212+ } else
213+ fprintf(stderr, "Unknown command: %s\n", &uxn.ram[addr]);
214+ break;
215+ }
216+ case 0x4:
217+ uxn.wst.ptr = uxn.dev[4];
218+ break;
219+ case 0x5:
220+ uxn.rst.ptr = uxn.dev[5];
221+ break;
222+ case 0xe:
223+ system_print("WST", &uxn.wst);
224+ system_print("RST", &uxn.rst);
225+ break;
226+ }
227+}
228+
229+/*
230+@|Console ----------------------------------------------------------- */
231+
232+#define CONSOLE_STD 0x1
233+#define CONSOLE_ARG 0x2
234+#define CONSOLE_EOA 0x3
235+#define CONSOLE_END 0x4
236+
237+static int console_vector;
238+
239+#undef _POSIX_C_SOURCE
240+#define _POSIX_C_SOURCE 200112L
241+
242+#include <signal.h>
243+#include <sys/select.h>
244+#include <sys/wait.h>
245+
246+#ifdef __linux
247+#include <pty.h>
248+#include <sys/prctl.h>
249+#endif
250+
251+#ifdef __NetBSD__
252+#include <sys/ioctl.h>
253+#include <util.h>
254+#endif
255+
256+/* subprocess support */
257+static char *fork_args[4] = {"/bin/sh", "-c", "", NULL};
258+static int child_mode;
259+static int to_child_fd[2];
260+static int from_child_fd[2];
261+static int saved_in;
262+static int saved_out;
263+static pid_t child_pid;
264+
265+/* child_mode:
266+* 0x01: writes to child's stdin
267+* 0x02: reads from child's stdout
268+* 0x04: reads from child's stderr
269+* 0x08: kill previous process (if any) but do not start
270+* (other bits ignored for now )
271+*/
272+
273+#define CMD_LIVE 0x15 /* 0x00 not started, 0x01 running, 0xff dead */
274+#define CMD_EXIT 0x16 /* if dead, exit code of process */
275+#define CMD_ADDR 0x1c /* address to read command args from */
276+#define CMD_MODE 0x1e /* mode to execute, 0x00 to 0x07 */
277+#define CMD_EXEC 0x1f /* write to execute programs, etc */
278+
279+/* call after we're sure the process has exited */
280+
281+static void
282+clean_after_child(void)
283+{
284+ child_pid = 0;
285+ if(child_mode & 0x01) {
286+ close(to_child_fd[1]);
287+ dup2(saved_out, 1);
288+ }
289+ if(child_mode & (0x04 | 0x02)) {
290+ close(from_child_fd[0]);
291+ dup2(saved_in, 0);
292+ }
293+ child_mode = 0;
294+ saved_in = -1;
295+ saved_out = -1;
296+}
297+
298+static void
299+start_fork_pipe(void)
300+{
301+ pid_t pid;
302+ pid_t parent_pid = getpid();
303+ int addr = PEEK2(&uxn.dev[CMD_ADDR]);
304+ fflush(stdout);
305+ if(child_mode & 0x08) {
306+ uxn.dev[CMD_EXIT] = uxn.dev[CMD_LIVE] = 0x00;
307+ return;
308+ }
309+ if(child_mode & 0x01) {
310+ /* parent writes to child's stdin */
311+ if(pipe(to_child_fd) == -1) {
312+ uxn.dev[CMD_EXIT] = uxn.dev[CMD_LIVE] = 0xff;
313+ fprintf(stderr, "Pipe error to child.\n");
314+ return;
315+ }
316+ }
317+ if(child_mode & (0x04 | 0x02)) {
318+ /* parent reads from child's stdout and/or stderr */
319+ if(pipe(from_child_fd) == -1) {
320+ uxn.dev[CMD_EXIT] = uxn.dev[CMD_LIVE] = 0xff;
321+ fprintf(stderr, "Pipe error from child.\n");
322+ return;
323+ }
324+ }
325+
326+ fork_args[2] = (char *)&uxn.ram[addr];
327+ pid = fork();
328+ if(pid < 0) { /* failure */
329+ uxn.dev[CMD_EXIT] = uxn.dev[CMD_LIVE] = 0xff;
330+ fprintf(stderr, "Fork failure.\n");
331+ } else if(pid == 0) { /* child */
332+
333+#ifdef __linux__
334+ int r = prctl(PR_SET_PDEATHSIG, SIGTERM);
335+ if(r == -1) {
336+ perror(0);
337+ exit(6);
338+ }
339+ if(getppid() != parent_pid) exit(13);
340+#endif
341+
342+ if(child_mode & 0x01) {
343+ dup2(to_child_fd[0], 0);
344+ close(to_child_fd[1]);
345+ }
346+ if(child_mode & (0x04 | 0x02)) {
347+ if(child_mode & 0x02) dup2(from_child_fd[1], 1);
348+ if(child_mode & 0x04) dup2(from_child_fd[1], 2);
349+ close(from_child_fd[0]);
350+ }
351+ fflush(stdout);
352+ execvp(fork_args[0], fork_args);
353+ exit(1);
354+ } else { /*parent*/
355+ child_pid = pid;
356+ uxn.dev[CMD_LIVE] = 0x01;
357+ uxn.dev[CMD_EXIT] = 0x00;
358+ if(child_mode & 0x01) {
359+ saved_out = dup(1);
360+ dup2(to_child_fd[1], 1);
361+ close(to_child_fd[0]);
362+ }
363+ if(child_mode & (0x04 | 0x02)) {
364+ saved_in = dup(0);
365+ dup2(from_child_fd[0], 0);
366+ close(from_child_fd[1]);
367+ }
368+ }
369+}
370+
371+static void
372+check_child(void)
373+{
374+ int wstatus;
375+ if(child_pid) {
376+ if(waitpid(child_pid, &wstatus, WNOHANG)) {
377+ uxn.dev[CMD_LIVE] = 0xff;
378+ uxn.dev[CMD_EXIT] = WEXITSTATUS(wstatus);
379+ clean_after_child();
380+ } else {
381+ uxn.dev[CMD_LIVE] = 0x01;
382+ uxn.dev[CMD_EXIT] = 0x00;
383+ }
384+ }
385+}
386+
387+static void
388+kill_child(void)
389+{
390+ int wstatus;
391+ if(child_pid) {
392+ kill(child_pid, 9);
393+ if(waitpid(child_pid, &wstatus, WNOHANG)) {
394+ uxn.dev[CMD_LIVE] = 0xff;
395+ uxn.dev[CMD_EXIT] = WEXITSTATUS(wstatus);
396+ clean_after_child();
397+ }
398+ }
399+}
400+
401+static void
402+start_fork(void)
403+{
404+ fflush(stderr);
405+ kill_child();
406+ child_mode = uxn.dev[CMD_MODE];
407+ start_fork_pipe();
408+}
409+
410+static int
411+console_input(int c, int type)
412+{
413+ if(c == EOF) c = 0, type = 4;
414+ uxn.dev[0x12] = c, uxn.dev[0x17] = type;
415+ uxn_eval(console_vector);
416+ return type != 4;
417+}
418+
419+static void
420+console_arguments(int i, int argc, char **argv)
421+{
422+ for(; i < argc; i++) {
423+ char *p = argv[i];
424+ while(*p)
425+ console_input(*p++, CONSOLE_ARG);
426+ console_input('\n', i == argc - 1 ? CONSOLE_END : CONSOLE_EOA);
427+ }
428+}
429+
430+static Uint8
431+console_dei(Uint8 addr)
432+{
433+ switch(addr) {
434+ case CMD_LIVE:
435+ case CMD_EXIT: check_child(); break;
436+ }
437+ return uxn.dev[addr];
438+}
439+
440+static void
441+console_deo(Uint8 addr)
442+{
443+ FILE *fd;
444+ switch(addr) {
445+ case 0x11: console_vector = PEEK2(&uxn.dev[0x10]); return;
446+ case 0x18: fd = stdout, fputc(uxn.dev[0x18], fd), fflush(fd); break;
447+ case 0x19: fd = stderr, fputc(uxn.dev[0x19], fd), fflush(fd); break;
448+ case CMD_EXEC: start_fork(); break;
449+ }
450+}
451+
452+/*
453+@|File -------------------------------------------------------------- */
454+
455+#include <dirent.h>
456+#include <sys/stat.h>
457+#include <string.h>
458+
459+typedef struct {
460+ FILE *f;
461+ DIR *dir;
462+ char *filepath;
463+ enum { IDLE,
464+ FILE_READ,
465+ FILE_WRITE,
466+ DIR_READ,
467+ DIR_WRITE
468+ } state;
469+} UxnFile;
470+
471+static UxnFile ufs[2];
472+static Uint8 dirbuf[0x10000], *_dirbuf = dirbuf;
473+
474+static void
475+make_pathfile(char *pathbuf, const char *filepath, const char *basename)
476+{
477+ char c = '/';
478+ while(*filepath)
479+ c = *filepath++, *pathbuf = c, pathbuf++;
480+ if(c != '/')
481+ *pathbuf = '/', pathbuf++;
482+ while(*basename)
483+ *pathbuf = *basename++, pathbuf++;
484+ *pathbuf = 0;
485+}
486+
487+static int
488+put_fill(Uint8 *dest, int len, char c)
489+{
490+ int i;
491+ for(i = 0; i < len; i++)
492+ *dest = c, dest++;
493+ return len;
494+}
495+
496+static int
497+put_size(Uint8 *dest, int len, int size)
498+{
499+ int i;
500+ for(i = 0, dest += len; i < len; i++, size >>= 4)
501+ *(--dest) = "0123456789abcdef"[(Uint8)(size & 0xf)];
502+ return len;
503+}
504+
505+static int
506+put_text(Uint8 *dest, const char *text)
507+{
508+ Uint8 *anchor = dest;
509+ while(*text)
510+ *dest = *text++, dest++;
511+ *dest = 0;
512+ return dest - anchor;
513+}
514+
515+static int
516+put_stat(Uint8 *dest, int len, int size, int err, int dir, int capsize)
517+{
518+ if(err) return put_fill(dest, len, '!');
519+ if(dir) return put_fill(dest, len, '-');
520+ if(capsize && size >= 0x10000) return put_fill(dest, len, '?');
521+ return put_size(dest, len, size);
522+}
523+
524+static int
525+put_statfile(Uint8 *dest, const char *filepath, const char *basename)
526+{
527+ int err, dir;
528+ struct stat st;
529+ Uint8 *anchor = dest;
530+ char pathbuf[0x2000];
531+ make_pathfile(pathbuf, filepath, basename);
532+ err = stat(pathbuf, &st);
533+ dir = S_ISDIR(st.st_mode);
534+ dest += put_stat(dest, 4, st.st_size, err, dir, 1);
535+ dest += put_text(dest, " ");
536+ dest += put_text(dest, basename);
537+ dest += put_text(dest, dir ? "/\n" : "\n");
538+ return dest - anchor;
539+}
540+
541+static int
542+put_fdir(Uint8 *dest, int len, const char *filepath, DIR *dir)
543+{
544+ int i;
545+ struct dirent *de = readdir(dir);
546+ for(_dirbuf = dirbuf; de != NULL; de = readdir(dir)) {
547+ char *name = de->d_name;
548+ if(name[0] == '.' && (name[1] == '.' || name[1] == '\0'))
549+ continue;
550+ else
551+ _dirbuf += put_statfile(_dirbuf, filepath, name);
552+ }
553+ for(i = 0; i < len && dirbuf[i]; i++)
554+ dest[i] = dirbuf[i];
555+ dest[i] = 0;
556+ return i;
557+}
558+
559+static int
560+is_dir_path(char *p)
561+{
562+ char c;
563+ int saw_slash = 0;
564+ while((c = *p++)) saw_slash = c == '/';
565+ return saw_slash;
566+}
567+
568+static int
569+is_dir_real(char *p)
570+{
571+ struct stat st;
572+ return stat(p, &st) == 0 && S_ISDIR(st.st_mode);
573+}
574+
575+static int
576+file_write_dir(char *p)
577+{
578+ int ok = 1;
579+ char c, *s = p;
580+ for(; ok && (c = *p); p++) {
581+ if(c == '/') {
582+ *p = '\0';
583+ ok = is_dir_real(s) || (mkdir(s, 0755) == 0);
584+ *p = c;
585+ }
586+ }
587+ return ok;
588+}
589+
590+static void
591+file_reset(int id)
592+{
593+ if(ufs[id].f != NULL) fclose(ufs[id].f), ufs[id].f = NULL;
594+ if(ufs[id].dir != NULL) closedir(ufs[id].dir), ufs[id].dir = NULL;
595+ ufs[id].state = IDLE;
596+}
597+
598+static int
599+file_init(int id, Uint16 addr)
600+{
601+ file_reset(id);
602+ ufs[id].filepath = (char *)&uxn.ram[addr];
603+ return 0;
604+}
605+
606+static int
607+file_not_ready(int id)
608+{
609+ if(ufs[id].filepath == 0) {
610+ fprintf(stderr, "File %d is uninitialized\n", id);
611+ return 1;
612+ } else
613+ return 0;
614+}
615+
616+static int
617+file_read(int id, Uint16 addr, int len)
618+{
619+ void *dest = &uxn.ram[addr];
620+ if(file_not_ready(id))
621+ return 0;
622+ if(ufs[id].state != FILE_READ && ufs[id].state != DIR_READ) {
623+ file_reset(id);
624+ if((ufs[id].dir = opendir(ufs[id].filepath)) != NULL)
625+ ufs[id].state = DIR_READ;
626+ else if((ufs[id].f = fopen(ufs[id].filepath, "rb")) != NULL)
627+ ufs[id].state = FILE_READ;
628+ }
629+ if(ufs[id].state == FILE_READ)
630+ return fread(dest, 1, len, ufs[id].f);
631+ if(ufs[id].state == DIR_READ)
632+ return put_fdir(dest, len, ufs[id].filepath, ufs[id].dir);
633+ return 0;
634+}
635+
636+static int
637+file_write(int id, Uint16 addr, int len, Uint8 flags)
638+{
639+ int ret = 0;
640+ if(file_not_ready(id))
641+ return 0;
642+ file_write_dir(ufs[id].filepath);
643+ if(ufs[id].state != FILE_WRITE && ufs[id].state != DIR_WRITE) {
644+ file_reset(id);
645+ if(is_dir_path(ufs[id].filepath))
646+ ufs[id].state = DIR_WRITE;
647+ else if((ufs[id].f = fopen(ufs[id].filepath, (flags & 0x01) ? "ab" : "wb")) != NULL)
648+ ufs[id].state = FILE_WRITE;
649+ }
650+ if(ufs[id].state == FILE_WRITE)
651+ if((ret = fwrite(&uxn.ram[addr], 1, len, ufs[id].f)) > 0 && fflush(ufs[id].f) != 0)
652+ ret = 0;
653+ if(ufs[id].state == DIR_WRITE)
654+ ret = is_dir_real(ufs[id].filepath);
655+ return ret;
656+}
657+
658+static int
659+file_stat(int id, Uint16 addr, int len)
660+{
661+ int err, dir;
662+ struct stat st;
663+ if(file_not_ready(id))
664+ return 0;
665+ err = stat(ufs[id].filepath, &st);
666+ dir = S_ISDIR(st.st_mode);
667+ return put_stat(&uxn.ram[addr], len, st.st_size, err, dir, 0);
668+}
669+
670+static int
671+file_delete(int id)
672+{
673+ if(file_not_ready(id))
674+ return -1;
675+ return unlink(ufs[id].filepath);
676+}
677+
678+static void
679+file_success(int port, int value)
680+{
681+ POKE2(&uxn.dev[port], value);
682+}
683+
684+/* file registers */
685+
686+static int rL1, rL2;
687+
688+static void
689+file_deo(Uint8 port)
690+{
691+ switch(port) {
692+ /* File 1 */
693+ case 0xab: rL1 = PEEK2(&uxn.dev[0xaa]); break;
694+ case 0xa5: file_success(0xa2, file_stat(0, PEEK2(&uxn.dev[0xa4]), rL1)); break;
695+ case 0xa6: file_success(0xa2, file_delete(0)); break;
696+ case 0xa9: file_success(0xa2, file_init(0, PEEK2(&uxn.dev[0xa8]))); break;
697+ case 0xad: file_success(0xa2, file_read(0, PEEK2(&uxn.dev[0xac]), rL1)); break;
698+ case 0xaf: file_success(0xa2, file_write(0, PEEK2(&uxn.dev[0xae]), rL1, uxn.dev[0xa7])); break;
699+ /* File 2 */
700+ case 0xbb: rL2 = PEEK2(&uxn.dev[0xba]); break;
701+ case 0xb5: file_success(0xb2, file_stat(1, PEEK2(&uxn.dev[0xb4]), rL2)); break;
702+ case 0xb6: file_success(0xb2, file_delete(1)); break;
703+ case 0xb9: file_success(0xb2, file_init(1, PEEK2(&uxn.dev[0xb8]))); break;
704+ case 0xbd: file_success(0xb2, file_read(1, PEEK2(&uxn.dev[0xbc]), rL2)); break;
705+ case 0xbf: file_success(0xb2, file_write(1, PEEK2(&uxn.dev[0xbe]), rL2, uxn.dev[0xb7])); break;
706+ }
707+}
708+
709+/*
710+@|Datetime ---------------------------------------------------------- */
711+
712+#include <time.h>
713+
714+static Uint8
715+datetime_dei(Uint8 addr)
716+{
717+ time_t seconds = time(NULL);
718+ struct tm zt = {0};
719+ struct tm *t = localtime(&seconds);
720+ if(t == NULL) t = &zt;
721+ switch(addr) {
722+ case 0xc0: return (t->tm_year + 1900) >> 8;
723+ case 0xc1: return (t->tm_year + 1900);
724+ case 0xc2: return t->tm_mon;
725+ case 0xc3: return t->tm_mday;
726+ case 0xc4: return t->tm_hour;
727+ case 0xc5: return t->tm_min;
728+ case 0xc6: return t->tm_sec;
729+ case 0xc7: return t->tm_wday;
730+ case 0xc8: return t->tm_yday >> 8;
731+ case 0xc9: return t->tm_yday;
732+ case 0xca: return t->tm_isdst;
733+ default: return uxn.dev[addr];
734+ }
735+}
736+
737+/*
738+@|Core -------------------------------------------------------------- */
739
740 Uint8
741 emu_dei(Uint8 addr)
742 {
743 switch(addr & 0xf0) {
744 case 0x00: return system_dei(addr);
745+ case 0x10: console_dei(addr); break;
746 case 0xc0: return datetime_dei(addr);
747 }
748 return uxn.dev[addr];
749@@ -48,7 +760,7 @@ main(int argc, char **argv)
750 {
751 int i = 1;
752 if(argc == 2 && argv[1][0] == '-' && argv[1][1] == 'v')
753- return !fprintf(stdout, "Uxn(cli) - Varvara Emulator, 14 Mar 2025.\n");
754+ return !fprintf(stdout, "Uxn(cli) - Varvara Emulator, 19 Mar 2025.\n");
755 else if(argc == 1)
756 return !fprintf(stdout, "usage: %s [-v] file.rom [args..]\n", argv[0]);
757 else if(!system_boot((Uint8 *)calloc(0x10000 * RAM_PAGES, sizeof(Uint8)), argv[i++], argc > 2))