/* libspectrum.c: Some general routines
   Copyright (c) 2001-2004 Philip Kendall, Darren Salt, Fredrick Meunier

   $Id: libspectrum.c,v 1.84 2007/02/02 16:35:42 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 <config.h>

#include <stdio.h>
#include <string.h>
#ifdef HAVE_STRINGS_H
#include <strings.h>		/* Needed for strcasecmp() on QNX6 */
#endif				/* #ifdef HAVE_STRINGS_H */

#ifdef HAVE_GCRYPT_H

#include <gcrypt.h>

/* The version of libgcrypt that we need */
static const char *MIN_GCRYPT_VERSION = "1.1.42";

#endif				/* #ifdef HAVE_GCRYPT_H */

static const char *gcrypt_version;

#include "internals.h"

/* The various real devices that can be attached to a virtual joystick */
const int LIBSPECTRUM_JOYSTICK_INPUT_NONE             = 0;
const int LIBSPECTRUM_JOYSTICK_INPUT_KEYBOARD         = 1 << 0;
const int LIBSPECTRUM_JOYSTICK_INPUT_JOYSTICK_1       = 1 << 1;
const int LIBSPECTRUM_JOYSTICK_INPUT_JOYSTICK_2       = 1 << 2;

libspectrum_error
libspectrum_default_error_function( libspectrum_error error,
				    const char *format, va_list ap );

/* The function to call on errors */
libspectrum_error_function_t libspectrum_error_function =
  libspectrum_default_error_function;

static void
gcrypt_log_handler( void *opaque, int level, const char *format, va_list ap );

/* Initialise the library */
libspectrum_error
libspectrum_init( void )
{
#ifdef HAVE_GCRYPT_H

  if( !gcry_control( GCRYCTL_ANY_INITIALIZATION_P ) ) {

    gcrypt_version = gcry_check_version( MIN_GCRYPT_VERSION );
    if( !gcrypt_version ) {
      libspectrum_print_error(
        LIBSPECTRUM_ERROR_LOGIC,
	"libspectrum_init: found libgcrypt %s, but need %s",
	gcry_check_version( NULL ), MIN_GCRYPT_VERSION
      );
      return LIBSPECTRUM_ERROR_LOGIC;	/* FIXME: better error code */
    }

    /* Ugly hack to prevent the 'Secure memory is not locked into
       core' message appearing */
    gcry_set_log_handler( gcrypt_log_handler, NULL );

    /* Initialise the 'secure' memory (which probably won't actually
       be scure, but that doesn't matter as libspectrum's 'security'
       is bogus anyway) */
    gcry_control( GCRYCTL_INIT_SECMEM, 16384 );

    /* Restore the default log handler */
    gcry_set_log_handler( NULL, NULL );

    gcry_control( GCRYCTL_INITIALIZATION_FINISHED );
  }

#else				/* #ifdef HAVE_GCRYPT_H */

  gcrypt_version = NULL;

#endif				/* #ifdef HAVE_GCRYPT_H */

  return LIBSPECTRUM_ERROR_NONE;
}

static void
gcrypt_log_handler( void *opaque, int level, const char *format, va_list ap )
{
  /* Do nothing */
}

int
libspectrum_check_version( const char *version )
{
  size_t i;

  int actual_version[4]   = { 0, 0, 0, 0 },
      required_version[4] = { 0, 0, 0, 0 };

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

  for( i = 0; i < 4; i++ ) {
    
    if( actual_version[i] < required_version[i] ) return 0;
    if( actual_version[i] > required_version[i] ) return 1;

  }

  /* All version numbers exactly equal, so return OK */
  return 1;
}

const char *
libspectrum_version( void )
{
  return VERSION;
}

const char *
libspectrum_gcrypt_version( void )
{
  return gcrypt_version;
}

libspectrum_error
libspectrum_print_error( libspectrum_error error, const char *format, ... )
{
  va_list ap;

  /* If we don't have an error function, do nothing */
  if( !libspectrum_error_function ) return LIBSPECTRUM_ERROR_NONE;

  /* Otherwise, call that error function */
  va_start( ap, format );
  libspectrum_error_function( error, format, ap );
  va_end( ap );

  return LIBSPECTRUM_ERROR_NONE;
}

