#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

#ifndef VERSION
#define VERSION "???"
#endif

#define SELF "atr2xfd"
#define CHECK "atrcheck"

char *self = SELF;

void classify_boot_sector(FILE *);

int main(int argc, char **argv) {
	char *type;
	struct stat st;
	char infile[4096], outfile[4096];
	unsigned char buf[16];
	FILE *in, *out;
	int i, paras, hparas, secsize, seccount;
	int checkonly = 0, bytes = 0;

	if(strstr(argv[0], CHECK)) {
		self = CHECK;
		checkonly = 1;
	}

	if(argc < 2 || argc > 3) {
		fprintf(stderr,
				"%s v" VERSION " by B. Watson (WTFPL)\n", self);
		fprintf(stderr,
				"Usage: %s input.xfd%s\n",
				self,
				(checkonly ? "" : " [output.atr]"));
		exit(1);
	}

	strcpy(infile, argv[1]);
	if(argc == 3) {
		strcpy(outfile, argv[2]);
		if(checkonly)
			fprintf(stderr,
					"%s: output file not used with %s (ignoring it).\n",
					self, self);
	} else if(strcmp(infile, "-") == 0) {
		strcpy(outfile, "-");
	} else {
		char *p;
		strcpy(outfile, argv[1]);

		p = strstr(outfile, ".atr");
		if(!p) p = strstr(outfile, ".ATR");
		if(!p) p = outfile + strlen(outfile);
		strcpy(p, ".xfd");
	}

	if(!checkonly)
		fprintf(stderr, "%s: input '%s', output '%s'\n", self, infile, outfile);

	if(strcmp(infile, "-") == 0) {
		in = stdin;
	} else {
		if( !(in = fopen(infile, "rb")) ) {
			fprintf(stderr, "%s: (fatal) can't read %s: %s\n",
					self, infile, strerror(errno));
			exit(1);
		}
	}

	if(fstat(fileno(in), &st)) {
		fprintf(stderr, "%s: (fatal) can't stat %s: %s\n",
				self, infile, strerror(errno));
		exit(1);
	}

	/* A few sanity checks... */
	if(st.st_size < 400) {
		fprintf(stderr,
				"%s: (fatal) %s too small to be an ATR image (<400 bytes)\n",
				self, infile);
		exit(2);
	}

	if(st.st_size % 128 == 0 ) {
		fprintf(stderr,
				"%s: (fatal) %s looks like an XFD image, not an ATR\n",
				self, infile);
		exit(2);
	}

	if( (st.st_size - 16) % 128 != 0 ) {
		fprintf(stderr,
				"%s: (fatal) %s not a valid ATR image (not an even number "
				"of sectors)\n",
				self, infile);
		exit(2);
	}

	if(st.st_size > (65535 * 256)) {
		fprintf(stderr,
				"%s: (fatal) %s too large to be an ATR image (>16M)\n",
				self, infile);
		exit(2);
	}

	paras = st.st_size / 16 - 1;
	fprintf(stderr, "%s: size is %d 16-byte paragraphs\n", self, paras);

	if(fread(buf, 1, 16, in) != 16) {
		fprintf(stderr,
				"%s: (fatal) can't read ATR header from %s: %s\n",
				self, infile, strerror(errno));
		exit(1);
	}

	if( !(buf[0] == 0x96 && buf[1] == 0x02) ) {
		fprintf(stderr,
				"%s: (fatal) %s not an ATR file (no NICKATARI signature)!\n",
				self, infile);
		exit(2);
	}

	secsize = buf[4] + (buf[5] << 8);

	if( !(secsize == 128 || secsize == 256) ) {
		fprintf(stderr,
				"%s: (fatal) %s has invalid sector size %d\n",
				self, infile, secsize);
		exit(2);
	}

	if(secsize == 128)
		seccount = (st.st_size - 16) / secsize;
	else {
		seccount = (st.st_size - 400) / secsize + 3;
		if((st.st_size - 16) % 256 != 128)
			fprintf(stderr, "%s: partial sector at end of DD image, might "
					"be a truncated or bogus ATR.\n", self);
	}

	fprintf(stderr,
			"%s: sectors: %d, sector size: %d bytes",
			self, seccount, secsize);

	if(secsize == 256)
		fprintf(stderr, " (first 3 sectors are 128 bytes)");
	fputc('\n', stderr);

	if(secsize == 128) {
		if(seccount < 720)
			type = "short SS/SD image, <90K";
		else if(seccount == 720)
			type = "standard SS/SD image, 90K";
		else if(seccount == 1040)
			type = "1050 SS/ED image, 130K";
		else if(seccount == 1440)
			type = "XF551 DS/SD image, 180K";
		else
			type = "high-capacity floppy or hard disk image, SD";
	} else {
		if(seccount < 720)
			type = "short SS/DD image, <180K";
		else if(seccount == 720)
			type = "standard SS/DD image, 180K";
		else if(seccount == 1440)
			type = "XF551 DS/DD or ATR8000 SS/QD image, 360K";
		else if(seccount == 2880)
			type = "ATR8000 DS/QD or SS/PC image, 720K";
		else if(seccount == 5760)
			type = "ATR8000 PC 1.44M image, 1440K";
		else
			type = "high-capacity floppy or hard disk image, DD";
	}

	fprintf(stderr, "%s: %s is a %s\n", self, infile, type);

	if(checkonly) {
		classify_boot_sector(in);
	}

	hparas = buf[2] + (buf[3] << 8) + (buf[6] << 16);
	if(hparas != paras) {
		/* this is only a fatal error if checkonly is true */
		fprintf(stderr,
				"%s: (%s) %s file size (%d paragraphs) doesn't agree with "
				"ATR header (%d paragraphs). File may be truncated or corrupt.\n",
				self,
				(checkonly ? "fatal" : "warning"),
				infile, paras, hparas);

		if(checkonly)
			exit(1);

		fprintf(stderr,
				"%s: Using actual file size for XFD image; expect trouble.\n",
				self);
	}

	if(checkonly) {
		exit(0);
	}

	fprintf(stderr, "%s: ATR image OK (no fatal errors).\n", self);

	/* Only open the output file after the ATR is known to be good */
	if(strcmp(outfile, "-") == 0) {
		out = stdout;
	} else {
		if( !(out = fopen(outfile, "wb")) ) {
			fprintf(stderr,
					"%s: (fatal) can't write %s: %s\n",
					self, outfile, strerror(errno));
			exit(1);
		}
	}

	/* copy the data */
	while( (i = fgetc(in)) != EOF ) {
		fputc(i, out);
		bytes++;
	}

	/* fputc() returns EOF on error *or* EOF;
		check for I/O errors, return 1 if so */
	i = 0;

	if(ferror(in)) {
		i = 1;
		fprintf(stderr,
				"%s: error reading %s: %s\n", self, infile, strerror(errno));
	}

	if(ferror(out)) {
		i = 1;
		fprintf(stderr,
				"%s: error writing %s: %s\n", self, outfile, strerror(errno));
	}

	fclose(in);
	fclose(out);

	if(!i)
		fprintf(stderr, "%s: XFD image OK, wrote %d bytes\n", self, bytes);

	return i;
}

