/* szx.c: Routines for .szx snapshots Copyright (c) 1998,2003 Philip Kendall Copyright (c) 2004 Fredrick Meunier $Id: szx.c,v 1.43 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" /* I've had to make some assumptions about the 'right' way to do some things in the .szx format due to the documentation not being up to scratch: * http://www.spectaculator.com/docs/zx-state/header.shtml says "chMinorVersion Minor version number of the file format. Currently 1." despite the current version of the format being 1.2. libspectrum writes .szx files with a minor version number of 3 as they contain blocks not in the v1.2 specification. * The ZXSTSPECREGS block says that the ch1ffd member should be set to zero for machines other than the +2A/+3. libspectrum makes this field non-zero for Scorpion emulation. * In a ZXSTRAMPAGE block, the Timex machines should save the same pages as the 48K machine, and RAM pages 8-15 are valid for Scorpion emulation. Places where these points are used in the code are marked with [Assumption]. I've mentioned these points to Jonathan Needle ('maintainer' of the .szx format), but he hasn't replied :-( */ /* The machine numbers used in the .szx format */ typedef enum szx_machine_type { SZX_MACHINE_16 = 0, SZX_MACHINE_48, SZX_MACHINE_128, SZX_MACHINE_PLUS2, SZX_MACHINE_PLUS2A, SZX_MACHINE_PLUS3, SZX_MACHINE_PLUS3E, SZX_MACHINE_PENTAGON, SZX_MACHINE_TC2048, SZX_MACHINE_TC2068, SZX_MACHINE_SCORPION, SZX_MACHINE_SE, SZX_MACHINE_TS2068, } szx_machine_type; static const char *signature = "ZXST"; static const size_t signature_length = 4; static const libspectrum_byte SZX_VERSION_MAJOR = 1; static const libspectrum_byte SZX_VERSION_MINOR = 3; /* Constants etc for each chunk type */ #define ZXSTBID_CREATOR "CRTR" #define ZXSTBID_Z80REGS "Z80R" static const libspectrum_byte ZXSTZF_EILAST = 1; static const libspectrum_byte ZXSTZF_HALTED = 2; #define ZXSTBID_SPECREGS "SPCR" #define ZXSTBID_RAMPAGE "RAMP" static const libspectrum_word ZXSTRF_COMPRESSED = 1; #define ZXSTBID_AY "AY\0\0" #define ZXSTBID_MULTIFACE "MFCE" #define ZXSTBID_USPEECH "USPE" #define ZXSTBID_SPECDRUM "DRUM" #define ZXSTBID_ZXTAPE "TAPE" #define ZXSTBID_KEYBOARD "KEYB" static const libspectrum_dword ZXSTKF_ISSUE2 = 1; #define ZXSTBID_JOYSTICK "JOY\0" static const libspectrum_dword ZXSTJOYF_ALWAYSPORT31 = 1; typedef enum szx_joystick_type { ZXJT_KEMPSTON = 0, ZXJT_FULLER, ZXJT_CURSOR, ZXJT_SINCLAIR1, ZXJT_SINCLAIR2, ZXJT_SPECTRUMPLUS, ZXJT_TIMEX1, ZXJT_TIMEX2, ZXJT_NONE, } szx_joystick_type; #define ZXSTBID_IF2ROM "IF2R" #define ZXSTBID_MOUSE "AMXM" #define ZXSTBID_ROM "ROM\0" #define ZXSTBID_ZXPRINTER "ZXPR" #define ZXSTBID_IF1 "IF1\0" #define ZXSTBID_MICRODRIVE "MDRV" #define ZXSTBID_PLUS3DISK "+3\0\0" #define ZXSTBID_DSKFILE "DSK\0" #define ZXSTBID_TIMEXREGS "SCLD" #define ZXSTBID_BETA128 "B128" static const libspectrum_dword ZXSTBETAF_CONNECTED = 1; static const libspectrum_dword ZXSTBETAF_PAGED = 4; static const libspectrum_dword ZXSTBETAF_SEEKLOWER = 16; #define ZXSTBID_BETADISK "BDSK" #define ZXSTBID_GS "GS\0\0" #define ZXSTBID_GSRAMPAGE "GSRP" #define ZXSTBID_COVOX "COVX" #define ZXSTBID_DOCK "DOCK" static const libspectrum_word ZXSTDOCKF_RAM = 2; static const libspectrum_word ZXSTDOCKF_EXROMDOCK = 4; #define ZXSTBID_ZXATASP "ZXAT" static const libspectrum_word ZXSTZXATF_UPLOAD = 1; static const libspectrum_word ZXSTZXATF_WRITEPROTECT = 2; #define ZXSTBID_ZXATASPRAMPAGE "ATRP" #define ZXSTBID_ZXCF "ZXCF" static const libspectrum_word ZXSTZXCFF_UPLOAD = 1; #define ZXSTBID_ZXCFRAMPAGE "CFRP" static libspectrum_error read_chunk( libspectrum_snap *snap, libspectrum_word version, const libspectrum_byte **buffer, const libspectrum_byte *end ); typedef libspectrum_error (*read_chunk_fn)( libspectrum_snap *snap, libspectrum_word version, const libspectrum_byte **buffer, const libspectrum_byte *end, size_t data_length ); static libspectrum_error write_file_header( libspectrum_byte **buffer, libspectrum_byte **ptr, size_t *length, int *out_flags, libspectrum_snap *snap ); static libspectrum_error write_crtr_chunk( libspectrum_byte **bufer, libspectrum_byte **ptr, size_t *length, libspectrum_creator *creator ); static libspectrum_error write_z80r_chunk( libspectrum_byte **buffer, libspectrum_byte **ptr, size_t *length, libspectrum_snap *snap ); static libspectrum_error write_spcr_chunk( libspectrum_byte **buffer, libspectrum_byte **ptr, size_t *length, libspectrum_snap *snap ); static libspectrum_error write_joy_chunk( libspectrum_byte **buffer, libspectrum_byte **ptr, size_t *length, int *out_flags, libspectrum_snap *snap ); static libspectrum_error write_keyb_chunk( libspectrum_byte **buffer, libspectrum_byte **ptr, size_t *length, int *out_flags, libspectrum_snap *snap ); static libspectrum_error write_ram_pages( libspectrum_byte **buffer, libspectrum_byte **ptr, size_t *length, libspectrum_snap *snap, int compress ); static libspectrum_error write_ramp_chunk( libspectrum_byte **buffer, libspectrum_byte **ptr, size_t *length, libspectrum_snap *snap, int page, int compress ); static libspectrum_error write_ram_page( libspectrum_byte **buffer, libspectrum_byte **ptr, size_t *length, const char *id, const libspectrum_byte *data, size_t data_length, int page, int compress, int extra_flags ); static libspectrum_error write_ay_chunk( libspectrum_byte **buffer, libspectrum_byte **ptr, size_t *length, libspectrum_snap *snap ); static libspectrum_error write_scld_chunk( libspectrum_byte **buffer, libspectrum_byte **ptr, size_t *length, libspectrum_snap *snap ); static libspectrum_error write_b128_chunk( libspectrum_byte **buffer, libspectrum_byte **ptr, size_t *length, libspectrum_snap *snap ); static libspectrum_error write_zxat_chunk( libspectrum_byte **buffer, libspectrum_byte **ptr, size_t *length, libspectrum_snap *snap ); static libspectrum_error write_atrp_chunk( libspectrum_byte **buffer, libspectrum_byte **ptr, size_t *length, libspectrum_snap *snap, int page, int compress ); static libspectrum_error write_zxcf_chunk( libspectrum_byte **buffer, libspectrum_byte **ptr, size_t *length, libspectrum_snap *snap ); static libspectrum_error write_cfrp_chunk( libspectrum_byte **buffer, libspectrum_byte **ptr, size_t *length, libspectrum_snap *snap, int page, int compress ); #ifdef HAVE_ZLIB_H static libspectrum_error write_if2r_chunk( libspectrum_byte **buffer, libspectrum_byte **ptr, size_t *length, libspectrum_snap *snap ); #endif /* #ifdef HAVE_ZLIB_H */ static libspectrum_error write_dock_chunk( libspectrum_byte **buffer, libspectrum_byte **ptr, size_t *length, libspectrum_snap *snap, int exrom_dock, const libspectrum_byte *data, int page, int writeable, int compress ); static libspectrum_error write_chunk_header( libspectrum_byte **buffer, libspectrum_byte **ptr, size_t *length, const char *id, libspectrum_dword block_length ); static libspectrum_error read_ram_page( libspectrum_byte **data, size_t *page, const libspectrum_byte **buffer, size_t data_length, size_t uncompressed_length, libspectrum_word *flags ) { #ifdef HAVE_ZLIB_H libspectrum_error error; #endif /* #ifdef HAVE_ZLIB_H */ if( data_length < 3 ) { libspectrum_print_error( LIBSPECTRUM_ERROR_UNKNOWN, "%s:read_ram_page: length %lu too short", __FILE__, (unsigned long)data_length ); return LIBSPECTRUM_ERROR_UNKNOWN; } *flags = libspectrum_read_word( buffer ); *page = **buffer; (*buffer)++; if( *flags & ZXSTRF_COMPRESSED ) { #ifdef HAVE_ZLIB_H error = libspectrum_zlib_inflate( *buffer, data_length - 3, data, &uncompressed_length ); if( error ) return error; *buffer += data_length - 3; #else /* #ifdef HAVE_ZLIB_H */ libspectrum_print_error( LIBSPECTRUM_ERROR_UNKNOWN, "%s:read_ram_page: zlib needed for decompression\n", __FILE__ ); return LIBSPECTRUM_ERROR_UNKNOWN; #endif /* #ifdef HAVE_ZLIB_H */ } else { if( data_length < 3 + uncompressed_length ) { libspectrum_print_error( LIBSPECTRUM_ERROR_UNKNOWN, "%s:read_ram_page: length %lu too short", __FILE__, (unsigned long)data_length ); return LIBSPECTRUM_ERROR_UNKNOWN; } *data = malloc( uncompressed_length * sizeof( libspectrum_byte ) ); if( !( *data ) ) { libspectrum_print_error( LIBSPECTRUM_ERROR_MEMORY, "%s:read_ram_page: out of memory at %d", __FILE__, __LINE__ ); return LIBSPECTRUM_ERROR_MEMORY; } memcpy( *data, *buffer, uncompressed_length ); *buffer += uncompressed_length; } return LIBSPECTRUM_ERROR_NONE; } static libspectrum_error read_atrp_chunk( libspectrum_snap *snap, libspectrum_word version GCC_UNUSED, const libspectrum_byte **buffer, const libspectrum_byte *end GCC_UNUSED, size_t data_length ) { libspectrum_byte *data; size_t page; libspectrum_error error; libspectrum_word flags; error = read_ram_page( &data, &page, buffer, data_length, 0x4000, &flags ); if( error ) return error; if( page >= SNAPSHOT_ZXATASP_PAGES ) { libspectrum_print_error( LIBSPECTRUM_ERROR_CORRUPT, "%s:read_atrp_chunk: unknown page number %lu", __FILE__, (unsigned long)page ); free( data ); return LIBSPECTRUM_ERROR_CORRUPT; } libspectrum_snap_set_zxatasp_ram( snap, page, data ); return LIBSPECTRUM_ERROR_NONE; } static libspectrum_error read_ay_chunk( libspectrum_snap *snap, libspectrum_word version GCC_UNUSED, const libspectrum_byte **buffer, const libspectrum_byte *end GCC_UNUSED, size_t data_length ) { size_t i; if( data_length != 18 ) { libspectrum_print_error( LIBSPECTRUM_ERROR_UNKNOWN, "szx_read_ay_chunk: unknown length %lu", (unsigned long)data_length ); return LIBSPECTRUM_ERROR_UNKNOWN; } (*buffer)++; /* Skip the flags */ libspectrum_snap_set_out_ay_registerport( snap, **buffer ); (*buffer)++; for( i = 0; i < 16; i++ ) { libspectrum_snap_set_ay_registers( snap, i, **buffer ); (*buffer)++; } return LIBSPECTRUM_ERROR_NONE; } static libspectrum_error read_b128_chunk( libspectrum_snap *snap, libspectrum_word version GCC_UNUSED, const libspectrum_byte **buffer, const libspectrum_byte *end GCC_UNUSED, size_t data_length ) { libspectrum_dword flags; if( data_length < 10 ) { libspectrum_print_error( LIBSPECTRUM_ERROR_UNKNOWN, "szx_read_b128_chunk: length %lu too short", (unsigned long)data_length ); return LIBSPECTRUM_ERROR_UNKNOWN; } flags = libspectrum_read_dword( buffer ); libspectrum_snap_set_beta_paged( snap, flags & ZXSTBETAF_PAGED ); libspectrum_snap_set_beta_direction( snap, !( flags & ZXSTBETAF_SEEKLOWER ) ); (*buffer)++; /* Skip the number of drives */ libspectrum_snap_set_beta_system( snap, **buffer ); (*buffer)++; libspectrum_snap_set_beta_track ( snap, **buffer ); (*buffer)++; libspectrum_snap_set_beta_sector( snap, **buffer ); (*buffer)++; libspectrum_snap_set_beta_data ( snap, **buffer ); (*buffer)++; libspectrum_snap_set_beta_status( snap, **buffer ); (*buffer)++; /* Skip any extra data (most likely a custom ROM) */ *buffer += data_length - 10; return LIBSPECTRUM_ERROR_NONE; } static libspectrum_error read_cfrp_chunk( libspectrum_snap *snap, libspectrum_word version GCC_UNUSED, const libspectrum_byte **buffer, const libspectrum_byte *end GCC_UNUSED, size_t data_length ) { libspectrum_byte *data; size_t page; libspectrum_error error; libspectrum_word flags; error = read_ram_page( &data, &page, buffer, data_length, 0x4000, &flags ); if( error ) return error; if( page >= SNAPSHOT_ZXCF_PAGES ) { libspectrum_print_error( LIBSPECTRUM_ERROR_CORRUPT, "%s:read_cfrp_chunk: unknown page number %lu", __FILE__, (unsigned long)page ); free( data ); return LIBSPECTRUM_ERROR_CORRUPT; } libspectrum_snap_set_zxcf_ram( snap, page, data ); return LIBSPECTRUM_ERROR_NONE; } static void add_joystick( libspectrum_snap *snap, libspectrum_joystick type, int inputs ) { size_t i; size_t num_joysticks = libspectrum_snap_joystick_active_count( snap ); for( i = 0; i < num_joysticks; i++ ) { if( libspectrum_snap_joystick_list( snap, i ) == type ) { libspectrum_snap_set_joystick_inputs( snap, i, inputs | libspectrum_snap_joystick_inputs( snap, i ) ); return; } } libspectrum_snap_set_joystick_list( snap, num_joysticks, type ); libspectrum_snap_set_joystick_inputs( snap, num_joysticks, inputs ); libspectrum_snap_set_joystick_active_count( snap, num_joysticks + 1 ); } static libspectrum_error read_joy_chunk( libspectrum_snap *snap, libspectrum_word version, const libspectrum_byte **buffer, const libspectrum_byte *end GCC_UNUSED, size_t data_length ) { libspectrum_dword flags; if( data_length != 6 ) { libspectrum_print_error( LIBSPECTRUM_ERROR_UNKNOWN, "%s:read_joy_chunk: unknown length %lu", __FILE__, (unsigned long)data_length ); return LIBSPECTRUM_ERROR_UNKNOWN; } flags = libspectrum_read_dword( buffer ); if( flags & ZXSTJOYF_ALWAYSPORT31 ) { add_joystick( snap, LIBSPECTRUM_JOYSTICK_KEMPSTON, LIBSPECTRUM_JOYSTICK_INPUT_NONE ); } switch( **buffer ) { case ZXJT_KEMPSTON: add_joystick( snap, LIBSPECTRUM_JOYSTICK_KEMPSTON, LIBSPECTRUM_JOYSTICK_INPUT_JOYSTICK_1 ); break; case ZXJT_FULLER: add_joystick( snap, LIBSPECTRUM_JOYSTICK_FULLER, LIBSPECTRUM_JOYSTICK_INPUT_JOYSTICK_1 ); break; case ZXJT_CURSOR: add_joystick( snap, LIBSPECTRUM_JOYSTICK_CURSOR, LIBSPECTRUM_JOYSTICK_INPUT_JOYSTICK_1 ); break; case ZXJT_SINCLAIR1: add_joystick( snap, LIBSPECTRUM_JOYSTICK_SINCLAIR_1, LIBSPECTRUM_JOYSTICK_INPUT_JOYSTICK_1 ); break; case ZXJT_SINCLAIR2: add_joystick( snap, LIBSPECTRUM_JOYSTICK_SINCLAIR_2, LIBSPECTRUM_JOYSTICK_INPUT_JOYSTICK_1 ); break; case ZXJT_TIMEX1: add_joystick( snap, LIBSPECTRUM_JOYSTICK_TIMEX_1, LIBSPECTRUM_JOYSTICK_INPUT_JOYSTICK_1 ); break; case ZXJT_TIMEX2: add_joystick( snap, LIBSPECTRUM_JOYSTICK_TIMEX_2, LIBSPECTRUM_JOYSTICK_INPUT_JOYSTICK_1 ); break; case ZXJT_NONE: break; } (*buffer)++; switch( **buffer ) { case ZXJT_KEMPSTON: add_joystick( snap, LIBSPECTRUM_JOYSTICK_KEMPSTON, LIBSPECTRUM_JOYSTICK_INPUT_JOYSTICK_2 ); break; case ZXJT_FULLER: add_joystick( snap, LIBSPECTRUM_JOYSTICK_FULLER, LIBSPECTRUM_JOYSTICK_INPUT_JOYSTICK_2 ); break; case ZXJT_CURSOR: add_joystick( snap, LIBSPECTRUM_JOYSTICK_CURSOR, LIBSPECTRUM_JOYSTICK_INPUT_JOYSTICK_2 ); break; case ZXJT_SINCLAIR1: add_joystick( snap, LIBSPECTRUM_JOYSTICK_SINCLAIR_1, LIBSPECTRUM_JOYSTICK_INPUT_JOYSTICK_2 ); break; case ZXJT_SINCLAIR2: add_joystick( snap, LIBSPECTRUM_JOYSTICK_SINCLAIR_2, LIBSPECTRUM_JOYSTICK_INPUT_JOYSTICK_2 ); break; case ZXJT_TIMEX1: add_joystick( snap, LIBSPECTRUM_JOYSTICK_TIMEX_1, LIBSPECTRUM_JOYSTICK_INPUT_JOYSTICK_2 ); break; case ZXJT_TIMEX2: add_joystick( snap, LIBSPECTRUM_JOYSTICK_TIMEX_2, LIBSPECTRUM_JOYSTICK_INPUT_JOYSTICK_2 ); break; case ZXJT_NONE: break; } (*buffer)++; return LIBSPECTRUM_ERROR_NONE; } static libspectrum_error read_keyb_chunk( libspectrum_snap *snap, libspectrum_word version, const libspectrum_byte **buffer, const libspectrum_byte *end GCC_UNUSED, size_t data_length ) { size_t expected_length; libspectrum_dword flags; expected_length = version >= 0x0101 ? 5 : 4; if( data_length != expected_length ) { libspectrum_print_error( LIBSPECTRUM_ERROR_UNKNOWN, "%s:read_keyb_chunk: unknown length %lu", __FILE__, (unsigned long)data_length ); return LIBSPECTRUM_ERROR_UNKNOWN; } flags = libspectrum_read_dword( buffer ); libspectrum_snap_set_issue2( snap, flags & ZXSTKF_ISSUE2 ); if( expected_length >= 5 ) { switch( **buffer ) { case ZXJT_KEMPSTON: add_joystick( snap, LIBSPECTRUM_JOYSTICK_KEMPSTON, LIBSPECTRUM_JOYSTICK_INPUT_KEYBOARD ); break; case ZXJT_FULLER: add_joystick( snap, LIBSPECTRUM_JOYSTICK_FULLER, LIBSPECTRUM_JOYSTICK_INPUT_KEYBOARD ); break; case ZXJT_CURSOR: add_joystick( snap, LIBSPECTRUM_JOYSTICK_CURSOR, LIBSPECTRUM_JOYSTICK_INPUT_KEYBOARD ); break; case ZXJT_SINCLAIR1: add_joystick( snap, LIBSPECTRUM_JOYSTICK_SINCLAIR_1, LIBSPECTRUM_JOYSTICK_INPUT_KEYBOARD ); break; case ZXJT_SINCLAIR2: add_joystick( snap, LIBSPECTRUM_JOYSTICK_SINCLAIR_2, LIBSPECTRUM_JOYSTICK_INPUT_KEYBOARD ); break; case ZXJT_TIMEX1: add_joystick( snap, LIBSPECTRUM_JOYSTICK_TIMEX_1, LIBSPECTRUM_JOYSTICK_INPUT_KEYBOARD ); break; case ZXJT_TIMEX2: add_joystick( snap, LIBSPECTRUM_JOYSTICK_TIMEX_2, LIBSPECTRUM_JOYSTICK_INPUT_KEYBOARD ); break; case ZXJT_SPECTRUMPLUS: /* Actually, no joystick at all */ break; } (*buffer)++; } return LIBSPECTRUM_ERROR_NONE; } static libspectrum_error read_ramp_chunk( libspectrum_snap *snap, libspectrum_word version GCC_UNUSED, const libspectrum_byte **buffer, const libspectrum_byte *end GCC_UNUSED, size_t data_length ) { libspectrum_byte *data; size_t page; libspectrum_error error; libspectrum_word flags; error = read_ram_page( &data, &page, buffer, data_length, 0x4000, &flags ); if( error ) return error; if( page > 15 ) { libspectrum_print_error( LIBSPECTRUM_ERROR_CORRUPT, "%s:read_ramp_chunk: unknown page number %lu", __FILE__, (unsigned long)page ); free( data ); return LIBSPECTRUM_ERROR_CORRUPT; } libspectrum_snap_set_pages( snap, page, data ); return LIBSPECTRUM_ERROR_NONE; } static libspectrum_error read_scld_chunk( libspectrum_snap *snap, libspectrum_word version GCC_UNUSED, const libspectrum_byte **buffer, const libspectrum_byte *end GCC_UNUSED, size_t data_length ) { if( data_length != 2 ) { libspectrum_print_error( LIBSPECTRUM_ERROR_UNKNOWN, "szx_read_scld_chunk: unknown length %lu", (unsigned long)data_length ); return LIBSPECTRUM_ERROR_UNKNOWN; } libspectrum_snap_set_out_scld_hsr( snap, **buffer ); (*buffer)++; libspectrum_snap_set_out_scld_dec( snap, **buffer ); (*buffer)++; return LIBSPECTRUM_ERROR_NONE; } static libspectrum_error read_spcr_chunk( libspectrum_snap *snap, libspectrum_word version, const libspectrum_byte **buffer, const libspectrum_byte *end GCC_UNUSED, size_t data_length ) { libspectrum_byte out_ula; if( data_length != 8 ) { libspectrum_print_error( LIBSPECTRUM_ERROR_UNKNOWN, "szx_read_spcr_chunk: unknown length %lu", (unsigned long)data_length ); return LIBSPECTRUM_ERROR_UNKNOWN; } out_ula = **buffer & 0x07; (*buffer)++; libspectrum_snap_set_out_128_memoryport( snap, **buffer ); (*buffer)++; libspectrum_snap_set_out_plus3_memoryport( snap, **buffer ); (*buffer)++; if( version >= 0x0101 ) out_ula |= **buffer & 0xf8; (*buffer)++; libspectrum_snap_set_out_ula( snap, out_ula ); *buffer += 4; /* Skip 'reserved' data */ return LIBSPECTRUM_ERROR_NONE; } static libspectrum_error read_z80r_chunk( libspectrum_snap *snap, libspectrum_word version, const libspectrum_byte **buffer, const libspectrum_byte *end GCC_UNUSED, size_t data_length ) { if( data_length != 37 ) { libspectrum_print_error( LIBSPECTRUM_ERROR_UNKNOWN, "szx_read_z80r_chunk: unknown length %lu", (unsigned long)data_length ); return LIBSPECTRUM_ERROR_UNKNOWN; } libspectrum_snap_set_a ( snap, **buffer ); (*buffer)++; libspectrum_snap_set_f ( snap, **buffer ); (*buffer)++; libspectrum_snap_set_bc ( snap, libspectrum_read_word( buffer ) ); libspectrum_snap_set_de ( snap, libspectrum_read_word( buffer ) ); libspectrum_snap_set_hl ( snap, libspectrum_read_word( buffer ) ); libspectrum_snap_set_a_ ( snap, **buffer ); (*buffer)++; libspectrum_snap_set_f_ ( snap, **buffer ); (*buffer)++; libspectrum_snap_set_bc_ ( snap, libspectrum_read_word( buffer ) ); libspectrum_snap_set_de_ ( snap, libspectrum_read_word( buffer ) ); libspectrum_snap_set_hl_ ( snap, libspectrum_read_word( buffer ) ); libspectrum_snap_set_ix ( snap, libspectrum_read_word( buffer ) ); libspectrum_snap_set_iy ( snap, libspectrum_read_word( buffer ) ); libspectrum_snap_set_sp ( snap, libspectrum_read_word( buffer ) ); libspectrum_snap_set_pc ( snap, libspectrum_read_word( buffer ) ); libspectrum_snap_set_i ( snap, **buffer ); (*buffer)++; libspectrum_snap_set_r ( snap, **buffer ); (*buffer)++; libspectrum_snap_set_iff1( snap, **buffer ); (*buffer)++; libspectrum_snap_set_iff2( snap, **buffer ); (*buffer)++; libspectrum_snap_set_im ( snap, **buffer ); (*buffer)++; libspectrum_snap_set_tstates( snap, libspectrum_read_dword( buffer ) ); if( version >= 0x0101 ) { (*buffer)++; /* Skip dwHoldIntReqCycles */ /* Flags; ignore the 'last instruction EI' flag for now */ libspectrum_snap_set_halted( snap, **buffer & ZXSTZF_HALTED ); (*buffer)++; (*buffer)++; /* Skip the hidden register */ (*buffer)++; /* Skip the reserved byte */ } else { *buffer += 4; /* Skip the reserved dword */ } return LIBSPECTRUM_ERROR_NONE; } static libspectrum_error read_zxat_chunk( libspectrum_snap *snap, libspectrum_word version GCC_UNUSED, const libspectrum_byte **buffer, const libspectrum_byte *end GCC_UNUSED, size_t data_length ) { libspectrum_word flags; if( data_length != 8 ) { libspectrum_print_error( LIBSPECTRUM_ERROR_UNKNOWN, "%s:read_zxat_chunk: unknown length %lu", __FILE__, (unsigned long)data_length ); return LIBSPECTRUM_ERROR_UNKNOWN; } libspectrum_snap_set_zxatasp_active( snap, 1 ); flags = libspectrum_read_word( buffer ); libspectrum_snap_set_zxatasp_upload( snap, flags & ZXSTZXATF_UPLOAD ); libspectrum_snap_set_zxatasp_writeprotect( snap, flags & ZXSTZXATF_WRITEPROTECT ); libspectrum_snap_set_zxatasp_port_a( snap, **buffer ); (*buffer)++; libspectrum_snap_set_zxatasp_port_b( snap, **buffer ); (*buffer)++; libspectrum_snap_set_zxatasp_port_c( snap, **buffer ); (*buffer)++; libspectrum_snap_set_zxatasp_control( snap, **buffer ); (*buffer)++; libspectrum_snap_set_zxatasp_pages( snap, **buffer ); (*buffer)++; libspectrum_snap_set_zxatasp_current_page( snap, **buffer ); (*buffer)++; return LIBSPECTRUM_ERROR_NONE; } static libspectrum_error read_zxcf_chunk( libspectrum_snap *snap, libspectrum_word version GCC_UNUSED, const libspectrum_byte **buffer, const libspectrum_byte *end GCC_UNUSED, size_t data_length ) { libspectrum_word flags; if( data_length != 4 ) { libspectrum_print_error( LIBSPECTRUM_ERROR_UNKNOWN, "read_zxcf_chunk: unknown length %lu", (unsigned long)data_length ); return LIBSPECTRUM_ERROR_UNKNOWN; } libspectrum_snap_set_zxcf_active( snap, 1 ); flags = libspectrum_read_word( buffer ); libspectrum_snap_set_zxcf_upload( snap, flags & ZXSTZXCFF_UPLOAD ); libspectrum_snap_set_zxcf_memctl( snap, **buffer ); (*buffer)++; libspectrum_snap_set_zxcf_pages( snap, **buffer ); (*buffer)++; return LIBSPECTRUM_ERROR_NONE; } static libspectrum_error read_if2r_chunk( libspectrum_snap *snap, libspectrum_word version GCC_UNUSED, const libspectrum_byte **buffer, const libspectrum_byte *end GCC_UNUSED, size_t data_length ) { #ifdef HAVE_ZLIB_H libspectrum_byte *buffer2; size_t compressed_length; size_t uncompressed_length; libspectrum_error error; if( data_length < 4 ) { libspectrum_print_error( LIBSPECTRUM_ERROR_UNKNOWN, "szx_read_if2r_chunk: length %lu too short", (unsigned long)data_length ); return LIBSPECTRUM_ERROR_UNKNOWN; } compressed_length = libspectrum_read_dword( buffer ); uncompressed_length = 0x4000; error = libspectrum_zlib_inflate( *buffer, data_length - 4, &buffer2, &uncompressed_length ); if( error ) return error; *buffer += data_length - 4; libspectrum_snap_set_interface2_active( snap, 1 ); libspectrum_snap_set_interface2_rom( snap, 0, buffer2 ); return LIBSPECTRUM_ERROR_NONE; #else /* #ifdef HAVE_ZLIB_H */ libspectrum_print_error( LIBSPECTRUM_ERROR_UNKNOWN, "%s:read_if2r_chunk: zlib needed for decompression\n", __FILE__ ); return LIBSPECTRUM_ERROR_UNKNOWN; #endif /* #ifdef HAVE_ZLIB_H */ } static libspectrum_error read_dock_chunk( libspectrum_snap *snap, libspectrum_word version GCC_UNUSED, const libspectrum_byte **buffer, const libspectrum_byte *end GCC_UNUSED, size_t data_length ) { libspectrum_byte *data; size_t page; libspectrum_error error; libspectrum_word flags; libspectrum_byte writeable; error = read_ram_page( &data, &page, buffer, data_length, 0x2000, &flags ); if( error ) return error; if( page > 7 ) { libspectrum_print_error( LIBSPECTRUM_ERROR_CORRUPT, "%s:read_dock_chunk: unknown page number %ld", __FILE__, (unsigned long)page ); free( data ); return LIBSPECTRUM_ERROR_CORRUPT; } libspectrum_snap_set_dock_active( snap, 1 ); writeable = flags & ZXSTDOCKF_RAM; if( flags & ZXSTDOCKF_EXROMDOCK ) { libspectrum_snap_set_dock_ram( snap, page, writeable ); libspectrum_snap_set_dock_cart( snap, page, data ); } else { libspectrum_snap_set_exrom_ram( snap, page, writeable ); libspectrum_snap_set_exrom_cart( snap, page, data ); } return LIBSPECTRUM_ERROR_NONE; } static libspectrum_error skip_chunk( libspectrum_snap *snap GCC_UNUSED, libspectrum_word version GCC_UNUSED, const libspectrum_byte **buffer, const libspectrum_byte *end GCC_UNUSED, size_t data_length ) { *buffer += data_length; return LIBSPECTRUM_ERROR_NONE; } struct read_chunk_t { const char *id; read_chunk_fn function; }; static struct read_chunk_t read_chunks[] = { { ZXSTBID_AY, read_ay_chunk }, { ZXSTBID_BETA128, read_b128_chunk }, { ZXSTBID_BETADISK, skip_chunk }, { ZXSTBID_COVOX, skip_chunk }, { ZXSTBID_CREATOR, skip_chunk }, { ZXSTBID_DOCK, read_dock_chunk }, { ZXSTBID_DSKFILE, skip_chunk }, { ZXSTBID_GS, skip_chunk }, { ZXSTBID_GSRAMPAGE, skip_chunk }, { ZXSTBID_IF1, skip_chunk }, { ZXSTBID_IF2ROM, read_if2r_chunk }, { ZXSTBID_JOYSTICK, read_joy_chunk }, { ZXSTBID_KEYBOARD, read_keyb_chunk }, { ZXSTBID_MICRODRIVE, skip_chunk }, { ZXSTBID_MOUSE, skip_chunk }, { ZXSTBID_MULTIFACE, skip_chunk }, { ZXSTBID_PLUS3DISK, skip_chunk }, { ZXSTBID_RAMPAGE, read_ramp_chunk }, { ZXSTBID_ROM, skip_chunk }, { ZXSTBID_SPECDRUM, skip_chunk }, { ZXSTBID_SPECREGS, read_spcr_chunk }, { ZXSTBID_TIMEXREGS, read_scld_chunk }, { ZXSTBID_USPEECH, skip_chunk }, { ZXSTBID_Z80REGS, read_z80r_chunk }, { ZXSTBID_ZXATASPRAMPAGE, read_atrp_chunk }, { ZXSTBID_ZXATASP, read_zxat_chunk }, { ZXSTBID_ZXCF, read_zxcf_chunk }, { ZXSTBID_ZXCFRAMPAGE, read_cfrp_chunk }, { ZXSTBID_ZXPRINTER, skip_chunk }, { ZXSTBID_ZXTAPE, skip_chunk }, }; static size_t read_chunks_count = sizeof( read_chunks ) / sizeof( struct read_chunk_t ); static libspectrum_error read_chunk_header( char *id, libspectrum_dword *data_length, const libspectrum_byte **buffer, const libspectrum_byte *end ) { if( end - *buffer < 8 ) { libspectrum_print_error( LIBSPECTRUM_ERROR_CORRUPT, "szx_read_chunk_header: not enough data for chunk header" ); return LIBSPECTRUM_ERROR_CORRUPT; } memcpy( id, *buffer, 4 ); id[4] = '\0'; *buffer += 4; *data_length = libspectrum_read_dword( buffer ); return LIBSPECTRUM_ERROR_NONE; } static libspectrum_error read_chunk( libspectrum_snap *snap, libspectrum_word version, const libspectrum_byte **buffer, const libspectrum_byte *end ) { char id[5]; libspectrum_dword data_length; libspectrum_error error; size_t i; int done; error = read_chunk_header( id, &data_length, buffer, end ); if( error ) return error; if( *buffer + data_length > end ) { libspectrum_print_error( LIBSPECTRUM_ERROR_CORRUPT, "szx_read_chunk: chunk length goes beyond end of file" ); return LIBSPECTRUM_ERROR_CORRUPT; } done = 0; for( i = 0; !done && i < read_chunks_count; i++ ) { if( !memcmp( id, read_chunks[i].id, 4 ) ) { error = read_chunks[i].function( snap, version, buffer, end, data_length ); if( error ) return error; done = 1; } } if( !done ) { libspectrum_print_error( LIBSPECTRUM_ERROR_UNKNOWN, "szx_read_chunk: unknown chunk id '%s'", id ); *buffer += data_length; } return LIBSPECTRUM_ERROR_NONE; } libspectrum_error libspectrum_szx_read( libspectrum_snap *snap, const libspectrum_byte *buffer, size_t length ) { libspectrum_word version; libspectrum_error error; const libspectrum_byte *end = buffer + length; if( end - buffer < 8 ) { libspectrum_print_error( LIBSPECTRUM_ERROR_CORRUPT, "libspectrum_szx_read: not enough data for SZX header" ); return LIBSPECTRUM_ERROR_CORRUPT; } if( memcmp( buffer, signature, signature_length ) ) { libspectrum_print_error( LIBSPECTRUM_ERROR_SIGNATURE, "libspectrum_szx_read: wrong signature" ); return LIBSPECTRUM_ERROR_SIGNATURE; } buffer += signature_length; version = (*buffer++) << 8; version |= *buffer++; switch( *buffer ) { case SZX_MACHINE_16: libspectrum_snap_set_machine( snap, LIBSPECTRUM_MACHINE_16 ); break; case SZX_MACHINE_48: libspectrum_snap_set_machine( snap, LIBSPECTRUM_MACHINE_48 ); break; case SZX_MACHINE_128: libspectrum_snap_set_machine( snap, LIBSPECTRUM_MACHINE_128 ); break; case SZX_MACHINE_PLUS2: libspectrum_snap_set_machine( snap, LIBSPECTRUM_MACHINE_PLUS2 ); break; case SZX_MACHINE_PLUS2A: libspectrum_snap_set_machine( snap, LIBSPECTRUM_MACHINE_PLUS2A ); break; case SZX_MACHINE_PLUS3: libspectrum_snap_set_machine( snap, LIBSPECTRUM_MACHINE_PLUS3 ); break; case SZX_MACHINE_PLUS3E: libspectrum_snap_set_machine( snap, LIBSPECTRUM_MACHINE_PLUS3E ); break; case SZX_MACHINE_PENTAGON: libspectrum_snap_set_machine( snap, LIBSPECTRUM_MACHINE_PENT ); break; case SZX_MACHINE_TC2048: libspectrum_snap_set_machine( snap, LIBSPECTRUM_MACHINE_TC2048 ); break; case SZX_MACHINE_TC2068: libspectrum_snap_set_machine( snap, LIBSPECTRUM_MACHINE_TC2068 ); break; case SZX_MACHINE_TS2068: libspectrum_snap_set_machine( snap, LIBSPECTRUM_MACHINE_TS2068 ); break; case SZX_MACHINE_SCORPION: libspectrum_snap_set_machine( snap, LIBSPECTRUM_MACHINE_SCORP ); break; case SZX_MACHINE_SE: libspectrum_snap_set_machine( snap, LIBSPECTRUM_MACHINE_SE ); break; default: libspectrum_print_error( LIBSPECTRUM_MACHINE_UNKNOWN, "libspectrum_szx_read: unknown machine type %d", (int)*buffer ); return LIBSPECTRUM_MACHINE_UNKNOWN; } /* Skip to the end of the header */ buffer += 2; while( buffer < end ) { error = read_chunk( snap, version, &buffer, end ); if( error ) return error; } return LIBSPECTRUM_ERROR_NONE; } libspectrum_error libspectrum_szx_write( libspectrum_byte **buffer, size_t *length, int *out_flags, libspectrum_snap *snap, libspectrum_creator *creator, int in_flags ) { libspectrum_byte *ptr = *buffer; int capabilities, compress; libspectrum_error error; size_t i; *out_flags = 0; capabilities = libspectrum_machine_capabilities( libspectrum_snap_machine( snap ) ); compress = !( in_flags & LIBSPECTRUM_FLAG_SNAPSHOT_NO_COMPRESSION ); error = write_file_header( buffer, &ptr, length, out_flags, snap ); if( error ) return error; if( creator ) { error = write_crtr_chunk( buffer, &ptr, length, creator ); if( error ) return error; } error = write_z80r_chunk( buffer, &ptr, length, snap ); if( error ) return error; error = write_spcr_chunk( buffer, &ptr, length, snap ); if( error ) return error; error = write_joy_chunk( buffer, &ptr, length, out_flags, snap ); if( error ) return error; error = write_keyb_chunk( buffer, &ptr, length, out_flags, snap ); if( error ) return error; error = write_ram_pages( buffer, &ptr, length, snap, compress ); if( error ) return error; if( capabilities & LIBSPECTRUM_MACHINE_CAPABILITY_AY ) { error = write_ay_chunk( buffer, &ptr, length, snap ); if( error ) return error; } if( capabilities & ( LIBSPECTRUM_MACHINE_CAPABILITY_TIMEX_MEMORY | LIBSPECTRUM_MACHINE_CAPABILITY_SE_MEMORY ) ) { error = write_scld_chunk( buffer, &ptr, length, snap ); if( error ) return error; } if( capabilities & LIBSPECTRUM_MACHINE_CAPABILITY_TRDOS_DISK ) { error = write_b128_chunk( buffer, &ptr, length, snap ); if( error ) return error; } if( libspectrum_snap_zxatasp_active( snap ) ) { error = write_zxat_chunk( buffer, &ptr, length, snap ); if( error ) return error; for( i = 0; i < libspectrum_snap_zxatasp_pages( snap ); i++ ) { error = write_atrp_chunk( buffer, &ptr, length, snap, i, compress ); if( error ) return error; } } if( libspectrum_snap_zxcf_active( snap ) ) { error = write_zxcf_chunk( buffer, &ptr, length, snap ); if( error ) return error; for( i = 0; i < libspectrum_snap_zxcf_pages( snap ); i++ ) { error = write_cfrp_chunk( buffer, &ptr, length, snap, i, compress ); if( error ) return error; } } if( libspectrum_snap_interface2_active( snap ) ) { #ifdef HAVE_ZLIB_H error = write_if2r_chunk( buffer, &ptr, length, snap ); if( error ) return error; #else /* IF2R blocks only support writing compressed images */ *out_flags |= LIBSPECTRUM_FLAG_SNAPSHOT_MAJOR_INFO_LOSS; #endif /* #ifdef HAVE_ZLIB_H */ } if( libspectrum_snap_dock_active( snap ) ) { for( i = 0; i < 8; i++ ) { if( libspectrum_snap_exrom_cart( snap, i ) ) { error = write_dock_chunk( buffer, &ptr, length, snap, 0, libspectrum_snap_exrom_cart( snap, i ), i, libspectrum_snap_exrom_ram( snap, i ), compress ); if( error ) return error; } if( libspectrum_snap_dock_cart( snap, i ) ) { error = write_dock_chunk( buffer, &ptr, length, snap, 1, libspectrum_snap_dock_cart( snap, i ), i, libspectrum_snap_dock_ram( snap, i ), compress ); if( error ) return error; } } } /* Set length to be actual length, not allocated length */ *length = ptr - *buffer; return LIBSPECTRUM_ERROR_NONE; } static libspectrum_error write_file_header( libspectrum_byte **buffer, libspectrum_byte **ptr, size_t *length, int *out_flags, libspectrum_snap *snap ) { libspectrum_error error; error = libspectrum_make_room( buffer, 8, ptr, length ); if( error ) return error; memcpy( *ptr, signature, 4 ); *ptr += 4; /* [Assumption] We currently write version 1.3 files (major, minor) */ *(*ptr)++ = SZX_VERSION_MAJOR; *(*ptr)++ = SZX_VERSION_MINOR; switch( libspectrum_snap_machine( snap ) ) { case LIBSPECTRUM_MACHINE_16: **ptr = SZX_MACHINE_16; break; case LIBSPECTRUM_MACHINE_48: **ptr = SZX_MACHINE_48; break; case LIBSPECTRUM_MACHINE_128: **ptr = SZX_MACHINE_128; break; case LIBSPECTRUM_MACHINE_PLUS2: **ptr = SZX_MACHINE_PLUS2; break; case LIBSPECTRUM_MACHINE_PLUS2A: **ptr = SZX_MACHINE_PLUS2A; break; case LIBSPECTRUM_MACHINE_PLUS3: **ptr = SZX_MACHINE_PLUS3; break; case LIBSPECTRUM_MACHINE_PLUS3E: **ptr = SZX_MACHINE_PLUS3E; break; case LIBSPECTRUM_MACHINE_PENT: **ptr = SZX_MACHINE_PENTAGON; break; case LIBSPECTRUM_MACHINE_TC2048: **ptr = SZX_MACHINE_TC2048; break; case LIBSPECTRUM_MACHINE_TC2068: **ptr = SZX_MACHINE_TC2068; break; case LIBSPECTRUM_MACHINE_TS2068: **ptr = SZX_MACHINE_TS2068; break; case LIBSPECTRUM_MACHINE_SCORP: **ptr = SZX_MACHINE_SCORPION; break; case LIBSPECTRUM_MACHINE_SE: **ptr = SZX_MACHINE_SE; break; case LIBSPECTRUM_MACHINE_UNKNOWN: libspectrum_print_error( LIBSPECTRUM_ERROR_LOGIC, "Emulated machine type is set to 'unknown'!" ); return LIBSPECTRUM_ERROR_LOGIC; } (*ptr)++; /* Reserved byte */ *(*ptr)++ = '\0'; return LIBSPECTRUM_ERROR_NONE; } static libspectrum_error write_crtr_chunk( libspectrum_byte **buffer, libspectrum_byte **ptr, size_t *length, libspectrum_creator *creator ) { libspectrum_error error; size_t custom_length; custom_length = libspectrum_creator_custom_length( creator ); error = write_chunk_header( buffer, ptr, length, ZXSTBID_CREATOR, 36 + custom_length ); if( error ) return error; memcpy( *ptr, libspectrum_creator_program( creator ), 32 ); *ptr += 32; libspectrum_write_word( ptr, libspectrum_creator_major( creator ) ); libspectrum_write_word( ptr, libspectrum_creator_minor( creator ) ); if( custom_length ) { memcpy( *ptr, libspectrum_creator_custom( creator ), custom_length ); *ptr += custom_length; } return LIBSPECTRUM_ERROR_NONE; } static libspectrum_error write_z80r_chunk( libspectrum_byte **buffer, libspectrum_byte **ptr, size_t *length, libspectrum_snap *snap ) { libspectrum_dword tstates; libspectrum_byte flags; libspectrum_error error; error = write_chunk_header( buffer, ptr, length, ZXSTBID_Z80REGS, 37 ); if( error ) return error; *(*ptr)++ = libspectrum_snap_a ( snap ); *(*ptr)++ = libspectrum_snap_f ( snap ); libspectrum_write_word( ptr, libspectrum_snap_bc ( snap ) ); libspectrum_write_word( ptr, libspectrum_snap_de ( snap ) ); libspectrum_write_word( ptr, libspectrum_snap_hl ( snap ) ); *(*ptr)++ = libspectrum_snap_a_( snap ); *(*ptr)++ = libspectrum_snap_f_( snap ); libspectrum_write_word( ptr, libspectrum_snap_bc_ ( snap ) ); libspectrum_write_word( ptr, libspectrum_snap_de_ ( snap ) ); libspectrum_write_word( ptr, libspectrum_snap_hl_ ( snap ) ); libspectrum_write_word( ptr, libspectrum_snap_ix ( snap ) ); libspectrum_write_word( ptr, libspectrum_snap_iy ( snap ) ); libspectrum_write_word( ptr, libspectrum_snap_sp ( snap ) ); libspectrum_write_word( ptr, libspectrum_snap_pc ( snap ) ); *(*ptr)++ = libspectrum_snap_i ( snap ); *(*ptr)++ = libspectrum_snap_r ( snap ); *(*ptr)++ = libspectrum_snap_iff1( snap ); *(*ptr)++ = libspectrum_snap_iff2( snap ); *(*ptr)++ = libspectrum_snap_im ( snap ); tstates = libspectrum_snap_tstates( snap ); libspectrum_write_dword( ptr, tstates ); /* Number of tstates remaining in which an interrupt can occur */ if( tstates < 48 ) { *(*ptr)++ = (unsigned char)(48 - tstates); } else { *(*ptr)++ = '\0'; } flags = '\0'; if( libspectrum_snap_last_instruction_ei( snap ) ) flags |= ZXSTZF_EILAST; if( libspectrum_snap_halted( snap ) ) flags |= ZXSTZF_HALTED; *(*ptr)++ = flags; /* Hidden register not supported */ *(*ptr)++ = '\0'; /* Reserved byte */ *(*ptr)++ = '\0'; return LIBSPECTRUM_ERROR_NONE; } static libspectrum_error write_spcr_chunk( libspectrum_byte **buffer, libspectrum_byte **ptr, size_t *length, libspectrum_snap *snap ) { libspectrum_error error; int capabilities; error = write_chunk_header( buffer, ptr, length, ZXSTBID_SPECREGS, 8 ); if( error ) return error; capabilities = libspectrum_machine_capabilities( libspectrum_snap_machine( snap ) ); /* Border colour */ *(*ptr)++ = libspectrum_snap_out_ula( snap ) & 0x07; if( capabilities & LIBSPECTRUM_MACHINE_CAPABILITY_128_MEMORY ) { *(*ptr)++ = libspectrum_snap_out_128_memoryport( snap ); } else { *(*ptr)++ = '\0'; } /* [Assumption] The Scorpion port 0x1ffd should be written here */ if( capabilities & LIBSPECTRUM_MACHINE_CAPABILITY_PLUS3_MEMORY || capabilities & LIBSPECTRUM_MACHINE_CAPABILITY_SCORP_MEMORY ) { *(*ptr)++ = libspectrum_snap_out_plus3_memoryport( snap ); } else { *(*ptr)++ = '\0'; } *(*ptr)++ = libspectrum_snap_out_ula( snap ); /* Reserved bytes */ libspectrum_write_dword( ptr, 0 ); return LIBSPECTRUM_ERROR_NONE; } static void write_joystick( libspectrum_byte **ptr, int *out_flags, libspectrum_snap *snap, const int connection ) { size_t num_joysticks = libspectrum_snap_joystick_active_count( snap ); int found = 0; int i; for( i = 0; i < num_joysticks; i++ ) { if( libspectrum_snap_joystick_inputs( snap, i ) & connection ) { switch( libspectrum_snap_joystick_list( snap, i ) ) { case LIBSPECTRUM_JOYSTICK_CURSOR: if( !found ) { *(*ptr)++ = ZXJT_CURSOR; found = 1; } else *out_flags |= LIBSPECTRUM_FLAG_SNAPSHOT_MINOR_INFO_LOSS; break; case LIBSPECTRUM_JOYSTICK_KEMPSTON: if( !found ) { *(*ptr)++ = ZXJT_KEMPSTON; found = 1; } else *out_flags |= LIBSPECTRUM_FLAG_SNAPSHOT_MINOR_INFO_LOSS; break; case LIBSPECTRUM_JOYSTICK_SINCLAIR_1: if( !found ) { *(*ptr)++ = ZXJT_SINCLAIR1; found = 1; } else *out_flags |= LIBSPECTRUM_FLAG_SNAPSHOT_MINOR_INFO_LOSS; break; case LIBSPECTRUM_JOYSTICK_SINCLAIR_2: if( !found ) { *(*ptr)++ = ZXJT_SINCLAIR2; found = 1; } else *out_flags |= LIBSPECTRUM_FLAG_SNAPSHOT_MINOR_INFO_LOSS; break; case LIBSPECTRUM_JOYSTICK_TIMEX_1: if( !found ) { *(*ptr)++ = ZXJT_TIMEX1; found = 1; } else *out_flags |= LIBSPECTRUM_FLAG_SNAPSHOT_MINOR_INFO_LOSS; break; case LIBSPECTRUM_JOYSTICK_TIMEX_2: if( !found ) { *(*ptr)++ = ZXJT_TIMEX2; found = 1; } else *out_flags |= LIBSPECTRUM_FLAG_SNAPSHOT_MINOR_INFO_LOSS; break; case LIBSPECTRUM_JOYSTICK_FULLER: if( !found ) { *(*ptr)++ = ZXJT_FULLER; found = 1; } else *out_flags |= LIBSPECTRUM_FLAG_SNAPSHOT_MINOR_INFO_LOSS; break; case LIBSPECTRUM_JOYSTICK_NONE: /* Shouldn't happen */ default: *(*ptr)++ = ZXJT_NONE; break; } } } if( !found ) *(*ptr)++ = ZXJT_NONE; } static libspectrum_error write_joy_chunk( libspectrum_byte **buffer, libspectrum_byte **ptr, size_t *length, int *out_flags, libspectrum_snap *snap ) { libspectrum_error error; libspectrum_dword flags; size_t num_joysticks = libspectrum_snap_joystick_active_count( snap ); int i; error = write_chunk_header( buffer, ptr, length, ZXSTBID_JOYSTICK, 6 ); if( error ) return error; flags = 0; for( i = 0; i < num_joysticks; i++ ) { if( libspectrum_snap_joystick_list( snap, i ) == LIBSPECTRUM_JOYSTICK_KEMPSTON ) flags |= ZXSTJOYF_ALWAYSPORT31; } libspectrum_write_dword( ptr, flags ); write_joystick( ptr, out_flags, snap, LIBSPECTRUM_JOYSTICK_INPUT_JOYSTICK_1 ); write_joystick( ptr, out_flags, snap, LIBSPECTRUM_JOYSTICK_INPUT_JOYSTICK_2 ); return LIBSPECTRUM_ERROR_NONE; } static libspectrum_error write_keyb_chunk( libspectrum_byte **buffer, libspectrum_byte **ptr, size_t *length, int *out_flags, libspectrum_snap *snap ) { libspectrum_error error; libspectrum_dword flags; error = write_chunk_header( buffer, ptr, length, ZXSTBID_KEYBOARD, 5 ); if( error ) return error; flags = 0; if( libspectrum_snap_issue2( snap ) ) flags |= ZXSTKF_ISSUE2; libspectrum_write_dword( ptr, flags ); write_joystick( ptr, out_flags, snap, LIBSPECTRUM_JOYSTICK_INPUT_KEYBOARD ); return LIBSPECTRUM_ERROR_NONE; } static libspectrum_error write_ram_pages( libspectrum_byte **buffer, libspectrum_byte **ptr, size_t *length, libspectrum_snap *snap, int compress ) { libspectrum_machine machine; int capabilities; libspectrum_error error; machine = libspectrum_snap_machine( snap ); capabilities = libspectrum_machine_capabilities( machine ); error = write_ramp_chunk( buffer, ptr, length, snap, 5, compress ); if( error ) return error; /* [Assumption] This is the right way to write Timex machine RAM */ if( machine != LIBSPECTRUM_MACHINE_16 ) { error = write_ramp_chunk( buffer, ptr, length, snap, 2, compress ); if( error ) return error; error = write_ramp_chunk( buffer, ptr, length, snap, 0, compress ); if( error ) return error; } if( capabilities & LIBSPECTRUM_MACHINE_CAPABILITY_128_MEMORY ) { error = write_ramp_chunk( buffer, ptr, length, snap, 1, compress ); if( error ) return error; error = write_ramp_chunk( buffer, ptr, length, snap, 3, compress ); if( error ) return error; error = write_ramp_chunk( buffer, ptr, length, snap, 4, compress ); if( error ) return error; error = write_ramp_chunk( buffer, ptr, length, snap, 6, compress ); if( error ) return error; error = write_ramp_chunk( buffer, ptr, length, snap, 7, compress ); if( error ) return error; /* [Assumption] RAM pages 8-15 are valid here */ if( capabilities & LIBSPECTRUM_MACHINE_CAPABILITY_SCORP_MEMORY ) { int i; for( i = 8; i < 16; i++ ) { error = write_ramp_chunk( buffer, ptr, length, snap, i, compress ); if( error ) return error; } } } if( capabilities & LIBSPECTRUM_MACHINE_CAPABILITY_SE_MEMORY ) { error = write_ramp_chunk( buffer, ptr, length, snap, 8, compress ); if( error ) return error; } return LIBSPECTRUM_ERROR_NONE; } static libspectrum_error write_ramp_chunk( libspectrum_byte **buffer, libspectrum_byte **ptr, size_t *length, libspectrum_snap *snap, int page, int compress ) { libspectrum_error error; const libspectrum_byte *data; data = libspectrum_snap_pages( snap, page ); error = write_ram_page( buffer, ptr, length, ZXSTBID_RAMPAGE, data, 0x4000, page, compress, 0x00 ); if( error ) return error; return LIBSPECTRUM_ERROR_NONE; } static libspectrum_error write_ram_page( libspectrum_byte **buffer, libspectrum_byte **ptr, size_t *length, const char *id, const libspectrum_byte *data, size_t data_length, int page, int compress, int extra_flags ) { libspectrum_error error; libspectrum_byte *block_length, *flags, *compressed_data; int use_compression; if( !data ) return LIBSPECTRUM_ERROR_NONE; /* 8 for the chunk header, 3 for the flags and the page number */ error = libspectrum_make_room( buffer, 8 + 3, ptr, length ); if( error ) return error; memcpy( *ptr, id, 4 ); (*ptr) += 4; /* Store this location for later */ block_length = *ptr; *ptr += 4; /* And this one */ flags = *ptr; *ptr += 2; *(*ptr)++ = (libspectrum_byte)page; use_compression = 0; compressed_data = NULL; #ifdef HAVE_ZLIB_H if( compress ) { size_t compressed_length; error = libspectrum_zlib_compress( data, data_length, &compressed_data, &compressed_length ); if( error ) return error; if( compress & LIBSPECTRUM_FLAG_SNAPSHOT_ALWAYS_COMPRESS || compressed_length < data_length ) { use_compression = 1; data = compressed_data; data_length = compressed_length; } } #endif /* #ifdef HAVE_ZLIB_H */ if( use_compression ) extra_flags |= ZXSTRF_COMPRESSED; libspectrum_write_dword( &block_length, 3 + data_length ); libspectrum_write_word( &flags, extra_flags ); error = libspectrum_make_room( buffer, data_length, ptr, length ); if( error ) { if( compressed_data ) free( compressed_data ); return error; } memcpy( *ptr, data, data_length ); *ptr += data_length; if( compressed_data ) free( compressed_data ); return LIBSPECTRUM_ERROR_NONE; } static libspectrum_error write_ay_chunk( libspectrum_byte **buffer, libspectrum_byte **ptr, size_t *length, libspectrum_snap *snap ) { libspectrum_error error; size_t i; error = write_chunk_header( buffer, ptr, length, ZXSTBID_AY, 18 ); if( error ) return error; *(*ptr)++ = '\0'; /* Flags */ *(*ptr)++ = libspectrum_snap_out_ay_registerport( snap ); for( i = 0; i < 16; i++ ) *(*ptr)++ = libspectrum_snap_ay_registers( snap, i ); return LIBSPECTRUM_ERROR_NONE; } static libspectrum_error write_scld_chunk( libspectrum_byte **buffer, libspectrum_byte **ptr, size_t *length, libspectrum_snap *snap ) { libspectrum_error error; error = write_chunk_header( buffer, ptr, length, ZXSTBID_TIMEXREGS, 2 ); if( error ) return error; *(*ptr)++ = libspectrum_snap_out_scld_hsr( snap ); *(*ptr)++ = libspectrum_snap_out_scld_dec( snap ); return LIBSPECTRUM_ERROR_NONE; } static libspectrum_error write_b128_chunk( libspectrum_byte **buffer, libspectrum_byte **ptr, size_t *length, libspectrum_snap *snap ) { libspectrum_error error; libspectrum_dword flags; error = write_chunk_header( buffer, ptr, length, ZXSTBID_BETA128, 10 ); if( error ) return error; flags = ZXSTBETAF_CONNECTED; /* Betadisk interface connected */ if( libspectrum_snap_beta_paged( snap ) ) flags |= ZXSTBETAF_PAGED; if( !libspectrum_snap_beta_direction( snap ) ) flags |= ZXSTBETAF_SEEKLOWER; libspectrum_write_dword( ptr, flags ); *(*ptr)++ = 2; /* 2 drives connected */ *(*ptr)++ = libspectrum_snap_beta_system( snap ); *(*ptr)++ = libspectrum_snap_beta_track ( snap ); *(*ptr)++ = libspectrum_snap_beta_sector( snap ); *(*ptr)++ = libspectrum_snap_beta_data ( snap ); *(*ptr)++ = libspectrum_snap_beta_status( snap ); return LIBSPECTRUM_ERROR_NONE; } static libspectrum_error write_zxat_chunk( libspectrum_byte **buffer, libspectrum_byte **ptr, size_t *length, libspectrum_snap *snap ) { libspectrum_word flags; libspectrum_error error; error = write_chunk_header( buffer, ptr, length, ZXSTBID_ZXATASP, 8 ); flags = 0; if( libspectrum_snap_zxatasp_upload ( snap ) ) flags |= ZXSTZXATF_UPLOAD; if( libspectrum_snap_zxatasp_writeprotect( snap ) ) flags |= ZXSTZXATF_WRITEPROTECT; libspectrum_write_word( ptr, flags ); *(*ptr)++ = libspectrum_snap_zxatasp_port_a( snap ); *(*ptr)++ = libspectrum_snap_zxatasp_port_b( snap ); *(*ptr)++ = libspectrum_snap_zxatasp_port_c( snap ); *(*ptr)++ = libspectrum_snap_zxatasp_control( snap ); *(*ptr)++ = libspectrum_snap_zxatasp_pages( snap ); *(*ptr)++ = libspectrum_snap_zxatasp_current_page( snap ); return LIBSPECTRUM_ERROR_NONE; } static libspectrum_error write_atrp_chunk( libspectrum_byte **buffer, libspectrum_byte **ptr, size_t *length, libspectrum_snap *snap, int page, int compress ) { libspectrum_error error; const libspectrum_byte *data; data = libspectrum_snap_zxatasp_ram( snap, page ); error = write_ram_page( buffer, ptr, length, ZXSTBID_ZXATASPRAMPAGE, data, 0x4000, page, compress, 0x00 ); if( error ) return error; return LIBSPECTRUM_ERROR_NONE; } static libspectrum_error write_zxcf_chunk( libspectrum_byte **buffer, libspectrum_byte **ptr, size_t *length, libspectrum_snap *snap ) { libspectrum_word flags; libspectrum_error error; error = write_chunk_header( buffer, ptr, length, ZXSTBID_ZXCF, 4 ); flags = 0; if( libspectrum_snap_zxcf_upload( snap ) ) flags |= ZXSTZXCFF_UPLOAD; libspectrum_write_word( ptr, flags ); *(*ptr)++ = libspectrum_snap_zxcf_memctl( snap ); *(*ptr)++ = libspectrum_snap_zxcf_pages( snap ); return LIBSPECTRUM_ERROR_NONE; } static libspectrum_error write_cfrp_chunk( libspectrum_byte **buffer, libspectrum_byte **ptr, size_t *length, libspectrum_snap *snap, int page, int compress ) { libspectrum_error error; const libspectrum_byte *data; data = libspectrum_snap_zxcf_ram( snap, page ); error = write_ram_page( buffer, ptr, length, ZXSTBID_ZXCFRAMPAGE, data, 0x4000, page, compress, 0x00 ); if( error ) return error; return LIBSPECTRUM_ERROR_NONE; } #ifdef HAVE_ZLIB_H static libspectrum_error write_if2r_chunk( libspectrum_byte **buffer, libspectrum_byte **ptr, size_t *length, libspectrum_snap *snap ) { libspectrum_error error; libspectrum_byte *block_length, *data, *cart_size, *compressed_data; size_t data_length, compressed_length; /* 8 for the chunk header, 4 for the compressed cart size */ error = libspectrum_make_room( buffer, 8 + 4, ptr, length ); if( error ) return error; memcpy( *ptr, ZXSTBID_IF2ROM, 4 ); (*ptr) += 4; /* Store this location for later */ block_length = *ptr; *ptr += 4; /* And this one */ cart_size = *ptr; *ptr += 4; data = libspectrum_snap_interface2_rom( snap, 0 ); data_length = 0x4000; compressed_data = NULL; error = libspectrum_zlib_compress( data, data_length, &compressed_data, &compressed_length ); if( error ) return error; libspectrum_write_dword( &block_length, 4 + compressed_length ); libspectrum_write_dword( &cart_size, compressed_length ); error = libspectrum_make_room( buffer, compressed_length, ptr, length ); if( error ) { if( compressed_data ) free( compressed_data ); return error; } memcpy( *ptr, compressed_data, compressed_length ); *ptr += compressed_length; if( compressed_data ) free( compressed_data ); return LIBSPECTRUM_ERROR_NONE; } #endif /* #ifdef HAVE_ZLIB_H */ static libspectrum_error write_dock_chunk( libspectrum_byte **buffer, libspectrum_byte **ptr, size_t *length, libspectrum_snap *snap, int exrom_dock, const libspectrum_byte *data, int page, int writeable, int compress ) { libspectrum_error error; libspectrum_byte extra_flags; extra_flags = 0; if( writeable ) extra_flags |= ZXSTDOCKF_RAM; if( exrom_dock ) extra_flags |= ZXSTDOCKF_EXROMDOCK; error = write_ram_page( buffer, ptr, length, ZXSTBID_DOCK, data, 0x2000, page, compress, extra_flags ); if( error ) return error; return LIBSPECTRUM_ERROR_NONE; } static libspectrum_error write_chunk_header( libspectrum_byte **buffer, libspectrum_byte **ptr, size_t *length, const char *id, libspectrum_dword block_length ) { libspectrum_error error; error = libspectrum_make_room( buffer, 8 + block_length, ptr, length ); if( error ) return error; memcpy( *ptr, id, 4 ); *ptr += 4; libspectrum_write_dword( ptr, block_length ); return LIBSPECTRUM_ERROR_NONE; }