/****************************************************************************

  Copyright (C) Antoine Mine' 2006

  Thomson 8-bit micro-computers.

  Handles SAP, QD and FD floppy formats with a BASIC-DOS filesystem
  (most floppies except some games and demo).

*****************************************************************************/

/* TODO:
   - improve two-sided floppy support
   - check & correct BASIC read filter
   - implement BASIC write filter
   - handle accented letters
 */


/* Thomson floppy geometry are:
   - 5"1/4 single density: 40 tracks, 16 sectors/track, 128 bytes/sector
   - 5"1/4 double density: 40 tracks, 16 sectors/track, 256 bytes/sector
   - 3"1/2 double density: 80 tracks, 16 sectors/track, 256 bytes/sector
   - 2"8: 25 logical tracks, 16 logical sectors/track, 128 bytes/sector

   Notes:

   - 2"8 QDD actually contain a single spiraling track, with 400 128-byte
   sectors; however, the filesystem considers it has 25 virtual tracks of
   16 sectors => we do the same in imgtool (but thomflop.c uses a 400-sector
   addressing to emulate the device hardware)

   - tracks always have 16 sectors, this is requred by the filesystem

   - two-sided floppies are handled as two one-sided floppies in two
   logical floppy drives on the original Thomsons; each has its own
   independent FAT and directory.
   In imgtool, we handle two-sided images as two partitions.
   For now, only 3"1/2 .fd and QDD .qd files can be two-sided.

   - it seems possible to create 3"1/2 single density floppies on the TO9,
   I am not sure this is standard, and we do not support it...

*/


/* Microsoft BASIC Filesystem.

   The floppy is divided into blocks, with always two blocks / track.
   Also, in double density, only the first 255 bytes of each 256-byte
   sector is used. Thus, blocks are 1024 bytes in single density, and
   2040 bytes in double density.
   Depending on the number of tracks, there can be 50, 80 or 160 blocks.

   Filesystem data always occupy track 20, whatever the floppy geometry.

   * sector 1:
     - bytes 0-7: floppy name
     - remaining bytes: undocumented, seem unused

   * sector 2: FAT, 1 byte info per block
     - byte 0: always 0
     - byte 1: block 0
        ...
     - byte i: block i-1
        ...
     - byte 160: block 159
     - remaining bytes: undocumented, seem unused

     The byte has the following meaning:
     - ff: block is free
     - fb: block is reserved
     - 00-bf: block allocated, points to then next file block
     - c1-c8: last block in file, (value & 15) is the number of sectors
              actually allocated to the file

     Note: in single density, we can reference only 127 blocks. This is not
     a problem because single density floppies can only have 50 or 80 blocks,
     never 160.

   * sector 3-16: directory

     no subdirectories

     each file entry occupies 32 bytes:
     - bytes 00-07: filename, padded with whitespaces
     - bytes 08-0A: file extension, padedd with whitespaces
     - byte  0B:    file type
                    . 0 = BASIC, ASCII or binary program (B)
	   	    . 1 = BASIC or ASCII data (D)
		    . 2 = machine code (M)
		    . 3 = ASCII assembler file (A)
     - byte  0C:    format flag
 		    . 00 = binary (tokenized)
                    . ff = ASCII
     - byte  0D:    first block
     - bytes 0E-0F: bytes used in the last sector
     - bytes 10-17: comment
     - byte 18:     day (of month)
     - byte 19:     month
     - byte 1A:     year (two last digits)
     - bytes 1B-1f: unknown

     Note that byte 0 has a special significance:
     - 00:    free directory entry
     - 20-7F: actual directory entry, value is the first character ini filename
     - ff:    end of directory

     There can be 56 files in single density, 112 in double density.
 */

#include "imgtool.h"
#include "iflopimg.h"
#include "formats/basicdsk.h"

#include <time.h>


#define MAXSIZE 80*16*256*2 /* room for two faces, double-density, 80 tracks */

typedef struct {

  imgtool_stream *stream;

  UINT16 sector_size;   /* 128 or 256 */
  UINT16 sectuse_size;  /* bytes used in sector: 128 or 255 */
  UINT8  tracks;        /* 25, 40, or 80 */
  UINT8  heads;         /* 1 or 2 */
  UINT8  data[MAXSIZE]; /* image data */

  int    modified;      /* data need to be copied back to image file */

} thom_floppy;


typedef enum {

  THOM_DIRENT_END,
  THOM_DIRENT_FREE,
  THOM_DIRENT_FILE,
  THOM_DIRENT_INVALID,

} thom_dirent_type;


typedef struct {

  thom_dirent_type type;
  int    index;

  char   name[9];
  char   ext[4];
  char   comment[9];
  UINT8  ftype;
  UINT8  format;
  UINT8  firstblock;
  UINT16 lastsectsize;
  UINT8  day;
  UINT8  month;
  UINT8  year;

} thom_dirent;


static void thom_basic_get_info(const imgtool_class *clas, UINT32 param,
				union imgtoolinfo *info);

static UINT8* thom_get_sector(thom_floppy* f, unsigned head,
			      unsigned track, unsigned sector);


/*********************** .fd / .qd format ************************/

/* .fd and .qd formats are very simple: the sectors are simply put one after
   another, starting at sector 1 of track 0 to sector 16 track 0, then
   starting again at sector 1 of track 1, and so on.
   There is no image or sector header, and no gap between sectors.
   The number of tracks and sector size are determined by the extension
   (.fd have 40 or 80 tracks, .qd have 25 tracks) and the file size.
*/

static imgtoolerr_t thom_open_fd_qd(imgtool_image *img, imgtool_stream *stream)
{
  thom_floppy* f = (thom_floppy*) imgtool_image_extra_bytes( img );
  int size = stream_size( stream );

  f->stream = stream;
  f->modified = 0;

  /* guess format */
  switch ( size ) {

  case 81920:
    f->tracks = 40;
    f->sector_size = 128;
    f->sectuse_size = 128;
    f->heads = 1;
    break;

  case 163840:
    f->tracks = 40;
    f->sector_size = 256;
    f->sectuse_size = 255;
    f->heads = 1;
    /* could also be: sector_size=128, heads=2 */
    /* maight even be: tracks=80, sector_size=128 */
    break;

  case 327680:
    f->tracks = 80;
    f->sector_size = 256;
    f->sectuse_size = 255;
    f->heads = 1;
    /* could also be: tracks=40, heads=2 */
    break;

  case 655360:
    f->tracks = 80;
    f->sector_size = 256;
    f->sectuse_size = 255;
    f->heads = 2;
    break;

  case 51200:
    f->tracks = 25;
    f->sector_size = 128;
    f->sectuse_size = 128;
    f->heads = 1;
    break;

  case 62400:
    f->tracks = 25;
    f->sector_size = 128;
    f->sectuse_size = 128;
    f->heads = 2;
    break;

  default:
    return IMGTOOLERR_CORRUPTIMAGE;
  }

  assert( size == f->heads * f->tracks * 16 * f->sector_size );

  stream_seek( stream, 0, SEEK_SET );
  if ( stream_read( stream, f->data, size ) < size )
    return IMGTOOLERR_READERROR;

  return IMGTOOLERR_SUCCESS;
}

static void thom_close_fd_qd(imgtool_image *img)
{
  thom_floppy* f = (thom_floppy*) imgtool_image_extra_bytes( img );

  /* save image */
  if ( f->modified ) {
    int size = f->heads * f->tracks * 16 * f->sector_size;
    stream_seek( f->stream, 0, SEEK_SET );
    if ( stream_write( f->stream, f->data, size ) < size )
      logerror( "thom_diskimage_close_fd_qd: write error\n" );
  }

  stream_close( f->stream );
}


/*********************** .sap format ************************/

/* SAP format, Copyright 1998 by Alexandre Pukall

   image is composed of:
   - byte 0:        version (0 or 1)
   - bytes 1..65:   signature
   - bytes 66..end: sectors

   each sector is composed of
   - a 4-byte header:
     . byte 0:     format
                      0 3"1/2 double density
                      1 5"1/4 low density
     . byte 1:     protection (?)
     . byte 2:     track index, in 0..79
     . byte 3:     sector index, in 1..16
   - sector data, XOR 0xB3
   - 2-byte CRC of header + data, high byte first

   there is no provision for two-sided images in sap
 */