/* Default error action is just to print a message to stderr */
libspectrum_error
libspectrum_default_error_function( libspectrum_error error,
				    const char *format, va_list ap )
{
   fprintf( stderr, "libspectrum error: " );
  vfprintf( stderr, format, ap );
   fprintf( stderr, "\n" );

  if( error == LIBSPECTRUM_ERROR_LOGIC ) abort();

  return LIBSPECTRUM_ERROR_NONE;
}

/* Get the name of a specific joystick type */
const char *
libspectrum_joystick_name( libspectrum_joystick type )
{
  switch( type ) {
  case LIBSPECTRUM_JOYSTICK_CURSOR:     return "Cursor";
  case LIBSPECTRUM_JOYSTICK_KEMPSTON:   return "Kempston";
  case LIBSPECTRUM_JOYSTICK_SINCLAIR_1: return "Sinclair 1";
  case LIBSPECTRUM_JOYSTICK_SINCLAIR_2: return "Sinclair 2";
  case LIBSPECTRUM_JOYSTICK_TIMEX_1:    return "Timex 1";
  case LIBSPECTRUM_JOYSTICK_TIMEX_2:    return "Timex 2";
  case LIBSPECTRUM_JOYSTICK_FULLER:     return "Fuller";

  case LIBSPECTRUM_JOYSTICK_NONE:       return "(None)";
  }

  return "(unknown type)";
}

/* Get the name of a specific machine type */
const char *
libspectrum_machine_name( libspectrum_machine type )
{
  switch( type ) {
  case LIBSPECTRUM_MACHINE_16:     return "Spectrum 16K";
  case LIBSPECTRUM_MACHINE_48:     return "Spectrum 48K";
  case LIBSPECTRUM_MACHINE_TC2048: return "Timex TC2048";
  case LIBSPECTRUM_MACHINE_TC2068: return "Timex TC2068";
  case LIBSPECTRUM_MACHINE_TS2068: return "Timex TS2068";
  case LIBSPECTRUM_MACHINE_128:    return "Spectrum 128K";
  case LIBSPECTRUM_MACHINE_PLUS2:  return "Spectrum +2";
  case LIBSPECTRUM_MACHINE_PENT:   return "Pentagon 128K";
  case LIBSPECTRUM_MACHINE_PLUS2A: return "Spectrum +2A";
  case LIBSPECTRUM_MACHINE_PLUS3:  return "Spectrum +3";
  case LIBSPECTRUM_MACHINE_PLUS3E: return "Spectrum +3e";
  case LIBSPECTRUM_MACHINE_SCORP:  return "Scorpion ZS 256";
  case LIBSPECTRUM_MACHINE_SE:     return "Spectrum SE";

  case LIBSPECTRUM_MACHINE_UNKNOWN: return "(unknown)";
  }

  return "(unknown type)";
}

/* The various capabilities of the different machines */
const int LIBSPECTRUM_MACHINE_CAPABILITY_AY           = 1 << 0; /* AY-3-8192 */
const int LIBSPECTRUM_MACHINE_CAPABILITY_128_MEMORY   = 1 << 1;
                                                  /* 128-style memory paging */
const int LIBSPECTRUM_MACHINE_CAPABILITY_PLUS3_MEMORY = 1 << 2;
                                                   /* +3-style memory paging */
const int LIBSPECTRUM_MACHINE_CAPABILITY_PLUS3_DISK   = 1 << 3;
                                                      /* +3-style disk drive */
const int LIBSPECTRUM_MACHINE_CAPABILITY_TIMEX_MEMORY = 1 << 4;
                                            /* TC20[46]8-style memory paging */
const int LIBSPECTRUM_MACHINE_CAPABILITY_TIMEX_VIDEO  = 1 << 5;
                                              /* TC20[46]8-style video modes */
const int LIBSPECTRUM_MACHINE_CAPABILITY_TRDOS_DISK   = 1 << 6;
                                                   /* TRDOS-style disk drive */
const int LIBSPECTRUM_MACHINE_CAPABILITY_TIMEX_DOCK   = 1 << 7;
                                           /* T[SC]2068-style cartridge port */
const int LIBSPECTRUM_MACHINE_CAPABILITY_SINCLAIR_JOYSTICK = 1 << 8;
                                            /* Sinclair-style joystick ports */
const int LIBSPECTRUM_MACHINE_CAPABILITY_KEMPSTON_JOYSTICK = 1 << 9;
                                            /* Kempston-style joystick ports */
