//#include "mamalleg.h"
#include <sys/stat.h>
#include <unistd.h>
#include <signal.h>
#include "driver.h"
#include "unzip.h"
#include "rc.h"
#include "zlib.h"

#ifdef MESS
#include "mess/msdos.h"
#endif

/* Verbose outputs to error.log ? */
#define VERBOSE 	0

/* Use the file cache ? */
#define FILE_CACHE	1

#if VERBOSE
#define LOG(x)	logerror x
#else
#define LOG(x)	/* x */
#endif

char *roms = NULL;
char **rompathv = NULL;
int rompathc = 0;

char *samples = NULL;
char **samplepathv = NULL;
int samplepathc = 0;

static const char *rompath;
static const char *samplepath;
static const char *cfgdir, *nvdir, *hidir, *inpdir, *stadir;
static const char *memcarddir, *artworkdir, *screenshotdir, *cheatdir;
#ifdef PINMAME_EXT
static const char *wavedir;
#endif /* PINMAME_EXT */
/* from datafile.c */
extern const char *history_filename;
extern const char *mameinfo_filename;
/* from cheat.c */
extern char *cheatfile;

static int decompose_rompath(struct rc_option *option, const char *arg, int priority);
static int decompose_samplepath(struct rc_option *option, const char *arg, int priority);

struct rc_option fileio_opts[] =
{
	/* name, shortname, type, dest, deflt, min, max, func, help */
	{ "Windows path and directory options", NULL, rc_seperator, NULL, NULL, 0, 0, NULL, NULL },
	{ "rompath", "rp", rc_string, &rompath, "roms", 0, 0, decompose_rompath, "path to romsets" },
	{ "samplepath", "sp", rc_string, &samplepath, "samples", 0, 0, decompose_samplepath, "path to samplesets" },
	{ "cfg_directory", NULL, rc_string, &cfgdir, "cfg", 0, 0, NULL, "directory to save configurations" },
	{ "nvram_directory", NULL, rc_string, &nvdir, "nvram", 0, 0, NULL, "directory to save nvram contents" },
	{ "memcard_directory", NULL, rc_string, &memcarddir, "memcard", 0, 0, NULL, "directory to save memory card contents" },
	{ "input_directory", NULL, rc_string, &inpdir, "inp", 0, 0, NULL, "directory to save input device logs" },
	{ "hiscore_directory", NULL, rc_string, &hidir, "hi", 0, 0, NULL, "directory to save hiscores" },
	{ "state_directory", NULL, rc_string, &stadir, "sta", 0, 0, NULL, "directory to save states" },
	{ "artwork_directory", NULL, rc_string, &artworkdir, "artwork", 0, 0, NULL, "directory for Artwork (Overlays etc.)" },
	{ "snapshot_directory", NULL, rc_string, &screenshotdir, "snap", 0, 0, NULL, "directory for screenshots (.png format)" },
#ifdef PINMAME_EXT
	{ "wavefile_directory", NULL, rc_string, &wavedir, "wavefile", 0, 0, NULL, "directory for wavefiles (.wav format)" },
#endif /* PINMAME_EXT */
	{ "cheat_file", NULL, rc_string, &cheatfile, "cheat.dat", 0, 0, NULL, "cheat filename" },
	{ "history_file", NULL, rc_string, &history_filename, "history.dat", 0, 0, NULL, NULL },
	{ "mameinfo_file", NULL, rc_string, &mameinfo_filename, "mameinfo.dat", 0, 0, NULL, NULL },
	{ NULL,	NULL, rc_end, NULL, NULL, 0, 0,	NULL, NULL }
};


char *alternate_name;				   /* for "-romdir" */

typedef enum
{
	kPlainFile,
	kRAMFile,
	kZippedFile
}	eFileType;

typedef struct
{
	FILE *file;
	unsigned char *data;
	unsigned int offset;
	unsigned int length;
	eFileType type;
	unsigned int crc;
}	FakeFileHandle;


//extern unsigned int crc32 (unsigned int crc, const unsigned char *buf, unsigned int len);
static int checksum_file (const char *file, unsigned char **p, unsigned int *size, unsigned int *crc);

/*
 * File stat cache LRU (Last Recently Used)
 */

#if FILE_CACHE
struct file_cache_entry
{
	struct stat stat_buffer;
	int result;
	char *file;
};

/* File cache buffer */
static struct file_cache_entry **file_cache_map = 0;

/* File cache size */
static unsigned int file_cache_max = 0;

/* AM 980919 */
static int cache_stat (const char *path, struct stat *statbuf)
{
	if( file_cache_max )
	{
		unsigned i;
		struct file_cache_entry *entry;

		/* search in the cache */
		for( i = 0; i < file_cache_max; ++i )
		{
			if( file_cache_map[i]->file && strcmp (file_cache_map[i]->file, path) == 0 )
			{	/* found */
				unsigned j;

//				LOG(("File cache HIT  for %s\n", path));
                /* store */
				entry = file_cache_map[i];

				/* shift */
				for( j = i; j > 0; --j )
					file_cache_map[j] = file_cache_map[j - 1];

				/* set the first entry */
				file_cache_map[0] = entry;

				if( entry->result == 0 )
					memcpy (statbuf, &entry->stat_buffer, sizeof (struct stat));

				return entry->result;
			}
		}
//		LOG(("File cache FAIL for %s\n", path));

		/* oldest entry */
		entry = file_cache_map[file_cache_max - 1];
		free (entry->file);

		/* shift */
		for( i = file_cache_max - 1; i > 0; --i )
			file_cache_map[i] = file_cache_map[i - 1];

		/* set the first entry */
		file_cache_map[0] = entry;

		/* file */
		entry->file = (char *) malloc (strlen (path) + 1);
		strcpy (entry->file, path);

		/* result and stat */
		entry->result = stat (path, &entry->stat_buffer);

		if( entry->result == 0 )
			memcpy (statbuf, &entry->stat_buffer, sizeof (struct stat));

		return entry->result;
	}
	else
	{
		return stat (path, statbuf);
	}
}

