summaryrefslogtreecommitdiffstatshomepage
path: root/src/mame/tools/imgtool/modules/thomson.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/mame/tools/imgtool/modules/thomson.c')
-rw-r--r--src/mame/tools/imgtool/modules/thomson.c1681
1 files changed, 1681 insertions, 0 deletions
diff --git a/src/mame/tools/imgtool/modules/thomson.c b/src/mame/tools/imgtool/modules/thomson.c
new file mode 100644
index 00000000000..43684bc0aea
--- /dev/null
+++ b/src/mame/tools/imgtool/modules/thomson.c
@@ -0,0 +1,1681 @@
+// license:BSD-3-Clause
+// copyright-holders:Antoine Mine
+/****************************************************************************
+
+ 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 <time.h>
+
+
+#define MAXSIZE 80*16*256*2 /* room for two faces, double-density, 80 tracks */
+
+struct thom_floppy {
+ 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 */
+
+};
+
+
+enum thom_dirent_type {
+ THOM_DIRENT_END,
+ THOM_DIRENT_FREE,
+ THOM_DIRENT_FILE,
+ THOM_DIRENT_INVALID,
+
+};
+
+
+struct thom_dirent {
+ 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;
+
+};
+
+
+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, int ddam)
+{
+ 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 ( ! core_stricmp( ext, "BAS" ) ) d.ftype = 0;
+ else if ( ! core_stricmp( ext, "BAT" ) ) d.ftype = 0;
+ else if ( ! core_stricmp( ext, "DAT" ) ) d.ftype = 1;
+ else if ( ! core_stricmp( ext, "ASC" ) ) d.ftype = 1;
+ else if ( ! core_stricmp( ext, "BIN" ) ) d.ftype = 2;
+ else if ( ! core_stricmp( ext, "MAP" ) ) d.ftype = 2;
+ else if ( ! core_stricmp( ext, "CFG" ) ) d.ftype = 2;
+ else if ( ! core_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 ( ! core_stricmp( ext, "DAT" ) ) d.format = 0xff;
+ else if ( ! core_stricmp( ext, "ASC" ) ) d.format = 0xff;
+ else if ( ! core_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, '\xff' );
+ 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, '\xfe' );
+ 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 );
+ }
+}