const int LIBSPECTRUM_MACHINE_CAPABILITY_SCORP_MEMORY = 1 << 10;
                                             /* Scorpion-style memory paging */
const int LIBSPECTRUM_MACHINE_CAPABILITY_EVEN_M1 = 1 << 11;
                             /* M1 cycles always start on even tstate counts */
const int LIBSPECTRUM_MACHINE_CAPABILITY_SE_MEMORY = 1 << 12;
                                                   /* SE-style memory paging */
const int LIBSPECTRUM_MACHINE_CAPABILITY_NTSC = 1 << 13;
                                                             /* NTSC display */

/* Given a machine type, what features does it have? */
int
libspectrum_machine_capabilities( libspectrum_machine type )
{
  int capabilities = 0;

  /* AY-3-8912 */
  switch( type ) {
  case LIBSPECTRUM_MACHINE_128: case LIBSPECTRUM_MACHINE_PLUS2:
  case LIBSPECTRUM_MACHINE_PLUS2A: case LIBSPECTRUM_MACHINE_PLUS3:
  case LIBSPECTRUM_MACHINE_PLUS3E:
  case LIBSPECTRUM_MACHINE_TC2068: case LIBSPECTRUM_MACHINE_TS2068:
  case LIBSPECTRUM_MACHINE_PENT: case LIBSPECTRUM_MACHINE_SCORP:
  case LIBSPECTRUM_MACHINE_SE:
    capabilities |= LIBSPECTRUM_MACHINE_CAPABILITY_AY; break;
  default:
    break;
  }

  /* 128K Spectrum-style 0x7ffd memory paging */
  switch( type ) {
  case LIBSPECTRUM_MACHINE_128: case LIBSPECTRUM_MACHINE_PLUS2:
  case LIBSPECTRUM_MACHINE_PLUS2A: case LIBSPECTRUM_MACHINE_PLUS3:
  case LIBSPECTRUM_MACHINE_PLUS3E:
  case LIBSPECTRUM_MACHINE_PENT: case LIBSPECTRUM_MACHINE_SCORP:
/* FIXME: SE needs to have this capability to be considered a 128k machine */
  case LIBSPECTRUM_MACHINE_SE:
    capabilities |= LIBSPECTRUM_MACHINE_CAPABILITY_128_MEMORY; break;
  default:
    break;
  }

  /* +3 Spectrum-style 0x1ffd memory paging */
  switch( type ) {
  case LIBSPECTRUM_MACHINE_PLUS2A: case LIBSPECTRUM_MACHINE_PLUS3:
  case LIBSPECTRUM_MACHINE_PLUS3E:
    capabilities |= LIBSPECTRUM_MACHINE_CAPABILITY_PLUS3_MEMORY; break;
  default:
    break;
  }

  /* +3 Spectrum-style disk */
  switch( type ) {
  case LIBSPECTRUM_MACHINE_PLUS3: case LIBSPECTRUM_MACHINE_PLUS3E:
    capabilities |= LIBSPECTRUM_MACHINE_CAPABILITY_PLUS3_DISK; break;
  default:
    break;
  }

  /* T[CS]20[46]8-style 0x00fd memory paging */
  switch( type ) {
  case LIBSPECTRUM_MACHINE_TC2048: case LIBSPECTRUM_MACHINE_TC2068:
  case LIBSPECTRUM_MACHINE_TS2068:
    capabilities |= LIBSPECTRUM_MACHINE_CAPABILITY_TIMEX_MEMORY; break;
  default:
    break;
  }

  /* T[CS]20[46]8-style 0x00ff video mode selection */
  switch( type ) {
  case LIBSPECTRUM_MACHINE_TC2048: case LIBSPECTRUM_MACHINE_TC2068:
  case LIBSPECTRUM_MACHINE_TS2068: case LIBSPECTRUM_MACHINE_SE:
    capabilities |= LIBSPECTRUM_MACHINE_CAPABILITY_TIMEX_VIDEO; break;
  default:
    break;
  }

  /* TRDOS-style disk */
  switch( type ) {
  case LIBSPECTRUM_MACHINE_PENT: case LIBSPECTRUM_MACHINE_SCORP:
    capabilities |= LIBSPECTRUM_MACHINE_CAPABILITY_TRDOS_DISK; break;
  default:
    break;
  }

  /* T[SC]2068-style cartridge port */
  switch( type ) {
  case LIBSPECTRUM_MACHINE_TC2068: case LIBSPECTRUM_MACHINE_TS2068:
    capabilities |= LIBSPECTRUM_MACHINE_CAPABILITY_TIMEX_DOCK; break;
  default:
    break;
  }

  /* Sinclair-style joystick ports */
  switch( type ) {
  case LIBSPECTRUM_MACHINE_PLUS2: case LIBSPECTRUM_MACHINE_PLUS2A:
  case LIBSPECTRUM_MACHINE_PLUS3: case LIBSPECTRUM_MACHINE_PLUS3E:
    capabilities |= LIBSPECTRUM_MACHINE_CAPABILITY_SINCLAIR_JOYSTICK; break;
  default:
    break;
  }

  /* Kempston-style joystick ports */
  switch( type ) {
  case LIBSPECTRUM_MACHINE_TC2048:
  case LIBSPECTRUM_MACHINE_SE:
    capabilities |= LIBSPECTRUM_MACHINE_CAPABILITY_KEMPSTON_JOYSTICK; break;
  default:
    break;
  }

  /* Scorpion-style 0x1ffd memory paging */
  switch( type ) {
  case LIBSPECTRUM_MACHINE_SCORP:
    capabilities |= LIBSPECTRUM_MACHINE_CAPABILITY_SCORP_MEMORY; break;
  default:
    break;
  }

  /* M1 cycles forced to start on even tstates */
  switch( type ) {
  case LIBSPECTRUM_MACHINE_SCORP:
    capabilities |= LIBSPECTRUM_MACHINE_CAPABILITY_EVEN_M1; break;
  default:
    break;
  }

  /* SE-style 0x7ffd and 0x00fd memory paging */
  switch( type ) {
  case LIBSPECTRUM_MACHINE_SE:
    capabilities |= LIBSPECTRUM_MACHINE_CAPABILITY_SE_MEMORY; break;
  default:
    break;
  }

  /* NTSC display */
  switch( type ) {
  case LIBSPECTRUM_MACHINE_TS2068:
    capabilities |= LIBSPECTRUM_MACHINE_CAPABILITY_NTSC; break;
  default:
    break;
  }

  return capabilities;
}

