#include #include #include #include #include #include #include #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); }