/* disk.c: Routines for handling disk images
   Copyright (c) 2007-2023 Gergely Szasz

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   This program 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 General Public License for more details.

   You should have received a copy of the GNU General Public License along
   with this program; if not, write to the Free Software Foundation, Inc.,
   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

   Author contact information:

   Philip: philip-fuse@shadowmagic.org.uk

*/

#include "config.h"

#include <string.h>
#ifdef HAVE_STRINGS_STRCASECMP
#include <strings.h>
#endif      /* #ifdef HAVE_STRINGS_STRCASECMP */
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#include "libspectrum.h"

#include "bitmap.h"
#include "crc.h"
#include "disk.h"
#include "settings.h"
#include "trdos.h"
#include "ui/ui.h"
#include "utils.h"

/* The ordering of these strings must match the order of the 
 * disk_error_t enumeration in disk.h */

static const char * const disk_error[] = {
  "OK",				/* DISK_OK */
  "Feature not implemented",	/* DISK_IMPL */
  "Out of memory",		/* DISK_MEM */
  "Invalid disk geometry",	/* DISK_GEOM */
  "Cannot open disk image",	/* DISK_OPEN */
  "Unsupported file feature",	/* DISK_UNSUP */
  "Read only disk",		/* DISK_RDONLY */
  "Cannot close file",		/* DISK_CLOSE */
  "Cannot write disk image",	/* DISK_WRFILE */
  "Partially written file",	/* DISK_WRPART */

  "Unknown error code"		/* DISK_LAST_ERROR */
};

static const int disk_bpt[] = {
  6250,				/* AUTO assumes DD */
  5208,				/* 8" SD */
  10416,			/* 8" DD */
  3125,				/* SD */
  6250,				/* DD */
  6500,				/* DD+ e.g. Coin Op Hits */
  12500,			/* HD */
};

static unsigned char head[256];

typedef struct disk_gap_t {
  int gap;			/* gap byte */
  int sync;			/* sync byte */
  int sync_len;
  int mark;			/* mark byte 0xa1 for MFM -1 for MF */
  int len[4];
} disk_gap_t;

disk_gap_t gaps[] = {
  { 0x4e, 0x00, 12, 0xa1, {  0, 60, 22, 24 } },			/* MGT MFM */
  { 0x4e, 0x00, 12, 0xa1, {  0, 10, 22, 60 } },			/* TRD MFM */
  { 0xff, 0x00,  6, -1,   { 40, 26, 11, 27 } },			/* IBM3740 FM */
  { 0x4e, 0x00, 12, 0xa1, { 80, 50, 22, 54 } },			/* IBM34 MFM */
  { 0xff, 0x00,  6, -1,   {  0, 16, 11, 10 } },			/* MINIMAL FM */
  { 0x4e, 0x00, 12, 0xa1, {  0, 32, 22, 24 } },			/* MINIMAL MFM */
  { 0xff, 0x00,  6, -1,   {  8,  8, 11, 10 } },			/* 4k UPD765 FM  - maybe never... */
  { 0x4e, 0x00, 12, 0xa1, { 16, 16, 22, 24 } },			/* 8k UPD765 MFM */
  { 0xff, 0x00,  6, -1,   {  0,  0,  0,  0 } },			/* CUSTOM FM */
  { 0x4e, 0x00, 12, 0xa1, {  0,  0,  0,  0 } },			/* CUSTOM MFM */
};

#define GAP_MGT_PLUSD	0
#define GAP_TRDOS	1
#define GAP_IBM3740	2
#define GAP_IBM34	3
#define GAP_MINIMAL_FM	4
#define GAP_MINIMAL_MFM	5
#define GAP_4k765_FM	6
#define GAP_8k765_MFM	7
#define GAP_CUSTOM_FM	8
#define GAP_CUSTOM_MFM	9

#define buffavail(buffer) ( buffer->file.length - buffer->index )
/* data buffer */
#define buff ( buffer->file.buffer + buffer->index )

typedef struct buffer_t {		/* to store buffer data */
  utils_file file;			/* buffer, length */
  size_t index;
} buffer_t;

void disk_update_tlens( disk_t *d );

const char *
disk_strerror( int error )
{
  if( error < 0 || error > DISK_LAST_ERROR )
    error = DISK_LAST_ERROR;
  return disk_error[ error ];
}

static int
buffread( void *data, size_t len, buffer_t *buffer )
{
  if( len > buffer->file.length - buffer->index )
    return 0;
  memcpy( data, buffer->file.buffer + buffer->index, len );
  buffer->index += len;
  return 1;
}

static int
buffseek( buffer_t *buffer, long offset, int whence )
{
  if( whence == SEEK_CUR )
    offset += buffer->index;
  if( offset >= buffer->file.length )
    return -1;
  buffer->index = offset;
  return 0;
}

static void
position_context_save( const disk_t *d, disk_position_context_t *c )
{
  c->track  = d->track;
  c->clocks = d->clocks;
  c->fm     = d->fm;
  c->weak   = d->weak;
  c->c_bpt  = d->c_bpt;
  c->i      = d->i;
}

static void
position_context_restore( disk_t *d, const disk_position_context_t *c )
{
  d->track  = c->track;
  d->clocks = c->clocks;
  d->fm     = c->fm;
  d->weak   = c->weak;
  d->c_bpt  = c->c_bpt;
  d->i      = c->i;
}

static int
id_read( disk_t *d, int *head, int *track, int *sector, int *length )
{
  int a1mark = 0;

  while( d->i < d->c_bpt ) {
    if( d->track[ d->i ] == 0xa1 && 
	bitmap_test( d->clocks, d->i ) ) {		/* 0xa1 with clock */
      a1mark = 1;
    } else if( d->track[ d->i ] == 0xfe && 
	( bitmap_test( d->clocks, d->i ) ||		/* 0xfe with clock */
	  a1mark ) ) {						/* or 0xfe with 0xa1 */
      d->i++;
      *track  = d->track[ d->i++ ];
      *head   = d->track[ d->i++ ];
      *sector = d->track[ d->i++ ];
      *length = d->track[ d->i++ ];
      d->i += 2;	/* skip CRC */
      return 1;
    } else {
      a1mark = 0;
    }
    d->i++;
  }
  return 0;
}

static int
datamark_read( disk_t *d, int *deleted )
{
  int a1mark = 0;

  while( d->i < d->c_bpt ) {
    if( d->track[ d->i ] == 0xa1 && 
	bitmap_test( d->clocks, d->i ) ) { /* 0xa1 with clock */
      a1mark = 1;
    } else if( d->track[ d->i ] >= 0xf8 && d->track[ d->i ] <= 0xfe &&
	       ( bitmap_test( d->clocks, d->i ) || a1mark ) ) {
      /* 0xfe with clock or 0xfe after 0xa1 mark */
      *deleted = d->track[ d->i ] == 0xf8 ? 1 : 0;
      d->i++;
      return 1;
    } else {
      a1mark = 0;
    }
    d->i++;
  }
  return 0;
}


static int
id_seek( disk_t *d, int sector )
{
  int h, t, s, b;
  d->i = 0;	/* start of the track */
  while( id_read( d, &h, &t, &s, &b ) ) {
    if( s == sector )
      return 1;
  }
  return 0;
}

/* seclen 00 -> 128, 01 -> 256 ... byte */
static int
data_write_file( disk_t *d, FILE *file, int seclen )
{
  int len = 0x80 << seclen;
  if( fwrite( &d->track[ d->i ], len, 1, file ) != 1 )
    return 1;
  return 0;
}

static int
savetrack( disk_t *d, FILE *file, int head, int track, 
	    int sector_base, int sectors, int seclen )
{
  int s;
  int del;

  DISK_SET_TRACK( d, head, track );
  d->i = 0;
  for( s = sector_base; s < sector_base + sectors; s++ ) {
    if( id_seek( d, s ) ) {
      if( datamark_read( d, &del ) ) {		/* write data if we have data */
	if( data_write_file( d, file, seclen ) )
	  return 1;
      }
    } else {
      return 1;
    }
  }
  return 0;
}

static int
saverawtrack( disk_t *d, FILE *file, int head, int track  )
{
  int h, t, s, seclen;
  int del;

  DISK_SET_TRACK( d, head, track );
  d->i = 0;
  while( id_read( d, &h, &t, &s, &seclen ) ) {
    if( datamark_read( d, &del ) ) {		/* write data if we have data */
      if( data_write_file( d, file, seclen ) )
	return 1;
    }
  }
  return 0;
}

#define DISK_ID_NOTMATCH 1
#define DISK_SECLEN_VARI 2
#define DISK_SPT_VARI 4
#define DISK_SBASE_VARI 8
#define DISK_MFM_VARI 16
#define DISK_DDAM 32
#define DISK_CORRUPT_SECTOR 64
#define DISK_UNFORMATTED_TRACK 128
#define DISK_FM_DATA 256
#define DISK_WEAK_DATA 512

static int
guess_track_geom( disk_t *d, int head, int track, int *sector_base,
		    int *sectors, int *seclen, int *mfm )
{
  int r = 0;
  int h, t, s, sl;
  int del = 0;
  *sector_base = -1;
  *sectors = 0;
  *seclen = -1;
  *mfm = -1;

  DISK_SET_TRACK( d, head, track );
  d->i = 0;
  while( id_read( d, &h, &t, &s, &sl ) ) {
    if( *sector_base == -1 )
      *sector_base = s;
    if( *seclen == -1 )
      *seclen = sl;
    if( *mfm == -1 )
      *mfm = d->track[ d->i ] == 0x4e ? 1 : 0;	/* not so robust */
    if( !datamark_read( d, &del ) )
      r |= DISK_CORRUPT_SECTOR;
    if( t != track )
      r |= DISK_ID_NOTMATCH;
    if( s < *sector_base )
      *sector_base = s;
    if( sl != *seclen ) {
      r |= DISK_SECLEN_VARI;
      if( sl > *seclen )
	*seclen = sl;
    }
    if( del )
      r |= DISK_DDAM;
    *sectors += 1;
  }
  return r;
}

static void
update_tracks_mode( disk_t *d )
{
  int i, j;
  int mfm, fm, weak;

  for( i = 0; i < d->cylinders * d->sides; i++ ) {
    DISK_SET_TRACK_IDX( d, i );
    mfm = 0, fm = 0, weak = 0;
    for( j = DISK_CLEN( d->c_bpt ) - 1; j >= 0; j-- ) {
      mfm  |= ~d->fm[j];
      fm   |= d->fm[j];
      weak |= d->weak[j];
    }
    if( mfm && !fm ) d->track[-1] = 0x00;
    if( !mfm && fm ) d->track[-1] = 0x01;
    if( mfm &&  fm ) d->track[-1] = 0x02;
    if( weak ) {
      d->track[-1] |= 0x80;
      d->have_weak = 1;
    }
  }
}

static int
check_disk_geom( disk_t *d, int *sector_base, int *sectors,
		 int *seclen, int *mfm, int *unf )
{
  int h, t, s, slen, sbase, m;
  int r = 0;

  DISK_SET_TRACK_IDX( d, 0 );
  d->i = 0;
  *sector_base = -1;
  *sectors = -1;
  *seclen = -1;
  *mfm = -1;
  *unf = -1;
  for( t = 0; t < d->cylinders; t++ ) {
    for( h = 0; h < d->sides; h++ ) {
      r |= ( d->track[-1] & 0x80 ) ? DISK_WEAK_DATA : 0;
      r |= ( d->track[-1] & 0x03 ) == 0x02 ? DISK_MFM_VARI : 0;
      r |= ( d->track[-1] & 0x03 ) == 0x01 ? DISK_FM_DATA : 0;
      r |= guess_track_geom( d, h, t, &sbase, &s, &slen, &m );
      if( *sector_base == -1 )
	*sector_base = sbase;
      if( *sectors == -1 )
	*sectors = s;
      if( *seclen == -1 )
	*seclen = slen;
      if( *mfm == -1 )
	*mfm = m;
      if( sbase == -1 ) {		/* unformatted */
        if( *unf == -1 && h > 0 ) *unf = -2;
        if( *unf == -1 ) *unf = t;
        continue;
      }
      if( *unf > -1 ) *unf = -2;
      if( sbase != *sector_base ) {
	r |= DISK_SBASE_VARI;
	if( sbase < *sector_base )
	  *sector_base = sbase;
      }
      if( s != *sectors ) {
	r |= DISK_SPT_VARI;
	if( s > *sectors )
	  *sectors = s;
      }
      if( slen != *seclen ) {
	r |= DISK_SECLEN_VARI;
	if( slen > *seclen )
	  *seclen = slen;
      }
      if( m != *mfm ) {
	r |= DISK_MFM_VARI;
	*mfm = 1;
      }
    }
  }
  if( *unf == -2 ) {
    r |= DISK_UNFORMATTED_TRACK;
    *unf = -1;
  }

  return r;
}

static int
gap_add( disk_t *d, int gap, int gaptype )
{
  disk_gap_t *g = &gaps[ gaptype ];
  if( d->i + g->len[gap]  >= d->c_bpt )  /* too many data bytes */
    return 1;
/*-------------------------------- given gap --------------------------------*/
  memset( d->track + d->i, g->gap,  g->len[gap] ); d->i += g->len[gap];
  return 0;
}

static int
preindex_len( disk_t *d, int gaptype )		/* preindex gap and index mark */
{
  disk_gap_t *g = &gaps[ gaptype ];
  return g->len[0] + g->sync_len + ( g->mark >= 0 ? 3 : 0 ) + 1;
}

/*
  [ ....GAP.... ] [ ... SYNC ... ] [ . MARK . ]
  |------------------------------------------->
		Preindex 
*/
static int
preindex_add( disk_t *d, int gaptype )		/* preindex gap and index mark */
{
  disk_gap_t *g = &gaps[ gaptype ];
  if( d->i + preindex_len( d, gaptype ) >= d->c_bpt )
    return 1;
/*------------------------------ pre-index gap -------------------------------*/
  if( gap_add( d, 0, gaptype ) )
    return 1;
/*------------------------------   sync    ---------------------------*/
  memset( d->track + d->i, g->sync, g->sync_len ); d->i += g->sync_len;
  if( g->mark >= 0 ) {
    memset( d->track + d->i , g->mark, 3 );
    bitmap_set( d->clocks, d->i ); d->i++;
    bitmap_set( d->clocks, d->i ); d->i++;
    bitmap_set( d->clocks, d->i ); d->i++;
  }
/*------------------------------     mark     ------------------------------*/
  if( g->mark < 0 )				/* FM */
    bitmap_set( d->clocks, d->i ); 		/* set clock mark */
  d->track[ d->i++ ] = 0xfc;			/* index mark */
  return 0;
}

static int
postindex_len( disk_t *d, int gaptype )		/* preindex gap and index mark */
{
  disk_gap_t *g = &gaps[ gaptype ];
  return g->len[1];
}

static int
postindex_add( disk_t *d, int gaptype )		/* postindex gap */
{
  return gap_add( d, 1, gaptype );
}

static int
gap4_add( disk_t *d, int gaptype )
{
  int len = d->c_bpt - d->i;
  disk_gap_t *g = &gaps[ gaptype ];

  if( len < 0 ) {
    return 1;
  }
/*------------------------------     GAP IV     ------------------------------*/
  memset( d->track + d->i, g->gap, len ); /* GAP IV fill until end of track */
  d->i = d->c_bpt;
  return 0;
}

