commit 9cc0785

Devine Lu Linvega  ·  2024-11-14 19:10:23 +0000 UTC
parent 1ebf0d8
Uxncli is now a single file implementation
2 files changed,  +639, -31
+4, -3
 1@@ -38,6 +38,7 @@ grab:
 2 	@ cp ../uxn-utils/cli/opctest/opctest.tal etc/opctest.tal
 3 archive:
 4 	@ cp src/uxnasm.c ../oscean/etc/uxnasm.c.txt
 5+	@ cp src/uxncli.c ../oscean/etc/uxncli.c.txt
 6 clean:
 7 	@ rm -fr bin
 8 
 9@@ -54,9 +55,9 @@ src/bios.c: src/utils/bios.tal
10 bin/uxnasm: src/uxnasm.c
11 	@ mkdir -p bin
12 	@ cc ${RELEASE_flags} ${CFLAGS} src/uxnasm.c -o bin/uxnasm
13-bin/uxncli: ${CLI_src} src/uxncli.c
14+bin/uxncli: src/uxncli.c
15 	@ mkdir -p bin
16-	@ cc ${RELEASE_flags} ${CFLAGS} ${CLI_src} src/uxncli.c -lutil -o bin/uxncli
17+	@ cc ${RELEASE_flags} ${CFLAGS} src/uxncli.c -lutil -o bin/uxncli
18 bin/uxn11: ${EMU_src} src/uxn11.c src/bios.c
19 	@ mkdir -p bin
20 	@ cc ${RELEASE_flags} ${CFLAGS} ${EMU_src} src/uxn11.c -lX11 -lutil -o bin/uxn11
21@@ -66,7 +67,7 @@ bin/uxnasm-debug: src/uxnasm.c
22 	@ cc ${DEBUG_flags} ${CFLAGS} src/uxnasm.c -o bin/uxnasm-debug
23 bin/uxncli-debug: ${CLI_src} src/uxncli.c
24 	@ mkdir -p bin
25-	@ cc ${DEBUG_flags} ${CFLAGS} ${CLI_src} src/uxncli.c -lutil -o bin/uxncli-debug
26+	@ cc ${DEBUG_flags} ${CFLAGS} src/uxncli.c -lutil -o bin/uxncli-debug
27 bin/uxn11-debug: ${EMU_src} src/uxn11.c
28 	@ mkdir -p bin
29 	@ cc ${DEBUG_flags} ${CFLAGS} ${EMU_src} src/uxn11.c -lX11 -lutil -o bin/uxn11-debug
+635, -28
  1@@ -1,11 +1,13 @@
  2+#define _XOPEN_SOURCE 500
  3 #include <stdio.h>
  4 #include <stdlib.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+#include <string.h>
 12+#include <dirent.h>
 13+#include <errno.h>
 14+#include <limits.h>
 15+#include <sys/stat.h>
 16+#include <unistd.h>
 17+#include <time.h>
 18 
 19 /*
 20 Copyright (c) 2021-2024 Devine Lu Linvega, Andrew Alderwick
 21@@ -18,15 +20,618 @@ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 22 WITH REGARD TO THIS SOFTWARE.
 23 */
 24 
 25+#define RAM_PAGES 0x10
 26+#define STEP_MAX 0x80000000
 27+#define PAGE_PROGRAM 0x0100
 28+
 29+typedef unsigned char Uint8;
 30+typedef signed char Sint8;
 31+typedef unsigned short Uint16;
 32+typedef signed short Sint16;
 33+typedef unsigned int Uint32;
 34+
 35+typedef struct {
 36+	Uint8 dat[0x100], ptr;
 37+} Stack;
 38+
 39+typedef struct Uxn {
 40+	Uint8 *ram, dev[0x100];
 41+	Stack wst, rst;
 42+} Uxn;
 43+
 44+/*
 45+@|Uxn
 46+*/
 47+
 48 Uxn uxn;
 49-Uint8 ram[0x10000 * (RAM_PAGES + 1)];
 50+Uint8 emu_dei(Uxn *u, Uint8 addr);
 51+void emu_deo(Uxn *u, Uint8 addr, Uint8 value);
 52+
 53+/* clang-format off */
 54+
 55+#define PEEK2(d) (*(d) << 8 | (d)[1])
 56+#define POKE2(d, v) { *(d) = (v) >> 8; (d)[1] = (v); }
 57+
 58+#define OPC(opc, init, body) {\
 59+	case 0x00|opc: {const int _2=0,_r=0;init body;} break;\
 60+	case 0x20|opc: {const int _2=1,_r=0;init body;} break;\
 61+	case 0x40|opc: {const int _2=0,_r=1;init body;} break;\
 62+	case 0x60|opc: {const int _2=1,_r=1;init body;} break;\
 63+	case 0x80|opc: {const int _2=0,_r=0,k=u->wst.ptr;init u->wst.ptr=k;body;} break;\
 64+	case 0xa0|opc: {const int _2=1,_r=0,k=u->wst.ptr;init u->wst.ptr=k;body;} break;\
 65+	case 0xc0|opc: {const int _2=0,_r=1,k=u->rst.ptr;init u->rst.ptr=k;body;} break;\
 66+	case 0xe0|opc: {const int _2=1,_r=1,k=u->rst.ptr;init u->rst.ptr=k;body;} break;\
 67+}
 68+
 69+#define JMI a = u->ram[pc] << 8 | u->ram[pc + 1], pc += a + 2;
 70+#define REM if(_r) u->rst.ptr -= 1 + _2; else u->wst.ptr -= 1 + _2;
 71+#define INC(s) u->s.dat[u->s.ptr++]
 72+#define DEC(s) u->s.dat[--u->s.ptr]
 73+#define JMP(i) pc = _2 ? i : pc + (Sint8)i;
 74+#define PO1(o) o = _r ? DEC(rst) : DEC(wst);
 75+#define PO2(o) o = _r ? DEC(rst) | DEC(rst) << 8 : DEC(wst) | DEC(wst) << 8;
 76+#define POx(o) { if(_2) PO2(o) else PO1(o) }
 77+#define PU1(i) { if(_r) INC(rst) = i; else INC(wst) = i; }
 78+#define RP1(i) { if(_r) INC(wst) = i; else INC(rst) = i; }
 79+#define PUx(i) { if(_2) { c = (i); PU1(c >> 8) PU1(c) } else PU1(i) }
 80+#define GET(o) { if(_2) PO1(o[1]) PO1(o[0]) }
 81+#define PUT(i) { PU1(i[0]) if(_2) PU1(i[1]) }
 82+#define DEI(i,o) o[0] = emu_dei(u, i); if(_2) o[1] = emu_dei(u, i + 1); PUT(o)
 83+#define DEO(i,j) emu_deo(u, i, j[0]); if(_2) emu_deo(u, i + 1, j[1]);
 84+#define PEK(i,o,m) o[0] = u->ram[i]; if(_2) o[1] = u->ram[(i + 1) & m]; PUT(o)
 85+#define POK(i,j,m) u->ram[i] = j[0]; if(_2) u->ram[(i + 1) & m] = j[1];
 86+
 87+int
 88+uxn_eval(Uxn *u, Uint16 pc)
 89+{
 90+	unsigned int a, b, c, x[2], y[2], z[2], step;
 91+	if(!pc || u->dev[0x0f]) return 0;
 92+	for(step = STEP_MAX; step; step--) {
 93+		switch(u->ram[pc++]) {
 94+		/* BRK */ case 0x00: return 1;
 95+		/* JCI */ case 0x20: if(DEC(wst)) { JMI break; } pc += 2; break;
 96+		/* JMI */ case 0x40: JMI break;
 97+		/* JSI */ case 0x60: c = pc + 2; INC(rst) = c >> 8; INC(rst) = c; JMI break;
 98+		/* LI2 */ case 0xa0: INC(wst) = u->ram[pc++]; /* fall-through */
 99+		/* LIT */ case 0x80: INC(wst) = u->ram[pc++]; break;
100+		/* L2r */ case 0xe0: INC(rst) = u->ram[pc++]; /* fall-through */
101+		/* LIr */ case 0xc0: INC(rst) = u->ram[pc++]; break;
102+		/* INC */ OPC(0x01,POx(a),PUx(a + 1))
103+		/* POP */ OPC(0x02,REM ,{})
104+		/* NIP */ OPC(0x03,GET(x) REM ,PUT(x))
105+		/* SWP */ OPC(0x04,GET(x) GET(y),PUT(x) PUT(y))
106+		/* ROT */ OPC(0x05,GET(x) GET(y) GET(z),PUT(y) PUT(x) PUT(z))
107+		/* DUP */ OPC(0x06,GET(x),PUT(x) PUT(x))
108+		/* OVR */ OPC(0x07,GET(x) GET(y),PUT(y) PUT(x) PUT(y))
109+		/* EQU */ OPC(0x08,POx(a) POx(b),PU1(b == a))
110+		/* NEQ */ OPC(0x09,POx(a) POx(b),PU1(b != a))
111+		/* GTH */ OPC(0x0a,POx(a) POx(b),PU1(b > a))
112+		/* LTH */ OPC(0x0b,POx(a) POx(b),PU1(b < a))
113+		/* JMP */ OPC(0x0c,POx(a),JMP(a))
114+		/* JCN */ OPC(0x0d,POx(a) PO1(b),if(b) JMP(a))
115+		/* JSR */ OPC(0x0e,POx(a),RP1(pc >> 8) RP1(pc) JMP(a))
116+		/* STH */ OPC(0x0f,GET(x),RP1(x[0]) if(_2) RP1(x[1]))
117+		/* LDZ */ OPC(0x10,PO1(a),PEK(a, x, 0xff))
118+		/* STZ */ OPC(0x11,PO1(a) GET(y),POK(a, y, 0xff))
119+		/* LDR */ OPC(0x12,PO1(a),PEK(pc + (Sint8)a, x, 0xffff))
120+		/* STR */ OPC(0x13,PO1(a) GET(y),POK(pc + (Sint8)a, y, 0xffff))
121+		/* LDA */ OPC(0x14,PO2(a),PEK(a, x, 0xffff))
122+		/* STA */ OPC(0x15,PO2(a) GET(y),POK(a, y, 0xffff))
123+		/* DEI */ OPC(0x16,PO1(a),DEI(a, x))
124+		/* DEO */ OPC(0x17,PO1(a) GET(y),DEO(a, y))
125+		/* ADD */ OPC(0x18,POx(a) POx(b),PUx(b + a))
126+		/* SUB */ OPC(0x19,POx(a) POx(b),PUx(b - a))
127+		/* MUL */ OPC(0x1a,POx(a) POx(b),PUx(b * a))
128+		/* DIV */ OPC(0x1b,POx(a) POx(b),PUx(a ? b / a : 0))
129+		/* AND */ OPC(0x1c,POx(a) POx(b),PUx(b & a))
130+		/* ORA */ OPC(0x1d,POx(a) POx(b),PUx(b | a))
131+		/* EOR */ OPC(0x1e,POx(a) POx(b),PUx(b ^ a))
132+		/* SFT */ OPC(0x1f,PO1(a) POx(b),PUx(b >> (a & 0xf) << (a >> 4)))
133+		}
134+	}
135+	return 0;
136+}
137+
138+/* clang-format on */
139+
140+/*
141+@|00:System
142+*/
143+
144+static int
145+system_load(char *filename)
146+{
147+	FILE *f = fopen(filename, "rb");
148+	if(f) {
149+		int i = 0, l = fread(&uxn.ram[PAGE_PROGRAM], 0x10000 - PAGE_PROGRAM, 1, f);
150+		while(l && ++i < RAM_PAGES)
151+			l = fread(uxn.ram + 0x10000 * i, 0x10000, 1, f);
152+		fclose(f);
153+	}
154+	return !!f;
155+}
156+
157+static void
158+system_printstack(char *name, Stack *s)
159+{
160+	Uint8 i;
161+	fprintf(stderr, "%s ", name);
162+	for(i = s->ptr - 8; i != (Uint8)(s->ptr); i++)
163+		fprintf(stderr, "%02x%c", s->dat[i], i == 0xff ? '|' : ' ');
164+	fprintf(stderr, "< \n");
165+}
166+
167+static int
168+system_boot(Uint8 *ram, char *rom)
169+{
170+	uxn.ram = ram;
171+	if(!system_load(rom))
172+		return 0;
173+	return 1;
174+}
175+
176+static Uint8
177+system_dei(Uint8 addr)
178+{
179+	switch(addr) {
180+	case 0x4: return uxn.wst.ptr;
181+	case 0x5: return uxn.rst.ptr;
182+	default: return uxn.dev[addr];
183+	}
184+}
185+
186+static void
187+system_deo(Uint8 port)
188+{
189+	switch(port) {
190+	case 0x3: {
191+		Uint16 addr = PEEK2(uxn.dev + 2);
192+		if(uxn.ram[addr] == 0x0) {
193+			Uint8 value = uxn.ram[addr + 7];
194+			Uint16 i, length = PEEK2(uxn.ram + addr + 1);
195+			Uint16 dst_page = PEEK2(uxn.ram + addr + 3), dst_addr = PEEK2(uxn.ram + addr + 5);
196+			int dst = (dst_page % RAM_PAGES) * 0x10000;
197+			for(i = 0; i < length; i++)
198+				uxn.ram[dst + (Uint16)(dst_addr + i)] = value;
199+		} else if(uxn.ram[addr] == 0x1) {
200+			Uint16 i, length = PEEK2(uxn.ram + addr + 1);
201+			Uint16 a_page = PEEK2(uxn.ram + addr + 3), a_addr = PEEK2(uxn.ram + addr + 5);
202+			Uint16 b_page = PEEK2(uxn.ram + addr + 7), b_addr = PEEK2(uxn.ram + addr + 9);
203+			int src = (a_page % RAM_PAGES) * 0x10000, dst = (b_page % RAM_PAGES) * 0x10000;
204+			for(i = 0; i < length; i++)
205+				uxn.ram[dst + (Uint16)(b_addr + i)] = uxn.ram[src + (Uint16)(a_addr + i)];
206+		} else if(uxn.ram[addr] == 0x2) {
207+			Uint16 i, length = PEEK2(uxn.ram + addr + 1);
208+			Uint16 a_page = PEEK2(uxn.ram + addr + 3), a_addr = PEEK2(uxn.ram + addr + 5);
209+			Uint16 b_page = PEEK2(uxn.ram + addr + 7), b_addr = PEEK2(uxn.ram + addr + 9);
210+			int src = (a_page % RAM_PAGES) * 0x10000, dst = (b_page % RAM_PAGES) * 0x10000;
211+			for(i = length - 1; i != 0xffff; i--)
212+				uxn.ram[dst + (Uint16)(b_addr + i)] = uxn.ram[src + (Uint16)(a_addr + i)];
213+		} else
214+			fprintf(stderr, "Unknown Expansion Command 0x%02x\n", uxn.ram[addr]);
215+		break;
216+	}
217+	case 0x4:
218+		uxn.wst.ptr = uxn.dev[4];
219+		break;
220+	case 0x5:
221+		uxn.rst.ptr = uxn.dev[5];
222+		break;
223+	case 0xe:
224+		system_printstack("WST", &uxn.wst);
225+		system_printstack("RST", &uxn.rst);
226+		break;
227+	}
228+}
229+
230+/*
231+@|10:Console
232+*/
233+
234+#define CONSOLE_STD 0x1
235+#define CONSOLE_ARG 0x2
236+#define CONSOLE_EOA 0x3
237+#define CONSOLE_END 0x4
238+
239+static int
240+console_input(Uint8 c, int type)
241+{
242+	uxn.dev[0x12] = c;
243+	uxn.dev[0x17] = type;
244+	return uxn_eval(&uxn, PEEK2(&uxn.dev[0x10]));
245+}
246+
247+static void
248+console_deo(Uint8 addr)
249+{
250+	FILE *fd;
251+	switch(addr) {
252+	case 0x18: fd = stdout, fputc(uxn.dev[0x18], fd), fflush(fd); break;
253+	case 0x19: fd = stderr, fputc(uxn.dev[0x19], fd), fflush(fd); break;
254+	}
255+}
256+
257+/*
258+@|a0:File
259+@|b0:File
260+*/
261+
262+#define DIR_SEP_CHAR '/'
263+#define DIR_SEP_STR "/"
264+#define pathcmp(path1, path2, length) strncmp(path1, path2, length)
265+#define notdriveroot(file_name) (file_name[0] != DIR_SEP_CHAR)
266+#define mkdir(file_name) (mkdir(file_name, 0755) == 0)
267+#define PATH_MAX 4096
268+
269+typedef struct {
270+	FILE *f;
271+	DIR *dir;
272+	char current_filename[PATH_MAX];
273+	struct dirent *de;
274+	enum { IDLE,
275+		FILE_READ,
276+		FILE_WRITE,
277+		DIR_READ,
278+		DIR_WRITE
279+	} state;
280+	int outside_sandbox;
281+} UxnFile;
282+
283+static UxnFile uxn_file[2];
284+
285+static void
286+file_reset(UxnFile *c)
287+{
288+	if(c->f != NULL)
289+		fclose(c->f), c->f = NULL;
290+	if(c->dir != NULL)
291+		closedir(c->dir), c->dir = NULL;
292+	c->de = NULL;
293+	c->state = IDLE;
294+	c->outside_sandbox = 0;
295+}
296+
297+static Uint16
298+file_get_entry(char *p, Uint16 len, const char *pathname, const char *basename, int fail_nonzero)
299+{
300+	struct stat st;
301+	if(len < strlen(basename) + 8)
302+		return 0;
303+	if(stat(pathname, &st))
304+		return fail_nonzero ? snprintf(p, len, "!!!! %s\n", basename) : 0;
305+	else if(S_ISDIR(st.st_mode))
306+		return snprintf(p, len, "---- %s/\n", basename);
307+	else if(st.st_size < 0x10000)
308+		return snprintf(p, len, "%04x %s\n", (unsigned int)st.st_size, basename);
309+	else
310+		return snprintf(p, len, "???? %s\n", basename);
311+}
312+
313+static Uint16
314+file_read_dir(UxnFile *c, char *dest, Uint16 len)
315+{
316+	static char pathname[4352];
317+	char *p = dest;
318+	if(c->de == NULL) c->de = readdir(c->dir);
319+	for(; c->de != NULL; c->de = readdir(c->dir)) {
320+		Uint16 n;
321+		if(c->de->d_name[0] == '.' && c->de->d_name[1] == '\0')
322+			continue;
323+		if(strcmp(c->de->d_name, "..") == 0) {
324+			/* hide "sandbox/.." */
325+			char cwd[PATH_MAX] = {'\0'}, *t;
326+			/* Note there's [currently] no way of chdir()ing from uxn, so $PWD
327+			* is always the sandbox top level. */
328+			getcwd(cwd, sizeof(cwd));
329+			/* We already checked that c->current_filename exists so don't need a wrapper. */
330+			t = realpath(c->current_filename, NULL);
331+			if(strcmp(cwd, t) == 0) {
332+				free(t);
333+				continue;
334+			}
335+			free(t);
336+		}
337+		if(strlen(c->current_filename) + 1 + strlen(c->de->d_name) < sizeof(pathname))
338+			snprintf(pathname, sizeof(pathname), "%s/%s", c->current_filename, c->de->d_name);
339+		else
340+			pathname[0] = '\0';
341+		n = file_get_entry(p, len, pathname, c->de->d_name, 1);
342+		if(!n) break;
343+		p += n;
344+		len -= n;
345+	}
346+	return p - dest;
347+}
348+
349+static char *
350+file_retry_realpath(const char *file_name)
351+{
352+	char *r, p[PATH_MAX] = {'\0'}, *x;
353+	int fnlen;
354+	if(file_name == NULL) {
355+		errno = EINVAL;
356+		return NULL;
357+	} else if((fnlen = strlen(file_name)) >= PATH_MAX) {
358+		errno = ENAMETOOLONG;
359+		return NULL;
360+	}
361+	if(notdriveroot(file_name)) {
362+		getcwd(p, sizeof(p));
363+		if(strlen(p) + strlen(DIR_SEP_STR) + fnlen >= PATH_MAX) {
364+			errno = ENAMETOOLONG;
365+			return NULL;
366+		}
367+		strcat(p, DIR_SEP_STR);
368+	}
369+	strcat(p, file_name);
370+	while((r = realpath(p, NULL)) == NULL) {
371+		if(errno != ENOENT)
372+			return NULL;
373+		x = strrchr(p, DIR_SEP_CHAR);
374+		if(x)
375+			*x = '\0';
376+		else
377+			return NULL;
378+	}
379+	return r;
380+}
381+
382+static void
383+file_check_sandbox(UxnFile *c)
384+{
385+	char *x, *rp, cwd[PATH_MAX] = {'\0'};
386+	x = getcwd(cwd, sizeof(cwd));
387+	rp = file_retry_realpath(c->current_filename);
388+	if(rp == NULL || (x && pathcmp(cwd, rp, strlen(cwd)) != 0)) {
389+		c->outside_sandbox = 1;
390+		fprintf(stderr, "file warning: blocked attempt to access %s outside of sandbox\n", c->current_filename);
391+	}
392+	free(rp);
393+}
394+
395+static Uint16
396+file_init(UxnFile *c, char *filename, size_t max_len, int override_sandbox)
397+{
398+	char *p = c->current_filename;
399+	size_t len = sizeof(c->current_filename);
400+	file_reset(c);
401+	if(len > max_len) len = max_len;
402+	while(len) {
403+		if((*p++ = *filename++) == '\0') {
404+			if(!override_sandbox) /* override sandbox for loading roms */
405+				file_check_sandbox(c);
406+			return 0;
407+		}
408+		len--;
409+	}
410+	c->current_filename[0] = '\0';
411+	return 0;
412+}
413+
414+static Uint16
415+file_read(UxnFile *c, void *dest, int len)
416+{
417+	if(c->outside_sandbox) return 0;
418+	if(c->state != FILE_READ && c->state != DIR_READ) {
419+		file_reset(c);
420+		if((c->dir = opendir(c->current_filename)) != NULL)
421+			c->state = DIR_READ;
422+		else if((c->f = fopen(c->current_filename, "rb")) != NULL)
423+			c->state = FILE_READ;
424+	}
425+	if(c->state == FILE_READ)
426+		return fread(dest, 1, len, c->f);
427+	if(c->state == DIR_READ)
428+		return file_read_dir(c, dest, len);
429+	return 0;
430+}
431+
432+static int
433+file_is_dir_path(char *p)
434+{
435+	char c;
436+	int saw_slash = 0;
437+	while((c = *p++))
438+		saw_slash = c == DIR_SEP_CHAR;
439+	return saw_slash;
440+}
441+
442+static int
443+file_dir_exists(char *p)
444+{
445+	struct stat st;
446+	return stat(p, &st) == 0 && S_ISDIR(st.st_mode);
447+}
448+
449+static int
450+file_ensure_parent_dirs(char *p)
451+{
452+	int ok = 1;
453+	char c, *s = p;
454+	for(; ok && (c = *p); p++) {
455+		if(c == DIR_SEP_CHAR) {
456+			*p = '\0';
457+			ok = file_dir_exists(s) || mkdir(s);
458+			*p = c;
459+		}
460+	}
461+	return ok;
462+}
463+
464+static Uint16
465+file_write(UxnFile *c, void *src, Uint16 len, Uint8 flags)
466+{
467+	Uint16 ret = 0;
468+	if(c->outside_sandbox) return 0;
469+	file_ensure_parent_dirs(c->current_filename);
470+	if(c->state != FILE_WRITE && c->state != DIR_WRITE) {
471+		file_reset(c);
472+		if(file_is_dir_path(c->current_filename))
473+			c->state = DIR_WRITE;
474+		else if((c->f = fopen(c->current_filename, (flags & 0x01) ? "ab" : "wb")) != NULL)
475+			c->state = FILE_WRITE;
476+	}
477+	if(c->state == FILE_WRITE) {
478+		if((ret = fwrite(src, 1, len, c->f)) > 0 && fflush(c->f) != 0)
479+			ret = 0;
480+	}
481+	if(c->state == DIR_WRITE) {
482+		ret = file_dir_exists(c->current_filename);
483+	}
484+	return ret;
485+}
486+
487+static Uint16
488+file_stat_fill(Uint8 *dest, Uint16 len, char c)
489+{
490+	Uint16 i;
491+	for(i = 0; i < len; i++) *(dest++) = c;
492+	return len;
493+}
494+
495+static Uint16
496+file_stat_size(Uint8 *dest, Uint16 len, off_t size)
497+{
498+	Uint16 i;
499+	dest += len - 1;
500+	for(i = 0; i < len; i++) {
501+		*(dest--) = "0123456789abcdef"[(Uint8)(size & 0xf)];
502+		size = size >> 4;
503+	}
504+	return size == 0 ? len : file_stat_fill(dest, len, '?');
505+}
506+
507+static Uint16
508+file_stat(UxnFile *c, void *dest, Uint16 len)
509+{
510+	struct stat st;
511+	if(c->outside_sandbox)
512+		return 0;
513+	else if(stat(c->current_filename, &st))
514+		return file_stat_fill(dest, len, '!');
515+	else if(S_ISDIR(st.st_mode))
516+		return file_stat_fill(dest, len, '-');
517+	else
518+		return file_stat_size(dest, len, st.st_size);
519+}
520+
521+static Uint16
522+file_delete(UxnFile *c)
523+{
524+	return c->outside_sandbox ? 0 : unlink(c->current_filename);
525+}
526+
527+static void
528+file_deo(Uint8 port)
529+{
530+	Uint16 addr, len, res;
531+	switch(port) {
532+	case 0xa5:
533+		addr = PEEK2(&uxn.dev[0xa4]);
534+		len = PEEK2(&uxn.dev[0xaa]);
535+		if(len > 0x10000 - addr)
536+			len = 0x10000 - addr;
537+		res = file_stat(&uxn_file[0], &uxn.ram[addr], len);
538+		POKE2(&uxn.dev[0xa2], res);
539+		break;
540+	case 0xa6:
541+		res = file_delete(&uxn_file[0]);
542+		POKE2(&uxn.dev[0xa2], res);
543+		break;
544+	case 0xa9:
545+		addr = PEEK2(&uxn.dev[0xa8]);
546+		res = file_init(&uxn_file[0], (char *)&uxn.ram[addr], 0x10000 - addr, 0);
547+		POKE2(&uxn.dev[0xa2], res);
548+		break;
549+	case 0xad:
550+		addr = PEEK2(&uxn.dev[0xac]);
551+		len = PEEK2(&uxn.dev[0xaa]);
552+		if(len > 0x10000 - addr)
553+			len = 0x10000 - addr;
554+		res = file_read(&uxn_file[0], &uxn.ram[addr], len);
555+		POKE2(&uxn.dev[0xa2], res);
556+		break;
557+	case 0xaf:
558+		addr = PEEK2(&uxn.dev[0xae]);
559+		len = PEEK2(&uxn.dev[0xaa]);
560+		if(len > 0x10000 - addr)
561+			len = 0x10000 - addr;
562+		res = file_write(&uxn_file[0], &uxn.ram[addr], len, uxn.dev[0xa7]);
563+		POKE2(&uxn.dev[0xa2], res);
564+		break;
565+	/* File 2 */
566+	case 0xb5:
567+		addr = PEEK2(&uxn.dev[0xb4]);
568+		len = PEEK2(&uxn.dev[0xba]);
569+		if(len > 0x10000 - addr)
570+			len = 0x10000 - addr;
571+		res = file_stat(&uxn_file[1], &uxn.ram[addr], len);
572+		POKE2(&uxn.dev[0xb2], res);
573+		break;
574+	case 0xb6:
575+		res = file_delete(&uxn_file[1]);
576+		POKE2(&uxn.dev[0xb2], res);
577+		break;
578+	case 0xb9:
579+		addr = PEEK2(&uxn.dev[0xb8]);
580+		res = file_init(&uxn_file[1], (char *)&uxn.ram[addr], 0x10000 - addr, 0);
581+		POKE2(&uxn.dev[0xb2], res);
582+		break;
583+	case 0xbd:
584+		addr = PEEK2(&uxn.dev[0xbc]);
585+		len = PEEK2(&uxn.dev[0xba]);
586+		if(len > 0x10000 - addr)
587+			len = 0x10000 - addr;
588+		res = file_read(&uxn_file[1], &uxn.ram[addr], len);
589+		POKE2(&uxn.dev[0xb2], res);
590+		break;
591+	case 0xbf:
592+		addr = PEEK2(&uxn.dev[0xbe]);
593+		len = PEEK2(&uxn.dev[0xba]);
594+		if(len > 0x10000 - addr)
595+			len = 0x10000 - addr;
596+		res = file_write(&uxn_file[1], &uxn.ram[addr], len, uxn.dev[0xb7]);
597+		POKE2(&uxn.dev[0xb2], res);
598+		break;
599+	}
600+}
601+
602+/*
603+@|c0:Datetime
604+*/
605+
606+static Uint8
607+datetime_dei(Uint8 addr)
608+{
609+	time_t seconds = time(NULL);
610+	struct tm zt = {0}, *t = localtime(&seconds);
611+	if(t == NULL)
612+		t = &zt;
613+	switch(addr) {
614+	case 0xc0: return (t->tm_year + 1900) >> 8;
615+	case 0xc1: return (t->tm_year + 1900);
616+	case 0xc2: return t->tm_mon;
617+	case 0xc3: return t->tm_mday;
618+	case 0xc4: return t->tm_hour;
619+	case 0xc5: return t->tm_min;
620+	case 0xc6: return t->tm_sec;
621+	case 0xc7: return t->tm_wday;
622+	case 0xc8: return t->tm_yday >> 8;
623+	case 0xc9: return t->tm_yday;
624+	case 0xca: return t->tm_isdst;
625+	default: return uxn.dev[addr];
626+	}
627+}
628+
629+/*
630+@|Emu
631+*/
632 
633 Uint8
634 emu_dei(Uxn *u, Uint8 addr)
635 {
636 	switch(addr & 0xf0) {
637-	case 0x00: return system_dei(u, addr);
638-	case 0x10: return console_dei(u, addr);
639+	case 0x00: return system_dei(addr);
640 	case 0xc0: return datetime_dei(addr);
641 	}
642 	return u->dev[addr];
643@@ -37,38 +642,40 @@ emu_deo(Uxn *u, Uint8 addr, Uint8 value)
644 {
645 	u->dev[addr] = value;
646 	switch(addr & 0xf0) {
647-	case 0x00: system_deo(u, addr); break;
648-	case 0x10: console_deo(u, addr); break;
649+	case 0x00: system_deo(addr); break;
650+	case 0x10: console_deo(addr); break;
651 	case 0xa0: file_deo(addr); break;
652 	case 0xb0: file_deo(addr); break;
653 	}
654 }
655 
656-static void
657-emu_run(void)
658-{
659-	while(!uxn.dev[0x0f]) {
660-		int c = fgetc(stdin);
661-		if(c == EOF) {
662-			console_input(0x00, CONSOLE_END);
663-			break;
664-		}
665-		console_input(c, CONSOLE_STD);
666-	}
667-}
668-
669 int
670 main(int argc, char **argv)
671 {
672 	int i = 1;
673 	if(argc == 2 && argv[1][0] == '-' && argv[1][1] == 'v')
674-		return !fprintf(stdout, "Uxn11(cli) - Varvara Emulator, 12 Nov 2024.\n");
675+		return !fprintf(stdout, "Uxn11(cli) - Varvara Emulator, 14 Nov 2024.\n");
676 	else if(argc == 1)
677 		return !fprintf(stdout, "usage: %s [-v] file.rom [args..]\n", argv[0]);
678-	else if(!system_boot(ram, argv[i++]))
679+	else if(!system_boot((Uint8 *)calloc(0x10000 * RAM_PAGES, sizeof(Uint8)), argv[i++]))
680 		return !fprintf(stdout, "Could not load %s.\n", argv[i - 1]);
681 	uxn.dev[0x17] = argc - 2;
682-	if(uxn_eval(&uxn, PAGE_PROGRAM) && uxn.dev[0x10])
683-		console_listen(2, argc, argv), emu_run();
684+	if(uxn_eval(&uxn, PAGE_PROGRAM) && uxn.dev[0x10]) {
685+		/* arguments input */
686+		for(; i < argc; i++) {
687+			char *p = argv[i];
688+			while(*p) console_input(*p++, CONSOLE_ARG);
689+			console_input('\n', i == argc - 1 ? CONSOLE_END : CONSOLE_EOA);
690+		}
691+		/* console input */
692+		while(!uxn.dev[0x0f]) {
693+			int c = fgetc(stdin);
694+			if(c == EOF) {
695+				console_input(0x00, CONSOLE_END);
696+				break;
697+			}
698+			console_input(c, CONSOLE_STD);
699+		}
700+	}
701 	return uxn.dev[0x0f] & 0x7f;
702 }