/* AM 980919 */
static void cache_allocate (unsigned entries)
{
	if( entries )
	{
		unsigned i;

		file_cache_max = entries;
		file_cache_map = (struct file_cache_entry **) malloc (file_cache_max * sizeof (struct file_cache_entry *));

		for( i = 0; i < file_cache_max; ++i )
		{
			file_cache_map[i] = (struct file_cache_entry *) malloc (sizeof (struct file_cache_entry));
			memset (file_cache_map[i], 0, sizeof (struct file_cache_entry));
		}
		LOG(("File cache allocated for %d entries\n", file_cache_max));
	}
}
#else

#define cache_stat(a,b) stat(a,b)

#endif

/* This function can be called several times with different parameters,
 * for example by "mame -verifyroms *". */
static int decompose_rompath(struct rc_option *option, const char *arg, int priority)
{
	char *token;

	/* start with zero path components */
	rompathc = 0;

	if (!roms)
		roms = malloc( strlen(arg) + 1);
	else
		roms = realloc( roms, strlen(arg) + 1);

	if( !roms )
	{
		logerror("decompose_rom_path: failed to malloc!\n");
		raise(SIGABRT);
	}

	strcpy (roms, arg);
	token = strtok (roms, ";");
	while( token )
	{
		if( rompathc )
			rompathv = realloc (rompathv, (rompathc + 1) * sizeof(char *));
		else
			rompathv = malloc (sizeof(char *));
		if( !rompathv )
			break;
		rompathv[rompathc++] = token;
		token = strtok (NULL, ";");
	}

#if FILE_CACHE
    /* AM 980919 */
    if( file_cache_max == 0 )
    {
        /* (rom path directories + 1 buffer)==rompathc+1 */
        /* (dir + .zip + .zif)==3 */
        /* (clone+parent)==2 */
        cache_allocate ((rompathc + 1) * 3 * 2);
    }
#endif

	option->priority = priority;
	return 0;
}

/* This function can be called several times with different parameters,
 * for example by "mame -verifysamples *". */
static int decompose_samplepath(struct rc_option *option, const char *arg, int priority)
{
	char *token;

	/* start with zero path components */
	samplepathc = 0;

	if (!samples)
		samples = malloc( strlen(arg) + 1);
	else
		samples = realloc( samples, strlen(arg) + 1);

	if( !samples )
	{
		logerror("decompose_sample_path: failed to malloc!\n");
		raise(SIGABRT);
	}

	strcpy (samples, arg);
	token = strtok (samples, ";");
	while( token )
	{
		if( samplepathc )
			samplepathv = realloc (samplepathv, (samplepathc + 1) * sizeof(char *));
		else
			samplepathv = malloc (sizeof(char *));
		if( !samplepathv )
			break;
		samplepathv[samplepathc++] = token;
		token = strtok (NULL, ";");
	}
	option->priority = priority;
	return 0;
}


/*
 * file handling routines
 *
 * gamename holds the driver name, filename is only used for ROMs and samples.
 * if 'write' is not 0, the file is opened for write. Otherwise it is opened
 * for read.
 */

/*
 * check if roms/samples for a game exist at all
 * return index+1 of the path vector component on success, otherwise 0
 */
int osd_faccess (const char *newfilename, int filetype)
{
	static int indx;
	static const char *filename;
    char name[256];
    char **pathv;
    int pathc;
	char *dir_name;

	/* if filename == NULL, continue the search */
	if( newfilename != NULL )
	{
		indx = 0;
		filename = newfilename;
	}
	else
		indx++;

#ifdef MESS
	if( filetype == OSD_FILETYPE_ROM ||
		filetype == OSD_FILETYPE_IMAGE_R ||
		filetype == OSD_FILETYPE_IMAGE_RW )
#else
	if( filetype == OSD_FILETYPE_ROM )
#endif
	{
		pathv = rompathv;
		pathc = rompathc;
	}
	else
	if( filetype == OSD_FILETYPE_SAMPLE )
	{
		pathv = samplepathv;
		pathc = samplepathc;
	}
	else
	if( filetype == OSD_FILETYPE_SCREENSHOT )
	{
		void *f;

		sprintf (name, "%s/%s.png", screenshotdir, newfilename);
		f = fopen (name, "rb");
		if( f )
		{
			fclose (f);
			return 1;
		}
		else
			return 0;
	}
#ifdef PINMAME_EXT
	else
	if( filetype == OSD_FILETYPE_WAVEFILE )
	{
		void *f;

		sprintf (name, "%s/%s.wav", wavedir, newfilename);
		f = fopen (name, "rb");
		if( f )
		{
			fclose (f);
			return 1;
		}
		else
			return 0;
	}
#endif /* PINMAME_EXT */
	else
		return 0;

	for( ; indx < pathc; indx++ )
	{
		struct stat stat_buffer;

		dir_name = pathv[indx];

		/* does such a directory (or file) exist? */
		sprintf (name, "%s/%s", dir_name, filename);
		if( cache_stat (name, &stat_buffer) == 0 )
			return indx + 1;

		/* try again with a .zip extension */
		sprintf (name, "%s/%s.zip", dir_name, filename);
		if( cache_stat (name, &stat_buffer) == 0 )
			return indx + 1;

		/* try again with a .zif extension */
		sprintf (name, "%s/%s.zif", dir_name, filename);
		if( cache_stat (name, &stat_buffer) == 0 )
			return indx + 1;
	}

	/* no match */
	return 0;
}

