/* fuse.c: The Free Unix Spectrum Emulator
   Copyright (c) 1999-2018 Philip Kendall and others

   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.,
   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

   Author contact information:

   E-mail: philip-fuse@shadowmagic.org.uk

*/

#include <config.h>

#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <time.h>

#ifdef WIN32
#include <windows.h>
#endif				/* #ifdef WIN32 */

#include <unistd.h>

/* We need to include SDL.h on Mac O X and Windows to do some magic
   bootstrapping by redefining main. As we now allow SDL joystick code to be
   used in the GTK and Xlib UIs we need to also do the magic when that code is
   in use, feel free to look away for the next line */
#if defined UI_SDL || (defined USE_JOYSTICK && !defined HAVE_JSW_H && (defined UI_X || defined UI_GTK) )
#include <SDL.h>		/* Needed on MacOS X and Windows */
#endif /* #if defined UI_SDL || (defined USE_JOYSTICK && !defined HAVE_JSW_H && (defined UI_X || defined UI_GTK) ) */

#ifdef GEKKO
#include <fat.h>
#endif				/* #ifdef GEKKO */

#ifdef HAVE_LIB_XML2
#include <libxml/encoding.h>
#endif

#include "debugger/debugger.h"
#include "display.h"
#include "event.h"
#include "fuse.h"
#include "infrastructure/startup_manager.h"
#include "keyboard.h"
#include "machine.h"
#include "machines/machines_periph.h"
#include "memory_pages.h"
#include "module.h"
#include "movie.h"
#include "mempool.h"
#include "peripherals/ay.h"
#include "peripherals/dck.h"
#include "peripherals/disk/beta.h"
#include "peripherals/disk/didaktik.h"
#include "peripherals/disk/fdd.h"
#include "peripherals/fuller.h"
#include "peripherals/ide/divide.h"
#include "peripherals/ide/divmmc.h"
#include "peripherals/ide/simpleide.h"
#include "peripherals/ide/zxatasp.h"
#include "peripherals/ide/zxcf.h"
#include "peripherals/ide/zxmmc.h"
#include "peripherals/joystick.h"
#include "peripherals/if1.h"
#include "peripherals/if2.h"
#include "peripherals/kempmouse.h"
#include "peripherals/melodik.h"
#include "peripherals/multiface.h"
#include "peripherals/printer.h"
#include "peripherals/scld.h"
#include "peripherals/speccyboot.h"
#include "peripherals/spectranet.h"
#include "peripherals/ttx2000s.h"
#include "peripherals/ula.h"
#include "peripherals/usource.h"
#include "phantom_typist.h"
#include "pokefinder/pokemem.h"
#include "profile.h"
#include "psg.h"
#include "rzx.h"
#include "screenshot.h"
#include "settings.h"
#include "slt.h"
#include "snapshot.h"
#include "sound.h"
#include "spectrum.h"
#include "tape.h"
#include "timer/timer.h"
#include "ui/scaler/scaler.h"
#include "ui/ui.h"
#include "ui/uimedia.h"
#include "unittests/unittests.h"
#include "utils.h"

#include "z80/z80.h"

/* What name were we called under? */
const char *fuse_progname;

/* A flag to say when we want to exit the emulator */
int fuse_exiting;

/* Is Spectrum emulation currently paused, and if so, how many times? */
int fuse_emulation_paused;

/* The creator information we'll store in file formats that support this */
libspectrum_creator *fuse_creator;

/* The earliest version of libspectrum we need */
static const char * const LIBSPECTRUM_MIN_VERSION = "0.5.0";

/* The various types of file we may want to run on startup */
typedef struct start_files_t {

  const char *disk_plus3;
  const char *disk_opus;
  const char *disk_plusd;
  const char *disk_beta;
  const char *disk_didaktik80;
  const char *disk_disciple;
  const char *dock;
  const char *if2;
  const char *playback;
  const char *recording;
  const char *snapshot;
  const char *tape;

  const char *simpleide_master, *simpleide_slave;
  const char *zxatasp_master, *zxatasp_slave;
  const char *zxcf;
  const char *divide_master, *divide_slave;
  const char *divmmc;
  const char *zxmmc;
  const char *mdr[8];

} start_files_t;