static const int sap_magic_num = 0xB3; /* simple XOR crypt */

static const char sap_header[]=
  "\001SYSTEME D'ARCHIVAGE PUKALL S.A.P. "
  "(c) Alexandre PUKALL Avril 1998";

static const UINT16 sap_crc[] = {
  0x0000, 0x1081, 0x2102, 0x3183,   0x4204, 0x5285, 0x6306, 0x7387,
  0x8408, 0x9489, 0xa50a, 0xb58b,   0xc60c, 0xd68d, 0xe70e, 0xf78f,
};

static UINT16 thom_sap_crc( UINT8* data, int size )
{
  int i;
  UINT16 crc = 0xffff, crc2;
  for ( i = 0; i < size; i++ ) {
    crc2 = ( crc >> 4 ) ^ sap_crc[ ( crc ^ data[i] ) & 15 ];
    crc = ( crc2 >> 4 ) ^ sap_crc[ ( crc2 ^ (data[i] >> 4) ) & 15 ];
  }
  return crc;
}

static imgtoolerr_t thom_open_sap(imgtool_image *img, imgtool_stream *stream)
{
  thom_floppy* f = (thom_floppy*) imgtool_image_extra_bytes( img );
  UINT8 buf[262];

  f->stream = stream;
  f->modified = 0;

 /* check image header */
  stream_seek( stream, 0, SEEK_SET );
  stream_read( stream, buf, 66 );
  if ( memcmp( buf+1, sap_header+1, 65 ) ) return IMGTOOLERR_CORRUPTIMAGE;

  /* guess format */
  stream_read( stream, buf, 1 );
  switch ( buf[0] ) {
  case 1:
  case 3:
    f->heads = 1;
    f->tracks = 40;
    f->sector_size = 128;
    f->sectuse_size = 128;
    break;
  case 0:
  case 2:
  case 4:
    f->heads = 1;
    f->tracks = 80;
    f->sector_size = 256;
    f->sectuse_size = 255;
    break;
  default: return IMGTOOLERR_CORRUPTIMAGE;
  }

 stream_seek( stream, 66, SEEK_SET );
  while ( 1) {
    int i, sector, track;
    UINT16 crc;

    /* load sector */
    if ( stream_read( stream, buf, 6 + f->sector_size ) < 6 + f->sector_size )
      break;

    /* parse sector header */
    track = buf[2];
    sector = buf[3];
    if ( sector < 1 || sector > 16 || track >= f->tracks )
      return IMGTOOLERR_CORRUPTIMAGE;

    /* decrypt */
    for ( i = 0; i < f->sector_size; i++ ) buf[ i + 4 ] ^= sap_magic_num;
    memcpy( thom_get_sector( f, 0, track, sector ), buf + 4, f->sector_size );

    /* check CRC */
    crc = thom_sap_crc( buf, f->sector_size + 4 );
    if ( ( (crc >> 8)   != buf[ f->sector_size + 4 ] ) ||
         ( (crc & 0xff) != buf[ f->sector_size + 5 ] ) )
      return IMGTOOLERR_CORRUPTIMAGE;
  }

  return IMGTOOLERR_SUCCESS;
}

static void thom_close_sap(imgtool_image *img)
{
  thom_floppy* f = (thom_floppy*) imgtool_image_extra_bytes( img );

  if ( f->modified ) {
    int i, sector, track;
    UINT8 buf[262];
    UINT16 crc;

    /* rewind */
    stream_seek( f->stream, 0, SEEK_SET );

    /* image header */
    if ( stream_write( f->stream, sap_header, 66 ) < 66 ) {
      logerror( "thom_diskimage_close_sap: write error\n" );
      return;
    }

    for ( track = 0; track < f->tracks; track++ )
      for ( sector = 1; sector <= 16; sector++ ) {

	/* sector header & data */
	buf[0] = ( f->tracks == 80 ) ? 2 : 1;
	buf[1] = 0;
	buf[2] = track;
	buf[3] = sector;
	memcpy( buf + 4,
		thom_get_sector( f, 0, track, sector), f->sector_size );

	/* compute crc */
	crc = thom_sap_crc( buf, f->sector_size + 4 );
	buf[ f->sector_size + 4 ] = crc >> 8;
	buf[ f->sector_size + 5 ] = crc & 0xff;

	/* crypt */
	for ( i = 0; i < f->sector_size; i++ ) buf[ i + 4 ] ^= sap_magic_num;

	/* save */
	if ( stream_write( f->stream, buf, f->sector_size + 6 ) <
	     f->sector_size + 6 ) {
	  logerror( "thom_diskimage_close_sap: write error\n" );
	  return;
	}
      }
  }

  stream_close( f->stream );
}


/*********************** low-level functions *********************/

static UINT8* thom_get_sector(thom_floppy* f, unsigned head,
			      unsigned track, unsigned sector)
{
  assert( head < f->heads);
  assert( track < f->tracks );
  assert( sector > 0 && sector <= 16 );
  return & f->data[ ( (head * f->tracks + track) * 16 + (sector-1) ) *
		    f->sector_size ];
}

static int thom_nb_blocks(thom_floppy* f)
{
  int n1 = f->tracks * 2;      /* 2 blocks per track */
  int n2 = f->sector_size - 1; /* FAT entries */
  return (n1 < n2) ? n1 : n2;
}

static int thom_max_dirent(thom_floppy* f)
{
  return 14 * f->sector_size / 32;
}

/* remove trailing spaces */
static void thom_stringify(char* str)
{
  char* s = str + strlen(str) - 1;
  while ( s >= str && *s == ' ' ) { *s = 0; s--; }
  for ( s = str; *s; s++ )
    if ( (UINT8)*s == 0xff ) *s = 0;
    else if ( *s < ' ' || *s >= 127 ) *s = '?';
}

/* pad with spaces until the length is nb */
static void thom_unstringity(char* str, int nb)
{
  char* s = str;
  while ( s < str + nb && *s ) s++;
  while ( s < str + nb ) { *s = ' '; s++; }
}

/* get filename and extension & truncate them to 8 + 3 */
static void thom_conv_filename(const char* s, char name[9], char ext[4])
{
  int i;
  memset( name, 0, 9 );
  memset( ext, 0, 4 );
  for ( i = 0; i < 8 && *s && *s != '.'; i++, s++ ) name[i] = *s;
  while ( *s && *s != '.' ) s++;
  if ( *s == '.' ) {
    s++;
    for ( i = 0; i < 3 && *s; i++, s++ ) ext[i] = *s;
  }
}

static time_t thom_crack_time(thom_dirent* d)
{
  struct tm t;
  time_t now;

  /* check */
  if ( d->day < 1 || d->day > 31 || d->month < 1 || d->month > 12 ) return 0;

  /* converts */
  time( &now );
  t = *localtime( &now );
  t.tm_sec = 0;
  t.tm_min = 0;
  t.tm_hour = 0;
  t.tm_mday = d->day;
  t.tm_mon = d->month - 1;
  t.tm_year = (d->year < 65 ) ? d->year + 100 : d->year;
  return mktime(&t);
}

static void thom_make_time(thom_dirent* d, time_t time)
{
  if ( ! time ) {
    d->day = d->month = d->year = 0;
  }
  else {
    struct tm t = *localtime( &time );
    d->day = t.tm_mday;
    d->month = t.tm_mon + 1;
    d->year = t.tm_year % 100;
  }
}

static void thom_make_time_now(thom_dirent* d)
{
  time_t now;
  time( &now );
  thom_make_time( d, now );
}

static void thom_get_dirent(thom_floppy* f, unsigned head,
			    unsigned n, thom_dirent* d)
{
  UINT8* base = thom_get_sector( f, head, 20, 3 ) + n * 32;
  memset( d, 0, sizeof(*d) );
  d->index = n;
  if ( n >= thom_max_dirent( f ) )
    d->type = THOM_DIRENT_END;
  else if ( *base == 0xff )
    d->type = THOM_DIRENT_END;
  else if ( *base == 0 )
    d->type = THOM_DIRENT_FREE;
  else {
    int i;
    /* fill regular entry */
    d->type = THOM_DIRENT_FILE;
    memcpy( d->name, base, 8 );
    memcpy( d->ext, base + 8, 3 );
    memcpy( d->comment, base + 16, 8 );
    thom_stringify( d->name );
    thom_stringify( d->ext );
    thom_stringify( d->comment );
    d->ftype = base[11];
    d->format = base[12];
    d->firstblock = base[13];
    d->lastsectsize = ((int)base[14] << 8) + base[15];
    d->day = base[24];
    d->month = base[25];
    d->year = base[26];
    /* sanity check */
    for ( i = 0; i < 11; i++ )
      if ( base[i] < ' ' || base[i] > 127 ) d->type = THOM_DIRENT_INVALID;
  }
}

