#include "unddi.h"
#include "nls.h"

int extr;
int extdirs;
int forceoffs;
long imgoffs;
int overwry = 0;
int overwrn = 0;
int uselfn;
int locase;
char *imgfile;

FILE *f;
int imgfmt;
__u8 ddimap[160];
struct tbootsec bootsect;
__u8 mediaid;
unsigned short *fat;
int fatentr;
int shortened;
int err;
int errtot = 0;


#ifndef __linux__
#define strcasecmp(s1,s2) stricmp(s1,s2)
#endif

int parsecmdline(int argc, char *argv[])
{
    int cmdl = 0;
    int cmdx = 0;
    int swo = 0;
    int tmpforceoffs = 0;
    unsigned long tmpoffs;
    int swnolfn = 0;
    int swnolocase = 0;
    int i;

    /* incorrect number of parameters? */
    if (argc < 3) return -1;
    if (argc > 8) return -1;

    /* checking for commands */
    if (strcmp(argv[1], "l") == 0) cmdl = 1;
    if (strcmp(argv[1], "x") == 0) {
        cmdx = 1;
	extdirs = 1;
    }
    if (strcmp(argv[1], "e") == 0) {
        cmdx = 1;
	extdirs = 0;
    }
    if (! (cmdl || cmdx)) return -1;

    /* checking for switches */
    for (i=2; i<argc-1; i++) {
        if (swo) {
            tmpforceoffs = 1;
            tmpoffs = strtoul(argv[i], NULL, 0);
            swo = 0;
            continue;
        }
        if (strcmp(argv[i], "-o") == 0) {
            swo = 1;
            continue;
        }
	if (strcmp(argv[i], "-oy") == 0) {
	    overwry = 1;
	    continue;
	}
	if (strcmp(argv[i], "-on") == 0) {
	    overwrn = 1;
	    continue;
	}
        if (strcmp(argv[i], "-nolfn") == 0) {
            swnolfn = 1;
            continue;
        }
        if (strcmp(argv[i], "-nolocase") == 0) {
            swnolocase = 1;
            continue;
        }
        return -1;
    }
    if (swo) return -1;
    if (overwry && overwrn) return -1;

    extr = cmdx;
    forceoffs = tmpforceoffs;
    if (forceoffs) imgoffs = tmpoffs;
    #ifdef LFN_AVAIL
        uselfn = !swnolfn;
    #else
        uselfn = 0;
    #endif
    locase = !swnolocase;
    imgfile = argv[argc-1];

    return 0;
}

void help(void)
{
    printf("UNDDI %d.%02d (%s)         by Mariusz Borkowski <borkowsm@ii.pw.edu.pl>\n",
           VER_MAJOR, VER_MINOR, __DATE__);
    printf("Latest sources and executables:   http://www.ii.pw.edu.pl/~borkowsm/unddi.htm\n");
    printf("This program is distributed under the terms of the GNU General Public License\n");
    printf("\n");
    printf("Usage:\n");
    printf("List contents of disk image:\n");
    printf("    unddi l [-o offset] imagefile\n");
    printf("Extract files from disk image:\n");
    #ifdef LFN_AVAIL
    printf("    unddi x [-o offset] [-oy | -on] [-nolfn] [-nolocase] imagefile\n");
    #else
    printf("    unddi x [-o offset] [-oy | -on] imagefile\n");
    #endif
    printf("Extract files from disk image without restoring directory structure:\n");
    #ifdef LFN_AVAIL
    printf("    unddi e [-o offset] [-oy | -on] [-nolfn] [-nolocase] imagefile\n");
    #else
    printf("    unddi e [-o offset] [-oy | -on] imagefile\n");
    #endif
    printf("\n");
    printf("-o offset : specify offset of disk image within imagefile\n");
    printf("            offset can be decimal, hexadecimal (0xoffset) or octal (0offset)\n");
    printf("            default is autodetection\n");
    printf("-oy | -on : overwrite existing files / don't overwrite existing files\n");
    printf("            default is prompt user\n");
    #ifdef LFN_AVAIL
    printf("-nolfn    : don't use long filenames\n");
    printf("-nolocase : don't convert long filenames to lowercase\n");
    printf("            short filenames are always converted to lowercase\n");
    #endif
    #ifdef __linux__
    printf("\n");
    #endif
}

