/* machine.c: Routines for handling the various machine types Copyright (c) 1999-2005 Philip Kendall $Id: machine.c,v 1.120 2007/02/02 16:21:51 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 "divide.h" #include "event.h" #include "fuse.h" #include "if1.h" #include "if2.h" #include "machine.h" #include "machines/machines.h" #include "machines/scorpion.h" #include "machines/spec128.h" #include "machines/spec48.h" #include "machines/specplus3.h" #include "machines/tc2068.h" #include "memory.h" #include "printer.h" #include "scld.h" #include "settings.h" #include "simpleide.h" #include "snapshot.h" #include "sound.h" #include "tape.h" #include "ui/ui.h" #include "ui/uidisplay.h" #include "ula.h" #include "utils.h" #include "z80/z80.h" #include "zxatasp.h" #include "zxcf.h" fuse_machine_info **machine_types = NULL; /* Array of available machines */ int machine_count = 0; fuse_machine_info *machine_current = NULL; /* The currently selected machine */ static int machine_location; /* Where is the current machine in machine_types[...]? */ static int machine_add_machine( int (*init_function)(fuse_machine_info *machine) ); static int machine_select_machine( fuse_machine_info *machine ); int machine_init_machines( void ) { int error; error = machine_add_machine( spec16_init ); if (error ) return error; error = machine_add_machine( spec48_init ); if (error ) return error; error = machine_add_machine( spec128_init ); if (error ) return error; error = machine_add_machine( specplus2_init ); if (error ) return error; error = machine_add_machine( specplus2a_init ); if( error ) return error; #ifdef HAVE_765_H /* Add the +3 only if we have FDC support; otherwise the +2A and +3 emulations are identical */ error = machine_add_machine( specplus3_init ); if (error ) return error; /* FIXME: what should we do about the +3e if we don't have lib765? */ error = machine_add_machine( specplus3e_init ); if( error ) return error; #endif /* #ifdef HAVE_765_H */ error = machine_add_machine( tc2048_init ); if (error ) return error; error = machine_add_machine( tc2068_init ); if( error ) return error; error = machine_add_machine( ts2068_init ); if( error ) return error; error = machine_add_machine( pentagon_init ); if (error ) return error; error = machine_add_machine( scorpion_init ); if ( error ) return error; error = machine_add_machine( spec_se_init ); if ( error ) return error; return 0; } static int machine_add_machine( int (*init_function)( fuse_machine_info *machine ) ) { fuse_machine_info *machine; int error; machine_count++; machine_types = realloc( machine_types, machine_count * sizeof( fuse_machine_info* ) ); if( machine_types == NULL ) { ui_error( UI_ERROR_ERROR, "out of memory at %s:%d", __FILE__, __LINE__ ); return 1; } machine_types[ machine_count - 1 ] = malloc( sizeof( fuse_machine_info ) ); if( !machine_types[ machine_count - 1 ] ) { ui_error( UI_ERROR_ERROR, "out of memory at %s:%d", __FILE__, __LINE__ ); return 1; } machine = machine_types[ machine_count - 1 ]; error = init_function( machine ); if( error ) return error; error = machine_set_timings( machine ); if( error ) return error; machine->capabilities = libspectrum_machine_capabilities( machine->machine ); return 0; } int machine_select( libspectrum_machine type ) { int i; int error; for( i=0; i < machine_count; i++ ) { if( machine_types[i]->machine == type ) { machine_location = i; error = machine_select_machine( machine_types[i] ); if( !error ) return 0; /* If we couldn't select the new machine type, try falling back to plain old 48K */ if( type != LIBSPECTRUM_MACHINE_48 ) error = machine_select( LIBSPECTRUM_MACHINE_48 ); /* If that still didn't work, give up */ if( error ) { ui_error( UI_ERROR_ERROR, "can't select 48K machine. Giving up." ); fuse_abort(); } else { ui_error( UI_ERROR_INFO, "selecting 48K machine" ); return 0; } return 0; } } ui_error( UI_ERROR_ERROR, "machine type %d unknown", type ); return 1; } int machine_select_id( const char *id ) { int i, error; for( i=0; i < machine_count; i++ ) { if( ! strcmp( machine_types[i]->id, id ) ) { error = machine_select_machine( machine_types[i] ); if( error ) return error; return 0; } } ui_error( UI_ERROR_ERROR, "Machine id '%s' unknown", id ); return 1; } const char* machine_get_id( libspectrum_machine type ) { int i; for( i=0; imachine == type ) return machine_types[i]->id; return NULL; } static int machine_select_machine( fuse_machine_info *machine ) { int width, height, i; int capabilities; int plus3, trdos; machine_current = machine; settings_set_string( &settings_current.start_machine, machine->id ); tstates = 0; /* Reset the event stack */ event_reset(); if( event_add( 0, EVENT_TYPE_TIMER ) ) return 1; if( event_add( machine->timings.tstates_per_frame, EVENT_TYPE_FRAME ) ) return 1; sound_end(); if( uidisplay_end() ) return 1; capabilities = libspectrum_machine_capabilities( machine->machine ); /* Set screen sizes here */ if( capabilities & LIBSPECTRUM_MACHINE_CAPABILITY_TIMEX_VIDEO ) { width = DISPLAY_SCREEN_WIDTH; height = 2*DISPLAY_SCREEN_HEIGHT; } else { width = DISPLAY_ASPECT_WIDTH; height = DISPLAY_SCREEN_HEIGHT; } if( uidisplay_init( width, height ) ) return 1; sound_init( settings_current.sound_device ); /* Mark RAM as not-present/read-only. The machine's reset function will * mark available pages as present/writeable. */ for( i = 0; i < 2 * SPECTRUM_RAM_PAGES; i++ ) memory_map_ram[i].writable = 0; if( machine_reset() ) return 1; /* Set the disk menu items and statusbar appropriately */ plus3 = capabilities & LIBSPECTRUM_MACHINE_CAPABILITY_PLUS3_DISK; trdos = capabilities & LIBSPECTRUM_MACHINE_CAPABILITY_TRDOS_DISK; if( plus3 || trdos ) { ui_menu_activate( UI_MENU_ITEM_MEDIA_DISK, 1 ); ui_statusbar_update( UI_STATUSBAR_ITEM_DISK, UI_STATUSBAR_STATE_INACTIVE ); } else { ui_menu_activate( UI_MENU_ITEM_MEDIA_DISK, 0 ); ui_statusbar_update( UI_STATUSBAR_ITEM_DISK, UI_STATUSBAR_STATE_NOT_AVAILABLE ); } ui_menu_activate( UI_MENU_ITEM_MEDIA_DISK_PLUS3, plus3 ); ui_menu_activate( UI_MENU_ITEM_MEDIA_DISK_TRDOS, trdos ); /* And the dock menu item */ if( capabilities & LIBSPECTRUM_MACHINE_CAPABILITY_TIMEX_DOCK ) { ui_menu_activate( UI_MENU_ITEM_MEDIA_CARTRIDGE_DOCK_EJECT, 0 ); } /* Reset any dialogue boxes etc. which contain machine-dependent state */ ui_widgets_reset(); return 0; } static int machine_load_rom_bank_internal( memory_page* bank_map, size_t which, int page_num, const char *filename, size_t expected_length ) { int fd, error; utils_file rom; size_t i, offset; fd = utils_find_auxiliary_file( filename, UTILS_AUXILIARY_ROM ); if( fd == -1 ) { ui_error( UI_ERROR_ERROR, "couldn't find ROM '%s'", filename ); return 1; } error = utils_read_fd( fd, filename, &rom ); if( error ) return error; if( rom.length != expected_length ) { ui_error( UI_ERROR_ERROR, "ROM '%s' is %ld bytes long; expected %ld bytes", filename, (unsigned long)rom.length, (unsigned long)expected_length ); utils_close_file( &rom ); return 1; } bank_map[ which ].offset = 0; bank_map[ which ].page_num = page_num; bank_map[ which ].page = memory_pool_allocate( rom.length ); if( !bank_map[ which ].page ) { utils_close_file( &rom ); return 1; } memcpy( bank_map[ which ].page, rom.buffer, rom.length ); for( i = 1, offset = MEMORY_PAGE_SIZE; offset < expected_length; i++, offset += MEMORY_PAGE_SIZE ) { bank_map[ which + i ].offset = offset; bank_map[ which + i ].page_num = page_num; bank_map[ which + i ].page = bank_map[ which ].page + offset; } if( utils_close_file( &rom ) ) return 1; return 0; } int machine_load_rom_bank( memory_page* bank_map, size_t which, int page_num, const char *filename, const char *fallback, size_t expected_length ) { int retval = machine_load_rom_bank_internal( bank_map, which, page_num, filename, expected_length ); if( retval && fallback ) retval = machine_load_rom_bank_internal( bank_map, which, page_num, fallback, expected_length ); return retval; } int machine_load_rom( size_t which, int page_num, const char *filename, const char *fallback, size_t expected_length ) { return machine_load_rom_bank( memory_map_rom, which, page_num, filename, fallback, expected_length ); } int machine_reset( void ) { size_t i; int error; /* These things should happen on all resets */ z80_reset(); sound_ay_reset(); printer_zxp_reset(); scld_reset(); tape_stop(); simpleide_reset(); /* DivIDE does not page in immediately on a reset condition (we do that by trapping PC instead); however, it needs to perform housekeeping tasks upon reset */ divide_reset(); memory_pool_free(); machine_current->ram.romcs = 0; /* Do the machine-specific bits, including loading the ROMs */ error = machine_current->reset(); if( error ) return error; error = machine_current->memory_map(); if( error ) return error; /* Set up the contention array */ for( i = 0; i < machine_current->timings.tstates_per_frame; i++ ) ula_contention[ i ] = machine_current->ram.contend_delay( i ); /* Check for an Interface I ROM */ ui_statusbar_update( UI_STATUSBAR_ITEM_MICRODRIVE, UI_STATUSBAR_STATE_NOT_AVAILABLE ); error = if1_reset(); if( error ) return error; /* Check for an Interface II ROM */ error = if2_reset(); if( error ) return error; /* Other interfaces that may assert /ROMCS */ zxatasp_reset(); zxcf_reset(); return 0; } int machine_set_timings( fuse_machine_info *machine ) { size_t y; /* Pull timings we use repeatedly out of libspectrum and store them for ourself */ machine->timings.processor_speed = libspectrum_timings_processor_speed( machine->machine ); machine->timings.left_border = libspectrum_timings_left_border( machine->machine ); machine->timings.horizontal_screen = libspectrum_timings_horizontal_screen( machine->machine ); machine->timings.right_border = libspectrum_timings_right_border( machine->machine ); machine->timings.tstates_per_line = libspectrum_timings_tstates_per_line( machine->machine ); machine->timings.interrupt_length = libspectrum_timings_interrupt_length( machine->machine ); machine->timings.tstates_per_frame = libspectrum_timings_tstates_per_frame( machine->machine ); /* Magic number alert libspectrum_timings_top_left_pixel gives us the number of tstates after the interrupt at which the top-left pixel of the screen is displayed. However, what's more useful for Fuse is when the first pixel of the top line of the border is displayed. Fuse shows DISPLAY_BORDER_HEIGHT lines of top border and DISPLAY_BORDER_WIDTH_COLS columns of left border, so we subtract the appropriate offset to get when the top-left pixel of the border is displayed. */ machine->line_times[0]= libspectrum_timings_top_left_pixel( machine->machine ) - /* DISPLAY_BORDER_HEIGHT lines of top border */ DISPLAY_BORDER_HEIGHT * machine->timings.tstates_per_line - 4 * DISPLAY_BORDER_WIDTH_COLS; /* Left border at 4 tstates per column */ for( y=1; yline_times[y] = machine->line_times[y-1] + machine->timings.tstates_per_line; } return 0; } int machine_end( void ) { int i; for( i=0; ishutdown ) machine_types[i]->shutdown(); free( machine_types[i] ); } free( machine_types ); return 0; }