#define SECLEN_128 0x00
#define SECLEN_256 0x01
#define SECLEN_512 0x02
#define SECLEN_1024 0x03
#define CRC_OK 0
#define CRC_ERROR 1

/*
  [ ....GAP.... ] [ ... SYNC ... ] [ . MARK . ] [ .. DATA .. ] [ . CRC . ]
                 |------------------------------------------------------->
		                    ID 
*/
static int
id_add( disk_t *d, int h, int t, int s, int l, int gaptype, int crc_error )
{
  libspectrum_word crc = 0xffff;
  disk_gap_t *g = &gaps[ gaptype ];
  if( d->i + g->sync_len + ( g->mark >= 0 ? 3 : 0 ) + 7 >= d->c_bpt )
    return 1;
/*------------------------------   sync    ---------------------------*/
  memset( d->track + d->i, g->sync, g->sync_len ); d->i += g->sync_len;
  if( g->mark >= 0 ) {
    memset( d->track + d->i , g->mark, 3 );
    bitmap_set( d->clocks, d->i ); d->i++;
    crc = crc_fdc( crc, g->mark );
    bitmap_set( d->clocks, d->i ); d->i++;
    crc = crc_fdc( crc, g->mark );
    bitmap_set( d->clocks, d->i ); d->i++;
    crc = crc_fdc( crc, g->mark );
  }
/*------------------------------     mark     ------------------------------*/
  if( g->mark < 0 )			/* FM */
    bitmap_set( d->clocks, d->i ); 	/* set clock mark */
  d->track[ d->i++ ] = 0xfe;		/* ID mark */
  crc = crc_fdc( crc, 0xfe );
/*------------------------------     header     ------------------------------*/
  d->track[ d->i++ ] = t; crc = crc_fdc( crc, t );
  d->track[ d->i++ ] = h; crc = crc_fdc( crc, h );
  d->track[ d->i++ ] = s; crc = crc_fdc( crc, s );
  d->track[ d->i++ ] = l; crc = crc_fdc( crc, l );
  d->track[ d->i++ ] = crc >> 8;
  if( crc_error ) {
    d->track[ d->i++ ] = (~crc) & 0xff;	/* record a CRC error */
  } else {
    d->track[ d->i++ ] = crc & 0xff;	/* CRC */
  }
/*------------------------------     GAP II     ------------------------------*/
  return gap_add( d, 2, gaptype );
}

/*
  [ ....GAP.... ] [ ... SYNC ... ] [ . MARK . ] [ .. DATA .. ] [ . CRC . ]
 |-------------------------------------------->
                  datamark
*/
static int
datamark_add( disk_t *d, int ddam, int gaptype )
{
  disk_gap_t *g = &gaps[ gaptype ];
  if( d->i + g->len[2] + g->sync_len + ( g->mark >= 0 ? 3 : 0 ) + 1 >= d->c_bpt )
    return 1;
/*------------------------------   sync    ---------------------------*/
  memset( d->track + d->i, g->sync, g->sync_len ); d->i += g->sync_len;
  if( g->mark >= 0 ) {
    memset( d->track + d->i , g->mark, 3 );
    bitmap_set( d->clocks, d->i ); d->i++;
    bitmap_set( d->clocks, d->i ); d->i++;
    bitmap_set( d->clocks, d->i ); d->i++;
  }
/*------------------------------     mark     ------------------------------*/
  if( g->mark < 0 )			/* FM */
    bitmap_set( d->clocks, d->i ); 	/* set clock mark */
  d->track[ d->i++ ] = ddam ? 0xf8 : 0xfb;	/* DATA mark 0xf8 -> deleted data */
  return 0;
}

#define NO_DDAM 0
#define DDAM 1
#define NO_AUTOFILL -1
/* copy data from *buffer and update *buffer->index */  
/* if 'buffer' == NULL, then copy data bytes from 'data' */  
static int
data_add( disk_t *d, buffer_t *buffer, unsigned char *data, int len, int ddam,
		int gaptype, int crc_error, int autofill, int *start_data )
{
  int length;
  libspectrum_word crc = 0xffff;
  disk_gap_t *g = &gaps[ gaptype ];

  if( datamark_add( d, ddam, gaptype ) )
    return 1;

  if( g->mark >= 0 ) {
    crc = crc_fdc( crc, g->mark );
    crc = crc_fdc( crc, g->mark );
    crc = crc_fdc( crc, g->mark );
  }
  crc = crc_fdc( crc, ddam ? 0xf8 : 0xfb );	/* deleted or normal */
  if( len < 0 )
    goto header_crc_error;			/* CRC error */
  if( d->i + len + 2 >= d->c_bpt )  		/* too many data bytes */
    return 1;
/*------------------------------      data      ------------------------------*/
  if( start_data != NULL ) *start_data = d->i;	/* record data start position */
  if( buffer == NULL ) {
    memcpy( d->track + d->i, data, len );
    length = len;
  } else {
    length = buffavail( buffer );
    if( length > len ) length = len;
    buffread( d->track + d->i, length, buffer );
  }
  if( length < len ) {	/* autofill with 'autofill' */
    if( autofill < 0 )
      return 1;
    while( length < len ) {
      d->track[ d->i + length ] = autofill;
      length++;
    }
  }
  length = 0;
  while( length < len ) {	/* calculate CRC */
    crc = crc_fdc( crc, d->track[ d->i ] );
    d->i++;
    length++;
  }
  if( crc_error ) crc ^= 1;	/* mess up CRC */
  d->track[ d->i++ ] = crc >> 8; d->track[ d->i++ ] = crc & 0xff;    /* CRC */
/*------------------------------     GAP III    ------------------------------*/
header_crc_error:
  return ( gap_add( d, 3, gaptype ) );
}

static int
calc_sectorlen( int mfm, int sector_length, int gaptype )
{
  int len = 0;
  disk_gap_t *g = &gaps[ gaptype ];
  
/*------------------------------     ID        ------------------------------*/
  len += g->sync_len + ( g->mark >= 0 ? 3 : 0 ) + 7;
/*------------------------------     GAP II    ------------------------------*/
  len += g->len[2];
/*---------------------------------  data   ---------------------------------*/
  len += g->sync_len + ( g->mark >= 0 ? 3 : 0 ) + 1;		/* DAM */
  len += sector_length;
  len += 2;		/* CRC */
/*------------------------------    GAP III    ------------------------------*/
  len += g->len[3];
  return len;
}

static int
calc_lenid( int sector_length )
{
  int id = 0;

  while( sector_length > 0x80 ) {
    id++;
    sector_length >>= 1;
  }

  return id;
}

#define NO_INTERLEAVE 1
#define INTERLEAVE_2 2
#define INTERLEAVE_OPUS 13
#define NO_PREINDEX 0
#define PREINDEX 1

static int
trackgen( disk_t *d, buffer_t *buffer, int head, int track,
	  int sector_base, int sectors, int sector_length, int preindex,
	  int gap, int interleave, int autofill )
{
  int i, s, pos;
  int slen = calc_sectorlen( ( d->density != DISK_SD && d->density != DISK_8_SD ),
			     sector_length, gap );
  int idx;

  d->i = 0;
  DISK_SET_TRACK( d, head, track );
  if( preindex && preindex_add( d, gap ) )
    return 1;
  if( postindex_add( d, gap ) )
    return 1;

  idx = d->i;
  pos = i = 0;
  for( s = sector_base; s < sector_base + sectors; s++ ) {
    d->i = idx + pos * slen;
    if( id_add( d, head, track, s, calc_lenid( sector_length ), gap, CRC_OK ) )
      return 1;
    if( data_add( d, buffer, NULL, sector_length, NO_DDAM, gap, CRC_OK, autofill, NULL ) )
      return 1;
    pos += interleave;
    if( pos >= sectors ) {	/* wrap around */
      pos -= sectors;
      if( pos <= i ) {		/* we fill this pos already */
        pos++;			/* skip one more pos */
        i++;
      }
    }
  }
  d->i = idx + sectors * slen;
  return gap4_add( d, gap );
}

/* close and destroy a disk structure and data */
void
disk_close( disk_t *d )
{
  if( d->data != NULL ) {
    libspectrum_free( d->data );
    d->data = NULL;
  }
  if( d->filename != NULL ) {
    libspectrum_free( d->filename );
    d->filename = NULL;
  }
  d->type = DISK_TYPE_NONE;
}

/*
 *  if d->density == DISK_DENS_AUTO => 
 *                            use d->tlen if d->bpt == 0
 *                             or use d->bpt to determine d->density
 *  or use d->density
 */
static int
disk_alloc( disk_t *d )
{
  size_t dlen;

  if( d->density != DISK_DENS_AUTO ) {
    d->bpt = disk_bpt[ d->density ];
  } else if( d->bpt > 12500 ) {
    return d->status = DISK_UNSUP;
  } else if( d->bpt > 10416 ) {
    d->density = DISK_HD;
    d->bpt = disk_bpt[ DISK_HD ];
  } else if( d->bpt > 6500 ) {
    d->density = DISK_8_DD;
    d->bpt = disk_bpt[ DISK_8_DD ];
  } else if( d->bpt > 6250 ) {
    d->density = DISK_DD_PLUS;
    d->bpt = disk_bpt[ DISK_DD_PLUS ];
  } else if( d->bpt > 5208 ) {
    d->density = DISK_DD;
    d->bpt = disk_bpt[ DISK_DD ];
  } else if( d->bpt > 3125 ) {
    d->density = DISK_8_SD;
    d->bpt = disk_bpt[ DISK_8_SD ];
  } else if( d->bpt > 0 ) {
    d->density = DISK_SD;
    d->bpt = disk_bpt[ DISK_SD ];
  }

  if( d->bpt > 0 )
    d->tlen = 4 + d->bpt + 3 * DISK_CLEN( d->bpt );

  dlen = d->sides * d->cylinders * d->tlen;	/* track len with clock and other marks */
  if( dlen == 0 ) return d->status = DISK_GEOM;

  d->data = libspectrum_new0( libspectrum_byte, dlen );

  disk_update_tlens( d );
  return d->status = DISK_OK;
}

/* create a new unformatted disk  */
int
disk_new( disk_t *d, int sides, int cylinders,
	     disk_dens_t density, disk_type_t type )
{
  d->filename = NULL;
  if( density < DISK_DENS_AUTO || density > DISK_HD ||	/* unknown density */
      type <= DISK_TYPE_NONE || type >= DISK_TYPE_LAST || /* unknown type */
      sides < 1 || sides > 2 ||				/* 1 or 2 side */
      cylinders < 35 || cylinders > 83 )		/* 35 .. 83 cylinder */
    return d->status = DISK_GEOM;

  d->type = type;
  d->density = density == DISK_DENS_AUTO ? DISK_DD : density;
  d->sides = sides;
  d->cylinders = cylinders;

  if( disk_alloc( d ) != DISK_OK )
    return d->status;

  d->wrprot = 0;
  d->dirty = 1;
  return d->status = DISK_OK;
}

static int
alloc_uncompress_buffer( unsigned char **buffer, int length )
{
  unsigned char *b;

  if( *buffer != NULL )				/* return if allocated */
    return 0;
  b = libspectrum_new0( unsigned char, length );
  if( b == NULL )
    return 1;
  *buffer = b;
  return 0;
}

int disk_preformat( disk_t *d )
{
  buffer_t buffer;

  buffer.file.length = 0;
  buffer.index = 0;

  if( d->sides == 2 ) {
    if( trackgen( d, &buffer, 1, 0, 0xff, 1, 128,
                  NO_PREINDEX, GAP_MINIMAL_MFM, NO_INTERLEAVE, 0xff ) )
    return DISK_GEOM;

    if( trackgen( d, &buffer, 1, 2, 0xfe, 1, 128,
                  NO_PREINDEX, GAP_MINIMAL_MFM, NO_INTERLEAVE, 0xff ) )
    return DISK_GEOM;
  }

  if( trackgen( d, &buffer, 0, 0, 0xff, 1, 128,
		      NO_PREINDEX, GAP_MINIMAL_MFM, NO_INTERLEAVE, 0xff ) )
    return DISK_GEOM;
  if( trackgen( d, &buffer, 0, 2, 0xfe, 1, 128,
		      NO_PREINDEX, GAP_MINIMAL_MFM, NO_INTERLEAVE, 0xff ) )
    return DISK_GEOM;

  return DISK_OK;
}

/* open a disk image */
#define GEOM_CHECK \
    if( d->sides < 1 || d->sides > 2 || \
       d->cylinders < 1 || d->cylinders > 85 ) return d->status = DISK_GEOM

#ifdef LIBSPECTRUM_SUPPORTS_ZLIB_COMPRESSION
static int
udi_read_compressed( const libspectrum_byte *buffer,
		   size_t compr_size, size_t uncompr_size,
		   libspectrum_byte **data, size_t *data_size )
{
  libspectrum_error error;
  libspectrum_byte *tmp;
  size_t olength = uncompr_size;

  tmp = NULL;

  error = libspectrum_zlib_inflate( buffer, compr_size, &tmp, &olength );
  if( error ) return error;

  if( *data_size < uncompr_size ) {
    *data = libspectrum_renew( libspectrum_byte, *data, uncompr_size );
    *data_size = uncompr_size;
  }
  memcpy( *data, tmp, uncompr_size );
  libspectrum_free( tmp );

  return 0;
}

static int
udi_write_compressed( const libspectrum_byte *buffer,
		      size_t uncompr_size, size_t *compr_size,
		      libspectrum_byte **data, size_t *data_size )
{
  libspectrum_error error;
  libspectrum_byte *tmp;

  tmp = NULL;
  error = libspectrum_zlib_compress( buffer, uncompr_size,
                                     &tmp, compr_size );
  if( error ) return error;

  if( *data_size < *compr_size ) {
    *data = libspectrum_renew( libspectrum_byte, *data, *compr_size );
    *data_size = *compr_size;
  }
  memcpy( *data, tmp, *compr_size );
  libspectrum_free( tmp );

  return LIBSPECTRUM_ERROR_NONE;
}
#endif			/* #ifdef LIBSPECTRUM_SUPPORTS_ZLIB_COMPRESSION */

static void
udi_pack_tracks( disk_t *d )
{
  int i, tlen, clen, ttyp;
  libspectrum_byte *tmp;

  for( i = 0; i < d->sides * d->cylinders; i++ ) {
    DISK_SET_TRACK_IDX( d, i );
    tmp = d->track;
    ttyp = tmp[-1];
    tlen = tmp[-3] + 256 * tmp[-2];
    clen = DISK_CLEN( tlen );
    tmp += tlen;
    /* copy clock if needed */
    if( tmp != d->clocks )
      memcpy( tmp, d->clocks, clen );
    if( ttyp == 0x00 || ttyp == 0x01 ) continue;
    tmp += clen;
    if( ttyp & 0x02 ) {		/* copy FM marks */
      if( tmp != d->fm )
        memcpy( tmp, d->fm, clen );
      tmp += clen;
    }
    if( ! ( ttyp & 0x80 ) ) continue;
    if( tmp != d->weak )		/* copy WEAK marks*/
      memcpy( tmp, d->weak, clen );
  }
}