long findimg(void)
{
    int imgfound = 0;
    long fileoffs = 0;
    long imgoffs;
    int actread;
    __u8 buf[4096];
    __u8 *srchst;
    __u8 *magpos;
    char mag5[5];
    __u16 mag2;

    f = fopen(imgfile, "rb");
    if (!f) {
        printf("Unable to open %s: %s\a\n", imgfile, strerror(errno));
        return ERR_OPEN;
    }
    while (1) {
        fseek(f, fileoffs, SEEK_SET);
        actread = fread(buf, 1, sizeof(buf), f);
        if (!actread)
            break;
        srchst = buf;
        while (1) {
            if (srchst-buf >= actread)
                break;
            magpos = memchr(srchst, 0xEB, actread-(srchst-buf));
            if (!magpos)
                break;
            fseek(f, fileoffs + (magpos - buf) + 0x36, SEEK_SET);
            if (fread(&mag5, 1, sizeof(mag5), f) == sizeof(mag5)) {
                if (strncmp(mag5, FAT12_MAGIC, strlen(FAT12_MAGIC)) == 0) {
                    imgfound = 1;
                    break;
                }
            }
            fseek(f, fileoffs + (magpos - buf) + 510, SEEK_SET);
            if (fread(&mag2, 1, sizeof(mag2), f) == sizeof(mag2)) {
                if (mag2 == 0xAA55) {
                    imgfound = 1;
                    break;
                }
            }
            fseek(f, fileoffs + (magpos - buf) + 513, SEEK_SET);
            if (fread(&mag2, 1, sizeof(mag2), f) == sizeof(mag2)) {
                if (mag2 == 0xFFFF) {
                    imgfound = 1;
                    break;
                }
            }
            srchst = magpos + 1;
        }
        if (imgfound) {
            break;
        }
        fileoffs = fileoffs + sizeof(buf);
    }
    fclose(f);
    if (!imgfound) {
        printf("No FAT12 disk image found\a\n");
        return ERR_BADFILE;
    }
    imgoffs = fileoffs + (magpos - buf);
    return imgoffs;
}

int chkfmt(void)
{
    int actread;
    __u16 mag2;

    f = fopen(imgfile, "rb");
    if (!f) {
        printf("Unable to open %s: %s\a\n", imgfile, strerror(errno));
        return ERR_OPEN;
    }
    actread = fread(&mag2, 1, sizeof(mag2), f);
    if (actread != sizeof(mag2)) {
        printf("Invalid disk image\a\n");
        return ERR_BADFILE;
    }
    fclose(f);
    if (mag2 == 0x4D49) {
        return FMT_DDI;
    } else {
        return FMT_RAW;
    }
}

int readddimap(void)
{
    int i;
    int actread;

    f = fopen(imgfile, "rb");
    if (!f) {
        printf("Unable to open %s: %s\a\n", imgfile, strerror(errno));
        return ERR_OPEN;
    }
    for (i=0; i<160; i++) {
        fseek(f, 0x65 + 6*i, SEEK_SET);
        actread = fread(&ddimap[i], 1, sizeof(__u8), f);
	if (actread != sizeof(__u8)) {
            printf("Invalid disk image\a\n");
            return ERR_BADFILE;	
	}
    }
    fclose(f);
    return 0;
}

size_t imgread(FILE *stream, long offset, size_t size, void *ptr)
{
    int tracksize;
    int i, tracklo, trackhi;
    int toffs, tsize;
    int remsize;
    size_t actread;
    
    if (imgfmt == FMT_DDI) {
        tracksize = bootsect.bytesec * bootsect.sectrack;
        tracklo = offset / tracksize;
        trackhi = (offset + size - 1) / tracksize;
        toffs = offset % tracksize;
	remsize = size;
        for (i=tracklo; i<=trackhi; i++) {
            if (remsize > tracksize-toffs) {
	        tsize = tracksize-toffs;
            } else {
	        tsize = remsize;
            }
	    fseek(stream,
	         imgoffs + (long)(ddimap[i]-1)*(long)tracksize + (long)toffs,
		 SEEK_SET);
            actread = fread(ptr, 1, tsize, stream);
	    if (actread != tsize)
	        return size - remsize + actread;
	    ptr = (__u8 *) ptr + tsize;
	    toffs = 0;
	    remsize -= tsize;
        }
        return size;
    } else {
        fseek(stream, imgoffs + offset, SEEK_SET);
	actread = fread(ptr, 1, size, stream);
	return actread;
    }
}

