/*****************************************************************************
 *                                                                           *
 *    CQM UDOS file extraction for P8000 floppies                            *
 *    Displays UDOS file directory and creates subdirectory for files        *
 *    Copyright (C) 2008: Matt Knoth, <mknoth@earthlink.net>                 *
 *                                                                           *
 *    This program has been derived from LIBDSK 1.2.1                        *
 *    LIBDSK: General floppy and diskimage access library                    *
 *    Copyright (C) 2001-2,2005  John Elliott <jce@seasip.demon.co.uk>       *
 *    Credits also to Per Ola Ingvarsson and Roger Plant                     *
 *                                                                           *
 *    This library is free software; you can redistribute it and/or          *
 *    modify it under the terms of the GNU Library General Public            *
 *    License as published by the Free Software Foundation; either           *
 *    version 2 of the License, or (at your option) any later version.       *
 *                                                                           *
 *    This library is distributed in the hope that it will be useful,        *
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of         *
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU      *
 *    Library General Public License for more details.                       *
 *                                                                           *
 *    You should have received a copy of the GNU Library General Public      *
 *    License along with this library; if not, write to the Free             *
 *    Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,        *
 *    MA 02111-1307, USA                                                     *
 *                                                                           *
 *****************************************************************************/

#include<stdio.h>
#include<string.h>
#include<ctype.h>
#include<stdlib.h>
#include<sys/stat.h>

#pragma warning(disable:4996)   /* depricated stdlib calls: old fashioned guys at work ;-) */

#define DRV_QM_DEBUG 1

#define QM_HEADER_SIZE 133
#define DSK_ERR_OK	 (0)	/* No error */
#define DSK_ERR_BADPTR	 (-1)	/* Bad pointer */
#define DSK_ERR_NOTME    (-5)	/* Driver rejects disc */
#define DSK_ERR_NOMEM  	 (-7)	/* Null return from malloc */