static void
udi_unpack_tracks( disk_t *d )
{
  int i, tlen, clen, ttyp;
  libspectrum_byte *tmp;
  libspectrum_byte mask[] = { 0xff, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe };

  for( i = 0; i < d->sides * d->cylinders; i++ ) {
    DISK_SET_TRACK_IDX( d, i );
    tmp = d->track;
    ttyp = tmp[-1];
    tlen = tmp[-3] + 256 * tmp[-2];
    clen = DISK_CLEN( tlen );
    tmp += tlen;
    if( ttyp & 0x80 ) tmp += clen;
    if( ttyp & 0x02 ) tmp += clen;
    if( ( ttyp & 0x80 ) ) {	/* copy WEAK marks*/
      if( tmp != d->weak )
        memcpy( d->weak, tmp, clen );
      tmp -= clen;
    } else {			/* clear WEAK marks*/
      memset( d->weak, 0, clen );
    }
    if( ttyp & 0x02 ) {		/* copy FM marks */
      if( tmp != d->fm )
        memcpy( d->fm, tmp, clen );
      tmp -= clen;
    } else {			/* set/clear FM marks*/
      memset( d->fm, ttyp & 0x01 ? 0xff : 0, clen );
      if( tlen % 8 ) {		/* adjust last byte */
        d->fm[clen - 1] &= mask[ tlen % 8 ];
      }
    }
    /* copy clock if needed */
    if( tmp != d->clocks )
      memcpy( d->clocks, tmp, clen );
  }
}

/* calculate track len from type, if type eq. 0x00/0x01/0x02/0x80/0x81/0x82
   !!! not for 0x83 nor 0xf0 !!!
*/
#define UDI_TLEN( type, bpt ) ( ( bpt ) + DISK_CLEN( bpt ) * ( 1 + \
					( type & 0x02 ? 1 : 0 ) + \
					( type & 0x80 ? 1 : 0 ) ) )

static int
udi_uncompress_tracks( disk_t *d )
{
  int i;
  libspectrum_byte *data = NULL;
#ifdef LIBSPECTRUM_SUPPORTS_ZLIB_COMPRESSION
  size_t data_size = 0;
  int bpt, tlen, clen, ttyp;
#endif			/* #ifdef LIBSPECTRUM_SUPPORTS_ZLIB_COMPRESSION */

  for( i = 0; i < d->sides * d->cylinders; i++ ) {
    DISK_SET_TRACK_IDX( d, i );
    if( d->track[-1] != 0xf0 ) continue;	/* if not compressed */

#ifndef LIBSPECTRUM_SUPPORTS_ZLIB_COMPRESSION
    /* if libspectrum cannot support */
    return d->status = DISK_UNSUP;
#else 			/* #ifndef LIBSPECTRUM_SUPPORTS_ZLIB_COMPRESSION */
    clen = d->track[-3] + 256 * d->track[-2] + 1;
    ttyp = d->track[0];				/* compressed track type   */
    bpt = d->track[1] + 256 * d->track[2];	/* compressed track len... */
    tlen = UDI_TLEN( ttyp, bpt );
    d->track[-1] = ttyp;
    d->track[-3] = d->track[1];
    d->track[-2] = d->track[2];
    if( udi_read_compressed( d->track + 3, clen, tlen, &data, &data_size ) ) {
      if( data ) libspectrum_free( data );
      return d->status = DISK_UNSUP;
    }
    memcpy( d->track, data, tlen );		/* read track */
#endif			/* #ifndef LIBSPECTRUM_SUPPORTS_ZLIB_COMPRESSION */
  }
  if( data ) libspectrum_free( data );
  return DISK_OK;
}

#ifdef LIBSPECTRUM_SUPPORTS_ZLIB_COMPRESSION
static int
udi_compress_tracks( disk_t *d )
{
  int i, tlen;
  libspectrum_byte *data = NULL;
  size_t clen, data_size = 0;

  for( i = 0; i < d->sides * d->cylinders; i++ ) {
    DISK_SET_TRACK_IDX( d, i );
    if( d->track[-1] == 0xf0 ) continue;	/* already compressed??? */

    tlen = UDI_TLEN( d->track[-1], d->track[-3] + 256 * d->track[-2] );
	/* if fail to compress, skip ... */
    if( udi_write_compressed( d->track, tlen, &clen, &data, &data_size ) ||
							clen < 1 ) continue;
	/* if compression too large, skip... */
    if( clen > 65535 || clen >= tlen ) continue;
    d->track[0] = d->track[-1];			/* track type... */
    d->track[1] = d->track[-3];			/* compressed track len... */
    d->track[2] = d->track[-2];			/* compressed track len... */
    memcpy( d->track + 3, data, clen );		/* read track */
    clen--;
    d->track[-1] = 0xf0;
    d->track[-3] = clen & 0xff;
    d->track[-2] = ( clen >> 8 ) & 0xff;
  }
  if( data ) libspectrum_free( data );
  return DISK_OK;
}
#endif			/* #ifdef LIBSPECTRUM_SUPPORTS_ZLIB_COMPRESSION */

static int
open_udi( buffer_t *buffer, disk_t *d )
{
  int i, bpt, ttyp, tlen, error;
  size_t eof;
  libspectrum_dword crc;

  crc = ~(libspectrum_dword) 0;

  /* check file length */
  eof = buff[4] + 256 * buff[5] + 65536 * buff[6] + 16777216 * buff[7];
  if( eof != buffer->file.length - 4 )
    return d->status = DISK_OPEN;
  /* check CRC32 */
  for( i = 0; i < eof; i++ )
    crc = crc_udi( crc, buff[i] );
  if( crc != buff[eof] + 256 * buff[eof + 1] + 65536 * buff[eof + 2] +
						16777216 * buff[eof + 3] )
    return d->status = DISK_OPEN;

  d->sides = buff[10] + 1;
  d->cylinders = buff[9] + 1;
  GEOM_CHECK;
  d->density = DISK_DENS_AUTO;
  buffer->index = 16;
  d->bpt = 0;

  /* scan file for the longest track */
  for( i = 0; buffer->index < eof; i++ ) {
    if( buffavail( buffer ) < 3 )
      return d->status = DISK_OPEN;
    ttyp = buff[0];
    if( ttyp != 0x00 && ttyp != 0x01 && ttyp != 0x02 && ttyp != 0x80 &&
        ttyp != 0x81 && ttyp != 0x82 && ttyp != 0x83 && ttyp != 0xf0 )
      return d->status = DISK_UNSUP;

    /* if libspectrum cannot suppot*/
#ifndef LIBSPECTRUM_SUPPORTS_ZLIB_COMPRESSION
    if( ttyp == 0xf0 ) d->status = DISK_UNSUP;
#endif			/* #ifndef LIBSPECTRUM_SUPPORTS_ZLIB_COMPRESSION */
    if( ttyp == 0x83 ) {			/* multiple read */
      if( i == 0 ) return d->status = DISK_GEOM;	/* cannot be first track */
      i--; bpt = 0;					/* not a real track */
      tlen = buff[1] + 256 * buff[2];		/* current track len... */
      tlen = ( tlen & 0xfff8 ) * ( tlen & 0x07 );
    } else if( ttyp == 0xf0 ) {			/* compressed track */
      if( buffavail( buffer ) < 7 )
        return d->status = DISK_OPEN;
      bpt = buff[4] + 256 * buff[5];
      tlen = 7 + buff[1] + 256 * buff[2];
    } else {
      bpt = buff[1] + 256 * buff[2];		/* current track len... */
      tlen = 3 + UDI_TLEN( ttyp, bpt );
    }
    if( bpt > d->bpt )
      d->bpt = bpt;
    if( buffseek( buffer, tlen, SEEK_CUR ) == -1 )
      return d->status = DISK_OPEN;
  }

  if( d->bpt == 0 )
    return d->status = DISK_GEOM;

  bpt = d->bpt;		/* save the maximal value */
  d->tlen = 3 + bpt + 3 * DISK_CLEN( bpt );
  d->bpt = 0;		/* we know exactly the track len... */
  if( disk_alloc( d ) != DISK_OK )
    return d->status;
  d->bpt = bpt;		/* restore the maximal byte per track */
  buffer->index = 16;

  for( i = 0; buffer->index < eof; i++ ) {
    DISK_SET_TRACK_IDX( d, i );
    ttyp = buff[0];
    bpt = buff[1] + 256 * buff[2];		/* current track len... */

    memset( d->track, 0x4e, d->bpt );		/* fillup */
						/* read track + clocks */
    if( ttyp == 0x83 ) {			/* multiple read */
      i--;					/* not a real track */
      DISK_SET_TRACK_IDX( d, i );		/* back to previouse track */
      d->weak += buff[3] + 256 * buff[4];	/* add offset to weak */
      tlen = ( buff[1] + 256 * buff[2] ) >> 3;	/* weak len in bytes */
      for( tlen--; tlen >= 0; tlen-- )
        d->weak[tlen] = 0xff;
      tlen = buff[1] + 256 * buff[2];		/* current track len... */
      tlen = ( tlen & 0xfff8 ) * ( tlen & 0x07 );
      buffseek( buffer, tlen, SEEK_CUR );
    } else {
      if( ttyp == 0xf0 )			/* compressed */
        tlen = bpt + 4;
      else
        tlen = UDI_TLEN( ttyp, bpt );
      d->track[-1] = ttyp;
      d->track[-3] = buff[1];
      d->track[-2] = buff[2];
      buffer->index += 3;
      buffread( d->track, tlen, buffer );	/* first read data */
    }
  }
  error = udi_uncompress_tracks( d );
  if( error ) return error;
  udi_unpack_tracks( d );

  return d->status = DISK_OK;
}

static int
open_img_mgt_opd( buffer_t *buffer, disk_t *d )
{
  int i, j, sectors, seclen;

  buffer->index = 0;

  /* guess geometry of disk:
   * 2*80*10*512, 1*80*10*512, 1*40*10*512, 1*40*18*256, 1*80*18*256,
   * 2*80*18*256
   */
  if( buffer->file.length == 2*80*10*512 ) {
    d->sides = 2; d->cylinders = 80; sectors = 10; seclen = 512;
  } else if( buffer->file.length == 1*80*10*512 ) {
    /* we cannot distinguish between a single sided 80 track image
     * and a double sided 40 track image (2*40*10*512) */
    d->sides = 1; d->cylinders = 80; sectors = 10; seclen = 512;
  } else if( buffer->file.length == 1*40*10*512 ) {
    d->sides = 1; d->cylinders = 40; sectors = 10; seclen = 512;
  } else if( buffer->file.length == 1*40*18*256 ) {
    d->sides = 1; d->cylinders = 40; sectors = 18; seclen = 256;
  } else if( buffer->file.length == 1*80*18*256 ) {
    /* we cannot distinguish between a single sided 80 track image
     * and a double sided 40 track image (2*40*18*256) */
    d->sides = 1; d->cylinders = 80; sectors = 18; seclen = 256;
  } else if( buffer->file.length == 2*80*18*256 ) {
    d->sides = 2; d->cylinders = 80; sectors = 18; seclen = 256;
  } else {
    return d->status = DISK_GEOM;
  }

  /* create a DD disk */
  d->density = DISK_DD;
  if( disk_alloc( d ) != DISK_OK )
    return d->status;

  if( d->type == DISK_IMG ) {	/* IMG out-out */
    for( j = 0; j < d->sides; j++ ) {
      for( i = 0; i < d->cylinders; i++ ) {
	if( trackgen( d, buffer, j, i, 1, sectors, seclen,
		      NO_PREINDEX, GAP_MGT_PLUSD, NO_INTERLEAVE, NO_AUTOFILL ) )
	  return d->status = DISK_GEOM;
      }
    }
  } else {			/* MGT / OPD alt */
    for( i = 0; i < d->cylinders; i++ ) {
      for( j = 0; j < d->sides; j++ ) {
        if( trackgen( d, buffer, j, i, d->type == DISK_MGT ? 1 : 0, sectors, seclen,
		      NO_PREINDEX, GAP_MGT_PLUSD,
		      d->type == DISK_MGT ? NO_INTERLEAVE : INTERLEAVE_OPUS, NO_AUTOFILL ) )
	  return d->status = DISK_GEOM;
      }
    }
  }

  return d->status = DISK_OK;
}

static int
open_d40_d80( buffer_t *buffer, disk_t *d )
{
  int i, j, sectors, seclen;

  if( buffavail( buffer ) < 180 )
    return d->status = DISK_OPEN;

  /* guess geometry of disk */
  d->sides =     buff[0xb1] & 0x10 ? 2 : 1;
  d->cylinders = buff[0xb2];
  sectors =      buff[0xb3];

  if( d->sides < 1 || d->sides > 2 || d->cylinders > 83 || sectors > 127 )
    return d->status = DISK_GEOM;

  seclen = 512;

  buffer->index = 0;

  /* create a DD disk */
  d->density = DISK_DD;
  if( disk_alloc( d ) != DISK_OK )
    return d->status;

  for( i = 0; i < d->cylinders; i++ ) {
      for( j = 0; j < d->sides; j++ ) {
        if( trackgen( d, buffer, j, i, 1, sectors, seclen,
		      NO_PREINDEX, GAP_MGT_PLUSD, NO_INTERLEAVE, NO_AUTOFILL ) )
	  return d->status = DISK_GEOM;
      }
  }

  return d->status = DISK_OK;
}

static int
open_sad( buffer_t *buffer, disk_t *d, int preindex )
{
  int i, j, sectors, seclen;

  d->sides = buff[18];
  d->cylinders = buff[19];
  GEOM_CHECK;
  sectors = buff[20];
  seclen = buff[21] * 64;
  buffer->index = 22;

  /* create a DD disk */
  d->density = DISK_DD;
  if( disk_alloc( d ) != DISK_OK )
    return d->status;

  for( j = 0; j < d->sides; j++ ) {
    for( i = 0; i < d->cylinders; i++ ) {
      if( trackgen( d, buffer, j, i, 1, sectors, seclen, preindex, 
		    GAP_MGT_PLUSD, NO_INTERLEAVE, NO_AUTOFILL ) )
	return d->status = DISK_GEOM;
    }
  }

  return d->status = DISK_OK;
}

/* 1 RANDOMIZE USR 15619: REM : RUN "        " */
static libspectrum_byte beta128_boot_loader[] = {
  0x00, 0x01, 0x1c, 0x00, 0xf9, 0xc0, 0x31, 0x35, 0x36, 0x31, 0x39, 0x0e, 
  0x00, 0x00, 0x03, 0x3d, 0x00, 0x3a, 0xea, 0x3a, 0xf7, 0x22, 0x20, 0x20, 
  0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x0d, 
};

