mame/src/emu/fileio.c
2008-05-15 08:56:29 +00:00

855 lines
23 KiB
C

/***************************************************************************
fileio.c
File access functions.
Copyright Nicola Salmoria and the MAME Team.
Visit http://mamedev.org for licensing and usage restrictions.
***************************************************************************/
#include "osdepend.h"
#include "corefile.h"
#include "astring.h"
#include "driver.h"
#include "chd.h"
#include "hash.h"
#include "unzip.h"
#include "options.h"
/***************************************************************************
CONSTANTS
***************************************************************************/
#define PLAIN_FILE 0
#define ZIPPED_FILE 1
#define OPEN_FLAG_HAS_CRC 0x10000
#ifdef MAME_DEBUG
#define DEBUG_COOKIE 0xbaadf00d
#endif
/***************************************************************************
TYPE DEFINITIONS
***************************************************************************/
/* typedef struct _mame_file mame_file -- declared in fileio.h */
struct _mame_file
{
#ifdef DEBUG_COOKIE
UINT32 debug_cookie; /* sanity checking for debugging */
#endif
astring * filename; /* full filename */
core_file * file; /* core file pointer */
UINT32 openflags; /* flags we used for the open */
char hash[HASH_BUF_SIZE]; /* hash data for the file */
zip_file * zipfile; /* ZIP file pointer */
UINT8 * zipdata; /* ZIP file data */
UINT64 ziplength; /* ZIP file length */
};
typedef struct _path_iterator path_iterator;
struct _path_iterator
{
const char * base;
const char * cur;
int index;
};
/* typedef struct _mame_path mame_path -- declared in fileio.h */
struct _mame_path
{
path_iterator iterator;
osd_directory * curdir;
astring * pathbuffer;
int buflen;
};
/***************************************************************************
FUNCTION PROTOTYPES
***************************************************************************/
/* core functions */
static void fileio_exit(running_machine *machine);
/* file open/close */
static file_error fopen_internal(core_options *opts, const char *searchpath, const char *filename, UINT32 crc, UINT32 flags, mame_file **file);
static file_error fopen_attempt_zipped(astring *fullname, UINT32 crc, UINT32 openflags, mame_file *file);
/* path iteration */
static void path_iterator_init(path_iterator *iterator, core_options *opts, const char *searchpath);
static int path_iterator_get_next(path_iterator *iterator, astring *buffer);
/* misc helpers */
static file_error load_zipped_file(mame_file *file);
static int zip_filename_match(const zip_file_header *header, const astring *afilename);
/***************************************************************************
CORE FUNCTIONS
***************************************************************************/
/*-------------------------------------------------
fileio_init - initialize the internal file
I/O system; note that the OS layer is free to
call mame_fopen before fileio_init
-------------------------------------------------*/
void fileio_init(running_machine *machine)
{
add_exit_callback(machine, fileio_exit);
}
/*-------------------------------------------------
fileio_exit - clean up behind ourselves
-------------------------------------------------*/
static void fileio_exit(running_machine *machine)
{
zip_file_cache_clear();
}
/***************************************************************************
FILE OPEN/CLOSE
***************************************************************************/
/*-------------------------------------------------
mame_fopen - open a file for access and
return an error code
-------------------------------------------------*/
file_error mame_fopen(const char *searchpath, const char *filename, UINT32 openflags, mame_file **file)
{
return fopen_internal(mame_options(), searchpath, filename, 0, openflags, file);
}
/*-------------------------------------------------
mame_fopen_crc - open a file by name or CRC
and return an error code
-------------------------------------------------*/
file_error mame_fopen_crc(const char *searchpath, const char *filename, UINT32 crc, UINT32 openflags, mame_file **file)
{
return mame_fopen_crc_options(mame_options(), searchpath, filename, crc, openflags, file);
}
/*-------------------------------------------------
mame_fopen_options - open a file for access and
return an error code
-------------------------------------------------*/
file_error mame_fopen_options(core_options *opts, const char *searchpath, const char *filename, UINT32 openflags, mame_file **file)
{
return fopen_internal(opts, searchpath, filename, 0, openflags, file);
}
/*-------------------------------------------------
mame_fopen_crc_options - open a file by name
or CRC and return an error code
-------------------------------------------------*/
file_error mame_fopen_crc_options(core_options *opts, const char *searchpath, const char *filename, UINT32 crc, UINT32 openflags, mame_file **file)
{
return fopen_internal(opts, searchpath, filename, crc, openflags | OPEN_FLAG_HAS_CRC, file);
}
/*-------------------------------------------------
mame_fopen_ram - open a "file" which is
actually just an array of data in RAM
-------------------------------------------------*/
file_error mame_fopen_ram(const void *data, UINT32 length, UINT32 openflags, mame_file **file)
{
file_error filerr;
/* allocate the file itself */
*file = malloc(sizeof(**file));
if (*file == NULL)
return FILERR_OUT_OF_MEMORY;
/* reset the file handle */
memset(*file, 0, sizeof(**file));
(*file)->openflags = openflags;
#ifdef DEBUG_COOKIE
(*file)->debug_cookie = DEBUG_COOKIE;
#endif
/* attempt to open the file directly */
filerr = core_fopen_ram(data, length, openflags, &(*file)->file);
if (filerr == FILERR_NONE)
goto error;
/* handle errors and return */
error:
if (filerr != FILERR_NONE)
{
mame_fclose(*file);
*file = NULL;
}
return filerr;
}
/*-------------------------------------------------
fopen_internal - open a file
-------------------------------------------------*/
static file_error fopen_internal(core_options *opts, const char *searchpath, const char *filename, UINT32 crc, UINT32 openflags, mame_file **file)
{
file_error filerr = FILERR_NOT_FOUND;
path_iterator iterator;
/* can either have a hash or open for write, but not both */
if ((openflags & OPEN_FLAG_HAS_CRC) && (openflags & OPEN_FLAG_WRITE))
return FILERR_INVALID_ACCESS;
/* allocate the file itself */
*file = malloc(sizeof(**file));
if (*file == NULL)
return FILERR_OUT_OF_MEMORY;
/* reset the file handle */
memset(*file, 0, sizeof(**file));
(*file)->openflags = openflags;
#ifdef DEBUG_COOKIE
(*file)->debug_cookie = DEBUG_COOKIE;
#endif
/* if the path is absolute, null out the search path */
if (searchpath != NULL && osd_is_absolute_path(searchpath))
searchpath = NULL;
/* determine the maximum length of a composed filename, plus some extra space for .zip extensions */
path_iterator_init(&iterator, opts, searchpath);
/* loop over paths */
(*file)->filename = astring_alloc();
while (path_iterator_get_next(&iterator, (*file)->filename))
{
/* compute the full pathname */
if (astring_len((*file)->filename) > 0)
astring_catc((*file)->filename, PATH_SEPARATOR);
astring_catc((*file)->filename, filename);
/* attempt to open the file directly */
filerr = core_fopen(astring_c((*file)->filename), openflags, &(*file)->file);
if (filerr == FILERR_NONE)
break;
/* if we're opening for read-only we have other options */
if ((openflags & (OPEN_FLAG_READ | OPEN_FLAG_WRITE)) == OPEN_FLAG_READ)
{
filerr = fopen_attempt_zipped((*file)->filename, crc, openflags, *file);
if (filerr == FILERR_NONE)
break;
}
}
/* handle errors and return */
if (filerr != FILERR_NONE)
{
mame_fclose(*file);
*file = NULL;
}
return filerr;
}
/*-------------------------------------------------
fopen_attempt_zipped - attempt to open a
ZIPped file
-------------------------------------------------*/
static file_error fopen_attempt_zipped(astring *fullname, UINT32 crc, UINT32 openflags, mame_file *file)
{
astring *filename = astring_alloc();
zip_error ziperr;
zip_file *zip;
/* loop over directory parts up to the start of filename */
while (1)
{
const zip_file_header *header;
int dirsep;
/* find the final path separator */
dirsep = astring_rchr(fullname, 0, PATH_SEPARATOR[0]);
if (dirsep == -1)
{
astring_free(filename);
return FILERR_NOT_FOUND;
}
/* insert the part from the right of the separator into the head of the filename */
if (astring_len(filename) > 0)
astring_insc(filename, 0, "/");
astring_inssubstr(filename, 0, fullname, dirsep + 1, -1);
/* remove this part of the filename and append a .zip extension */
astring_substr(fullname, 0, dirsep);
astring_catc(fullname, ".zip");
/* attempt to open the ZIP file */
ziperr = zip_file_open(astring_c(fullname), &zip);
/* chop the .zip back off the filename before continuing */
astring_substr(fullname, 0, dirsep);
/* if we failed to open this file, continue scanning */
if (ziperr != ZIPERR_NONE)
continue;
/* see if we can find a file with the right name and (if available) crc */
for (header = zip_file_first_file(zip); header != NULL; header = zip_file_next_file(zip))
if (zip_filename_match(header, filename) && (!(openflags & OPEN_FLAG_HAS_CRC) || header->crc == crc))
break;
/* if that failed, look for a file with the right crc, but the wrong filename */
if (header == NULL && (openflags & OPEN_FLAG_HAS_CRC))
for (header = zip_file_first_file(zip); header != NULL; header = zip_file_next_file(zip))
if (header->crc == crc)
break;
/* if that failed, look for a file with the right name; reporting a bad checksum */
/* is more helpful and less confusing than reporting "rom not found" */
if (header == NULL)
for (header = zip_file_first_file(zip); header != NULL; header = zip_file_next_file(zip))
if (zip_filename_match(header, filename))
break;
/* if we got it, read the data */
if (header != NULL)
{
UINT8 crcs[4];
file->zipfile = zip;
file->ziplength = header->uncompressed_length;
/* build a hash with just the CRC */
hash_data_clear(file->hash);
crcs[0] = header->crc >> 24;
crcs[1] = header->crc >> 16;
crcs[2] = header->crc >> 8;
crcs[3] = header->crc >> 0;
hash_data_insert_binary_checksum(file->hash, HASH_CRC, crcs);
astring_free(filename);
return FILERR_NONE;
}
/* close up the ZIP file and try the next level */
zip_file_close(zip);
}
}
/*-------------------------------------------------
mame_fclose - closes a file
-------------------------------------------------*/
void mame_fclose(mame_file *file)
{
#ifdef DEBUG_COOKIE
assert(file->debug_cookie == DEBUG_COOKIE);
file->debug_cookie = 0;
#endif
/* close files and free memory */
if (file->zipfile != NULL)
zip_file_close(file->zipfile);
if (file->file != NULL)
core_fclose(file->file);
if (file->zipdata != NULL)
free(file->zipdata);
if (file->filename != NULL)
astring_free(file->filename);
free(file);
}
/***************************************************************************
FILE POSITIONING
***************************************************************************/
/*-------------------------------------------------
mame_fseek - seek within a file
-------------------------------------------------*/
int mame_fseek(mame_file *file, INT64 offset, int whence)
{
/* load the ZIP file now if we haven't yet */
if (file->zipfile != NULL)
load_zipped_file(file);
/* seek if we can */
if (file->file != NULL)
return core_fseek(file->file, offset, whence);
return 1;
}
/*-------------------------------------------------
mame_ftell - return the current file position
-------------------------------------------------*/
UINT64 mame_ftell(mame_file *file)
{
/* load the ZIP file now if we haven't yet */
if (file->zipfile != NULL)
load_zipped_file(file);
/* tell if we can */
if (file->file != NULL)
return core_ftell(file->file);
return 0;
}
/*-------------------------------------------------
mame_feof - return 1 if we're at the end
of file
-------------------------------------------------*/
int mame_feof(mame_file *file)
{
/* load the ZIP file now if we haven't yet */
if (file->zipfile != NULL)
load_zipped_file(file);
/* return EOF if we can */
if (file->file != NULL)
return core_feof(file->file);
return 0;
}
/*-------------------------------------------------
mame_fsize - returns the size of a file
-------------------------------------------------*/
UINT64 mame_fsize(mame_file *file)
{
/* use the ZIP length if present */
if (file->zipfile != NULL)
return file->ziplength;
/* return length if we can */
if (file->file != NULL)
return core_fsize(file->file);
return 0;
}
/***************************************************************************
FILE READ
***************************************************************************/
/*-------------------------------------------------
mame_fread - read from a file
-------------------------------------------------*/
UINT32 mame_fread(mame_file *file, void *buffer, UINT32 length)
{
/* load the ZIP file now if we haven't yet */
if (file->zipfile != NULL)
load_zipped_file(file);
/* read the data if we can */
if (file->file != NULL)
return core_fread(file->file, buffer, length);
return 0;
}
/*-------------------------------------------------
mame_fgetc - read a character from a file
-------------------------------------------------*/
int mame_fgetc(mame_file *file)
{
/* load the ZIP file now if we haven't yet */
if (file->zipfile != NULL)
load_zipped_file(file);
/* read the data if we can */
if (file->file != NULL)
return core_fgetc(file->file);
return EOF;
}
/*-------------------------------------------------
mame_ungetc - put back a character read from
a file
-------------------------------------------------*/
int mame_ungetc(int c, mame_file *file)
{
/* load the ZIP file now if we haven't yet */
if (file->zipfile != NULL)
load_zipped_file(file);
/* read the data if we can */
if (file->file != NULL)
return core_ungetc(c, file->file);
return 1;
}
/*-------------------------------------------------
mame_fgets - read a line from a text file
-------------------------------------------------*/
char *mame_fgets(char *s, int n, mame_file *file)
{
/* load the ZIP file now if we haven't yet */
if (file->zipfile != NULL)
load_zipped_file(file);
/* read the data if we can */
if (file->file != NULL)
return core_fgets(s, n, file->file);
return NULL;
}
/***************************************************************************
FILE WRITE
***************************************************************************/
/*-------------------------------------------------
mame_fwrite - write to a file
-------------------------------------------------*/
UINT32 mame_fwrite(mame_file *file, const void *buffer, UINT32 length)
{
/* write the data if we can */
if (file->file != NULL)
return core_fwrite(file->file, buffer, length);
return 0;
}
/*-------------------------------------------------
mame_fputs - write a line to a text file
-------------------------------------------------*/
int mame_fputs(mame_file *file, const char *s)
{
/* write the data if we can */
if (file->file != NULL)
return core_fputs(file->file, s);
return 0;
}
/*-------------------------------------------------
mame_vfprintf - vfprintf to a text file
-------------------------------------------------*/
static int mame_vfprintf(mame_file *file, const char *fmt, va_list va)
{
/* write the data if we can */
return (file->file != NULL) ? core_vfprintf(file->file, fmt, va) : 0;
}
/*-------------------------------------------------
mame_fprintf - vfprintf to a text file
-------------------------------------------------*/
int CLIB_DECL mame_fprintf(mame_file *file, const char *fmt, ...)
{
int rc;
va_list va;
va_start(va, fmt);
rc = mame_vfprintf(file, fmt, va);
va_end(va);
return rc;
}
/***************************************************************************
FILE ENUMERATION
***************************************************************************/
/*-------------------------------------------------
mame_openpath - open a search path (multiple
directories) for iteration
-------------------------------------------------*/
mame_path *mame_openpath(core_options *opts, const char *searchpath)
{
mame_path *path;
/* allocate a new mame_path */
path = malloc(sizeof(*path));
if (path == NULL)
return NULL;
memset(path, 0, sizeof(*path));
/* initialize the iterator */
path_iterator_init(&path->iterator, opts, searchpath);
/* allocate a buffer to hold the current path */
path->pathbuffer = astring_alloc();
return path;
}
/*-------------------------------------------------
mame_readpath - return information about the
next file in the search path
-------------------------------------------------*/
const osd_directory_entry *mame_readpath(mame_path *path)
{
const osd_directory_entry *result;
/* loop over potentially empty directories */
while (1)
{
/* if no open directory, get the next path */
while (path->curdir == NULL)
{
/* if we fail to get anything more, we're done */
if (!path_iterator_get_next(&path->iterator, path->pathbuffer))
return NULL;
/* open the path */
path->curdir = osd_opendir(astring_c(path->pathbuffer));
}
/* get the next entry from the current directory */
result = osd_readdir(path->curdir);
if (result != NULL)
return result;
/* we're done; close this directory */
osd_closedir(path->curdir);
path->curdir = NULL;
}
}
/*-------------------------------------------------
mame_closepath - close an open seach path
-------------------------------------------------*/
void mame_closepath(mame_path *path)
{
/* close anything open */
if (path->curdir != NULL)
osd_closedir(path->curdir);
/* free memory */
if (path->pathbuffer != NULL)
astring_free(path->pathbuffer);
free(path);
}
/***************************************************************************
MISCELLANEOUS
***************************************************************************/
/*-------------------------------------------------
mame_core_file - return the core_file
underneath the mame_file
-------------------------------------------------*/
core_file *mame_core_file(mame_file *file)
{
/* load the ZIP file now if we haven't yet */
if (file->zipfile != NULL)
load_zipped_file(file);
/* return the core file */
return file->file;
}
/*-------------------------------------------------
mame_file_full_name - return the full filename
for a given mame_file
-------------------------------------------------*/
const char *mame_file_full_name(mame_file *file)
{
/* return a pointer to the string if we have one; otherwise, return NULL */
if (file->filename != NULL)
return astring_c(file->filename);
return NULL;
}
/*-------------------------------------------------
mame_fhash - returns the hash for a file
-------------------------------------------------*/
const char *mame_fhash(mame_file *file, UINT32 functions)
{
const UINT8 *filedata;
UINT32 wehave;
/* if we already have the functions we need, just return */
wehave = hash_data_used_functions(file->hash);
if ((wehave & functions) == functions)
return file->hash;
/* load the ZIP file now if we haven't yet */
if (file->zipfile != NULL)
load_zipped_file(file);
if (file->file == NULL)
return file->hash;
/* read the data if we can */
filedata = core_fbuffer(file->file);
if (filedata == NULL)
return file->hash;
/* compute the hash */
hash_compute(file->hash, filedata, core_fsize(file->file), wehave | functions);
return file->hash;
}
/***************************************************************************
PATH ITERATION
***************************************************************************/
/*-------------------------------------------------
path_iterator_init - prepare to iterate over
a given path type
-------------------------------------------------*/
static void path_iterator_init(path_iterator *iterator, core_options *opts, const char *searchpath)
{
/* reset the structure */
memset(iterator, 0, sizeof(*iterator));
iterator->base = (searchpath != NULL) ? options_get_string(opts, searchpath) : "";
iterator->cur = iterator->base;
}
/*-------------------------------------------------
path_iterator_get_next - get the next entry
in a multipath sequence
-------------------------------------------------*/
static int path_iterator_get_next(path_iterator *iterator, astring *buffer)
{
const char *semi;
/* if none left, return FALSE to indicate we are done */
if (iterator->index != 0 && *iterator->cur == 0)
return FALSE;
/* copy up to the next semicolon */
semi = strchr(iterator->cur, ';');
if (semi == NULL)
semi = iterator->cur + strlen(iterator->cur);
astring_cpych(buffer, iterator->cur, semi - iterator->cur);
iterator->cur = (*semi == 0) ? semi : semi + 1;
/* bump the index and return TRUE */
iterator->index++;
return TRUE;
}
/***************************************************************************
MISC HELPERS
***************************************************************************/
/*-------------------------------------------------
load_zipped_file - load a ZIPped file
-------------------------------------------------*/
static file_error load_zipped_file(mame_file *file)
{
file_error filerr;
zip_error ziperr;
assert(file->file == NULL);
assert(file->zipdata == NULL);
assert(file->zipfile != NULL);
/* allocate some memory */
file->zipdata = malloc(file->ziplength);
if (file->zipdata == NULL)
return FILERR_OUT_OF_MEMORY;
/* read the data into our buffer and return */
ziperr = zip_file_decompress(file->zipfile, file->zipdata, file->ziplength);
if (ziperr != ZIPERR_NONE)
{
free(file->zipdata);
file->zipdata = NULL;
return FILERR_FAILURE;
}
/* convert to RAM file */
filerr = core_fopen_ram(file->zipdata, file->ziplength, file->openflags, &file->file);
if (filerr != FILERR_NONE)
{
free(file->zipdata);
file->zipdata = NULL;
return FILERR_FAILURE;
}
/* close out the ZIP file */
zip_file_close(file->zipfile);
file->zipfile = NULL;
return FILERR_NONE;
}
/*-------------------------------------------------
zip_filename_match - compare zip filename
to expected filename, ignoring any directory
-------------------------------------------------*/
static int zip_filename_match(const zip_file_header *header, const astring *filename)
{
const char *zipfile = header->filename + header->filename_length - astring_len(filename);
return (zipfile >= header->filename && astring_icmpc(filename, zipfile) == 0 &&
(zipfile == header->filename || zipfile[-1] == '/'));
}