/* Context for the display startup routine */
static display_startup_context display_context;

static int fuse_init(int argc, char **argv);

static void creator_register_startup( void );

static void fuse_show_copyright(void);
static void fuse_show_version( void );
static void fuse_show_help( void );

static int setup_start_files( start_files_t *start_files );
static int parse_nonoption_args( int argc, char **argv, int first_arg,
				 start_files_t *start_files );
static int do_start_files( start_files_t *start_files );

static int fuse_end(void);

#ifdef UI_WIN32
int fuse_main(int argc, char **argv)
#else
int main(int argc, char **argv)
#endif
{
  int r;

#ifdef WIN32
  SetErrorMode( SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX );
#endif

#ifdef GEKKO
  fatInitDefault();
#endif				/* #ifdef GEKKO */
  
  if(fuse_init(argc,argv)) {
    fprintf(stderr,"%s: error initialising -- giving up!\n", fuse_progname);
    return 1;
  }

  if( settings_current.show_help ||
      settings_current.show_version ) return 0;

  if( settings_current.unittests ) {
    r = unittests_run();
  } else {
    while( !fuse_exiting ) {
      z80_do_opcodes();
      event_do_events();
    }
    r = debugger_get_exit_code();
  }

  fuse_end();
  
  return r;
}

static int
fuse_libspectrum_init( void *context )
{
  if( libspectrum_check_version( LIBSPECTRUM_MIN_VERSION ) ) {
    if( libspectrum_init() ) return 1;
  } else {
    ui_error( UI_ERROR_ERROR,
              "libspectrum version %s found, but %s required",
	      libspectrum_version(), LIBSPECTRUM_MIN_VERSION );
    return 1;
  }

  return 0;
}

static void
libspectrum_register_startup( void )
{
  startup_manager_module dependencies[] = {
    STARTUP_MANAGER_MODULE_DISPLAY
  };
  startup_manager_register( STARTUP_MANAGER_MODULE_LIBSPECTRUM, dependencies,
                            ARRAY_SIZE( dependencies ), fuse_libspectrum_init,
                            NULL, NULL );
}

static int
libxml2_init( void *context )
{
#ifdef HAVE_LIB_XML2
  LIBXML_TEST_VERSION
#endif

  return 0;
}

static void
libxml2_register_startup( void )
{
  startup_manager_module dependencies[] = { STARTUP_MANAGER_MODULE_SETUID };
  startup_manager_register( STARTUP_MANAGER_MODULE_LIBXML2, dependencies,
                            ARRAY_SIZE( dependencies ), libxml2_init, NULL,
                            NULL );
}

static int
setuid_init( void *context )
{
#ifdef HAVE_GETEUID
  int error;

  /* Drop root privs if we have them */
  if( !geteuid() ) {
    error = setuid( getuid() );
    if( error ) {
      ui_error( UI_ERROR_ERROR, "Could not drop root privileges" );
      return 1;
    }
  }
#endif				/* #ifdef HAVE_GETEUID */

  return 0;
}

static void
setuid_register_startup()
{
  startup_manager_module dependencies[] = {
    STARTUP_MANAGER_MODULE_DISPLAY,
    STARTUP_MANAGER_MODULE_LIBSPECTRUM,
  };
  startup_manager_register( STARTUP_MANAGER_MODULE_SETUID, dependencies,
                            ARRAY_SIZE( dependencies ), setuid_init, NULL,
                            NULL );
}