static void thom_set_dirent(thom_floppy* f, unsigned head,
			    unsigned n, thom_dirent* d)
{
  UINT8* base = thom_get_sector( f, head, 20, 3 ) + n * 32;
  if ( n >= thom_max_dirent( f ) ) return;
  memset( base, 0, 32 );
  if ( d->type == THOM_DIRENT_END ) base[ 0 ] = 0xff;
  else if ( d->type == THOM_DIRENT_FREE ) base[ 0 ] = 0;
  else {
    memcpy( base, d->name, 8 );
    memcpy( base + 8, d->ext, 3 );
    memcpy( base + 16, d->comment, 8 );
    thom_unstringity( (char*)base, 8 );
    thom_unstringity( (char*)base + 8, 3 );
    if ( base[16] ) thom_unstringity( (char*)base + 16, 8 );
    base[11] = d->ftype;
    base[12] = d->format;
    base[13] = d->firstblock;
    base[14] = d->lastsectsize >> 8;
    base[15] = d->lastsectsize & 0xff;
    base[24] = d->day;
    base[25] = d->month;
    base[26] = d->year;
  }
  f->modified = 1;
}

/* returns 1 if file found, 0 if not */
static int thom_find_dirent(thom_floppy* f, unsigned head,
			    const char* name, thom_dirent* d)
{
  int n = 0;
  while ( 1 ) {
    thom_get_dirent( f, head, n, d );
    if ( d->type == THOM_DIRENT_END ) return 0;
    if ( d->type == THOM_DIRENT_FILE ) {
      char buf[13];
      sprintf( buf, "%s.%s", d->name, d->ext );
      if ( ! strcmp( buf, name ) ) return 1;
    }
    n++;
  }
}

/* returns 1 if free entry found, 0 if none available */
static int thom_find_free_dirent(thom_floppy* f, unsigned head, thom_dirent* d)
{
  int n = 0;
  while ( 1 ) {
    thom_get_dirent( f, head, n, d );
    if ( d->type == THOM_DIRENT_FREE ) return 1;
    if ( d->type == THOM_DIRENT_END ) break;
    n++;
  }
  if ( n + 1 >= thom_max_dirent( f ) ) return 0;
  thom_set_dirent( f, head, n+1, d );
  d->type = THOM_DIRENT_FREE;
  return 1;
}

/* returns the file size in bytes, or -1 if error */
static int thom_get_file_size(thom_floppy* f, unsigned head, thom_dirent* d)
{
  UINT8* fat = thom_get_sector( f, head, 20, 2 );
  int nbblocks = thom_nb_blocks(f);
  int block = d->firstblock;
  int size = 0;
  int timeout = nbblocks;
  if ( d->type != THOM_DIRENT_FILE ) return -1;
  if ( block >= nbblocks ) return -1;
  block = fat[ block + 1 ];
  while ( 1 ) {
    if ( block < nbblocks ) {
      /* full block */
      size += 8 * f->sectuse_size;
      block = fat[ block + 1 ];
    }
    else if ( block >= 0xc1 && block <= 0xc8 ) {
      /* last block in file */
      size += (block-0xc1) * f->sectuse_size;
      size += d->lastsectsize;
      return size;
    }
    else return -1;
    timeout--;
    if ( timeout < 0 ) return -1;
   }
}

/* number of blocks used by file */
static int thom_get_file_blocks(thom_floppy* f, unsigned head, thom_dirent* d)
{
  UINT8* fat = thom_get_sector( f, head, 20, 2 );
  int nbblocks = thom_nb_blocks(f);
  int block = d->firstblock;
  int nb = 0;
  if ( d->type != THOM_DIRENT_FILE ) return 0;
  if ( block >= nbblocks ) return 0;
  block = fat[ block + 1 ];
  while ( 1 ) {
    if ( block < nbblocks ) {
      /* full block */
      nb++;
      block = fat[ block + 1 ];
    }
    else if ( block >= 0xc1 && block <= 0xc8 ) {
      /* last block in file */
      nb++;
      return nb;
    }
    else return nb;
  }
}

/* number of free blocks */
static int thom_get_free_blocks(thom_floppy* f, unsigned head)
{
  UINT8* fat = thom_get_sector( f, head, 20, 2 );
  int nbblocks = thom_nb_blocks(f);
  int i, nb = 0;
  for ( i = 1; i <= nbblocks; i++ )
    if ( fat[i] == 0xff ) nb++;
  return nb;
}

/* dump file contents into dst */
static void thom_get_file(thom_floppy* f, unsigned head,
			  thom_dirent* d, imgtool_stream *dst)
{
  UINT8* fat = thom_get_sector( f, head, 20, 2 );
  int nbblocks = thom_nb_blocks(f);
  int block = d->firstblock;
  if ( block >= nbblocks ) return;
  while ( 1 ) {
    int nextblock = fat[ block + 1 ];
    int track = block / 2;
    int firstsect = (block % 2) ? 9 : 1;
    if ( nextblock < nbblocks ) {
      /* full block */
      int i;
      for ( i = 0; i < 8; i++ ) {
	UINT8* data = thom_get_sector( f, head, track, firstsect + i );
	stream_write( dst, data, f->sectuse_size );
      }
      block = fat[ block + 1 ];
    }
    else if ( nextblock >= 0xc1 && nextblock <= 0xc8 ) {
      /* last block in file */
      int i;
      UINT8* data;
      for ( i = 0; i < nextblock - 0xc1; i++ ) {
	data = thom_get_sector( f, head, track, firstsect + i );
	stream_write( dst, data, f->sectuse_size );
      }
      data = thom_get_sector( f, head, track, firstsect + i );
      stream_write( dst, data, d->lastsectsize );
      return;
    }
    else {
      /* invalid, assume last block */
      UINT8* data = thom_get_sector( f, head, track, firstsect );
      stream_write( dst, data, d->lastsectsize );
      return;
    }
    block = nextblock;
  }
}

static void thom_del_file(thom_floppy* f, unsigned head, thom_dirent* d)
{
  UINT8* fat = thom_get_sector( f, head, 20, 2 );
  int nbblocks = thom_nb_blocks(f);
  int block = d->firstblock;
  if ( d->type != THOM_DIRENT_FILE ) return;
  if ( block >= nbblocks ) return;
  while ( 1 ) {
    int nextblock = fat[ block + 1 ];
    fat[ block ] = 0xff;
    if ( nextblock < nbblocks ) block = fat[ block + 1 ];
    else break;
  }
  d->type = THOM_DIRENT_FREE;
  thom_set_dirent( f, head, d->index, d );
  f->modified = 1;
}

/* create a new file or overwrite an old one, with the contents of src */
static void thom_put_file(thom_floppy* f, unsigned head,
			  thom_dirent* d, imgtool_stream *src)
{
  int size = stream_size( src );
  UINT8* fat = thom_get_sector( f, head, 20, 2 );
  int nbblocks = thom_nb_blocks(f);
  int block;

  /* find first free block */
  for ( block = 0; block < nbblocks && fat[ block + 1 ] != 0xff; block++ );
  if ( block >= nbblocks ) return;
  d->firstblock = block;

  /* store file */
  while (1) {
    int track = block / 2;
    int firstsect = (block % 2) ? 9 : 1;
    int i;

    /* store data, full sectors */
    for ( i = 0; i < 8 && size > f->sectuse_size; i++ ) {
      UINT8* dst = thom_get_sector( f, head, track, firstsect + i );
      stream_read( src, dst, f->sectuse_size );
      size -= f->sectuse_size;
    }

    /* store data, last sector */
    if ( i < 8 ) {
      UINT8* dst = thom_get_sector( f, head, track, firstsect + i );
      stream_read( src, dst, size );
      fat[ block + 1 ] = 0xc1 + i;
      d->lastsectsize = size;
      break;
    }

    /* find next free block & update fat */
    i = block;
    for ( block++; block < nbblocks && fat[ block + 1 ] != 0xff; block++ );
    if ( block >= nbblocks ) {
      /* out of memory! */
      fat[ i + 1 ] = 0;
      d->lastsectsize = 0;
      break;
    }
    fat[ i + 1 ] = block;
  }

  d->type = THOM_DIRENT_FILE;
  thom_set_dirent( f, head, d->index, d );
  f->modified = 1;
}


