#include <stdio.h>

#define SECSIZE 256		/* Change this to handle 1050 dbl density
				 * disks */

#if SECSIZE == 256
#define SECCODE 1
#define SECTORS 18
#else
#define SECCODE 0
#define SECTORS 26
#endif


int             ls_file();
int             cat_file();
int             dup_file();
char           *dmainit();
FILE           *
fopen(), *bopen();

struct dta {
	char            reserved[21];
	char            attr;
	int             utime;
	int             udate;
	long            len;
	char            name[13];
	char            dummy[3];
}               fdata;

int             debug = 0;
int             trace = 0;
int             drive, cmd;
int             first;
int             save[5];
char            seq[27] = {
       6, 12, 18, 5, 11, 17, 4, 10, 16, 3, 9, 15, 2, 8, 14, 1, 7, 13, 0xFF};


int             bitmask[8] = {
			    0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01};


unsigned char  *bp;
int            *seccnt;
int             tracks;
int             track_offset, sector_offset;

unsigned char   buf[1024];
unsigned char   map[1280];
unsigned char   dirbuf[1024];
char            dirname[100];

unsigned int    bpp = 0;
int             bin_flag = 0;
unsigned char   dosbuf[10240];

main(argc, argv)
	int             argc;
	char           *argv[];
{
	int             sec, ct, i;
	char           *p, *argp;


	bp = dmainit(buf);
	seccnt = (int *) &map[3];
	for (ct = 0; ct < 1280; ct++)
		map[ct] = 0;
	first = '\t';
	dirname[0] = '\0';
	drive = 0;
	track_offset = -40;
	tracks = 40;
	sector_offset = -1;
	cmd = '?';
	printf("UTIL v1.1.5\n");
	for (i = 1; i <= argc; i++) {
		argp = argv[i];
		if (*argp == '-') {
			while (*++argp != '\0') {
				switch (toupper(*argp)) {
				case 'B':
					bin_flag = 1;
					break;
				case 'R':
				case 'I':
				case 'P':
				case 'L':
				case 'W':
				case 'D':
					if (cmd == '?')
					{
						cmd = toupper(*argp);
					}
					break;
				case 'T':
					track_offset = -atoi(&argp[1]);
					tracks = (track_offset > -60) ? 40 : 80;
					argp[1] = '\0';
					break;
				case 'S':
					sector_offset = atoi(&argp[1]);
					argp[1] = '\0';
					break;
				case 'X':
					debug = 1;
					trace = 1;
					break;
				}
			}
		} else {
			if (argp[1] == ':' && argp[2] == '\0') {
				drive = toupper(argp[0]) - 'A';
				++i;
			}
			break;
		}
	}
	if (debug) {
		printf("%s\n", bin_flag ? "Binary" : "Ascii");
		printf("Tracks = %d, offset = %d, sector offset = %d\n", tracks, track_offset, sector_offset);
		printf("Cmd=%c, Drive=%c:\n", cmd, drive + 'A');
	}
	if (debug) {
		if (bp != buf)
			printf("Buffer base moved from %x to %x\n", buf, bp);
	}
	savefb(SECCODE, SECTORS, save);
	if (debug) {
		printf("SAVE=0x%04X.0x%04X.0x%04X.0x%04X.0x%04X\n", save[0], save[1], save[2], save[3], save[4]);
	}
	switch (cmd) {
	case 'W':		/* Write directories or files(from MSDOS) */
		getmap(drive);
		msdir(argc - i, &argv[i]);
		putmap(drive);
		break;
	case 'D':		/* Delete files */
		getmap(drive);
		delfiles(argc - i, &argv[i]);
		putmap(drive);
		break;
	case 'I':		/* Initialize (Format) an Atari Disk */
		fmtdisk(drive);
		break;
	case 'L':		/* List an Atari Directory */
		listdir(361, ls_file);
		break;
	case 'P':		/* Display/Print Atari (printable) files */
		listdir(361, cat_file);
		break;
	case 'R':		/* Read Atari formatted disk files(to MSDOS) */
		listdir(361, dup_file);
		break;
	default:
		syntax();
		break;
	}
	restfb(save);
}


