/*

    Program to read in a copyqm file, and expand it into a .dd output file.
    Input is file, output will be file.dd and file.txt

    .dd is the raw disk

    Usage:

    expandqm [/n] [/v] [/f] [/oOutputBase] file.ext

    Where file.ext is a copyqm image file.
    It will expand file.ext to 2 files

    file.txt (Which has the Copyqm information, including comment, etc)
    from file.ext

    and

    file.dd which has the raw disk contents from file.ext
    suitable for writing to a disk using dd, or rwfloppy.

    /n - Will cause the disk not to be expanded to it's full size. by
         default the program will expand the disk image to it's original
         size.

    /v - Verbose

    /oOutputFile - Change output file name (Can have it created in alternate directory,
         with different name)

    /f - Allow output file to be overwritten.

    Compiled with Turbo-C 2.0
    Also compiles with djgpp


*/

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <ctype.h>
#include <string.h>

#ifdef __TURBOC__
#include <dos.h>
#endif

#define MAXPATH 500

#define TRUE    1
#define FALSE   0

typedef int BOOL;
typedef unsigned char BYTE;

/* Global values */

BOOL    blnExpand=TRUE;
BOOL    blnVerbose;
BOOL    blnForce;   /* Force overwrite */
char * szInputFile;
char * szOutBase;   /* Base Name of the output file (If specified) */



/* Header structure */
#pragma pack(1)

typedef struct SydexHeader
{
    char chID[2];         /* 0,1 */
    short nVersion;      /* 2,3 */
    char Ver2;          /* 4 */

    /* This section only valid on non blind reads */
    char Unk1;          /* 5 */
    char Unk2;          /* 6 */
    short nSecSize;      /* 7, 8 */
    short Unk3;       /* 9,A */
    short nSectors;     /* B,C Number of sectors */
    BYTE bID;           /* D Disk id byte (Maybe) */

    /* Valid most times */

    short Unk4;             /* E,F */
    short nSectPerTrack;    /* 10,11 */
    short nHeads;           /* 12,13 */
    short Unk5;             /* 14,15 16,17 18,19 1A,1B */
    short Unk6;
    short Unk7;
    short Unk8;
    char szDesc[60];        /* Maybe 1C-57 */
    BYTE bBlind;            /* 1 = blind  58 */
    BYTE bDensity;          /* 1 = hD 59 */
    BYTE bUsedTracks;       /* Tracks In image 5A */
    BYTE bTotalTracks;      /* Tracks on disk 5B */
    char Unk10[4];          /* 5C, 5F */
    char cLabel[11];       /* 60, 6A */
    unsigned short timeRecorded;    /* Time Written 6B,6C */
    unsigned short datRecorded;     /* Date Written 6D,6E */
    short nCommentLen;      /* Length of comment 6F,70*/
    char Unk11[19];         /* 71-83 */
    BYTE bChecksum;         /* 84 */
} SydexHeader;

BOOL ReadCheckHeader(FILE *fpIn, SydexHeader *pSH, char ** szComment);
BOOL WriteHeader(char *szOutName, char *szComment, SydexHeader *pSH);
BOOL WriteData(FILE *fpIn, char *szOutName, SydexHeader *pSH);
BOOL scanargs(int argc, char ** argv);
int usage();
BOOL exists(char *name);


#ifndef __TURBOC__

/*
    Convert a file time to a time_t.
    (djgpp version)
*/

time_t TimeFromFTime(unsigned short nTime, unsigned short nDate)
{
    struct tm tm;

    tm.tm_sec = (nTime & 0x31) * 2;
    tm.tm_min = (nTime >> 5) & 63;
    tm.tm_hour = ((nTime >> 11) & 31);
    tm.tm_isdst = 1;
    tm.tm_year = ((nDate >> 9) & 127) + 80; /* Dos date from 1980 on */
    tm.tm_mon = ((nDate >> 5) & 15) - 1;
    tm.tm_mday = nDate & 31;
    tm.tm_isdst = 0;

    return mktime(&tm);
}

#else

time_t TimeFromFTime(unsigned short nTime, unsigned short nDate)
{

    struct date da;
    struct time ti;

    ti.ti_hund=0;
    ti.ti_sec=(nTime & 0x31) * 2;
    ti.ti_min = (nTime >> 5) & 63;
    ti.ti_hour = (nTime >> 11) & 31;

    da.da_day = nDate & 31;
    da.da_mon = (nDate >> 5) & 15;
    da.da_year = ((nDate >> 9) & 127) + 1980;

    return (time_t) dostounix(&da, &ti);
}


#endif /* __DJGPP__ */