/********************** module functions ***********************/

static imgtoolerr_t thom_get_sector_size(imgtool_image* img, UINT32 track,
					 UINT32 head, UINT32 sector,
					 UINT32 *sector_size)
{
  thom_floppy* f = (thom_floppy*) imgtool_image_extra_bytes( img );

  if ( head >= f->heads || sector < 1 || sector > 16 || track >= f->tracks ) {
    if ( sector_size ) *sector_size = 0;
    return IMGTOOLERR_SEEKERROR;
  }
  if ( sector_size ) *sector_size = f->sector_size;
  return IMGTOOLERR_SUCCESS;
}

static imgtoolerr_t thom_get_geometry(imgtool_image* img, UINT32* tracks,
				      UINT32* heads, UINT32* sectors)
{
  thom_floppy* f = (thom_floppy*) imgtool_image_extra_bytes( img );
  if ( tracks ) *tracks = f->tracks;
  if ( heads ) *heads = f->heads;
  if ( sectors ) *sectors = 16;
  return IMGTOOLERR_SUCCESS;
}

static imgtoolerr_t thom_read_sector(imgtool_image* img, UINT32 track,
				     UINT32 head, UINT32 sector, void *buf,
				     size_t len)
{
  thom_floppy* f = (thom_floppy*) imgtool_image_extra_bytes( img );
  if ( head >= f->heads || sector < 1 || sector > 16 || track >= f->tracks )
    return IMGTOOLERR_SEEKERROR;
  if ( len > f->sector_size) return IMGTOOLERR_READERROR;
  memcpy( buf, thom_get_sector( f, head, track, sector ), len );
  return IMGTOOLERR_SUCCESS;
}

static imgtoolerr_t thom_write_sector(imgtool_image *img, UINT32 track,
				      UINT32 head, UINT32 sector,
				      const void *buf, size_t len)
{
  thom_floppy* f = (thom_floppy*) imgtool_image_extra_bytes( img );
  if ( stream_isreadonly( f->stream ) ) return IMGTOOLERR_WRITEERROR;
  if ( head >= f->heads || sector < 1 || sector > 16 || track >= f->tracks )
    return IMGTOOLERR_SEEKERROR;
  if ( len > f->sector_size) return IMGTOOLERR_WRITEERROR;
  f->modified = 1;
  memcpy( thom_get_sector( f, head, track, sector ), buf, len );
  return IMGTOOLERR_SUCCESS;
}

/* returns floopy name */
/* actually, each side has its own name, but we only return the one on side 0.
 */
static void thom_info(imgtool_image *img, char *string, size_t len)
{
  thom_floppy* f = (thom_floppy*) imgtool_image_extra_bytes( img );
  UINT8* base = thom_get_sector( f, 0, 20, 1 );
  char buf[9];
  memcpy( buf, base, 8 );
  buf[8] = 0;
  thom_stringify( buf );
  strncpy( string, buf, len );
}

/* each side of a floppy has its own filesystem, we treat them as'partitions'
 */
static imgtoolerr_t thom_list_partitions(imgtool_image *img,
					 imgtool_partition_info *partitions,
					 size_t len)
{
  thom_floppy* f = (thom_floppy*) imgtool_image_extra_bytes( img );
  if ( len >= 1 ) {
    partitions[0].get_info = thom_basic_get_info;
    partitions[0].base_block = 0;
    partitions[0].block_count = 1;
  }
  if ( len >= 2 && f->heads >= 2 ) {
    partitions[1].get_info = thom_basic_get_info;
    partitions[1].base_block = 1;
    partitions[1].block_count = 1;
  }
  return IMGTOOLERR_SUCCESS;
}

static imgtoolerr_t thom_open_partition(imgtool_partition *part,
					UINT64 first_block, UINT64 block_count)
{
  imgtool_image* img = imgtool_partition_image( part );
  thom_floppy* f = (thom_floppy*) imgtool_image_extra_bytes( img );
  if ( first_block >= f->heads )
    return IMGTOOLERR_INVALIDPARTITION;
  * ( (int*) imgtool_partition_extra_bytes( part ) ) = first_block;
  return IMGTOOLERR_SUCCESS;
}


static imgtoolerr_t thom_begin_enum(imgtool_directory *enumeration,
				    const char *path)
{
  int* n = (int*) imgtool_directory_extrabytes( enumeration );
  *n = 0;
  return IMGTOOLERR_SUCCESS;
}

static imgtoolerr_t thom_next_enum(imgtool_directory *enumeration,
				   imgtool_dirent *ent)
{
  imgtool_partition *part = imgtool_directory_partition( enumeration);
  int head = *( (int*) imgtool_partition_extra_bytes( part ) );
  imgtool_image* img = imgtool_partition_image( part );
  thom_floppy* f = (thom_floppy*) imgtool_image_extra_bytes( img );
  int* n = (int*) imgtool_directory_extrabytes( enumeration );
  thom_dirent d;

  do {
    thom_get_dirent( f, head, *n, &d );
    (*n) ++;
  }
  while ( d.type == THOM_DIRENT_FREE );
  if ( d.type == THOM_DIRENT_END ) ent->eof = 1;
  else if ( d.type == THOM_DIRENT_INVALID ) {
    ent->corrupt = 1;
  }
  else {
    int size;
    snprintf( ent->filename, sizeof(ent->filename), "%s.%s", d.name, d.ext );
    snprintf( ent->attr, sizeof(ent->attr), "%c %c %s",
	      (d.ftype == 0) ? 'B' :  (d.ftype == 1) ? 'D' :
	      (d.ftype == 2) ? 'M' :  (d.ftype == 3) ? 'A' : '?',
	      (d.format == 0) ? 'B' : (d.format == 0xff) ? 'A' : '?',
	      d.comment );
    ent->creation_time = thom_crack_time( &d );
    size  = thom_get_file_size( f, head, &d );
    if ( size >= 0 ) ent->filesize = size;
    else {
      ent->filesize = 0;
      ent->corrupt = 1;
    }
  }
  return IMGTOOLERR_SUCCESS;
}

static imgtoolerr_t thom_free_space(imgtool_partition *part, UINT64 *size)
{
  int head = *( (int*) imgtool_partition_extra_bytes( part ) );
  imgtool_image* img = imgtool_partition_image( part );
  thom_floppy* f = (thom_floppy*) imgtool_image_extra_bytes( img );
  int nb = thom_get_free_blocks( f, head );
  (*size) = nb * f->sectuse_size * 8;
  return IMGTOOLERR_SUCCESS;
}

static imgtoolerr_t thom_read_file(imgtool_partition *part,
				   const char *filename,
				   const char *fork,
				   imgtool_stream *destf)
{
  int head = *( (int*) imgtool_partition_extra_bytes( part ) );
  imgtool_image* img = imgtool_partition_image( part );
  thom_floppy* f = (thom_floppy*) imgtool_image_extra_bytes( img );
  thom_dirent d;
  char name[9], ext[4], fname[14];
  int size;

  /* convert filename */
  thom_conv_filename( filename, name, ext );
  sprintf( fname, "%s.%s", name, ext );

  if ( ! thom_find_dirent( f, head, fname, &d ) )
    return IMGTOOLERR_FILENOTFOUND;
  size = thom_get_file_size( f, head, &d );
  if ( size < 0 ) return IMGTOOLERR_CORRUPTFILE;
  thom_get_file( f, head, &d, destf );
  return IMGTOOLERR_SUCCESS;
}

