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