static int
run_startup_manager( int *argc, char ***argv )
{
  startup_manager_init();

  display_context.argc = argc;
  display_context.argv = argv;

  /* Get every module to register its init function */
  ay_register_startup();
  beta_register_startup();
  creator_register_startup();
  covox_register_startup();
  debugger_register_startup();
  didaktik80_register_startup();
  disciple_register_startup();
  display_register_startup( &display_context );
  divide_register_startup();
  divmmc_register_startup();
  event_register_startup();
  fdd_register_startup();
  fuller_register_startup();
  if1_register_startup();
  if2_register_startup();
  joystick_register_startup();
  kempmouse_register_startup();
  keyboard_register_startup();
  libspectrum_register_startup();
  libxml2_register_startup();
  machine_register_startup();
  machines_periph_register_startup();
  melodik_register_startup();
  memory_register_startup();
  mempool_register_startup();
  multiface_register_startup();
  opus_register_startup();
  phantom_typist_register_startup();
  plusd_register_startup();
  printer_register_startup();
  profile_register_startup();
  psg_register_startup();
  rzx_register_startup();
  scld_register_startup();
  screenshot_register_startup();
  settings_register_startup();
  setuid_register_startup();
  simpleide_register_startup();
  slt_register_startup();
  sound_register_startup();
  speccyboot_register_startup();
  specdrum_register_startup();
  spectranet_register_startup();
  spectrum_register_startup();
  tape_register_startup();
  ttx2000s_register_startup();
  timer_register_startup();
  ula_register_startup();
  usource_register_startup();
  z80_register_startup();
  zxatasp_register_startup();
  zxcf_register_startup();
  zxmmc_register_startup();

  return startup_manager_run();
}

static int fuse_init(int argc, char **argv)
{
  int error, first_arg;
  char *start_scaler;
  start_files_t start_files;

  /* Seed the bad but widely-available random number
     generator with the current time */
  srand( (unsigned)time( NULL ) );

  /* Some platforms (e.g. Wii) do not have argc/argv */
  if(argc > 0)
    fuse_progname = argv[0];
  else
    fuse_progname = "fuse";
  
  libspectrum_error_function = ui_libspectrum_error;

#ifdef GEKKO
  /* On the Wii, init the display first so we have a way of outputting
     messages */
  if( display_init(&argc,&argv) ) return 1;
#endif

  if( settings_init( &first_arg, argc, argv ) ) return 1;

  if( settings_current.show_version ) {
    fuse_show_version();
    return 0;
  } else if( settings_current.show_help ) {
    fuse_show_help();
    return 0;
  }

  start_scaler = utils_safe_strdup( settings_current.start_scaler_mode );

  fuse_show_copyright();

  if( run_startup_manager( &argc, &argv ) ) return 1;

  error = machine_select_id( settings_current.start_machine );
  if( error ) return error;

  error = scaler_select_id( start_scaler ); libspectrum_free( start_scaler );
  if( error ) return error;

  if( setup_start_files( &start_files ) ) return 1;
  if( parse_nonoption_args( argc, argv, first_arg, &start_files ) ) return 1;
  if( do_start_files( &start_files ) ) return 1;

  /* Must do this after all subsytems are initialised */
  debugger_command_evaluate( settings_current.debugger_command );

  if( ui_mouse_present ) ui_mouse_grabbed = ui_mouse_grab( 1 );

  fuse_emulation_paused = 0;
  movie_init();

  return 0;
}

static int
creator_init( void *context )
{
  size_t i;
  unsigned int version[4] = { 0, 0, 0, 0 };
  char *custom, osname[ 192 ];
  static const size_t CUSTOM_SIZE = 256;
  
  libspectrum_error error; int sys_error;

  const char *gcrypt_version;

  sscanf( VERSION, "%u.%u.%u.%u",
	  &version[0], &version[1], &version[2], &version[3] );

  for( i=0; i<4; i++ ) if( version[i] > 0xff ) version[i] = 0xff;

  sys_error = compat_osname( osname, sizeof( osname ) );
  if( sys_error ) return 1;

  fuse_creator = libspectrum_creator_alloc();

  error = libspectrum_creator_set_program( fuse_creator, "Fuse" );
  if( error ) { libspectrum_creator_free( fuse_creator ); return error; }

  error = libspectrum_creator_set_major( fuse_creator,
					 version[0] * 0x100 + version[1] );
  if( error ) { libspectrum_creator_free( fuse_creator ); return error; }

  error = libspectrum_creator_set_minor( fuse_creator,
					 version[2] * 0x100 + version[3] );
  if( error ) { libspectrum_creator_free( fuse_creator ); return error; }

  custom = libspectrum_new( char, CUSTOM_SIZE );

  gcrypt_version = libspectrum_gcrypt_version();
  if( !gcrypt_version ) gcrypt_version = "not available";

  snprintf( custom, CUSTOM_SIZE,
	    "gcrypt: %s\nlibspectrum: %s\nuname: %s", gcrypt_version,
	    libspectrum_version(), osname );

  error = libspectrum_creator_set_custom(
    fuse_creator, (libspectrum_byte*)custom, strlen( custom )
  );
  if( error ) {
    libspectrum_free( custom ); libspectrum_creator_free( fuse_creator );
    return error;
  }

  return 0;
}