static imgtoolerr_t thom_delete_file(imgtool_partition *part,
				     const char *filename)
{
  int head = *( (int*) imgtool_partition_extra_bytes( part ) );
  imgtool_image* img = imgtool_partition_image( part );
  thom_floppy* f = (thom_floppy*) imgtool_image_extra_bytes( img );
  thom_dirent d;
  char name[9], ext[4], fname[14];

  /* convert filename */
  thom_conv_filename( filename, name, ext );
  sprintf( fname, "%s.%s", name, ext );

  if ( ! thom_find_dirent( f, head, fname, &d ) )
    return IMGTOOLERR_FILENOTFOUND;
  /*if ( thom_get_file_size( f, head, &d ) < 0 ) return IMGTOOLERR_CORRUPTFILE;*/
  if ( stream_isreadonly( f->stream ) ) return IMGTOOLERR_WRITEERROR;
  thom_del_file( f, head, &d );
  return IMGTOOLERR_SUCCESS;
}

static imgtoolerr_t thom_write_file(imgtool_partition *part,
				    const char *filename,
				    const char *fork,
				    imgtool_stream *sourcef,
				    option_resolution *opts)
{
  int head = *( (int*) imgtool_partition_extra_bytes( part ) );
  imgtool_image* img = imgtool_partition_image( part );
  thom_floppy* f = (thom_floppy*) imgtool_image_extra_bytes( img );
  thom_dirent d;
  int size = stream_size( sourcef );
  int blocks = thom_get_free_blocks( f, head );
  char name[9], ext[4], fname[14];
  const char* comment;
  int is_new = 1;

  if ( stream_isreadonly( f->stream ) ) return IMGTOOLERR_WRITEERROR;

  /* convert filename */
  thom_conv_filename( filename, name, ext );
  sprintf( fname, "%s.%s", name, ext );

  /* check available space & find dir entry */
  if ( thom_find_dirent( f, head, fname, &d ) ) {
    /* file already exists: delete it */
    if ( thom_get_file_size( f, head, &d ) < 0 ) return IMGTOOLERR_CORRUPTFILE;
    blocks += thom_get_file_blocks( f, head, &d );
    if ( blocks * 8 * f->sectuse_size < size ) return IMGTOOLERR_NOSPACE;
    thom_del_file( f, head, &d );
    is_new = 0;
  }
  else {
    /* new file, need new dir entry */
    if ( blocks * 8 * f->sectuse_size < size ) return IMGTOOLERR_NOSPACE;
    if ( ! thom_find_free_dirent( f, head, &d ) ) return IMGTOOLERR_NOSPACE;
    thom_make_time_now( &d );
  }

  /* fill-in dir entry */
  memcpy( d.name, name, 9 );
  memcpy( d.ext, ext, 4 );

  /* file type */
  switch ( option_resolution_lookup_int( opts, 'T' ) ) {
  case 0:
    if ( ! is_new ) break;
    if ( ! mame_stricmp( ext, "BAS" ) ) d.ftype = 0;
    else if ( ! mame_stricmp( ext, "BAT" ) ) d.ftype = 0;
    else if ( ! mame_stricmp( ext, "DAT" ) ) d.ftype = 1;
    else if ( ! mame_stricmp( ext, "ASC" ) ) d.ftype = 1;
    else if ( ! mame_stricmp( ext, "BIN" ) ) d.ftype = 2;
    else if ( ! mame_stricmp( ext, "MAP" ) ) d.ftype = 2;
    else if ( ! mame_stricmp( ext, "CFG" ) ) d.ftype = 2;
    else if ( ! mame_stricmp( ext, "ASM" ) ) d.ftype = 3;
    else d.ftype = 2;
    break;
  case 1: d.ftype = 0; break;
  case 2: d.ftype = 1; break;
  case 3: d.ftype = 2; break;
  case 4: d.ftype = 3; break;
  }

 /* format flag */
 switch ( option_resolution_lookup_int( opts, 'F' ) ) {
  case 0:
    if ( ! is_new ) break;
    if ( ! mame_stricmp( ext, "DAT" ) ) d.format = 0xff;
    else if ( ! mame_stricmp( ext, "ASC" ) ) d.format = 0xff;
    else if ( ! mame_stricmp( ext, "ASM" ) ) d.format = 0xff;
    else d.format = 0;
    break;
  case 1: d.format = 0; break;
  case 2: d.format = 0xff; break;
  }

 /* comment */
 comment = option_resolution_lookup_string( opts, 'C' );
 if ( comment && *comment )
   strncpy( d.comment, comment, 8 );

  /* write file */
 thom_put_file( f, head, &d, sourcef );
 return IMGTOOLERR_SUCCESS;
}

static imgtoolerr_t thom_suggest_transfer(imgtool_partition *part,
					  const char *fname,
					  imgtool_transfer_suggestion *suggestions,
					  size_t suggestions_length)
{
  int head = *( (int*) imgtool_partition_extra_bytes( part ) );
  imgtool_image* img = imgtool_partition_image( part );
  thom_floppy* f = (thom_floppy*) imgtool_image_extra_bytes( img );
  thom_dirent d;
  int is_basic = 0;

  if ( suggestions_length < 1 ) return IMGTOOLERR_SUCCESS;

  if ( fname ) {
    if ( ! thom_find_dirent( f, head, fname, &d ) )
      return IMGTOOLERR_FILENOTFOUND;
    if ( d.ftype == 0 && d.format == 0 ) is_basic = 1;
  }

  if ( is_basic ) {
    suggestions[0].viability = SUGGESTION_RECOMMENDED;
    suggestions[0].filter = filter_thombas128_getinfo;
    if ( suggestions_length >= 2 ) {
      suggestions[1].viability = SUGGESTION_POSSIBLE;
      suggestions[1].filter = filter_thombas7_getinfo;
    }
    if ( suggestions_length >= 3 ) {
      suggestions[2].viability = SUGGESTION_POSSIBLE;
      suggestions[2].filter = filter_thombas5_getinfo;
    }
    if ( suggestions_length >= 4 ) {
      suggestions[3].viability = SUGGESTION_POSSIBLE;
      suggestions[3].filter = NULL;
    }
  }
  else {
    suggestions[0].viability = SUGGESTION_RECOMMENDED;
    suggestions[0].filter = NULL;
  }

  return IMGTOOLERR_SUCCESS;
}

static imgtoolerr_t thom_create(imgtool_image* img,
				imgtool_stream *stream,
				option_resolution *opts)
{
  thom_floppy* f = (thom_floppy*) imgtool_image_extra_bytes( img );
  int i;
  UINT8* buf;
  const char* name;

  f->stream = stream;
  f->modified = 0;

  /* get parameters */
  f->heads = option_resolution_lookup_int( opts, 'H' );
  f->tracks = option_resolution_lookup_int( opts, 'T' );
  name = option_resolution_lookup_string( opts, 'N' );
  switch ( option_resolution_lookup_int( opts, 'D' ) ) {
  case 0: f->sector_size = 128; f->sectuse_size = 128; break;
  case 1: f->sector_size = 256; f->sectuse_size = 255; break;
  default: return IMGTOOLERR_PARAMCORRUPT;
  }

  /* sanity checks */
  switch ( f->tracks ) {
  case 25: if ( f->sector_size != 128 ) return IMGTOOLERR_PARAMCORRUPT; break;
  case 40: break;
  case 80: if ( f->sector_size != 256 ) return IMGTOOLERR_PARAMCORRUPT; break;
  default: return IMGTOOLERR_PARAMCORRUPT;
  }

  memset( f->data, 0xe5, sizeof( f->data ) );

  for ( i = 0; i < f->heads; i++ ) {

    /* disk info */
    buf = thom_get_sector( f, i, 20, 1 );
    memset( buf, 0xff, f->sector_size );
    if ( name ) {
      strncpy( (char*)buf, name, 8 );
      thom_unstringity( (char*)buf, 8 );
    }
    else memset( buf, ' ', 8 );

    /* FAT */
    buf = thom_get_sector( f, i, 20, 2 );
    memset( buf, 0, f->sector_size );
    memset( buf + 1, 0xff, thom_nb_blocks( f ) );
    buf[ 41 ] = buf[ 42 ] = 0xfe; /* reserved for FAT */

    /* (empty) directory */
    buf = thom_get_sector( f, i, 20, 3 );
    memset( buf, 0xff, 14 * f->sector_size );
  }

  f->modified = 1;

  return IMGTOOLERR_SUCCESS;
}