/* JB 980920 update */
/* AM 980919 update */
void *osd_fopen (const char *game, const char *filename, int filetype, int openforwrite)
{
	char name[256];
	char *gamename;
	int found = 0;
	int indx;
	struct stat stat_buffer;
	FakeFileHandle *f;
	int pathc;
	char **pathv;


	f = (FakeFileHandle *) malloc (sizeof (FakeFileHandle));
	if( !f )
	{
		logerror("osd_fopen: failed to malloc FakeFileHandle!\n");
        return 0;
	}
	memset (f, 0, sizeof (FakeFileHandle));

	gamename = (char *) game;

	/* Support "-romdir" yuck. */
	if( alternate_name )
	{
		/* check for DEFAULT.CFG file request */
		if( filetype == OSD_FILETYPE_CONFIG && gamename == "default" )
		{
			LOG(("osd_fopen: default input configuration file requested; -romdir switch not applied\n"));
		} else {
			LOG(("osd_fopen: -romdir overrides '%s' by '%s'\n", gamename, alternate_name));
        		gamename = alternate_name;
        	}
	}

	switch( filetype )
	{
	case OSD_FILETYPE_ROM:
	case OSD_FILETYPE_SAMPLE:

		/* only for reading */
		if( openforwrite )
		{
			logerror("osd_fopen: type %02x write not supported\n",filetype);
            break;
		}

		if( filetype == OSD_FILETYPE_SAMPLE )
		{
			LOG(("osd_fopen: using samplepath\n"));
            pathc = samplepathc;
            pathv = samplepathv;
        }
		else
		{
			LOG(("osd_fopen: using rompath\n"));
            pathc = rompathc;
            pathv = rompathv;
		}

		for( indx = 0; indx < pathc && !found; ++indx )
		{
			const char *dir_name = pathv[indx];

			if( !found )
			{
				sprintf (name, "%s/%s", dir_name, gamename);
				LOG(("Trying %s\n", name));
                if( cache_stat (name, &stat_buffer) == 0 && (stat_buffer.st_mode & S_IFDIR) )
				{
					sprintf (name, "%s/%s/%s", dir_name, gamename, filename);
					if( filetype == OSD_FILETYPE_ROM )
					{
						if( checksum_file (name, &f->data, &f->length, &f->crc) == 0 )
						{
							f->type = kRAMFile;
							f->offset = 0;
							found = 1;
						}
					}
					else
					{
						f->type = kPlainFile;
						f->file = fopen (name, "rb");
						found = f->file != 0;
					}
				}
			}

			if( !found )
			{
				/* try with a .zip extension */
				sprintf (name, "%s/%s.zip", dir_name, gamename);
				LOG(("Trying %s file\n", name));
                if( cache_stat (name, &stat_buffer) == 0 )
				{
					if( load_zipped_file (name, filename, &f->data, &f->length) == 0 )
					{
						LOG(("Using (osd_fopen) zip file for %s\n", filename));
						f->type = kZippedFile;
						f->offset = 0;
						f->crc = crc32 (0L, f->data, f->length);
						found = 1;
					}
				}
			}

			if( !found )
			{
				/* try with a .zip directory (if ZipMagic is installed) */
				sprintf (name, "%s/%s.zip", dir_name, gamename);
				LOG(("Trying %s directory\n", name));
                if( cache_stat (name, &stat_buffer) == 0 && (stat_buffer.st_mode & S_IFDIR) )
				{
					sprintf (name, "%s/%s.zip/%s", dir_name, gamename, filename);
					if( filetype == OSD_FILETYPE_ROM )
					{
						if( checksum_file (name, &f->data, &f->length, &f->crc) == 0 )
						{
							f->type = kRAMFile;
							f->offset = 0;
							found = 1;
						}
					}
					else
					{
						f->type = kPlainFile;
						f->file = fopen (name, "rb");
						found = f->file != 0;
					}
				}
			}
		}

		break;

#ifdef MESS
	case OSD_FILETYPE_IMAGE_R:

		/* only for reading */
		if( openforwrite )
		{
			logerror("osd_fopen: type %02x write not supported\n",filetype);
            break;
		}
        else
		{
			LOG(("osd_fopen: using rompath\n"));
            pathc = rompathc;
            pathv = rompathv;
		}

		LOG(("Open IMAGE_R '%s' for %s\n", filename, game));
        for( indx = 0; indx < pathc && !found; ++indx )
		{
			const char *dir_name = pathv[indx];

			/* this section allows exact path from .cfg */
			if( !found )
			{
				sprintf(name,"%s",dir_name);
				if( cache_stat(name,&stat_buffer) == 0 && (stat_buffer.st_mode & S_IFDIR) )
				{
					sprintf(name,"%s/%s",dir_name,filename);
					if( filetype == OSD_FILETYPE_ROM )
					{
						if( checksum_file (name, &f->data, &f->length, &f->crc) == 0 )
						{
							f->type = kRAMFile;
							f->offset = 0;
							found = 1;
						}
					}
					else
					{
						f->type = kPlainFile;
						f->file = fopen(name,"rb");
						found = f->file!=0;
					}
				}
			}

			if( !found )
			{
				sprintf (name, "%s/%s", dir_name, gamename);
				LOG(("Trying %s directory\n", name));
                if( cache_stat (name, &stat_buffer) == 0 && (stat_buffer.st_mode & S_IFDIR) )
				{
					sprintf (name, "%s/%s/%s", dir_name, gamename, filename);
					LOG(("Trying %s file\n", name));
                    if( filetype == OSD_FILETYPE_ROM )
					{
						if( checksum_file(name, &f->data, &f->length, &f->crc) == 0 )
						{
							f->type = kRAMFile;
							f->offset = 0;
							found = 1;
						}
					}
					else
					{
						f->type = kPlainFile;
						f->file = fopen (name, "rb");
						found = f->file != 0;
					}
				}
			}

			/* Zip cart support for MESS */
			if( !found && filetype == OSD_FILETYPE_IMAGE_R )
			{
				char *extension = strrchr (name, '.');    /* find extension */
				if( extension )
					strcpy (extension, ".zip");
				else
					strcat (name, ".zip");
				LOG(("Trying %s file\n", name));
				if( cache_stat(name, &stat_buffer) == 0 )
				{
					if( load_zipped_file(name, filename, &f->data, &f->length) == 0 )
					{
						LOG(("Using (osd_fopen) zip file for %s\n", filename));
						f->type = kZippedFile;
						f->offset = 0;
						f->crc = crc32 (0L, f->data, f->length);
						found = 1;
					}
				}
			}

			if( !found )
			{
				/* try with a .zip extension */
				sprintf (name, "%s/%s.zip", dir_name, gamename);
				LOG(("Trying %s file\n", name));
				if( cache_stat(name, &stat_buffer) == 0 )
				{
					if( load_zipped_file(name, filename, &f->data, &f->length) == 0 )
					{
						LOG(("Using (osd_fopen) zip file for %s\n", filename));
						f->type = kZippedFile;
						f->offset = 0;
						f->crc = crc32 (0L, f->data, f->length);
						found = 1;
					}
				}
			}
    	}
    break; /* end of IMAGE_R */

	case OSD_FILETYPE_IMAGE_RW:
		{
			static char *write_modes[] = {"rb","wb","r+b","r+b","w+b"};
            char file[256];
			char *extension;

			LOG(("Open IMAGE_RW '%s' for %s mode '%s'\n", filename, game, write_modes[openforwrite]));
			strcpy (file, filename);

			do
            {
			/* 29-05-00 Lee Ward: Reversed the search order. */
            for (indx=rompathc-1; indx>=0; --indx)
			{
				const char *dir_name = rompathv[indx];

					/* Exact path support */

					/* 29-05-00 Lee Ward: Changed the search order to prevent new files
					   being created in the application root as default */

					if( !found )
					{
						sprintf (name, "%s/%s", dir_name, gamename);
						LOG(("Trying %s directory\n", name));
						if( cache_stat(name, &stat_buffer) == 0 && (stat_buffer.st_mode & S_IFDIR) )
						{
							sprintf (name, "%s/%s/%s", dir_name, gamename, file);
							LOG(("Trying %s file\n", name));
                                                        f->file = fopen (name, write_modes[openforwrite]);
							found = f->file != 0;
							if( !found && openforwrite == 3 )
							{
								f->file = fopen(name, write_modes[4]);
								found = f->file != 0;
                                                         }
                                                 }
					}

					/* Steph - Zip disk images support for MESS */
					if( !found && !openforwrite )
					{
						extension = strrchr (name, '.');    /* find extension */
						/* add .zip for zipfile */
						if( extension )
							strcpy(extension, ".zip");
						else
							strcat(extension, ".zip");
						LOG(("Trying %s file\n", name));
						if( cache_stat(name, &stat_buffer) == 0 )
						{
							if( load_zipped_file(name, filename, &f->data, &f->length) == 0 )
							{
								LOG(("Using (osd_fopen) zip file for %s\n", filename));
								f->type = kZippedFile;
								f->offset = 0;
								f->crc = crc32(0L, f->data, f->length);
								found = 1;
							}
						}
					}

					if (!found)
					{
						sprintf(name, "%s", dir_name);
						LOG(("Trying %s directory\n", name));
						if( cache_stat(name,&stat_buffer) == 0 && (stat_buffer.st_mode & S_IFDIR) )
						{
							sprintf(name,"%s/%s", dir_name, file);
							LOG(("Trying %s file\n", name));
                                                        f->file = fopen(name, write_modes[openforwrite]);
							found = f->file != 0;
							if( !found && openforwrite == 3 )
							{
								f->file = fopen(name, write_modes[4]);
								found = f->file != 0;
                                                         }
						}
					}

                    if( !found && !openforwrite )
                    {
                        extension = strrchr (name, '.');    /* find extension */
                        /* add .zip for zipfile */
                        if( extension )
                            strcpy(extension, ".zip");
                        else
                            strcat(extension, ".zip");
						LOG(("Trying %s file\n", name));
						if( cache_stat(name, &stat_buffer) == 0 )
                        {
							if( load_zipped_file(name, filename, &f->data, &f->length) == 0 )
                            {
                                LOG(("Using (osd_fopen) zip file for %s\n", filename));
                                f->type = kZippedFile;
                                f->offset = 0;
								f->crc = crc32(0L, f->data, f->length);
                                found = 1;
                            }
                        }
                    }

					if( !found && !openforwrite )
					{
						/* try with a .zip extension */
						sprintf (name, "%s/%s.zip", dir_name, gamename);
						LOG(("Trying %s file\n", name));
						if( cache_stat (name, &stat_buffer) == 0 )
						{
							if( load_zipped_file (name, file, &f->data, &f->length) == 0 )
							{
								LOG(("Using (osd_fopen) zip file for %s\n", filename));
								f->type = kZippedFile;
								f->offset = 0;
								f->crc = crc32 (0L, f->data, f->length);
								found = 1;
							}
						}
					}

					if( !found )
					{
						/* try with a .zip directory (if ZipMagic is installed) */
						sprintf (name, "%s/%s.zip", dir_name, gamename);
						LOG(("Trying %s ZipMagic directory\n", name));
						if( cache_stat (name, &stat_buffer) == 0 && (stat_buffer.st_mode & S_IFDIR) )
						{
							sprintf (name, "%s/%s.zip/%s", dir_name, gamename, file);
							LOG(("Trying %s\n", name));
							f->file = fopen (name, write_modes[openforwrite]);
							found = f->file != 0;
							if( !found && openforwrite == 3 )
							{
								f->file = fopen(name, write_modes[4]);
								found = f->file != 0;
                            }
                        }
					}
					if( found )
						LOG(("IMAGE_RW %s FOUND in %s!\n", file, name));
				}

				extension = strrchr (file, '.');
				if( extension )
					*extension = '\0';
			} while( !found && extension );
		}
		break;
#endif	/* MESS */


	case OSD_FILETYPE_NVRAM:
		if( !found )
		{
			sprintf (name, "%s/%s.nv", nvdir, gamename);
			f->type = kPlainFile;
			f->file = fopen (name, openforwrite ? "wb" : "rb");
			found = f->file != 0;
		}

		if( !found )
		{
			/* try with a .zip directory (if ZipMagic is installed) */
			sprintf (name, "%s.zip/%s.nv", nvdir, gamename);
			f->type = kPlainFile;
			f->file = fopen (name, openforwrite ? "wb" : "rb");
			found = f->file != 0;
		}

		if( !found )
		{
			/* try with a .zif directory (if ZipFolders is installed) */
			sprintf (name, "%s.zif/%s.nv", nvdir, gamename);
			f->type = kPlainFile;
			f->file = fopen (name, openforwrite ? "wb" : "rb");
			found = f->file != 0;
		}
		break;

	case OSD_FILETYPE_HIGHSCORE:
		if( mame_highscore_enabled () )
		{
			if( !found )
			{
				sprintf (name, "%s/%s.hi", hidir, gamename);
				f->type = kPlainFile;
				f->file = fopen (name, openforwrite ? "wb" : "rb");
				found = f->file != 0;
			}

			if( !found )
			{
				/* try with a .zip directory (if ZipMagic is installed) */
				sprintf (name, "%s.zip/%s.hi", hidir, gamename);
				f->type = kPlainFile;
				f->file = fopen (name, openforwrite ? "wb" : "rb");
				found = f->file != 0;
			}

			if( !found )
			{
				/* try with a .zif directory (if ZipFolders is installed) */
				sprintf (name, "%s.zif/%s.hi", hidir, gamename);
				f->type = kPlainFile;
				f->file = fopen (name, openforwrite ? "wb" : "rb");
				found = f->file != 0;
			}
		}
		break;

    case OSD_FILETYPE_CONFIG:
		sprintf (name, "%s/%s.cfg", cfgdir, gamename);
		f->type = kPlainFile;
		f->file = fopen (name, openforwrite ? "wb" : "rb");
		found = f->file != 0;

		if( !found )
		{
			/* try with a .zip directory (if ZipMagic is installed) */
			sprintf (name, "%s.zip/%s.cfg", cfgdir, gamename);
			f->type = kPlainFile;
			f->file = fopen (name, openforwrite ? "wb" : "rb");
			found = f->file != 0;
		}

		if( !found )
		{
			/* try with a .zif directory (if ZipFolders is installed) */
			sprintf (name, "%s.zif/%s.cfg", cfgdir, gamename);
			f->type = kPlainFile;
			f->file = fopen (name, openforwrite ? "wb" : "rb");
			found = f->file != 0;
		}
		break;

	case OSD_FILETYPE_INPUTLOG:
		sprintf (name, "%s/%s.inp", inpdir, gamename);
		f->type = kPlainFile;
		f->file = fopen (name, openforwrite ? "wb" : "rb");
		found = f->file != 0;

        if( !found )
		{
			/* try with a .zip directory (if ZipMagic is installed) */
			sprintf (name, "%s.zip/%s.cfg", inpdir, gamename);
			f->type = kPlainFile;
			f->file = fopen (name, openforwrite ? "wb" : "rb");
			found = f->file != 0;
		}

		if( !found )
		{
			/* try with a .zif directory (if ZipFolders is installed) */
			sprintf (name, "%s.zif/%s.cfg", inpdir, gamename);
			f->type = kPlainFile;
			f->file = fopen (name, openforwrite ? "wb" : "rb");
			found = f->file != 0;
        }

		if( !openforwrite )
		{
			char file[256];
			sprintf (file, "%s.inp", gamename);
            sprintf (name, "%s/%s.zip", inpdir, gamename);
			LOG(("Trying %s in %s\n", file, name));
            if( cache_stat (name, &stat_buffer) == 0 )
			{
				if( load_zipped_file (name, file, &f->data, &f->length) == 0 )
				{
					LOG(("Using (osd_fopen) zip file %s for %s\n", name, file));
					f->type = kZippedFile;
					f->offset = 0;
					found = 1;
				}
			}
		}

        break;

	case OSD_FILETYPE_STATE:
		sprintf (name, "%s/%s.sta", stadir, gamename);
		f->file = fopen (name, openforwrite ? "wb" : "rb");
		found = !(f->file == 0);
		if( !found )
		{
			/* try with a .zip directory (if ZipMagic is installed) */
			sprintf (name, "%s.zip/%s.sta", stadir, gamename);
			f->file = fopen (name, openforwrite ? "wb" : "rb");
			found = !(f->file == 0);
		}
		if( !found )
		{
			/* try with a .zif directory (if ZipFolders is installed) */
			sprintf (name, "%s.zif/%s.sta", stadir, gamename);
			f->file = fopen (name, openforwrite ? "wb" : "rb");
			found = !(f->file == 0);
		}
		break;

	case OSD_FILETYPE_ARTWORK:
		/* only for reading */
		if( openforwrite )
		{
			logerror("osd_fopen: type %02x write not supported\n",filetype);
            break;
		}
		sprintf (name, "%s/%s", artworkdir, filename);
		f->type = kPlainFile;
		f->file = fopen (name, openforwrite ? "wb" : "rb");
		found = f->file != 0;
		if( !found )
		{
			/* try with a .zip directory (if ZipMagic is installed) */
			sprintf (name, "%s.zip/%s.png", artworkdir, filename);
			f->type = kPlainFile;
			f->file = fopen (name, openforwrite ? "wb" : "rb");
			found = f->file != 0;
		}

		if( !found )
		{
			/* try with a .zif directory (if ZipFolders is installed) */
			sprintf (name, "%s.zif/%s.png", artworkdir, filename);
			f->type = kPlainFile;
			f->file = fopen (name, openforwrite ? "wb" : "rb");
			found = f->file != 0;
        }

		if( !found )
		{
			char file[256], *extension;
			sprintf(file, "%s", filename);
            sprintf(name, "%s/%s", artworkdir, filename);
            extension = strrchr(name, '.');
			if( extension )
				strcpy (extension, ".zip");
			else
				strcat (name, ".zip");
			LOG(("Trying %s in %s\n", file, name));
            if( cache_stat (name, &stat_buffer) == 0 )
			{
				if( load_zipped_file (name, file, &f->data, &f->length) == 0 )
				{
					LOG(("Using (osd_fopen) zip file %s\n", name));
					f->type = kZippedFile;
					f->offset = 0;
					found = 1;
				}
			}
			if( !found )
			{
				sprintf(name, "%s/%s.zip", artworkdir, game);
				LOG(("Trying %s in %s\n", file, name));
				if( cache_stat (name, &stat_buffer) == 0 )
				{
					if( load_zipped_file (name, file, &f->data, &f->length) == 0 )
					{
						LOG(("Using (osd_fopen) zip file %s\n", name));
						f->type = kZippedFile;
						f->offset = 0;
						found = 1;
					}
				}
            }
        }
        break;

	case OSD_FILETYPE_MEMCARD:
		sprintf (name, "%s/%s", memcarddir, filename);
		f->type = kPlainFile;
		f->file = fopen (name, openforwrite ? "wb" : "rb");
		found = f->file != 0;
		break;

	case OSD_FILETYPE_SCREENSHOT:
		/* only for writing */
		if( !openforwrite )
		{
			logerror("osd_fopen: type %02x read not supported\n",filetype);
			break;
		}

		sprintf (name, "%s/%s.png", screenshotdir, filename);
		f->type = kPlainFile;
		f->file = fopen (name, openforwrite ? "wb" : "rb");
		found = f->file != 0;
		break;
#ifdef PINMAME_EXT
	case OSD_FILETYPE_WAVEFILE:
		/* only for writing */
		if( !openforwrite )
		{
			logerror("osd_fopen: type %02x read not supported\n",filetype);
			break;
		}

		sprintf (name, "%s/%s.wav", wavedir, filename);
		f->type = kPlainFile;
		f->file = fopen (name, openforwrite ? "wb" : "rb");
		found = f->file != 0;
		break;
#endif /* PINMAME_EXT */
	case OSD_FILETYPE_HIGHSCORE_DB:
		/* only for reading */
		if( openforwrite )
		{
			logerror("osd_fopen: type %02x write not supported\n",filetype);
            break;
		}
		f->type = kPlainFile;
		/* open as ASCII files, not binary like the others */
		f->file = fopen (filename, openforwrite ? "w" : "r");
		found = f->file != 0;
        break;

	case OSD_FILETYPE_HISTORY:
		/* only for reading */
		if( openforwrite )
		{
			logerror("osd_fopen: type %02x write not supported\n",filetype);
            break;
		}
		f->type = kPlainFile;
		/* open as _binary_ like the others */
		f->file = fopen (filename, openforwrite ? "wb" : "rb");
		found = f->file != 0;
        break;


	/* Steph */
	case OSD_FILETYPE_CHEAT:
		sprintf (name, "%s/%s", cheatdir, filename);
		f->type = kPlainFile;
		/* open as ASCII files, not binary like the others */
		f->file = fopen (filename, openforwrite ? "a" : "r");
		found = f->file != 0;
        break;

	case OSD_FILETYPE_LANGUAGE:
		/* only for reading */
		if( openforwrite )
		{
			logerror("osd_fopen: type %02x write not supported\n",filetype);
            break;
		}
		sprintf (name, "%s.lng", filename);
		f->type = kPlainFile;
		/* open as ASCII files, not binary like the others */
		f->file = fopen (name, openforwrite ? "w" : "r");
		found = f->file != 0;
logerror("fopen %s = %08x\n",name,(int)f->file);
        break;

	default:
		logerror("osd_fopen(): unknown filetype %02x\n",filetype);
	}

	if( !found )
	{
		free (f);
		return 0;
	}

	return f;
}