static int
trdos_insert_basic_file( disk_t *d, trdos_spec_t *spec,
                         const libspectrum_byte *data, unsigned int size )
{
  unsigned int fat_sector, fat_entry, n_sec, n_bytes, n_copied;
  int i, t, s, slen, len_pre_dam, len_pre_data;
  disk_gap_t *g = &gaps[ GAP_TRDOS ];
  trdos_dirent_t entry;
  libspectrum_byte trailing_data[] = { 0x80, 0xaa, 0x01, 0x00 }; /* line 1 */

  /* Check free FAT entries (we don't purge deleted files) */
  if( spec->file_count >= 128 )
    return DISK_UNSUP;

  /* Check free sectors */
  n_sec = ( size + ARRAY_SIZE( trailing_data ) + 255 ) / 256;
  if( spec->free_sectors < n_sec )
    return DISK_UNSUP;

  /* Calculate sector raw length */
  slen = calc_sectorlen( ( d->density != DISK_SD && d->density != DISK_8_SD ),
                         256, GAP_TRDOS );

  /* Calculate initial gap before data in a sector */
  len_pre_dam = 0;
  len_pre_dam += g->sync_len + ( g->mark >= 0 ? 3 : 0 ) + 7;     /* ID */
  len_pre_dam += g->len[2];                                      /* GAP II */

  len_pre_data = len_pre_dam;
  len_pre_data += g->sync_len + ( g->mark >= 0 ? 3 : 0 ) + 1;    /* DAM */

  /* Write file data */
  n_copied = 0;
  s = spec->first_free_sector;
  t = spec->first_free_track;
  DISK_SET_TRACK_IDX( d, t );

  for( i = 0; i < n_sec; i++ ) {
    memset( head, 0, 256 );
    n_bytes = 0;

    /* Copy chunk of file body */
    if( n_copied < size ) {
      n_bytes = ( size - n_copied > 256 )? 256 : size - n_copied;
      memcpy( head, data + n_copied, n_bytes );
      n_copied += n_bytes;
    }

    /* Copy trailing parameters */
    if( n_copied >= size ) {
      while( n_copied - size < ARRAY_SIZE( trailing_data ) && n_bytes < 256 ) {
        head[ n_bytes ] = trailing_data[ n_copied - size ];
        n_copied++;
        n_bytes++;
      }
    }

    /* Write buffer to disk */
    d->i = g->len[1] + ( s % 8 * 2 + s / 8 ) * slen;    /* 1 9 2 10 3 ... */  
    d->i += len_pre_dam;
    data_add( d, NULL, head, 256, NO_DDAM, GAP_TRDOS, CRC_OK, NO_AUTOFILL,
              NULL );

    /* Next sector */
    s = ( s + 1 ) % 16;

    /* Next track */
    if( s == 0 ) {
      t = t + 1;
      if( t >= d->cylinders ) return DISK_UNSUP;
      DISK_SET_TRACK_IDX( d, t );
    }
  }

  /* Write FAT entry */
  memcpy( entry.filename, "boot    ", 8 );
  entry.file_extension = 'B';
  entry.param1         = size; /* assumes variables = 0 */
  entry.param2         = size;
  entry.file_length    = n_sec;
  entry.start_sector   = spec->first_free_sector;
  entry.start_track    = spec->first_free_track;

  /* Copy sector to buffer, modify and write back to disk recalculating CRCs */
  DISK_SET_TRACK_IDX( d, 0 );
  fat_sector = spec->file_count / 16;
  d->i = g->len[1] + ( ( fat_sector ) % 8 * 2 + ( fat_sector ) / 8 ) * slen;  
  memcpy( head, d->track + d->i + len_pre_data, 256 );

  fat_entry  = spec->file_count % 16;
  trdos_write_dirent( head + fat_entry * 16, &entry );

  d->i += len_pre_dam;
  data_add( d, NULL, head, 256, NO_DDAM, GAP_TRDOS, CRC_OK, NO_AUTOFILL, NULL );

  /* Write specification sector */
  spec->file_count       += 1;
  spec->free_sectors     -= n_sec;
  spec->first_free_sector = s;
  spec->first_free_track  = t;
  trdos_write_spec( head, spec );

  d->i = g->len[1] + slen + len_pre_dam;    /* sector-9: 1 9 2 10 3 ... */  
  data_add( d, NULL, head, 256, NO_DDAM, GAP_TRDOS, CRC_OK, NO_AUTOFILL, NULL );

  return DISK_OK;
}

static void
trdos_insert_boot_loader( disk_t *d )
{
  trdos_spec_t spec;
  trdos_boot_info_t info;
  int slen, del;

  /* TR-DOS specification sector */
  DISK_SET_TRACK_IDX( d, 0 );
  if( !id_seek( d, 9 ) || !datamark_read( d, &del ) )
    return;

  if( trdos_read_spec( &spec, d->track + d->i ) )
    return;

  /* Check free FAT entries (we don't purge deleted files) */
  if( spec.file_count >= 128 )
    return;

  /* Check there is at least one free sector */
  if( spec.free_sectors == 0 )
    return;
  /* TODO: stealth mode? some boot loaders hide between sectors 10-16 */

  /* Calculate sector raw length */
  slen = calc_sectorlen( ( d->density != DISK_SD && d->density != DISK_8_SD ),
                         256, GAP_TRDOS );

  /* Read FAT entries */
  if( !id_seek( d, 1 ) || !datamark_read( d, &del ) )
    return;

  if( trdos_read_fat( &info, d->track + d->i, slen ) )
    return;

  /* Check actual boot file (nothing to do) */
  if( info.have_boot_file )
    return;

  /* Insert a simple boot loader that runs the first program */
  if( info.basic_files_count >= 1 ) {
    memcpy( beta128_boot_loader + 22, info.first_basic_file, 8 );

    trdos_insert_basic_file( d, &spec, beta128_boot_loader,
                             ARRAY_SIZE( beta128_boot_loader ) );
  }

  /* TODO: use also a boot loader that can handle multiple basic pograms */
}

static int
open_trd( buffer_t *buffer, disk_t *d )
{
  int i, j, sectors, seclen;
  disk_position_context_t context;

  if( buffseek( buffer, 8*256, SEEK_CUR ) == -1 )
      return d->status = DISK_OPEN;
  if( buffavail( buffer ) < 256 )
    return d->status = DISK_OPEN;

  if( buff[231] != 0x10 || buff[227] < 0x16 || buff[227] > 0x19 )
    return d->status = DISK_OPEN;	/*?*/

  /* guess geometry of disk */
  d->sides =  buff[227] & 0x08 ? 1 : 2;
  d->cylinders = buff[227] & 0x01 ? 40 : 80;
  /* we have more tracks then on a standard disk... */
  if( buffer->file.length > d->sides * d->cylinders * 16 * 256 ) {
    for( i = d->cylinders + 1; i < 83; i++ ) {
      if( d->sides * i * 16 * 256 >= buffer->file.length ) break;
    }
    d->cylinders = i;
  }
  sectors = 16; seclen = 256;

  /* create a DD disk */
  d->density = DISK_DD;
  if( disk_alloc( d ) != DISK_OK )
    return d->status;

  buffer->index = 0;
  for( i = 0; i < d->cylinders; i++ ) {
    for( j = 0; j < d->sides; j++ ) {
      if( trackgen( d, buffer, j, i, 1, sectors, seclen,
                    NO_PREINDEX, GAP_TRDOS, INTERLEAVE_2, 0x00 ) )
        return d->status = DISK_GEOM;
    }
  }
  
  if( settings_current.auto_load ) {
    position_context_save( d, &context );
    trdos_insert_boot_loader( d );
    position_context_restore( d, &context );
  }

  return d->status = DISK_OK;
}

static int
open_fdi( buffer_t *buffer, disk_t *d, int preindex )
{
  int i, j, h, gap;
  int bpt, bpt_fm, max_bpt = 0, max_bpt_fm = 0;
  int data_offset, track_offset, head_offset, sector_offset;

  d->wrprot = buff[0x03] == 1 ? 1 : 0;
  d->sides = buff[0x06] + 256 * buff[0x07];
  d->cylinders = buff[0x04] + 256 * buff[0x05];
  GEOM_CHECK;
  data_offset = buff[0x0a] + 256 * buff[0x0b];
  h = 0x0e + buff[0x0c] + 256 * buff[0x0d];		/* save head start */
  head_offset = h;

  /* first determine the longest track */
  d->bpt = 0;
  for( i = 0; i < d->cylinders * d->sides; i++ ) {	/* ALT */
    buffer->index = head_offset;
    if( buffread( head, 7, buffer ) != 1 )	/* 7 := track head  */
      return d->status = DISK_OPEN;
    bpt = postindex_len( d, GAP_MINIMAL_MFM ) +
	  ( preindex ? preindex_len( d, GAP_MINIMAL_MFM ) : 0 ) + 6; /* +gap4 */
    bpt_fm = postindex_len( d, GAP_MINIMAL_FM ) +
	     ( preindex ? preindex_len( d, GAP_MINIMAL_FM ) : 0 ) + 3;  /* +gap4 */
    for( j = 0; j < head[0x06]; j++ ) {		/* calculate track len */
      if( j % 35 == 0 ) {				/* 35-sector header */
	if( buffread( head + 7, 245, buffer ) != 1 )	/* 7*35 := max 35 sector head */
	  return d->status = DISK_OPEN;
      }
      if( ( head[ 0x0b + 7 * ( j % 35 ) ] & 0x3f ) != 0 ) {
	bpt += calc_sectorlen( 1, 0x80 << head[ 0x0a + 7 * ( j % 35 ) ], GAP_MINIMAL_MFM );
	bpt_fm += calc_sectorlen( 0, 0x80 << head[ 0x0a + 7 * ( j % 35 ) ], GAP_MINIMAL_FM );
      }
    }
    if( bpt > max_bpt )
      max_bpt = bpt;
    if( bpt_fm > max_bpt_fm )
      max_bpt_fm = bpt_fm;

    head_offset += 7 + 7 * head[ 0x06 ];
  }

  if( max_bpt == 0 || max_bpt_fm == 0 )
    return d->status = DISK_GEOM;

  d->density = DISK_DENS_AUTO;		/* disk_alloc use d->bpt */
  if( max_bpt_fm < 3000 ) {		/* we choose an SD disk with FM */
    d->bpt = max_bpt_fm;
    gap = GAP_MINIMAL_FM;
  } else {
    d->bpt = max_bpt;
    gap = GAP_MINIMAL_MFM;
  }
  if( disk_alloc( d ) != DISK_OK )
    return d->status;
    /* start reading the tracks */

  head_offset = h;			/* restore head start */
  for( i = 0; i < d->cylinders * d->sides; i++ ) {	/* ALT */
    buffer->index = head_offset;
    buffread( head, 7, buffer );	/* 7 = track head */
    track_offset = head[0x00] + 256 * head[0x01] +
    			 65536 * head[0x02] + 16777216 * head[0x03];
    DISK_SET_TRACK_IDX( d, i );
    d->i = 0;
    if( preindex )
      preindex_add( d, gap );
    postindex_add( d, gap );
    for( j = 0; j < head[0x06]; j++ ) {
      if( j % 35 == 0 ) {			/* if we have more than 35 sector in a track,
						   we have to seek back to the next sector
						   headers and read it ( max 35 sector header */
	buffer->index = head_offset + 7 *( j + 1 );
	buffread( head + 7, 245, buffer );	/* 7*35 := max 35 sector head */
      }
      id_add( d, head[ 0x08 + 7 * ( j % 35 ) ], head[ 0x07 + 7*( j % 35 ) ],
	      head[ 0x09 + 7*( j % 35 ) ], head[ 0x0a + 7*( j % 35 ) ], gap, 
	      ( head[ 0x0b + 7*( j % 35 ) ] & 0x3f ) ? CRC_OK : CRC_ERROR );
      sector_offset = head[ 0x0c + 7 * ( j % 35 ) ] +
		      256 * head[ 0x0d + 7 * ( j % 35 ) ];
      buffer->index = data_offset + track_offset + sector_offset;
      data_add( d, buffer, NULL, ( head[ 0x0b + 7 * ( j % 35 ) ] & 0x3f ) == 0 ? 
			       -1 : 0x80 << head[ 0x0a + 7 * ( j % 35 ) ],
		head[ 0x0b + 7 * ( j % 35 ) ] & 0x80 ? DDAM : NO_DDAM,
		gap, CRC_OK, NO_AUTOFILL, NULL );
    }
    head_offset += 7 + 7 * head[0x06];
    gap4_add( d, gap );
  }
  return d->status = DISK_OK;
}

static void
cpc_set_weak_range( disk_t *d, int idx, buffer_t *buffer, int n, int len )
{
  int i, j, first = -1, last = -1;
  libspectrum_byte *t, *w;

  t = d->track + idx;
  w = buffer->file.buffer + buffer->index;

  for( i = 0; i < len; i++, t++, w++ ) {
    for( j = 0; j < n - 1; j ++ ) {
      if( *t != w[j * len] ) {
        if( first == -1 ) first = idx + i;
        last = idx + i;
      }
    }
  }
  if( first == -1 || last == -1 ) {
    return;
  }
  for( ; first <= last; first++ ) {
    bitmap_set( d->weak, first );
  }
}

/*
#define CPC_DEBUG 1
#define CPC_DEBUG_EXIT 1
*/
#ifdef CPC_DEBUG_EXIT
extern int fuse_exiting;		/* Shall we exit now? */
#endif
#define CPC_ISSUE_NONE 0
/* 1x 8k sector with 6k or 8k data */
#define CPC_ISSUE_1 1
/* 1x 8k + Nx 512 sector */
#define CPC_ISSUE_2 2
/* (128 )256 512 1024 2048 4096 */
#define CPC_ISSUE_3 3
/* track38 512 256 512 256 512 256 ... (9x) */
#define CPC_ISSUE_5 5