syntax()
{
	printf("Usage: UTIL [-LPRWDIB] [-T#] [-S#] [ABCD]: [file ...]\n");
	printf("Where: -L      List directory\n");
	printf("       -P      Display text files interactivly\n");
	printf("       -R      Copy files interactivly to MSDOS\n");
	printf("       -W      Copy specified files from MSDOS\n");
	printf("       -D      Delete specifed files\n");
	printf("       -I      Initialize diskette\n");
	printf("       -B      Binary copy\n");
	printf("       -T#     Track offset\n");
	printf("       -S#     Sector offset\n");
	printf("       [ABCD]: Floppy selection, A: thru D: (optional)\n");
	printf("       file    File to copy from MSDOS (with -W) or delete (with -D)\n");
}


dump()
{
	int             ct;
	char           *p;

	for (p = bp, ct = 0; ct != 256; p += 16, ct += 16) {
		printf("%03X %02X%02X %02X%02X %02X%02X %02X%02X", ct, p[0] & 0xFF, p[1] & 0xFF, p[2] & 0xFF, p[3] & 0xFF, p[4] & 0xFF, p[5] & 0xFF,
		       p[6] & 0xFF, p[7] & 0xFF);
		printf(" %02X%02X %02X%02X %02X%02X %02X%02X\n", p[8] & 0xFF, p[9] & 0xFF, p[10] & 0xFF, p[11] & 0xFF, p[12] & 0xFF, p[13] & 0xFF,
		       p[14] & 0xFF, p[15] & 0xFF);
	}
}


listdir(sec, filefunc)
	int             sec;
	int             (*filefunc) ();
{
	int             k, j, i, *num;
	unsigned char  *p, *tp, *dp;
	char            text[30];

	printf("Files in \"%c:%s\"\n", drive + 'A', dirname);
	for (j = 0; j < 1024; sec++) {
		if (aread(sec, drive, bp)) {
			printf("Failure reading directory!  Operation aborted!\n");
			return;
		}
		if (trace) {
			dump();
		}
		for (i = 0; i < 128;)
			dirbuf[j++] = bp[i++];
	}


	if (debug) {
		printf("Directory read:\n");
	}
	for (p = dirbuf; p < dirbuf + 1024; p += 16) {
		if (*p == 0) {
			if (debug) {
				printf("End of directory at byte %d\n", p - dirbuf);
			}
			break;
		}
		if (!debug) {
			if ((*p & 0x91) != 0x00)
				continue;
		}
		for (tp = text, dp = p + 5; *dp > ' ' && dp != p + 13;)
			*tp++ = *dp++;
		if (p[13] > ' ') {
			*tp++ = '.';
			*tp++ = p[13];
			if (p[14] > ' ') {
				*tp++ = p[14];
				if (p[15] > ' ')
					*tp++ = p[15];
			}
		}
		*tp = '\0';
		num = (int *) (p + 1);
		(*filefunc) (text, num[1], num[0], *p);
	}


	if (first == '\n') {
		putchar('\n');
		first = '\t';
	}
	for (p = dirbuf; p < dirbuf + 1024; p += 16) {
		if (*p == 0)
			return;
		if ((*p & 0x91) != 0x10)
			continue;
		for (tp = text, dp = p + 5; *dp > ' ' && dp != p + 13;)
			*tp++ = *dp++;
		if (p[13] > ' ') {
			*tp++ = '.';
			*tp++ = p[13];
			if (p[14] > ' ') {
				*tp++ = p[14];
				if (p[15] > ' ')
					*tp++ = p[15];
			}
		}
		*tp = '\0';
		if (debug) {
			printf("Subdirectory :%s!\n", text);
		}
		num = (int *) (p + 1);
		k = strlen(dirname);
		dirname[k] = '\\';
		dirname[k + 1] = '\0';
		strcat(dirname, text);
		if (cmd = 'D')
			if (cd(text) != 0)
				if (md(text) != 0) {
					printf("Cannot use or create directory \"%s\"\n", text);
					return;
				}
		listdir(num[1], filefunc);
		if (cmd == 'D')
			cd("..");
		dirname[k] = '\0';
	}


}


ls_file(name, at, size, flags)
	char           *name;
	int             at, size, flags;
{
	printf("%c%c%c%-12s[%04X] %d secs%c", flags & 0x20 ? '*' : ' ', flags & 0x10 ? ':' : ' ', flags & 0x04 ? '!' : flags & 0x02 ? ' ' : '#',
	       name, at, size, first);
	first ^= '\n' ^ '\t';
}