/* JB 980920 update */
int osd_fread (void *file, void *buffer, int length)
{
	FakeFileHandle *f = (FakeFileHandle *) file;

	switch( f->type )
	{
	case kPlainFile:
		return fread (buffer, 1, length, f->file);
		break;
	case kZippedFile:
	case kRAMFile:
		/* reading from the RAM image of a file */
		if( f->data )
		{
			if( length + f->offset > f->length )
				length = f->length - f->offset;
			memcpy (buffer, f->offset + f->data, length);
			f->offset += length;
			return length;
		}
		break;
	}

	return 0;
}

int osd_fread_swap (void *file, void *buffer, int length)
{
	int i;
	unsigned char *buf;
	unsigned char temp;
	int res;


	res = osd_fread (file, buffer, length);

	buf = buffer;
	for( i = 0; i < length; i += 2 )
	{
		temp = buf[i];
		buf[i] = buf[i + 1];
		buf[i + 1] = temp;
	}

	return res;
}


/* AM 980919 update */
int osd_fwrite (void *file, const void *buffer, int length)
{
	FakeFileHandle *f = (FakeFileHandle *) file;

	switch( f->type )
	{
	case kPlainFile:
		return fwrite (buffer, 1, length, ((FakeFileHandle *) file)->file);
	default:
		return 0;
	}
}

