/* sna.c: Routines for handling .sna snapshots Copyright (c) 2001-2002 Philip Kendall $Id: sna.c,v 1.26 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" #define LIBSPECTRUM_SNA_HEADER_LENGTH 27 #define LIBSPECTRUM_SNA_128_HEADER_LENGTH 4 static int identify_machine( size_t buffer_length, libspectrum_snap *snap ); static int libspectrum_sna_read_header( const libspectrum_byte *buffer, size_t buffer_length, libspectrum_snap *snap ); static int libspectrum_sna_read_data( const libspectrum_byte *buffer, size_t buffer_length, libspectrum_snap *snap ); static int libspectrum_sna_read_128_header( const libspectrum_byte *buffer, size_t buffer_length, libspectrum_snap *snap ); static int libspectrum_sna_read_128_data( const libspectrum_byte *buffer, size_t buffer_length, libspectrum_snap *snap ); static libspectrum_error write_header( libspectrum_byte **buffer, libspectrum_byte **ptr, size_t *length, libspectrum_byte **sp, libspectrum_snap *snap ); static libspectrum_error write_48k_sna( libspectrum_byte **buffer, libspectrum_byte **ptr, size_t *length, libspectrum_byte *sp, libspectrum_snap *snap ); static libspectrum_error write_128k_sna( libspectrum_byte **buffer, libspectrum_byte **ptr, size_t *length, libspectrum_snap *snap ); static libspectrum_error write_page( libspectrum_byte *buffer, libspectrum_snap *snap, int page ); libspectrum_error libspectrum_sna_read( libspectrum_snap *snap, const libspectrum_byte *buffer, size_t buffer_length ) { return internal_sna_read( snap, buffer, buffer_length ); } libspectrum_error internal_sna_read( libspectrum_snap *snap, const libspectrum_byte *buffer, size_t buffer_length ) { int error; error = identify_machine( buffer_length, snap ); if( error != LIBSPECTRUM_ERROR_NONE ) return error; error = libspectrum_sna_read_header( buffer, buffer_length, snap ); if( error != LIBSPECTRUM_ERROR_NONE ) return error; error = libspectrum_sna_read_data( &buffer[LIBSPECTRUM_SNA_HEADER_LENGTH], buffer_length - LIBSPECTRUM_SNA_HEADER_LENGTH, snap ); if( error != LIBSPECTRUM_ERROR_NONE ) return error; return LIBSPECTRUM_ERROR_NONE; } static int identify_machine( size_t buffer_length, libspectrum_snap *snap ) { switch( buffer_length ) { case 49179: libspectrum_snap_set_machine( snap, LIBSPECTRUM_MACHINE_48 ); break; case 131103: case 147487: libspectrum_snap_set_machine( snap, LIBSPECTRUM_MACHINE_128 ); break; default: libspectrum_print_error( LIBSPECTRUM_ERROR_CORRUPT, "libspectrum_sna_identify: unknown length" ); return LIBSPECTRUM_ERROR_CORRUPT; } return LIBSPECTRUM_ERROR_NONE; } static int libspectrum_sna_read_header( const libspectrum_byte *buffer, size_t buffer_length, libspectrum_snap *snap ) { int iff; if( buffer_length < LIBSPECTRUM_SNA_HEADER_LENGTH ) { libspectrum_print_error( LIBSPECTRUM_ERROR_CORRUPT, "libspectrum_sna_read_header: not enough data in buffer" ); return LIBSPECTRUM_ERROR_CORRUPT; } libspectrum_snap_set_a ( snap, buffer[22] ); libspectrum_snap_set_f ( snap, buffer[21] ); libspectrum_snap_set_bc ( snap, buffer[13] + buffer[14]*0x100 ); libspectrum_snap_set_de ( snap, buffer[11] + buffer[12]*0x100 ); libspectrum_snap_set_hl ( snap, buffer[ 9] + buffer[10]*0x100 ); libspectrum_snap_set_a_ ( snap, buffer[ 8] ); libspectrum_snap_set_f_ ( snap, buffer[ 7] ); libspectrum_snap_set_bc_( snap, buffer[ 5] + buffer[ 6]*0x100 ); libspectrum_snap_set_de_( snap, buffer[ 3] + buffer[ 4]*0x100 ); libspectrum_snap_set_hl_( snap, buffer[ 1] + buffer[ 2]*0x100 ); libspectrum_snap_set_ix ( snap, buffer[17] + buffer[18]*0x100 ); libspectrum_snap_set_iy ( snap, buffer[15] + buffer[16]*0x100 ); libspectrum_snap_set_i ( snap, buffer[ 0] ); libspectrum_snap_set_r ( snap, buffer[20] ); libspectrum_snap_set_pc ( snap, buffer[ 6] + buffer[ 7]*0x100 ); libspectrum_snap_set_sp ( snap, buffer[23] + buffer[24]*0x100 ); iff = ( buffer[19] & 0x04 ) ? 1 : 0; libspectrum_snap_set_iff1( snap, iff ); libspectrum_snap_set_iff2( snap, iff ); libspectrum_snap_set_im( snap, buffer[25] & 0x03 ); libspectrum_snap_set_out_ula( snap, buffer[26] & 0x07 ); return LIBSPECTRUM_ERROR_NONE; } static int libspectrum_sna_read_data( const libspectrum_byte *buffer, size_t buffer_length, libspectrum_snap *snap ) { int error; int offset; int page; int i,j; if( buffer_length < 0xc000 ) { libspectrum_print_error( LIBSPECTRUM_ERROR_CORRUPT, "libspectrum_sna_read_data: not enough data in buffer" ); return LIBSPECTRUM_ERROR_CORRUPT; } switch( libspectrum_snap_machine( snap ) ) { case LIBSPECTRUM_MACHINE_48: /* Rescue PC from the stack */ offset = libspectrum_snap_sp( snap ) - 0x4000; libspectrum_snap_set_pc( snap, buffer[offset] + 0x100 * buffer[offset+1] ); /* Increase SP as PC has been unstacked */ libspectrum_snap_set_sp( snap, libspectrum_snap_sp( snap ) + 2 ); /* And split the pages up */ error = libspectrum_split_to_48k_pages( snap, buffer ); if( error != LIBSPECTRUM_ERROR_NONE ) return error; break; case LIBSPECTRUM_MACHINE_128: for( i=0; i<8; i++ ) { libspectrum_byte *ram; ram = malloc( 0x4000 * sizeof( libspectrum_byte ) ); if( ram == NULL ) { for( j = 0; j < i; j++ ) { free( libspectrum_snap_pages( snap, i ) ); libspectrum_snap_set_pages( snap, i, NULL ); } libspectrum_print_error( LIBSPECTRUM_ERROR_MEMORY, "libspectrum_sna_read_data: out of memory" ); return LIBSPECTRUM_ERROR_MEMORY; } libspectrum_snap_set_pages( snap, i, ram ); } memcpy( libspectrum_snap_pages( snap, 5 ), &buffer[0x0000], 0x4000 ); memcpy( libspectrum_snap_pages( snap, 2 ), &buffer[0x4000], 0x4000 ); error = libspectrum_sna_read_128_header( buffer + 0xc000, buffer_length - 0xc000, snap ); if( error != LIBSPECTRUM_ERROR_NONE ) return error; page = libspectrum_snap_out_128_memoryport( snap ) & 0x07; if( page == 5 || page == 2 ) { if( memcmp( libspectrum_snap_pages( snap, page ), &buffer[0x8000], 0x4000 ) ) { libspectrum_print_error( LIBSPECTRUM_ERROR_CORRUPT, "libspectrum_sna_read_data: duplicated page not identical" ); return LIBSPECTRUM_ERROR_CORRUPT; } } else { memcpy( libspectrum_snap_pages( snap, page ), &buffer[0x8000], 0x4000 ); } buffer += 0xc000 + LIBSPECTRUM_SNA_128_HEADER_LENGTH; buffer_length -= 0xc000 + LIBSPECTRUM_SNA_128_HEADER_LENGTH; error = libspectrum_sna_read_128_data( buffer, buffer_length, snap ); if( error != LIBSPECTRUM_ERROR_NONE ) return error; break; default: libspectrum_print_error( LIBSPECTRUM_ERROR_LOGIC, "libspectrum_sna_read_data: unknown machine" ); return LIBSPECTRUM_ERROR_LOGIC; } return LIBSPECTRUM_ERROR_NONE; } static int libspectrum_sna_read_128_header( const libspectrum_byte *buffer, size_t buffer_length, libspectrum_snap *snap ) { if( buffer_length < 4 ) { libspectrum_print_error( LIBSPECTRUM_ERROR_CORRUPT, "libspectrum_sna_read_128_header: not enough data in buffer" ); return LIBSPECTRUM_ERROR_CORRUPT; } libspectrum_snap_set_pc( snap, buffer[0] + 0x100 * buffer[1] ); libspectrum_snap_set_out_128_memoryport( snap, buffer[2] ); return LIBSPECTRUM_ERROR_NONE; } static int libspectrum_sna_read_128_data( const libspectrum_byte *buffer, size_t buffer_length, libspectrum_snap *snap ) { int i, page; page = libspectrum_snap_out_128_memoryport( snap ) & 0x07; for( i=0; i<=7; i++ ) { if( i==2 || i==5 || i==page ) continue; /* Already got this page */ /* Check we've still got some data to read */ if( buffer_length < 0x4000 ) { libspectrum_print_error( LIBSPECTRUM_ERROR_CORRUPT, "libspectrum_sna_read_128_data: not enough data in buffer" ); return LIBSPECTRUM_ERROR_CORRUPT; } /* Copy the data across */ memcpy( libspectrum_snap_pages( snap, i ), buffer, 0x4000 ); /* And update what we're looking at here */ buffer += 0x4000; buffer_length -= 0x4000; } return LIBSPECTRUM_ERROR_NONE; } libspectrum_error libspectrum_sna_write( libspectrum_byte **buffer, size_t *length, int *out_flags, libspectrum_snap *snap, int in_flags GCC_UNUSED ) { libspectrum_error error; libspectrum_byte *ptr, *sp; /* Minor info loss already due to things like tstate count, halted state, etc which are not stored in .sna format */ *out_flags = LIBSPECTRUM_FLAG_SNAPSHOT_MINOR_INFO_LOSS; /* We don't save the ZXATASP or ZXCF at all */ if( libspectrum_snap_zxatasp_active( snap ) ) *out_flags |= LIBSPECTRUM_FLAG_SNAPSHOT_MAJOR_INFO_LOSS; if( libspectrum_snap_zxcf_active( snap ) ) *out_flags |= LIBSPECTRUM_FLAG_SNAPSHOT_MAJOR_INFO_LOSS; /* We don't save the Interface II ROM at all */ if( libspectrum_snap_interface2_active( snap ) ) *out_flags |= LIBSPECTRUM_FLAG_SNAPSHOT_MAJOR_INFO_LOSS; /* We don't save the Timex Dock at all */ if( libspectrum_snap_dock_active( snap ) ) *out_flags |= LIBSPECTRUM_FLAG_SNAPSHOT_MAJOR_INFO_LOSS; ptr = *buffer; error = write_header( buffer, &ptr, length, &sp, snap ); if( error ) return error; switch( libspectrum_snap_machine( snap ) ) { case LIBSPECTRUM_MACHINE_TC2048: case LIBSPECTRUM_MACHINE_TC2068: case LIBSPECTRUM_MACHINE_TS2068: *out_flags |= LIBSPECTRUM_FLAG_SNAPSHOT_MAJOR_INFO_LOSS; /* Fall through */ case LIBSPECTRUM_MACHINE_16: case LIBSPECTRUM_MACHINE_48: error = write_48k_sna( buffer, &ptr, length, sp, snap ); break; case LIBSPECTRUM_MACHINE_PLUS2A: case LIBSPECTRUM_MACHINE_PLUS3: case LIBSPECTRUM_MACHINE_PLUS3E: case LIBSPECTRUM_MACHINE_PENT: case LIBSPECTRUM_MACHINE_SCORP: case LIBSPECTRUM_MACHINE_SE: *out_flags |= LIBSPECTRUM_FLAG_SNAPSHOT_MAJOR_INFO_LOSS; /* Fall through */ case LIBSPECTRUM_MACHINE_PLUS2: case LIBSPECTRUM_MACHINE_128: error = write_128k_sna( buffer, &ptr, length, snap ); break; case LIBSPECTRUM_MACHINE_UNKNOWN: libspectrum_print_error( LIBSPECTRUM_ERROR_LOGIC, "Emulated machine type is set to 'unknown'!" ); return LIBSPECTRUM_ERROR_LOGIC; } if( error ) return error; /* Set length to be actual length, not allocated length */ *length = ptr - *buffer; return LIBSPECTRUM_ERROR_NONE; } static libspectrum_error write_header( libspectrum_byte **buffer, libspectrum_byte **ptr, size_t *length, libspectrum_byte **sp, libspectrum_snap *snap ) { libspectrum_error error; error = libspectrum_make_room( buffer, LIBSPECTRUM_SNA_HEADER_LENGTH, ptr, length ); if( error ) return error; *(*ptr)++ = libspectrum_snap_i ( snap ); libspectrum_write_word( ptr, libspectrum_snap_hl_( snap ) ); libspectrum_write_word( ptr, libspectrum_snap_de_( snap ) ); libspectrum_write_word( ptr, libspectrum_snap_bc_( snap ) ); *(*ptr)++ = libspectrum_snap_f_( snap ); *(*ptr)++ = libspectrum_snap_a_( snap ); libspectrum_write_word( ptr, libspectrum_snap_hl ( snap ) ); libspectrum_write_word( ptr, libspectrum_snap_de ( snap ) ); libspectrum_write_word( ptr, libspectrum_snap_bc ( snap ) ); libspectrum_write_word( ptr, libspectrum_snap_iy ( snap ) ); libspectrum_write_word( ptr, libspectrum_snap_ix ( snap ) ); *(*ptr)++ = libspectrum_snap_iff2( snap ) ? 0x04 : 0x00; *(*ptr)++ = libspectrum_snap_r ( snap ); *(*ptr)++ = libspectrum_snap_f ( snap ); *(*ptr)++ = libspectrum_snap_a ( snap ); /* Store this for later */ *sp = *ptr; libspectrum_write_word( ptr, libspectrum_snap_sp ( snap ) ); *(*ptr)++ = libspectrum_snap_im( snap ); *(*ptr)++ = libspectrum_snap_out_ula( snap ) & 0x07; return LIBSPECTRUM_ERROR_NONE; } static libspectrum_error write_48k_sna( libspectrum_byte **buffer, libspectrum_byte **ptr, size_t *length, libspectrum_byte *sp, libspectrum_snap *snap ) { libspectrum_error error; libspectrum_byte *stack; /* Must have somewhere in RAM to store some registers */ if( libspectrum_snap_sp( snap ) < 0x4002 ) { libspectrum_print_error( LIBSPECTRUM_ERROR_INVALID, "SP is too low (0x%04x) to stack registers", libspectrum_snap_sp( snap ) ); return LIBSPECTRUM_ERROR_INVALID; } error = libspectrum_make_room( buffer, 0xc000, ptr, length ); if( error ) return error; error = write_page( &( (*ptr)[ 0x0000 ] ), snap, 5 ); if( error ) return error; error = write_page( &( (*ptr)[ 0x4000 ] ), snap, 2 ); if( error ) return error; error = write_page( &( (*ptr)[ 0x8000 ] ), snap, 0 ); if( error ) return error; /* Overwrite a bit of memory with SP */ stack = &( (*ptr)[ libspectrum_snap_sp( snap ) - 0x4000 - 2 ] ); libspectrum_write_word( &stack, libspectrum_snap_pc( snap ) ); *ptr += 0xc000; /* Store the new value of SP */ libspectrum_write_word( &sp, libspectrum_snap_sp( snap ) ); return LIBSPECTRUM_ERROR_NONE; } static libspectrum_error write_128k_sna( libspectrum_byte **buffer, libspectrum_byte **ptr, size_t *length, libspectrum_snap *snap ) { size_t i, page; libspectrum_error error; page = libspectrum_snap_out_128_memoryport( snap ) & 0x07; error = libspectrum_make_room( buffer, 0xc000 + LIBSPECTRUM_SNA_128_HEADER_LENGTH, ptr, length ); if( error ) return error; error = write_page( *ptr, snap, 5 ); (*ptr) += 0x4000; if( error ) return error; error = write_page( *ptr, snap, 2 ); (*ptr) += 0x4000; if( error ) return error; error = write_page( *ptr, snap, page ); (*ptr) += 0x4000; if( error ) return error; libspectrum_write_word( ptr, libspectrum_snap_pc( snap ) ); *(*ptr)++ = libspectrum_snap_out_128_memoryport( snap ); *(*ptr)++ = '\0'; for( i = 0; i < 8; i++ ) { /* Already written pages 5, 2 and whatever's paged in */ if( i == 5 || i == 2 || i == page ) continue; error = libspectrum_make_room( buffer, 0x4000, ptr, length ); if( error ) return error; error = write_page( *ptr, snap, i ); (*ptr) += 0x4000; if( error ) return error; } return LIBSPECTRUM_ERROR_NONE; } static libspectrum_error write_page( libspectrum_byte *buffer, libspectrum_snap *snap, int page ) { libspectrum_byte *ram; ram = libspectrum_snap_pages( snap, page ); if( ram ) { memcpy( buffer, ram, 0x4000 ); } else { memset( buffer, 0xff, 0x4000 ); } return LIBSPECTRUM_ERROR_NONE; }