static int
open_cpc( buffer_t *buffer, disk_t *d, int preindex )
{
  int i, j, seclen=0, idlen, gap, idx;
  int bpt, max_bpt = 0, trlen;
  int cpc_fix;
  unsigned char *hdrb, *tltbl=NULL;
#ifdef CPC_DEBUG
  int cpc_fix_fix = 0;
#endif

  d->sides = buff[0x31];
  d->cylinders = buff[0x30];			/* maximum number of tracks */
  GEOM_CHECK;
/* first scan for the longest track */
  if( d->type != DISK_ECPC ) {
    /* All track sizes in the standard disk image must be the same */
    trlen = buff[0x32] + 256 * buff[0x33];
  } else {
    trlen = -1;
    tltbl = &buff[0x34]; /* track size table */
  }
  idx = 256;
  for( i = 0; i < d->sides * d->cylinders; i++ ) {
    if( buff[ idx + 0x13 ] == 0 ) /* we now assume MFM */
      buff[ idx + 0x13 ] = 2;
    idx += d->type == DISK_ECPC ? tltbl[i] * 256 : trlen;
  }
  buffer->index = idx;
/* later we try to use Offset-Info block to determine gap lengths...
  if( buffavail( buffer ) >= 13 && !memcmp( buff, "Offset-Info\r\n", 13 ) )
    offinf = buff;
  else
    offinf = NULL;
*/
  buffer->index = 256;
#ifdef CPC_DEBUG
fprintf( stderr, "%ds/%dc\n", d->sides, d->cylinders );
#endif
/* first scan for the longest track */
  for( i = 0; i < d->sides*d->cylinders; i++ ) {

    /* ignore Sector Offset block */
    if( buffavail( buffer ) >= 13 && !memcmp( buff, "Offset-Info\r\n", 13 ) ) {
      buffer->index = buffer->file.length;
    }

      /* sometimes in the header there are more track than in the file */
    if( buffavail( buffer ) == 0 ) {			/* no more data */
      d->cylinders = i / d->sides + i % d->sides;	/* the real cylinder number */
      break;
    }
    if( d->type == DISK_ECPC && tltbl[i] == 0 ) continue; /* skip unformatted tracks */
    if( buffavail( buffer ) < 256 ||
	memcmp( buff, "Track-Info", 10 ) )		/* check track header */
      return d->status = DISK_OPEN;
    j = 24 + 8 * buff[ 0x15 ];				/* sector info block(s) */
    j += 0x100 - ( j % 0x100 );				/* round up to next 0x100 */
    if( buff[ 0x15 ] > 29 && buffavail( buffer ) < j ) { /* https://simonowen.com/misc/extextdsk.txt */
      return d->status = DISK_OPEN;                      /* Sector count space check */
    }
/*
    gap = (unsigned char)buff[0x16] == 0xff ? GAP_MINIMAL_FM :
    					    GAP_MINIMAL_MFM;
*/
    if( buff[0x1b] >= 6 ) {
        gap = buff[0x13] == 2 ? GAP_8k765_MFM : GAP_4k765_FM;
    } else {
        gap = buff[0x13] == 2 ? GAP_IBM34 : GAP_IBM3740;
    }
    cpc_fix = 0;

    if( i >= d->sides*d->cylinders || 
	i != buff[0x10] * d->sides + buff[0x11] )	/* problem with track idx. */
      return d->status = DISK_OPEN;

    bpt = postindex_len( d, gap ) +
	    ( preindex ? preindex_len( d, gap ) : 0 ) +
		( buff[ 0x13 ] == 2 ? 6 : 3 );	/* gap4 */
#ifdef CPC_DEBUG
fprintf( stderr, "%d:: %d/%d.%d [%d] %d", i, buff[ 0x15 ], gap, buff[0x13], buffer->index, bpt );
#endif
    for( j = 0; j < buff[ 0x15 ]; j++ ) {			/* each sector */
      seclen = d->type == DISK_ECPC ? buff[ 0x1e + 8 * j ] +
				      256 * buff[ 0x1f + 8 * j ]
				    : 0x80 << buff[ 0x1b + 8 * j ];
      idlen = 0x80 << ( buff[ 0x1b + 8 * j ] > 7 ? 8 : buff[ 0x1b + 8 * j ] );
					/* sector length from ID if N >= 8 -> N = 8 */
      if( d->type != DISK_ECPC && (
            ( seclen == 6144 && idlen != 8192 ) ||
            ( idlen > 4096 && seclen != 0 ) ||
            seclen != idlen
          ) )
        return d->status = DISK_OPEN; /* relax the sector length rule */

#ifdef CPC_DEBUG
fprintf( stderr, " (%d/%d) %d", seclen, idlen, bpt );
#endif
      bpt += calc_sectorlen( buff[ 0x13 ] == 2 ? 1 : 0, seclen > idlen ? idlen : seclen, gap );
      if( d->flag & DISK_FLAG_PLUS3_CPC ) {
	if( j == 0 && buff[ 0x1b + 8 * j ] == 6 ) /* sec1 = 8k */
	  cpc_fix = CPC_ISSUE_1;
	else if( j == 0 && (
		 ( buff[ 0x18 ] == 0 && buff[ 0x19 ] == 0 &&
		   buff[ 0x1a ] == 0 && buff[ 0x1b ] == 0 ) ||
		 ( buff[ 0x18 ] == 1 && buff[ 0x19 ] == 1 &&
                   buff[ 0x1a ] == 1 && buff[ 0x1b ] == 1 ) ) )
          cpc_fix = CPC_ISSUE_3;
	else if( j == 1 && cpc_fix == CPC_ISSUE_1 && /* sec1 = 8k && sec2 = 512 sec3 = 512 ... sec10 = 512 */
                 buff[ 0x1b + 8 * j ] == 2 )
	  cpc_fix = CPC_ISSUE_2;
	else if( i == 38 && j == 0 && buff[ 0x1b + 8 * j ] == 2 )
	  cpc_fix = CPC_ISSUE_5;
	else if( j > 1 && cpc_fix == CPC_ISSUE_2 && buff[ 0x1b + 8 * j ] != 2 )
	  cpc_fix = CPC_ISSUE_NONE;
	else if( j > 0 && cpc_fix == CPC_ISSUE_3 &&
		 ( buff[ 0x18 + 8 * j ] != j + buff[ 0x18 ] ||
		   buff[ 0x19 + 8 * j ] != j + buff[ 0x18 ] ||
		   buff[ 0x1a + 8 * j ] != j + buff[ 0x18 ] ||
		   buff[ 0x1b + 8 * j ] != j + buff[ 0x18 ] ) )
	  cpc_fix = CPC_ISSUE_NONE;
	else if( j > 10 && cpc_fix == CPC_ISSUE_2 )
	  cpc_fix = CPC_ISSUE_NONE;
	else if( i == 38 && j > 0 && cpc_fix == CPC_ISSUE_5 &&
		 buff[ 0x1b + 8 * j ] != 2 - ( j & 1 ) )
	  cpc_fix = CPC_ISSUE_NONE;
      }
    }
    buff[0x00] = cpc_fix;		/* we use "Track-Info..." T to store "FIX" */
    /* bpt := bpt(0) + NxGAP3 */
    if( bpt > 6250 / ( buff[0x13] == 2 ? 1 : 2 ) ) { /* DD first try to align gap3 */
      if( cpc_fix == CPC_ISSUE_NONE && 
          bpt - buff[ 0x15 ] * gaps[gap].len[3] <= 
            6250 / ( buff[0x13] == 2 ? 1 : 2 ) - buff[ 0x15 ] * 8 ) {
        buff[0x16] = ( 6250 / ( buff[0x13] == 2 ? 1 : 2 ) + buff[ 0x15 ] * gaps[gap].len[3] - bpt ) / buff[ 0x15 ];
        buff[0x00] |= 0x80;		/* we use custom gap3 length */
      } else if( cpc_fix == CPC_ISSUE_NONE ) {
        fprintf( stderr, "Warning: unknown sector configuration, cannot build track. BPT too high: %d", bpt );
      }
    }
    if( buff[0x00] == CPC_ISSUE_1 && seclen > 6144 ) bpt = 6500;/* Type 1 variant DD+ (e.g. Coin Op Hits) */
    else if( buff[0x00] != CPC_ISSUE_NONE ) bpt = 6250;/* we assume a standard DD track */
#ifdef CPC_DEBUG
fprintf( stderr, "----spec:%d gap3%s:%d bpt:%d/%d\n", cpc_fix, buff[0x00] & 0x80 ? "*" : "", buff[0x16], bpt, max_bpt );
if( cpc_fix ) cpc_fix_fix = cpc_fix;
#endif
/* extended DSK image uses track size table */
    buffer->index += ( d->type == DISK_ECPC ? 256 * tltbl[i] : trlen );
    if( bpt > max_bpt )
      max_bpt = bpt;
  }
#ifdef CPC_DEBUG
if( cpc_fix_fix ) {
  fprintf( stderr, "@@@spec:%d\n", cpc_fix_fix );
  cpc_fix_fix = 0;
}
#endif
/****
  open unformatted disks e.g.: edd_the_duck_master_b.dsk
  this disk looks has 0 track... only 256 byte
*/
  if( max_bpt == 0 && buffer->index == 0x100 ) {
    max_bpt = 6250;
    if( d->cylinders == 0 ) d->cylinders = 1;
  }
  if( max_bpt == 0 )
    return d->status = DISK_GEOM;

  d->density = DISK_DENS_AUTO;			/* disk_alloc use d->bpt */
  d->bpt = max_bpt;
  if( disk_alloc( d ) != DISK_OK )
    return d->status;

  DISK_SET_TRACK_IDX( d, 0 );
  buffer->index = 256;				/* rewind to first track */
  for( i = 0; i < d->sides*d->cylinders; i++ ) {
    int idx_save;

    if( d->type == DISK_ECPC && tltbl[i] == 0 ) continue; /* skip unformatted tracks */
    hdrb = buff;
    idx_save = buffer->index;
    buffer->index += 256;		/* skip to data */
/*
    gap = (unsigned char)hdrb[0x16] == 0xff ? GAP_MINIMAL_FM : GAP_MINIMAL_MFM;
*/
    if( hdrb[0x1b] >= 6 ) {
        gap = hdrb[0x13] == 2 ? GAP_8k765_MFM : GAP_4k765_FM;
    } else {
        gap = hdrb[0x13] == 2 ? GAP_IBM34 : GAP_IBM3740;
    }

    if( hdrb[0x10] * d->sides + hdrb[0x11] > i )		/* adjust track No. */
      i = hdrb[0x10] * d->sides + hdrb[0x11];
    cpc_fix = hdrb[ 0x00 ];
    if( cpc_fix & 0x80 ) { /* we use custom gap3 value */
      int new_gap;

      new_gap = hdrb[0x13] == 2 ? GAP_CUSTOM_MFM : GAP_CUSTOM_FM;
      for( j = 0; j < 3; j++ )
        gaps[ new_gap ].len[ j ] = gaps[ gap ].len[ j ];
      gaps[ new_gap ].len[ 3 ] = hdrb[0x16];
      gap = new_gap;
      cpc_fix &= 0x7f;
    }

    DISK_SET_TRACK_IDX( d, i );
    if( d->c_bpt == 6500 ) { /* DD+ */
      if( cpc_fix != CPC_ISSUE_1 ) { /* only + tracks use 6500 bpt */
        d->track[-3] = 6250 % 256;
        d->track[-2] = 6250 / 256;
        d->c_bpt = 6250;
        DISK_SET_TRACK_IDX( d, i ); /* recalculate */
      }
    }
    d->i = 0;
    if( preindex)
      preindex_add( d, gap );
    postindex_add( d, gap );

    for( j = 0; j < hdrb[0x15]; j++ ) {			/* each sector */
      seclen = d->type == DISK_ECPC ? hdrb[ 0x1e + 8 * j ] +	/* data length in sector */
				      256 * hdrb[ 0x1f + 8 * j ]
				    : 0x80 << hdrb[ 0x1b + 8 * j ];
      idlen = 0x80 << ( hdrb[ 0x1b + 8 * j ] > 7 ? 8 : hdrb[ 0x1b + 8 * j ] );
					/* sector length from ID if N >= 8 -> N = 8
					https://simonowen.com/misc/extextdsk.txt */

      if( hdrb[ 0x00 ] == CPC_ISSUE_2 && j == 0 ) {	/* repositionate the dummy sector (?)  */
        d->i = 8;
      }
      id_add( d, hdrb[ 0x19 + 8 * j ], hdrb[ 0x18 + 8 * j ],
		 hdrb[ 0x1a + 8 * j ], hdrb[ 0x1b + 8 * j ], gap,
                 hdrb[ 0x1c + 8 * j ] & 0x20 && !( hdrb[ 0x1d + 8 * j ] & 0x20 ) ? 
                 CRC_ERROR : CRC_OK );
      if( cpc_fix == CPC_ISSUE_1 && j == 0 ) {	/* 6144 or 8192 */
        data_add( d, buffer, NULL, seclen > 6144 ? 6384 : seclen,
		hdrb[ 0x1d + 8 * j ] & 0x40 ? DDAM : NO_DDAM, gap, 
		hdrb[ 0x1c + 8 * j ] & 0x20 && hdrb[ 0x1d + 8 * j ] & 0x20 ?
		CRC_ERROR : CRC_OK, 0x00, NULL );
	if( seclen > 6144 )
          buffer->index += seclen - 6384;
      } else if( cpc_fix == CPC_ISSUE_2 && j == 0 ) {	/* 6144, 10x512 */
        datamark_add( d, hdrb[ 0x1d + 8 * j ] & 0x40 ? DDAM : NO_DDAM, gap );
        gap_add( d, 2, gap );
        buffer->index += seclen;
      } else if( cpc_fix == CPC_ISSUE_3 ) {	/* (128,) 256, 512, ... 4096k */
        data_add( d, buffer, NULL, 128,
		hdrb[ 0x1d + 8 * j ] & 0x40 ? DDAM : NO_DDAM, gap, 
		hdrb[ 0x1c + 8 * j ] & 0x20 && hdrb[ 0x1d + 8 * j ] & 0x20 ?
		CRC_ERROR : CRC_OK, 0x00, NULL );
        buffer->index += seclen - 128;
      } else if( cpc_fix == CPC_ISSUE_5 ) {	/* 9x512 */
      /* 512 256 512 256 512 256 512 256 512 */
        if( idlen == 256 ) {
          data_add( d, NULL, buff, 512,
		hdrb[ 0x1d + 8 * j ] & 0x40 ? DDAM : NO_DDAM, gap,
		hdrb[ 0x1c + 8 * j ] & 0x20 && hdrb[ 0x1d + 8 * j ] & 0x20 ?
		CRC_ERROR : CRC_OK, 0x00, NULL );
	  buffer->index += idlen;
        } else {
          data_add( d, buffer, NULL, idlen,
		hdrb[ 0x1d + 8 * j ] & 0x40 ? DDAM : NO_DDAM, gap,
		hdrb[ 0x1c + 8 * j ] & 0x20 && hdrb[ 0x1d + 8 * j ] & 0x20 ?
		CRC_ERROR : CRC_OK, 0x00, NULL );
	}
      } else {
        if( data_add( d, buffer, NULL, seclen > idlen ? idlen : seclen,
		hdrb[ 0x1d + 8 * j ] & 0x40 ? DDAM : NO_DDAM, gap,
		hdrb[ 0x1c + 8 * j ] & 0x20 && hdrb[ 0x1d + 8 * j ] & 0x20 ?
		CRC_ERROR : CRC_OK, 0x00, &idx ) )
	  buffer->index += seclen; /* if cannot add data, we have to advance buffer->index! */
        if( seclen > idlen && seclen % idlen ) { /* data in gap ??? */
          int k, save_index;

          save_index = d->i;
          /* idx -> first data byte */
          d->i = idx + idlen; /* end of the sector data (CRC) */
          for( k = seclen - idlen; k > 0; k-- ) {
            if( d->i == d->c_bpt )
              d->i = 0; /* wrap around */
            d->track[ d->i ] = *buff;
            d->i++;
            buffer->index++;
          }
          d->i = save_index;			/* restore pointer */
        } else if( seclen > idlen ) {		/* weak sector with multiple copy  */
          cpc_set_weak_range( d, idx, buffer, seclen / idlen, idlen );
          buffer->index += ( seclen / idlen - 1 ) * idlen;
					/* ( ( N * len ) / len - 1 ) * len */
        }
      }
    }
    gap4_add( d, gap );
/* extended DSK image uses track size table */
    buffer->index = idx_save + ( d->type == DISK_ECPC ? 256 * tltbl[i] : trlen );
  }
  return d->status = DISK_OK;
}