int osd_fwrite_swap (void *file, const void *buffer, int length)
{
	int i;
	unsigned char *buf;
	unsigned char temp;
	int res;


	buf = (unsigned char *) buffer;
	for( i = 0; i < length; i += 2 )
	{
		temp = buf[i];
		buf[i] = buf[i + 1];
		buf[i + 1] = temp;
	}

	res = osd_fwrite (file, buffer, length);

	for( i = 0; i < length; i += 2 )
	{
		temp = buf[i];
		buf[i] = buf[i + 1];
		buf[i + 1] = temp;
	}

	return res;
}

int osd_fread_scatter (void *file, void *buffer, int length, int increment)
{
	unsigned char *buf = buffer;
	FakeFileHandle *f = (FakeFileHandle *) file;
	unsigned char tempbuf[4096];
	int totread, r, i;

	switch( f->type )
	{
	case kPlainFile:
		totread = 0;
		while (length)
		{
			r = length;
			if( r > 4096 )
				r = 4096;
			r = fread (tempbuf, 1, r, f->file);
			if( r == 0 )
				return totread;		   /* error */
			for( i = 0; i < r; i++ )
			{
				*buf = tempbuf[i];
				buf += increment;
			}
			totread += r;
			length -= r;
		}
		return totread;
		break;
	case kZippedFile:
	case kRAMFile:
		/* reading from the RAM image of a file */
		if( f->data )
		{
			if( length + f->offset > f->length )
				length = f->length - f->offset;
			for( i = 0; i < length; i++ )
			{
				*buf = f->data[f->offset + i];
				buf += increment;
			}
			f->offset += length;
			return length;
		}
		break;
	}

	return 0;
}