/* Automatically generated CRC table */
/* polynomial: 0x104C11DB7, bit reverse algorithm */
const unsigned long crc32r_table[256] = {
    0x00000000UL,0x77073096UL,0xEE0E612CUL,0x990951BAUL, 0x076DC419UL,0x706AF48FUL,0xE963A535UL,0x9E6495A3UL, 0x0EDB8832UL,0x79DCB8A4UL,0xE0D5E91EUL,0x97D2D988UL,
    0x09B64C2BUL,0x7EB17CBDUL,0xE7B82D07UL,0x90BF1D91UL, 0x1DB71064UL,0x6AB020F2UL,0xF3B97148UL,0x84BE41DEUL, 0x1ADAD47DUL,0x6DDDE4EBUL,0xF4D4B551UL,0x83D385C7UL,
    0x136C9856UL,0x646BA8C0UL,0xFD62F97AUL,0x8A65C9ECUL, 0x14015C4FUL,0x63066CD9UL,0xFA0F3D63UL,0x8D080DF5UL, 0x3B6E20C8UL,0x4C69105EUL,0xD56041E4UL,0xA2677172UL,
    0x3C03E4D1UL,0x4B04D447UL,0xD20D85FDUL,0xA50AB56BUL, 0x35B5A8FAUL,0x42B2986CUL,0xDBBBC9D6UL,0xACBCF940UL, 0x32D86CE3UL,0x45DF5C75UL,0xDCD60DCFUL,0xABD13D59UL,
    0x26D930ACUL,0x51DE003AUL,0xC8D75180UL,0xBFD06116UL, 0x21B4F4B5UL,0x56B3C423UL,0xCFBA9599UL,0xB8BDA50FUL, 0x2802B89EUL,0x5F058808UL,0xC60CD9B2UL,0xB10BE924UL,
    0x2F6F7C87UL,0x58684C11UL,0xC1611DABUL,0xB6662D3DUL, 0x76DC4190UL,0x01DB7106UL,0x98D220BCUL,0xEFD5102AUL, 0x71B18589UL,0x06B6B51FUL,0x9FBFE4A5UL,0xE8B8D433UL,
    0x7807C9A2UL,0x0F00F934UL,0x9609A88EUL,0xE10E9818UL, 0x7F6A0DBBUL,0x086D3D2DUL,0x91646C97UL,0xE6635C01UL, 0x6B6B51F4UL,0x1C6C6162UL,0x856530D8UL,0xF262004EUL,
    0x6C0695EDUL,0x1B01A57BUL,0x8208F4C1UL,0xF50FC457UL, 0x65B0D9C6UL,0x12B7E950UL,0x8BBEB8EAUL,0xFCB9887CUL, 0x62DD1DDFUL,0x15DA2D49UL,0x8CD37CF3UL,0xFBD44C65UL,
    0x4DB26158UL,0x3AB551CEUL,0xA3BC0074UL,0xD4BB30E2UL, 0x4ADFA541UL,0x3DD895D7UL,0xA4D1C46DUL,0xD3D6F4FBUL, 0x4369E96AUL,0x346ED9FCUL,0xAD678846UL,0xDA60B8D0UL,
    0x44042D73UL,0x33031DE5UL,0xAA0A4C5FUL,0xDD0D7CC9UL, 0x5005713CUL,0x270241AAUL,0xBE0B1010UL,0xC90C2086UL, 0x5768B525UL,0x206F85B3UL,0xB966D409UL,0xCE61E49FUL,
    0x5EDEF90EUL,0x29D9C998UL,0xB0D09822UL,0xC7D7A8B4UL, 0x59B33D17UL,0x2EB40D81UL,0xB7BD5C3BUL,0xC0BA6CADUL, 0xEDB88320UL,0x9ABFB3B6UL,0x03B6E20CUL,0x74B1D29AUL,
    0xEAD54739UL,0x9DD277AFUL,0x04DB2615UL,0x73DC1683UL, 0xE3630B12UL,0x94643B84UL,0x0D6D6A3EUL,0x7A6A5AA8UL, 0xE40ECF0BUL,0x9309FF9DUL,0x0A00AE27UL,0x7D079EB1UL,
    0xF00F9344UL,0x8708A3D2UL,0x1E01F268UL,0x6906C2FEUL, 0xF762575DUL,0x806567CBUL,0x196C3671UL,0x6E6B06E7UL, 0xFED41B76UL,0x89D32BE0UL,0x10DA7A5AUL,0x67DD4ACCUL,
    0xF9B9DF6FUL,0x8EBEEFF9UL,0x17B7BE43UL,0x60B08ED5UL, 0xD6D6A3E8UL,0xA1D1937EUL,0x38D8C2C4UL,0x4FDFF252UL, 0xD1BB67F1UL,0xA6BC5767UL,0x3FB506DDUL,0x48B2364BUL,
    0xD80D2BDAUL,0xAF0A1B4CUL,0x36034AF6UL,0x41047A60UL, 0xDF60EFC3UL,0xA867DF55UL,0x316E8EEFUL,0x4669BE79UL, 0xCB61B38CUL,0xBC66831AUL,0x256FD2A0UL,0x5268E236UL,
    0xCC0C7795UL,0xBB0B4703UL,0x220216B9UL,0x5505262FUL, 0xC5BA3BBEUL,0xB2BD0B28UL,0x2BB45A92UL,0x5CB36A04UL, 0xC2D7FFA7UL,0xB5D0CF31UL,0x2CD99E8BUL,0x5BDEAE1DUL,
    0x9B64C2B0UL,0xEC63F226UL,0x756AA39CUL,0x026D930AUL, 0x9C0906A9UL,0xEB0E363FUL,0x72076785UL,0x05005713UL, 0x95BF4A82UL,0xE2B87A14UL,0x7BB12BAEUL,0x0CB61B38UL,
    0x92D28E9BUL,0xE5D5BE0DUL,0x7CDCEFB7UL,0x0BDBDF21UL, 0x86D3D2D4UL,0xF1D4E242UL,0x68DDB3F8UL,0x1FDA836EUL, 0x81BE16CDUL,0xF6B9265BUL,0x6FB077E1UL,0x18B74777UL,
    0x88085AE6UL,0xFF0F6A70UL,0x66063BCAUL,0x11010B5CUL, 0x8F659EFFUL,0xF862AE69UL,0x616BFFD3UL,0x166CCF45UL, 0xA00AE278UL,0xD70DD2EEUL,0x4E048354UL,0x3903B3C2UL,
    0xA7672661UL,0xD06016F7UL,0x4969474DUL,0x3E6E77DBUL, 0xAED16A4AUL,0xD9D65ADCUL,0x40DF0B66UL,0x37D83BF0UL, 0xA9BCAE53UL,0xDEBB9EC5UL,0x47B2CF7FUL,0x30B5FFE9UL,
    0xBDBDF21CUL,0xCABAC28AUL,0x53B39330UL,0x24B4A3A6UL, 0xBAD03605UL,0xCDD70693UL,0x54DE5729UL,0x23D967BFUL, 0xB3667A2EUL,0xC4614AB8UL,0x5D681B02UL,0x2A6F2B94UL,
    0xB40BBE37UL,0xC30C8EA1UL,0x5A05DF1BUL,0x2D02EF8DUL,
    };