/* these signatures are for the first N bytes of sector 1. */
unsigned char dos1_sig[] = "\x00\x01\x00\x07\x00\x13\x4c\x12";
unsigned char dos2_sig[] = "\x00\x03\x00\x07\x40\x15\x4c\x14";
unsigned char dos3_sig[] = "\x01\x09\x00\x32\x06\x32";
unsigned char dos4_sig[] = "\x00\x21\x01\x07\xf8\x07\x4c\xd9";
unsigned char dos4_noboot_sig[] = "\x00\x00\x00\x01\x00\x01\x01\x01";
unsigned char mydos30_sig[] = "\x00\x03\x00\x07\xd8\x17\x4c\x14";
unsigned char mydos31_sig[] = "\x00\x03\x00\x07\xe0\x07\x4c\x14";
unsigned char mydos4_sig[] = "\x4d\x03\x00\x07\xe0\x07\x4c\x14";
unsigned char topdos11_sig[] = "\xc0\x03\x00\x07\x40\x15\x4c\x24";
unsigned char topdos15_sig[] = "\x80\x03\x00\x07\x40\x15\x4c\x24";
unsigned char blank_sig[] = "\x00\x00\x00\x00\x00\x00\x00\x00";
unsigned char pico_sig[] = "\x00\x03\x00\x10\x10\x4c";
unsigned char superdos2_sig[] = "\x29\x03\x00\x07\x40\x15\xa0";
unsigned char superdos5_sig[] = "\x64\x03\x00\x07\x40\x15\xa9\x07";
unsigned char smartdos_sig[] = "\x00\x03\x00\x07\xd4\x15\x4c\x14";
unsigned char dosxe_sig[] = "\x58\x03\x00\x07\xca\x0c\x4c\x30";
unsigned char fenders_sig[] = "\x00\x03\x00\x07\x77\xe4\xa0\x00\x8c\x44";
unsigned char fenders2_sig[] = "\x00\x03\x00\x07\x77\xe4\xa0\x01\x8c\x44";

/* both of these claim to be "Turbo DOS XE version 2.1" */
unsigned char turbodos_sig[] = "\x04\x03\x00\x07\x40\x15\x4c\x16";
unsigned char turbodosxe_sig[] = "\x01\x03\x00\x07\x40\x15\x4c\x16";

