/***************************************************************************
 *                                                                         *
 *    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>     *
 *    ported to MAME for i8272_floppy support                              *
 *                                                                         *
 *    Copyright (C) 2008 Matt Knoth <p8000@knothusa.net>                   *
 *                                                                         *
 *    This file 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                                                   *
 *                                                                         *
 ***************************************************************************/
/*
 *  This driver is for CopyQM images
 *  Written by Per Ola Ingvarsson.
 *  Thanks to Roger Plant for the expandqm program which has helped a lot.
 *  Currently it is read only
 */

#include <ctype.h>
#include "driver.h"
#include "machine/cqm_image.h"

//#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;
    const 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[4];       // support for 4 floppy drives within the system
static cqm_image     qm_image[4]; // as used by the floppy controller for each drive

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

static int drv_qm_load_image( UINT8 which, FILE* fp )
{   
    QM_DSK_DRIVER* qm_self = &qm[which];
    unsigned char* image = 0;
    int errcond = DSK_ERR_OK;
    size_t max_size;

    /* 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 */
    int 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 */
    max_size = 80 * 32 * 256;  // cylinder * sectors * bytes
    qm_self->qm_image = auto_malloc( (image_size>max_size)? image_size : max_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 );
       //DEBUG fprintf(stderr,"INFO: wrote image file image.dd - size %d\n",image_size);
    }
#endif
    
    return errcond;
}

static int drv_qm_load_header( UINT8 which, char* header ) 
{
    QM_DSK_DRIVER* qm_self = &qm[which];
    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;
}

// This Read/Write pointer is valid for one sector only (could be more but not guarenteed)
unsigned char* qm_image_get_rw_ptr( UINT8 which, int cylinder, int head, int sector)
{
    QM_DSK_DRIVER *qm_self;
    long offset;

//DEBUG fprintf(stderr,"qm_image_get_rw_ptr( which=%d, cylinder=%d, head=%d, sector=%d)\n",which,cylinder,head,sector);

    if(which > 3) {
       return NULL;
    }

    qm_self= &qm[which];
    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;

    return qm_self->qm_image + offset;
}

// This Read/Write pointer is valid for one sector only (could be more but not guarenteed)
unsigned char* qm_image_get_block_rw_ptr( UINT8 which, long block)
{
    QM_DSK_DRIVER *qm_self;
    long offset;

//DEBUG fprintf(stderr,"qm_image_get_rw_ptr( which=%d, cylinder=%d, head=%d, sector=%d)\n",which,cylinder,head,sector);

    if(which > 3) {
       return NULL;
    }

    qm_self= &qm[which];
    offset = block*qm_self->qm_h_sector_size;

    return qm_self->qm_image + offset;
}

// create an empty floppy with max size. Formatting will set the correct dimensions later
cqm_image* qm_image_new( UINT8 which, const char* fname) 
{
    QM_DSK_DRIVER *qm_self;
    size_t max_size;

    qm_self= &qm[which];
    qm_self->qm_filename = fname;
    qm_self->qm_calc_crc = 0;
    qm_self->qm_h_total_tracks       = 0;
    qm_self->qm_h_nbr_heads          = 0;
    qm_self->qm_h_nbr_sec_per_track  = 0;
    qm_self->qm_h_sector_size        = 0;

    qm_image[which].fname[255] = '\0';
    qm_image[which].sector_size = 256;
    qm_image[which].sectors_per_track = 16;
    qm_image[which].heads = 2;
    qm_image[which].tracks = 80;
    qm_image[which].current_sector=1;
    qm_image[which].current_head=0;
    qm_image[which].current_track=0;

    max_size = 80 * 2 * 16 * 256;  // cylinder * heads * sectors * bytes
    qm_self->qm_image = auto_malloc( max_size );

    return &qm_image[which];
}

// open an existing image
cqm_image* qm_image_open( UINT8 which, const char* fname) 
{
    char header[QM_HEADER_SIZE];
    QM_DSK_DRIVER *qm_self;
    FILE* fp;
    size_t res;
    int errcond = 0;
    char buf[256];

    if(!fname) return NULL;

    if(which > 3) 
       return NULL;

    qm_self= &qm[which];
    qm_self->qm_image = 0;
    qm_self->qm_filename = 0;
    qm_self->qm_calc_crc = 0;
    
    sprintf(buf,"%s\\%s\\%s",options_get_string(mame_options(),OPTION_FLOPPY_DIRECTORY),options_get_string(mame_options(),OPTION_GAMENAME),fname);

    fp = fopen(buf, "rb");
    if ( ! fp ) {
        //DEBUG fprintf(stderr,"ERROR: cannot open file %s - abort\n",buf);
        return NULL;
    }

    /* Load the header */
    res = fread( header, QM_HEADER_SIZE, 1, fp );
    if ( res != 1 ) {
	 //DEBUG fprintf(stderr,"ERROR while reading header - abort\n");
         return NULL;
    }
    
    /* Load the header */
    errcond = drv_qm_load_header( which, 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 = auto_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) {
                     //DEBUG fprintf(stderr,"ERROR: comment length exceeded image - abort\n");
                     return NULL;
                } 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 */
                    //DEBUG fprintf(stderr,"qm: Disk Comment: %s\n",comment_buf);
                    free(comment_buf);
                }
            }
        }
    }

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

    qm_self->qm_filename = fname;

    strncpy( qm_image[which].fname, fname, 255);
    qm_image[which].fname[255] = '\0';
    qm_image[which].sector_size = qm_self->qm_h_sector_size;
    qm_image[which].sectors_per_track = qm_self->qm_h_nbr_sec_per_track;
    qm_image[which].heads = qm_self->qm_h_nbr_heads;
    qm_image[which].tracks = qm_self->qm_h_total_tracks;
    qm_image[which].current_sector=1;
    qm_image[which].current_head=0;
    qm_image[which].current_track=0;

    fclose( fp );
    return &qm_image[which];
}

