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