/****************** BASIC tokenization *********************/

/* character codes >= 128 are reserved for BASIC keywords */

static const char *const thombas7[2][128] = {
  { /* statements */
    "END", "FOR", "NEXT", "DATA", "DIM", "READ", "LET", "GO", "RUN", "IF",
    "RESTORE", "RETURN", "REM", "'", "STOP", "ELSE", "TRON", "TROFF", "DEFSTR",
    "DEFINT", "DEFSNG", "DEFDBL", "ON", "WAIT", "ERROR", "RESUME", "AUTO",
    "DELETE", "LOCATE", "CLS", "CONSOLE", "PSET", "MOTOR", "SKIP", "EXEC",
    "BEEP", "COLOR", "LINE", "BOX", "UNMASK", "ATTRB", "DEF", "POKE", "PRINT",
    "CONT", "LIST", "CLEAR", "WHILE", "WHEN", "NEW", "SAVE", "LOAD", "MERGE",
    "OPEN", "CLOSE", "INPEN", "PEN", "PLAY", "TAB(", "TO", "SUB", "FN",
    "SPC(", "USING", "USR", "ERL", "ERR", "OFF", "THEN", "NOT", "STEP",
    "+", "-", "*", "/", "^", "AND", "OR", "XOR", "EQV", "IMP", "MOD", "@",
    ">", "=", "<",
    /* DOS specific */
    "DSKINI", "DSKO$", "KILL", "NAME", "FIELD", "LSET", "RSET",
    "PUT", "GET", "VERIFY", "DEVICE", "DIR", "FILES", "WRITE", "UNLOAD",
    "BACKUP", "COPY", "CIRCLE", "PAINT", "DRAW", "RENUM", "SWAP", "DENSITY",
  },
  { /* functions: 0xff prefix */
    "SGN", "INT", "ABS", "FRE", "SQR", "LOG", "EXP", "COS", "SIN",
    "TAN", "PEEK", "LEN", "STR$", "VAL", "ASC", "CHR$", "EOF", "CINT", "CSNG",
    "CDBL", "FIX", "HEX$", "OCT$", "STICK", "STRIG", "GR$", "LEFT$", "RIGHT$",
    "MID$", "INSTR", "VARPTR", "RND", "INKEY$", "INPUT", "CSRLIN", "POINT",
    "SCREEN", "POS", "PTRIG",
    /* DOS specific */
    "DSKL", "CVI", "CVS", "CVD", "MKI$", "MKS$", "MKD$", "LOC", "LOF",
    "SPACE$", "STRING$", "DSKI$",
  }
};

/* MO5: some keywords ar missing; DOS and TUNE are added */
static const char *const thombas5[2][128] = {
  { /* statements */
    "END", "FOR", "NEXT", "DATA", "DIM", "READ", NULL, "GO", "RUN", "IF",
    "RESTORE", "RETURN", "REM", "'", "STOP", "ELSE", "TRON", "TROFF", "DEFSTR",
    "DEFINT", "DEFSNG", NULL, "ON", "TUNE", "ERROR", "RESUME", "AUTO",
    "DELETE", "LOCATE", "CLS", "CONSOLE", "PSET", "MOTOR", "SKIP", "EXEC",
    "BEEP", "COLOR", "LINE", "BOX", NULL, "ATTRB", "DEF", "POKE", "PRINT",
    "CONT", "LIST", "CLEAR", "DOS", NULL, "NEW", "SAVE", "LOAD", "MERGE",
    "OPEN", "CLOSE", "INPEN", "PEN", "PLAY", "TAB(", "TO", "SUB", "FN",
    "SPC(", "USING", "USR", "ERL", "ERR", "OFF", "THEN", "NOT", "STEP",
    "+", "-", "*", "/", "^", "AND", "OR", "XOR", "EQV", "IMP", "MOD", "@",
    ">", "=", "<",
    /* DOS specific */
    "DSKINI", "DSKO$", "KILL", "NAME", "FIELD", "LSET", "RSET",
    "PUT", "GET", "VERIFY", "DEVICE", "DIR", "FILES", "WRITE", "UNLOAD",
    "BACKUP","COPY", "CIRCLE", "PAINT", "DRAW", "RENUM", "SWAP", "SEARCH",
    "DENSITY",
  },
  { /* functions: 0xff prefix */
    "SGN", "INT", "ABS", "FRE", "SQR", "LOG", "EXP", "COS", "SIN",
    "TAN", "PEEK", "LEN", "STR$", "VAL", "ASC", "CHR$", "EOF", "CINT", NULL,
    NULL, "FIX", "HEX$", NULL, "STICK", "STRIG", "GR$", "LEFT$", "RIGHT$",
    "MID$", "INSTR", "VARPTR", "RND", "INKEY$", "INPUT", "CSRLIN", "POINT",
    "SCREEN", "POS", "PTRIG",
    /* DOS specific */
    "DSKL", "CVI", "CVS", "CVD", "MKI$", "MKS$", "MKD$", "LOC", "LOF",
    "SPACE$", "STRING$", "DSKI$",
  }
};

/* BASIC 128 has many new keywords */
/* DENSITY is missing on BASIC 512 & BASIC 128 for MO6 but, otherwise, they
   have the same tokens
*/
static const char *const thombas128[2][128] = {
  { /* statements */
    "END", "FOR", "NEXT", "DATA", "DIM", "READ", "LET", "GO", "RUN", "IF",
    "RESTORE", "RETURN", "REM", "'", "STOP", "ELSE", "TRON", "TROFF", "DEFSTR",
    "DEFINT", "DEFSNG", "DEFDBL", "ON", "WAIT", "ERROR", "RESUME", "AUTO",
    "DELETE", "LOCATE", "CLS", "CONSOLE", "PSET", "MOTOR", "SKIP", "EXEC",
    "BEEP", "COLOR", "LINE", "BOX", "UNMASK", "ATTRB", "DEF", "POKE", "PRINT",
    "CONT", "LIST", "CLEAR", "INTERVAL", "KEY", "NEW", "SAVE", "LOAD", "MERGE",
    "OPEN", "CLOSE", "INPEN", "PEN", "PLAY", "TAB(", "TO", "SUB", "FN",
    "SPC(", "USING", "USR", "ERL", "ERR", "OFF", "THEN", "NOT", "STEP",
    "+", "-", "*", "/", "^", "AND", "OR", "XOR", "EQV", "IMP", "MOD", "@",
    ">", "=", "<",
    "DSKINI", "DSKO$", "KILL", "NAME", "FIELD", "LSET", "RSET",
    "PUT", "GET", "VERIFY", "DEVICE", "DIR", "FILES", "WRITE", "UNLOAD",
    "BACKUP", "COPY", "CIRCLE", "PAINT", "RESET", "RENUM", "SWAP", "DENSITY",
    "WINDOW", "PATTERN", "DO", "LOOP", "EXIT", "INMOUSE", "MOUSE", "CHAINE",
    "COMMON", "SEARCH", "FWD", "TURTLE",
  },
  { /* functions: 0xff prefix */
    "SGN", "INT", "ABS", "FRE", "SQR", "LOG", "EXP", "COS", "SIN",
    "TAN", "PEEK", "LEN", "STR$", "VAL", "ASC", "CHR$", "EOF", "CINT", "CSNG",
    "CDBL", "FIX", "HEX$", "OCT$", "STICK", "STRIG", "GR$", "LEFT$", "RIGHT$",
    "MID$", "INSTR", "VARPTR", "RND", "INKEY$", "INPUT", "CSRLIN", "POINT",
    "SCREEN", "POS", "PTRIG",
    "DSKL", "CVI", "CVS", "CVD", "MKI$", "MKS$", "MKD$", "LOC", "LOF",
    "SPACE$", "STRING$", "DSKI$",
    "FKEY$", "MIN(", "MAX(", "ATN", "CRUNCH$", "MTRIG", "EVAL", "PALETTE",
    "BANK", "HEAD", "ROT", "SHOW", "ZOOM", "TRACE",
  }
};