static int
open_scl( buffer_t *buffer, disk_t *d )
{
  int i, j, s, sectors, seclen;
  int scl_deleted, scl_files, scl_i;
  disk_position_context_t context;

  d->sides = 2;
  d->cylinders = 80;
  d->density = DISK_DD;
  if( disk_alloc( d ) != DISK_OK )
    return d->status;

/*
 TR-DOS:
- Root directory is 8 sectors long starting from track 0, sector 1
- Max root entries are 128
- Root entry dimension is 16 bytes (16*128 = 2048 => 8 sector
- Logical sector(start from 0) 8th (9th physical) holds disc info
- Logical sectors from 9 to 15 are unused
- Files are *NOT* fragmented, and start on track 1, sector 1...

*/

  if( ( scl_files = buff[8] ) > 128 || scl_files < 1 ) 	/* number of files */
    return d->status = DISK_GEOM;	/* too many file */
  buffer->index = 9;		/* read SCL entries */

  DISK_SET_TRACK_IDX( d, 0 );
  d->i = 0;
  postindex_add( d, GAP_TRDOS );
  scl_i = d->i;			/* the position of first sector */
  s = 1;			/* start sector number */
  scl_deleted = 0;		/* deleted files */
  sectors = 0;			/* allocated sectors */
				/* we use 'head[]' to build
				   TR-DOS directory */
  j = 0;			/* index for head[] */
  memset( head, 0, 256 );
  seclen = calc_sectorlen( 1, 256, GAP_TRDOS );	/* one sector raw length */
  for( i = 0; i < scl_files; i++ ) {	/* read all entry and build TR-DOS dir */
    if( buffread( head + j, 14, buffer ) != 1 )
      return d->status = DISK_OPEN;
    head[ j + 14 ] = sectors % 16; /* ( sectors + 16 ) % 16 := sectors % 16
    							 starting sector */
    head[ j + 15 ] = sectors / 16 + 1; /* ( sectors + 16 ) / 16 := sectors / 16 + 1
    							 starting track */
    sectors += head[ j + 13 ];
    if( d->data[j] == 0x01 )		/* deleted file */
      scl_deleted++;
    if( sectors > 16 * 159 ) 	/* too many sectors needed */
      return d->status = DISK_MEM;	/* or DISK_GEOM??? */

    j += 16;
    if( j == 256 ) {			/* one sector ready */
      d->i = scl_i + ( ( s - 1 ) % 8 * 2 + ( s - 1 ) / 8 ) * seclen;	/* 1 9 2 10 3 ... */
      id_add( d, 0, 0, s, SECLEN_256, GAP_TRDOS, CRC_OK );
      data_add( d, NULL, head, 256, NO_DDAM, GAP_TRDOS, CRC_OK, NO_AUTOFILL, NULL );
      memset( head, 0, 256 );
      s++;
      j = 0;
    }
  }

  if( j != 0 ) {	/* we have to add this sector  */
    d->i = scl_i + ( ( s - 1 ) % 8 * 2 + ( s - 1 ) / 8 ) * seclen;	/* 1 9 2 10 3 ... */
    id_add( d, 0, 0, s, SECLEN_256, GAP_TRDOS, CRC_OK );
    data_add( d, NULL, head, 256, NO_DDAM, GAP_TRDOS, CRC_OK, NO_AUTOFILL, NULL );
    s++;
  }
			/* and add empty sectors up to No. 16 */
  memset( head, 0, 256 );
  for( ; s <= 16; s++ ) {			/* finish the first track */
    d->i = scl_i + ( ( s - 1 ) % 8 * 2 + ( s - 1 ) / 8 ) * seclen;	/* 1 9 2 10 3 ... */
    id_add( d, 0, 0, s, SECLEN_256, GAP_TRDOS, CRC_OK );
    if( s == 9 ) {			/* TR-DOS descriptor */
      head[225] = sectors % 16;	/* first free sector */
      head[226] = sectors / 16 + 1;	/* first free track */
      head[227] = 0x16;		/* 80 track 2 side disk */
      head[228] = scl_files;		/* number of files */
      head[229] = ( 2544 - sectors ) % 256;	/* free sectors */
      head[230] = ( 2544 - sectors ) / 256;
      head[231] = 0x10;		/* TR-DOS ID byte */
      memset( head + 234, 32, 9 );
      head[244] = scl_deleted;	/* number of deleted files */
      memcpy( head + 245, "FUSE-SCL", 8 );
    }
    data_add( d, NULL, head, 256, NO_DDAM, GAP_TRDOS, CRC_OK, NO_AUTOFILL, NULL );
    if( s == 9 )
      memset( head, 0, 256 );		/* clear sector data... */
  }
  gap4_add( d, GAP_TRDOS );

  /* now we continue with the data */
  for( i = 1; i < d->sides * d->cylinders; i++ ) {
    if( trackgen( d, buffer, i % 2, i / 2, 1, 16, 256,
                  NO_PREINDEX, GAP_TRDOS, INTERLEAVE_2, 0x00 ) )
      return d->status = DISK_OPEN;
  }

  if( settings_current.auto_load ) {
    position_context_save( d, &context );
    trdos_insert_boot_loader( d );
    position_context_restore( d, &context );
  }

  return d->status = DISK_OK;
}

static int
open_td0( buffer_t *buffer, disk_t *d, int preindex )
{
  int i, j, s, sectors, seclen, bpt, gap, mfm, mfm_old;
  int data_offset, track_offset, sector_offset;
  unsigned char *uncomp_buff, *hdrb;

  if( buff[0] == 't' )		/* signature "td" -> advanced compression */
    return d->status = DISK_IMPL;	/* not implemented */

  uncomp_buff = NULL;			/* we may use this buffer */
  mfm_old = buff[5] & 0x80 ? 0 : 1;	/* td0notes say: may older teledisk
					   indicate the SD on high bit of
					   data rate */
  d->sides = buff[9];			/* 1 or 2 */
  if( d->sides < 1 || d->sides > 2 )
    return d->status = DISK_GEOM;
					/* skip comment block if any */
  data_offset = track_offset = 12 + ( buff[7] & 0x80 ? 
					10 + buff[14] + 256 * buff[15] : 0 );

  /* determine the greatest track length */
  d->bpt = 0;
  d->cylinders = 0;
  seclen = 0;
  while( 1 ) {
    buffer->index = track_offset;
    if( buffavail( buffer ) < 1 )
      return d->status = DISK_OPEN;
    if( ( sectors = buff[0] ) == 255 ) /* sector number 255 => end of tracks */
      break;
    if( buffavail( buffer ) < 4 )	/* check track header is avail. */
      return d->status = DISK_OPEN;
    if( buff[1] + 1 > d->cylinders )	/* find the biggest cylinder number */
      d->cylinders = buff[1] + 1;
    sector_offset = track_offset + 4;
    mfm = buff[2] & 0x80 ? 0 : 1;	/* 0x80 == 1 => SD track */
    bpt = postindex_len( d, mfm_old || mfm ? GAP_MINIMAL_FM : GAP_MINIMAL_MFM ) +
	  ( preindex ? 
	    preindex_len( d, mfm_old || mfm ? GAP_MINIMAL_FM : GAP_MINIMAL_MFM ) :
	    0 ) +
	  mfm_old || mfm ? 6 : 3;
    for( s = 0; s < sectors; s++ ) {
      buffer->index = sector_offset;
      if( buffavail( buffer ) < 6 )		/* check sector header is avail. */
	return d->status = DISK_OPEN;
      if( !( buff[4] & 0x30 ) ) {		/* only if we have data */
	if( buffavail( buffer ) < 9  )		/* check data header is avail. */
	  return d->status = DISK_OPEN;

	bpt += calc_sectorlen( mfm_old || mfm, 0x80 << buff[3], 
				    mfm_old || mfm ? GAP_MINIMAL_FM : GAP_MINIMAL_MFM );
	if( buff[3] > seclen )
	  seclen = buff[3];			/* biggest sector */
	sector_offset += buff[6] + 256 * buff[7] - 1;
      }
      sector_offset += 9;
    }
    if( bpt > d->bpt )
      d->bpt = bpt;
    track_offset = sector_offset;
  }

  if( d->bpt == 0 )
    return d->status = DISK_GEOM;

  d->density = DISK_DENS_AUTO;
  if( disk_alloc( d ) != DISK_OK )
    return d->status;

  DISK_SET_TRACK_IDX( d, 0 );

  buffer->index = data_offset;		/* first track header */
  while( 1 ) {
    if( ( sectors = buff[0] ) == 255 ) /* sector number 255 => end of tracks */
      break;

    DISK_SET_TRACK( d, ( buff[2] & 0x01 ), buff[1] );
    d->i = 0;
    			/* later teledisk -> if buff[2] & 0x80 -> FM track */
    gap = mfm_old || buff[2] & 0x80 ? GAP_MINIMAL_FM : GAP_MINIMAL_MFM;
    postindex_add( d, gap );

    buffer->index += 4;		/* sector header*/
    for( s = 0; s < sectors; s++ ) {
      hdrb = buff;
      buffer->index += 9;		/* skip to data */
      if( !( hdrb[4] & 0x40 ) )		/* if we have id we add */
	id_add( d, hdrb[1], hdrb[0], hdrb[2], hdrb[3], gap,
				   hdrb[4] & 0x02 ? CRC_ERROR : CRC_OK );
      if( hdrb[4] & 0x40 ) {		/* if we have _no_ id we drop data... */
	buffer->index += hdrb[6] + 256 * hdrb[7] - 1;
	continue;		/* next sector */
      }
      if( !( hdrb[4] & 0x30 ) ) {		/* only if we have data */
        seclen = 0x80 << hdrb[3];

        switch( hdrb[8] ) {
	case 0:				/* raw sector data */
	  if( hdrb[6] + 256 * hdrb[7] - 1 != seclen ) {
	    if( uncomp_buff )
	      libspectrum_free( uncomp_buff );
	    return d->status = DISK_OPEN;
	  }
	  if( data_add( d, buffer, NULL, hdrb[6] + 256 * hdrb[7] - 1,
			hdrb[4] & 0x04 ? DDAM : NO_DDAM, gap, CRC_OK, NO_AUTOFILL, NULL ) ) {
	    if( uncomp_buff )
	      libspectrum_free( uncomp_buff );
	    return d->status = DISK_OPEN;
	  }
	  break;
	case 1:				/* Repeated 2-byte pattern */
	  if( uncomp_buff == NULL && alloc_uncompress_buffer( &uncomp_buff, 8192 ) )
	    return d->status = DISK_MEM;
	  for( i = 0; i < seclen; ) {			/* fill buffer */
	    if( buffavail( buffer ) < 13 ) { 		/* check block header is avail. */
	      libspectrum_free( uncomp_buff );
	      return d->status = DISK_OPEN;
	    }
	    if( i + 2 * ( hdrb[9] + 256*hdrb[10] ) > seclen ) {
						  /* too many data bytes */
	      libspectrum_free( uncomp_buff );
	      return d->status = DISK_OPEN;
	    }
	    /* ab ab ab ab ab ab ab ab ab ab ab ... */
	    for( j = 1; j < hdrb[9] + 256 * hdrb[10]; j++ )
	      memcpy( uncomp_buff + i + j * 2, &hdrb[11], 2 );
	    i += 2 * ( hdrb[9] + 256 * hdrb[10] );
	  }
	  if( data_add( d, NULL, uncomp_buff, hdrb[6] + 256 * hdrb[7] - 1,
		      hdrb[4] & 0x04 ? DDAM : NO_DDAM, gap, CRC_OK, NO_AUTOFILL, NULL ) ) {
	    libspectrum_free( uncomp_buff );
	    return d->status = DISK_OPEN;
	  }
	  break;
	case 2:				/* Run Length Encoded data */
	  if( uncomp_buff == NULL && alloc_uncompress_buffer( &uncomp_buff, 8192 ) )
	    return d->status = DISK_MEM;
	  for( i = 0; i < seclen; ) {			/* fill buffer */
	    if( buffavail( buffer ) < 11 ) {		/* check block header is avail */
	      libspectrum_free( uncomp_buff );
	      return d->status = DISK_OPEN;
	    }
	    if( hdrb[9] == 0 ) {		/* raw bytes */
	      if( i + hdrb[10] > seclen ||	/* too many data bytes */
		      buffread( uncomp_buff + i, hdrb[10], buffer ) != 1 ) {
	        libspectrum_free( uncomp_buff );
	        return d->status = DISK_OPEN;
	      }
	      i += hdrb[10];
	    } else {				/* repeated samples */
	      if( i + 2 * hdrb[9] * hdrb[10] > seclen || /* too many data bytes */
		      buffread( uncomp_buff + i, 2 * hdrb[9], buffer ) != 1 ) {
	        libspectrum_free( uncomp_buff );
	        return d->status = DISK_OPEN;
	      }
	      /*
		 abcdefgh abcdefg abcdefg abcdefg ...
		 \--v---/ 
		  2*hdrb[9]
		 |        |       |       |           |
		 +- 0     +- 1    +- 2    +- 3    ... +- hdrb[10]-1
	      */
	      for( j = 1; j < hdrb[10]; j++ ) /* repeat 'n' times */
	        memcpy( uncomp_buff + i + j * 2 * hdrb[9], uncomp_buff + i, 2 * hdrb[9] );
	      i += 2 * hdrb[9] * hdrb[10];
	    }
	  }
	  if( data_add( d, NULL, uncomp_buff, hdrb[6] + 256 * hdrb[7] - 1,
	      hdrb[4] & 0x04 ? DDAM : NO_DDAM, gap, CRC_OK, NO_AUTOFILL, NULL ) ) {
	    libspectrum_free( uncomp_buff );
	    return d->status = DISK_OPEN;
	  }
	  break;
	default:
	  if( uncomp_buff )
	    libspectrum_free( uncomp_buff );
	  return d->status = DISK_OPEN;
	  break;
	}
      }
    }
    gap4_add( d, gap );
  }

  if( uncomp_buff )
    libspectrum_free( uncomp_buff );
  return d->status = DISK_OK;
}

/* update tracks TLEN */
void
disk_update_tlens( disk_t *d )
{
  int i;

  for( i = 0; i < d->sides * d->cylinders; i++ ) {	/* check tracks */
    DISK_SET_TRACK_IDX( d, i );
    if( d->track[-3] + 256 * d->track[-2] == 0 ) {
      d->track[-3] = d->bpt & 0xff;
      d->track[-2] = ( d->bpt >> 8 ) & 0xff;
    }
  }
}

/* open a disk image file, read and convert to our format
 * if preindex != 0 we generate preindex gap if needed
 */
static int
disk_open2( disk_t *d, const char *filename, int preindex )
{
  buffer_t buffer;
  libspectrum_id_t type;
  int error;

#ifdef GEKKO		/* Wii doesn't have access() */
  d->wrprot = 0;
#else			/* #ifdef GEKKO */
  if( access( filename, W_OK ) == -1 )		/* file read only */
    d->wrprot = 1;
  else
    d->wrprot = 0;
#endif			/* #ifdef GEKKO */

  if( utils_read_file( filename, &buffer.file ) )
    return d->status = DISK_OPEN;

  buffer.index = 0;

  error = libspectrum_identify_file_raw( &type, filename,
					 buffer.file.buffer, buffer.file.length );
  if( error ) return d->status = DISK_OPEN;
  d->type = DISK_TYPE_NONE;
#ifdef CPC_DEBUG
fprintf( stderr, "\n::::%s:::: ", filename );
#endif
  switch ( type ) {
  case LIBSPECTRUM_ID_DISK_UDI:
    d->type = DISK_UDI;
    open_udi( &buffer, d );
    break;
  case LIBSPECTRUM_ID_DISK_OPD:
    d->type = DISK_OPD;
  case LIBSPECTRUM_ID_DISK_MGT:
    if( d->type == DISK_TYPE_NONE) d->type = DISK_MGT;
  case LIBSPECTRUM_ID_DISK_IMG:
    if( d->type == DISK_TYPE_NONE) d->type = DISK_IMG;
    open_img_mgt_opd( &buffer, d );
    break;
  case LIBSPECTRUM_ID_DISK_SAD:
    d->type = DISK_SAD;
    open_sad( &buffer, d, preindex );
    break;
  case LIBSPECTRUM_ID_DISK_TRD:
    d->type = DISK_TRD;
    open_trd( &buffer, d );
    break;
  case LIBSPECTRUM_ID_DISK_FDI:
    d->type = DISK_FDI;
    open_fdi( &buffer, d, preindex );
    break;
  case LIBSPECTRUM_ID_DISK_CPC:
    d->type = DISK_CPC;
  case LIBSPECTRUM_ID_DISK_ECPC:
    if( d->type == DISK_TYPE_NONE) d->type = DISK_ECPC;
    open_cpc( &buffer, d, preindex );
    break;
  case LIBSPECTRUM_ID_DISK_SCL:
    d->type = DISK_SCL;
    open_scl( &buffer, d );
    break;
  case LIBSPECTRUM_ID_DISK_TD0:
    d->type = DISK_TD0;
    open_td0( &buffer, d, preindex );
    break;
  case LIBSPECTRUM_ID_DISK_D80:
    d->type = DISK_D80;
    open_d40_d80( &buffer, d );
    break;
  default:
    utils_close_file( &buffer.file );
    return d->status = DISK_OPEN;
  }
  if( d->status != DISK_OK ) {
    if( d->data != NULL )
      libspectrum_free( d->data );
    utils_close_file( &buffer.file );
#ifdef CPC_DEBUG
fprintf( stderr, "\n!!!!error opening: %s!!!!\n", filename );
#ifdef CPC_DEBUG_EXIT
fuse_exiting = 1;
#endif
#endif
    return d->status;
  }
  utils_close_file( &buffer.file );
  d->dirty = 0;
  update_tracks_mode( d );
  d->filename = utils_safe_strdup( filename );
#ifdef CPC_DEBUG_EXIT
fuse_exiting = 1;
#endif
  return d->status = DISK_OK;
}