/* JB 980920 update */
int osd_fseek (void *file, int offset, int whence)
{
	FakeFileHandle *f = (FakeFileHandle *) file;
	int err = 0;

	switch( f->type )
	{
	case kPlainFile:
		return fseek (f->file, offset, whence);
		break;
	case kZippedFile:
	case kRAMFile:
		/* seeking within the RAM image of a file */
		switch( whence )
		{
		case SEEK_SET:
			f->offset = offset;
			break;
		case SEEK_CUR:
			f->offset += offset;
			break;
		case SEEK_END:
			f->offset = f->length + offset;
			break;
		}
		break;
	}

	return err;
}

/* JB 980920 update */
void osd_fclose (void *file)
{
	FakeFileHandle *f = (FakeFileHandle *) file;

	switch( f->type )
	{
	case kPlainFile:
		fclose (f->file);
		break;
	case kZippedFile:
	case kRAMFile:
		if( f->data )
			free (f->data);
		break;
	}
	free (f);
}

/* JB 980920 update */
/* AM 980919 */
static int checksum_file (const char *file, unsigned char **p, unsigned int *size, unsigned int *crc)
{
	int length;
	unsigned char *data;
	FILE *f;

	f = fopen (file, "rb");
	if( !f )
		return -1;

	/* determine length of file */
	if( fseek (f, 0L, SEEK_END) != 0 )
	{
		fclose (f);
		return -1;
	}

	length = ftell (f);
	if( length == -1L )
	{
		fclose (f);
		return -1;
	}

	/* allocate space for entire file */
	data = (unsigned char *) malloc (length);
	if( !data )
	{
		fclose (f);
		return -1;
	}

	/* read entire file into memory */
	if( fseek (f, 0L, SEEK_SET) != 0 )
	{
		free (data);
		fclose (f);
		return -1;
	}

	if( fread (data, sizeof (unsigned char), length, f) != length )
	{
		free (data);
		fclose (f);
		return -1;
	}

	*size = length;
	*crc = crc32 (0L, data, length);
	if( p )
		*p = data;
	else
		free (data);

	fclose (f);

	return 0;
}