/* tables for simple XOR encryption sieve */
static const UINT8 crypt1[11] = {
  0x80, 0x19, 0x56, 0xAA, 0x80, 0x76, 0x22, 0xF1,  0x82, 0x38, 0xAA
};

static const UINT8 crypt2[13] = {
  0x86, 0x1E, 0xD7, 0xBA, 0x87, 0x99, 0x26, 0x64, 0x87, 0x23, 0x34, 0x58, 0x86
};

/* decrypt BASIC protected files */
static void thom_decrypt(imgtool_stream* out, imgtool_stream* in)
{
  int i1 = 11, i2 = 13;
  while ( 1 ) {
    UINT8 b;
    if ( stream_read( in, &b, 1) < 1 ) break;
    b = ( (UINT8)(b - i2) ^ crypt2[i2-1] ^ crypt1[i1-1] ) + i1;
    stream_putc( out, b );
    i1--; i2--;
    if ( !i1 ) i1 = 11;
    if ( !i2 ) i2 = 13;
  }
}

/* encrypt BASIC protected files */
static void thom_encrypt(imgtool_stream* out, imgtool_stream* in)
{
  int i1 = 11, i2 = 13;
  while ( 1 ) {
    UINT8 b;
    if ( stream_read( in, &b, 1) < 1 ) break;
    b = ( (UINT8)(b - i1) ^ crypt2[i2-1] ^ crypt1[i1-1] ) + i2;
    stream_putc( out, b );
    i1--; i2--;
    if ( !i1 ) i1 = 11;
    if ( !i2 ) i2 = 13;
  }
}

static imgtoolerr_t thomcrypt_read_file(imgtool_partition *part,
					const char *name,
					const char *fork, imgtool_stream *dst)
{
  UINT8 buf[3];
  imgtool_stream *org = stream_open_mem( NULL, 0 );
  imgtoolerr_t err;
  if ( !org ) return IMGTOOLERR_OUTOFMEMORY;

  /* read file */
  err = thom_read_file( part, name, fork, org );
  if ( err ) {
    stream_close( org );
    return err;
  }

  stream_seek( org, 0, SEEK_SET );
  if ( stream_read( org, buf, 3 ) < 3 || buf[0] != 0xfe ) {
    /* regular file */
    stream_seek( org, 0, SEEK_SET );
    stream_transfer_all( dst, org );
  }
  else {
    /* encrypted file */
    stream_putc( dst, 0xff );
    stream_write( dst, buf+1, 2 );
    thom_decrypt( dst, org );
  }

  stream_close( org );
  return IMGTOOLERR_SUCCESS;
}

static imgtoolerr_t thomcrypt_write_file(imgtool_partition *part,
					 const char *name,
					 const char *fork, imgtool_stream *src,
					 option_resolution *opts)
{
  UINT8 buf[3];

  if ( stream_read( src, buf, 3 ) < 3 || buf[0] == 0xfe ) {
    /* too short or already encrypted file */
    stream_seek( src, 0, SEEK_SET );
    return thom_write_file( part, name, fork, src, opts );
  }
  else {
    /* regular file */
    imgtool_stream *dst = stream_open_mem( NULL, 0 );
    imgtoolerr_t err;
    if ( !dst ) return IMGTOOLERR_OUTOFMEMORY;
    stream_putc( dst, 0xfe );
    stream_write( dst, buf+1, 2 );
    thom_encrypt( dst, src );
    stream_seek( dst, 0, SEEK_SET );
    err = thom_write_file( part, name, fork, dst, opts );
    stream_close( dst);
    return err;
  }
}

void filter_thomcrypt_getinfo(UINT32 state, union filterinfo *info)
{
  switch(state) {
  case FILTINFO_STR_NAME:
    info->s = "thomcrypt";
    break;
  case FILTINFO_STR_HUMANNAME:
    info->s = "Thomson BASIC, Protected file encryption (no tokenization)";
    break;
  case FILTINFO_PTR_READFILE:
    info->read_file = thomcrypt_read_file;
    break;
  case FILTINFO_PTR_WRITEFILE:
    info->write_file = thomcrypt_write_file;
    break;
  }
}

/* untokenization automatically decrypt protected files */
static imgtoolerr_t thom_basic_read_file(imgtool_partition *part,
					 const char *name,
					 const char *fork,
					 imgtool_stream *dst,
					 const char *const table[2][128])
{
  imgtool_stream *org = stream_open_mem( NULL, 0 );
  imgtoolerr_t err;
  UINT8 buf[4];
  int i;

  if ( !org ) return IMGTOOLERR_OUTOFMEMORY;

  err = thomcrypt_read_file( part, name, fork, org );
  if ( err ) {
    stream_close( org );
    return err;
  }
  stream_seek( org, 3, SEEK_SET ); /* skip header */

  while ( 1 ) {
    int in_str = 0, in_fun = 0;
    int linelength, linenum;

    /* line header: line length and line number */
    /* I am not sure this is 100% correct but it works in many cases */
    if ( stream_read( org, buf, 2 ) < 2 ) goto end;
    linelength = ((int)buf[0] << 8) + (int)buf[1] - 4;
    if ( linelength <= 0 ) goto end;
    if ( stream_read( org, buf, 2 ) < 2 ) goto end;
    linenum = ((int)buf[0] << 8) + buf[1];
    stream_printf( dst, "%u ", linenum );

    /* process line */
    for ( i = 0; i < linelength; i++ ) {
      UINT8 c;
      if ( stream_read( org, &c, 1 ) < 1 ) break;
      if ( c == 0 ) {
	/* Sometimes, linelength seems wrong and we must rely on the fact that
	   BASIC lines are 0-terminated.
	   At other times, there are 0 embedded within lines or extra stuff
	   between the 0 and the following line, and so, we must rely
	   on linelength to cut the line (!)
	*/
	if ( linelength > 256 ) break;
      }
      else if ( c == 0xff && ! in_str ) in_fun = 1; /* function prefix */
      else {
	if ( c >= 0x80 && ! in_str ) {
	  /* token */
	  const char* token = table[ in_fun ][ c - 0x80 ];
	  if ( token ) stream_puts( dst, token );
	  else stream_puts( dst, "???" );
	}
	else {
	  /* regular character */
	  if ( c == '"' ) in_str = 1 - in_str;
	  stream_putc( dst, c ); /* normal letter */
	}
	in_fun = 0;
      }
    }
    stream_putc( dst, '\n' );
  }
  end:

  stream_close( org );
  return IMGTOOLERR_SUCCESS;
}

static imgtoolerr_t thom_basic_write_file(imgtool_partition *part,
					 const char *name,
					 const char *fork,
					 imgtool_stream *src,
					 option_resolution *opts,
					 const char *const table[2][128])
{
  return IMGTOOLERR_UNIMPLEMENTED;
}


#define FILTER(short,long)						\
  static imgtoolerr_t short##_read_file(imgtool_partition *part,	\
					const char *name,		\
					const char *fork,		\
					imgtool_stream *dst)		\
  {									\
    return thom_basic_read_file( part, name, fork, dst, short );	\
  }									\
  static imgtoolerr_t short##_write_file(imgtool_partition *part,	\
					 const char *name,		\
					 const char *fork,		\
					 imgtool_stream *src,		\
					 option_resolution *opts)	\
  {									\
    return thom_basic_write_file( part, name, fork, src, opts, short );	\
  }									\
  void filter_##short##_getinfo(UINT32 state, union filterinfo *info)	\
  {									\
    switch(state)							\
      {									\
      case FILTINFO_STR_NAME:						\
	info->s = #short;						\
	break;								\
      case FILTINFO_STR_HUMANNAME:					\
	info->s = long;							\
	break;								\
      case FILTINFO_PTR_READFILE:					\
	info->read_file = short##_read_file;				\
	break;								\
      case FILTINFO_PTR_WRITEFILE:					\
	info->write_file = short##_write_file;				\
	break;								\
      }									\
  }

FILTER( thombas5,
	"Thomson MO5 w/ BASIC 1.0, Tokenized Files (read-only, auto-decrypt)" )
FILTER( thombas7,
	"Thomson TO7 w/ BASIC 1.0, Tokenized Files (read-only, auto-decrypt)" )
FILTER( thombas128,
	"Thomson w/ BASIC 128/512, Tokenized Files (read-only, auto-decrypt)" )


/************************* driver ***************************/