/*--------------------- other fuctions -----------------------*/

/* create a two sided disk (d) from two one sided (d1 and d2) */
int
disk_merge_sides( disk_t *d, disk_t *d1, disk_t *d2, int autofill )
{
  int i;

  if( d1->sides != 1 || d2->sides != 1 ||
      ( autofill < 0 && d1->cylinders != d2->cylinders ) )
    return DISK_GEOM;

  d->wrprot = 0;
  d->dirty = 0;
  d->sides = 2;
  d->type = d1->type;
  d->cylinders = d2->cylinders > d1->cylinders ? d2->cylinders : d1->cylinders;
  d->bpt = d2->bpt >= d1->bpt ? d2->bpt : d1->bpt;
  d->density = DISK_DENS_AUTO;

  if( disk_alloc( d ) != DISK_OK )
    return d->status;

  d->track = d->data;
  d1->track = d1->data;
  d2->track = d2->data;
  for( i = 0; i < d->cylinders; i++ ) {
    if( i < d1->cylinders )
      memcpy( d->track, d1->track, d1->tlen );
    else {
      d->track[0] = d1->bpt & 0xff;
      d->track[1] = ( d1->bpt >> 8 ) & 0xff;
      d->track[2] = 0x00;
      memset( d->track + 3, autofill & 0xff, d1->bpt );		/* fill data */
      memset( d->track + 3 + d1->bpt, 0x00, 3 * DISK_CLEN( d1->bpt ) );		/* no clock and other marks */
    }
    d->track += d->tlen;
    d1->track += d1->tlen;
    if( i < d2->cylinders )
      memcpy( d->track, d2->track, d2->tlen );
    else {
      d->track[0] = d2->bpt & 0xff;
      d->track[1] = ( d2->bpt >> 8 ) & 0xff;
      d->track[2] = 0x00;
      memset( d->track + 3, autofill & 0xff, d2->bpt );		/* fill data */
      memset( d->track + 3 + d2->bpt, 0x00, 3 * DISK_CLEN( d2->bpt ) );		/* no clock and other marks */
    }
    d->track += d->tlen;
    d2->track += d2->tlen;
  }
  disk_close( d1 );
  disk_close( d2 );
  return d->status = DISK_OK;
}

int
disk_open( disk_t *d, const char *filename, int preindex, int merge_disks )
{
  char *filename2;
  char c = ' ';
  int l, g = 0, pos = 0;
  disk_t d1, d2;

  d->filename = NULL;
  if( filename == NULL || *filename == '\0' )
    return d->status = DISK_OPEN;

  l = strlen( filename );

  if( !merge_disks || l < 7 )	/* if we do not want to open two separated disk image as one double sided disk */
    return disk_open2( d, filename, preindex );

  filename2 = (char *)filename + ( l - 1 );
  while( l ) {				/* [Ss]ide[ _][abAB12][ _.]*[ _.] */
    if( g == 0 && ( *filename2 == '.' || *filename2 == '_' ||
		    *filename2 == ' ' ) ) {
      g++;
    } else if( g == 1 && ( *filename2 == '.' || *filename2 == '_' ||
			   *filename2 == ' ' ) ) {
      ;
    } else if( g == 1 && ( *filename2 == '1' || *filename2 == 'a' ||
			   *filename2 == 'A' ) ) {
      g++;
      pos = filename2 - filename;
      c = *filename2 + 1;		/* 1->2, a->b, A->B */
    } else if( g == 1 && ( *filename2 == '2' || *filename2 == 'b' ||
			   *filename2 == 'B' ) ) {
      g++;
      pos = filename2 - filename;
      c = *filename2 - 1;		/* 2->1, b->a, B->A */
    } else if( g == 2 && ( *filename2 < '0' ||
                           ( *filename2 > '9' && *filename2 < 'A') ||
                           ( *filename2 > 'Z' && *filename2 < 'a') ||
                           *filename2 > 'x' ) ) {
      g++;
    } else if( g == 3 && l >= 5 && ( !memcmp( filename2 - 3, "Side", 4 ) ||
				     !memcmp( filename2 - 3, "side", 4 ) ) ) {
      g++;
      break;
    } else {
      g = 0;
    }
    l--;
    filename2--;
  }
  if( g != 4 )
    return d->status = disk_open2( d, filename, preindex );
  d1.data = NULL; d1.flag = d->flag;
  d2.data = NULL; d2.flag = d->flag;
  filename2 = utils_safe_strdup( filename );
  *(filename2 + pos) = c;

  if( settings_current.disk_ask_merge &&
      !ui_query( "Try to merge 'B' side of this disk?" ) ) {
    libspectrum_free( filename2 );
    return d->status = disk_open2( d, filename, preindex );
  }

  if( disk_open2( &d2, filename2, preindex ) ) {
    return d->status = disk_open2( d, filename, preindex );
  }

  if( disk_open2( &d1, filename, preindex ) )
    return d->status = d1.status;

  if( disk_merge_sides( d, &d1, &d2, 0x00 ) ) {
    disk_close( &d2 );
    *d = d1;
  }
/*  fprintf( stderr, "`%s' and `%s' merged\n", filename, filename2 ); */
  libspectrum_free( filename2 );
  return d->status;
}

/*--------------------- start of write section ----------------*/

static int
write_udi( FILE *file, disk_t *d )
{
  int i, j, error;
  size_t len;
  libspectrum_dword crc;

  udi_pack_tracks( d );
#ifdef LIBSPECTRUM_SUPPORTS_ZLIB_COMPRESSION
  udi_compress_tracks( d );
#endif			/* #ifdef LIBSPECTRUM_SUPPORTS_ZLIB_COMPRESSION */

  crc = ~( libspectrum_dword ) 0;
  len = 16;

  for( i = 0; i < d->sides * d->cylinders; i++ ) {	/* check tracks */
    DISK_SET_TRACK_IDX( d, i );
    if( d->track[-1] == 0xf0 )
      len += 7 + d->track[-3] + 256 * d->track[-2];
    else
      len += 3 + UDI_TLEN( d->track[-1], d->track[-3] + 256 * d->track[-2] );
  }
  head[0] = 'U';
  head[1] = 'D';
  head[2] = 'I';
  head[3] = '!';
  head[4] = len & 0xff;
  head[5] = ( len >> 8 ) & 0xff;
  head[6] = ( len >> 16 ) & 0xff;
  head[7] = ( len >> 24 ) & 0xff;
  head[8] = 0x00;
  head[9] = d->cylinders - 1;
  head[10] = d->sides - 1;
  head[11] = head[12] = head[13] = head[14] = head[15] = 0;
  if( fwrite( head, 16, 1, file ) != 1 )
    return d->status = DISK_WRPART;
  for( j = 0; j < 16; j++ )
    crc = crc_udi( crc, head[j] );
  for( i = 0; i < d->sides * d->cylinders; i++ ) {	/* write tracks */
    DISK_SET_TRACK_IDX( d, i );
    head[0] = d->track[-1];		/* track type */
    head[1] = d->track[-3];		/* track len  */
    head[2] = d->track[-2];		/* track len2 */
    if( fwrite( head, 3, 1, file ) != 1 )
      return d->status = DISK_WRPART;

    for( j = 0; j < 3; j++ )
      crc = crc_udi( crc, head[j] );

    if( d->track[-1] == 0xf0 )
      len = 4 + d->track[-3] + 256 * d->track[-2];
    else
      len = UDI_TLEN( d->track[-1], d->track[-3] + 256 * d->track[-2] );
    if( fwrite( d->track, len, 1, file ) != 1 )
      return d->status = DISK_WRPART;

    for( j = len; j > 0; j-- ) {
      crc = crc_udi( crc, *d->track );
      d->track++;
    }
  }
  head[0] = crc & 0xff;
  head[1] = ( crc >> 8 ) & 0xff;
  head[2] = ( crc >> 16 ) & 0xff;
  head[3] = ( crc >> 24 ) & 0xff;
  if( fwrite( head, 4, 1, file ) != 1 )		/* CRC */
    fclose( file );

#ifdef LIBSPECTRUM_SUPPORTS_ZLIB_COMPRESSION
  /* Keep tracks uncompressed in memory */
  error = udi_uncompress_tracks( d );
  if( error ) return error;
#endif			/* #ifdef LIBSPECTRUM_SUPPORTS_ZLIB_COMPRESSION */

  udi_unpack_tracks( d );
  return d->status = DISK_OK;
}

static int
write_img_mgt_opd( FILE *file, disk_t *d )
{
  int i, j, sbase, sectors, seclen, mfm, cyl;

  if( check_disk_geom( d, &sbase, &sectors, &seclen, &mfm, &cyl ) ||
      ( d->type != DISK_OPD && ( sbase != 1 || seclen != 2 || sectors != 10 ) ) ||
      ( d->type == DISK_OPD && ( sbase != 0 || seclen != 1 || sectors != 18 ) ) )
    return d->status = DISK_GEOM;

  if( cyl == -1 ) cyl = d->cylinders;
  if( cyl != 40 && cyl != 80 )
    return d->status = DISK_GEOM;

  if( d->type == DISK_IMG ) {	/* out-out */
    for( j = 0; j < d->sides; j++ ) {
      for( i = 0; i < cyl; i++ ) {
	if( savetrack( d, file, j, i, 1, sectors, seclen ) )
	  return d->status = DISK_GEOM;
      }
    }
  } else {			/* alt */
    for( i = 0; i < cyl; i++ ) {	/* MGT */
      for( j = 0; j < d->sides; j++ ) {
	if( savetrack( d, file, j, i, d->type == DISK_MGT ? 1 : 0,
	    sectors, seclen ) )
	  return d->status = DISK_GEOM;
      }
    }
  }
  return d->status = DISK_OK;
}

static int
write_d40_d80( FILE *file, disk_t *d )
{
  int i, j, sbase, sectors, seclen, mfm, cyl;

  if( check_disk_geom( d, &sbase, &sectors, &seclen, &mfm, &cyl ) ||
      ( sbase != 1 ) )
    return d->status = DISK_GEOM;

  if( cyl == -1 ) cyl = d->cylinders;
  if( ( d->type == DISK_D40 && cyl > 43 ) ||
      ( d->type == DISK_D80 && cyl > 83 ) )
    return d->status = DISK_GEOM;

  for( i = 0; i < cyl; i++ ) {
    for( j = 0; j < d->sides; j++ ) {
      if( savetrack( d, file, j, i, 1,
	    sectors, seclen ) )
	  return d->status = DISK_GEOM;
    }
  }
  return d->status = DISK_OK;
}

static int
write_trd( FILE *file, disk_t *d )
{
  int i, j, sbase, sectors, seclen, mfm, cyl;

  if( check_disk_geom( d, &sbase, &sectors, &seclen, &mfm, &cyl ) ||
      sbase != 1 || seclen != 1 || sectors != 16 )
    return d->status = DISK_GEOM;

  if( cyl == -1 ) cyl = d->cylinders;
  for( i = 0; i < cyl; i++ ) {
    for( j = 0; j < d->sides; j++ ) {
      if( savetrack( d, file, j, i, 1, sectors, seclen ) )
	return d->status = DISK_GEOM;
    }
  }
  return d->status = DISK_OK;
}

static int
write_sad( FILE *file, disk_t *d )
{
  int i, j, sbase, sectors, seclen, mfm, cyl;

  if( check_disk_geom( d, &sbase, &sectors, &seclen, &mfm, &cyl ) || sbase != 1 )
    return d->status = DISK_GEOM;

  if( cyl == -1 ) cyl = d->cylinders;
  memcpy( head, "Aley's disk backup", 18 );
  head[18] = d->sides;
  head[19] = cyl;
  head[20] = sectors;
  head[21] = seclen * 4;
  if( fwrite( head, 22, 1, file ) != 1 )	/* SAD head */
    return d->status = DISK_WRPART;

  for( j = 0; j < d->sides; j++ ) {	/* OUT-OUT */
    for( i = 0; i < cyl; i++ ) {
      if( savetrack( d, file, j, i, 1, sectors, seclen ) )
	return d->status = DISK_GEOM;
    }
  }
  return d->status = DISK_OK;
}