int readbootsec(void)
{
    int actread;

    f = fopen(imgfile, "rb");
    if (!f) {
        printf("Unable to open %s: %s\a\n", imgfile, strerror(errno));
        return ERR_OPEN;
    }
    fseek(f, imgoffs, SEEK_SET);
    actread = fread(&bootsect, 1, sizeof(struct tbootsec), f);
    if (actread != sizeof(struct tbootsec)) {
        printf("Invalid disk image\a\n");
        return ERR_BADFILE;
    }
    fclose(f);
    return 0;
}

int readmediaid(void)
{
    int actread;

    f = fopen(imgfile, "rb");
    if (!f) {
        printf("Unable to open %s: %s\a\n", imgfile, strerror(errno));
        return ERR_OPEN;
    }
    fseek(f, imgoffs+512, SEEK_SET);
    actread = fread(&mediaid, 1, sizeof(mediaid), f);
    if (actread != sizeof(mediaid)) {
        printf("Invalid disk image\a\n");
        return ERR_BADFILE;
    }
    fclose(f);
    return 0;
}

int comparefats(long fat1offs, long fat2offs, int fatsize)
{
    void *fat1, *fat2;
    int actread;

    fat1 = malloc(fatsize);
    if (!fat1) {
        printf("Unable to allocate memory\a\n");
        exit(1);
    }
    fat2 = malloc(fatsize);
    if (!fat2) {
        printf("Unable to allocate memory\a\n");
        exit(1);
    }
    f = fopen(imgfile, "rb");
    if (!f) {
        free(fat2);
        free(fat1);
        printf("Unable to open %s: %s\a\n", imgfile, strerror(errno));
        return ERR_OPEN;
    }
    actread = imgread(f, fat1offs, fatsize, fat1);
    actread = imgread(f, fat2offs, fatsize, fat2);
    if (actread != fatsize) {
        fclose(f);
        free(fat2);
        free(fat1);
        printf("Invalid disk image\a\n");
        return ERR_BADFILE;
    }
    fclose(f);
    if (memcmp(fat1, fat2, fatsize))
        printf("Warning: FAT copies are different\a\n");
    free(fat2);
    free(fat1);
    return 0;
}

int readfat(int entries)
{
    int i;
    struct {
        unsigned clust1 : 12;
        unsigned clust2 : 12;
    } dblclust;

    f = fopen(imgfile, "rb");
    if (!f) {
        printf("Unable to open %s: %s\a\n", imgfile, strerror(errno));
        return ERR_OPEN;
    }
    for (i=0; i<entries/2; i++) {
	imgread(f, bootsect.bytesec + i*sizeof(dblclust), sizeof(dblclust), &dblclust);
        fat[2*i] = dblclust.clust1;
        fat[2*i + 1] = dblclust.clust2;
    }
    fclose(f);
    return 0;
}

long getdirsize(int cluster)
{
    int nclust = 1;

    if (cluster == 0)
        return bootsect.rootent * sizeof(union tdirent);
    while (fat[cluster] < 0xFF8) {
        cluster = fat[cluster];
        nclust++;
    }
    return bootsect.bytesec * bootsect.secclu * nclust;
}

unsigned char sfncksum(char *s)
{
    unsigned char cksum = 0;
    int i;

    for (i=0; i<11; i++) {
        cksum = (((cksum & 1) << 7) | ((cksum & 0xfe) >> 1)) + s[i];
    }
    return cksum;
}