/* JB 980920 updated */
/* AM 980919 updated */
int osd_fchecksum (const char *game, const char *filename, unsigned int *length, unsigned int *sum)
{
	char name[256];
	int indx;
	struct stat stat_buffer;
	int found = 0;
	const char *gamename = game;

	/* Support "-romdir" yuck. */
	if( alternate_name )
		gamename = alternate_name;

	for( indx = 0; indx < rompathc && !found; ++indx )
	{
		const char *dir_name = rompathv[indx];

		if( !found )
		{
			sprintf (name, "%s/%s", dir_name, gamename);
			if( cache_stat (name, &stat_buffer) == 0 && (stat_buffer.st_mode & S_IFDIR) )
			{
				sprintf (name, "%s/%s/%s", dir_name, gamename, filename);
				if( checksum_file (name, 0, length, sum) == 0 )
                {
					found = 1;
                }
			}
		}

		if( !found )
		{
			/* try with a .zip extension */
			sprintf (name, "%s/%s.zip", dir_name, gamename);
			if( cache_stat (name, &stat_buffer) == 0 )
			{
				if( checksum_zipped_file (name, filename, length, sum) == 0 )
				{
					LOG(("Using (osd_fchecksum) zip file for %s\n", filename));
					found = 1;
				}
			}
		}

		if( !found )
		{
			/* try with a .zif directory (if ZipFolders is installed) */
			sprintf (name, "%s/%s.zif", dir_name, gamename);
			if( cache_stat (name, &stat_buffer) == 0 )
			{
				sprintf (name, "%s/%s.zif/%s", dir_name, gamename, filename);
				if( checksum_file (name, 0, length, sum) == 0 )
				{
					found = 1;
				}
			}
		}
	}

	if( !found )
		return -1;

	return 0;
}