static int
write_fdi( FILE *file, disk_t *d )
{
  int i, j, k, sbase, sectors, seclen, mfm, del;
  int h, t, s, b;
  int toff, soff;

  memset( head, 0, 14 );
  memcpy( head, "FDI", 3 );
  head[0x03] = d->wrprot = 1;
  head[0x04] = d->cylinders & 0xff;
  head[0x05] = d->cylinders >> 8;
  head[0x06] = d->sides & 0xff;
  head[0x07] = d->sides >> 8;
  sectors = 0;
  for( j = 0; j < d->cylinders; j++ ) {	/* count sectors */
    for( i = 0; i < d->sides; i++ ) {
      guess_track_geom( d, i, j, &sbase, &s, &seclen, &mfm );
      sectors += s;
    }
  }
  h = ( sectors + d->cylinders * d->sides ) * 7;	/* track header len */
  head[0x08] = ( h + 0x0e ) & 0xff;	/* description offset */
  head[0x09] = ( h + 0x0e ) >> 8; /* "http://fuse-emulator.sourceforge.net" */
  head[0x0a] = ( h + 0x33 ) & 0xff;	/* data offset */
  head[0x0b] = ( h + 0x33 ) >> 8;
  if( fwrite( head, 14, 1, file ) != 1 )	/* FDI head */
    return d->status = DISK_WRPART;

  /* write track headers */
  toff = 0;			/* offset of track data */
  for( i = 0; i < d->cylinders; i++ ) {
    for( j = 0; j < d->sides; j++ ) {
      DISK_SET_TRACK( d, j, i );
      d->i = 0;
      head[0x00] = toff & 0xff;
      head[0x01] = ( toff >> 8 ) & 0xff;	/* track offset */
      head[0x02] = ( toff >> 16 ) & 0xff;
      head[0x03] = ( toff >> 24 ) & 0xff;
      head[0x04] = 0;
      head[0x05] = 0;
      guess_track_geom( d, j, i, &sbase, &sectors, &seclen, &mfm );
      head[0x06] = sectors;
      if( fwrite( head, 7, 1, file ) != 1 )	/* track header */
	return d->status = DISK_WRPART;

      DISK_SET_TRACK( d, j, i );
      d->i = 0;
      k = 0;
      soff = 0;
      while( sectors > 0 ) {
	while( k < 35 && id_read( d, &h, &t, &s, &b ) ) {
	  head[ 0x00 + k * 7 ] = t;
	  head[ 0x01 + k * 7 ] = h;
	  head[ 0x02 + k * 7 ] = s;
	  head[ 0x03 + k * 7 ] = b;
	  head[ 0x05 + k * 7 ] = soff & 0xff;
	  head[ 0x06 + k * 7 ] = ( soff >> 8 ) & 0xff;
	  if( !datamark_read( d, &del ) ) {
	    head[ 0x04 + k * 7 ] = 0;	/* corrupt sector data */
	  } else {
	    head[ 0x04 + k * 7 ] = 1 << b | ( del ? 0x80 : 0x00 );
	    soff += 0x80 << b;
	  }
	  k++;
	}			/* Sector header */
	if( fwrite( head, 7 * k, 1, file ) != 1 )
	  return d->status = DISK_WRPART;

	sectors -= k;
	k = 0;
      }
      toff += soff;
    }
  }
  if( fwrite( "http://fuse-emulator.sourceforge.net", 37, 1, file ) != 1 )
    return d->status = DISK_WRPART;

  /* write data */
  for( i = 0; i < d->cylinders; i++ ) {
    for( j = 0; j < d->sides; j++ ) {
      if( saverawtrack( d, file, j, i ) )
	return d->status = DISK_WRPART;
    }
  }
  return d->status = DISK_OK;
}

static int
write_cpc( FILE *file, disk_t *d )
{
  int i, j, k, sbase, sectors, seclen, mfm, cyl;
  int h, t, s, b;
  size_t len;

  i = check_disk_geom( d, &sbase, &sectors, &seclen, &mfm, &cyl );
  if( i & DISK_SECLEN_VARI || i & DISK_SPT_VARI || i & DISK_WEAK_DATA )
    return d->status = DISK_GEOM;

  if( i & DISK_MFM_VARI )
    mfm = -1;
  if( cyl == -1 ) cyl = d->cylinders;

  memset( head, 0, 256 );
  memcpy( head, "MV - CPCEMU Disk-File\r\nDisk-Info\r\n", 34 );
  head[0x30] = cyl;
  head[0x31] = d->sides;
  len = sectors * ( 0x80 << seclen ) + 256;
  head[0x32] = len & 0xff;
  head[0x33] = len >> 8;
  if( fwrite( head, 256, 1, file ) != 1 )	/* CPC head */
    return d->status = DISK_WRPART;

  memset( head, 0, 256 );
  memcpy( head, "Track-Info\r\n", 12 );
  for( i = 0; i < cyl; i++ ) {
    for( j = 0; j < d->sides; j++ ) {
      DISK_SET_TRACK( d, j, i );
      d->i = 0;
      head[0x10] = i;
      head[0x11] = j;
      head[0x14] = seclen;
      head[0x15] = sectors;
      if( mfm != -1 )
	head[0x16] = mfm ? 0x4e : 0xff;
      head[0x17] = 0xe5;
      k = 0;
      while( id_read( d, &h, &t, &s, &b ) ) {
	head[ 0x18 + k * 8 ] = t;
	head[ 0x19 + k * 8 ] = h;
	head[ 0x1a + k * 8 ] = s;
	head[ 0x1b + k * 8 ] = b;
	if( k == 0 && mfm == -1 ) {	/* if mixed MFM/FM tracks */
	  head[0x16] = d->track[ d->i ] == 0x4e ? 0x4e : 0xff;
	}
	k++;
      }
      if( fwrite( head, 256, 1, file ) != 1 )	/* Track head */
	return d->status = DISK_WRPART;

      if( saverawtrack( d, file, j, i ) )
	return d->status = DISK_WRPART;
    }
  }
  return d->status = DISK_OK;
}

static int
write_scl( FILE *file, disk_t *d )
{
  int i, j, k, l, t, s, sbase, sectors, seclen, mfm, del, cyl;
  int entries;
  libspectrum_dword sum = 597;		/* sum of "SINCLAIR" */

  if( check_disk_geom( d, &sbase, &sectors, &seclen, &mfm, &cyl ) ||
      sbase != 1 || seclen != 1 || sectors != 16 )
    return d->status = DISK_GEOM;

  DISK_SET_TRACK_IDX( d, 0 );

  /* TR-DOS descriptor */
  if( !id_seek( d, 9 ) || !datamark_read( d, &del ) )
    return d->status = DISK_GEOM;

  entries = head[8] = d->track[ d->i + 228 ];	/* number of files */

  if( entries > 128 || d->track[ d->i + 231 ] != 0x10 ||
      ( d->track[ d->i + 227 ] != 0x16 && d->track[ d->i + 227 ] != 0x17 &&
	d->track[ d->i + 227 ] != 0x18 && d->track[ d->i + 227 ] != 0x19 ) ||
      d->track[ d->i ] != 0 )
    return d->status = DISK_GEOM;

  memcpy( head, "SINCLAIR", 8 );
  sum += entries;
  if( fwrite( head, 9, 1, file ) != 1 )
    return d->status = DISK_WRPART;

  /* save SCL entries */
  j = 1;			/* sector number */
  k = 0;
  sectors = 0;
  for( i = 0; i < entries; i++ ) {	/* read TR-DOS dir */
    if( j > 8 )		/* TR-DOS dir max 8 sector len */
      return d->status = DISK_GEOM;

    if( k == 0 && ( !id_seek( d, j ) || !datamark_read( d, &del ) ) )
      return d->status = DISK_GEOM;

    if( fwrite( d->track + d->i + k, 14, 1, file ) != 1 )
      return d->status = DISK_WRPART;
    sectors += d->track[ d->i + k + 13 ];	/* file length in sectors */
    for( s = 0; s < 14; s++ ) {
      sum += d->track[ d->i + k ];
      k++;
    }
    k += 2;
    if( k >= 256 ) {		/* end of sector */
      j++;
      k = 0;
    }
  }
  /* save data */
  /* we have to 'defragment' the disk :) */
  j = 1;			/* sector number */
  k = 0;			/* byte offset */
  for( i = 0; i < entries; i++ ) {	/* read TR-DOS dir */
    DISK_SET_TRACK_IDX( d, 0 );
    if( k == 0 ) {
      if ( !id_seek( d, j ) || !datamark_read( d, &del ) )
	return d->status = DISK_GEOM;
      memcpy( head, d->track + d->i, 256 );
    }

    s = head[ k + 14 ];	/* starting sector */
    t = head[ k + 15 ];	/* starting track */
    sectors = head[ k + 13 ] + s;	/* last sector */
    k += 16;
    if( k == 256 ) {
      k = 0;
      j++;
    }
    if( t >= d->sides * d->cylinders )
      return d->status = DISK_GEOM;

    if( s % 16 == 0 )
      t--;
    DISK_SET_TRACK_IDX( d, t );

    for( ; s < sectors; s++ ) {		/* save all sectors */
      if( s % 16 == 0 ) {
	t++;
	if( t >= d->sides * d->cylinders )
	  return d->status = DISK_GEOM;
        DISK_SET_TRACK_IDX( d, t );
      }
      if( id_seek( d, s % 16 + 1 ) ) {
	if( datamark_read( d, &del ) ) {	/* write data if we have data */
	  if( data_write_file( d, file, 1 ) ) {
	    return d->status = DISK_GEOM;
	  } else {
	    for( l = 0; l < 256; l++ )
	      sum += d->track[ d->i + l ];
	  }
	}
      } else {
	return DISK_GEOM;
      }
    }
  }
  head[0] = sum & 0xff;
  head[1] = ( sum >> 8 ) & 0xff;
  head[2] = ( sum >> 16 ) & 0xff;
  head[3] = ( sum >> 24 ) & 0xff;

  if( fwrite( head, 4, 1, file ) != 1 )
    return d->status = DISK_WRPART;

  return d->status = DISK_OK;
}

static int
write_log( FILE *file, disk_t *d )
{
  int i, j, k, del, rev;
  int h, t, s, b;
  char str[17];

  str[16] = '\0';
  fprintf( file, "DISK tracks log!\n" );
  fprintf( file, "Sides: %d, cylinders: %d\n", d->sides, d->cylinders );
  for( j = 0; j < d->cylinders; j++ ) {	/* ALT :) */
    for( i = 0; i < d->sides; i++ ) {
      DISK_SET_TRACK( d, i, j );
      d->i = 0;
      fprintf( file, "\n*********\nSide: %d, cylinder: %d type: 0x%02x tlen: %5u\n",
			i, j, d->track[-1], d->track[-3] + 256 * d->track[-2] );
      while( id_read( d, &h, &t, &s, &b ) ) {
	fprintf( file, "  h:%d t:%d s:%d l:%d(%d)", h, t, s, b, 0x80 << b );
	if( datamark_read( d, &del ) )
	  fprintf( file, " %s\n", del ? "deleted" : "normal" );
	else
	  fprintf( file, " noDAM\n" );
      }
    }
  }
  fprintf( file, "\n***************************\nSector Data Dump:\n" );
  for( j = 0; j < d->cylinders; j++ ) {	/* ALT :) */
    for( i = 0; i < d->sides; i++ ) {
      DISK_SET_TRACK( d, i, j );
      d->i = 0;
      fprintf( file, "\n*********\nSide: %d, cylinder: %d type: 0x%02x tlen: %5u\n",
			i, j, d->track[-1], d->track[-3] + 256 * d->track[-2] );
      rev = k = 0;
      while( id_read( d, &h, &t, &s, &b ) ) {
	b = 0x80 << b;
	if( datamark_read( d, &del ) )
	  fprintf( file, "  h:%d t:%d s:%d l:%d (%s)\n", h, t, s, b,
		   del ? "deleted" : "normal" );
	else
	  fprintf( file, "  h:%d t:%d s:%d l:%d (missing data)\n", h,
		   t, s, b );
	k = 0;
	while( k < b ) {
	  if( !( k % 16 ) )
	    fprintf( file, "0x%08x:", k );
	  fprintf( file, " 0x%02x", d->track[ d->i ] );
	  str[ k & 0x0f ] = d->track[ d->i ] >= 32 &&
			    d->track[ d->i ] < 127 ? d->track[ d->i ] : '.';
	  k++;
	  if( !( k % 16 ) )
	    fprintf( file, " | %s\n", str );
	  d->i++;
	  if( d->i >= d->c_bpt ) {
	    d->i = 0;
	    rev++;
	    if( rev == 6 )
	      break;
	  }
	}
      }
    }
  }
  fprintf( file, "\n***************************\n**Full Dump:\n" );
  for( j = 0; j < d->cylinders; j++ ) {	/* ALT :) */
    for( i = 0; i < d->sides; i++ ) {
      DISK_SET_TRACK( d, i, j );
      d->i = 0;
      fprintf( file, "\n*********\nSide: %d, cylinder: %d type: 0x%02x tlen: %5u\n",
			i, j, d->track[-1], d->track[-3] + 256 * d->track[-2] );
      k = 0;
      while( d->i < d->c_bpt ) {
	if( !( k % 8 ) )
	  fprintf( file, "0x%08x:", d->i );
	fprintf( file, " 0x%04x", d->track[ d->i ] |
		 ( bitmap_test( d->clocks, d->i ) ? 0x0c00 : 0x0000 ) |
		 ( bitmap_test( d->fm, d->i ) ? 0x1000 : 0x0000 ) |
		 ( bitmap_test( d->weak, d->i ) ? 0x8000 : 0x0000 ) );
	k++;
	if( !( k % 8 ) )
	  fprintf( file, "\n" );
	d->i++;
      }
    }
  }
  return d->status = DISK_OK;
}

int
disk_write( disk_t *d, const char *filename )
{
  FILE *file;
  const char *ext;
  size_t namelen;
  disk_position_context_t context;

  if( ( file = fopen( filename, "wb" ) ) == NULL )
    return d->status = DISK_WRFILE;

  namelen = strlen( filename );
  if( namelen < 4 )
    ext = "";
  else
    ext = filename + namelen - 4;

  if( d->type == DISK_TYPE_NONE ) {
    if( !strcasecmp( ext, ".udi" ) )
      d->type = DISK_UDI;				/* ALT side */
    else if( !strcasecmp( ext, ".dsk" ) )
      d->type = DISK_CPC;				/* ALT side */
    else if( !strcasecmp( ext, ".mgt" ) )
      d->type = DISK_MGT;				/* ALT side */
    else if( !strcasecmp( ext, ".opd" ) || !strcasecmp( ext, ".opu" ) )
      d->type = DISK_OPD;				/* ALT side */
    else if( !strcasecmp( ext, ".img" ) )		/* out-out */
      d->type = DISK_IMG;
    else if( !strcasecmp( ext, ".trd" ) )		/* ALT */
      d->type = DISK_TRD;
    else if( !strcasecmp( ext, ".sad" ) )		/* ALT */
      d->type = DISK_SAD;
    else if( !strcasecmp( ext, ".fdi" ) )		/* ALT */
      d->type = DISK_FDI;
    else if( !strcasecmp( ext, ".d40" ) )		/* ALT side */
      d->type = DISK_D40;
    else if( !strcasecmp( ext, ".d80" ) )		/* ALT side */
      d->type = DISK_D80;
    else if( !strcasecmp( ext, ".scl" ) )		/* not really a disk image */
      d->type = DISK_SCL;
    else if( !strcasecmp( ext, ".td0" ) )		/* not supported */
      d->type = DISK_TD0;
    else if( !strcasecmp( ext, ".log" ) )		/* ALT */
      d->type = DISK_LOG;
    else
      d->type = DISK_UDI;				/* ALT side */
  }

  /* Save position of current data */
  position_context_save( d, &context );

  update_tracks_mode( d );
  switch( d->type ) {
  case DISK_UDI:
    write_udi( file, d );
    break;
  case DISK_IMG:
  case DISK_MGT:
  case DISK_OPD:
    write_img_mgt_opd( file, d );
    break;
  case DISK_D40:
  case DISK_D80:
    write_d40_d80( file, d );
    break;
  case DISK_TRD:
    write_trd( file, d );
    break;
  case DISK_SAD:
    write_sad( file, d );
    break;
  case DISK_FDI:
    write_fdi( file, d );
    break;
  case DISK_SCL:
    write_scl( file, d );
    break;
  case DISK_CPC:
    write_cpc( file, d );
    break;
  case DISK_LOG:
    write_log( file, d );
    break;
  default:
    d->status = DISK_WRFILE;
    break;
  }

  position_context_restore( d, &context );

  if( d->status != DISK_OK ) {
    fclose( file );
    return d->status;
  }

  if( fclose( file ) == -1 )
    return d->status = DISK_WRFILE;

  return d->status = DISK_OK;
}