/* Given a buffer and optionally a filename, make a best guess as to
   what sort of file this is */
libspectrum_error
libspectrum_identify_file_with_class(
  libspectrum_id_t *type, libspectrum_class_t *libspectrum_class,
  const char *filename, const unsigned char *buffer, size_t length )
{
  libspectrum_error error;
  char *new_filename; unsigned char *new_buffer; size_t new_length;

  error = libspectrum_identify_file_raw( type, filename, buffer, length );
  if( error ) return error;

  error = libspectrum_identify_class( libspectrum_class, *type );
  if( error ) return error;

  if( *libspectrum_class != LIBSPECTRUM_CLASS_COMPRESSED )
    return LIBSPECTRUM_ERROR_NONE;

  error = libspectrum_uncompress_file( &new_buffer, &new_length, &new_filename,
				       *type, buffer, length, filename );
  if( error ) return error;

  error = libspectrum_identify_file_with_class( type, libspectrum_class,
						new_filename, new_buffer,
						new_length );
  if( error ) return error;

  free( new_filename ); free( new_buffer );

  return LIBSPECTRUM_ERROR_NONE;
}

/* Identify a file, but without worrying about its class */
libspectrum_error
libspectrum_identify_file( libspectrum_id_t *type, const char *filename,
			   const unsigned char *buffer, size_t length )
{
  libspectrum_class_t class;

  return libspectrum_identify_file_with_class( type, &class, filename,
					       buffer, length );
}