static void
creator_end( void )
{
  libspectrum_creator_free( fuse_creator );
}

static void
creator_register_startup( void )
{
  startup_manager_module dependencies[] = { STARTUP_MANAGER_MODULE_SETUID };
  startup_manager_register( STARTUP_MANAGER_MODULE_CREATOR, dependencies,
                            ARRAY_SIZE( dependencies ), creator_init, NULL,
                            creator_end );
}

static void fuse_show_copyright(void)
{
  printf( "\n" );
  fuse_show_version();
  printf(
   FUSE_COPYRIGHT "; see the file\n"
   "'AUTHORS' for more details.\n"
   "\n"
   "For help, please mail <fuse-emulator-devel@lists.sf.net> or use\n"
   "the forums at <http://sourceforge.net/p/fuse-emulator/discussion/>.\n"
   "\n"
   "This program is distributed in the hope that it will be useful,\n"
   "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
   "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n"
   "GNU General Public License for more details.\n\n");
}

static void fuse_show_version( void )
{
  printf( "The Free Unix Spectrum Emulator (Fuse) version " VERSION ".\n" );
}

static void fuse_show_help( void )
{
  printf( "\n" );
  fuse_show_version();
  printf(
   "\nAvailable command-line options:\n\n"
   "Boolean options (use `--no-<option>' to turn off):\n\n"
   "--auto-load            Automatically load tape files when opened.\n"
   "--compress-rzx         Write RZX files out compressed.\n"
   "--issue2               Emulate an Issue 2 Spectrum.\n"
   "--kempston             Emulate the Kempston joystick on QAOP<space>.\n"
   "--loading-sound        Emulate the sound of tapes loading.\n"
   "--sound                Produce sound.\n"
   "--sound-force-8bit     Generate 8-bit sound even if 16-bit is available.\n"
   "--slt                  Turn SLT traps on.\n"
   "--traps                Turn tape traps on.\n\n"
   "Other options:\n\n"
   "--help                 This information.\n"
   "--machine <type>       Which machine should be emulated?\n"
   "--playback <filename>  Play back RZX file <filename>.\n"
   "--record <filename>    Record to RZX file <filename>.\n"
   "--separation <type>    Use ACB/ABC stereo for the AY-3-8912 sound chip.\n"
   "--snapshot <filename>  Load snapshot <filename>.\n"
   "--speed <percentage>   How fast should emulation run?\n"
   "--fb-mode <mode>       Which mode should be used for FB?\n"
   "--tape <filename>      Open tape file <filename>.\n"
   "--version              Print version number and exit.\n"
   "\n"
   "For help, please mail <fuse-emulator-devel@lists.sf.net> or use\n"
   "the forums at <http://sourceforge.net/p/fuse-emulator/discussion/>.\n"
   "For complete documentation, see the manual page of Fuse.\n\n" );
}

/* Stop all activities associated with actual Spectrum emulation */
int fuse_emulation_pause(void)
{
  int error;

  /* If we were already paused, just return. In any case, increment
     the pause count */
  if( fuse_emulation_paused++ ) return 0;

  /* Stop recording any competition mode RZX file */
  if( rzx_recording && rzx_competition_mode ) {
    ui_error( UI_ERROR_INFO, "Stopping competition mode RZX recording" );
    error = rzx_stop_recording(); if( error ) return error;
  }
      
  /* If we had sound enabled (and hence doing the speed regulation),
     turn it off */
  sound_pause();

  return 0;
}

/* Restart emulation activities */
int fuse_emulation_unpause(void)
{
  int error;

  /* If this doesn't start us running again, just return. In any case,
     decrement the pause count */
  if( --fuse_emulation_paused ) return 0;

  /* If we now want sound, enable it */
  sound_unpause();

  /* Restart speed estimation with no information */
  error = timer_estimate_reset(); if( error ) return error;

  return 0;
}