/* JB 980920 */
int osd_fsize (void *file)
{
	FakeFileHandle *f = (FakeFileHandle *) file;

	if( f->type == kRAMFile || f->type == kZippedFile )
		return f->length;

	if( f->file )
	{
		int size, offs;
		offs = ftell( f->file );
		fseek( f->file, 0, SEEK_END );
		size = ftell( f->file );
		fseek( f->file, offs, SEEK_SET );
		return size;
	}

    return 0;
}

/* JB 980920 */
unsigned int osd_fcrc (void *file)
{
	FakeFileHandle *f = (FakeFileHandle *) file;

	return f->crc;
}

int osd_fgetc(void *file)
{
	FakeFileHandle *f = (FakeFileHandle *) file;

	if (f->type == kPlainFile && f->file)
		return fgetc(f->file);
	else
		return EOF;
}

int osd_ungetc(int c, void *file)
{
	FakeFileHandle *f = (FakeFileHandle *) file;

	if (f->type == kPlainFile && f->file)
		return ungetc(c,f->file);
	else
		return EOF;
}

char *osd_fgets(char *s, int n, void *file)
{
	FakeFileHandle *f = (FakeFileHandle *) file;

	if (f->type == kPlainFile && f->file)
		return fgets(s,n,f->file);
	else
		return NULL;
}

int osd_feof(void *file)
{
	FakeFileHandle *f = (FakeFileHandle *) file;

	if (f->type == kPlainFile && f->file)
		return feof(f->file);
	else
		return 1;
}

int osd_ftell(void *file)
{
	FakeFileHandle *f = (FakeFileHandle *) file;

	if (f->type == kPlainFile && f->file)
		return ftell(f->file);
	else
		return -1L;
}


/* called while loading ROMs. It is called a last time with name == 0 to signal */
/* that the ROM loading process is finished. */
/* return non-zero to abort loading */
int osd_display_loading_rom_message (const char *name, int current, int total)
{
	if( name )
		fprintf (stdout, "loading %-12s\r", name);
	else
		fprintf (stdout, "                    \r");
	fflush (stdout);

//	if( keyboard_pressed (KEYCODE_LCONTROL) && keyboard_pressed (KEYCODE_C) )
//		return 1;

	return 0;
}