/* Identify a file without attempting to decompress it */
libspectrum_error
libspectrum_identify_file_raw( libspectrum_id_t *type, const char *filename,
			       const unsigned char *buffer, size_t length )
{
  struct type {

    int type;

    const char *extension; int extension_score;

    const char *signature; size_t offset, length; int sig_score;
  };

  struct type *ptr,
    types[] = {
      
      { LIBSPECTRUM_ID_RECORDING_RZX, "rzx", 3, "RZX!",		    0, 4, 4 },

      { LIBSPECTRUM_ID_SNAPSHOT_SNA,  "sna", 3, NULL,		    0, 0, 0 },
      { LIBSPECTRUM_ID_SNAPSHOT_SNP,  "snp", 3, NULL,		    0, 0, 0 },
      { LIBSPECTRUM_ID_SNAPSHOT_SP,   "sp",  3, "\x53\x50\0",	    0, 3, 1 },
      { LIBSPECTRUM_ID_SNAPSHOT_SZX,  "szx", 3, "ZXST",		    0, 4, 4 },
      { LIBSPECTRUM_ID_SNAPSHOT_Z80,  "z80", 3, "\0\0",		    6, 2, 1 },
      /* .slt files also dealt with by the .z80 loading code */
      { LIBSPECTRUM_ID_SNAPSHOT_Z80,  "slt", 3, "\0\0",		    6, 2, 1 },
      { LIBSPECTRUM_ID_SNAPSHOT_ZXS,  "zxs", 3, "SNAP",		    8, 4, 4 },

      { LIBSPECTRUM_ID_CARTRIDGE_DCK, "dck", 3, NULL,		    0, 0, 0 },
      { LIBSPECTRUM_ID_CARTRIDGE_IF2, "rom", 3, NULL,		    0, 0, 0 },

      { LIBSPECTRUM_ID_MICRODRIVE_MDR, "mdr", 3, NULL,		    0, 0, 0 },

      { LIBSPECTRUM_ID_TAPE_TAP,      "tap", 3, "\x13\0\0",	    0, 3, 1 },
      { LIBSPECTRUM_ID_TAPE_TZX,      "tzx", 3, "ZXTape!",	    0, 7, 4 },
      { LIBSPECTRUM_ID_TAPE_WARAJEVO, "tap", 2, "\xff\xff\xff\xff", 8, 4, 2 },

      { LIBSPECTRUM_ID_DISK_DSK,      "dsk", 3, NULL,		    0, 0, 0 },
      { LIBSPECTRUM_ID_DISK_SCL,      "scl", 3, "SINCLAIR",         0, 8, 4 },
      { LIBSPECTRUM_ID_DISK_TRD,      "trd", 3, NULL,		    0, 0, 0 },

      { LIBSPECTRUM_ID_HARDDISK_HDF,  "hdf", 3, "RS-IDE\x1a",	    0, 7, 4 },

      { LIBSPECTRUM_ID_COMPRESSED_BZ2,"bz2", 3, "BZh",		    0, 3, 4 },
      { LIBSPECTRUM_ID_COMPRESSED_GZ, "gz",  3, "\x1f\x8b",	    0, 2, 4 },

      { LIBSPECTRUM_ID_TAPE_Z80EM,    "raw", 1, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0Raw tape sample",  0, 64, 0 },
      { LIBSPECTRUM_ID_TAPE_CSW,      "csw", 2, "Compressed Square Wave\x1a",  0, 23, 4 },

      { -1, NULL, 0, NULL, 0, 0, 0 }, /* End marker */

    };

  const char *extension = NULL;
  int score, best_score, best_guess, duplicate_best;

  /* Get the filename extension, if it exists */
  if( filename ) {
    extension = strrchr( filename, '.' ); if( extension ) extension++;
  }

  best_guess = LIBSPECTRUM_ID_UNKNOWN; best_score = 0; duplicate_best = 0;

  /* Compare against known extensions and signatures */
  for( ptr = types; ptr->type != -1; ptr++ ) {

    score = 0;

    if( extension && ptr->extension &&
	!strcasecmp( extension, ptr->extension ) )
      score += ptr->extension_score;

    if( ptr->signature && length >= ptr->offset + ptr->length &&
	!memcmp( &buffer[ ptr->offset ], ptr->signature, ptr->length ) )
      score += ptr->sig_score;

    if( score > best_score ) {
      best_guess = ptr->type; best_score = score; duplicate_best = 0;
    } else if( score == best_score && ptr->type != best_guess ) {
      duplicate_best = 1;
    }
  }

  /* If two things were equally good, we can't identify this. Otherwise,
     return our best guess */
  if( duplicate_best ) {
    *type = LIBSPECTRUM_ID_UNKNOWN;
  } else {
    *type = best_guess;
  }

  return LIBSPECTRUM_ERROR_NONE;
}

/* What generic 'class' of file is this file */
libspectrum_error
libspectrum_identify_class( libspectrum_class_t *libspectrum_class,
			    libspectrum_id_t type )
{
  switch( type ) {

  case LIBSPECTRUM_ID_UNKNOWN:
    *libspectrum_class = LIBSPECTRUM_CLASS_UNKNOWN; return 0;
  
  case LIBSPECTRUM_ID_CARTRIDGE_DCK:
    *libspectrum_class = LIBSPECTRUM_CLASS_CARTRIDGE_TIMEX; return 0;

  case LIBSPECTRUM_ID_COMPRESSED_BZ2:
  case LIBSPECTRUM_ID_COMPRESSED_GZ:
    *libspectrum_class = LIBSPECTRUM_CLASS_COMPRESSED; return 0;

  case LIBSPECTRUM_ID_DISK_DSK:
    *libspectrum_class = LIBSPECTRUM_CLASS_DISK_PLUS3; return 0;

  case LIBSPECTRUM_ID_DISK_SCL:
  case LIBSPECTRUM_ID_DISK_TRD:
    *libspectrum_class = LIBSPECTRUM_CLASS_DISK_TRDOS; return 0;

  case LIBSPECTRUM_ID_HARDDISK_HDF:
    *libspectrum_class = LIBSPECTRUM_CLASS_HARDDISK; return 0;

  case LIBSPECTRUM_ID_MICRODRIVE_MDR:
    *libspectrum_class = LIBSPECTRUM_CLASS_MICRODRIVE; return 0;

  case LIBSPECTRUM_ID_RECORDING_RZX:
    *libspectrum_class = LIBSPECTRUM_CLASS_RECORDING; return 0;

  case LIBSPECTRUM_ID_SNAPSHOT_PLUSD:
  case LIBSPECTRUM_ID_SNAPSHOT_SNA:
  case LIBSPECTRUM_ID_SNAPSHOT_SNP:
  case LIBSPECTRUM_ID_SNAPSHOT_SP:
  case LIBSPECTRUM_ID_SNAPSHOT_SZX:
  case LIBSPECTRUM_ID_SNAPSHOT_Z80:
  case LIBSPECTRUM_ID_SNAPSHOT_ZXS:
    *libspectrum_class = LIBSPECTRUM_CLASS_SNAPSHOT; return 0;

  case LIBSPECTRUM_ID_TAPE_TAP:
  case LIBSPECTRUM_ID_TAPE_TZX:
  case LIBSPECTRUM_ID_TAPE_WARAJEVO:
  case LIBSPECTRUM_ID_TAPE_Z80EM:
  case LIBSPECTRUM_ID_TAPE_CSW:
    *libspectrum_class = LIBSPECTRUM_CLASS_TAPE; return 0;

  case LIBSPECTRUM_ID_CARTRIDGE_IF2:
    *libspectrum_class = LIBSPECTRUM_CLASS_CARTRIDGE_IF2; return 0;

  }

  libspectrum_print_error( LIBSPECTRUM_ERROR_UNKNOWN,
			   "Unknown file type %d", type );
  return LIBSPECTRUM_ERROR_UNKNOWN;
}

libspectrum_error
libspectrum_uncompress_file( unsigned char **new_buffer, size_t *new_length,
			     char **new_filename, libspectrum_id_t type,
			     const unsigned char *old_buffer,
			     size_t old_length, const char *old_filename )
{
  libspectrum_class_t class;
  libspectrum_error error;

  error = libspectrum_identify_class( &class, type );
  if( error ) return error;

  if( class != LIBSPECTRUM_CLASS_COMPRESSED ) {
    libspectrum_print_error( LIBSPECTRUM_ERROR_LOGIC,
			     "file type %d is not a compressed type", type );
    return LIBSPECTRUM_ERROR_LOGIC;
  }

  if( new_filename && old_filename ) {
    *new_filename = strdup( old_filename );
    if( !*new_filename ) {
      libspectrum_print_error( LIBSPECTRUM_ERROR_MEMORY,
			       "out of memory at %s:%d", __FILE__, __LINE__ );
      return LIBSPECTRUM_ERROR_MEMORY;
    }
  }

  /* Tells the inflation routines to allocate memory for us */
  *new_length = 0;
  
  switch( type ) {

  case LIBSPECTRUM_ID_COMPRESSED_BZ2:

#ifdef HAVE_LIBBZ2

    if( new_filename ) {
      if( strlen( *new_filename ) >= 4 &&
	  !strcasecmp( &(*new_filename)[ strlen( *new_filename ) - 4 ],
		       ".bz2" ) )
	(*new_filename)[ strlen( *new_filename ) - 4 ] = '\0';
    }

    error = libspectrum_bzip2_inflate( old_buffer, old_length,
				       new_buffer, new_length );
    if( error ) { free( new_filename ); return error; }

#else				/* #ifdef HAVE_LIBBZ2 */

    libspectrum_print_error(
      LIBSPECTRUM_ERROR_UNKNOWN,
      "libbz2 not available to decompress bzipped file"
    );
    free( new_filename );
    return LIBSPECTRUM_ERROR_UNKNOWN;

#endif				/* #ifdef HAVE_LIBBZ2 */

    break;

  case LIBSPECTRUM_ID_COMPRESSED_GZ:

#ifdef HAVE_ZLIB_H

    if( new_filename ) {
      if( strlen( *new_filename ) >= 3 &&
	  !strcasecmp( &(*new_filename)[ strlen( *new_filename ) - 3 ],
		       ".gz" ) )
	(*new_filename)[ strlen( *new_filename ) - 3 ] = '\0';
    }
      
    error = libspectrum_gzip_inflate( old_buffer, old_length,
				      new_buffer, new_length );
    if( error ) { free( new_filename ); return error; }

#else				/* #ifdef HAVE_ZLIB_H */

    libspectrum_print_error( LIBSPECTRUM_ERROR_UNKNOWN,
			     "zlib not available to decompress gzipped file" );
    free( new_filename );
    return LIBSPECTRUM_ERROR_UNKNOWN;

#endif				/* #ifdef HAVE_ZLIB_H */

    break;

  default:
    libspectrum_print_error( LIBSPECTRUM_ERROR_LOGIC,
			     "unknown compressed type %d", type );
    free( new_filename );
    return LIBSPECTRUM_ERROR_LOGIC;
  }

  return LIBSPECTRUM_ERROR_NONE;
}

/* Ensure there is room for `requested' characters after the current
   position `ptr' in `buffer'. If not, realloc() and update the
   pointers as necessary */
int
libspectrum_make_room( libspectrum_byte **dest, size_t requested,
		       libspectrum_byte **ptr, size_t *allocated )
{
  size_t current_length;

  current_length = *ptr - *dest;

  if( *allocated == 0 ) {

    (*allocated) = requested;

    *dest = (libspectrum_byte*)malloc( requested * sizeof(libspectrum_byte) );
    if( *dest == NULL ) return 1;

  } else {

    /* If there's already enough room here, just return */
    if( current_length + requested <= (*allocated) ) return 0;

    /* Make the new size the maximum of the new needed size and the
     old allocated size * 2 */
    (*allocated) =
      current_length + requested > 2 * (*allocated) ?
      current_length + requested :
      2 * (*allocated);

    *dest = (libspectrum_byte*)
      realloc( *dest, (*allocated) * sizeof( libspectrum_byte ) );
    if( *dest == NULL ) return 1;

  }

  /* Update the secondary pointer to the block */
  *ptr = *dest + current_length;

  return 0;

}

/* Read an LSB word from 'buffer' */
libspectrum_word
libspectrum_read_word( const libspectrum_byte **buffer )
{
  libspectrum_word value;

  value = (*buffer)[0] + (*buffer)[1] * 0x100;

  (*buffer) += 2;

  return value;
}

/* Read an LSB dword from buffer */
libspectrum_dword
libspectrum_read_dword( const libspectrum_byte **buffer )
{
  libspectrum_dword value;

  value = (*buffer)[0]             +
          (*buffer)[1] *     0x100 +
	  (*buffer)[2] *   0x10000 +
          (*buffer)[3] * 0x1000000 ;

  (*buffer) += 4;

  return value;
}

/* Write an (LSB) word to buffer */
int
libspectrum_write_word( libspectrum_byte **buffer, libspectrum_word w )
{
  *(*buffer)++ = w & 0xff;
  *(*buffer)++ = w >> 8;
  return LIBSPECTRUM_ERROR_NONE;
}

/* Write an LSB dword to buffer */
int libspectrum_write_dword( libspectrum_byte **buffer, libspectrum_dword d )
{
  *(*buffer)++ = ( d & 0x000000ff )      ;
  *(*buffer)++ = ( d & 0x0000ff00 ) >>  8;
  *(*buffer)++ = ( d & 0x00ff0000 ) >> 16;
  *(*buffer)++ = ( d & 0xff000000 ) >> 24;
  return LIBSPECTRUM_ERROR_NONE;
}
