/*
 * DMS-check: by ????????, the unknown programmer
 *
 * This is a program for checking "dms" files on unix systems.
 * Its usage is:
    dmscheck <option> files ...
 *
 * Options are:
    -h or -? : little help
    -s : silent mode. No output at all, only status returned.
    -v : verbose mode. Gives the content of the file.
    -d : debug mode. Very verbose.

 * In standart mode, you only get a message "FILE GOOD" or "FILE BAD"
 * for each file you specify.
 * The code is pretty basic Unix, there should be no compiling problem,
 * just do "cc dmscheck.c -o dmscheck" to get the executable.
 * Care was taken to read and use the data as char arrays to avoid
 * alignement problems inside structures (a main portability nuisance).
 *
 * NB: People with little endian machines should define LITTLEINDIAN below.
 *     If you're not sure then try out normally. If it fails/coredumps, try with
 *     debug flag (-d) and see if CRC's are backwards. If yes: define it.
 * [little-endian compatibility by Geoff C. Wing (gwing@mullauna.cs.mu.oz.au)]
 * that's right, blame it on me if it doesn't work anymore for big-endians :-)
 *
 * TODO: a bit more error checking, I reckon.
 */
/* #define LITTLEINDIAN /* for little-endian machines */

#include <stdio.h>
#include <sys/types.h>
#include <sys/file.h>

#define BOXSIZE	5

#define MAXTRACKS 85

typedef char BYTE;
typedef unsigned char UBYTE;
typedef short SHORT;
typedef unsigned short USHORT;
typedef long LONG;
typedef unsigned long ULONG;

#define SILENT 0
#define NORMAL 1
#define VERBOSE 2
#define DEBUG  3

int MessageLevel = NORMAL ;

#define NOERROR 0
#define NONFATAL 1
#define FATAL 2

int BadFileFlag = 0 ;
int GlobalStatus = 0 ;

int FirstTrack = 0 ;
int LastTrack = 0 ;

char Tracks[MAXTRACKS] ;

char IoBuf[60000] ;
USHORT Crc ;

/* Format of a simple DMS file:

	Ident File_Head CRC Track_Head CRC Data Track_Head CRC Data  ....

*/

char Ident[6] ;			/* "DMS!" */

/* 
   These macros are used to access the file header components
   with no concern about the alignement requirements of each machine.
*/

#ifdef LITTLEINDIAN
#define little_swap_short(a) (((a & 0x00ff) << 8) \
			    + ((a & 0xff00) >> 8))
#define little_swap_long(a)  (((a & 0x000000ff) << 24) \
			    + ((a & 0x0000ff00) <<  8) \
			    + ((a & 0x00ff0000) >>  8) \
			    + ((a & 0xff000000) >> 24)) 
#else /* not LITTLEINDIAN */
#define little_swap_short(a)  (a)
#define little_swap_long(a)   (a)
#endif /* not LITTLEINDIAN */

#define DHEAD_FROM(tab)  (SHORT)little_swap_short(*((SHORT *) (tab+12)))
#define DHEAD_TO(tab)    (SHORT)little_swap_short(*((SHORT *) (tab+14)))
#define DHEAD_CSIZE(tab) (ULONG)little_swap_long(*((ULONG *) (tab+16)))
#define DHEAD_SIZE(tab)  (ULONG)little_swap_long(*((ULONG *) (tab+20)))
#define DHEAD_MODE(tab)  (SHORT)little_swap_short(*((SHORT *) (tab+48)))

char DHeader[50] ;