/* these signatures are for offset $18 of sector 1. */
unsigned char dos2_code_sig[] = "\x36\xad\x12\x07";
unsigned char dos25_code_sig[] = "\x35\x20\x5f\x07";

#define SIG_MATCH(x, y) (memcmp(x, y, sizeof(y) - 1) == 0)

void classify_boot_sector(FILE *f) {
	unsigned char buf[128];
	char *dos_type = "Unknown", *boot_text = "";
	int bootable = -1; /* -1 = unknown, 0 = no, 1 = yes */
	int dos2_bootable, i;

	/* read 1st sector... */
	if( (fread(buf, 1, 128, f) < 128) ) {
		fprintf(stderr, "%s: EOF reading sector 1, truncated image.\n", self);
		exit(2);
	}

#if 1
	for(i = 0; i < 8; i++) fprintf(stderr, "\\x%02x", buf[i]);
	fputc('\n', stderr);
#endif

	dos2_bootable = (buf[14] != 0);

	/* now figure out what kind of boot sector this is */
	if(SIG_MATCH(buf, dos1_sig)) {
		dos_type = "DOS 1.0";
		bootable = dos2_bootable;
	} else if(SIG_MATCH(buf, dos3_sig)) {
		dos_type = "DOS 3.0";
		/* can't tell if bootable from just the boot sector, would have
			to search for FMS.SYS */
	} else if(SIG_MATCH(buf, dos4_sig)) {
		dos_type = "DOS 4.0";
		bootable = 1;
	} else if(SIG_MATCH(buf, dos4_noboot_sig)) {
		dos_type = "DOS 4.0";
		bootable = 0;
	} else if(SIG_MATCH(buf, dos2_sig)) {
		if(SIG_MATCH(buf + 0x18, dos2_code_sig))
			dos_type = "DOS 2.0";
		else if(SIG_MATCH(buf + 0x18, dos25_code_sig))
			dos_type = "DOS 2.5";
		else
			dos_type = "DOS 2.0 compatible";
		bootable = dos2_bootable;
	} else if(buf[0] == 0x00 && buf[1] == 0x03 &&
			(buf[32] == 0x11 || buf[32] == 0x20) &&
			buf[6] == 0x4c && buf[7] == 0x80) {
		if(buf[32] == 0x11)
			dos_type = "SpartaDOS 1.1";
		else
			dos_type = "SpartaDOS >= 2";
	} else if(SIG_MATCH(buf, mydos30_sig)) {
		dos_type = "MyDOS 3.0x";
		bootable = dos2_bootable;
	} else if(SIG_MATCH(buf, mydos31_sig)) {
		dos_type = "MyDOS 3.1x";
		bootable = dos2_bootable;
	} else if(SIG_MATCH(buf, mydos4_sig)) {
		dos_type = "MyDOS 4.x";
		bootable = dos2_bootable;
	} else if(SIG_MATCH(buf, pico_sig)) {
		dos_type = "MyPicoDOS 4.x";
	} else if(SIG_MATCH(buf, topdos11_sig)) {
		dos_type = "Top DOS 1.1";
		bootable = !(buf[15] == 0 && buf[16] == 0);
	} else if(SIG_MATCH(buf, topdos15_sig)) {
		dos_type = "Top DOS 1.5";
		bootable = dos2_bootable;
	} else if(SIG_MATCH(buf, superdos2_sig)) {
		dos_type = "SuperDOS 2.9";
		bootable = (buf[7] != 0);
	} else if(SIG_MATCH(buf, superdos5_sig)) {
		dos_type = "SuperDOS 5.1";
	} else if(SIG_MATCH(buf, turbodos_sig)) {
		dos_type = "Turbo DOS XE";
	} else if(SIG_MATCH(buf, turbodosxe_sig)) {
		dos_type = "Turbo DOS XE";
	} else if(SIG_MATCH(buf, dosxe_sig)) {
		dos_type = "DOS XE";
	} else if(SIG_MATCH(buf, smartdos_sig)) {
		dos_type = "Smart DOS";
		bootable = dos2_bootable;
	} else if(SIG_MATCH(buf, fenders_sig) || SIG_MATCH(buf, fenders2_sig)) {
		dos_type = "Fenders 3-sector loader";
		bootable = 1;
	} else if(SIG_MATCH(buf, blank_sig)) {
		dos_type = "None (empty boot sector)";
		bootable = 0;
	}

	if(bootable == 0)
		boot_text = ", not bootable";
	else if(bootable == 1)
		boot_text = ", bootable";

	fprintf(stderr, "%s: DOS type is: %s%s\n", self, dos_type, boot_text);
}