static int
setup_start_files( start_files_t *start_files )
{
  start_files->disk_plus3 = settings_current.plus3disk_file;
  start_files->disk_opus = settings_current.opusdisk_file;
  start_files->disk_plusd = settings_current.plusddisk_file;
  start_files->disk_didaktik80 = settings_current.didaktik80disk_file;
  start_files->disk_disciple = settings_current.discipledisk_file;
  start_files->disk_beta = settings_current.betadisk_file;
  start_files->dock = settings_current.dck_file;
  start_files->if2 = settings_current.if2_file;
  start_files->playback = settings_current.playback_file;
  start_files->recording = settings_current.record_file;
  start_files->snapshot = settings_current.snapshot;
  start_files->tape = settings_current.tape_file;

  start_files->simpleide_master =
    utils_safe_strdup( settings_current.simpleide_master_file );
  start_files->simpleide_slave = 
    utils_safe_strdup( settings_current.simpleide_slave_file );

  start_files->zxatasp_master =
    utils_safe_strdup( settings_current.zxatasp_master_file );
  start_files->zxatasp_slave =
    utils_safe_strdup( settings_current.zxatasp_slave_file );

  start_files->zxcf = utils_safe_strdup( settings_current.zxcf_pri_file );

  start_files->divide_master =
    utils_safe_strdup( settings_current.divide_master_file );
  start_files->divide_slave =
    utils_safe_strdup( settings_current.divide_slave_file );

  start_files->divmmc =
    utils_safe_strdup( settings_current.divmmc_file );

  start_files->zxmmc = utils_safe_strdup( settings_current.zxmmc_file );

  start_files->mdr[0] = settings_current.mdr_file;
  start_files->mdr[1] = settings_current.mdr_file2;
  start_files->mdr[2] = settings_current.mdr_file3;
  start_files->mdr[3] = settings_current.mdr_file4;
  start_files->mdr[4] = settings_current.mdr_file5;
  start_files->mdr[5] = settings_current.mdr_file6;
  start_files->mdr[6] = settings_current.mdr_file7;
  start_files->mdr[7] = settings_current.mdr_file8;

  return 0;
}

