/* microdrive.c: Routines for handling microdrive images Copyright (c) 2004-2005 Philip Kendall $Id: microdrive.c,v 1.6 2007/02/02 16:35:43 pak21 Exp $ This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Author contact information: E-mail: philip-fuse@shadowmagic.org.uk */ #include #include #include "internals.h" /* The type itself */ struct libspectrum_microdrive { libspectrum_byte data[ LIBSPECTRUM_MICRODRIVE_CARTRIDGE_LENGTH ]; int write_protect; libspectrum_byte cartridge_len; }; typedef struct libspectrum_microdrive_block { libspectrum_byte hdflag; /* bit0 = 1-head, ( 0 - data ) */ libspectrum_byte hdbnum; /* block num 1 -- 254 */ libspectrum_word hdblen; /* not used */ libspectrum_byte hdbnam[11]; /* cartridge label + \0 */ libspectrum_byte hdchks; /* header checksum */ libspectrum_byte recflg; /* bit0 = 0-data, bit1, bit2 */ libspectrum_byte recnum; /* data block num */ libspectrum_word reclen; /* block length 0 -- 512 */ libspectrum_byte recnam[11]; /* record (file) name + \0 */ libspectrum_byte rechks; /* descriptor checksum */ libspectrum_byte data[512]; /* data bytes */ libspectrum_byte datchk; /* data checksum */ } libspectrum_microdrive_block; const static size_t MDR_LENGTH = LIBSPECTRUM_MICRODRIVE_CARTRIDGE_LENGTH + 1; /* Constructor/destructor */ /* Allocate a microdrive image */ libspectrum_error libspectrum_microdrive_alloc( libspectrum_microdrive **microdrive ) { *microdrive = malloc( sizeof( **microdrive ) ); if( !*microdrive ) { libspectrum_print_error( LIBSPECTRUM_ERROR_MEMORY, "libspectrum_microdrive_alloc: out of memory at %s:%d", __FILE__, __LINE__ ); return LIBSPECTRUM_ERROR_MEMORY; } return LIBSPECTRUM_ERROR_NONE; } /* Free a microdrive image */ libspectrum_error libspectrum_microdrive_free( libspectrum_microdrive *microdrive ) { free( microdrive ); return LIBSPECTRUM_ERROR_NONE; } /* Accessors */ libspectrum_byte libspectrum_microdrive_data( const libspectrum_microdrive *microdrive, size_t which ) { return microdrive->data[ which ]; } void libspectrum_microdrive_set_data( libspectrum_microdrive *microdrive, size_t which, libspectrum_byte data ) { microdrive->data[ which ] = data; } int libspectrum_microdrive_write_protect( const libspectrum_microdrive *microdrive ) { return microdrive->write_protect; } void libspectrum_microdrive_set_write_protect( libspectrum_microdrive *microdrive, int write_protect ) { microdrive->write_protect = write_protect; } libspectrum_byte libspectrum_microdrive_cartridge_len( const libspectrum_microdrive *microdrive ) { return microdrive->cartridge_len; } void libspectrum_microdrive_set_cartridge_len( libspectrum_microdrive *microdrive, libspectrum_byte len ) { microdrive->cartridge_len = len; } void libspectrum_microdrive_get_block( const libspectrum_microdrive *microdrive, libspectrum_byte which, libspectrum_microdrive_block *block ) { const libspectrum_byte *d; d = microdrive->data + which * LIBSPECTRUM_MICRODRIVE_BLOCK_LEN; /* The block header */ block->hdflag = *(d++); block->hdbnum = *(d++); block->hdblen = *d + ( *( d + 1 ) << 8 ); d += 2; memcpy( block->hdbnam, d, 10 ); d += 10; block->hdbnam[10] = '\0'; block->hdchks = *(d++); /* The data header */ block->recflg = *(d++); block->recnum = *(d++); block->reclen = *d + ( *( d + 1 ) << 8 ); d += 2; memcpy( block->recnam, d, 10 ); d += 10; block->recnam[10] = '\0'; block->rechks = *(d++); /* The data itself */ memcpy( block->data, d, 512 ); d += 512; block->datchk = *(d++); } void libspectrum_microdrive_set_block( libspectrum_microdrive *microdrive, libspectrum_byte which, libspectrum_microdrive_block *block ) { libspectrum_byte *d = µdrive->data[ which * LIBSPECTRUM_MICRODRIVE_BLOCK_LEN ]; /* The block header */ *(d++) = block->hdflag; *(d++) = block->hdbnum; *(d++) = block->hdblen & 0xff; *(d++) = block->hdblen >> 8; memcpy( d, block->hdbnam, 10 ); d += 10; *(d++) = block->hdchks; /* The data header */ *(d++) = block->recflg; *(d++) = block->recnum; *(d++) = block->reclen & 0xff; *(d++) = block->reclen >> 8; memcpy( d, block->recnam, 10 ); d += 10; *(d++) = block->rechks; /* The data itself */ memcpy( d, block->data, 512 ); d += 512; *(d++) = block->datchk; } int libspectrum_microdrive_checksum( libspectrum_microdrive *microdrive, libspectrum_byte what ) { libspectrum_byte *data; unsigned int checksum, carry; int i; #define DO_CHECK \ checksum += *data; /* LD A,E */ \ /* ADD A, (HL) */ \ if( checksum > 255 ) { /* ... ADD A, (HL) --> CY*/ \ carry = 1; \ checksum -= 256; \ } else { \ carry = 0; \ } \ data++; /* INC HL */ \ checksum += carry + 1; /* ADC A, #01 */ \ if( checksum == 256 ) { /* JR Z,LSTCHK */ \ checksum -= 256; /* LD E,A */\ } else { \ checksum--; /* DEC A */ \ } /* LOOP-C: LD A,E ; fetch running sum ADD A,(HL) ; add to current location. INC HL ; point to next location. ADC A,#01 ; avoid the value #FF. JR Z,LSTCHK ; forward to STCHK DEC A ; decrement. LSTCHK LD E,A ; update the 8-bit sum. */ data = &(microdrive->data[ what * LIBSPECTRUM_MICRODRIVE_BLOCK_LEN ]); if( ( *( data + 15 ) & 2 ) && ( *( data + 17 ) == 0 ) && ( *( data + 18 ) == 0 ) ) { return -1; /* PRESET BAD BLOCK */ } checksum = 0; /* Block header */ for( i = LIBSPECTRUM_MICRODRIVE_HEAD_LEN; i > 1; i-- ) { DO_CHECK; } if( *(data++) != checksum ) return 1; checksum = 0; /* Record header */ for( i = LIBSPECTRUM_MICRODRIVE_HEAD_LEN; i > 1; i-- ) { DO_CHECK; } if( *(data++) != checksum ) return 2; if( ( *( data - 13 ) == 0 ) && ( *( data - 12 ) == 0 ) ) { return 0; /* Erased / empty block: data checksum irrelevant */ } checksum = 0; /* Data */ for( i = LIBSPECTRUM_MICRODRIVE_DATA_LEN; i > 0; i-- ) { DO_CHECK; } if( *data != checksum ) return 3; return 0; } /* .mdr format routines */ libspectrum_error libspectrum_microdrive_mdr_read( libspectrum_microdrive *microdrive, libspectrum_byte *buffer, size_t length ) { libspectrum_microdrive_block b; libspectrum_byte label[10]; libspectrum_byte n; int e, nolabel; if( length < LIBSPECTRUM_MICRODRIVE_BLOCK_LEN * 10 || ( length % LIBSPECTRUM_MICRODRIVE_BLOCK_LEN ) > 1 ) { libspectrum_print_error( LIBSPECTRUM_ERROR_CORRUPT, "libspectrum_microdrive_mdr_read: not enough data in buffer" ); return LIBSPECTRUM_ERROR_CORRUPT; } length = length > MDR_LENGTH ? MDR_LENGTH : length; memcpy( microdrive->data, buffer, length ); buffer += length; if( ( length % LIBSPECTRUM_MICRODRIVE_BLOCK_LEN ) == 1 ) libspectrum_microdrive_set_write_protect( microdrive, *buffer ); else libspectrum_microdrive_set_write_protect( microdrive, 0 ); libspectrum_microdrive_set_cartridge_len( microdrive, length / LIBSPECTRUM_MICRODRIVE_BLOCK_LEN ); n = libspectrum_microdrive_cartridge_len( microdrive ); nolabel = 1; /* No real label ! */ while( n > 0 ) { n--; if( ( e = libspectrum_microdrive_checksum( microdrive, n ) ) > 0 ) { libspectrum_print_error( LIBSPECTRUM_ERROR_CORRUPT, "libspectrum_microdrive_mdr_read: %s checksum error in #%d record", e == 1 ? "record header" : e == 2 ? "data header" : "data", n ); return LIBSPECTRUM_ERROR_CORRUPT; } libspectrum_microdrive_get_block( microdrive, 0, &b ); if( !nolabel && memcmp( label, b.hdbnam, 10 ) ) { libspectrum_print_error( LIBSPECTRUM_ERROR_CORRUPT, "libspectrum_microdrive_mdr_read: inconsistent labels in #%d record", n ); return LIBSPECTRUM_ERROR_CORRUPT; } if( e == 0 && nolabel ) { memcpy( label, b.hdbnam, 10 ); nolabel = 0; } } return LIBSPECTRUM_ERROR_NONE; } libspectrum_error libspectrum_microdrive_mdr_write( const libspectrum_microdrive *microdrive, libspectrum_byte **buffer, size_t *length ) { *buffer = malloc( *length = microdrive->cartridge_len * LIBSPECTRUM_MICRODRIVE_BLOCK_LEN + 1 ); if( !*buffer ) { libspectrum_print_error( LIBSPECTRUM_ERROR_MEMORY, "libspectrum_microdrive_mdr_write: out of memory at %s:%d", __FILE__, __LINE__ ); return LIBSPECTRUM_ERROR_MEMORY; } memcpy( *buffer, microdrive->data, *length ); (*buffer)[ *length ] = microdrive->write_protect; return LIBSPECTRUM_ERROR_NONE; }