typedef struct
{
    void*         qm_super;
    char*         qm_filename;
    int           qm_h_sector_size;
    /* Number of total sectors. Not valid if blind */
    int           qm_h_nbr_sectors;
    int           qm_h_nbr_sec_per_track;
    int           qm_h_nbr_heads;
    int           qm_h_comment_len;
    /* Density - 1 means HD, 2 means QD */
    int           qm_h_density;
    /* Blind transfer or not. */
    int           qm_h_blind;
    int           qm_h_used_tracks;
    int           qm_h_total_tracks;
    /* Interleave */
    int           qm_h_interleave;
    /* Skew. Negative number for skew between sides */
    int           qm_h_skew;
    /* Sector number base. */
    signed char   qm_h_secbase;
    /* The crc read from the header */
    unsigned long qm_h_crc;
    /* The crc calculated while the image is read */
    unsigned long qm_calc_crc;
    unsigned int  qm_image_offset;
    unsigned char* qm_image;
    /* Fake sector for READ ID command */
    unsigned int  qm_sector;
} QM_DSK_DRIVER;

static QM_DSK_DRIVER qm;
static QM_DSK_DRIVER* qm_self = &qm;


static int get_i16( char* buf, int pos )
{
    unsigned char low_byte;
    unsigned char high_byte;
    unsigned int outInt;
    
    low_byte = buf[pos++];
    high_byte = buf[pos];
    
    /* Signed, eh. Lets see. */
    outInt = 0;
    if ( (signed char)high_byte < 0 ) {
        /* Set all to ones except for the lower 16 */
        /* Should work if sizeof(int) >= 16 */
        outInt = (-1) << 16;
    }

    outInt |= (high_byte << 8 ) | low_byte;
    
    return outInt;
}

static unsigned int get_u16( char* buf, int pos )
{
    return ((unsigned int)get_i16( buf, pos )) & 0xffff;
}

static unsigned long get_u32( char* buf, int pos )
{
    int i;
    unsigned long ret_val = 0;
    for ( i = 3; i >= 0; --i ) {
        ret_val <<= 8;
        ret_val |= ( (unsigned long)buf[pos+i] & 0xff );
    }
    return ret_val;
}

static void drv_qm_update_crc( unsigned long* crc, unsigned char byte )
{
    /* Note that there is a bug in the CopyQM CRC calculation  */
    /* When indexing in this table, they shift the crc ^ data  */
    /* 2 bits up to address longwords, but they do that in an  */
    /* eight bit register, so that the top 2 bits are lost,    */
    /* thus the anding with 0x3f                               */
    *crc = crc32r_table[(byte ^ (unsigned char)*crc) & 0x3f ] ^ (*crc >> 8);
}