static OPTION_GUIDE_START( thom_createimage_optguide )
  OPTION_INT( 'H', "heads", "Heads" )
  OPTION_INT( 'T', "tracks", "Tracks" )
  OPTION_ENUM_START( 'D', "density", "Density" )
    OPTION_ENUM( 0, "SD", "Single density (128 bytes)" )
    OPTION_ENUM( 1, "DD", "Double density (256 bytes)" )
  OPTION_ENUM_END
  OPTION_STRING( 'N', "name", "Floppy name" )
OPTION_GUIDE_END

static OPTION_GUIDE_START( thom_writefile_optguide )
  OPTION_ENUM_START( 'T', "ftype", "File type" )
    OPTION_ENUM( 0, "auto", "Automatic (by extension)" )
    OPTION_ENUM( 1, "B", "Program" )
    OPTION_ENUM( 2, "D", "Data" )
    OPTION_ENUM( 3, "M", "Machine Code" )
    OPTION_ENUM( 4, "A",  "Assembler Source" )
  OPTION_ENUM_END
  OPTION_ENUM_START( 'F', "format", "Format flag" )
    OPTION_ENUM( 0, "auto",   "Automatic (by extension)" )
    OPTION_ENUM( 1, "B", "Binary, Tokenized BASIC" )
    OPTION_ENUM( 2, "A", "ASCII" )
  OPTION_ENUM_END
  OPTION_STRING( 'C', "comment", "Comment" )
OPTION_GUIDE_END

static void thom_basic_get_info(const imgtool_class *clas,
				UINT32 param,
				union imgtoolinfo *info)
{
  switch ( param ) {

  case IMGTOOLINFO_INT_IMAGE_EXTRA_BYTES:
    info->i = sizeof(thom_floppy); break;
  case IMGTOOLINFO_INT_PARTITION_EXTRA_BYTES:
    info->i = sizeof(int); break;
  case IMGTOOLINFO_INT_DIRECTORY_EXTRA_BYTES:
    info->i = sizeof(int); break;
  case IMGTOOLINFO_INT_SUPPORTS_CREATION_TIME:
  case IMGTOOLINFO_INT_PREFER_UCASE:
    info->i = 1; break;
  case IMGTOOLINFO_INT_PATH_SEPARATOR:
    info->i = 0; break;
  case IMGTOOLINFO_STR_FILE:
    strcpy( info->s = imgtool_temp_str(), __FILE__ ); break;
  case IMGTOOLINFO_STR_NAME:
    strcpy( info->s = imgtool_temp_str(), "thom" ); break;
  case IMGTOOLINFO_STR_DESCRIPTION:
    strcpy( info->s = imgtool_temp_str(), "Thomson BASIC filesystem" );
    break;
  case IMGTOOLINFO_PTR_CREATE:
    info->create = thom_create; break;
  case IMGTOOLINFO_PTR_CREATEIMAGE_OPTGUIDE:
    info->createimage_optguide = thom_createimage_optguide; break;
  case IMGTOOLINFO_PTR_BEGIN_ENUM:
    info->begin_enum = thom_begin_enum; break;
  case IMGTOOLINFO_PTR_NEXT_ENUM:
    info->next_enum = thom_next_enum; break;
  case IMGTOOLINFO_PTR_READ_FILE:
    info->read_file = thom_read_file; break;
  case IMGTOOLINFO_PTR_WRITE_FILE:
    info->write_file = thom_write_file; break;
  case IMGTOOLINFO_PTR_WRITEFILE_OPTGUIDE:
    info->writefile_optguide = thom_writefile_optguide; break;
  case IMGTOOLINFO_STR_WRITEFILE_OPTSPEC:
    strcpy( info->s = imgtool_temp_str(), "T[0]-4;F[0]-2;C" ); break;
  case IMGTOOLINFO_PTR_SUGGEST_TRANSFER:
    info->suggest_transfer = thom_suggest_transfer; break;
  case IMGTOOLINFO_PTR_DELETE_FILE:
    info->delete_file = thom_delete_file; break;
  case IMGTOOLINFO_PTR_FREE_SPACE:
    info->free_space = thom_free_space; break;
  case IMGTOOLINFO_PTR_GET_SECTOR_SIZE:
    info->get_sector_size = thom_get_sector_size; break;
  case IMGTOOLINFO_PTR_GET_GEOMETRY:
    info->get_geometry = thom_get_geometry; break;
  case IMGTOOLINFO_PTR_INFO:
    info->info = thom_info; break;
  case IMGTOOLINFO_PTR_READ_SECTOR:
    info->read_sector = thom_read_sector; break;
  case IMGTOOLINFO_PTR_WRITE_SECTOR:
    info->write_sector = thom_write_sector; break;
  case IMGTOOLINFO_PTR_LIST_PARTITIONS:
    info->list_partitions = thom_list_partitions; break;
  case IMGTOOLINFO_PTR_OPEN_PARTITION:
    info->open_partition = thom_open_partition; break;
  }
}

void thom_fd_basic_get_info(const imgtool_class *clas,
			    UINT32 param,
			    union imgtoolinfo *info)
{
  switch ( param ) {
  case IMGTOOLINFO_STR_NAME:
    strcpy( info->s = imgtool_temp_str(), "thom_fd" ); break;
  case IMGTOOLINFO_STR_DESCRIPTION:
    strcpy( info->s = imgtool_temp_str(),
	    "Thomson .fd disk image, BASIC format" );
    break;
  case IMGTOOLINFO_STR_FILE_EXTENSIONS:
    strcpy( info->s = imgtool_temp_str(), "fd" ); break;
  case IMGTOOLINFO_PTR_OPEN:
    info->open = thom_open_fd_qd; break;
  case IMGTOOLINFO_PTR_CLOSE:
    info->close = thom_close_fd_qd; break;
  case IMGTOOLINFO_STR_CREATEIMAGE_OPTSPEC:
    strcpy( info->s = imgtool_temp_str(), "H[1]-2;T40/[80];D0-[1];N" ); break;
  default:
    thom_basic_get_info( clas, param, info );
  }
}

void thom_qd_basic_get_info(const imgtool_class *clas,
			    UINT32 param,
			    union imgtoolinfo *info)
{
  switch ( param ) {
  case IMGTOOLINFO_STR_NAME:
    strcpy( info->s = imgtool_temp_str(), "thom_qd" ); break;
  case IMGTOOLINFO_STR_DESCRIPTION:
    strcpy( info->s = imgtool_temp_str(),
	    "Thomson .qd disk image, BASIC format" );
    break;
  case IMGTOOLINFO_STR_FILE_EXTENSIONS:
    strcpy( info->s = imgtool_temp_str(), "qd" ); break;
  case IMGTOOLINFO_PTR_OPEN:
    info->open = thom_open_fd_qd; break;
  case IMGTOOLINFO_PTR_CLOSE:
    info->close = thom_close_fd_qd; break;
  case IMGTOOLINFO_STR_CREATEIMAGE_OPTSPEC:
    strcpy( info->s = imgtool_temp_str(), "H[1]-2;T[25];D[0];N" ); break;
  default:
    thom_basic_get_info( clas, param, info );
  }
}

void thom_sap_basic_get_info(const imgtool_class *clas,
			     UINT32 param,
			     union imgtoolinfo *info)
{
  switch ( param ) {
  case IMGTOOLINFO_STR_NAME:
    strcpy( info->s = imgtool_temp_str(), "thom_sap" ); break;
  case IMGTOOLINFO_STR_DESCRIPTION:
    strcpy( info->s = imgtool_temp_str(),
	    "Thomson .sap disk image, BASIC format" );
    break;
  case IMGTOOLINFO_STR_FILE_EXTENSIONS:
    strcpy( info->s = imgtool_temp_str(), "sap" ); break;
  case IMGTOOLINFO_PTR_OPEN:
    info->open = thom_open_sap; break;
  case IMGTOOLINFO_PTR_CLOSE:
    info->close = thom_close_sap; break;
  case IMGTOOLINFO_STR_CREATEIMAGE_OPTSPEC:
    strcpy( info->s = imgtool_temp_str(), "H[1];T40/[80];D0-[1];N" ); break;
  default:
    thom_basic_get_info( clas, param, info );
  }
}