struct File_Head		/*offset - sizeof(File_Head) = 50 */
{
	long u1  ;		/* 0 -  ???? */
	long u2  ;		/* 4 -  ???? */
	long date ;		/* 8 - Creation date of file */
	short from ;		/*12 - index of first track */
	short to ; 		/*14 - index of last track */
	ULONG  size_after ;	/*16 - total size of data after compression */
	ULONG  size_before ;	/*20 - total size of data before compression */
	long u3 ;		/*24 -  0 */
	short cpu ;		/*28 -  Cpu type ( 1:68000, 2:68010, ... ) */
	short copross ; 	/*30 -  Cpu coprocessor ( 1:68881 ) */
	short machine ;		/*32 -  Machine used ( 1:Amiga, 2:PC, ...) */
	short u4 ;		/*34 -  ??? */
	short cpu_speed ;	/*36 -  */
	long time ;		/*38 -  Time to create archive in sec. */
	short c_version ;	/*42 -  Version of creator (66, 67, 68) */
	short n_version ;	/*44 -  Version needed to extract */
	short disk_type ;	/*46 -  Disktype of archive */
	short cmode ;		/*48 -  compression mode 0-4 */
} ;

char THeader[18] ;

#define THEAD_IDENT(tab) (USHORT)little_swap_short(*((USHORT *) (tab+0 )))
#define THEAD_TNUM(tab)  (SHORT)little_swap_short(*((SHORT *)  (tab+2 )))
#define THEAD_SIZE(tab)  (ULONG)little_swap_long(*((ULONG *)  (tab+4 )))
#define THEAD_DCRC(tab)  (USHORT)little_swap_short(*((USHORT *) (tab+16)))

struct Track_Head		/*offset -  sizeof(Track_Head) = 18 */
{
	short delim ;		/* 0 - delimiter, 0x5452, is 'TR' */
	short number ;		/* 2 - track number, -1 if text */
	ULONG size ;		/* 4 - size of data part */
	USHORT plength ;	/* 8 - length of non-encoded data */
	USHORT ulength ;	/*10 - length of encoded data */
	short mode ;		/*12 - encryption mode(0: simple, 102: quick)*/
	USHORT usum ;		/*14 - raw data checksum */
	USHORT dcrc ;		/*16 - data CRC */
} ;


static USHORT CRCTab[256]=
{
	0x0000,0xC0C1,0xC181,0x0140,0xC301,0x03C0,0x0280,0xC241,
	0xC601,0x06C0,0x0780,0xC741,0x0500,0xC5C1,0xC481,0x0440,
	0xCC01,0x0CC0,0x0D80,0xCD41,0x0F00,0xCFC1,0xCE81,0x0E40,
	0x0A00,0xCAC1,0xCB81,0x0B40,0xC901,0x09C0,0x0880,0xC841,
	0xD801,0x18C0,0x1980,0xD941,0x1B00,0xDBC1,0xDA81,0x1A40,
	0x1E00,0xDEC1,0xDF81,0x1F40,0xDD01,0x1DC0,0x1C80,0xDC41,
	0x1400,0xD4C1,0xD581,0x1540,0xD701,0x17C0,0x1680,0xD641,
	0xD201,0x12C0,0x1380,0xD341,0x1100,0xD1C1,0xD081,0x1040,
	0xF001,0x30C0,0x3180,0xF141,0x3300,0xF3C1,0xF281,0x3240,
	0x3600,0xF6C1,0xF781,0x3740,0xF501,0x35C0,0x3480,0xF441,
	0x3C00,0xFCC1,0xFD81,0x3D40,0xFF01,0x3FC0,0x3E80,0xFE41,
	0xFA01,0x3AC0,0x3B80,0xFB41,0x3900,0xF9C1,0xF881,0x3840,
	0x2800,0xE8C1,0xE981,0x2940,0xEB01,0x2BC0,0x2A80,0xEA41,
	0xEE01,0x2EC0,0x2F80,0xEF41,0x2D00,0xEDC1,0xEC81,0x2C40,
	0xE401,0x24C0,0x2580,0xE541,0x2700,0xE7C1,0xE681,0x2640,
	0x2200,0xE2C1,0xE381,0x2340,0xE101,0x21C0,0x2080,0xE041,
	0xA001,0x60C0,0x6180,0xA141,0x6300,0xA3C1,0xA281,0x6240,
	0x6600,0xA6C1,0xA781,0x6740,0xA501,0x65C0,0x6480,0xA441,
	0x6C00,0xACC1,0xAD81,0x6D40,0xAF01,0x6FC0,0x6E80,0xAE41,
	0xAA01,0x6AC0,0x6B80,0xAB41,0x6900,0xA9C1,0xA881,0x6840,
	0x7800,0xB8C1,0xB981,0x7940,0xBB01,0x7BC0,0x7A80,0xBA41,
	0xBE01,0x7EC0,0x7F80,0xBF41,0x7D00,0xBDC1,0xBC81,0x7C40,
	0xB401,0x74C0,0x7580,0xB541,0x7700,0xB7C1,0xB681,0x7640,
	0x7200,0xB2C1,0xB381,0x7340,0xB101,0x71C0,0x7080,0xB041,
	0x5000,0x90C1,0x9181,0x5140,0x9301,0x53C0,0x5280,0x9241,
	0x9601,0x56C0,0x5780,0x9741,0x5500,0x95C1,0x9481,0x5440,
	0x9C01,0x5CC0,0x5D80,0x9D41,0x5F00,0x9FC1,0x9E81,0x5E40,
	0x5A00,0x9AC1,0x9B81,0x5B40,0x9901,0x59C0,0x5880,0x9841,
	0x8801,0x48C0,0x4980,0x8941,0x4B00,0x8BC1,0x8A81,0x4A40,
	0x4E00,0x8EC1,0x8F81,0x4F40,0x8D01,0x4DC0,0x4C80,0x8C41,
	0x4400,0x84C1,0x8581,0x4540,0x8701,0x47C0,0x4680,0x8641,
	0x8201,0x42C0,0x4380,0x8341,0x4100,0x81C1,0x8081,0x4040
};