int drv_qm_load_image( QM_DSK_DRIVER* qm_self, FILE* fp )
{
    unsigned char* image = 0;
    int errcond = DSK_ERR_OK;
    /* Write position in the memory image */
    size_t curwritepos = 0;
    /* FIXME: Use the used tracks instead of the total tracks to detect */
    /*        that there is the correct amount of data in the image     */
    size_t image_size = (size_t)qm_self->qm_h_nbr_sec_per_track *
        (size_t)qm_self->qm_h_nbr_heads *
        (size_t)qm_self->qm_h_total_tracks * (size_t)qm_self->qm_h_sector_size;
    /* Set the position after the header and comment */
    size_t res = fseek( fp, QM_HEADER_SIZE + qm_self->qm_h_comment_len, SEEK_SET );
    if ( res < 0 ) return DSK_ERR_NOTME;

    /* Alloc memory for the image */
    qm_self->qm_image = malloc( image_size );
    if ( ! qm_self->qm_image ) return DSK_ERR_NOMEM;
    image = qm_self->qm_image;
    /* Start reading */
    /* Note that it seems like each track starts a new block */
    while ( curwritepos < image_size ) {
        /* Read the length */
        char lengthBuf[2];
        res = fread( lengthBuf, 2, 1, fp );
        if ( res != 1 ) {
            if ( feof( fp ) ) {
                /* End of file - fill with f6 - do not update CRC for these */
                memset( image + curwritepos, 0xf6, image_size - curwritepos );
                curwritepos += image_size - curwritepos;
            } else {
                return DSK_ERR_NOTME;
            }
        } else {
            int length = get_i16( lengthBuf, 0 );
            if ( length < 0 ) {            
                /* Negative number - next byte is repeated (-length) times */
                int c = fgetc( fp );
                if ( c == EOF ) return DSK_ERR_NOTME;
                /* Copy the byte into memory and update the offset */
                memset( image + curwritepos, c, -length );                
                curwritepos -= length;
                /* Update CRC */
                for( ; length != 0; ++length ) {
                    drv_qm_update_crc( &qm_self->qm_calc_crc, (unsigned char)c );
                }
            } else {
                if ( length != 0 ) {
                    /* Positive number - length different characters */
                    res = fread( image + curwritepos, length, 1, fp );
                    /* Update CRC (and write pos) */
                    while ( length-- ) {
                        drv_qm_update_crc( &qm_self->qm_calc_crc,
                                           image[curwritepos++] );
                    }
                    if ( res != 1 ) return DSK_ERR_NOTME;
                }
            }
        }
    }
#ifdef DRV_QM_DEBUG    
    fprintf( stderr, "drv_qm_load_image - crc from header = 0x%08lx, "
             "calc = 0x%08lx\n", qm_self->qm_h_crc, qm_self->qm_calc_crc );
#endif
    /* Compare the CRCs */
    /* The CRC is zero on old images so it cannot be checked then */
    if ( qm_self->qm_h_crc ) {
        if ( qm_self->qm_h_crc != qm_self->qm_calc_crc ) {
            return DSK_ERR_NOTME;
        }
    }

//#ifdef DRV_QM_DEBUG
//    /* Write image to file for testing */
//    if(1) {
//       FILE* tmpFile = fopen("image.dd", "wb" );
//       fwrite( image, image_size, 1, tmpFile );
//       fclose( tmpFile );
//       fprintf(stderr,"INFO: wrote image file image.dd - size %d\n",image_size);
//    }
//#endif
    
    return errcond;
}