/* Make 'best guesses' as to what to do with non-option arguments */
static int
parse_nonoption_args( int argc, char **argv, int first_arg,
		      start_files_t *start_files )
{
  size_t i, j;
  const char *filename;
  utils_file file;
  libspectrum_id_t type;
  libspectrum_class_t class;
  int error;

#ifdef GEKKO
  /* No argv on the Wii. Just return */
  return 0;
#endif

  for( i = first_arg; i < (size_t)argc; i++ ) {

    filename = argv[i];

    error = utils_read_file( filename, &file );
    if( error ) return error;

    error = libspectrum_identify_file_with_class( &type, &class, filename,
						  file.buffer, file.length );
    if( error ) {
      utils_close_file( &file );
      return error;
    }

    switch( class ) {

    case LIBSPECTRUM_CLASS_CARTRIDGE_TIMEX:
      start_files->dock = filename; break;

    case LIBSPECTRUM_CLASS_CARTRIDGE_IF2:
      start_files->if2 = filename; break;

    case LIBSPECTRUM_CLASS_HARDDISK:
      if( settings_current.zxcf_active ) {
	start_files->zxcf = filename;
      } else if( settings_current.zxatasp_active ) {
	start_files->zxatasp_master = filename;
      } else if( settings_current.simpleide_active ) {
	start_files->simpleide_master = filename;
      } else if( settings_current.divide_enabled ) {
	start_files->divide_master = filename;
      } else if( settings_current.divmmc_enabled ) {
	start_files->divmmc = filename;
      } else if( settings_current.zxmmc_enabled ) {
	start_files->zxmmc = filename;
      } else {
	/* No IDE interface active, so activate the ZXCF */
	settings_current.zxcf_active = 1;
	start_files->zxcf = filename;
      }
      break;

    case LIBSPECTRUM_CLASS_DISK_PLUS3:
      start_files->disk_plus3 = filename; break;

    case LIBSPECTRUM_CLASS_DISK_OPUS:
      start_files->disk_opus = filename; break;

    case LIBSPECTRUM_CLASS_DISK_DIDAKTIK:
      start_files->disk_didaktik80 = filename; break;

    case LIBSPECTRUM_CLASS_DISK_PLUSD:
      if( periph_is_active( PERIPH_TYPE_DISCIPLE ) )
        start_files->disk_disciple = filename;
      else
        start_files->disk_plusd = filename;
      break;

    case LIBSPECTRUM_CLASS_DISK_TRDOS:
      start_files->disk_beta = filename; break;

    case LIBSPECTRUM_CLASS_DISK_GENERIC:
      if( machine_current->capabilities &
                 LIBSPECTRUM_MACHINE_CAPABILITY_PLUS3_DISK )
        start_files->disk_plus3 = filename;
      else if( machine_current->capabilities &
                 LIBSPECTRUM_MACHINE_CAPABILITY_TRDOS_DISK )
        start_files->disk_beta = filename; 
      else {
        if( periph_is_active( PERIPH_TYPE_BETA128 ) )
          start_files->disk_beta = filename; 
        else if( periph_is_active( PERIPH_TYPE_PLUSD ) )
          start_files->disk_plusd = filename;
        else if( periph_is_active( PERIPH_TYPE_DIDAKTIK80 ) )
          start_files->disk_didaktik80 = filename;
        else if( periph_is_active( PERIPH_TYPE_DISCIPLE ) )
          start_files->disk_disciple = filename;
        else if( periph_is_active( PERIPH_TYPE_OPUS ) )
          start_files->disk_opus = filename;
      }
      break;

    case LIBSPECTRUM_CLASS_RECORDING:
      start_files->playback = filename; break;

    case LIBSPECTRUM_CLASS_SNAPSHOT:
      start_files->snapshot = filename; break;

    case LIBSPECTRUM_CLASS_MICRODRIVE:
      for( j = 0; j < 8; j++ ) {
        if( !start_files->mdr[j] ) {
	  start_files->mdr[j] = filename;
	  break;
	}
      }
      break;

    case LIBSPECTRUM_CLASS_TAPE:
      start_files->tape = filename; break;

    case LIBSPECTRUM_CLASS_AUXILIARY:
      if( type == LIBSPECTRUM_ID_AUX_POK ) {
        pokemem_set_pokfile( filename );
      }
      break;

    case LIBSPECTRUM_CLASS_UNKNOWN:
      ui_error( UI_ERROR_WARNING, "couldn't identify '%s'; ignoring it",
		filename );
      break;

    default:
      ui_error( UI_ERROR_ERROR, "parse_nonoption_args: unknown file class %d",
		class );
      break;

    }

    utils_close_file( &file );
  }

  return 0;
}