void qm_image_close( UINT8 which )
{
    QM_DSK_DRIVER *qm_self;

    if(which > 3) 
       return ;

    qm_self= &qm[which];
    free(qm_self->qm_image);
    qm_self->qm_image = 0;
    qm_self->qm_filename = 0;

// FIXME: add the image writeback functionality

}

void qm_image_format( UINT8 which, cqm_image* geometry, UINT8 databyte )
{
    QM_DSK_DRIVER *qm_self;
    long offset;

    if(which > 3) 
       return;

    qm_self= &qm[which];
    offset = geometry->current_track * geometry->sectors_per_track * geometry->heads * geometry->sector_size;
    offset+= geometry->current_head * geometry->sectors_per_track * geometry->sector_size;
    offset+= (geometry->current_sector - 1) * geometry->sector_size;

    if((geometry->current_track+1) > qm_self->qm_h_total_tracks)      qm_self->qm_h_total_tracks      = geometry->current_track + 1;
    if((geometry->current_head +1) > qm_self->qm_h_nbr_heads)         qm_self->qm_h_nbr_heads         = geometry->current_head + 1;
    if(geometry->sectors_per_track > qm_self->qm_h_nbr_sec_per_track) qm_self->qm_h_nbr_sec_per_track = geometry->sectors_per_track;
    if(geometry->sector_size       > qm_self->qm_h_sector_size)       qm_self->qm_h_sector_size       = geometry->sector_size;

    memset(qm_self->qm_image + offset, databyte, geometry->sector_size);
//DEBUG fprintf(stderr,"FORMAT: 0x%04lx with %02x - %d bytes\n",offset,databyte,geometry->sector_size);
}

