commit 5025c03

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