/* tape.c: Routines for handling tape files Copyright (c) 2001-2007 Philip Kendall, Darren Salt $Id: tape.c,v 1.73 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 #include "internals.h" #include "tape_block.h" /* The tape type itself */ struct libspectrum_tape { /* All the blocks */ GSList* blocks; /* The current block */ GSList* current_block; /* Where to return to after a loop, and how many iterations of the loop to do */ GSList* loop_block; size_t loop_count; }; /*** Constants ***/ /* The timings for the standard ROM loader */ static const libspectrum_dword LIBSPECTRUM_TAPE_TIMING_PILOT = 2168; /*Pilot*/ static const libspectrum_dword LIBSPECTRUM_TAPE_TIMING_SYNC1 = 667; /*Sync 1*/ static const libspectrum_dword LIBSPECTRUM_TAPE_TIMING_SYNC2 = 735; /*Sync 2*/ static const libspectrum_dword LIBSPECTRUM_TAPE_TIMING_DATA0 = 855; /*Reset*/ static const libspectrum_dword LIBSPECTRUM_TAPE_TIMING_DATA1 = 1710; /*Set*/ /*** Local function prototypes ***/ /* Free the memory used by a block */ static void block_free( gpointer data, gpointer user_data ); /* Functions to get the next edge */ static libspectrum_error rom_edge( libspectrum_tape_rom_block *block, libspectrum_dword *tstates, int *end_of_block ); static libspectrum_error rom_next_bit( libspectrum_tape_rom_block *block ); static libspectrum_error turbo_edge( libspectrum_tape_turbo_block *block, libspectrum_dword *tstates, int *end_of_block ); static libspectrum_error turbo_next_bit( libspectrum_tape_turbo_block *block ); static libspectrum_error tone_edge( libspectrum_tape_pure_tone_block *block, libspectrum_dword *tstates, int *end_of_block ); static libspectrum_error pulses_edge( libspectrum_tape_pulses_block *block, libspectrum_dword *tstates, int *end_of_block ); static libspectrum_error pure_data_edge( libspectrum_tape_pure_data_block *block, libspectrum_dword *tstates, int *end_of_block ); static libspectrum_error raw_data_edge( libspectrum_tape_raw_data_block *block, libspectrum_dword *tstates, int *end_of_block ); static libspectrum_error generalised_data_edge( libspectrum_tape_generalised_data_block *block, libspectrum_dword *tstates, int *end_of_block, int *flags ); static libspectrum_error jump_blocks( libspectrum_tape *tape, int offset ); static libspectrum_error rle_pulse_edge( libspectrum_tape_rle_pulse_block *block, libspectrum_dword *tstates, int *end_of_block ); /*** Function definitions ****/ /* Allocate a list of blocks */ libspectrum_error libspectrum_tape_alloc( libspectrum_tape **tape ) { (*tape) = (libspectrum_tape*)malloc( sizeof( libspectrum_tape ) ); if( !(*tape) ) { libspectrum_print_error( LIBSPECTRUM_ERROR_MEMORY, "libspectrum_tape_alloc: out of memory" ); return LIBSPECTRUM_ERROR_MEMORY; } (*tape)->current_block = (*tape)->blocks = NULL; return LIBSPECTRUM_ERROR_NONE; } /* Free the memory used by a list of blocks, but not the object itself */ libspectrum_error libspectrum_tape_clear( libspectrum_tape *tape ) { g_slist_foreach( tape->blocks, block_free, NULL ); g_slist_free( tape->blocks ); tape->current_block = tape->blocks = NULL; return LIBSPECTRUM_ERROR_NONE; } /* Free up a list of blocks */ libspectrum_error libspectrum_tape_free( libspectrum_tape *tape ) { libspectrum_error error; error = libspectrum_tape_clear( tape ); if( error ) return error; free( tape ); return LIBSPECTRUM_ERROR_NONE; } /* Free the memory used by one block */ static void block_free( gpointer data, gpointer user_data GCC_UNUSED ) { libspectrum_tape_block_free( data ); } /* Read in a tape file, optionally guessing what sort of file it is */ libspectrum_error libspectrum_tape_read( libspectrum_tape *tape, const libspectrum_byte *buffer, size_t length, libspectrum_id_t type, const char *filename ) { libspectrum_id_t raw_type; libspectrum_class_t class; libspectrum_byte *new_buffer; libspectrum_error error; int uncompressed; /* If we don't know what sort of file this is, make a best guess */ if( type == LIBSPECTRUM_ID_UNKNOWN ) { error = libspectrum_identify_file( &type, filename, buffer, length ); if( error ) return error; /* If we still can't identify it, give up */ if( type == LIBSPECTRUM_ID_UNKNOWN ) { libspectrum_print_error( LIBSPECTRUM_ERROR_UNKNOWN, "libspectrum_tape_read: couldn't identify file" ); return LIBSPECTRUM_ERROR_UNKNOWN; } } /* Find out if this file needs decompression */ uncompressed = 0; new_buffer = NULL; error = libspectrum_identify_file_raw( &raw_type, filename, buffer, length ); if( error ) return error; error = libspectrum_identify_class( &class, raw_type ); if( error ) return error; if( class == LIBSPECTRUM_CLASS_COMPRESSED ) { size_t new_length; error = libspectrum_uncompress_file( &new_buffer, &new_length, NULL, raw_type, buffer, length, NULL ); buffer = new_buffer; length = new_length; uncompressed = 1; } switch( type ) { case LIBSPECTRUM_ID_TAPE_TAP: error = libspectrum_tap_read( tape, buffer, length ); break; case LIBSPECTRUM_ID_TAPE_TZX: error = libspectrum_tzx_read( tape, buffer, length ); break; case LIBSPECTRUM_ID_TAPE_WARAJEVO: error = libspectrum_warajevo_read( tape, buffer, length ); break; case LIBSPECTRUM_ID_TAPE_Z80EM: error = libspectrum_z80em_read( tape, buffer, length ); break; case LIBSPECTRUM_ID_TAPE_CSW: error = libspectrum_csw_read( tape, buffer, length ); break; default: libspectrum_print_error( LIBSPECTRUM_ERROR_CORRUPT, "libspectrum_tape_read: not a tape file" ); free( new_buffer ); return LIBSPECTRUM_ERROR_CORRUPT; } free( new_buffer ); return error; } libspectrum_error libspectrum_tape_write( libspectrum_byte **buffer, size_t *length, libspectrum_tape *tape, libspectrum_id_t type ) { libspectrum_class_t class; libspectrum_error error; error = libspectrum_identify_class( &class, type ); if( error ) return error; if( class != LIBSPECTRUM_CLASS_TAPE ) { libspectrum_print_error( LIBSPECTRUM_ERROR_INVALID, "libspectrum_tape_write: not a tape format" ); return LIBSPECTRUM_ERROR_INVALID; } switch( type ) { case LIBSPECTRUM_ID_TAPE_TAP: return libspectrum_tap_write( buffer, length, tape ); case LIBSPECTRUM_ID_TAPE_TZX: return libspectrum_tzx_write( buffer, length, tape ); default: libspectrum_print_error( LIBSPECTRUM_ERROR_UNKNOWN, "libspectrum_tape_write: format not supported" ); return LIBSPECTRUM_ERROR_UNKNOWN; } } /* Does this tape structure actually contain a tape? */ int libspectrum_tape_present( const libspectrum_tape *tape ) { return tape->blocks != NULL; } /* Some flags which may be set after calling libspectrum_tape_get_next_edge */ const int LIBSPECTRUM_TAPE_FLAGS_BLOCK = 1 << 0; /* End of block */ const int LIBSPECTRUM_TAPE_FLAGS_STOP = 1 << 1; /* Stop tape */ const int LIBSPECTRUM_TAPE_FLAGS_STOP48 = 1 << 2; /* Stop tape if in 48K mode */ const int LIBSPECTRUM_TAPE_FLAGS_NO_EDGE = 1 << 3; /* Not an edge really */ const int LIBSPECTRUM_TAPE_FLAGS_LEVEL_LOW = 1 << 4; /* Set level low */ const int LIBSPECTRUM_TAPE_FLAGS_LEVEL_HIGH = 1 << 5; /* Set level high */ /* The main function: called with a tape object and returns the number of t-states until the next edge, and a marker if this was the last edge on the tape */ libspectrum_error libspectrum_tape_get_next_edge( libspectrum_dword *tstates, int *flags, libspectrum_tape *tape ) { int error; libspectrum_tape_block *block = (libspectrum_tape_block*)tape->current_block->data; /* Has this edge ended the block? */ int end_of_block = 0; /* After getting a new block, do we want to advance to the next one? */ int no_advance = 0; /* Assume no special flags by default */ *flags = 0; switch( block->type ) { case LIBSPECTRUM_TAPE_BLOCK_ROM: error = rom_edge( &(block->types.rom), tstates, &end_of_block ); if( error ) return error; break; case LIBSPECTRUM_TAPE_BLOCK_TURBO: error = turbo_edge( &(block->types.turbo), tstates, &end_of_block ); if( error ) return error; break; case LIBSPECTRUM_TAPE_BLOCK_PURE_TONE: error = tone_edge( &(block->types.pure_tone), tstates, &end_of_block ); if( error ) return error; break; case LIBSPECTRUM_TAPE_BLOCK_PULSES: error = pulses_edge( &(block->types.pulses), tstates, &end_of_block ); if( error ) return error; break; case LIBSPECTRUM_TAPE_BLOCK_PURE_DATA: error = pure_data_edge( &(block->types.pure_data), tstates, &end_of_block); if( error ) return error; break; case LIBSPECTRUM_TAPE_BLOCK_RAW_DATA: error = raw_data_edge( &(block->types.raw_data), tstates, &end_of_block ); if( error ) return error; break; case LIBSPECTRUM_TAPE_BLOCK_GENERALISED_DATA: error = generalised_data_edge( &(block->types.generalised_data), tstates, &end_of_block, flags ); if( error ) return error; break; case LIBSPECTRUM_TAPE_BLOCK_PAUSE: *tstates = ( block->types.pause.length * 69888 ) / 20; end_of_block = 1; /* 0 ms pause => stop tape */ if( *tstates == 0 ) { *flags |= LIBSPECTRUM_TAPE_FLAGS_STOP; } break; case LIBSPECTRUM_TAPE_BLOCK_JUMP: error = jump_blocks( tape, block->types.jump.offset ); if( error ) return error; *tstates = 0; end_of_block = 1; no_advance = 1; break; case LIBSPECTRUM_TAPE_BLOCK_LOOP_START: tape->loop_block = tape->current_block->next; tape->loop_count = block->types.loop_start.count; *tstates = 0; end_of_block = 1; break; case LIBSPECTRUM_TAPE_BLOCK_LOOP_END: if( --tape->loop_count ) { tape->current_block = tape->loop_block; no_advance = 1; } *tstates = 0; end_of_block = 1; break; case LIBSPECTRUM_TAPE_BLOCK_STOP48: *tstates = 0; *flags |= LIBSPECTRUM_TAPE_FLAGS_STOP48; end_of_block = 1; break; /* For blocks which contain no Spectrum-readable data, return zero tstates and set end of block set so we instantly get the next block */ case LIBSPECTRUM_TAPE_BLOCK_GROUP_START: case LIBSPECTRUM_TAPE_BLOCK_GROUP_END: case LIBSPECTRUM_TAPE_BLOCK_SELECT: case LIBSPECTRUM_TAPE_BLOCK_COMMENT: case LIBSPECTRUM_TAPE_BLOCK_MESSAGE: case LIBSPECTRUM_TAPE_BLOCK_ARCHIVE_INFO: case LIBSPECTRUM_TAPE_BLOCK_HARDWARE: case LIBSPECTRUM_TAPE_BLOCK_CUSTOM: *tstates = 0; end_of_block = 1; break; case LIBSPECTRUM_TAPE_BLOCK_RLE_PULSE: error = rle_pulse_edge( &(block->types.rle_pulse), tstates, &end_of_block); if( error ) return error; break; default: *tstates = 0; libspectrum_print_error( LIBSPECTRUM_ERROR_LOGIC, "libspectrum_tape_get_next_edge: unknown block type 0x%02x", block->type ); return LIBSPECTRUM_ERROR_LOGIC; } /* If that ended the block, move onto the next block */ if( end_of_block ) { *flags |= LIBSPECTRUM_TAPE_FLAGS_BLOCK; /* Advance to the next block, unless we've been told not to */ if( !no_advance ) { tape->current_block = tape->current_block->next; /* If we've just hit the end of the tape, stop the tape (and then `rewind' to the start) */ if( tape->current_block == NULL ) { *flags |= LIBSPECTRUM_TAPE_FLAGS_STOP; tape->current_block = tape->blocks; } } /* Initialise the new block */ error = libspectrum_tape_block_init( tape->current_block->data ); if( error ) return error; } return LIBSPECTRUM_ERROR_NONE; } static libspectrum_error rom_edge( libspectrum_tape_rom_block *block, libspectrum_dword *tstates, int *end_of_block ) { int error; switch( block->state ) { case LIBSPECTRUM_TAPE_STATE_PILOT: /* The next edge occurs in one pilot edge timing */ *tstates = LIBSPECTRUM_TAPE_TIMING_PILOT; /* If that was the last pilot edge, change state */ if( --(block->edge_count) == 0 ) block->state = LIBSPECTRUM_TAPE_STATE_SYNC1; break; case LIBSPECTRUM_TAPE_STATE_SYNC1: /* The first short sync pulse */ *tstates = LIBSPECTRUM_TAPE_TIMING_SYNC1; /* Followed by the second sync pulse */ block->state = LIBSPECTRUM_TAPE_STATE_SYNC2; break; case LIBSPECTRUM_TAPE_STATE_SYNC2: /* The second short sync pulse */ *tstates = LIBSPECTRUM_TAPE_TIMING_SYNC2; /* Followed by the first bit of data */ error = rom_next_bit( block ); if( error ) return error; break; case LIBSPECTRUM_TAPE_STATE_DATA1: /* The first edge for a bit of data */ *tstates = block->bit_tstates; /* Followed by the second edge */ block->state = LIBSPECTRUM_TAPE_STATE_DATA2; break; case LIBSPECTRUM_TAPE_STATE_DATA2: /* The second edge for a bit of data */ *tstates = block->bit_tstates; /* Followed by the next bit of data (or the end of data) */ error = rom_next_bit( block ); if( error ) return error; break; case LIBSPECTRUM_TAPE_STATE_PAUSE: /* The pause at the end of the block */ *tstates = (block->pause * 69888)/20; /* FIXME: should vary with tstates per frame */ *end_of_block = 1; break; default: libspectrum_print_error( LIBSPECTRUM_ERROR_LOGIC, "rom_edge: unknown state %d", block->state ); return LIBSPECTRUM_ERROR_LOGIC; } return LIBSPECTRUM_ERROR_NONE; } static libspectrum_error rom_next_bit( libspectrum_tape_rom_block *block ) { int next_bit; /* Have we finished the current byte? */ if( ++(block->bits_through_byte) == 8 ) { /* If so, have we finished the entire block? If so, all we've got left after this is the pause at the end */ if( ++(block->bytes_through_block) == block->length ) { block->state = LIBSPECTRUM_TAPE_STATE_PAUSE; return LIBSPECTRUM_ERROR_NONE; } /* If we've finished the current byte, but not the entire block, get the next byte */ block->current_byte = block->data[ block->bytes_through_block ]; block->bits_through_byte = 0; } /* Get the high bit, and shift the byte out leftwards */ next_bit = block->current_byte & 0x80; block->current_byte <<= 1; /* And set the timing and state for another data bit */ block->bit_tstates = ( next_bit ? LIBSPECTRUM_TAPE_TIMING_DATA1 : LIBSPECTRUM_TAPE_TIMING_DATA0 ); block->state = LIBSPECTRUM_TAPE_STATE_DATA1; return LIBSPECTRUM_ERROR_NONE; } static libspectrum_error turbo_edge( libspectrum_tape_turbo_block *block, libspectrum_dword *tstates, int *end_of_block ) { int error; switch( block->state ) { case LIBSPECTRUM_TAPE_STATE_PILOT: /* The next edge occurs in one pilot edge timing */ *tstates = block->pilot_length; /* If that was the last pilot edge, change state */ if( --(block->edge_count) == 0 ) block->state = LIBSPECTRUM_TAPE_STATE_SYNC1; break; case LIBSPECTRUM_TAPE_STATE_SYNC1: /* The first short sync pulse */ *tstates = block->sync1_length; /* Followed by the second sync pulse */ block->state = LIBSPECTRUM_TAPE_STATE_SYNC2; break; case LIBSPECTRUM_TAPE_STATE_SYNC2: /* The second short sync pulse */ *tstates = block->sync2_length; /* Followed by the first bit of data */ error = turbo_next_bit( block ); if( error ) return error; break; case LIBSPECTRUM_TAPE_STATE_DATA1: /* The first edge for a bit of data */ *tstates = block->bit_tstates; /* Followed by the second edge */ block->state = LIBSPECTRUM_TAPE_STATE_DATA2; break; case LIBSPECTRUM_TAPE_STATE_DATA2: /* The second edge for a bit of data */ *tstates = block->bit_tstates; /* Followed by the next bit of data (or the end of data) */ error = turbo_next_bit( block ); if( error ) return error; break; case LIBSPECTRUM_TAPE_STATE_PAUSE: /* The pause at the end of the block */ *tstates = (block->pause * 69888)/20; /* FIXME: should vary with tstates per frame */ *end_of_block = 1; break; default: libspectrum_print_error( LIBSPECTRUM_ERROR_LOGIC, "turbo_edge: unknown state %d", block->state ); return LIBSPECTRUM_ERROR_LOGIC; } return LIBSPECTRUM_ERROR_NONE; } static libspectrum_error turbo_next_bit( libspectrum_tape_turbo_block *block ) { int next_bit; /* Have we finished the current byte? */ if( ++(block->bits_through_byte) == 8 ) { /* If so, have we finished the entire block? If so, all we've got left after this is the pause at the end */ if( ++(block->bytes_through_block) == block->length ) { block->state = LIBSPECTRUM_TAPE_STATE_PAUSE; return LIBSPECTRUM_ERROR_NONE; } /* If we've finished the current byte, but not the entire block, get the next byte */ block->current_byte = block->data[ block->bytes_through_block ]; /* If we're looking at the last byte, take account of the fact it may have less than 8 bits in it */ if( block->bytes_through_block == block->length-1 ) { block->bits_through_byte = 8 - block->bits_in_last_byte; } else { block->bits_through_byte = 0; } } /* Get the high bit, and shift the byte out leftwards */ next_bit = block->current_byte & 0x80; block->current_byte <<= 1; /* And set the timing and state for another data bit */ block->bit_tstates = ( next_bit ? block->bit1_length : block->bit0_length ); block->state = LIBSPECTRUM_TAPE_STATE_DATA1; return LIBSPECTRUM_ERROR_NONE; } static libspectrum_error tone_edge( libspectrum_tape_pure_tone_block *block, libspectrum_dword *tstates, int *end_of_block ) { /* The next edge occurs in one pilot edge timing */ *tstates = block->length; /* If that was the last edge, the block is finished */ if( --(block->edge_count) == 0 ) (*end_of_block) = 1; return LIBSPECTRUM_ERROR_NONE; } static libspectrum_error pulses_edge( libspectrum_tape_pulses_block *block, libspectrum_dword *tstates, int *end_of_block ) { /* Get the length of this edge */ *tstates = block->lengths[ block->edge_count ]; /* Was that the last edge? */ if( ++(block->edge_count) == block->count ) (*end_of_block) = 1; return LIBSPECTRUM_ERROR_NONE; } static libspectrum_error pure_data_edge( libspectrum_tape_pure_data_block *block, libspectrum_dword *tstates, int *end_of_block ) { int error; switch( block->state ) { case LIBSPECTRUM_TAPE_STATE_DATA1: /* The first edge for a bit of data */ *tstates = block->bit_tstates; /* Followed by the second edge */ block->state = LIBSPECTRUM_TAPE_STATE_DATA2; break; case LIBSPECTRUM_TAPE_STATE_DATA2: /* The second edge for a bit of data */ *tstates = block->bit_tstates; /* Followed by the next bit of data (or the end of data) */ error = libspectrum_tape_pure_data_next_bit( block ); if( error ) return error; break; case LIBSPECTRUM_TAPE_STATE_PAUSE: /* The pause at the end of the block */ *tstates = (block->pause * 69888)/20; /* FIXME: should vary with tstates per frame */ *end_of_block = 1; break; default: libspectrum_print_error( LIBSPECTRUM_ERROR_LOGIC, "pure_data_edge: unknown state %d", block->state ); return LIBSPECTRUM_ERROR_LOGIC; } return LIBSPECTRUM_ERROR_NONE; } libspectrum_error libspectrum_tape_pure_data_next_bit( libspectrum_tape_pure_data_block *block ) { int next_bit; /* Have we finished the current byte? */ if( ++(block->bits_through_byte) == 8 ) { /* If so, have we finished the entire block? If so, all we've got left after this is the pause at the end */ if( ++(block->bytes_through_block) == block->length ) { block->state = LIBSPECTRUM_TAPE_STATE_PAUSE; return LIBSPECTRUM_ERROR_NONE; } /* If we've finished the current byte, but not the entire block, get the next byte */ block->current_byte = block->data[ block->bytes_through_block ]; /* If we're looking at the last byte, take account of the fact it may have less than 8 bits in it */ if( block->bytes_through_block == block->length-1 ) { block->bits_through_byte = 8 - block->bits_in_last_byte; } else { block->bits_through_byte = 0; } } /* Get the high bit, and shift the byte out leftwards */ next_bit = block->current_byte & 0x80; block->current_byte <<= 1; /* And set the timing and state for another data bit */ block->bit_tstates = ( next_bit ? block->bit1_length : block->bit0_length ); block->state = LIBSPECTRUM_TAPE_STATE_DATA1; return LIBSPECTRUM_ERROR_NONE; } static libspectrum_error raw_data_edge( libspectrum_tape_raw_data_block *block, libspectrum_dword *tstates, int *end_of_block ) { int error; switch (block->state) { case LIBSPECTRUM_TAPE_STATE_DATA1: *tstates = block->bit_tstates; error = libspectrum_tape_raw_data_next_bit( block ); if( error ) return error; break; case LIBSPECTRUM_TAPE_STATE_PAUSE: /* The pause at the end of the block */ *tstates = ( block->pause * 69888 )/20; /* FIXME: should vary with tstates per frame */ *end_of_block = 1; break; default: libspectrum_print_error( LIBSPECTRUM_ERROR_LOGIC, "raw_edge: unknown state %d", block->state ); return LIBSPECTRUM_ERROR_LOGIC; } return LIBSPECTRUM_ERROR_NONE; } libspectrum_error libspectrum_tape_raw_data_next_bit( libspectrum_tape_raw_data_block *block ) { int length = 0; if( block->bytes_through_block == block->length ) { block->state = LIBSPECTRUM_TAPE_STATE_PAUSE; return LIBSPECTRUM_ERROR_NONE; } block->state = LIBSPECTRUM_TAPE_STATE_DATA1; /* Step through the data until we find an edge */ do { length++; if( ++block->bits_through_byte == 8 ) { if( ++block->bytes_through_block == block->length - 1 ) { block->bits_through_byte = 8 - block->bits_in_last_byte; } else { block->bits_through_byte = 0; } if( block->bytes_through_block == block->length ) break; } } while( (block->data[block->bytes_through_block] << block->bits_through_byte & 0x80 ) != block->last_bit) ; block->bit_tstates = length * block->bit_length; block->last_bit ^= 0x80; return LIBSPECTRUM_ERROR_NONE; } static libspectrum_byte get_generalised_data_bit( libspectrum_tape_generalised_data_block *block ) { libspectrum_byte r = block->current_byte & 0x80 ? 1 : 0; block->current_byte <<= 1; if( ++block->bits_through_byte == 8 ) { block->bits_through_byte = 0; block->bytes_through_stream++; block->current_byte = block->data[ block->bytes_through_stream ]; } return r; } static libspectrum_byte get_generalised_data_symbol( libspectrum_tape_generalised_data_block *block ) { libspectrum_byte symbol; size_t i; for( i = 0, symbol = 0; i < block->bits_per_data_symbol; i++ ) { symbol <<= 1; symbol |= get_generalised_data_bit( block ); } return symbol; } static void set_tstates_and_flags( libspectrum_tape_generalised_data_symbol *symbol, libspectrum_byte edge, libspectrum_dword *tstates, int *flags ) { *tstates = symbol->lengths[ edge ]; if( !edge ) { switch( symbol->edge_type ) { case LIBSPECTRUM_TAPE_GENERALISED_DATA_SYMBOL_EDGE: break; case LIBSPECTRUM_TAPE_GENERALISED_DATA_SYMBOL_NO_EDGE: *flags |= LIBSPECTRUM_TAPE_FLAGS_NO_EDGE; break; case LIBSPECTRUM_TAPE_GENERALISED_DATA_SYMBOL_LOW: *flags |= LIBSPECTRUM_TAPE_FLAGS_LEVEL_LOW; break; case LIBSPECTRUM_TAPE_GENERALISED_DATA_SYMBOL_HIGH: *flags |= LIBSPECTRUM_TAPE_FLAGS_LEVEL_HIGH; break; } } } static libspectrum_error generalised_data_edge( libspectrum_tape_generalised_data_block *block, libspectrum_dword *tstates, int *end_of_block, int *flags ) { libspectrum_tape_generalised_data_symbol_table *table; libspectrum_tape_generalised_data_symbol *symbol; size_t current_symbol; switch( block->state ) { case LIBSPECTRUM_TAPE_STATE_PILOT: table = &( block->pilot_table ); current_symbol = block->pilot_symbols[ block->run ]; symbol = &( table->symbols[ current_symbol ] ); set_tstates_and_flags( symbol, block->edges_through_symbol, tstates, flags ); block->edges_through_symbol++; if( block->edges_through_symbol == table->max_pulses || symbol->lengths[ block->edges_through_symbol ] == 0 ) { block->edges_through_symbol = 0; if( ++block->symbols_through_run == block->pilot_repeats[ block->run ] ) { block->symbols_through_run = 0; if( ++block->run == table->symbols_in_block ) { block->state = LIBSPECTRUM_TAPE_STATE_DATA1; block->bits_through_byte = 0; block->bytes_through_stream = 0; block->symbols_through_stream = 0; block->current_byte = block->data[ 0 ]; block->current_symbol = get_generalised_data_symbol( block ); } } } break; case LIBSPECTRUM_TAPE_STATE_DATA1: table = &( block->data_table ); symbol = &( table->symbols[ block->current_symbol ] ); set_tstates_and_flags( symbol, block->edges_through_symbol, tstates, flags ); block->edges_through_symbol++; if( block->edges_through_symbol == table->max_pulses || symbol->lengths[ block->edges_through_symbol ] == 0 ) { if( ++block->symbols_through_stream == table->symbols_in_block ) { block->state = LIBSPECTRUM_TAPE_STATE_PAUSE; } else { block->edges_through_symbol = 0; block->current_symbol = get_generalised_data_symbol( block ); } } break; case LIBSPECTRUM_TAPE_STATE_PAUSE: /* The pause at the end of the block */ *tstates = ( block->pause * 69888 )/20; /* FIXME: should vary with tstates per frame */ *end_of_block = 1; break; default: libspectrum_print_error( LIBSPECTRUM_ERROR_LOGIC, "%s: unknown state %d", __func__, block->state ); return LIBSPECTRUM_ERROR_LOGIC; } return LIBSPECTRUM_ERROR_NONE; } static libspectrum_error jump_blocks( libspectrum_tape *tape, int offset ) { gint current_position; GSList *new_block; current_position = g_slist_position( tape->blocks, tape->current_block ); if( current_position == -1 ) return LIBSPECTRUM_ERROR_LOGIC; new_block = g_slist_nth( tape->blocks, current_position + offset ); if( new_block == NULL ) return LIBSPECTRUM_ERROR_CORRUPT; tape->current_block = new_block; return LIBSPECTRUM_ERROR_NONE; } /* Extra, non-TZX, blocks which can be handled as if TZX */ static libspectrum_error rle_pulse_edge( libspectrum_tape_rle_pulse_block *block, libspectrum_dword *tstates, int *end_of_block ) { if( block->data[block->index] ) { *tstates = block->scale * block->data[ block->index++ ]; } else { if( block->index + 5 > block->length ) { libspectrum_print_error( LIBSPECTRUM_ERROR_LOGIC, "rle_pulse_edge: file is truncated\n" ); return LIBSPECTRUM_ERROR_LOGIC; } *tstates = block->scale * ( block->data[ block->index + 1 ] | block->data[ block->index + 2 ] << 8 | block->data[ block->index + 3 ] << 16 | block->data[ block->index + 4 ] << 24 ); block->index += 5; } if( block->index == block->length ) *end_of_block = 1; return LIBSPECTRUM_ERROR_NONE; } /* Get the current block */ libspectrum_tape_block* libspectrum_tape_current_block( libspectrum_tape *tape ) { return tape->current_block ? tape->current_block->data : NULL; } /* Peek at the next block on the tape */ libspectrum_tape_block* libspectrum_tape_peek_next_block( libspectrum_tape *tape ) { if( !tape->current_block ) return NULL; return tape->current_block->next ? tape->current_block->next->data : tape->blocks->data; } /* Cause the next block on the tape to be active, initialise it and return it */ libspectrum_tape_block* libspectrum_tape_select_next_block( libspectrum_tape *tape ) { if( !tape->current_block ) return NULL; tape->current_block = tape->current_block->next; if( !tape->current_block ) tape->current_block = tape->blocks; if( libspectrum_tape_block_init( tape->current_block->data ) ) return NULL; return tape->current_block->data; } /* Get the position on the tape of the current block */ libspectrum_error libspectrum_tape_position( int *n, libspectrum_tape *tape ) { *n = g_slist_position( tape->blocks, tape->current_block ); if( *n == -1 ) { libspectrum_print_error( LIBSPECTRUM_ERROR_LOGIC, "libspectrum_tape_position: current block is not in tape!" ); return LIBSPECTRUM_ERROR_LOGIC; } return LIBSPECTRUM_ERROR_NONE; } /* Select the nth block on the tape */ libspectrum_error libspectrum_tape_nth_block( libspectrum_tape *tape, int n ) { GSList *new_block; libspectrum_error error; new_block = g_slist_nth( tape->blocks, n ); if( !new_block ) { libspectrum_print_error( LIBSPECTRUM_ERROR_CORRUPT, "libspectrum_tape_nth_block: tape does not have block %d", n ); return LIBSPECTRUM_ERROR_CORRUPT; } tape->current_block = new_block; error = libspectrum_tape_block_init( tape->current_block->data ); if( error ) return error; return LIBSPECTRUM_ERROR_NONE; } libspectrum_error libspectrum_tape_append_block( libspectrum_tape *tape, libspectrum_tape_block *block ) { tape->blocks = g_slist_append( tape->blocks, (gpointer)block ); /* If we previously didn't have a tape loaded ( implied by tape->current_block == NULL ), set up so that we point to the start of the tape */ if( !tape->current_block ) { tape->current_block = tape->blocks; libspectrum_tape_block_init( tape->blocks->data ); } return LIBSPECTRUM_ERROR_NONE; } libspectrum_error libspectrum_tape_remove_block( libspectrum_tape *tape, libspectrum_tape_iterator it ) { if( it->data ) libspectrum_tape_block_free( it->data ); tape->blocks = g_slist_delete_link( tape->blocks, it ); return LIBSPECTRUM_ERROR_NONE; } libspectrum_error libspectrum_tape_insert_block( libspectrum_tape *tape, libspectrum_tape_block *block, size_t position ) { tape->blocks = g_slist_insert( tape->blocks, block, position ); return LIBSPECTRUM_ERROR_NONE; } libspectrum_error libspectrum_tape_block_description( char *buffer, size_t length, libspectrum_tape_block *block ) { switch( block->type ) { case LIBSPECTRUM_TAPE_BLOCK_ROM: strncpy( buffer, "Standard Speed Data", length ); break; case LIBSPECTRUM_TAPE_BLOCK_TURBO: strncpy( buffer, "Turbo Speed Data", length ); break; case LIBSPECTRUM_TAPE_BLOCK_PURE_TONE: strncpy( buffer, "Pure Tone", length ); break; case LIBSPECTRUM_TAPE_BLOCK_PULSES: strncpy( buffer, "List of Pulses", length ); break; case LIBSPECTRUM_TAPE_BLOCK_PURE_DATA: strncpy( buffer, "Pure Data", length ); break; case LIBSPECTRUM_TAPE_BLOCK_RAW_DATA: strncpy( buffer, "Raw Data", length ); break; case LIBSPECTRUM_TAPE_BLOCK_GENERALISED_DATA: strncpy( buffer, "Generalised Data", length ); break; case LIBSPECTRUM_TAPE_BLOCK_PAUSE: strncpy( buffer, "Pause", length ); break; case LIBSPECTRUM_TAPE_BLOCK_GROUP_START: strncpy( buffer, "Group Start", length ); break; case LIBSPECTRUM_TAPE_BLOCK_GROUP_END: strncpy( buffer, "Group End", length ); break; case LIBSPECTRUM_TAPE_BLOCK_JUMP: strncpy( buffer, "Jump", length ); break; case LIBSPECTRUM_TAPE_BLOCK_LOOP_START: strncpy( buffer, "Loop Start Block", length ); break; case LIBSPECTRUM_TAPE_BLOCK_LOOP_END: strncpy( buffer, "Loop End", length ); break; case LIBSPECTRUM_TAPE_BLOCK_SELECT: strncpy( buffer, "Select", length ); break; case LIBSPECTRUM_TAPE_BLOCK_STOP48: strncpy( buffer, "Stop Tape If In 48K Mode", length ); break; case LIBSPECTRUM_TAPE_BLOCK_COMMENT: strncpy( buffer, "Comment", length ); break; case LIBSPECTRUM_TAPE_BLOCK_MESSAGE: strncpy( buffer, "Message", length ); break; case LIBSPECTRUM_TAPE_BLOCK_ARCHIVE_INFO: strncpy( buffer, "Archive Info", length ); break; case LIBSPECTRUM_TAPE_BLOCK_HARDWARE: strncpy( buffer, "Hardware Information", length ); break; case LIBSPECTRUM_TAPE_BLOCK_CUSTOM: strncpy( buffer, "Custom Info", length ); break; case LIBSPECTRUM_TAPE_BLOCK_RLE_PULSE: strncpy( buffer, "RLE Pulse", length ); break; default: libspectrum_print_error( LIBSPECTRUM_ERROR_LOGIC, "libspectrum_tape_block_description: unknown block type 0x%02x", block->type ); return LIBSPECTRUM_ERROR_LOGIC; } buffer[ length-1 ] = '\0'; return LIBSPECTRUM_ERROR_NONE; } /* Given a tape file, attempt to guess which sort of hardware it should run on by looking for a hardware block (0x33). Deliberately not mentioned in libspectrum.h(.in) as I'm really not sure this function is actually useful. */ libspectrum_error libspectrum_tape_guess_hardware( libspectrum_machine *machine, const libspectrum_tape *tape ) { GSList *ptr; int score, current_score; size_t i; *machine = LIBSPECTRUM_MACHINE_UNKNOWN; current_score = 0; if( !libspectrum_tape_present( tape ) ) return LIBSPECTRUM_ERROR_NONE; for( ptr = tape->blocks; ptr; ptr = ptr->next ) { libspectrum_tape_block *block = (libspectrum_tape_block*)ptr->data; libspectrum_tape_hardware_block *hardware; if( block->type != LIBSPECTRUM_TAPE_BLOCK_HARDWARE ) continue; hardware = &( block->types.hardware ); for( i=0; icount; i++ ) { /* Only interested in which computer types this tape runs on */ if( hardware->types[i] != 0 ) continue; /* Skip if the tape doesn't run on this hardware */ if( hardware->values[i] == 3 ) continue; /* If the tape uses the special hardware, choose that preferentially. If it doesn't (or is unknown), it's a possibility */ if( hardware->values[i] == 1 ) { score = 2; } else { score = 1; } if( score <= current_score ) continue; switch( hardware->ids[i] ) { case 0: /* 16K Spectrum */ *machine = LIBSPECTRUM_MACHINE_16; current_score = score; break; case 1: /* 48K Spectrum */ case 2: /* 48K Issue 1 Spectrum */ *machine = LIBSPECTRUM_MACHINE_48; current_score = score; break; case 3: /* 128K Spectrum */ *machine = LIBSPECTRUM_MACHINE_128; current_score = score; break; case 4: /* +2 */ *machine = LIBSPECTRUM_MACHINE_PLUS2; current_score = score; break; case 5: /* +2A and +3. Gee, thanks to whoever designed the TZX format for distinguishing those :-( */ *machine = LIBSPECTRUM_MACHINE_PLUS3; current_score = score; break; case 6: /* TC2048 */ *machine = LIBSPECTRUM_MACHINE_TC2048; current_score = score; break; } } } return LIBSPECTRUM_ERROR_NONE; } /* * Tape iterator functions */ libspectrum_tape_block* libspectrum_tape_iterator_init( libspectrum_tape_iterator *iterator, libspectrum_tape *tape ) { *iterator = tape->blocks; return *iterator ? (*iterator)->data : NULL; } libspectrum_tape_block* libspectrum_tape_iterator_next( libspectrum_tape_iterator *iterator ) { *iterator = (*iterator)->next; return *iterator ? (*iterator)->data : NULL; }