USHORT DoBlockCRC(Mem, Size)
UBYTE* Mem;
int Size ;
{
	register USHORT CRC = 0;

	while(Size--)
		CRC = CRCTab[((CRC ^ *Mem++) & 255)] ^ ((CRC >> 8) & 255);

	return (CRC) ;
}

#define MAXFILES 250 

char * ListOfFiles[MAXFILES] ;
int NumberOfFiles ;

static void AppendFileName(f)
char * f;
{
	if ( NumberOfFiles >= MAXFILES )
	{
		printf("\tToo many files. Max is %d\n",MAXFILES) ;
		exit(1) ;
	}
	else
	{
		ListOfFiles[NumberOfFiles++] = f ;
	}
}

static void ReadError(s,n)
char* s;
int n;
{
	if ( MessageLevel >= VERBOSE )
	    printf("\tError reading %s (%d char read)\n",s,n) ;
}

static void WhereInFile(fd,s)
int fd ;
char *s;
{
	int init;

	init = lseek(fd, 0L, L_INCR) ;
	if ( MessageLevel >= DEBUG )
	    printf("\tStarting read of %s at offset %d\n",s,(int) init ) ;
}

/*
 * Handling track data
 *
 */

static void ReadTrackHeader(fd)
int fd ;
{
	int n_read ;

	WhereInFile(fd,"Track header") ;

	n_read = read(fd, THeader, sizeof(THeader) ) ;
	if ( n_read != sizeof(THeader)  )	
	{
	    ReadError("Theader DATA",n_read) ;
	    BadFileFlag = FATAL;
	    return;
	}

	n_read = read(fd, (char *) &Crc, 2) ;
	if ( n_read != 2 )	
	{
	    ReadError("THeader CRC",n_read) ;
	    BadFileFlag = FATAL;
	}
	Crc = little_swap_short(Crc);
}

static void VerifyTrackHeader()
{
	USHORT crc ;

	if ( THEAD_IDENT(THeader) != 0x5452 )	/* Magic string "TR" */
	{
	    if ( MessageLevel >= VERBOSE )
	        printf("\tBAD THeader identifier: %4x\n",
					(int) THEAD_IDENT(THeader) );

	    BadFileFlag = FATAL ;
	    return ;
	}

	crc = DoBlockCRC( THeader, sizeof(THeader) ) ;

	if ( crc != Crc )
	{
	    if ( MessageLevel >= VERBOSE )
	        printf("\tBAD THeader checksum. Read %4x, computed %4x\n",
		       				(int) Crc, (int) crc ) ;
	    BadFileFlag = FATAL ;	/* size is not sure */
	}
	else if ( MessageLevel >= DEBUG )
	    printf("\tTrack Header checksum OK (%4x)\n",(int) crc ) ;
}