static int
do_start_files( start_files_t *start_files )
{
  int autoload, error, i, check_snapshot;

  /* Can't do both input recording and playback */
  if( start_files->playback && start_files->recording ) {
    ui_error(
      UI_ERROR_WARNING,
      "can't do both input playback and recording; recording disabled"
    );
    start_files->recording = NULL;
  }

  /* Can't use both +3 and TR-DOS disks simultaneously */
  if( start_files->disk_plus3 && start_files->disk_beta ) {
    ui_error(
      UI_ERROR_WARNING,
      "can't use +3 and TR-DOS disks simultaneously; +3 disk ignored"
    );
    start_files->disk_plus3 = NULL;
  }

  /* Can't use disks and the dock simultaneously */
  if( ( start_files->disk_plus3 || start_files->disk_beta ) &&
      start_files->dock                                         ) {
    ui_error(
      UI_ERROR_WARNING,
      "can't use disks and the dock simultaneously; dock cartridge ignored"
    );
    start_files->dock = NULL;
  }

  /* Can't use disks and the Interface 2 simultaneously */
  if( ( start_files->disk_plus3 || start_files->disk_beta ) &&
      start_files->if2                                          ) {
    ui_error(
      UI_ERROR_WARNING,
      "can't use disks and the Interface 2 simultaneously; cartridge ignored"
    );
    start_files->if2 = NULL;
  }

  /* If a snapshot has been specified, don't autoload tape, disks etc */
  autoload = start_files->snapshot ? 0 : tape_can_autoload();

  /* Load in each of the files. Input recording must be done after
     snapshot loading such that the right snapshot is embedded into
     the file; input playback being done after snapshot loading means
     any embedded snapshot in the input recording will override any
     specified snapshot */

  if( start_files->disk_plus3 ) {
    error = utils_open_file( start_files->disk_plus3, autoload, NULL );
    if( error ) return error;
  }

  if( start_files->disk_plusd ) {
    error = utils_open_file( start_files->disk_plusd, autoload, NULL );
    if( error ) return error;
  }

  if( start_files->disk_didaktik80 ) {
    error = utils_open_file( start_files->disk_didaktik80, autoload, NULL );
    if( error ) return error;
  }

  if( start_files->disk_disciple ) {
    error = utils_open_file( start_files->disk_disciple, autoload, NULL );
    if( error ) return error;
  }

  if( start_files->disk_opus ) {
    error = utils_open_file( start_files->disk_opus, autoload, NULL );
    if( error ) return error;
  }

  if( start_files->disk_beta ) {
    error = utils_open_file( start_files->disk_beta, autoload, NULL );
    if( error ) return error;
  }

  if( start_files->dock ) {
    error = utils_open_file( start_files->dock, autoload, NULL );
    if( error ) return error;
  }

  if( start_files->if2 ) {
    error = utils_open_file( start_files->if2, autoload, NULL );
    if( error ) return error;
  }

  if( start_files->snapshot ) {
    error = utils_open_file( start_files->snapshot, autoload, NULL );
    if( error ) return error;
  }

  if( start_files->tape ) {
    error = utils_open_file( start_files->tape, autoload, NULL );
    if( error ) return error;
  }

  /* Microdrive cartridges */

  for( i = 0; i < 8; i++ ) {
    if( start_files->mdr[i] ) {
      error = utils_open_file( start_files->mdr[i], autoload, NULL );
      if( error ) return error;
    }
  }

  /* IDE hard disk images */

  if( start_files->simpleide_master ) {
    error = simpleide_insert( start_files->simpleide_master,
			      LIBSPECTRUM_IDE_MASTER );
    simpleide_reset( 0 );
    if( error ) return error;
  }

  if( start_files->simpleide_slave ) {
    error = simpleide_insert( start_files->simpleide_slave,
			      LIBSPECTRUM_IDE_SLAVE );
    simpleide_reset( 0 );
    if( error ) return error;
  }

  if( start_files->zxatasp_master ) {
    error = zxatasp_insert( start_files->zxatasp_master,
			    LIBSPECTRUM_IDE_MASTER );
    if( error ) return error;
  }

  if( start_files->zxatasp_slave ) {
    error = zxatasp_insert( start_files->zxatasp_slave,
			    LIBSPECTRUM_IDE_SLAVE );
    if( error ) return error;
  }

  if( start_files->zxcf ) {
    error = zxcf_insert( start_files->zxcf ); if( error ) return error;
  }

  if( start_files->divide_master ) {
    error = divide_insert( start_files->divide_master,
			    LIBSPECTRUM_IDE_MASTER );
    if( error ) return error;
  }

  if( start_files->divide_slave ) {
    error = divide_insert( start_files->divide_slave,
			    LIBSPECTRUM_IDE_SLAVE );
    if( error ) return error;
  }

  if( start_files->divmmc ) {
    error = divmmc_insert( start_files->divmmc );
    if( error ) return error;
  }

  if( start_files->zxmmc ) {
    error = zxmmc_insert( start_files->zxmmc );
    if( error ) return error;
  }

  /* Input recordings */

  if( start_files->playback ) {
    check_snapshot = start_files->snapshot ? 0 : 1;
    error = rzx_start_playback( start_files->playback, check_snapshot );
    if( error ) return error;
  }

  if( start_files->recording ) {
    error = rzx_start_recording( start_files->recording,
				 settings_current.embed_snapshot );
    if( error ) return error;
  }

  return 0;
}

/* Tidy-up function called at end of emulation */
static int fuse_end(void)
{
  movie_stop();		/* stop movie recording */

  startup_manager_run_end();

  periph_end();
  ui_end();
  ui_media_drive_end();
  module_end();
  pokemem_end();

  svg_capture_end();

  libspectrum_end();

  return 0;
}

/* Emergency shutdown */
void
fuse_abort( void )
{
  fuse_end();
  abort();
}