int drv_qm_load_header( QM_DSK_DRIVER* qm_self, char* header ) 
{
    int i;
    
    /* Check the header checksum */
    unsigned char chksum = 0;
    for ( i = 0; i < QM_HEADER_SIZE; ++i ) {
        chksum += (unsigned char)(header[i]);
    }
    if ( chksum != 0 ) {
#ifdef DRV_QM_DEBUG
        fprintf( stderr, "qm: header checksum error\n");
#endif
        return DSK_ERR_NOTME;
    } else {
#ifdef DRV_QM_DEBUG
        fprintf( stderr, "qm: header checksum is ok!\n");
#endif
    }
    if ( header[0] != 'C' || header[1] != 'Q' ) {
#ifdef DRV_QM_DEBUG
        fprintf( stderr, "qm: First two chars are %c%c\n", header[0],
                 header[1] );
#endif
        return DSK_ERR_NOTME;
    }
    /* I'm guessing sector size is at 3. Expandqm thinks 7 */
    qm_self->qm_h_sector_size = get_u16( header, 0x03 );
    /* Number of sectors 0x0B-0x0C, strange number for non-blind, often 116 */
    qm_self->qm_h_nbr_sectors = get_u16( header, 0x0b );
    /* Number of sectors per track */
    qm_self->qm_h_nbr_sec_per_track = get_u16( header, 0x10 );
    /* Number of heads */
    qm_self->qm_h_nbr_heads = get_u16( header, 0x12 );
    /* Blind or not */
    qm_self->qm_h_blind = header[0x58];
    /* Density - 0 is DD, 1 means HD */
    qm_self->qm_h_density = header[0x59];    
    /* Number of used tracks */
    qm_self->qm_h_used_tracks = header[0x5a];
    /* Number of total tracks */
    qm_self->qm_h_total_tracks = header[0x5b];
    /* CRC 0x5c - 0x5f */
    qm_self->qm_h_crc = get_u32( header, 0x5c );
    /* Length of comment */
    qm_self->qm_h_comment_len = get_u16( header, 0x6f );
    /* 0x71 is first sector number - 1 */
    qm_self->qm_h_secbase = (signed char)(header[0x71]);
    /* 0x74 is interleave, I think. Normally 1, but 0 for old copyqm */
    qm_self->qm_h_interleave = header[0x74];
    /* 0x75 is skew. Normally 0. Negative number for alternating sides */
    qm_self->qm_h_skew = header[0x75];

#ifdef DRV_QM_DEBUG
    fprintf( stderr, "qm: sector size is %d\n", qm_self->qm_h_sector_size);
    fprintf( stderr, "qm: nbr sectors %d\n",
             qm_self->qm_h_nbr_sectors );
    fprintf( stderr, "qm: nbr sectors/track %d\n",
             qm_self->qm_h_nbr_sec_per_track);
    fprintf( stderr, "qm: nbr heads %d\n",
             qm_self->qm_h_nbr_heads );
    fprintf( stderr, "qm: secbase %d\n", qm_self->qm_h_secbase );
    fprintf( stderr, "qm: density %d\n", qm_self->qm_h_density );
    fprintf( stderr, "qm: used tracks %d\n", qm_self->qm_h_used_tracks );
    fprintf( stderr, "qm: CRC 0x%08lx\n", qm_self->qm_h_crc );
    fprintf( stderr, "qm: interleave %d\n", qm_self->qm_h_interleave );
    fprintf( stderr, "qm: skew %d\n", qm_self->qm_h_skew );
    fprintf( stderr, "qm: description \"%s\"\n" , header + 0x1c );
#endif

    /* Fix the interleave value for old versions of CopyQM */
    if ( qm_self->qm_h_interleave == 0 ) {
        qm_self->qm_h_interleave = 1;
    }
    
    return DSK_ERR_OK;
}

int drv_qm_get_read_ptr(QM_DSK_DRIVER *qm_self, unsigned char **buf_ptr, int cylinder, int head, int sector)
{
    long offset;
    if (!buf_ptr || !qm_self)
        return DSK_ERR_BADPTR;

    offset = (cylinder * qm_self->qm_h_nbr_heads) + head;        /* Drive track */
    offset *= qm_self->qm_h_nbr_sec_per_track;
    offset += (sector - ((qm_self->qm_h_secbase + 1) & 0xFF));
    offset *=  qm_self->qm_h_sector_size;

    *buf_ptr = qm_self->qm_image + offset;

    return DSK_ERR_OK;
}

void drv_qm_dump_sector_hex(unsigned char* buf,int sz)
{
    int m,n;

    for (n = 0; n < sz; n = ((n / 16)+1)*16)
    {
	int base = (n/16)*16;
	int c = 0;

	printf("%04x: ", n);
	for (m = 0; m < (n%16); m++)
	{
		putchar(' ');
		putchar(' ');
		if ((c & 3) == 3) putchar(' ');
		c++;
	}
	for (m = (n % 16); m < 16; m++)
	{
		printf("%02x", buf[base+m]);
		if ((c & 3) == 3) putchar(' ');
		c++;
		if ((n + m + 1) >= sz) break;
	}
	for (; c < 16; c++)
	{
		putchar(' ');
		putchar(' ');
		if ((c & 3) == 3) putchar(' ');
	}

    	printf(" *");
    	for (m = 0; m < (n%16); m++) putchar(' ');
		
    	for (m = (n % 16); m < 16; m++)
    	{
		if (isprint(buf[base+m])) 
			putchar(buf[base+m]);
		else    putchar('.');
		if ((n + m + 1) >= sz) break;
   	}
   	putchar('*');
   	putchar('\n');
    }
}