cat_file(name, at, size, flags)
	char           *name;
	int             at, size, flags;
{
	int             x, ct;
	unsigned char  *p;

	printf("Print %s (%ld bytes, f=%02X) <YN> ?", name, size * ((long) SECSIZE - 3L), flags);
	x = toupper(getchar());
	if (x != '\n')
		while (getchar() != '\n');
	if (x == 'Y') {
		for (x = at; x != 0; x = ((bp[SECSIZE - 3] << 8) + bp[SECSIZE - 2]) & 0x03FF) {
			while (aread(x, drive, bp))
				printf("5 Failures reading sector on Atari disk\n");
			for (ct = bp[SECSIZE - 1], p = bp; ct > 0; ct--, p++) {
				if (*p == 0x7F)
					putchar('\t');
				else if (*p == 0x9B)
					putchar('\n');
				else
					putchar(*p);
			}
		}
	}
}


dup_file(name, at, size, flags)
	char           *name;
	int             at, size, flags;
{
	int             x, ct, mask;
	char            binary_copy;
	unsigned char  *p;
	FILE           *outf;

	binary_copy = bin_flag;
	printf("Copy %s (%ld bytes, f=%02X) in %s, <YNAB> ?", name, size * ((long) SECSIZE - 3L), flags, (binary_copy ? "Binary" : "Ascii"));
	mask = (flags & 0x04) ? 0xFF : 0x03;
	x = toupper(getchar());
	if (x != '\n')
		while (getchar() != '\n');
	if (x == 'Y' || x == 'A' || x == 'B') {
		if (x == 'A')
			binary_copy = 0;
		else if (x == 'B')
			binary_copy = 1;
		outf = bopen(name, "wb");
		if (outf == NULL) {
			printf("Cannot create file \"%s\"", name);
			return;
		}
		printf("Copying <<%s>>, %s\n", name, (binary_copy ? "Binary" : "Ascii"));
		for (x = at; x != 0; x = ((bp[SECSIZE - 3] & mask) << 8) + (bp[SECSIZE - 2] & 0xFF)) {
			while (aread(x, drive, bp))
				printf("5 Failures reading sector on Atari disk\n");
			if (debug) {
				printf("Link data = %02x, %02x, %02x\n", bp[SECSIZE - 3], bp[SECSIZE - 2], bp[SECSIZE - 1]);
			}
			for (ct = bp[SECSIZE - 1], p = bp; ct > 0; ct--, p++) {
				if (trace) {
					putchar(*p);
				}
				if (binary_copy)
					bput(*p, outf);
				else if (*p == 0x7F)
					bput('\t', outf);
				else if (*p == 0x9B) {
					bput('\r', outf);
					bput('\n', outf);
				} else
					bput(*p, outf);
			}
		}
	}
	bclose(outf);
}


getmap(drive)
	int             drive;
{
	int             p, n, k;
	char           *mp;

	for (mp = map, n = 0; n++ < sizeof(map);)
		*mp++ = 0;
	mp = map;
	n = 360;
	p = 3;
	do {
		if (aread(n--, drive, bp)) {
			printf("Unable to read the Atari disk VTOC/Bit Map!  Program aborted!\n");
			exit(1);
		}
		for (k = 0; k < 256; k++)
			*mp++ = bp[k];
		if (map[0] > (sizeof(map) / 256 + 2)) {
			printf("Invalid drive, more than %d sectors\n", (sizeof(map) - 10) * 8);
			exit(1);
		}
	} while (++p <= map[0]);
	if (debug) {
		printf("Drive %c: map header: <%02X> %02x%02x %02x%02x ", drive + 'A', map[0], map[2], map[1], map[4], map[3]);
		printf("%02x %02x %02x %02x %02x\n", map[5], map[6], map[7], map[8], map[9]);
	}
}


putmap(drive)
	int             drive;
{
	int             p, n, k;
	char           *mp;

	mp = map;
	n = 360;
	p = map[0];
	do {
		for (k = 0; k < 256; k++)
			bp[k] = *mp++;
		awrite(n--, drive, bp);
	} while (--p >= 3);
	if (debug) {
		printf("Drive %c: map header: <%02X> %02x%02x %02x%02x ", drive + 'A', map[0], map[2], map[1], map[4], map[3]);
		printf("%02x %02x %02x %02x %02x\n", map[5], map[6], map[7], map[8], map[9]);
	}
}