void drv_qm_dump_sector_hex(FILE* ofp,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;

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

        fprintf(ofp," *");
        for (m = 0; m < (n%16); m++) fputc(' ',ofp);

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

void qm_dump_disk( UINT8 which )
{
    QM_DSK_DRIVER *qm_self;
    unsigned char* rd_ptr;
    int rd_cyl,rd_head,rd_sec,rd_track;
    FILE* ofp;

    if((ofp=fopen("dump.hex","wb"))==NULL) {
        return;
    }

    qm_self= &qm[which];
    fprintf(ofp,"qm_filename=%s\nqm_h_total_tracks=%d\nqm_h_nbr_heads=%d\nqm_h_nbr_sec_per_track=%d\nqm_h_sector_size=%d\n\n",
                 qm_self->qm_filename,qm_self->qm_h_total_tracks,qm_self->qm_h_nbr_heads,qm_self->qm_h_nbr_sec_per_track,qm_self->qm_h_sector_size);

    rd_cyl = 0;
    rd_head = 0;
    rd_sec = 1;
    rd_track = 0;
    
    do {
         rd_ptr = qm_image_get_rw_ptr( which, rd_cyl, rd_head, rd_sec);
         fprintf(ofp,"Track=%d Sector=%d\n",rd_track,rd_sec);
         fprintf(ofp,"drv_qm_read(cylinder=%d,head=%d,sector=%d)\n",rd_cyl,rd_head,rd_sec);
         drv_qm_dump_sector_hex( ofp, rd_ptr, qm_self->qm_h_sector_size);

         rd_sec++;
         if(rd_sec > qm_self->qm_h_nbr_sec_per_track) {
            rd_sec=1;
            rd_track++;

            rd_cyl = (rd_track / qm_self->qm_h_nbr_heads);
            rd_head = (rd_track % qm_self->qm_h_nbr_heads);
         }
   } while((rd_cyl < qm_self->qm_h_total_tracks) && (rd_head < qm_self->qm_h_nbr_heads) && (rd_sec < (1 + qm_self->qm_h_nbr_sec_per_track)));

   fclose(ofp);
}

void drv_qm_dump_compressed(FILE* ofp,unsigned long* pcrc,unsigned char* rd_ptr, int sector_size)
{
   unsigned char* p;
   unsigned char  a;
   int i,l,len;

   for(i=0; i<sector_size; i++) drv_qm_update_crc(pcrc, rd_ptr[i]);   // warming up cache

   for(p=rd_ptr, i=0, l=0, len=sector_size-4; l < len; ) {

       a=p[i];
       if((a==p[i+1]) && (a==p[i+2]) && (a==p[i+3])) {  // equals break even after 3, minimum 4 required

          if(i) {                                       // flush out previous non-equals
             fwrite(&i,1,2,ofp);                        // positive len
             while(i--) fwrite(p++,1,1,ofp);            // runlen data
          }

          for(a=*p, i=0; (l<sector_size) && (a==*p);) { // find true length of equals
                i++; l++; p++;
          }
          i*=-1;
          fwrite(&i,1,2,ofp);                           // negative len
          fwrite(&a,1,1,ofp);                           // runlen data
          i=0;

       } else {
          i++;
          l++;
       }
   }

   if(i || (l<sector_size)) {                          // dump remaining buffer after end of block
      i+=sector_size-l;
      fwrite(&i,1,2,ofp);                              // rest of block
      while(i--) fwrite(p++,1,1,ofp);                  // runlen data
   }
}

void qm_image_save( UINT8 which )
{
    int rd_cyl,rd_head,rd_sec,rd_track;
    QM_DSK_DRIVER *qm_self;
    unsigned char* rd_ptr;
    unsigned char buf[256];
    unsigned long crc;
    FILE* ofp;
    int tmp;

    qm_self= &qm[which];
    sprintf(buf,"%s\\%s\\%s",options_get_string(mame_options(),OPTION_FLOPPY_DIRECTORY),options_get_string(mame_options(),OPTION_GAMENAME),qm_self->qm_filename);
    if(!strstr(qm_self->qm_filename,".cqm"))
       strcat(buf,".cqm");

    if((ofp=fopen(buf,"wb"))==NULL) {
        //DEBUG fprintf(stderr,"ERROR: unable to open file %s for writing\n", qm_self->qm_filename);
        return;
    }

    memset(buf,0x00,150);   

    buf[0x00]= 'C';
    buf[0x01]= 'Q';
    buf[0x02]= 0x14;
    buf[0x03]= (unsigned char)qm_self->qm_h_sector_size;
    buf[0x04]= (unsigned char)(qm_self->qm_h_sector_size>>8);

    tmp = qm_self->qm_h_total_tracks * qm_self->qm_h_nbr_heads * qm_self->qm_h_nbr_sec_per_track;
    buf[0x0b]= (unsigned char)tmp;
    buf[0x0c]= (unsigned char)(tmp>>8);

    buf[0x10]= (unsigned char)qm_self->qm_h_nbr_sec_per_track;
    buf[0x11]= (unsigned char)(qm_self->qm_h_nbr_sec_per_track>>8);
    buf[0x12]= (unsigned char)qm_self->qm_h_nbr_heads;

    tmp = (qm_self->qm_h_total_tracks * qm_self->qm_h_nbr_heads * qm_self->qm_h_nbr_sec_per_track * qm_self->qm_h_sector_size)>>10;
    sprintf(&buf[0x1c],"%dK MAME emulation floppy",tmp);

    buf[0x5a]= (unsigned char)qm_self->qm_h_total_tracks;
    buf[0x5b]= (unsigned char)qm_self->qm_h_total_tracks;

    sprintf(&buf[0x60],"** NONE **"); // volume label  0x60

    fwrite(buf,1,133,ofp);   // write header preliminary

    rd_cyl = 0;
    rd_head = 0;
    rd_sec = 1;
    rd_track = 0;

    crc = 0l;
    do {
         rd_ptr = qm_image_get_rw_ptr( which, rd_cyl, rd_head, rd_sec);
         drv_qm_dump_compressed( ofp, &crc, rd_ptr, qm_self->qm_h_sector_size);

         rd_sec++;
         if(rd_sec > qm_self->qm_h_nbr_sec_per_track) {
            rd_sec=1;
            rd_track++;

            rd_cyl = (rd_track / qm_self->qm_h_nbr_heads);
            rd_head = (rd_track % qm_self->qm_h_nbr_heads);
         }
   } while((rd_cyl < qm_self->qm_h_total_tracks) && (rd_head < qm_self->qm_h_nbr_heads) && (rd_sec < (1 + qm_self->qm_h_nbr_sec_per_track)));

   buf[0x5c]= (unsigned char)crc;
   buf[0x5d]= (unsigned char)(crc>>8);
   buf[0x5e]= (unsigned char)(crc>>16);
   buf[0x5f]= (unsigned char)(crc>>24);

   crc = 0l;
   for(tmp=0; tmp<132; tmp++) crc+=buf[tmp];

   crc = - crc;
   buf[0x84]= (unsigned char)crc;

   fseek(ofp,0l,SEEK_SET);
   fwrite(buf,1,133,ofp);   // write header final

   fclose(ofp);
}