void dispfile(union tdirent *dir, char *lfn)
{
    printf("%.8s", dir->s.name);
    if (strncmp(dir->s.ext, "   ", 3) == 0) {
        printf(" ");
    } else {
        printf(".");
    }
    printf("%.3s ", dir->s.ext);
    if (dir->s.attr & ATTR_DIR) {
        printf("<DIR>    ");
    } else {
        printf("%9ld", dir->s.size);
    }
    printf(" %4d-%02d-%02d %2d:%02d", dir->s.dt.year+1980, dir->s.dt.mon,
           dir->s.dt.mday, dir->s.dt.hour, dir->s.dt.min);
    if (strlen(lfn)) {
        printf(" %s\n", lfn);
    } else {
        printf("\n");
    }
}

void extrfile(union tdirent *dir, char *sfn, char *lfn)
{
    FILE *fout;
    void *buf;
    long clsize = bootsect.bytesec * bootsect.secclu;
    long lastclsize;
    int cluster;
    struct tm st;
    struct utimbuf ub;
    string choice;
    char *name;
    int errpf = 0;
    int skipfile = 0;
    #ifdef DOS_ATTR
    unsigned attrib = 0;
    #endif
    #ifdef UNIX_ATTR
    mode_t savumask;
    #endif

    if (uselfn) {
        name = lfn;
    } else {
        name = sfn;
    }
    if (dir->s.attr & ATTR_DIR) {
        if (dir->s.name[0] == '.')
            return;
        printf("Creating    %-40s  ", name);
        err = access(name, 0);
	if (!err) {
            printf("Directory already exists\n");
            return;
	}
        #ifdef __linux__
        err = mkdir(name, 0777);
        #else
        err = mkdir(name);
        #endif
        if (err) {
            errtot++;
            printf("%s\a\n", strerror(errno));
        } else {
            printf("OK\n");
        }
    } else {
	while (1) {
	    int dblbreak = 0;

	    err = access(name, 0);
	    if (err) {
	        break;
	    } else {
	        if (overwry) {
		    break;
		}
		if (overwrn) {
		    skipfile = 1;
		    break;
		}
		printf("%s already exists. Overwrite it? Yes/No/All/nonE/Rename ",
		       name);
		while (1) {
		    gets(choice);
		    if (strcasecmp(choice, "y") == 0) {
		        dblbreak = 1;
		        break;
                    } else {
		    if (strcasecmp(choice, "n") == 0) {
		        skipfile = 1;
			dblbreak = 1;
		        break;
		    } else {
		    if (strcasecmp(choice, "a") == 0) {
		        overwry = 1;
			dblbreak = 1;
		        break;
		    } else {
		    if (strcasecmp(choice, "e") == 0) {
		        overwrn = 1;
		        skipfile = 1;
			dblbreak = 1;
		        break;
		    } else {
		    if (strcasecmp(choice, "r") == 0) {
		        printf("Enter new name: ");
			gets(name);
			dblbreak = 0;
			break;
		    } else {
		        printf("Overwrite it? Yes/No/All/nonE/Rename ");
		    }}}}}
		}
		if (dblbreak) break;
	    }
	}
	if (skipfile) {
	    printf("Skipping    %s\n", name);
	    return;
	}
        printf("Extracting  %-40s  ", name);
        fout = fopen(name, "wb");
        if (!fout) {
            errtot++;
            printf("%s\a\n", strerror(errno));
            return;
        }
        cluster = dir->s.clust;
        if (cluster != 0) {
            lastclsize = dir->s.size % clsize;
            if (lastclsize == 0)
                lastclsize = clsize;
            buf = malloc(clsize);
            if (!buf) {
                printf("Unable to allocate memory\a\n");
                exit(1);
            }
            do {
	        imgread(f, bootsect.bytesec +
	                bootsect.bytesec * bootsect.fatcps * bootsect.secfat +
		        getdirsize(0) + (cluster-2)*clsize,
		        clsize, buf);
                cluster = fat[cluster];
                if (cluster >= 0xFF8) {
                    if (fwrite(buf, 1, lastclsize, fout) != lastclsize) {
                        errpf++;
                        printf("Error\a\n");
                        break;
                    }
                } else {
                    if (fwrite(buf, 1, clsize, fout) != clsize) {
                        errpf++;
                        printf("Error\a\n");
                        break;
                    }
                }
            } while (cluster < 0xFF8);
            free(buf);
	}
        fclose(fout);
        if (errpf) {
            errtot++;
        } else {
            printf("OK\n");
        }
        st.tm_year = dir->s.dt.year + 80;
        st.tm_mon = dir->s.dt.mon - 1;
        st.tm_mday = dir->s.dt.mday;
        st.tm_hour = dir->s.dt.hour;
        st.tm_min = dir->s.dt.min;
        st.tm_sec = dir->s.dt.sec2 * 2;
        ub.modtime = ub.actime = mktime(&st);
        err = utime(name, &ub);
        if (err) {
            errtot++;
            printf("Unable to set file timestamp: %s\a\n", strerror(errno));
        }
        #ifdef DOS_ATTR
            if (dir->s.attr & ATTR_RO)  attrib = attrib | FA_RDONLY;
            if (dir->s.attr & ATTR_HID) attrib = attrib | FA_HIDDEN;
            if (dir->s.attr & ATTR_SYS) attrib = attrib | FA_SYSTEM;
            if (dir->s.attr & ATTR_ARC) attrib = attrib | FA_ARCH;
            err = _dos_setfileattr(name, attrib);
            if (err) {
                errtot++;
                printf("Unable to set file attributes: %s\a\n", strerror(errno));
            }
        #endif
        #ifdef UNIX_ATTR
            if (dir->s.attr & ATTR_RO) {
                savumask = umask(0777);
                err = chmod(name, 0444 & ~savumask);
                if (err) {
                    errtot++;
                    printf("Unable to set file attributes: %s\a\n", strerror(errno));
                }
                umask(savumask);
            }
        #endif
    }
}