msdir(argc, argv)
	int             argc;
	char           *argv[];
{
	int             rc, k, sc, fno, bk, bk0, bkn;
	FILE           *inf;

	restfb(save);
	bkn = -1;
	while (--argc >= 0) {
		if (debug) {
			printf("Argument <<%s>>\n", *argv);
		}
		if ((rc = ffirst(&fdata, *argv)) == 0) {
			do {
				printf("Copying <<%s>>, Length = %ld, %s\n", fdata.name, fdata.len, (bin_flag ? "Binary" : "Ascii"));
				inf = fopen(fdata.name, (bin_flag ? "rb" : "r"));
				savefb(SECCODE, SECTORS, save);
				fno = dirfnd(fdata.name);
				bk0 = bk = alloc();
				if (debug) {
					printf("   First block = %d (FNO=%d)\n", bk, fno);
				}
				k = (fno << 4) & 0x0070;
				bp[k + 3] = bk;
				bp[k + 4] = bk >> 8;
				k = (fno >> 3) + 361;
				if (debug) {
					printf("   Writing dir bk. %d, drive %c:\n", k, drive + 'A');
				}
				awrite(k, drive, bp);
				restfb(save);
				sc = 1;
				fno <<= 2;
				k = 0;
				while ((rc = fgetc(inf)) >= 0) {
					if (!bin_flag) {
						if (rc == '\t')
							rc = 0x7F;
						else if (rc == '\n')
							rc = 0x9B;
					}
					if (k >= SECSIZE - 3) {
						bkn = alloc();
						if (bkn <= 0) {
							printf("No more sectors available\n");
							break;
						}
						if (debug) {
							printf("   Writing block %d (fno=%04X,link=%d)\n", bk, fno, bkn);
						}
						bp[SECSIZE - 3] = fno + (bkn >> 8);
						bp[SECSIZE - 2] = bkn & 0xFF;
						bp[SECSIZE - 1] = SECSIZE - 3;
						savefb(SECCODE, SECTORS, save);
						awrite(bk, drive, bp);
						restfb(save);
						bk = bkn;
						k = 0;
						sc++;
					}
					bp[k++] = rc;
				}
				fclose(inf);
				bp[SECSIZE - 3] = fno;
				bp[SECSIZE - 2] = 0;
				bp[SECSIZE - 1] = k;
				if (debug) {
					printf("   Writing last block %d (fno=%04X,size=%d)\n", bk, fno, k);
				}
				savefb(SECCODE, SECTORS, save);
				awrite(bk, drive, bp);
				bk = bk0;
				dirup(sc, 0x42, fno >> 2);
				restfb(save);
			} while (fnext(&fdata) == 0 && bkn != 0);
		} else
			printf("%s <<%s>>\n", rc < 18 ? "Invalid path for" : "No matching file", *argv);
		argv++;
	}
	savefb(SECCODE, SECTORS, save);
}


delfiles(argc, argv)
	int             argc;
	char           *argv[];
{
	int fno, k;

	while (--argc >= 0) {
		if (debug) {
			printf("Argument <<%s>>\n", *argv);
		}
		fno = dirfnd(*argv);
		k = (fno << 4) & 0x0070;
		bp[k] = 0x80;
		argv++;
	}
}


dirfnd(name)
	char           *name;
{
	char           *name0, *cp;
	char            namev[12];
	int             j, k, first;

	k = 0;
	for (j = 0; j < 11; j++)
		namev[j] = ' ';
	namev[11] = '\0';
	while (*name != '.' && *name != '\0') {
		namev[k++] = *name++;
		if (k >= 11)
			break;
	}
	if (k < 11 && *name == '.') {
		k = 8;
		while (*++name != '\0' && k < 11) {
			if (*name == '\n')
				break;
			namev[k++] = *name;
		}
	}
	first = -1;
	for (k = 361; k < 369; k++) {
		if (aread(k, drive, bp)) {
			printf("Unable to read ROOT directory for updating! Program aborted!\n");
			exit(1);
		}
		for (j = 0; j < 0x80; j += 0x10) {
			if (namecomp(&bp[j + 5], namev)) {
				first = ((k - 361) << 3) + (j >> 4);
				if (debug) {
					printf("   Replaced file no. = %d\n", first);
				}
				dealloc_file(bp[j+3] + bp[j+4]<<8);
				return (first);
			}
			if (bp[j] == 0) {
				if (first < 0)
					first = ((k - 361) << 3) + (j >> 4);
				goto allcf;
			}
			if ((bp[j] & 0x80) && first < 0)
				first = ((k - 361) << 3) + (j >> 4);
		}
	}
allcf:
	if (debug) {
		printf("allocated file no. = %d\n", first);
	}
	if (first < 0) {
		printf("No directory entries free\n");
		exit(1);
	}
	if ((first >> 3) != (k - 361))
		while (aread((first >> 3) + 361, drive, bp));
	cp = &bp[(first & 0x07) << 4];
	*cp = 0x03;
	cp += 5;
	for (k = 0; k < 11; k++)
		*cp++ = namev[k];
	if (debug) {
		printf("%s==>%s(FNO=%d)\n", name0, namev, first);
	}
	return (first);
}