static void ReadTrackData(fd)
int fd ;
{
	int n_read ;

	if ( MessageLevel >= DEBUG )
            printf("\tSize of track data: %d\n", (int) THEAD_SIZE(THeader) );

	n_read = read(fd, IoBuf, (int) THEAD_SIZE(THeader)) ;
	if ( n_read != THEAD_SIZE(THeader) )	
	{
	    ReadError("track data",n_read) ;
	    BadFileFlag = FATAL ; 
	    return;
	}
}


static void VerifyTrackData()
{
	USHORT crc ;

	crc = DoBlockCRC( IoBuf, (int) THEAD_SIZE(THeader) );

	if ( crc != THEAD_DCRC(THeader) )
	{
	    if ( MessageLevel >= VERBOSE )
	    {
                printf("\tTrack: %2d ", (int) THEAD_TNUM(THeader)) ;
		printf("-BAD CRC: read %4x, computed %4x\n",
				(int) THEAD_DCRC(THeader), (int) crc ) ;
	    }
	    BadFileFlag = NONFATAL ;
	}
	else if ( MessageLevel >= VERBOSE )
	{
            printf("\tTrack: %2d ", (int) THEAD_TNUM(THeader)) ;
	    printf("-GOOD CRC: %4x\n",(int) crc ) ;
	}
}

static void UpdateTrackCount()
{
	Tracks[(int) THEAD_TNUM(THeader)] = 1 ;
}

static void TreatTrackHunk(fd)
int fd ;
{
	ReadTrackHeader(fd) ;

	if ( BadFileFlag != FATAL )
		VerifyTrackHeader() ;
	
	if ( BadFileFlag != FATAL )
		ReadTrackData(fd) ;

	if ( BadFileFlag != FATAL )
		VerifyTrackData() ;

	if ( BadFileFlag != FATAL )
		UpdateTrackCount() ;
}

static int MoreTrackToRead()
{
	return (( Tracks[LastTrack] ) ? 0 : 1) ;
}

static void TreatTracks(fd)
int fd ;
{
	while ( BadFileFlag != FATAL && MoreTrackToRead() )
		TreatTrackHunk(fd) ;
}

static void InitialiseTrackData()
{
	int i;

	BadFileFlag = 0 ;

	for ( i= 0; i< MAXTRACKS; i++ )
		Tracks[i] = 0 ;
}

/*
 * Handling one file dms header
 *
 */

void VerifyDHeader()
{
	USHORT crc ;

	if ( strncmp(Ident, "DMS!", 4 ) )
	{
	    if ( MessageLevel >= NORMAL )
		printf("\tThis is *not* a dms archive\n") ;
	    BadFileFlag = FATAL ; 
	    return;
	}

	crc = DoBlockCRC(DHeader, sizeof(DHeader) ) ;

	if ( crc != Crc )
	{
	    if ( MessageLevel >= VERBOSE )
		printf("\tBAD file header checksum. Read %4x, computed %4x\n",
					(int) Crc, (int) crc) ;
	    BadFileFlag = FATAL ;
	}
	else if ( MessageLevel >= DEBUG )
	    printf("\tDms Header checksum OK (%4x = %4x)\n",
						(int) crc, (int) Crc );
}

static void UpdateFileData()
{

	if ( MessageLevel >= VERBOSE )
	{
	    printf("\tContains tracks: %d to %d\n",
			(int) DHEAD_FROM(DHeader), (int) DHEAD_TO(DHeader) );

	   printf("\tCompression mode: %d\n", (int) DHEAD_MODE(DHeader)) ;
	   printf("\tData sizes: raw: %d     compressed: %d\n",
			(int) DHEAD_SIZE(DHeader), (int) DHEAD_CSIZE(DHeader));
	}

	FirstTrack = (int) DHEAD_FROM(DHeader) ;
	LastTrack = (int) DHEAD_TO(DHeader) ;
}