void dump_udos_file(unsigned char* descriptor,unsigned char* file_name)
{
    unsigned char* dataptr;
    unsigned char* ptrs;
    unsigned char buf[48];
    FILE* ofp;
    int i,len,lastlen,ren;

    strncpy(buf,file_name+1, 0x1f & *file_name);
    buf[0x1f & *file_name]=0;

    ren=0;
    while((ofp=fopen(buf,"rb"))!=NULL) {   // Problem: UDOS has case sensitive names. DOS doesn't. Thanks Bill!
	   fclose(ofp);
	   sprintf(&buf[0x1f & *file_name],"_renamed%d",ren);
	   ren++;
    }

    if((ofp=fopen(buf,"wb"))==NULL) {
        fprintf(stderr,"ERROR: unable to open file %s\n", buf);
        return;
    }

    len=(descriptor[0x10]<<8)|descriptor[0x0f];
    lastlen=((descriptor[0x17]<<8)|descriptor[0x16]);
    drv_qm_get_read_ptr( qm_self, &ptrs, descriptor[0x81], 0, 1+ (descriptor[0x80] & 0x1f));  // checkout POINTERS

    for(i=2; !((i>ptrs[0xfa]) && (ptrs[0xfe]==0xff) && (ptrs[0xff]==0xff)); i+=2) {

        if((i>=0xf9) && (ptrs[0xfe]!=0xff) && (ptrs[0xff]!=0xff)) {
            drv_qm_get_read_ptr( qm_self, &ptrs, ptrs[0xff], 0, 1+ (ptrs[0xfe] & 0x1f));      // checkout next POINTERS block
            i=0;
		}

		drv_qm_get_read_ptr( qm_self, &dataptr, ptrs[i+1], 0, 1+ (ptrs[i] & 0x1f));   // checkout this data block, order is swapped in P8000!
		if((i==ptrs[0xfa]) && (ptrs[0xfe]==0xff) && (ptrs[0xff]==0xff)) 
           fwrite(dataptr, 1, lastlen, ofp);
		else
           fwrite(dataptr, 1, len, ofp);
    }

    fclose(ofp);
}

void extract_udos_directory_block(FILE* fp, unsigned char* rd_buf, int extract, int blk)
{
    int i, j, n, d;
    unsigned char* descriptor;
    unsigned char buf[32];

    for(i=0; (i<512) && (rd_buf[i]!=0xff); i+=n+3) {
        n=rd_buf[i] & 0x7f;                     // get name length
        d=rd_buf[i+n+1] | (rd_buf[i+n+2]<<8);   // get descriptor block

        for(j=1; j<=n; j++)                     // print name
            fputc(rd_buf[i+j],fp);

        for(; j<35; j++)                        // fill rest 
            fputc(' ',fp);

        drv_qm_get_read_ptr( qm_self, &descriptor, d>>8, 0, 1+ (d & 0x1f)); // checkout DESCRIPTOR

        fprintf(fp,"%04xh  ",d);                                            // descriptor address
        fprintf(fp,"%04xh   ",(descriptor[0x81]<<8)|descriptor[0x80]);
    
        fprintf(fp,"%6d   ",(((descriptor[0x10]<<8)|descriptor[0x0f]) *     // blocklen
                             ((descriptor[0x0e]<<8)|descriptor[0x0d])) -    // number of blocks
                            (((descriptor[0x10]<<8)|descriptor[0x0f])  -    // blocklen
                             ((descriptor[0x17]<<8)|descriptor[0x16])));    // bytes in last block

        fprintf(fp,"%c    ",(rd_buf[i] & 0x80)? 'S':'.');                   // secret attribute
    
        if(descriptor[0x0c] & 0x10)  fprintf(fp,"B    ");                   // file type
        if(descriptor[0x0c] & 0x20)  fprintf(fp,"A    ");
        if(descriptor[0x0c] & 0x40)  fprintf(fp,"D    ");
        if(descriptor[0x0c] & 0x80)  fprintf(fp,"P    ");

        fprintf(fp,"%3d    %4d   %4d  ",(descriptor[0xe]<<8)|descriptor[0xd], (descriptor[0x10]<<8)|descriptor[0x0f],(descriptor[0x17]<<8)|descriptor[0x16]);
    
        fprintf(fp,"%c%c%c%c%c%c  %c%c%c%c%c%c   ",descriptor[0x18],descriptor[0x19],descriptor[0x1a],descriptor[0x1b],descriptor[0x1c],descriptor[0x1d],
                                                   descriptor[0x20],descriptor[0x21],descriptor[0x22],descriptor[0x23],descriptor[0x24],descriptor[0x25]);
    
        strcpy(buf,"......");
        if(descriptor[0x13] & 0x04)  buf[ 5]='W'; 
        if(descriptor[0x13] & 0x08)  buf[ 4]='E';
        if(descriptor[0x13] & 0x10)  buf[ 3]='L';
        if(descriptor[0x13] & 0x20)  buf[ 2]='S';
        if(descriptor[0x13] & 0x40)  buf[ 1]='R';
        if(descriptor[0x13] & 0x80)  buf[ 0]='F';
        fprintf(fp,"%s   ",buf);

        fprintf(fp,"%04x    %04x   %04x   "  ,(descriptor[0x29]<<8)|descriptor[0x28], (descriptor[0x7b]<<8)|descriptor[0x7a],(descriptor[0x7d]<<8)|descriptor[0x7c]);

        fprintf(fp,"%5d   %4d"     ,(descriptor[0x2b]<<8)|descriptor[0x2a], (descriptor[0x7f]<<8)|descriptor[0x7e]);

        fprintf(fp,"\n");

        if((i||blk) && extract) dump_udos_file(descriptor,&rd_buf[i]);   // dump out this file - except DIRECTORY
    }
}