dirup(cnt, attr, indx)
	int             cnt, attr, indx;
{
	int             k;

	while (aread(361 + (indx >> 3), drive, bp));
	k = (indx << 4) & 0x0070;
	bp[k] = attr;
	*(int *) (&bp[k + 1]) = cnt;
	awrite(361 + (indx >> 3), drive, bp);
}


alloc()
{
	int             p, x, mask;

	for (x = 10; map[x] == 0; x++)
		if (x >= 1280)
			return (0);
	p = (x - 10) << 3;
	mask = 0x80;
	while ((map[x] & mask) == 0) {
		mask >>= 1;
		p++;
	}
	map[x] &= ~mask;
	--*seccnt;
	return (p);
}


dealloc(sec)
	int             sec;
{
	map[(sec >> 3) + 10] |= bitmask[sec & 0x07];
}


dealloc_file(sec)
int sec;
{
	if (debug) {
		printf("Dealloc sector chain %d\n",sec);
	}
	while (sec != 0) {
		dealloc(sec);
		while (aread(sec, drive, bp))
			printf("5 Failures reading sector on Atari disk\n");
		sec = ((bp[SECSIZE - 3] << 8) + bp[SECSIZE - 2]) & 0x03FF;
	}
}


namecomp(a, b)
	char           *a, *b;
{
	int             i;

	for (i = 0; i < 11; i++)
		if (*a++ != *b++)
			return (0);
	return (1);
}


FILE           *
bopen(name, mode)
	char           *name, *mode;
{
	FILE           *val;

	restfb(save);
	val = fopen(name, mode);
	savefb(SECCODE, SECTORS, save);
	return (val);
}


bput(data, out)
	int             data;
	FILE           *out;
{
	int             len;

	if (bpp >= sizeof(dosbuf)) {
		restfb(save);
		len = fwrite(dosbuf, 1, sizeof(dosbuf), out);
		if (len != sizeof(dosbuf))
			printf("Error writing to DOS file, %d bytes not written\n", sizeof(dosbuf) - len);
		if (debug) {
			printf("Writing block of %d bytes to DOS file\n", sizeof(dosbuf));
		}
		bpp = 0;
		savefb(SECCODE, SECTORS, save);
	}
	dosbuf[bpp++] = data;
}


bclose(out)
	FILE           *out;
{
	int             len;

	restfb(save);
	if (bpp != 0) {
		len = fwrite(dosbuf, 1, bpp, out);
		if (len != bpp)
			printf("Error closing file, %d bytes not written\n", bpp - len);
	}
	if (debug) {
		printf("Last file block contains %d bytes\n", bpp);
	}
	bpp = 0;
	fclose(out);
	savefb(SECCODE, SECTORS, save);
}


fmtdisk(drive)
	int             drive;
{
	int             ct;

	for (ct = 0; ct < 40; ct++) {
		if (aformat(ct, drive, bp, seq) < 0)
			printf("Error formatting track %d\n", ct);
	}
	for (ct = 0; ct < 512; ct++)
		map[ct] = 0;
	map[0] = 2;
	map[1] = map[3] = 0xc4;
	map[2] = map[4] = 0x02;
	for (ct = 4; ct < 360; ct++)
		dealloc(ct);
	for (ct = 369; ct < 720; ct++)
		dealloc(ct);
	for (ct = 0; ct < 1024; ct++)
		dirbuf[ct] = 0;
	putmap(drive);
	for (ct = 361; ct < 369; ct++)
		awrite(ct, drive, dirbuf);
}