void scantree(int cluster, char *dirsfn, char *dirlfn)
{
    union tdirent *dir;
    union tdirent dirent;
    long dirsize;
    int dirread = 0;
    int i, j, pass;
    shortstring sfn, tmpext;
    string lfn;
    string subdirsfn, subdirlfn;
    unsigned char cksum;
    long clsize = bootsect.bytesec * bootsect.secclu;

    dirsize = getdirsize(cluster);
    dir = malloc(dirsize);
    if (!dir) {
        printf("Unable to allocate memory\a\n");
        exit(1);
    }
    if (cluster == 0) {
	imgread(f, bootsect.bytesec +
	           bootsect.bytesec * bootsect.fatcps * bootsect.secfat,
		dirsize, dir);
    } else {
        do {
	    imgread(f, bootsect.bytesec +
	               bootsect.bytesec * bootsect.fatcps * bootsect.secfat +
		       getdirsize(0) + (cluster-2)*clsize,
		    clsize, &dir[dirread/sizeof(union tdirent)]);
            cluster = fat[cluster];
            dirread = dirread + clsize;
        } while (cluster < 0xFF8);
    }

    if (!extr) {
        if (strcmp(dirsfn, SLASH) != 0)
            printf("\n");
        printf(" Directory of %s\n", dirsfn);
        if (strcmp(dirsfn, dirlfn)) {
            printf("              %s\n", dirlfn);
        }
        printf("\n");
    }
    for (pass=1; pass<=3; pass++) {
        strcpy(lfn, "");
        for (i=0; i<dirsize/sizeof(union tdirent); i++) {
	    memcpy(&dirent, &dir[i], sizeof(union tdirent));
            if (dirent.s.name[0] == '\0') {
                strcpy(lfn, "");
                break;
            }
            if (dirent.s.name[0] == 0xE5) {
                strcpy(lfn, "");
                continue;
            }
            if (dirent.s.attr == ATTR_LFN) {
                if (dirent.l.deleted) {
                    strcpy(lfn, "");
                    continue;
                }
                if (dirent.l.last) {
                    lfn[dirent.l.seqnum * 13] = '\0';
                }
                lfn[(dirent.l.seqnum-1) * 13] = conv_uni2local(dirent.l.c0);
                lfn[(dirent.l.seqnum-1) * 13 + 1] = conv_uni2local(dirent.l.c1);
                lfn[(dirent.l.seqnum-1) * 13 + 2] = conv_uni2local(dirent.l.c2);
                lfn[(dirent.l.seqnum-1) * 13 + 3] = conv_uni2local(dirent.l.c3);
                lfn[(dirent.l.seqnum-1) * 13 + 4] = conv_uni2local(dirent.l.c4);
                lfn[(dirent.l.seqnum-1) * 13 + 5] = conv_uni2local(dirent.l.c5);
                lfn[(dirent.l.seqnum-1) * 13 + 6] = conv_uni2local(dirent.l.c6);
                lfn[(dirent.l.seqnum-1) * 13 + 7] = conv_uni2local(dirent.l.c7);
                lfn[(dirent.l.seqnum-1) * 13 + 8] = conv_uni2local(dirent.l.c8);
                lfn[(dirent.l.seqnum-1) * 13 + 9] = conv_uni2local(dirent.l.c9);
                lfn[(dirent.l.seqnum-1) * 13 + 10] = conv_uni2local(dirent.l.c10);
                lfn[(dirent.l.seqnum-1) * 13 + 11] = conv_uni2local(dirent.l.c11);
                lfn[(dirent.l.seqnum-1) * 13 + 12] = conv_uni2local(dirent.l.c12);
                cksum = dirent.l.cksum;
                continue;
            }
            if (dirent.s.attr & ATTR_VOL) {
                strcpy(lfn, "");
                continue;
            }
            if (strlen(lfn)) {
                if (cksum != sfncksum(dirent.s.name))
                    strcpy(lfn, "");
            }
	    if (dirent.s.name[0] == 0x05)
	        dirent.s.name[0] = 0xE5;
	    conv_fat2local(dirent.s.name, 8);
	    conv_fat2local(dirent.s.ext, 3);
            strcpy(subdirsfn, dirsfn);
            strcpy(subdirlfn, dirlfn);
            if (subdirsfn[strlen(subdirsfn)-1] != SLASH[0])
                strcat(subdirsfn, SLASH);
            if (subdirlfn[strlen(subdirlfn)-1] != SLASH[0])
                strcat(subdirlfn, SLASH);
            strncpy(sfn, dirent.s.name, 8);
            sfn[8] = '\0';
            for (j=7; j>=0; j--) {
                if (sfn[j] == ' ') {
                    sfn[j] = '\0';
                } else {
                    break;
                }
            }
            strncpy(tmpext, dirent.s.ext, 3);
            tmpext[3] = '\0';
            for (j=2; j>=0; j--) {
                if (tmpext[j] == ' ') {
                    tmpext[j] = '\0';
                } else {
                    break;
                }
            }
            if (strlen(tmpext)) {
                strcat(sfn, ".");
                strcat(sfn, tmpext);
            }
            if (extr) {
                strlwrl(sfn);
                if (locase) {
                    strlwrl(lfn);
                }
            }
            strcat(subdirsfn, sfn);
            if (strlen(lfn)) {
                strcat(subdirlfn, lfn);
            } else {
                strcat(subdirlfn, sfn);
		if (extr) {
		    strcpy(lfn, sfn);
		}
            }

            if (pass==1) {
	    /* list/create subdirectories */
                if (!(dirent.s.attr & ATTR_DIR)) {
                    strcpy(lfn, "");
                    continue;
                }
                if (extr) {
		    if (!extdirs) continue;
                    extrfile(&dirent, &subdirsfn[1], &subdirlfn[1]);
                } else {
                    dispfile(&dirent, lfn);
                }
            }
            if (pass==2) {
	    /* list/extract regular files */
                if (dirent.s.attr & ATTR_DIR) {
                    strcpy(lfn, "");
                    continue;
                }
                if (extr) {
		    if (extdirs) {
                        extrfile(&dirent, &subdirsfn[1], &subdirlfn[1]);
		    } else {
		        extrfile(&dirent, sfn, lfn);
		    }
                } else {
                    dispfile(&dirent, lfn);
                }
            }
            if (pass==3) {
	    /* recurse subdirectories */
                if (!(dirent.s.attr & ATTR_DIR)) {
                    strcpy(lfn, "");
                    continue;
                }
                if (dirent.s.name[0] == '.') {
                    strcpy(lfn, "");
                    continue;
                }
                scantree(dirent.s.clust, subdirsfn, subdirlfn);
            }
            strcpy(lfn, "");
        }
    }
    free(dir);
}

