/* tzx_read.c: Routines for reading .tzx files Copyright (c) 2001-2007 Philip Kendall, Darren Salt $Id: tzx_read.c,v 1.65.2.2 2007/04/25 15:01:41 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 #include #include "internals.h" /* The .tzx file signature (first 8 bytes) */ const char *libspectrum_tzx_signature = "ZXTape!\x1a"; /*** Local function prototypes ***/ static libspectrum_error tzx_read_rom_block( libspectrum_tape *tape, const libspectrum_byte **ptr, const libspectrum_byte *end ); static libspectrum_error tzx_read_turbo_block( libspectrum_tape *tape, const libspectrum_byte **ptr, const libspectrum_byte *end ); static libspectrum_error tzx_read_pure_tone( libspectrum_tape *tape, const libspectrum_byte **ptr, const libspectrum_byte *end ); static libspectrum_error tzx_read_pulses_block( libspectrum_tape *tape, const libspectrum_byte **ptr, const libspectrum_byte *end ); static libspectrum_error tzx_read_pure_data( libspectrum_tape *tape, const libspectrum_byte **ptr, const libspectrum_byte *end ); static libspectrum_error tzx_read_raw_data( libspectrum_tape *tape, const libspectrum_byte **ptr, const libspectrum_byte *end ); static libspectrum_error tzx_read_generalised_data( libspectrum_tape *tape, const libspectrum_byte **ptr, const libspectrum_byte *end ); static libspectrum_error tzx_read_pause( libspectrum_tape *tape, const libspectrum_byte **ptr, const libspectrum_byte *end ); static libspectrum_error tzx_read_group_start( libspectrum_tape *tape, const libspectrum_byte **ptr, const libspectrum_byte *end ); static libspectrum_error tzx_read_jump( libspectrum_tape *tape, const libspectrum_byte **ptr, const libspectrum_byte *end ); static libspectrum_error tzx_read_loop_start( libspectrum_tape *tape, const libspectrum_byte **ptr, const libspectrum_byte *end ); static libspectrum_error tzx_read_select( libspectrum_tape *tape, const libspectrum_byte **ptr, const libspectrum_byte *end ); static libspectrum_error tzx_read_stop( libspectrum_tape *tape, const libspectrum_byte **ptr, const libspectrum_byte *end ); static libspectrum_error tzx_read_comment( libspectrum_tape *tape, const libspectrum_byte **ptr, const libspectrum_byte *end ); static libspectrum_error tzx_read_message( libspectrum_tape *tape, const libspectrum_byte **ptr, const libspectrum_byte *end ); static libspectrum_error tzx_read_archive_info( libspectrum_tape *tape, const libspectrum_byte **ptr, const libspectrum_byte *end ); static libspectrum_error tzx_read_hardware( libspectrum_tape *tape, const libspectrum_byte **ptr, const libspectrum_byte *end ); static libspectrum_error tzx_read_custom( libspectrum_tape *tape, const libspectrum_byte **ptr, const libspectrum_byte *end ); static libspectrum_error tzx_read_concat( const libspectrum_byte **ptr, const libspectrum_byte *end ); static libspectrum_error tzx_read_empty_block( libspectrum_tape *tape, libspectrum_tape_type id ); static libspectrum_error tzx_read_data( const libspectrum_byte **ptr, const libspectrum_byte *end, size_t *length, int bytes, libspectrum_byte **data ); static libspectrum_error tzx_read_string( const libspectrum_byte **ptr, const libspectrum_byte *end, char **dest ); /*** Function definitions ***/ /* The main load function */ libspectrum_error libspectrum_tzx_read( libspectrum_tape *tape, const libspectrum_byte *buffer, const size_t length ) { libspectrum_error error; const libspectrum_byte *ptr, *end; size_t signature_length = strlen( libspectrum_tzx_signature ); ptr = buffer; end = buffer + length; /* Must be at least as many bytes as the signature, and the major/minor version numbers */ if( length < signature_length + 2 ) { libspectrum_print_error( LIBSPECTRUM_ERROR_CORRUPT, "libspectrum_tzx_create: not enough data in buffer" ); return LIBSPECTRUM_ERROR_CORRUPT; } /* Now check the signature */ if( memcmp( ptr, libspectrum_tzx_signature, signature_length ) ) { libspectrum_print_error( LIBSPECTRUM_ERROR_SIGNATURE, "libspectrum_tzx_create: wrong signature" ); return LIBSPECTRUM_ERROR_SIGNATURE; } ptr += signature_length; /* Just skip the version numbers */ ptr += 2; while( ptr < end ) { /* Get the ID of the next block */ libspectrum_tape_type id = *ptr++; switch( id ) { case LIBSPECTRUM_TAPE_BLOCK_ROM: error = tzx_read_rom_block( tape, &ptr, end ); if( error ) { libspectrum_tape_clear( tape ); return error; } break; case LIBSPECTRUM_TAPE_BLOCK_TURBO: error = tzx_read_turbo_block( tape, &ptr, end ); if( error ) { libspectrum_tape_clear( tape ); return error; } break; case LIBSPECTRUM_TAPE_BLOCK_PURE_TONE: error = tzx_read_pure_tone( tape, &ptr, end ); if( error ) { libspectrum_tape_clear( tape ); return error; } break; case LIBSPECTRUM_TAPE_BLOCK_PULSES: error = tzx_read_pulses_block( tape, &ptr, end ); if( error ) { libspectrum_tape_clear( tape ); return error; } break; case LIBSPECTRUM_TAPE_BLOCK_PURE_DATA: error = tzx_read_pure_data( tape, &ptr, end ); if( error ) { libspectrum_tape_clear( tape ); return error; } break; case LIBSPECTRUM_TAPE_BLOCK_RAW_DATA: error = tzx_read_raw_data( tape, &ptr, end ); if( error ) { libspectrum_tape_clear( tape ); return error; } break; case LIBSPECTRUM_TAPE_BLOCK_GENERALISED_DATA: error = tzx_read_generalised_data( tape, &ptr, end ); if( error ) { libspectrum_tape_clear( tape ); return error; } break; case LIBSPECTRUM_TAPE_BLOCK_PAUSE: error = tzx_read_pause( tape, &ptr, end ); if( error ) { libspectrum_tape_clear( tape ); return error; } break; case LIBSPECTRUM_TAPE_BLOCK_GROUP_START: error = tzx_read_group_start( tape, &ptr, end ); if( error ) { libspectrum_tape_clear( tape ); return error; } break; case LIBSPECTRUM_TAPE_BLOCK_GROUP_END: error = tzx_read_empty_block( tape, id ); if( error ) { libspectrum_tape_clear( tape ); return error; } break; case LIBSPECTRUM_TAPE_BLOCK_JUMP: error = tzx_read_jump( tape, &ptr, end ); if( error ) { libspectrum_tape_clear( tape ); return error; } break; case LIBSPECTRUM_TAPE_BLOCK_LOOP_START: error = tzx_read_loop_start( tape, &ptr, end ); if( error ) { libspectrum_tape_clear( tape ); return error; } break; case LIBSPECTRUM_TAPE_BLOCK_LOOP_END: error = tzx_read_empty_block( tape, id ); if( error ) { libspectrum_tape_clear( tape ); return error; } break; case LIBSPECTRUM_TAPE_BLOCK_SELECT: error = tzx_read_select( tape, &ptr, end ); if( error ) { libspectrum_tape_clear( tape ); return error; } break; case LIBSPECTRUM_TAPE_BLOCK_STOP48: error = tzx_read_stop( tape, &ptr, end ); if( error ) { libspectrum_tape_clear( tape ); return error; } break; case LIBSPECTRUM_TAPE_BLOCK_COMMENT: error = tzx_read_comment( tape, &ptr, end ); if( error ) { libspectrum_tape_clear( tape ); return error; } break; case LIBSPECTRUM_TAPE_BLOCK_MESSAGE: error = tzx_read_message( tape, &ptr, end ); if( error ) { libspectrum_tape_clear( tape ); return error; } break; case LIBSPECTRUM_TAPE_BLOCK_ARCHIVE_INFO: error = tzx_read_archive_info( tape, &ptr, end ); if( error ) { libspectrum_tape_clear( tape ); return error; } break; case LIBSPECTRUM_TAPE_BLOCK_HARDWARE: error = tzx_read_hardware( tape, &ptr, end ); if( error ) { libspectrum_tape_clear( tape ); return error; } break; case LIBSPECTRUM_TAPE_BLOCK_CUSTOM: error = tzx_read_custom( tape, &ptr, end ); if( error ) { libspectrum_tape_clear( tape ); return error; } break; case LIBSPECTRUM_TAPE_BLOCK_CONCAT: error = tzx_read_concat( &ptr, end ); if( error ) { libspectrum_tape_clear( tape ); return error; } break; default: /* For now, don't handle anything else */ libspectrum_tape_clear( tape ); libspectrum_print_error( LIBSPECTRUM_ERROR_UNKNOWN, "libspectrum_tzx_create: unknown block type 0x%02x", id ); return LIBSPECTRUM_ERROR_UNKNOWN; } } return LIBSPECTRUM_ERROR_NONE; } static libspectrum_error tzx_read_rom_block( libspectrum_tape *tape, const libspectrum_byte **ptr, const libspectrum_byte *end ) { libspectrum_tape_block* block; size_t length; libspectrum_byte *data; libspectrum_error error; /* Check there's enough left in the buffer for the pause and the data length */ if( end - (*ptr) < 4 ) { libspectrum_print_error( LIBSPECTRUM_ERROR_CORRUPT, "tzx_read_rom_block: not enough data in buffer" ); return LIBSPECTRUM_ERROR_CORRUPT; } /* Get a new block */ error = libspectrum_tape_block_alloc( &block, LIBSPECTRUM_TAPE_BLOCK_ROM ); if( error ) return error; /* Get the pause length */ libspectrum_tape_block_set_pause( block, (*ptr)[0] + (*ptr)[1] * 0x100 ); (*ptr) += 2; /* And the data */ error = tzx_read_data( ptr, end, &length, 2, &data ); if( error ) { free( block ); return error; } libspectrum_tape_block_set_data_length( block, length ); libspectrum_tape_block_set_data( block, data ); /* Finally, put the block into the block list */ error = libspectrum_tape_append_block( tape, block ); if( error ) { libspectrum_tape_block_free( block ); return error; } /* And return with no error */ return LIBSPECTRUM_ERROR_NONE; } static libspectrum_error tzx_read_turbo_block( libspectrum_tape *tape, const libspectrum_byte **ptr, const libspectrum_byte *end ) { libspectrum_tape_block* block; size_t length; libspectrum_byte *data; libspectrum_error error; /* Check there's enough left in the buffer for all the metadata */ if( end - (*ptr) < 18 ) { libspectrum_print_error( LIBSPECTRUM_ERROR_CORRUPT, "tzx_read_turbo_block: not enough data in buffer" ); return LIBSPECTRUM_ERROR_CORRUPT; } /* Get a new block */ error = libspectrum_tape_block_alloc( &block, LIBSPECTRUM_TAPE_BLOCK_TURBO ); if( error ) return error; /* Get the metadata */ libspectrum_tape_block_set_pilot_length( block, (*ptr)[0] + (*ptr)[1] * 0x100 ); (*ptr) += 2; libspectrum_tape_block_set_sync1_length( block, (*ptr)[0] + (*ptr)[1] * 0x100 ); (*ptr) += 2; libspectrum_tape_block_set_sync2_length( block, (*ptr)[0] + (*ptr)[1] * 0x100 ); (*ptr) += 2; libspectrum_tape_block_set_bit0_length ( block, (*ptr)[0] + (*ptr)[1] * 0x100 ); (*ptr) += 2; libspectrum_tape_block_set_bit1_length ( block, (*ptr)[0] + (*ptr)[1] * 0x100 ); (*ptr) += 2; libspectrum_tape_block_set_pilot_pulses( block, (*ptr)[0] + (*ptr)[1] * 0x100 ); (*ptr) += 2; libspectrum_tape_block_set_bits_in_last_byte( block, **ptr ); (*ptr)++; libspectrum_tape_block_set_pause ( block, (*ptr)[0] + (*ptr)[1] * 0x100 ); (*ptr) += 2; /* Read the data in */ error = tzx_read_data( ptr, end, &length, 3, &data ); if( error ) { free( block ); return error; } libspectrum_tape_block_set_data_length( block, length ); libspectrum_tape_block_set_data( block, data ); /* Finally, put the block into the block list */ error = libspectrum_tape_append_block( tape, block ); if( error ) { libspectrum_tape_block_free( block ); return error; } /* And return with no error */ return LIBSPECTRUM_ERROR_NONE; } static libspectrum_error tzx_read_pure_tone( libspectrum_tape *tape, const libspectrum_byte **ptr, const libspectrum_byte *end ) { libspectrum_tape_block* block; libspectrum_error error; /* Check we've got enough bytes */ if( end - (*ptr) < 4 ) { libspectrum_print_error( LIBSPECTRUM_ERROR_CORRUPT, "tzx_read_pure_tone: not enough data in buffer" ); return LIBSPECTRUM_ERROR_CORRUPT; } /* Get a new block */ error = libspectrum_tape_block_alloc( &block, LIBSPECTRUM_TAPE_BLOCK_PURE_TONE ); if( error ) return error; /* Read in the data, and move along */ libspectrum_tape_block_set_pulse_length( block, (*ptr)[0] + (*ptr)[1] * 0x100 ); (*ptr) += 2; libspectrum_tape_block_set_count( block, (*ptr)[0] + (*ptr)[1] * 0x100 ); (*ptr) += 2; /* Finally, put the block into the block list */ error = libspectrum_tape_append_block( tape, block ); if( error ) { libspectrum_tape_block_free( block ); return error; } /* And return with no error */ return LIBSPECTRUM_ERROR_NONE; } static libspectrum_error tzx_read_pulses_block( libspectrum_tape *tape, const libspectrum_byte **ptr, const libspectrum_byte *end ) { libspectrum_tape_block* block; libspectrum_dword *lengths; libspectrum_error error; size_t i, count; /* Check the count byte exists */ if( (*ptr) == end ) { libspectrum_print_error( LIBSPECTRUM_ERROR_CORRUPT, "tzx_read_pulses_block: not enough data in buffer" ); return LIBSPECTRUM_ERROR_CORRUPT; } /* Get a new block */ error = libspectrum_tape_block_alloc( &block, LIBSPECTRUM_TAPE_BLOCK_PULSES ); if( error ) return error; /* Get the count byte */ count = **ptr; (*ptr)++; libspectrum_tape_block_set_count( block, count ); /* Check enough data exists for every pulse */ if( end - (*ptr) < (ptrdiff_t)( 2 * count ) ) { free( block ); libspectrum_print_error( LIBSPECTRUM_ERROR_CORRUPT, "tzx_read_pulses_block: not enough data in buffer" ); return LIBSPECTRUM_ERROR_CORRUPT; } /* Allocate memory for the actual list of lengths */ lengths = malloc( count * sizeof( libspectrum_dword ) ); if( lengths == NULL ) { free( block ); libspectrum_print_error( LIBSPECTRUM_ERROR_MEMORY, "tzx_read_pulses_block: out of memory" ); return LIBSPECTRUM_ERROR_MEMORY; } /* Copy the data across */ for( i = 0; i < count; i++ ) { lengths[i] = (*ptr)[0] + (*ptr)[1] * 0x100; (*ptr) += 2; } libspectrum_tape_block_set_pulse_lengths( block, lengths ); /* Put the block into the block list */ error = libspectrum_tape_append_block( tape, block ); if( error ) { libspectrum_tape_block_free( block ); return error; } /* And return with no error */ return LIBSPECTRUM_ERROR_NONE; } static libspectrum_error tzx_read_pure_data( libspectrum_tape *tape, const libspectrum_byte **ptr, const libspectrum_byte *end ) { libspectrum_tape_block* block; size_t length; libspectrum_byte *data; libspectrum_error error; /* Check there's enough left in the buffer for all the metadata */ if( end - (*ptr) < 10 ) { libspectrum_print_error( LIBSPECTRUM_ERROR_CORRUPT, "tzx_read_pure_data: not enough data in buffer" ); return LIBSPECTRUM_ERROR_CORRUPT; } /* Get a new block */ error = libspectrum_tape_block_alloc( &block, LIBSPECTRUM_TAPE_BLOCK_PURE_DATA ); if( error ) return error; /* Get the metadata */ libspectrum_tape_block_set_bit0_length( block, (*ptr)[0] + (*ptr)[1] * 0x100 ); (*ptr) += 2; libspectrum_tape_block_set_bit1_length( block, (*ptr)[0] + (*ptr)[1] * 0x100 ); (*ptr) += 2; libspectrum_tape_block_set_bits_in_last_byte( block, **ptr ); (*ptr)++; libspectrum_tape_block_set_pause( block, (*ptr)[0] + (*ptr)[1] * 0x100 ); (*ptr) += 2; /* And the actual data */ error = tzx_read_data( ptr, end, &length, 3, &data ); if( error ) { free( block ); return error; } libspectrum_tape_block_set_data_length( block, length ); libspectrum_tape_block_set_data( block, data ); /* Finally, put the block into the block list */ error = libspectrum_tape_append_block( tape, block ); if( error ) { libspectrum_tape_block_free( block ); return error; } /* And return with no error */ return LIBSPECTRUM_ERROR_NONE; } static libspectrum_error tzx_read_raw_data (libspectrum_tape *tape, const libspectrum_byte **ptr, const libspectrum_byte *end) { libspectrum_tape_block* block; size_t length; libspectrum_byte *data; libspectrum_error error; /* Check there's enough left in the buffer for all the metadata */ if (end - (*ptr) < 8) { libspectrum_print_error( LIBSPECTRUM_ERROR_CORRUPT, "tzx_read_raw_data: not enough data in buffer" ); return LIBSPECTRUM_ERROR_CORRUPT; } /* Get a new block */ error = libspectrum_tape_block_alloc( &block, LIBSPECTRUM_TAPE_BLOCK_RAW_DATA ); if( error ) return error; /* Get the metadata */ libspectrum_tape_block_set_bit_length( block, (*ptr)[0] + (*ptr)[1] * 0x100 ); libspectrum_tape_block_set_pause ( block, (*ptr)[2] + (*ptr)[3] * 0x100 ); libspectrum_tape_block_set_bits_in_last_byte( block, (*ptr)[4] ); (*ptr) += 5; /* And the actual data */ error = tzx_read_data( ptr, end, &length, 3, &data ); if( error ) { free( block ); return error; } libspectrum_tape_block_set_data_length( block, length ); libspectrum_tape_block_set_data( block, data ); /* Finally, put the block into the block list */ error = libspectrum_tape_append_block( tape, block ); if( error ) { libspectrum_tape_block_free( block ); return error; } /* And return with no error */ return LIBSPECTRUM_ERROR_NONE; } static libspectrum_error tzx_read_generalised_data( libspectrum_tape *tape, const libspectrum_byte **ptr, const libspectrum_byte *end ) { libspectrum_tape_block *block; libspectrum_dword length, symbol_count, data_count; libspectrum_word symbol_count2; libspectrum_error error; libspectrum_tape_generalised_data_symbol_table *table; libspectrum_byte *symbols, *data; libspectrum_word *repeats; const libspectrum_byte *blockend, *ptr2; size_t i; int bits_per_symbol; /* Check the length exists */ if( end - (*ptr) < 4 ) { libspectrum_print_error( LIBSPECTRUM_ERROR_CORRUPT, "%s: not enough data in buffer", __func__ ); return LIBSPECTRUM_ERROR_CORRUPT; } length = libspectrum_read_dword( ptr ); blockend = *ptr + length; /* Sanity check */ if( length < 14 ) { libspectrum_print_error( LIBSPECTRUM_ERROR_CORRUPT, "%s: length less than minimum", __func__ ); return LIBSPECTRUM_ERROR_CORRUPT; } /* Check this much data exists */ if( end - (*ptr) < length ) { libspectrum_print_error( LIBSPECTRUM_ERROR_CORRUPT, "%s: not enough data in buffer", __func__ ); return LIBSPECTRUM_ERROR_CORRUPT; } error = libspectrum_tape_block_alloc( &block, LIBSPECTRUM_TAPE_BLOCK_GENERALISED_DATA ); if( error ) return error; libspectrum_tape_block_zero( block ); libspectrum_tape_block_set_pause( block, (*ptr)[0] + (*ptr)[1] * 0x100 ); (*ptr) += 2; error = libspectrum_tape_block_read_symbol_table_parameters( block, 1, ptr ); if( error ) { libspectrum_tape_block_free( block ); return error; } error = libspectrum_tape_block_read_symbol_table_parameters( block, 0, ptr ); if( error ) { libspectrum_tape_block_free( block ); return error; } length -= 14; ptr2 = *ptr; table = libspectrum_tape_block_pilot_table( block ); error = libspectrum_tape_block_read_symbol_table( table, ptr, length ); if( error ) { libspectrum_tape_block_free( block ); return error; } length -= ptr2 - *ptr; symbol_count = libspectrum_tape_generalised_data_symbol_table_symbols_in_block( table ); if( length < 3 * symbol_count ) { libspectrum_tape_block_free( block ); libspectrum_print_error( LIBSPECTRUM_ERROR_CORRUPT, "%s: not enough data in buffer", __func__ ); return LIBSPECTRUM_ERROR_CORRUPT; } symbols = malloc( symbol_count * sizeof( *symbols ) ); if( !symbols ) { libspectrum_tape_block_free( block ); libspectrum_print_error( LIBSPECTRUM_ERROR_MEMORY, "%s:%d", __func__, __LINE__ ); return LIBSPECTRUM_ERROR_MEMORY; } repeats = malloc( symbol_count * sizeof( *repeats ) ); if( !repeats ) { libspectrum_tape_block_free( block ); libspectrum_print_error( LIBSPECTRUM_ERROR_MEMORY, "%s:%d", __func__, __LINE__ ); return LIBSPECTRUM_ERROR_MEMORY; } for( i = 0; i < symbol_count; i++ ) { symbols[ i ] = **ptr; (*ptr)++; repeats[ i ] = (*ptr)[0] + 0x100 * (*ptr)[1]; (*ptr) += 2; } libspectrum_tape_block_set_pilot_symbols( block, symbols ); libspectrum_tape_block_set_pilot_repeats( block, repeats ); length -= 3 * symbol_count; ptr2 = *ptr; table = libspectrum_tape_block_data_table( block ); libspectrum_tape_block_read_symbol_table( table, ptr, length ); length -= ptr2 - *ptr; symbol_count2 = libspectrum_tape_generalised_data_symbol_table_symbols_in_table( table ); bits_per_symbol = ceil( log( symbol_count2 ) / M_LN2 ); libspectrum_tape_block_set_bits_per_data_symbol( block, bits_per_symbol ); symbol_count = libspectrum_tape_generalised_data_symbol_table_symbols_in_block( table ); data_count = ( ( bits_per_symbol * symbol_count ) + 7 ) / 8; data = malloc( data_count * sizeof( *data ) ); if( !data ) { libspectrum_tape_block_free( block ); libspectrum_print_error( LIBSPECTRUM_ERROR_MEMORY, "%s:%d", __func__, __LINE__ ); return LIBSPECTRUM_ERROR_MEMORY; } memcpy( data, *ptr, data_count * sizeof( *data ) ); *ptr += data_count; libspectrum_tape_block_set_data( block, data ); /* Sanity check */ if( *ptr != blockend ) { libspectrum_tape_block_free( block ); libspectrum_print_error( LIBSPECTRUM_ERROR_CORRUPT, "%s: sanity check failed", __func__ ); return LIBSPECTRUM_ERROR_CORRUPT; } error = libspectrum_tape_append_block( tape, block ); if( error ) { libspectrum_tape_block_free( block ); return error; } return LIBSPECTRUM_ERROR_NONE; } static libspectrum_error tzx_read_pause( libspectrum_tape *tape, const libspectrum_byte **ptr, const libspectrum_byte *end ) { libspectrum_tape_block *block; libspectrum_error error; /* Check the pause actually exists */ if( end - (*ptr) < 2 ) { libspectrum_print_error( LIBSPECTRUM_ERROR_CORRUPT, "tzx_read_pause: not enough data in buffer" ); return LIBSPECTRUM_ERROR_CORRUPT; } /* Get a new block */ error = libspectrum_tape_block_alloc( &block, LIBSPECTRUM_TAPE_BLOCK_PAUSE ); if( error ) return error; /* Get the pause length */ libspectrum_tape_block_set_pause( block, (*ptr)[0] + (*ptr)[1] * 0x100 ); (*ptr) += 2; /* Put the block into the block list */ error = libspectrum_tape_append_block( tape, block ); if( error ) { libspectrum_tape_block_free( block ); return error; } /* And return */ return LIBSPECTRUM_ERROR_NONE; } static libspectrum_error tzx_read_group_start( libspectrum_tape *tape, const libspectrum_byte **ptr, const libspectrum_byte *end ) { libspectrum_tape_block *block; char *name; libspectrum_error error; /* Check the length byte exists */ if( (*ptr) == end ) { libspectrum_print_error( LIBSPECTRUM_ERROR_CORRUPT, "tzx_read_group_start: not enough data in buffer" ); return LIBSPECTRUM_ERROR_CORRUPT; } /* Get a new block */ error = libspectrum_tape_block_alloc( &block, LIBSPECTRUM_TAPE_BLOCK_GROUP_START ); if( error ) return error; /* Read in the description of the group */ error = tzx_read_string( ptr, end, &name ); if( error ) { free( block ); return error; } libspectrum_tape_block_set_text( block, name ); /* Finally, put the block into the block list */ error = libspectrum_tape_append_block( tape, block ); if( error ) { libspectrum_tape_block_free( block ); return error; } return LIBSPECTRUM_ERROR_NONE; } static libspectrum_error tzx_read_jump( libspectrum_tape *tape, const libspectrum_byte **ptr, const libspectrum_byte *end ) { libspectrum_tape_block *block; int offset; libspectrum_error error; /* Check the offset exists */ if( end - (*ptr) < 2 ) { libspectrum_print_error( LIBSPECTRUM_ERROR_CORRUPT, "tzx_read_jump: not enough data in buffer" ); return LIBSPECTRUM_ERROR_CORRUPT; } /* Get a new block */ error = libspectrum_tape_block_alloc( &block, LIBSPECTRUM_TAPE_BLOCK_JUMP ); if( error ) return error; /* Get the offset */ offset = (*ptr)[0] + (*ptr)[1] * 0x100; (*ptr) += 2; if( offset >= 32768 ) offset -= 65536; libspectrum_tape_block_set_offset( block, offset); /* Finally, put the block into the block list */ error = libspectrum_tape_append_block( tape, block ); if( error ) { libspectrum_tape_block_free( block ); return error; } return LIBSPECTRUM_ERROR_NONE; } static libspectrum_error tzx_read_loop_start( libspectrum_tape *tape, const libspectrum_byte **ptr, const libspectrum_byte *end ) { libspectrum_tape_block *block; libspectrum_error error; /* Check the count exists */ if( end - (*ptr) < 2 ) { libspectrum_print_error( LIBSPECTRUM_ERROR_CORRUPT, "tzx_read_loop_start: not enough data in buffer" ); return LIBSPECTRUM_ERROR_CORRUPT; } /* Get a new block */ error = libspectrum_tape_block_alloc( &block, LIBSPECTRUM_TAPE_BLOCK_LOOP_START ); if( error ) return error; /* Get the repeat count */ libspectrum_tape_block_set_count( block, (*ptr)[0] + (*ptr)[1] * 0x100 ); (*ptr) += 2; /* Finally, put the block into the block list */ error = libspectrum_tape_append_block( tape, block ); if( error ) { libspectrum_tape_block_free( block ); return error; } return LIBSPECTRUM_ERROR_NONE; } static libspectrum_error tzx_read_select( libspectrum_tape *tape, const libspectrum_byte **ptr, const libspectrum_byte *end ) { libspectrum_tape_block* block; libspectrum_error error; size_t length, count, i, j; int *offsets; char **descriptions; /* Check there's enough left in the buffer for the length and the count byte */ if( end - (*ptr) < 3 ) { libspectrum_print_error( LIBSPECTRUM_ERROR_CORRUPT, "tzx_read_select: not enough data in buffer" ); return LIBSPECTRUM_ERROR_CORRUPT; } /* Get the length, and check we've got that many bytes in the buffer */ length = (*ptr)[0] + (*ptr)[1] * 0x100; (*ptr) += 2; if( end - (*ptr) < (ptrdiff_t)length ) { libspectrum_print_error( LIBSPECTRUM_ERROR_CORRUPT, "tzx_read_select: not enough data in buffer" ); return LIBSPECTRUM_ERROR_CORRUPT; } /* Get a new block */ error = libspectrum_tape_block_alloc( &block, LIBSPECTRUM_TAPE_BLOCK_SELECT ); if( error ) return error; /* Get the number of selections */ count = **ptr; (*ptr)++; libspectrum_tape_block_set_count( block, count ); /* Allocate memory */ offsets = malloc( count * sizeof( int ) ); if( !offsets ) { free( block ); libspectrum_print_error( LIBSPECTRUM_ERROR_MEMORY, "tzx_read_select: out of memory" ); return LIBSPECTRUM_ERROR_MEMORY; } libspectrum_tape_block_set_offsets( block, offsets ); descriptions = malloc( count * sizeof( char* ) ); if( !descriptions ) { free( offsets ); free( block ); libspectrum_print_error( LIBSPECTRUM_ERROR_MEMORY, "tzx_read_select: out of memory" ); return LIBSPECTRUM_ERROR_MEMORY; } libspectrum_tape_block_set_texts( block, descriptions ); /* Read in the data */ for( i = 0; i < count; i++ ) { /* Check we've got the offset and a length byte */ if( end - (*ptr) < 3 ) { for( j = 0; j < i; j++ ) free( descriptions[j] ); free( descriptions ); free( offsets ); free( block ); libspectrum_print_error( LIBSPECTRUM_ERROR_CORRUPT, "tzx_read_select: not enough data in buffer" ); return LIBSPECTRUM_ERROR_CORRUPT; } /* Get the offset */ offsets[i] = (*ptr)[0] + (*ptr)[1] * 0x100; (*ptr) += 2; /* Get the description of this selection */ error = tzx_read_string( ptr, end, &descriptions[i] ); if( error ) { for( j = 0; j < i; j++ ) free( descriptions[j] ); free( descriptions ); free( offsets ); free( block ); return error; } } /* Finally, put the block into the block list */ error = libspectrum_tape_append_block( tape, block ); if( error ) { libspectrum_tape_block_free( block ); return error; } return LIBSPECTRUM_ERROR_NONE; } static libspectrum_error tzx_read_stop( libspectrum_tape *tape, const libspectrum_byte **ptr, const libspectrum_byte *end ) { libspectrum_tape_block *block; libspectrum_error error; /* Check the length field exists */ if( end - (*ptr) < 4 ) { libspectrum_print_error( LIBSPECTRUM_ERROR_CORRUPT, "tzx_read_stop: not enough data in buffer" ); return LIBSPECTRUM_ERROR_CORRUPT; } /* But then just skip over it, as I don't care what it is */ (*ptr) += 4; /* Get a new block */ error = libspectrum_tape_block_alloc( &block, LIBSPECTRUM_TAPE_BLOCK_STOP48 ); if( error ) return error; /* Put the block into the block list */ error = libspectrum_tape_append_block( tape, block ); if( error ) { libspectrum_tape_block_free( block ); return error; } return LIBSPECTRUM_ERROR_NONE; } static libspectrum_error tzx_read_comment( libspectrum_tape *tape, const libspectrum_byte **ptr, const libspectrum_byte *end ) { libspectrum_tape_block* block; char *text; libspectrum_error error; /* Check the length byte exists */ if( (*ptr) == end ) { libspectrum_print_error( LIBSPECTRUM_ERROR_CORRUPT, "tzx_read_comment: not enough data in buffer" ); return LIBSPECTRUM_ERROR_CORRUPT; } /* Get a new block */ error = libspectrum_tape_block_alloc( &block, LIBSPECTRUM_TAPE_BLOCK_COMMENT ); if( error ) return error; /* Get the actual comment */ error = tzx_read_string( ptr, end, &text ); if( error ) { free( block ); return error; } libspectrum_tape_block_set_text( block, text ); /* Finally, put the block into the block list */ error = libspectrum_tape_append_block( tape, block ); if( error ) { libspectrum_tape_block_free( block ); return error; } return LIBSPECTRUM_ERROR_NONE; } static libspectrum_error tzx_read_message( libspectrum_tape *tape, const libspectrum_byte **ptr, const libspectrum_byte *end ) { libspectrum_tape_block* block; char *text; libspectrum_error error; /* Check the time and length byte exists */ if( end - (*ptr) < 2 ) { libspectrum_print_error( LIBSPECTRUM_ERROR_CORRUPT, "tzx_read_message: not enough data in buffer" ); return LIBSPECTRUM_ERROR_CORRUPT; } /* Get a new block */ error = libspectrum_tape_block_alloc( &block, LIBSPECTRUM_TAPE_BLOCK_MESSAGE ); if( error ) return error; /* Get the time */ libspectrum_tape_block_set_pause( block, **ptr ); (*ptr)++; /* Get the message itself */ error = tzx_read_string( ptr, end, &text ); if( error ) { free( block ); return error; } libspectrum_tape_block_set_text( block, text ); /* Finally, put the block into the block list */ error = libspectrum_tape_append_block( tape, block ); if( error ) { libspectrum_tape_block_free( block ); return error; } return LIBSPECTRUM_ERROR_NONE; } static libspectrum_error tzx_read_archive_info( libspectrum_tape *tape, const libspectrum_byte **ptr, const libspectrum_byte *end ) { libspectrum_tape_block* block; libspectrum_error error; size_t i, count; int *ids; char **strings; /* Check there's enough left in the buffer for the length and the count byte */ if( end - (*ptr) < 3 ) { libspectrum_print_error( LIBSPECTRUM_ERROR_CORRUPT, "tzx_read_archive_info: not enough data in buffer" ); return LIBSPECTRUM_ERROR_CORRUPT; } /* Get a new block */ error = libspectrum_tape_block_alloc( &block, LIBSPECTRUM_TAPE_BLOCK_ARCHIVE_INFO ); if( error ) return error; /* Skip the length, as I don't care about it */ (*ptr) += 2; /* Get the number of string */ count = **ptr; (*ptr)++; libspectrum_tape_block_set_count( block, count ); /* Allocate memory */ ids = malloc( count * sizeof( int ) ); if( !ids ) { free( block ); libspectrum_print_error( LIBSPECTRUM_ERROR_MEMORY, "tzx_read_archive_info: out of memory" ); return LIBSPECTRUM_ERROR_MEMORY; } libspectrum_tape_block_set_ids( block, ids ); strings = malloc( count * sizeof( char* ) ); if( !strings ) { free( ids ); free( block ); libspectrum_print_error( LIBSPECTRUM_ERROR_MEMORY, "tzx_read_archive_info: out of memory" ); return LIBSPECTRUM_ERROR_MEMORY; } libspectrum_tape_block_set_texts( block, strings ); for( i = 0; i < count; i++ ) { /* Must be ID byte and length byte */ if( end - (*ptr) < 2 ) { size_t j; for( j=0; j