void extract_udos_directory(FILE* fp, unsigned char* rd_buf, int extract) 
{
    int i,n;
    unsigned char* descriptor, *ptrs, *dptr;

    fprintf(fp,"FILENAME %30s%7s%9s%5s%5s%7s%8s%7s%7s%8s","DESCR","PNTRS","SIZE","ATTR","TYP","RECS","BLKLEN","LASTR","CREAT","MODFD");
    fprintf(fp,"%8s%9s%7s%8s%7s%8s\n","PROP","STARTAD","LOWAD","HIGHAD","LN1SEG","STKSZ");

    drv_qm_get_read_ptr( qm_self, &descriptor, rd_buf[11], 0, 1+ (rd_buf[10] & 0x1f));        // checkout DESCRIPTOR of DIRECTORY
    drv_qm_get_read_ptr( qm_self, &ptrs, descriptor[0x81], 0, 1+ (descriptor[0x80] & 0x1f));  // checkout pointers of DIRECTORY

    n=0;                                                                                      // block counter - prevents binary dump of file DIRECTORY
    for(i=2; !((i>=ptrs[0xfa]) && (ptrs[0xfe]==0xff) && (ptrs[0xff]==0xff)); i+=2) {          // step through all ptr blocks

        if((i>=ptrs[0xfa]) && (ptrs[0xfe]!=0xff) && (ptrs[0xff]!=0xff)) {                     // go to next ptr block
            drv_qm_get_read_ptr( qm_self, &ptrs, ptrs[0xff], 0, 1+ (ptrs[0xfe] & 0x1f));      // checkout next ptrs block
            i=0;
            n++;
        }

        drv_qm_get_read_ptr( qm_self, &dptr, ptrs[i+1], 0, 1+ (ptrs[i] & 0x1f));              // checkout next DIRECTORY data block

        extract_udos_directory_block(fp, dptr, extract, n);                                   // extract this DIRECTORY block
    }
}