int main(int argc, char *argv[])
{
    int i;
    __u8 dummy;

    nlsinit(CODEPAGE_LOCAL, CODEPAGE_FAT);
    setbuf(stdout, NULL);
    printf("\n");
    err = parsecmdline(argc, argv);
    if (err) {
        help();
        return 1;
    }
    if (!forceoffs) {
        imgoffs = findimg();
        if (imgoffs < 0) {
            return 1;
        }
    }
    imgfmt = chkfmt();
    if (imgfmt < 0) {
        return 1;
    }
    if (imgfmt == FMT_DDI) {
        err = readddimap();
        if (err) {
            return 1;
        }	
    }
    err = readbootsec();
    if (err) {
        return 1;
    }
    err = readmediaid();
    if (err) {
        return 1;
    }
    switch (mediaid) {
        case MEDIA_D8:
	    bootsect.bytesec = 512;
	    bootsect.secclu = 2;
	    bootsect.fatcps = 2;
	    bootsect.rootent = 112;
	    bootsect.totalsc = 640;
	    bootsect.secfat = 1;
	    bootsect.sectrack = 8;
	    break;
	case MEDIA_S8:
	    bootsect.bytesec = 512;
	    bootsect.secclu = 1;
	    bootsect.fatcps = 2;
	    bootsect.rootent = 64;
	    bootsect.totalsc = 320;
	    bootsect.secfat = 1;
	    bootsect.sectrack = 8;
	    break;
	default: ;
    }
    if (bootsect.secclu == 0) {
        printf("It isn't FAT12 disk image\a\n");
	return 1;
    }
    if (bootsect.totalsc/bootsect.secclu > 4078) {
        printf("It isn't FAT12 disk image\a\n");
	return 1;
    }
    if (imgfmt == FMT_DDI) {
        imgoffs = bootsect.bytesec * bootsect.sectrack;
    }
    if (bootsect.fatcps == 2) {
        err = comparefats(bootsect.bytesec,
                  bootsect.bytesec + bootsect.secfat*bootsect.bytesec,
                  bootsect.secfat*bootsect.bytesec);
        if (err) {
            return 1;
        }
    }
    fatentr = bootsect.secfat * bootsect.bytesec / 3 * 2;
    fat = malloc(fatentr*sizeof(unsigned short));
    if (!fat) {
        printf("Unable to allocate memory\a\n");
        exit(1);
    }
    err = readfat(fatentr);
    if (err) {
        return 1;
    }
    f = fopen(imgfile, "rb");
    if (!f) {
        printf("Unable to open %s: %s\a\n", imgfile, strerror(errno));
        return 1;
    }
    fseek(f, imgoffs + (long) bootsect.bytesec * bootsect.totalsc - 1,
          SEEK_SET);
    if (fread(&dummy, 1, sizeof(dummy), f)) {
        shortened = 0;
    } else {
        shortened = 1;
    }    
    printf(" %s: ", imgfile);
    if (imgfmt == FMT_DDI) {
        printf("DiskDupe file, ");
    }
    printf("%ldKB disk image ",
           (long) bootsect.bytesec * bootsect.totalsc / 1024);
    if (shortened) {
        printf("(shortened) ");
    }
    printf("at offset 0x%lX\n", imgoffs);
    /* treat volume label containing non-printable characters as invaild */
    for (i=0; i<sizeof(bootsect.volumel); i++) {
        if (bootsect.volumel[i] < ' ')
            bootsect.volumel[0] = '\0';
    }
    printf(" Volume Label: %.11s\n", bootsect.volumel);
    printf(" Volume Serial Number: %04X-%04X\n",
           bootsect.volumen[1], bootsect.volumen[0]);
    if (extr)
        printf("\n");
    scantree(0, SLASH, SLASH);
    fclose(f);
    if (extr) {
        printf("\n");
        if (errtot) {
            printf("Total errors: %d\n", errtot);
        } else {
            printf("All OK\n");
        }
    }
    #ifdef __linux__
    printf("\n");
    #endif
    return 0;
}