int main(int argc, char **argv)
{

    /*
        read in a sydex header
    */


    SydexHeader shHeader;
    FILE * fpIn, *fp2;
    BYTE * bptr;
    int x, y;
    char * szComment,*cp;
    char * szInBuf, *szOutBuf;
    time_t tRecorded;
    char szTmpBuf[MAXPATH];
    char szOutTxt[MAXPATH];
    char szOutDD[MAXPATH];          /* Name of .dd output file */

    if(!scanargs(argc,argv))
        exit(1);

    if(!szInputFile)
        return usage();

    /* Get name of output files */

    if(szOutBase)
    {
        strcpy(szTmpBuf, szOutBase);
    }
    else    /* Extract the Name from the input file */
    {

        strcpy(szTmpBuf, szInputFile);

        if((cp=strrchr(szTmpBuf,'\\')) || (cp=strrchr(szTmpBuf,'/')) || (cp=strrchr(szTmpBuf,':')) )
            cp++;            /* move past path section of name (if any ) */
        else
            cp=szTmpBuf;

        if(cp=strrchr(cp,'.'))
            *cp='\0'; /* remove any extension specification in input file */
    }

    sprintf(szOutTxt,"%s.Txt",szTmpBuf);  /* Name of information file */
    sprintf(szOutDD,"%s.DD",szTmpBuf);  /* name of data file */

    if(blnVerbose)
    {
        printf(
         "    In = %s\n"
         "Out DD = %s\n"
         "Out Txt= %s\n"
         "\n",
         szInputFile,
         szOutDD,
         szOutTxt
         );
    }

    if(!blnForce)   /* Check files don't exist if not force */
    {
        if(exists(szOutTxt) || exists(szOutDD))
        {
            printf("Output file exists, use /f to force overwrite\n");
            exit(1);
        }
    }



    if(!(fpIn=fopen(szInputFile,"rb")))
    {
        printf("Couldn't open file %s for reading.\n", szInputFile);
        exit(1);
    }

    if(szInBuf = malloc(16384))
        setvbuf(fpIn,szInBuf, _IOFBF, 16384);

    if(!ReadCheckHeader(fpIn, &shHeader, &szComment))
        exit(1);

    if(!WriteHeader(szOutTxt, szComment, &shHeader))
        exit(1);

    if(!WriteData(fpIn, szOutDD, &shHeader))
        exit(1);

    if(blnVerbose)
        printf("Successful\n");

    return 0;
}


/* Extract the data */
BOOL WriteData(FILE *fpIn, char *szOutName, SydexHeader *pSH)
{
    FILE *fpOut;
    char *szOutBuf;
    long lOutLen,lMaxLen;

    /* Length of output file */
    lMaxLen = (long) pSH->nSectPerTrack * (long)pSH->bTotalTracks *
        (long) pSH->nHeads * 512L;

    if(!(fpOut=fopen(szOutName,"wb")))
    {
        printf("Error Opening Output file %s\n", szOutName);
        return FALSE;
    }

    if(szOutBuf = malloc(16384))
        setvbuf(fpOut,szOutBuf, _IOFBF, 16384);

    lOutLen=0;

    for(;;)
    {
        short nLen;
        int c;
        if(fread(&nLen,1,sizeof(nLen),fpIn) != sizeof(nLen))
            break;

        /* -ve = Repeated characters */
        if(nLen < 0)
        {
            lOutLen += abs(nLen);

            c=getc(fpIn);
            for(;nLen;nLen++)
                putc(c, fpOut);
        }
        else    /* Positive = block of differing characters */
        {
            for(;nLen;nLen--,lOutLen++)
                putc(getc(fpIn),fpOut);
        }
    }

    if(lOutLen & 0xff)
    {
        printf("Input file length is invalid\n");
        return FALSE;
    }

    if(blnExpand) /* Want expanded to full length (Default) */
    {
        for(;lOutLen < lMaxLen; lOutLen++)    /* Fill out file, with default format char */
            putc(0xf6,fpOut);
    }

return TRUE;
}

/* Dump header to specified output stream (Maybe console) */

void DumpHeader(FILE *fpOut, SydexHeader *pSH, char *szComment)
{

    time_t tRecorded;
    tRecorded = TimeFromFTime(pSH->timeRecorded, pSH->datRecorded);

    fprintf(fpOut,"Copy        : %s\n", pSH->bBlind ? "Blind" : "Normal");
    fprintf(fpOut,"Disk type   : %s\n", pSH->szDesc);
    fprintf(fpOut,"Label       : %0.11s\n", pSH->cLabel);
    fprintf(fpOut,"Sectors     : %d\n", pSH->nSectPerTrack);
    fprintf(fpOut,"Tracks Used : %d\n", pSH->bUsedTracks);
    fprintf(fpOut,"Tracks Total: %d\n", pSH->bTotalTracks);
    fprintf(fpOut,"Sides       : %d\n", pSH->nHeads);
    fprintf(fpOut,"Density     : %s\n", (pSH->bDensity) ? "High" : "Double");
    fprintf(fpOut,"Recorded    : %s\n", ctime(&tRecorded));
    fprintf(fpOut,"Comment     : %s\n", szComment);
}