int main(int argc, char** argv) 
{
    FILE* fp;
    char header[QM_HEADER_SIZE];
    size_t res;
    int errcond = 0;

    unsigned char* rd_buf;
    unsigned char* udos_directory;
    char extract_dir[256];

    if(argc<2) {
       fprintf(stderr,"ERROR: missing file argument - abort\n");
       fprintf(stderr,"USAGE: %s <filename>\n",argv[0]);
       fprintf(stderr,"INFO:  %s - derived from LibDsk-1.2.1 - GNU Library General Public License applies\n",argv[0]);
       fprintf(stderr,"INFO:  %s - read CopyQM image, reports drive statistics and extracts UDOS files \n\n",argv[0]);
       return 0;
    }

    /* Zero some stuff */
    qm_self->qm_image = 0;
    qm_self->qm_filename = 0;
    
    /* Open file. Read only for now */
    fp = fopen(argv[1], "rb");
    if ( ! fp ) {
        fprintf(stderr,"ERROR: cannot open file %s - abort\n",argv[1]);
        return DSK_ERR_BADPTR;
    }

    /* Load the header */
    res = fread( header, QM_HEADER_SIZE, 1, fp );
    if ( res != 1 ) {
	   fprintf(stderr,"ERROR while reading header - abort\n");
         return DSK_ERR_NOTME;
    }
    
    /* Load the header */
    errcond = drv_qm_load_header( qm_self, header );

    /* If there's a comment, allocate a temporary buffer for it and load it. */
    if (errcond == DSK_ERR_OK && qm_self->qm_h_comment_len)
    {
        char *comment_buf = malloc(1 + qm_self->qm_h_comment_len);
        /* If malloc fails, ignore it - comments aren't essential */
        if (comment_buf)
        {
            int res = fseek( fp, QM_HEADER_SIZE, SEEK_SET );
            if ( res < 0 ) errcond = DSK_ERR_NOTME;
            else 
            {
                res = fread(comment_buf, 1, qm_self->qm_h_comment_len, fp);
                if ( res < qm_self->qm_h_comment_len) {
                     fprintf(stderr,"ERROR: comment length exceeded image - abort\n");
                     return DSK_ERR_NOTME;
                } else {
                    comment_buf[qm_self->qm_h_comment_len] = 0;
                    /*dsk_set_comment(&qm_self->qm_super, comment_buf);  In case we want to store the comment */
                    fprintf(stderr,"qm: Disk Comment: %s\n",comment_buf);
                    free(comment_buf);
                }
            }
        }
    }

    if ( errcond != DSK_ERR_OK ) {
         fprintf( stderr, "drv_qm_load_header returned %d\n", (int)errcond );
         return(errcond);
    }

    errcond = drv_qm_load_image( qm_self, fp );
    if ( errcond != DSK_ERR_OK ) {
         fprintf( stderr, "drv_qm_load_image returned %d\n", (int)errcond );
         return(errcond);
    }

    // check if P8000 UDOS floppy at all
    drv_qm_get_read_ptr( qm_self, &rd_buf, 22, 0, 1 );                                       // checkout DIRECTORY DESCRIPTOR
    if(!((rd_buf[0x0c] & 0x40) && (rd_buf[0x0f]==0x00) && (rd_buf[0x10]==0x01) &&            // type D, record length 256
         (rd_buf[0x06]==rd_buf[0x08]) && (rd_buf[0x07]==rd_buf[0x09]))) {                    // Dptr == first data record
          fprintf(stderr,"ERROR: not a UDOS disk! - no directory found\n");
          return(DSK_ERR_NOTME);
    }

    sprintf(extract_dir,"FLOPPY_crc%08lx",qm_self->qm_calc_crc);  // set extract directory
    fprintf(stderr,"assigning extract directory %s\n",extract_dir);
    mkdir(extract_dir,0777);
    chdir(extract_dir);

    drv_qm_get_read_ptr( qm_self, &rd_buf, rd_buf[0x07], 0, 1+ (rd_buf[0x06] & 0x1f));       // checkout DIRECTORY
    if((rd_buf[0]==0x89) && ~strncmp(&rd_buf[1],"DIRECTORY",9)) {
       FILE* fop;

        if((fop=fopen("DIRECTORY","wb"))==NULL) {
           fprintf(stderr,"ERROR: unable to write DIRECTORY file - abort\n");
           return(DSK_ERR_BADPTR);
        }
        udos_directory = rd_buf;
        extract_udos_directory(stderr,rd_buf,1);
        extract_udos_directory(fop   ,rd_buf,0);
        fclose(fop);
    } else {
        udos_directory=NULL;
        fprintf(stderr,"ERROR: not a UDOS disk! - no directory found\n");
        return(DSK_ERR_NOTME);
    }

    drv_qm_get_read_ptr( qm_self, &rd_buf, 0, 0, 1 ); // checkout MBR
    if(!strncmp(&rd_buf[0x80],"P8000SYS",8)) {
       FILE* fop;

       fprintf(stderr,"P8000SYS disk found - extract MasterBootRecord\n");
       if((fop=fopen("MBR.dat","wb"))==NULL) {
           fprintf(stderr,"ERROR: unable to write MBR.dat - abort\n");
           return(DSK_ERR_BADPTR);
       }
       fwrite(rd_buf,1,256,fop);
       fclose(fop);
    }


    /* Close the file */
    if ( fp ) {
        fclose( fp );
    }
    return DSK_ERR_OK;
}