void ReadDHeader(fd)
int fd ;
{
	int n_read ;

	WhereInFile(fd,"Ident") ;

	n_read = read(fd, Ident, 4) ;
	if ( n_read < 4 )	
	{
	    ReadError("Ident",n_read) ;
	    BadFileFlag = FATAL ; 
	    return ;
	}

	WhereInFile(fd,"DHeader") ;

	n_read = read(fd, DHeader, sizeof(DHeader)) ;
	if ( n_read < sizeof(DHeader) )	
	{
	    ReadError("DHeader DATA",n_read) ;
	    BadFileFlag = FATAL ; 
	    return ;
	}

	n_read = read(fd, (char *) &Crc, 2) ;
	if ( n_read < 2 )	
	{
	    ReadError("DHeader CRC",n_read) ;
	    BadFileFlag = FATAL ;
	}
	Crc = little_swap_short(Crc);
}

void TreatDHeader(fd)
int fd ;
{
	ReadDHeader(fd) ;

	if ( BadFileFlag != FATAL )
	    VerifyDHeader() ;

	if ( BadFileFlag != FATAL )
	    UpdateFileData() ;
}

/*
 * Handling one file
 *
 */

static void TreatFile(file_)
char * file_ ;
{
	int fd ;

	if ( (fd = open(file_, 0)) == -1 )
	{
		fprintf(stderr,"\tError opening file: %s\n", file_) ;
		return ;
	}

	if ( NumberOfFiles > 1 && MessageLevel >= NORMAL )
	    printf("Treating file: %s\n", file_ ) ;

	InitialiseTrackData() ;

	TreatDHeader(fd) ;

	if ( BadFileFlag != FATAL )
	    TreatTracks(fd) ;

	if ( BadFileFlag )
	{
	    if ( MessageLevel >= NORMAL )
		printf("\t>>> FILE BAD\n") ;
	    GlobalStatus = 1 ;
	}
	else if ( MessageLevel >= NORMAL )
	    printf("\t>>> FILE GOOD\n" ) ;

	close(fd) ;
}

/*
 * Various intialisations and command line handle
 *
 */

static PrintHelp()
{
	puts("Valid options are:") ;
	puts("\t-s : silent") ;
	puts("\t-v : verbose") ;
	puts("\t-d : debug (very verbose)") ;
	puts("\t-h,-? : help") ;

	exit(0) ;
}

static int DecodeOption(argv, arg)
char * argv[] ;
int arg ;
{
        char *argp = argv[arg]+1 ;

        switch ( *argp )
        {
            case 's':
                 MessageLevel = SILENT ;
                 return (arg+1) ;

            case 'v':
                 MessageLevel = VERBOSE ;
                 return (arg+1) ;

            case 'd':
		 MessageLevel = DEBUG ;
                 return (arg+1) ;

            case 'h':
	    case '?':
	    default:
		 PrintHelp() ;
                 return (arg+1) ;		/* Not executed */

	}
}


void DecodeArgLine(argc, argv)
int argc ;
char * argv[] ;
{
  	int arg ;

	if ( argc <= 1)
	{
	    PrintHelp() ;
	}

        for ( arg = 1; arg < argc ; )
        {
            if ( *argv[arg] != '-' )
            {
                AppendFileName(argv[arg]) ;
                arg += 1 ;
            }
            else
                arg = DecodeOption(argv,arg) ;
        }
}

/*
 * Main part
 *
 */

int main(argc,argv)
int argc ;
char ** argv ;
{
	int i;
	
	DecodeArgLine(argc, argv) ;
	
	for ( i=0 ; i< NumberOfFiles; i++ )
	{
	    TreatFile(ListOfFiles[i]) ;
	}

	return ( GlobalStatus ) ;
}
