OXYGENHUB
◆ main x16-emulator / src / ieee.c
46.9 KiB · C · last changed chore(): initial commit 15 days ago source blame history raw
1
// Commander X16 Emulator
2
// Copyright (c) 2022, 2023 Michael Steil, et al
3
// All rights reserved. License: 2-clause BSD
4
5
// Commodore Bus emulation
6
// * L2: TALK/LISTEN layer: https://www.pagetable.com/?p=1031
7
// * L3: Commodore DOS: https://www.pagetable.com/?p=1038
8
// This is used from
9
// * serial.c: L1: Serial Bus emulation (low level)
10
// * main.c: IEEE KERNAL call hooks (high level)
11
12
#ifndef __APPLE__
13
#define _XOPEN_SOURCE   600
14
#define _POSIX_C_SOURCE 1
15
#endif
16
#include <string.h>
17
#include <stdio.h>
18
#include <stdbool.h>
19
#include <sys/stat.h>
20
#include <dirent.h>
21
#include <unistd.h>
22
#include <stdlib.h>
23
#include <SDL.h>
24
#include <errno.h>
25
#include <time.h>
26
#include <limits.h>
27
#include "memory.h"
28
#include "ieee.h"
29
#include "glue.h"
30
#include "utf8_encode.h"
31
#include "utf8.h"
32
#include "iso_8859_15.h"
33
#ifdef __MINGW32__
34
#include <direct.h>
35
// Windows just has to be different
36
#define localtime_r(S,D) !localtime_s(D,S)
37
#include <io.h>
38
#define F_OK 0
39
#define access _access
40
#endif
41
42
extern SDL_RWops *prg_file;
43
44
#define WILDCARD_ALL 0
45
#define WILDCARD_PRG 1
46
#define WILDCARD_DIR 2
47
48
// Globals
49
50
//bool log_ieee = true;
51
bool log_ieee = false;
52
53
bool ieee_initialized_once = false;
54
55
uint8_t error[256];
56
int error_len = 0;
57
int error_pos = 0;
58
uint8_t cmd[256];
59
int cmdlen = 0;
60
int namelen = 0;
61
int channel = 0;
62
bool listening = false;
63
bool talking = false;
64
bool opening = false;
65
bool overwrite = false;
66
bool path_exists = false;
67
bool prg_consumed = false;
68
69
int ieee_unit = 8;
70
71
uint8_t *hostfscwd = NULL;
72
73
uint8_t dirlist[1024]; // Plenty large to hold a single entry
74
int dirlist_len = 0;
75
int dirlist_pos = 0;
76
bool dirlist_cwd = false; // whether we're doing a cwd dirlist or a normal one
77
bool dirlist_eof = true;
78
bool dirlist_timestmaps = false;
79
bool dirlist_long = false;
80
DIR *dirlist_dirp;
81
uint8_t dirlist_wildcard[256];
82
uint8_t dirlist_type_filter;
83
84
uint16_t cbdos_flags = 0;
85
86
const char *blocks_free = "BLOCKS FREE.";
87
88
typedef struct {
89
	uint8_t name[80];
90
	bool read;
91
	bool write;
92
	SDL_RWops *f;
93
} channel_t;
94
95
channel_t channels[16];
96
97
#ifdef __MINGW32__
98
// realpath doesn't exist on Windows. This function implements its behavior.
99
static char *
100
realpath(const char *path, char *resolved_path) {
101
	char *ret = _fullpath(resolved_path, path, PATH_MAX);
102
103
	if (ret && _access(ret,0) && errno == ENOENT) {
104
		if (resolved_path == NULL)
105
			free(ret);
106
		return NULL;
107
	}
108
109
	return ret;
110
}
111
#endif
112
113
#define u8strchr(A,B) (uint8_t *)strchr((char *)A,B)
114
#define u8strrchr(A,B) (uint8_t *)strrchr((char *)A,B)
115
#define u8strcpy(A,B) strcpy((char *)A,(char *)B)
116
#define u8strcmp(A,B) strcmp((char *)A,(char *)B)
117
#define u8strncmp(A,B,C) strncmp((char *)A,(char *)B,C)
118
#define u8strncpy(A,B,C) strncpy((char *)A,(char *)B,C)
119
#define u8strlen(A) strlen((char *)A)
120
#define u8stat(A,B) stat((char *)A,B)
121
#define u8realpath(A,B) (uint8_t *)realpath((char *)A,(char *)B)
122
#define u8getcwd(A,B) (uint8_t *)getcwd((char *)A,B)
123
124
// Prototypes for some of the static functions
125
126
static void clear_error();
127
static void set_error(int e, int t, int s);
128
static void cchdir(uint8_t *dir);
129
static int cgetcwd(uint8_t *buf, size_t len);
130
static void cseek(int channel, uint32_t pos);
131
static void ctell(int channel);
132
static void cmkdir(uint8_t *dir);
133
static void crmdir(uint8_t *dir);
134
static void cunlink(uint8_t *f);
135
static void crename(uint8_t *f);
136
137
// Functions
138
139
static void
140
set_kernal_cbdos_flags(uint8_t flags)
141
{
142
	if (cbdos_flags)
143
		write6502(cbdos_flags, 0, flags);
144
}
145
146
static uint8_t
147
get_kernal_cbdos_flags(void)
148
{
149
	if (cbdos_flags) {
150
		return read6502(cbdos_flags, 0);
151
	} else {
152
		return 0;
153
	}
154
}
155
156
157
static void
158
utf8_to_iso_string(uint8_t *dst, const uint8_t *src)
159
{
160
	int i;
161
	int e;
162
	uint32_t cp;
163
164
	// utf8_decode requires the source string be at least 4 bytes
165
	// longer than the string (IOW, 4 terminating nulls)
166
	uint8_t *s = malloc(u8strlen(src)+4);
167
	memset(s, 0, u8strlen(src)+4);
168
	u8strcpy(s, src);
169
170
	uint8_t *so = s;
171
172
	for (i = 0; *so; i++) {
173
		so = utf8_decode(so, &cp, &e);
174
		if (e) {
175
			dst[i] = '?';
176
		} else {
177
			switch (cp) {
178
				case ',':
179
				case '"':
180
				case '*':
181
				case ':':
182
				case '\\':
183
				case '<':
184
				case '>':
185
				case '|':
186
					// these are not valid in filenames on FAT32 but might be encountered on hostfs
187
					dst[i] = '?';
188
					break;
189
				default:
190
					dst[i] = iso8859_15_from_unicode(cp);
191
					break;
192
			}
193
		}
194
	}
195
196
	dst[i] = 0;
197
	free(s);
198
}
199
200
// Puts the emulated cwd in buf, up to the maximum length specified by len
201
// Turn null termination into a space
202
// This is for displaying in the directory header
203
static int
204
cgetcwd(uint8_t *buf, size_t len)
205
{
206
	int o = 0;
207
	uint8_t l = fsroot_path[u8strlen(fsroot_path)-1];
208
	if (l == '/' || l == '\\')
209
		o--;
210
	if (u8strlen(fsroot_path) == u8strlen(hostfscwd))
211
		u8strncpy(buf, "/", len);
212
	else 
213
		u8strncpy(buf, hostfscwd+u8strlen(fsroot_path)+o, len);
214
	// Turn backslashes into slashes
215
	for (o = 0; o < len; o++) {
216
		if (buf[o] == 0) buf[o] = ' ';
217
		if (buf[o] == '\\') buf[o] = '/';
218
	}
219
220
	return 0;
221
}
222
223
static uint32_t
224
case_fold_unicode(uint32_t cp)
225
{
226
	// ASCII letters, and most of the ISO letters
227
	if ((cp >= 0x41 && cp <= 0x5a) || (cp >= 0xc0 && cp <= 0xde)) cp += 0x20;
228
	// fold the Š
229
	else if (cp == 0x0160) cp = 0x0161;
230
	// fold the Ž
231
	else if (cp == 0x017d) cp = 0x017e;
232
	// fold the Œ
233
	else if (cp == 0x0152) cp = 0x0153;
234
	// fold the Ÿ
235
	else if (cp == 0x0178) cp = 0xff;
236
237
	return cp;
238
}
239
240
static uint8_t
241
case_fold_iso(uint8_t c) {
242
	uint8_t f = c;
243
	// ASCII letters, and most of the ISO letters
244
	if ((f >= 0x41 && f <= 0x5a) || (f >= 0xc0 && f <= 0xde)) f += 0x20;
245
	// fold the Š
246
	else if (f == 0xa6) f = 0xa8;
247
	// fold the Ž
248
	else if (f == 0xb4) f = 0xb8;
249
	// fold the Œ
250
	else if (f == 0xbc) f = 0xbd;
251
	// fold the Ÿ
252
	else if (f == 0xbe) f = 0xff;
253
254
	return f;
255
}
256
257
258
static uint32_t
259
utf8_to_codepoint(uint8_t *str, int *off)
260
{
261
	uint32_t cp;
262
	int e;
263
264
	// utf8_decode requires the source string be at least 4 bytes
265
	// longer than the string (IOW, 4 terminating nulls)
266
	uint8_t *s = malloc(u8strlen(str+(*off))+4);
267
	memset(s, 0, u8strlen(str+(*off))+4);
268
	u8strcpy(s, str+(*off));
269
	
270
	uint8_t *so = utf8_decode(s, &cp, &e);
271
272
	*off += (so - s) - 1;
273
	free(s);
274
275
	if (e) {
276
		cp = '?';
277
	}
278
279
	return cp;
280
}
281
282
283
// compare two characters in a UTF-8 string
284
// in a case-insensitive manner,
285
// return value is cp2 - cp1 after case folding
286
// hence will be zero on match
287
static int
288
u8compare_utf8_char_i(uint8_t *str1, uint8_t *str2, int *off1, int *off2)
289
{
290
	uint32_t cp1, cp2;
291
292
	// these functions will increment *off1/*off2 to the
293
	// byte before the next character if the UTF-8
294
	// strings are multi-byte
295
	cp1 = utf8_to_codepoint(str1, off1);
296
	cp2 = utf8_to_codepoint(str2, off2);
297
298
	return case_fold_unicode(cp2) - case_fold_unicode(cp1);
299
}
300
301
302
static uint8_t *
303
parse_dos_filename(const uint8_t *name, bool dirhandling)
304
{
305
	// in case the name starts with something with special meaning,
306
	// such as @0:
307
	uint8_t *name_ptr = NULL;
308
	uint8_t *newname = malloc(u8strlen(name)+1);
309
	int i, j;
310
311
	newname[u8strlen(name)] = 0;
312
313
	overwrite = false;
314
315
	// [[@][<media 0-9>][</relative_path/> | <//absolute_path/>]:]<file_path>[*]
316
	// Examples of valid dos filenames
317
	//   ":FILE.PRG"  (same as "FILE.PRG")
318
	//   "@:FILE.PRG"  (same as "FILE.PRG" but overwrite okay)
319
	//   "@0:FILE.PRG"  (same as above)
320
	//   "//DIR/:FILE.PRG"  (same as "/DIR/FILE.PRG")
321
	//   "/DIR/:FILE.PRG"  (same as "./DIR/FILE.PRG")
322
	//   "FILE*" (matches the first file in the directory which starts with "FILE")
323
324
	// This routine only parses the bits before the ':'
325
	// and normalizes directory parts by attaching them to the name part
326
327
	// resolve_path*() is responsible for resolving absolute and relative
328
	// paths, and for processing the wildcard option
329
330
	name_ptr = u8strchr(name,':');
331
	if (dirhandling && !name_ptr)
332
		name_ptr = (uint8_t *)(name+u8strlen(name));
333
334
	if (name_ptr) {
335
		name_ptr++;
336
		i = 0;
337
		j = 0;
338
339
340
		if (!dirhandling) {
341
			// @ is overwrite flag
342
			if (name[i] == '@') {
343
				overwrite = true;
344
				i++;
345
			}
346
347
			// Medium, we don't care what this number is
348
			while (name[i] >= '0' && name[i] <= '9')
349
				i++;
350
		}
351
352
		// Directory name
353
		if (name[i] == '/') {
354
			i++;
355
			for (; name+i+1 < name_ptr; i++, j++) {
356
				newname[j] = name[i];
357
			}
358
359
			// mangled directory name (single slash?)
360
			if (j == 0) {
361
				free(newname);
362
				return NULL;
363
			}
364
365
			// Directory portion must end with /
366
			if (newname[j-1] != '/') {
367
				free(newname);
368
				return NULL;
369
			}
370
		}
371
372
		u8strcpy(newname+j,name_ptr);
373
374
	} else {
375
		u8strcpy(newname,name);
376
	}
377
378
	return newname;
379
}
380
381
// Returns a ptr to malloc()ed space which must be free()d later, or NULL
382
static uint8_t *
383
resolve_path_utf8(const uint8_t *name, bool must_exist, int wildcard_filetype)
384
{
385
	path_exists = false;
386
	clear_error();
387
	// Resolve the filename in the context of the emulated cwd
388
	// allocate plenty of string space
389
	uint8_t *tmp = malloc(u8strlen(name)+u8strlen(hostfscwd)+2);
390
	uint8_t *tmp2 = malloc(u8strlen(name)+u8strlen(hostfscwd)+2);
391
	uint8_t *c;
392
	uint8_t *d;
393
	uint8_t *ret;
394
	DIR *dirp;
395
	struct dirent *dp;
396
	struct stat st;
397
	int i;
398
	int j;
399
	bool has_wildcard_chars = false;
400
401
	if (tmp == NULL || tmp2 == NULL) {
402
		if (tmp) free(tmp);
403
		if (tmp2) free(tmp2);
404
		set_error(0x70, 0, 0);
405
		return NULL;
406
	}
407
408
	// If the filename begins with /, simply append it to the fsroot_path,
409
	// slash(es) and all, otherwise append it to the cwd, but with a /
410
	// in between
411
	if (name[0] == '/' || name[0] == '\\') { // absolute
412
		u8strcpy(tmp, fsroot_path);
413
		u8strcpy(tmp+u8strlen(fsroot_path), name);
414
	} else { // relative
415
		u8strcpy(tmp, hostfscwd);
416
		tmp[u8strlen(hostfscwd)] = '/';
417
		u8strcpy(tmp+u8strlen(hostfscwd)+1, name);
418
	}
419
420
	// keep the original parsed name
421
	u8strcpy(tmp2, tmp);
422
423
	if (u8strchr(tmp,'*') || u8strchr(tmp,'?')) { // oh goodie, a wildcard
424
		has_wildcard_chars = true;
425
	}
426
	// We have to search the directory for the first occurrence
427
	// in directory order.
428
429
	c = u8strrchr(tmp,'\\');
430
	d = u8strrchr(tmp,'/');
431
432
	if (c == NULL && d == NULL) { // This should never happen
433
		free(tmp);
434
		free(tmp2);
435
		set_error(0x62, 0, 0);
436
		return NULL;
437
	}
438
439
	// Chop off ret at the last path separator
440
	// and set c to point at the element with the wildcard
441
	// pattern
442
	if (c > d) {
443
		*c = 0;
444
		c++;
445
	} else {
446
		*d = 0;
447
		c = d+1;
448
	}
449
450
	// XXX
451
	//
452
	// tmp could have been populated with path parts that have not been checked
453
	// this will fail on case-sensitive filesystems
454
	// but if the user did not specify a path part in their filename
455
	// we should be able to resolve the new part of the name
456
	// we'll probably want to fix this at some point
457
	// but it requires some thought
458
459
	if (!(dirp = opendir((char *)tmp))) { // Directory couldn't be opened
460
		free(tmp);
461
		free(tmp2);
462
		set_error(0x62, 0, 0);
463
		return NULL;
464
	}
465
466
	ret = NULL;
467
468
	bool found = false;
469
470
	while ((dp = readdir(dirp))) {
471
		// in a wildcard match that starts at first position, leading dot filenames are not considered
472
		if ((*c == '*' || *c == '?') && *(dp->d_name) == '.')
473
			continue;
474
		for (i = 0, j = 0; j < u8strlen(c) && i < u8strlen(dp->d_name); i++, j++) {
475
			if (c[j] == '*') {
476
				found = true;
477
				break;
478
			} else if (c[j] == '?') {
479
				// '?' needs to eat the extra UTF-8 bytes if applicable
480
				if (((dp->d_name)[i] & 0xe0) == 0xc0) i++;
481
				else if (((dp->d_name)[i] & 0xf0) == 0xe0) i+=2;
482
				else if (((dp->d_name)[i] & 0xf8) == 0xf0) i+=3;
483
				continue;
484
			} else if (u8compare_utf8_char_i(c, (uint8_t *)(dp->d_name), &j, &i)) {
485
				break;
486
			}
487
		}
488
489
		// If we reach the end of both strings, it's a match
490
		if (i == u8strlen(dp->d_name) && j == u8strlen(c))
491
			found = true;
492
493
		// If we reach the end of the filename, but the next char
494
		// in the search string is *, then it's also a match
495
		else if (i == u8strlen(dp->d_name) && c[j] == '*')
496
			found = true;
497
498
		if (found) { // simple wildcard match
499
			ret = malloc(u8strlen(tmp)+u8strlen(dp->d_name)+2);
500
			if (ret == NULL) { // memory allocation error
501
				free(tmp);
502
				free(tmp2);
503
				closedir(dirp);
504
				set_error(0x70, 0, 0);
505
				return NULL;
506
			}
507
			u8strcpy(ret, tmp);
508
			ret[u8strlen(tmp)] = '/';
509
			u8strcpy(ret+u8strlen(tmp)+1, dp->d_name);
510
			if (wildcard_filetype) {
511
				u8stat(ret, &st);
512
				// in a wildcard match where the filetype is wrong, mark as not found
513
				// and continue
514
				if (wildcard_filetype == WILDCARD_DIR && !S_ISDIR(st.st_mode)) {
515
					free(ret);
516
					ret = NULL;
517
					found = false;
518
					continue;
519
				} else if (wildcard_filetype == WILDCARD_PRG && !S_ISREG(st.st_mode)) {
520
					free(ret);
521
					ret = NULL;
522
					found = false;
523
					continue;
524
				}
525
			}
526
			break;
527
		}
528
	}
529
	closedir(dirp);
530
531
	free(tmp);
532
533
	if (ret) { // We had a match
534
		free(tmp2);
535
		tmp = ret;
536
	} else if (has_wildcard_chars) {
537
		// our original query had * or ? and we didn't find it
538
		free(tmp2);
539
		set_error(0x62, 0, 0);
540
		return NULL;
541
	} else { // reset tmp to the original name
542
		tmp = tmp2;
543
	}
544
545
	// now resolve the path using OS routines
546
	ret = u8realpath(tmp, NULL);
547
	free(tmp);
548
549
	if (ret == NULL) {
550
		if (must_exist) {
551
			// path wasn't found or had another error in construction
552
			set_error(0x62, 0, 0);
553
		} else {
554
			// path does not exist, but as long as everything but the final
555
			// path element exists, we're still okay.
556
			tmp = malloc(u8strlen(name)+1);
557
			if (tmp == NULL) {
558
				set_error(0x70, 0, 0);
559
				return NULL;
560
			}
561
			u8strcpy(tmp, name);
562
			c = u8strrchr(tmp, '/');
563
			d = u8strrchr(tmp, '\\');
564
			if (c > d) {
565
				*c = 0;
566
			} else if (d != NULL) {
567
				*d = 0;
568
				c = d;
569
			}
570
571
			// assemble a path with what we have left
572
			ret = malloc(u8strlen(name)+u8strlen(hostfscwd)+2);
573
			if (ret == NULL) {
574
				free(tmp);
575
				set_error(0x70, 0, 0);
576
				return NULL;
577
			}
578
579
			if (name[0] == '/' || name[0] == '\\') { // absolute
580
				u8strcpy(ret, fsroot_path);
581
				if (c == tmp) { // leading slash was the only slash
582
					*tmp = name[0];
583
					c = NULL;
584
				}
585
				u8strcpy(ret+u8strlen(fsroot_path), tmp);
586
			} else { // relative
587
				u8strcpy(ret, hostfscwd);
588
				*(ret+u8strlen(hostfscwd)) = '/';
589
				u8strcpy(ret+u8strlen(hostfscwd)+1, tmp);
590
			}
591
592
			free(tmp);
593
594
			// if we found a path separator in the name string (non-leading)
595
			// we check everything up to that final separator
596
			if (c != NULL) {
597
				tmp = u8realpath(ret, NULL);
598
				free(ret);
599
				if (tmp == NULL) {
600
					// missing parent path element too
601
					set_error(0x62, 0, 0);
602
					ret = NULL;
603
				} else {
604
					free(tmp);
605
					// found everything up to the parent path element
606
					// restore ret to original case
607
					ret = malloc(u8strlen(name)+u8strlen(hostfscwd)+2);
608
					if (ret == NULL) {
609
						set_error(0x70, 0, 0);
610
						return NULL;
611
					}
612
					u8strcpy(ret, hostfscwd);
613
					ret[u8strlen(hostfscwd)] = '/';
614
					u8strcpy(ret+u8strlen(hostfscwd)+1, name);
615
				}
616
			}
617
		}
618
	} else {
619
		path_exists = true;
620
	}
621
622
	if (ret == NULL)
623
		return ret;
624
625
	// Prevent resolving outside the fsroot_path
626
	if (u8strlen(fsroot_path) > u8strlen(ret)) {
627
		free(ret);
628
		set_error(0x62, 0, 0);
629
		return NULL;
630
	} else if (u8strncmp(fsroot_path, ret, u8strlen(fsroot_path))) {
631
		free(ret);
632
		set_error(0x62, 0, 0);
633
		return NULL;
634
	} else if (u8strlen(fsroot_path) < u8strlen(ret) &&
635
	           fsroot_path[u8strlen(fsroot_path)-1] != '/' &&
636
			   fsroot_path[u8strlen(fsroot_path)-1] != '\\' &&
637
	           ret[u8strlen(fsroot_path)] != '/' &&
638
	           ret[u8strlen(fsroot_path)] != '\\')
639
	{
640
		// ret matches beginning of fsroot_path,
641
		// end of fsroot_path is not a path-separator,
642
		// and next char in ret is not a path-separator
643
		// This could happen if
644
		//   fsroot_path == "/home/user/ba" and
645
		//   ret == "/home/user/bah".
646
		// This condition should be considered a jailbreak and we fail out
647
		free(ret);
648
		set_error(0x62, 0, 0);
649
		return NULL;
650
	}
651
652
	return ret;
653
}
654
655
656
static uint8_t *
657
resolve_path_iso(const uint8_t *name, bool must_exist, int wildcard_filetype)
658
{
659
	uint8_t *ret;
660
	uint8_t *buf;
661
	int i, j;
662
	uint32_t cp;
663
664
	buf = malloc(u8strlen(name)*3+1);
665
666
	for (i = 0, j = 0; name[i]; i++) {
667
		cp = unicode_from_iso8859_15(name[i]);
668
		j += utf8_encode((char *)buf+j, cp);
669
	}
670
671
	ret = resolve_path_utf8(buf, must_exist, wildcard_filetype);
672
	free(buf);
673
	return ret;
674
}
675
676
static int
677
create_directory_listing(uint8_t *data, uint8_t *dirstring)
678
{
679
	uint8_t *data_start = data;
680
681
	dirlist_eof = true;
682
	dirlist_cwd = false;
683
	int i = 0;
684
	int j;
685
686
	
687
	dirlist_timestmaps = false;
688
	dirlist_long = false;
689
	dirlist_type_filter = 0;
690
	dirlist_wildcard[0] = 0;
691
692
	// Here's where we parse out directory listing options
693
	// Such as "$=T:MATCH*=P"
694
695
	while (i < u8strlen(dirstring)) {
696
		if (dirstring[i] == ':' || i == 0) {
697
			i++;
698
			j = 0;
699
			while (i < u8strlen(dirstring)) {
700
				if (i == 1 && dirstring[i] == ':')
701
					i++;
702
				if (dirstring[i] == '=' || dirstring[i] == 0) {
703
					if (dirstring[++i] == 'D')
704
						dirlist_type_filter = 'D';
705
					else if (dirstring[i] == 'P' && i != 2)
706
						dirlist_type_filter = 'P';
707
					else if (dirstring[i] == 'T' && i == 2)
708
						dirlist_timestmaps = true;
709
					else if (dirstring[i] == 'L' && i == 2) {
710
						dirlist_timestmaps = true;
711
						dirlist_long = true;
712
					}
713
					break;
714
				} else {
715
					dirlist_wildcard[j++] = dirstring[i++];
716
					dirlist_wildcard[j] = 0;
717
				}
718
			}
719
		}
720
		i++;
721
	}
722
723
	// load address
724
	*data++ = 1;
725
	*data++ = 8;
726
	// link
727
	*data++ = 1;
728
	*data++ = 1;
729
	// line number
730
	*data++ = 0;
731
	*data++ = 0;
732
	*data++ = 0x12; // REVERSE ON
733
	*data++ = '"';
734
	for (int i = 0; i < 16; i++) {
735
		*data++ = ' ';
736
	}
737
	if (cgetcwd(data - 16, 16)) {
738
		return false;
739
	}
740
	*data++ = '"';
741
	*data++ = ' ';
742
	*data++ = 'H';
743
	*data++ = 'O';
744
	*data++ = 'S';
745
	*data++ = 'T';
746
	*data++ = ' ';
747
	*data++ = 0;
748
749
	if (!(dirlist_dirp = opendir((char *)hostfscwd))) {
750
		return 0;
751
	}
752
	dirlist_eof = false;
753
	return data - data_start;
754
}
755
756
static int
757
continue_directory_listing(uint8_t *data)
758
{
759
	uint8_t *data_start = data;
760
	struct stat st;
761
	struct dirent *dp;
762
	size_t file_size;
763
	uint8_t *tmpnam;
764
	bool found;
765
	int i;
766
767
	while ((dp = readdir(dirlist_dirp))) {
768
		// Type match
769
		if (dirlist_type_filter) {
770
			// Because resolving the path within the hostfs
771
			// logic is expensive, and could keep us here for multiple seconds
772
			// when directories are very large, it is much faster and gets us
773
			// past the negative matches of the filter ASAP if we do a quick
774
			// stat().
775
			//
776
			// The later logic can validate if the path resolves to something
777
			// within the hostfs root or not.
778
			size_t nl = u8strlen(dp->d_name);
779
			size_t pl = u8strlen(hostfscwd);
780
			uint8_t *tn = malloc(nl+pl+3);
781
			u8strcpy(tn, hostfscwd);
782
			*(tn+pl) = '/';
783
			u8strcpy(tn+pl+1, dp->d_name);
784
			u8stat(tn, &st);
785
			free(tn);
786
787
			switch (dirlist_type_filter) {
788
				case 'D':
789
					if (!S_ISDIR(st.st_mode))
790
						continue;
791
					break;
792
				case 'P':
793
					if (!S_ISREG(st.st_mode))
794
						continue;
795
					break;
796
			}
797
		}
798
799
		size_t namlen = u8strlen(dp->d_name);
800
		tmpnam = resolve_path_utf8((uint8_t *)dp->d_name, true, WILDCARD_ALL);
801
		if (tmpnam == NULL) continue;
802
		u8stat(tmpnam, &st);
803
		free(tmpnam);
804
805
		// don't show the . or .. in the root directory
806
		// this behaves like SD card/FAT32
807
		if (!u8strcmp("..",dp->d_name) || !u8strcmp(".",dp->d_name)) {
808
			if (!u8strcmp(hostfscwd,fsroot_path)) {
809
				continue;
810
			}
811
		}
812
813
		tmpnam = malloc(namlen+1);
814
		utf8_to_iso_string(tmpnam, (uint8_t *)dp->d_name);
815
816
		if (dirlist_wildcard[0]) { // wildcard match selected
817
			// in a wildcard match that starts at first position, leading dot filenames are not considered
818
			if ((dirlist_wildcard[0] == '*' || dirlist_wildcard[0] == '?') && *(tmpnam) == '.')
819
				continue;
820
821
			found = false;
822
			for (i = 0; i < u8strlen(dirlist_wildcard) && i < u8strlen(tmpnam); i++) {
823
				if (dirlist_wildcard[i] == '*') {
824
					found = true;
825
					break;
826
				} else if (dirlist_wildcard[i] == '?') {
827
					continue;
828
				} else if (case_fold_iso(dirlist_wildcard[i]) != case_fold_iso((tmpnam)[i])) {
829
					break;
830
				}
831
			}
832
833
			// If we reach the end of both strings, it's a match
834
			if (i == u8strlen(tmpnam) && i == u8strlen(dirlist_wildcard))
835
				found = true;
836
837
			// If we reach the end of the filename and the following character in
838
			// the wildcard string is *, it's also a match
839
			else if (i == u8strlen(tmpnam) && dirlist_wildcard[i] == '*')
840
				found = true;
841
842
			if (!found) continue;
843
		}
844
845
		// link
846
		*data++ = 1;
847
		*data++ = 1;
848
849
		if (dirlist_long) {
850
			unsigned char unit = 'K';
851
			file_size = (st.st_size + 1023)/1024;
852
			if (file_size > 0xFFFF) {
853
				file_size /= 1024;
854
				unit = 'M';
855
			}
856
			if (file_size > 0xFFFF) {
857
				file_size /= 1024;
858
				unit = 'G';
859
			}
860
			if (file_size > 0xFFFF) {
861
				file_size = 0xFFFF;
862
			}
863
864
			*data++ = file_size & 0xFF;
865
			*data++ = file_size >> 8;
866
			*data++ = unit;
867
			*data++ = 'B';
868
		} else {
869
			file_size = (st.st_size + 255)/256;
870
			if (file_size > 0xFFFF) {
871
				file_size = 0xFFFF;
872
			}
873
			*data++ = file_size & 0xFF;
874
			*data++ = file_size >> 8;
875
		}
876
877
		if (file_size < 1000) {
878
			*data++ = ' ';
879
			if (file_size < 100) {
880
				*data++ = ' ';
881
				if (file_size < 10) {
882
					*data++ = ' ';
883
				}
884
			}
885
		}
886
		*data++ = '"';
887
		//if (namlen > 16) {
888
		//	namlen = 16; // TODO hack
889
		//}
890
		namlen = u8strlen(tmpnam);
891
		memcpy(data, tmpnam, namlen);
892
		data += namlen;
893
		*data++ = '"';
894
		for (int i = namlen; i < 16; i++) {
895
			*data++ = ' ';
896
		}
897
		*data++ = ' ';
898
		if (S_ISDIR(st.st_mode)) {
899
			*data++ = 'D';
900
			*data++ = 'I';
901
			*data++ = 'R';
902
		} else {
903
			*data++ = 'P';
904
			*data++ = 'R';
905
			*data++ = 'G';
906
		}
907
		// This would be a '<' if file were protected, but it's a space instead
908
		*data++ = ' ';
909
910
		if (dirlist_timestmaps) {
911
			// ISO-8601 date+time
912
			struct tm mtime;
913
			if (localtime_r(&st.st_mtime, &mtime)) {
914
				*data++ = ' '; // space before the date
915
				data += strftime((char *)data, 20, "%Y-%m-%d %H:%M:%S", &mtime);
916
				*data++ = ' '; // space after the date
917
			}
918
		}
919
920
		if (dirlist_long) {
921
			// attribute and size go at the end if dirlist_long is active
922
			uint8_t attrbyte = 0;
923
			if (S_ISDIR(st.st_mode)) attrbyte |= 0x10;
924
			if (S_ISREG(st.st_mode)) attrbyte |= 0x20;
925
			if (!(st.st_mode & 0200)) attrbyte |= 0x01; // read-only
926
			size_t fullsize = st.st_size;
927
			if (fullsize > 0xffffffff) {
928
				fullsize = 0xffffffff;
929
			}
930
931
			data += sprintf((char *)data, "%02X %08X ", attrbyte, (unsigned int)fullsize);
932
		}
933
934
		free(tmpnam);
935
936
		*data++ = 0;
937
		return data - data_start;
938
	}
939
940
	// link
941
	*data++ = 1;
942
	*data++ = 1;
943
944
	*data++ = 255; // "65535"
945
	*data++ = 255;
946
947
	memcpy(data, blocks_free, u8strlen(blocks_free));
948
	data += u8strlen(blocks_free);
949
	*data++ = 0;
950
951
	// link
952
	*data++ = 0;
953
	*data++ = 0;
954
	(void)closedir(dirlist_dirp);
955
	dirlist_eof = true;
956
	return data - data_start;
957
}
958
959
960
static int
961
create_cwd_listing(uint8_t *data)
962
{
963
	uint8_t *data_start = data;
964
	int file_size;
965
966
	// load address
967
	*data++ = 1;
968
	*data++ = 8;
969
	// link
970
	*data++ = 1;
971
	*data++ = 1;
972
	// line number
973
	*data++ = 0;
974
	*data++ = 0;
975
	*data++ = 0x12; // REVERSE ON
976
	*data++ = '"';
977
	for (int i = 0; i < 16; i++) {
978
		*data++ = ' ';
979
	}
980
	if (cgetcwd(data - 16, 16)) {
981
		dirlist_eof = true;
982
		return 0;
983
	}
984
	*data++ = '"';
985
	*data++ = ' ';
986
	*data++ = 'H';
987
	*data++ = 'O';
988
	*data++ = 'S';
989
	*data++ = 'T';
990
	*data++ = ' ';
991
	*data++ = 0;
992
993
	uint8_t *tmp = malloc(u8strlen(hostfscwd)+1);
994
	if (tmp == NULL) {
995
		set_error(0x70, 0, 0);
996
		return 0;
997
	}
998
	int i = u8strlen(hostfscwd);
999
	int j = u8strlen(fsroot_path);
1000
	u8strcpy(tmp,hostfscwd);
1001
1002
	for(; i>= j-1; --i) {
1003
		// find the beginning of a path element
1004
		if (i >= j && tmp[i-1] != '/' && tmp[i-1] != '\\')
1005
			continue;
1006
		
1007
		tmp[i-1]=0;
1008
1009
		if (i < j) {
1010
			u8strcpy(tmp+i,"/");
1011
		}
1012
1013
		file_size = 0;
1014
		size_t namlen = u8strlen(tmp+i);
1015
1016
		if (!namlen) continue; // there was a doubled path separator
1017
1018
		// link
1019
		*data++ = 1;
1020
		*data++ = 1;
1021
1022
		*data++ = file_size & 0xFF;
1023
		*data++ = file_size >> 8;
1024
		if (file_size < 1000) {
1025
			*data++ = ' ';
1026
			if (file_size < 100) {
1027
				*data++ = ' ';
1028
				if (file_size < 10) {
1029
					*data++ = ' ';
1030
				}
1031
			}
1032
		}
1033
		*data++ = '"';
1034
1035
		memcpy(data, tmp+i, namlen);
1036
		data += namlen;
1037
		*data++ = '"';
1038
		for (int i = namlen; i < 16; i++) {
1039
			*data++ = ' ';
1040
		}
1041
		*data++ = ' ';
1042
		*data++ = 'D';
1043
		*data++ = 'I';
1044
		*data++ = 'R';
1045
		*data++ = 0;
1046
	}
1047
1048
	free(tmp);
1049
1050
	// link
1051
	*data++ = 1;
1052
	*data++ = 1;
1053
1054
	*data++ = 255; // "65535"
1055
	*data++ = 255;
1056
1057
	memcpy(data, blocks_free, u8strlen(blocks_free));
1058
	data += u8strlen(blocks_free);
1059
	*data++ = 0;
1060
1061
	// link
1062
	*data++ = 0;
1063
	*data++ = 0;
1064
1065
	dirlist_eof = true;
1066
	dirlist_cwd = true;
1067
1068
	return data - data_start;
1069
}
1070
1071
1072
1073
static const char*
1074
error_string(int e)
1075
{
1076
	switch(e) {
1077
		case 0x00:
1078
			return " OK";
1079
		case 0x01:
1080
			return " FILES SCRATCHED";
1081
		case 0x02:
1082
			return "PARTITION SELECTED";
1083
		// 0x2x: Physical disk error
1084
		case 0x20:
1085
			return "READ ERROR"; // generic read error
1086
		case 0x25:
1087
			return "WRITE ERROR"; // generic write error
1088
		case 0x26:
1089
			return "WRITE PROTECT ON";
1090
		// 0x3x: Error parsing the command
1091
		case 0x30: // generic
1092
		case 0x31: // invalid command
1093
		case 0x32: // command buffer overflow
1094
			return "SYNTAX ERROR";
1095
		case 0x33: // illegal filename
1096
			return "ILLEGAL FILENAME";
1097
		case 0x34: // empty file name
1098
			return "EMPTY FILENAME";
1099
		case 0x39: // subdirectory not found
1100
			return "SUBDIRECTORY NOT FOUND";
1101
		// 0x4x: Controller error (CMD addition)
1102
		case 0x49:
1103
			return "INVALID FORMAT"; // partition present, but not FAT32
1104
		// 0x5x: Relative file related error
1105
		// unsupported
1106
		// 0x6x: File error
1107
		case 0x62:
1108
			return " FILE NOT FOUND";
1109
		case 0x63:
1110
			return "FILE EXISTS";
1111
		// 0x7x: Generic disk or device error
1112
		case 0x70:
1113
			return "NO CHANNEL"; // error allocating context
1114
		case 0x71:
1115
			return "DIRECTORY ERROR"; // FAT error
1116
		case 0x72:
1117
			return "PARTITION FULL"; // filesystem full
1118
		case 0x73:
1119
			return "HOST FS V1.0 X16";
1120
		case 0x74:
1121
			return "DRIVE NOT READY"; // illegal partition for any command but "CP"
1122
		case 0x75:
1123
			return "FORMAT ERROR";
1124
		case 0x77:
1125
			return "SELECTED PARTITION ILLEGAL";
1126
		default:
1127
			return "";
1128
	}
1129
}
1130
1131
static void
1132
set_activity(bool active)
1133
{
1134
	uint8_t cbdos_flags = get_kernal_cbdos_flags();
1135
	if (active) {
1136
		cbdos_flags |= 0x10; // set activity flag
1137
	} else {
1138
		cbdos_flags &= ~0x10; // clear activity flag
1139
	}
1140
	set_kernal_cbdos_flags(cbdos_flags);
1141
}
1142
1143
static void
1144
set_error_text(int e, const char *text, int t, int s)
1145
{
1146
	snprintf((char *)error, sizeof(error), "%02x,%s,%02d,%02d\r", e, text, t, s);
1147
	error_len = u8strlen(error);
1148
	error_pos = 0;
1149
	uint8_t cbdos_flags = get_kernal_cbdos_flags();
1150
	if (e < 0x10 || e == 0x73) {
1151
		cbdos_flags &= ~0x20; // clear error
1152
	} else {
1153
		cbdos_flags |= 0x20; // set error flag
1154
	}
1155
	set_kernal_cbdos_flags(cbdos_flags);
1156
}
1157
1158
static void
1159
set_error(int e, int t, int s)
1160
{
1161
	set_error_text(e, error_string(e), t, s);
1162
}
1163
1164
static void
1165
clear_error()
1166
{
1167
	set_error(0, 0, 0);
1168
}
1169
1170
1171
static void
1172
command(uint8_t *cmd)
1173
{
1174
	if (!cmd[0]) {
1175
		return;
1176
	}
1177
	if (log_ieee) {
1178
		if (cmd[0] == 'P') {
1179
			printf("  COMMAND \"%c\" [binary parameters suppressed]\n", cmd[0]);
1180
		} else {
1181
			printf("  COMMAND \"%s\"\n", cmd);
1182
		}
1183
	}
1184
	switch(cmd[0]) {
1185
		case 'C': // C (copy), CD (change directory), CP (change partition)
1186
			switch(cmd[1]) {
1187
				case 'D': // Change directory
1188
					cchdir(cmd+2);
1189
					return;
1190
				case 'P': // Change partition
1191
					set_error(0x02, 0, 0);
1192
					return;
1193
				default: // Copy
1194
					// NYI
1195
					set_error(0x30, 0, 0);
1196
					return;
1197
			}
1198
		case 'I': // Initialize
1199
			clear_error();
1200
			return;
1201
		case 'M': // MD
1202
			switch(cmd[1]) {
1203
				case 'D': // Make directory
1204
					cmkdir(cmd+2);
1205
					return;
1206
				default: // Memory (not implemented)
1207
					set_error(0x31, 0, 0);
1208
					return;
1209
			}
1210
		case 'P': // Seek
1211
			cseek(cmd[1],
1212
				((uint8_t)cmd[2])
1213
				| ((uint8_t)cmd[3] << 8)
1214
				| ((uint8_t)cmd[4] << 16)
1215
				| ((uint8_t)cmd[5] << 24));
1216
			return;
1217
		case 'R': // RD
1218
			switch(cmd[1]) {
1219
				case 'D': // Remove directory
1220
					crmdir(cmd+2);
1221
					return;
1222
				default: // Rename 
1223
					crename(cmd); // Need to parse out the arg in this function
1224
					return;
1225
			}
1226
		case 'S':
1227
			switch(cmd[1]) {
1228
				case '-': // Swap
1229
					if (cmd[2] == '8' || cmd[2] == 'D') {
1230
						ieee_unit = 8;
1231
						clear_error();
1232
					} else if (cmd[2] == '9') {
1233
						ieee_unit = 9;
1234
						clear_error();
1235
					} else {
1236
						set_error(0x31, 0, 0);
1237
					}
1238
					return;
1239
				default: // Scratch
1240
					cunlink(cmd); // Need to parse out the arg in this function
1241
					return;
1242
			}	
1243
		case 'T': // Tell
1244
			ctell(cmd[1]);
1245
			return;
1246
		case 'U':
1247
			switch(cmd[1]) {
1248
				case 'I': // UI: Reset
1249
					set_error(0x73, 0, 0);
1250
					return;
1251
				case '0': // U0
1252
					if (cmd[2] == '>') {
1253
						if (cmd[3] >= 8 && cmd[3] <= 15) {
1254
							ieee_unit = cmd[3];
1255
							clear_error();
1256
							return;
1257
						}
1258
					}
1259
			}
1260
1261
		default:
1262
			if (log_ieee) {
1263
				printf("    (unsupported command ignored)\n");
1264
			}
1265
	}
1266
	set_error(0x30, 0, 0);
1267
}
1268
1269
static void
1270
cchdir(uint8_t *dir)
1271
{
1272
	// The directory name is in dir, coming from the command channel
1273
	// with the CD portion stripped off
1274
	uint8_t *parsed;
1275
	uint8_t *resolved;
1276
	struct stat st;
1277
1278
	if (!u8strcmp(dir,":_")) { // turn CD:← into CD:..
1279
		parsed = parse_dos_filename((uint8_t *)":..", true);
1280
	} else {
1281
		parsed = parse_dos_filename(dir, true);
1282
	}
1283
1284
	if (parsed == NULL) {
1285
		set_error(0x32, 0, 0);
1286
		return;
1287
	}
1288
	
1289
	if ((resolved = resolve_path_iso(parsed, true, WILDCARD_DIR)) == NULL) {
1290
		free(parsed);
1291
		// error already set
1292
		return;
1293
	}
1294
1295
	free(parsed);
1296
1297
	// Is it a directory?
1298
	if (u8stat(resolved, &st)) {
1299
		// FNF
1300
		free(resolved);
1301
		set_error(0x62, 0, 0);
1302
	} else if (!S_ISDIR(st.st_mode)) {
1303
		// Not a directory
1304
		free(resolved);
1305
		set_error(0x39, 0, 0);
1306
	} else {
1307
		// cwd has been changed
1308
		free(hostfscwd);
1309
		hostfscwd = resolved;
1310
	}
1311
1312
	return;
1313
}
1314
1315
static void
1316
cmkdir(uint8_t *dir)
1317
{
1318
	// The directory name is in dir, coming from the command channel
1319
	// with the MD portion stripped off
1320
	uint8_t *resolved;
1321
	uint8_t *parsed;
1322
1323
	parsed = parse_dos_filename(dir, true);
1324
1325
	if (parsed == NULL) {
1326
		set_error(0x32, 0, 0);
1327
		return;
1328
	}
1329
1330
	clear_error();
1331
	if ((resolved = resolve_path_iso(parsed, false, WILDCARD_DIR)) == NULL) {
1332
		free(parsed);
1333
		// error already set
1334
		return;
1335
	}
1336
1337
	free(parsed);
1338
#ifdef __MINGW32__
1339
	if (_mkdir((char *)resolved))
1340
#else
1341
	if (mkdir((char *)resolved,0777))
1342
#endif
1343
	{
1344
		if (errno == EEXIST) {
1345
			set_error(0x63, 0, 0);
1346
		} else {
1347
			set_error(0x62, 0, 0);
1348
		}
1349
	}
1350
1351
	free(resolved);
1352
1353
	return;
1354
}
1355
1356
static void
1357
crename(uint8_t *f)
1358
{
1359
	// This function receives the whole R command, which could be
1360
	// "R:NEW=OLD" or "RENAME:NEW=OLD" or anything in between
1361
	// let's simply find the first colon and chop it there
1362
	uint8_t *tmp = malloc(u8strlen(f)+1);
1363
	if (tmp == NULL) {
1364
		set_error(0x70, 0, 0);
1365
		return;
1366
	}
1367
	u8strcpy(tmp,f);
1368
	uint8_t *d = u8strchr(tmp,':');
1369
1370
	if (d == NULL) {
1371
		// No colon, not a valid rename command
1372
		free(tmp);
1373
		set_error(0x34, 0, 0);
1374
		return;
1375
	}
1376
1377
	d++; // one character after the colon
1378
1379
	// Now split on the = sign to find
1380
	uint8_t *s = u8strchr(d,'=');
1381
1382
	if (s == NULL) {
1383
		// No equals sign, not a valid rename command
1384
		free(tmp);
1385
		set_error(0x34, 0, 0);
1386
		return;
1387
	}
1388
1389
	*(s++) = 0; // null-terminate d and advance s
1390
	
1391
	uint8_t *src;
1392
	uint8_t *dst;
1393
1394
	clear_error();
1395
	if ((src = resolve_path_iso(s, true, WILDCARD_ALL)) == NULL) {
1396
		// source not found
1397
		free(tmp);
1398
		set_error(0x62, 0, 0);
1399
		return;
1400
	}
1401
1402
	if ((dst = resolve_path_iso(d, false, WILDCARD_ALL)) == NULL) {
1403
		// dest not found
1404
		free(tmp);
1405
		free(src);
1406
		set_error(0x39, 0, 0);
1407
		return;
1408
	}
1409
1410
	free(tmp); // we're now done with d and s (part of tmp)
1411
1412
	if (rename((char *)src, (char *)dst)) {
1413
		if (errno == EACCES) {
1414
			set_error(0x63, 0, 0);
1415
		} else if (errno == EINVAL) {
1416
			set_error(0x33, 0, 0);
1417
		} else {
1418
			set_error(0x62, 0, 0);
1419
		}
1420
	}
1421
1422
	free(src);
1423
	free(dst);
1424
1425
	return;
1426
}
1427
1428
1429
static void
1430
crmdir(uint8_t *dir)
1431
{
1432
	// The directory name is in dir, coming from the command channel
1433
	// with the RD: portion stripped off
1434
	uint8_t *resolved;
1435
	uint8_t *parsed;
1436
1437
	parsed = parse_dos_filename(dir, true);
1438
1439
	if (parsed == NULL) {
1440
		set_error(0x32, 0, 0);
1441
		return;
1442
	}
1443
1444
	clear_error();
1445
	if ((resolved = resolve_path_iso(parsed, true, WILDCARD_DIR)) == NULL) {
1446
		free(parsed);
1447
		set_error(0x39, 0, 0);
1448
		return;
1449
	}
1450
1451
	free(parsed);
1452
1453
	if (rmdir((char *)resolved)) {
1454
		if (errno == ENOTEMPTY || errno == EACCES) {
1455
			set_error(0x63, 0, 0);
1456
		} else {
1457
			set_error(0x62, 0, 0);
1458
		}
1459
	}
1460
1461
	free(resolved);
1462
1463
	return;
1464
}
1465
1466
1467
static void
1468
cunlink(uint8_t *f)
1469
{
1470
	// This function receives the whole S command, which could be
1471
	// "S:FILENAME" or "SCRATCH:FILENAME" or anything in between
1472
	// let's simply find the first colon and chop it there
1473
	// TODO path syntax and multiple files
1474
	uint8_t *tmp = malloc(u8strlen(f)+1);
1475
	if (tmp == NULL) {
1476
		set_error(0x70, 0, 0);
1477
		return;
1478
	}
1479
	u8strcpy(tmp,f);
1480
	uint8_t *fn = u8strchr(tmp,':');
1481
1482
	if (fn == NULL) {
1483
		// No colon, not a valid scratch command
1484
		free(tmp);
1485
		set_error(0x34, 0, 0);
1486
		return;
1487
	}
1488
1489
	fn++; // one character after the colon
1490
	uint8_t *resolved;
1491
1492
	clear_error();
1493
	if ((resolved = resolve_path_iso(fn, true, WILDCARD_PRG)) == NULL) {
1494
		free(tmp);
1495
		set_error(0x62, 0, 0);
1496
		return;
1497
	}
1498
1499
	free(tmp); // we're now done with fn (part of tmp)
1500
1501
	if (unlink((char *)resolved)) {
1502
		if (errno == EACCES) {
1503
			set_error(0x63, 0, 0);
1504
		} else {
1505
			set_error(0x62, 0, 0);
1506
		}
1507
	} else {
1508
		set_error(0x01, 0, 0); // 1 file scratched
1509
	}
1510
1511
	free(resolved);
1512
1513
	return;
1514
}
1515
1516
1517
static int
1518
copen(int channel)
1519
{
1520
	if (channel == 15) {
1521
		command(channels[channel].name);
1522
		return -1;
1523
	}
1524
1525
	uint8_t *resolved_filename = NULL;
1526
	uint8_t *parsed_filename = NULL;
1527
	int ret = -1;
1528
1529
	// decode ",P,W"-like suffix to know whether we're writing
1530
	bool append = false;
1531
	channels[channel].read = true;
1532
	channels[channel].write = false;
1533
	uint8_t *first = u8strchr(channels[channel].name, ',');
1534
	if (first) {
1535
		*first = 0; // truncate name here
1536
		uint8_t *second = u8strchr(first+1, ',');
1537
		if (second) {
1538
			switch(second[1]) {
1539
				case 'A':
1540
					append = true;
1541
					// fallthrough
1542
				case 'W':
1543
					channels[channel].read = false;
1544
					channels[channel].write = true;
1545
					break;
1546
				case 'M':
1547
					channels[channel].read = true;
1548
					channels[channel].write = true;
1549
					break;
1550
			}
1551
		}
1552
	}
1553
	if (channel <= 1) {
1554
		// channels 0 and 1 are magic
1555
		channels[channel].write = channel;
1556
		channels[channel].read = !channel;
1557
	}
1558
	if (log_ieee) {
1559
		printf("  OPEN \"%s\",%d (%s%s)\n", channels[channel].name, channel,
1560
			channels[channel].read ? "R" : "",
1561
			channels[channel].write ? "W" : "");
1562
	}
1563
	
1564
	if (channels[channel].name[0] == 0) { // empty filename
1565
		set_error(0x34, 0, 0);
1566
		return -2;
1567
	}
1568
1569
	if (!channels[channel].write && channels[channel].name[0] == '$') {	
1570
		dirlist_pos = 0;
1571
		if (!u8strncmp(channels[channel].name,"$=C",3)) {
1572
			// This emulates the behavior in the ROM code in
1573
			// https://github.com/X16Community/x16-rom/pull/5
1574
			dirlist_len = create_cwd_listing(dirlist);
1575
		} else {
1576
			dirlist_len = create_directory_listing(dirlist, channels[channel].name);
1577
		}
1578
	} else {
1579
		if (!u8strcmp(channels[channel].name, ":*") && prg_file && !prg_consumed) {
1580
			channels[channel].f = prg_file; // special case
1581
			prg_consumed = true;
1582
		} else {
1583
			if ((parsed_filename = parse_dos_filename(channels[channel].name, false)) == NULL) {
1584
				set_error(0x32, 0, 0); // Didn't parse out properly
1585
				return -2; 
1586
			}
1587
1588
			resolved_filename = resolve_path_iso(parsed_filename, false, WILDCARD_PRG);
1589
			free(parsed_filename);
1590
1591
			if (resolved_filename == NULL) {
1592
				// Resolve the path, if we get a null ptr back, error is already set.
1593
				return -2;
1594
			}
1595
1596
			if (path_exists && !overwrite && !append && !channels[channel].read) {
1597
				free(resolved_filename);
1598
				set_error(0x63, 0, 0); // forbid overwrite unless requested
1599
				return -1;
1600
			}
1601
		
1602
			if (append) {
1603
				channels[channel].f = SDL_RWFromFile((char *)resolved_filename, "ab+");
1604
			} else if (channels[channel].read && channels[channel].write) {
1605
				if (access((char *)resolved_filename, F_OK) == 0) {
1606
					channels[channel].f = SDL_RWFromFile((char *)resolved_filename, "rb+");
1607
				} else {
1608
					channels[channel].f = SDL_RWFromFile((char *)resolved_filename, "wb+");
1609
				}
1610
			} else {
1611
				channels[channel].f = SDL_RWFromFile((char *)resolved_filename, channels[channel].write ? "wb" : "rb");
1612
			}
1613
1614
			free(resolved_filename);
1615
		}
1616
1617
		if (!channels[channel].f) {
1618
			if (log_ieee) {
1619
				printf("  FILE NOT FOUND\n");
1620
			}
1621
			set_error(0x62, 0, 0);
1622
			ret = -2; // FNF
1623
		} else {
1624
			clear_error();
1625
		}
1626
	}
1627
	return ret;
1628
}
1629
1630
static void
1631
cclose(int channel)
1632
{
1633
	if (log_ieee) {
1634
		printf("  CLOSE %d\n", channel);
1635
	}
1636
	channels[channel].name[0] = 0;
1637
	if (channels[channel].f) {
1638
		SDL_RWclose(channels[channel].f);
1639
		channels[channel].f = NULL;
1640
	}
1641
}
1642
1643
static void
1644
cseek(int channel, uint32_t pos)
1645
{
1646
	if (channel == 15) {
1647
		set_error(0x30, 0, 0);
1648
		return;
1649
	}
1650
1651
	if (channels[channel].f) {
1652
		SDL_RWseek(channels[channel].f, pos, RW_SEEK_SET);
1653
	} else {
1654
		set_error(0x70, 0, 0);
1655
	}
1656
}
1657
1658
static void
1659
ctell(int channel)
1660
{
1661
	char buf[32];
1662
1663
	if (channel == 15) {
1664
		set_error(0x30, 0, 0);
1665
		return;
1666
	}
1667
1668
	if (channels[channel].f) {
1669
		uint64_t pos = SDL_RWtell(channels[channel].f);
1670
		uint64_t siz = SDL_RWsize(channels[channel].f);
1671
		if (pos > 0xffffffffULL) {
1672
			pos = 0xffffffff;
1673
		}
1674
		if (siz > 0xffffffffULL) {
1675
			siz = 0xffffffff;
1676
		}
1677
		snprintf((char *)buf, sizeof(buf), "%08X %08X", (uint32_t)pos, (uint32_t)siz);
1678
		set_error_text(0x07, buf, 0, 0);
1679
	} else {
1680
		set_error(0x70, 0, 0);
1681
	}
1682
}
1683
1684
void
1685
ieee_init()
1686
{
1687
1688
	int ch;
1689
1690
	if (!ieee_initialized_once) {
1691
		// Init the hostfs "jail" and cwd
1692
		if (fsroot_path == NULL) { // if null, default to cwd
1693
			// We hold this for the lifetime of the program, and we don't have
1694
			// any sort of destructor, so we rely on the OS teardown to free() it.
1695
			fsroot_path = u8getcwd(NULL, 0);
1696
		} else {
1697
			// Normalize it
1698
			fsroot_path = u8realpath(fsroot_path, NULL);
1699
		}
1700
1701
		if (startin_path == NULL) {
1702
			// same as above
1703
			startin_path = u8getcwd(NULL, 0);
1704
		} else {
1705
			// Normalize it
1706
			startin_path = u8realpath(startin_path, NULL);
1707
		}
1708
		// Quick error checks
1709
		if (fsroot_path == NULL) {
1710
			fprintf(stderr, "Failed to resolve argument to -fsroot\n");
1711
			exit(1);
1712
		}
1713
1714
		if (startin_path == NULL) {
1715
			fprintf(stderr, "Failed to resolve argument to -startin\n");
1716
			exit(1);
1717
		}
1718
1719
		// Now we verify that startin_path is within fsroot_path
1720
		// In other words, if fsroot_path is a left-justified substring of startin_path
1721
1722
		// If startin_path is not reachable, we instead default to setting it
1723
		// back to fsroot_path
1724
		if (u8strncmp(fsroot_path, startin_path, u8strlen(fsroot_path))) { // not equal
1725
			free(startin_path);
1726
			startin_path = fsroot_path;
1727
		}
1728
1729
		for (ch = 0; ch < 16; ch++) {
1730
			channels[ch].f = NULL;
1731
			channels[ch].name[0] = 0;
1732
		}
1733
1734
		ieee_initialized_once = true;
1735
	} else {
1736
		for (ch = 0; ch < 16; ch++) {
1737
			cclose(ch);
1738
		}
1739
1740
		listening = false;
1741
		talking = false;
1742
		opening = false;
1743
1744
		free(hostfscwd);
1745
	}
1746
1747
	// Now initialize our emulated cwd.
1748
	hostfscwd = malloc(u8strlen(startin_path)+1);
1749
	if (hostfscwd == NULL) {
1750
		fprintf(stderr, "Failed to allocate memory for hostfscwd\n");
1751
		exit(1);
1752
	}
1753
	u8strcpy(hostfscwd, startin_path);
1754
1755
	// Locate and remember cbdos_flags variable address in KERNAL vars
1756
	{
1757
		// check JMP instruction at ACPTR API
1758
		if (debug_read6502(0xffa5, 0, 0) != 0x4c) goto fail;
1759
1760
		// get address of ACPTR routine
1761
		uint16_t kacptr = debug_read6502(0xffa6, 0, 0) | debug_read6502(0xffa7, 0, 0) << 8;
1762
		if (kacptr < 0xc000) goto fail;
1763
1764
		// first instruction is BIT cbdos_flags
1765
		if (debug_read6502(kacptr, 0, 0) != 0x2c) goto fail;
1766
1767
		// get the address of cbdos_flags
1768
		cbdos_flags = debug_read6502(kacptr+1, 0, 0) | debug_read6502(kacptr+2, 0, 0) << 8;
1769
1770
		if (cbdos_flags < 0x0200 || cbdos_flags >= 0x0400) goto fail;
1771
		goto success;
1772
fail:
1773
		printf("Unable to find KERNAL cbdos_flags\n");
1774
		cbdos_flags = 0;
1775
1776
	}
1777
success:
1778
1779
	set_error(0x73, 0, 0);
1780
}
1781
1782
int
1783
SECOND(uint8_t a)
1784
{
1785
	int ret = -1;
1786
	if (listening) {
1787
		if (log_ieee) {
1788
			printf("%s $%02x\n", __func__, a);
1789
		}
1790
		channel = a & 0xf;
1791
		opening = false;
1792
		if (channel == 15)
1793
			ret = 0;
1794
		switch(a & 0xf0) {
1795
			case 0x60:
1796
				if (log_ieee) {
1797
					printf("  WRITE %d...\n", channel);
1798
				}
1799
				break;
1800
			case 0xe0:
1801
				cclose(channel);
1802
				break;
1803
			case 0xf0:
1804
				if (log_ieee) {
1805
					printf("  OPEN %d...\n", channel);
1806
				}
1807
				opening = true;
1808
				namelen = 0;
1809
				break;
1810
		}
1811
	} else {
1812
		ret = -2;	// Not listening, do not handle.
1813
	}
1814
	return ret;
1815
}
1816
1817
int
1818
TKSA(uint8_t a)
1819
{
1820
	int ret = -1;
1821
	if (talking) {
1822
		if (log_ieee) {
1823
			printf("%s $%02x\n", __func__, a);
1824
		}
1825
		channel = a & 0xf;
1826
	} else {
1827
		ret = -2;	// Not talking, do not handle.
1828
	}
1829
	return ret;
1830
}
1831
1832
1833
int
1834
ACPTR(uint8_t *a)
1835
{
1836
	int ret = 0;
1837
	if (talking) {
1838
		if (log_ieee) {
1839
			printf("%s-> $%02x\n", __func__, *a);
1840
		}
1841
		if (channel == 15) {
1842
			*a = error[error_pos++];
1843
			if (error_pos >= error_len) {
1844
				clear_error();
1845
				ret = 0x40; // EOI
1846
			}
1847
		} else if (channels[channel].read) {
1848
			if (channels[channel].name[0] == '$') {
1849
				if (dirlist_pos < dirlist_len) {
1850
					*a = dirlist[dirlist_pos++];
1851
				} else {
1852
					*a = 0;
1853
				}
1854
				if (dirlist_pos == dirlist_len) {
1855
					if (dirlist_eof) {
1856
						ret = 0x40;
1857
					} else {
1858
						dirlist_pos = 0;
1859
						dirlist_len = continue_directory_listing(dirlist);
1860
					}
1861
				}
1862
			} else if (channels[channel].f) {
1863
				if (SDL_RWread(channels[channel].f, a, 1, 1) != 1) {
1864
					ret = 0x42;
1865
					*a = 0;
1866
				} else {
1867
					// We need to send EOI on the last byte of the file.
1868
					// We have to check every time since CMDR-DOS
1869
					// supports random access R/W mode
1870
1871
					Sint64 curpos = SDL_RWtell(channels[channel].f);
1872
					if (curpos == SDL_RWseek(channels[channel].f, 0, RW_SEEK_END)) {
1873
						ret = 0x40;
1874
						channels[channel].read = false;
1875
						cclose(channel);
1876
					} else {
1877
						SDL_RWseek(channels[channel].f, curpos, RW_SEEK_SET);
1878
					}
1879
				}
1880
			} else {
1881
				ret = 0x42;
1882
			}
1883
		} else {
1884
			ret = 0x42; // FNF
1885
		}
1886
	} else {
1887
		ret = -2;	// Not talking, do not handle.
1888
	}
1889
	return ret;
1890
}
1891
1892
int
1893
CIOUT(uint8_t a)
1894
{
1895
	int ret = 0;
1896
	if (listening) {
1897
		if (log_ieee) {
1898
			printf("%s $%02x\n", __func__, a);
1899
		}
1900
		if (opening) {
1901
			if (namelen < sizeof(channels[channel].name) - 1) {
1902
				channels[channel].name[namelen++] = a;
1903
			}
1904
		} else {
1905
			if (channel == 15) {
1906
				// P/T commands take binary parameters, so we can't terminate
1907
				// the command on CR.
1908
				if ((a == 13) && (cmd[0] != 'P' && cmd[0] != 'T')) {
1909
					cmd[cmdlen] = 0;
1910
					command(cmd);
1911
					cmdlen = 0;
1912
				} else {
1913
					if (cmdlen < sizeof(cmd) - 1) {
1914
						cmd[cmdlen++] = a;
1915
					}
1916
				}
1917
			} else if (channels[channel].write && channels[channel].f) {
1918
				if (!SDL_WriteU8(channels[channel].f, a))
1919
					ret = 0x40;
1920
			} else {
1921
				ret = 2; // FNF
1922
			}
1923
		}
1924
	} else {
1925
		ret = -2;	// Not listening, do not handle.
1926
	}
1927
	return ret;
1928
}
1929
1930
int
1931
UNTLK() {
1932
	if (talking) {
1933
		if (log_ieee) {
1934
			printf("%s\n", __func__);
1935
		}
1936
		talking = false;
1937
		set_activity(false);
1938
		return -1;
1939
	} else {
1940
		return -2;
1941
	}
1942
}
1943
1944
int
1945
UNLSN() {
1946
	if (listening) {
1947
		if (log_ieee) {
1948
			printf("%s\n", __func__);
1949
		}
1950
		listening = false;
1951
		set_activity(false);
1952
		if (opening) {
1953
			channels[channel].name[namelen] = 0; // term
1954
			opening = false;
1955
			copen(channel);
1956
		} else if (channel == 15) {
1957
			cmd[cmdlen] = 0;
1958
			command(cmd);
1959
			cmdlen = 0;
1960
		}
1961
		return -1;
1962
	} else {
1963
		return -2;
1964
	}
1965
}
1966
1967
int
1968
LISTEN(uint8_t a)
1969
{
1970
	int ret = -1;
1971
	if ((a & 0x1f) == ieee_unit) {
1972
		if (log_ieee) {
1973
			printf("%s $%02x\n", __func__, a);
1974
		}
1975
		listening = true;
1976
		set_activity(true);
1977
	} else {
1978
		ret = -2;	// Not us, do not handle.
1979
	}
1980
	return ret;
1981
}
1982
1983
int
1984
TALK(uint8_t a)
1985
{
1986
	int ret = -1;
1987
	if ((a & 0x1f) == ieee_unit) {
1988
		if (log_ieee) {
1989
			printf("%s $%02x\n", __func__, a);
1990
		}
1991
		talking = true;
1992
		set_activity(true);
1993
	} else {
1994
		ret = -2;	// Not us, do not handle.
1995
	}
1996
	return ret;
1997
}
1998
1999
int
2000
MACPTR(uint16_t addr, uint16_t *c, uint8_t stream_mode)
2001
{
2002
	if (talking) {
2003
		int ret = 0;
2004
		int count = *c ?: 256;
2005
		uint8_t ram_bank = read6502(0, 0);
2006
		int i = 0;
2007
2008
		if (channel != 15 && channels[channel].read && channels[channel].name[0] != '$' && channels[channel].f) {
2009
			// find the end of the file so we know our EOF condition ahead of time
2010
			Sint64 curpos = SDL_RWtell(channels[channel].f);
2011
			Sint64 endpos = SDL_RWseek(channels[channel].f, 0, RW_SEEK_END);
2012
			SDL_RWseek(channels[channel].f, curpos, RW_SEEK_SET);
2013
2014
			do {
2015
				uint8_t byte = 0;
2016
				if (SDL_RWread(channels[channel].f, &byte, 1, 1) != 1) {
2017
					ret = 0x42;
2018
					byte = 0;
2019
				} else {
2020
					curpos++;
2021
					if (curpos == endpos) {
2022
						ret = 0x40;
2023
						channels[channel].read = false;
2024
						cclose(channel);
2025
					}
2026
2027
					write6502(addr, 0, byte);
2028
					i++;
2029
					if (!stream_mode) {
2030
						addr++;
2031
						if (addr == 0xc000) {
2032
							addr = 0xa000;
2033
							ram_bank++;
2034
							write6502(0, 0, ram_bank);
2035
						}
2036
					}
2037
				}
2038
				if (ret > 0) {
2039
					break;
2040
				}
2041
			} while(i < count);
2042
		} else {
2043
			ret = -3; // unsupported
2044
		}
2045
		*c = i;
2046
		return ret;
2047
	} else {
2048
		return -2; // not us, do not handle
2049
	}
2050
}
2051
2052
int
2053
MCIOUT(uint16_t addr, uint16_t *c, uint8_t stream_mode)
2054
{
2055
	if (listening) {
2056
		int ret = 0;
2057
		int count = *c ?: 256;
2058
		uint8_t ram_bank = read6502(0, 0);
2059
		int i = 0;
2060
		if (channels[channel].f && channels[channel].write) {
2061
			do {
2062
				uint8_t byte;
2063
				byte = read6502(addr, 0);
2064
				i++;
2065
				if (!stream_mode) {
2066
					addr++;
2067
					if (addr == 0xc000) {
2068
						addr = 0xa000;
2069
						ram_bank++;
2070
						write6502(0, 0, ram_bank);
2071
					}
2072
				}
2073
				ret = CIOUT(byte);
2074
				if (ret) {
2075
					break;
2076
				}
2077
			} while(i < count);
2078
		} else {
2079
			ret = -3; // unsupported
2080
		}
2081
		*c = i;
2082
		return ret;
2083
	} else {
2084
		return -2; // not us, do not handle
2085
	}
2086
}
2087
2088
int
2089
XMACPTR(uint8_t stream_mode) // stream_mode is only for passing into MACPTR fallback
2090
{
2091
	if (talking) {
2092
		int ret = 0;
2093
		int count = read6502(X16_REG_R2L, 0) | (read6502(X16_REG_R2H, 0) << 8);
2094
		uint16_t destaddr = read6502(X16_REG_R0L, 0) | (read6502(X16_REG_R0H, 0) << 8);
2095
		uint8_t destbnk = read6502(X16_REG_R1L, 0);
2096
2097
		if (count == 0) {
2098
			count = 65536;
2099
		}
2100
2101
		if (destbnk == 0) {
2102
			int s;
2103
			uint16_t cnt = count;
2104
			s = MACPTR(destaddr, &cnt, stream_mode);
2105
			if (s < 0) {
2106
				return s;
2107
			}
2108
			write6502(X16_REG_R2L, 0, cnt & 0xff);
2109
			write6502(X16_REG_R2H, 0, cnt >> 8);
2110
			return s;
2111
		}
2112
2113
		int i = 0;
2114
		if (channel != 15 && channels[channel].read && channels[channel].name[0] != '$' && channels[channel].f) {
2115
			// find the end of the file so we know our EOF condition ahead of time
2116
			Sint64 curpos = SDL_RWtell(channels[channel].f);
2117
			Sint64 endpos = SDL_RWseek(channels[channel].f, 0, RW_SEEK_END);
2118
			SDL_RWseek(channels[channel].f, curpos, RW_SEEK_SET);
2119
2120
			do {
2121
				uint8_t byte = 0;
2122
				if (SDL_RWread(channels[channel].f, &byte, 1, 1) != 1) {
2123
					ret = 0x42;
2124
					byte = 0;
2125
				} else {
2126
					curpos++;
2127
					if (curpos == endpos) {
2128
						ret = 0x40;
2129
						channels[channel].read = false;
2130
						cclose(channel);
2131
					}
2132
2133
					write6502(destaddr, destbnk, byte);
2134
					i++;
2135
					destaddr++;
2136
					if (destaddr == 0) {
2137
						destbnk++;
2138
						if (destbnk == 0) {
2139
							break;
2140
						}
2141
					}
2142
				}
2143
				if (ret > 0) {
2144
					break;
2145
				}
2146
			} while(i < count);
2147
		} else {
2148
			ret = -3; // unsupported
2149
		}
2150
		write6502(X16_REG_R2L, 0, i & 0xff);
2151
		write6502(X16_REG_R2H, 0, i >> 8);
2152
		return ret;
2153
	} else {
2154
		return -2; // not us, do not handle
2155
	}
2156
}
2157
2158
int
2159
XMCIOUT(uint8_t stream_mode) // stream_mode is only for passing into MCIOUT fallback
2160
{
2161
	if (listening) {
2162
		int ret = 0;
2163
		int count = read6502(X16_REG_R2L, 0) | (read6502(X16_REG_R2H, 0) << 8);
2164
		uint16_t srcaddr = read6502(X16_REG_R0L, 0) | (read6502(X16_REG_R0H, 0) << 8);
2165
		uint8_t srcbnk = read6502(X16_REG_R1L, 0);
2166
2167
		if (count == 0) {
2168
			count = 65536;
2169
		}
2170
2171
		if (srcbnk == 0) {
2172
			int s;
2173
			uint16_t cnt = count;
2174
			s = MCIOUT(srcaddr, &cnt, stream_mode);
2175
			if (s < 0) {
2176
				return s;
2177
			}
2178
			write6502(X16_REG_R2L, 0, cnt & 0xff);
2179
			write6502(X16_REG_R2H, 0, cnt >> 8);
2180
			return s;
2181
		}
2182
2183
		int i = 0;
2184
		if (channels[channel].f && channels[channel].write) {
2185
			do {
2186
				uint8_t byte;
2187
				byte = read6502(srcaddr, srcbnk);
2188
				ret = CIOUT(byte);
2189
				i++;
2190
				srcaddr++;
2191
				if (srcaddr == 0) {
2192
					srcbnk++;
2193
					if (srcbnk == 0) {
2194
						break;
2195
					}
2196
				}
2197
				if (ret) {
2198
					break;
2199
				}
2200
			} while(i < count);
2201
		} else {
2202
			ret = -3; // unsupported
2203
		}
2204
2205
		write6502(X16_REG_R2L, 0, i & 0xff);
2206
		write6502(X16_REG_R2H, 0, i >> 8);
2207
		return ret;
2208
	} else {
2209
		return -2; // not us, do not handle
2210
	}
2211
}