/* Write out the header information to the second file */
BOOL WriteHeader(char *szOutName, char * szComment, SydexHeader *pSH)
{

    FILE *fpOut;
    time_t tRecorded;

    fpOut = fopen(szOutName,"w"); /* Text Mode */
    if(!fpOut)
    {
        printf("Error opening output file %s\n", szOutName);
        return FALSE;
    }

    DumpHeader(fpOut, pSH, szComment);

    close(fpOut);
    return TRUE;
}


/* Read and check the file header */

BOOL ReadCheckHeader(FILE *fpIn, SydexHeader *pSH, char ** szComment)
{
    int y,x;
    char *cp;
    BYTE *bptr;
    time_t tRecorded;

    if(fread(pSH, 1, sizeof(*pSH), fpIn) != sizeof(*pSH))
    {
        printf("Input file too short\n");
        return FALSE;
    }

    if(pSH->chID[0] != 'C' || pSH->chID[1] != 'Q')
    {
        printf("Input file has Invalid ID field\n");
        return FALSE;
    }

    y=0;
    for (x=0, bptr=(BYTE *) pSH; x < sizeof(*pSH) ; x++, bptr++)
        y += (unsigned int) *bptr;

    y = y & 0xff;
    if(y)
    {
        printf("Input File Checksum Error\n");
        return FALSE;
    }

    /* Read comment */

    *szComment = malloc(pSH->nCommentLen+1);
    if(fread(*szComment, 1, pSH->nCommentLen, fpIn) != pSH->nCommentLen)
    {
        printf("Input File too short\n");
        return FALSE;
    }

    /* Comments have 0's where new lines live, so make them new lines */
    for(cp=*szComment;cp<*szComment+pSH->nCommentLen;cp++)
        if(*cp == '\0')
            *cp='\n';

    *cp='\0';

    if(blnVerbose)
    {
        DumpHeader(stdout, pSH, *szComment);
    }

    return TRUE;
}



/* Scan in arguments */
#define MAXNARGS 8

BOOL scanargs(int argc, char ** argv)
{
    int x,t;
    register char *ptr;
    /* scan the argument string program was called with & convert to files & set option
    switches etc */

    if(argc > MAXNARGS)
        {
        printf("\nToo many arguments to program ");
        goto exit1;
        }

    for(x=1;x<argc;x++)
        {
        ptr=argv[x]; /* scan arguments for option switches and input & o/p files */
        if(*ptr == '/')
            {
            ptr++;
            switch (tolower(*ptr))
                {

                case 'n':
                    blnExpand = FALSE;
                    continue;

                case 'v':
                    blnVerbose = TRUE;
                    continue;

                case 'f':
                    blnForce = TRUE;
                    continue;

                case 'o':
                    szOutBase = ptr+1;
                    continue;

                case 'h':
                case '?':
                    usage();
                    goto exit1;

                default:
                    printf("\n Invalid switch '%s' was detected\n",ptr);
                    goto exit1;
                }
            }/* end of / if */

        if(!szInputFile) /* input name hasnt been set up yet */
            {
            szInputFile=ptr; /* must have been a file name if no / in front */
            continue;
            }

        } /* end of for loop */

    return TRUE; /* if get here must be ok */

exit1:
    return FALSE; /* bad return */
}

int usage()
{
    printf("\nExpandQM usage: ExpandQM [/v] [/n] [/f] [/oOutBase] ImageFile\n"
    "\n"
    "ExpandQM is a program to expand a COPYQM image file into a raw dump file\n"
    "Which can be dd'd to a floppy, or mounted using diskfile\n"
    "\n"
    "It creates 2 files, filename.dd, and filename.txt\n"
    "filename.dd contains the binary data, filename.txt contains comments,\n"
    "copyqm info etc\n"
    "\n"
    "Options:\n"
    "  /v         - Verbose\n"
    "  /oOutBase  - Output file has this base name (with .dd, .txt appended)\n"
    "  /n         - Output file is not expanded to it's original full size\n"
    "  /f         - Allow overwriting of existing output files\n"
    );
    return 1;
}


BOOL exists(char *name)
{
int fd;

/* See if file exists by opening it */

if((fd=open(name,0)) == -1)
    return FALSE;
close(fd);
return TRUE; /* file did exist */
}

