/***************************************************************************

    corefile.c

    File access functions.

****************************************************************************

    Copyright Aaron Giles
    All rights reserved.

    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions are
    met:

        * Redistributions of source code must retain the above copyright
          notice, this list of conditions and the following disclaimer.
        * Redistributions in binary form must reproduce the above copyright
          notice, this list of conditions and the following disclaimer in
          the documentation and/or other materials provided with the
          distribution.
        * Neither the name 'MAME' nor the names of its contributors may be
          used to endorse or promote products derived from this software
          without specific prior written permission.

    THIS SOFTWARE IS PROVIDED BY AARON GILES ''AS IS'' AND ANY EXPRESS OR
    IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
    WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
    DISCLAIMED. IN NO EVENT SHALL AARON GILES BE LIABLE FOR ANY DIRECT,
    INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
    (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
    SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
    HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
    STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
    IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    POSSIBILITY OF SUCH DAMAGE.

***************************************************************************/

#include "corefile.h"
#include "unicode.h"
#include <zlib.h>

#include <stdarg.h>
#include <ctype.h>



/***************************************************************************
    VALIDATION
***************************************************************************/

#if !defined(CRLF) || (CRLF < 1) || (CRLF > 3)
#error CRLF undefined: must be 1 (CR), 2 (LF) or 3 (CR/LF)
#endif



/***************************************************************************
    CONSTANTS
***************************************************************************/

#define FILE_BUFFER_SIZE		512

#define OPEN_FLAG_HAS_CRC		0x10000



/***************************************************************************
    TYPE DEFINITIONS
***************************************************************************/

enum _text_file_type
{
	TFT_OSD = 0,	/* OSD depdendent encoding format used when BOMs missing */
	TFT_UTF8,		/* UTF-8 */
	TFT_UTF16BE,	/* UTF-16 (big endian) */
	TFT_UTF16LE,	/* UTF-16 (little endian) */
	TFT_UTF32BE,	/* UTF-32 (UCS-4) (big endian) */
	TFT_UTF32LE		/* UTF-32 (UCS-4) (little endian) */
};
typedef enum _text_file_type text_file_type;


typedef struct _zlib_data zlib_data;
struct _zlib_data
{
	z_stream		stream;
	UINT8			buffer[1024];
	UINT64			realoffset;
	UINT64			nextoffset;
};


/* typedef struct _core_file core_file -- declared in corefile.h */
struct _core_file
{
	osd_file *		file;						/* OSD file handle */
	zlib_data *		zdata;						/* compression data */
	UINT32			openflags;					/* flags we were opened with */
	UINT8			data_allocated;				/* was the data allocated by us? */
	UINT8 *			data;						/* file data, if RAM-based */
	UINT64			offset;						/* current file offset */
	UINT64			length;						/* total file length */
	text_file_type	text_type;					/* text output format */
	char			back_chars[UTF8_CHAR_MAX];	/* buffer to hold characters for ungetc */
	int				back_char_head;				/* head of ungetc buffer */
	int				back_char_tail;				/* tail of ungetc buffer */
	UINT64			bufferbase;					/* base offset of internal buffer */
	UINT32			bufferbytes;				/* bytes currently loaded into buffer */
	UINT8			buffer[FILE_BUFFER_SIZE];	/* buffer data */
};



/***************************************************************************
    FUNCTION PROTOTYPES
***************************************************************************/

/* misc helpers */
static UINT32 safe_buffer_copy(const void *source, UINT32 sourceoffs, UINT32 sourcelen, void *dest, UINT32 destoffs, UINT32 destlen);
static file_error osd_or_zlib_read(core_file *file, void *buffer, UINT64 offset, UINT32 length, UINT32 *actual);
static file_error osd_or_zlib_write(core_file *file, const void *buffer, UINT64 offset, UINT32 length, UINT32 *actual);



/***************************************************************************
    INLINE FUNCTIONS
***************************************************************************/

/*-------------------------------------------------
    is_directory_separator - is a given character
    a directory separator? The following logic
    works for most platforms
-------------------------------------------------*/

INLINE int is_directory_separator(char c)
{
	return (c == '\\' || c == '/' || c == ':');
}



/***************************************************************************
    FILE OPEN/CLOSE
***************************************************************************/

/*-------------------------------------------------
    core_fopen - open a file for access and
    return an error code
-------------------------------------------------*/

file_error core_fopen(const char *filename, UINT32 openflags, core_file **file)
{
	file_error filerr = FILERR_NOT_FOUND;

	/* allocate the file itself */
	*file = (core_file *)malloc(sizeof(**file));
	if (*file == NULL)
		return FILERR_OUT_OF_MEMORY;
	memset(*file, 0, sizeof(**file));

	/* attempt to open the file */
	filerr = osd_open(filename, openflags, &(*file)->file, &(*file)->length);
	(*file)->openflags = openflags;

	/* handle errors and return */
	if (filerr != FILERR_NONE)
	{
		core_fclose(*file);
		*file = NULL;
	}
	return filerr;
}


/*-------------------------------------------------
    core_fopen_ram_internal - open a RAM-based buffer
    for file-like access, possibly copying the data,
    and return an error code
-------------------------------------------------*/

static file_error core_fopen_ram_internal(const void *data, size_t length, int copy_buffer, UINT32 openflags, core_file **file)
{
	/* can only do this for read access */
	if ((openflags & OPEN_FLAG_WRITE) != 0)
		return FILERR_INVALID_ACCESS;
	if ((openflags & OPEN_FLAG_CREATE) != 0)
		return FILERR_INVALID_ACCESS;

	/* allocate the file itself */
	*file = (core_file *)malloc(sizeof(**file) + (copy_buffer ? length : 0));
	if (*file == NULL)
		return FILERR_OUT_OF_MEMORY;
	memset(*file, 0, sizeof(**file));

	/* copy the buffer, if we're asked to */
	if (copy_buffer)
	{
		void *dest = ((UINT8 *) *file) + sizeof(**file);
		memcpy(dest, data, length);
		data = dest;
	}

	/* claim the buffer */
	(*file)->data = (UINT8 *)data;
	(*file)->length = length;
	(*file)->openflags = openflags;

	return FILERR_NONE;
}


/*-------------------------------------------------
    core_fopen_ram - open a RAM-based buffer for
    file-like access and return an error code
-------------------------------------------------*/

file_error core_fopen_ram(const void *data, size_t length, UINT32 openflags, core_file **file)
{
	return core_fopen_ram_internal(data, length, FALSE, openflags, file);
}


/*-------------------------------------------------
    core_fopen_ram_copy - open a copy of a RAM-based
    buffer for file-like access and return an error code
-------------------------------------------------*/

file_error core_fopen_ram_copy(const void *data, size_t length, UINT32 openflags, core_file **file)
{
	return core_fopen_ram_internal(data, length, TRUE, openflags, file);
}


/*-------------------------------------------------
    core_fclose - closes a file
-------------------------------------------------*/

void core_fclose(core_file *file)
{
	/* close files and free memory */
	if (file->zdata != NULL)
		core_fcompress(file, FCOMPRESS_NONE);
	if (file->file != NULL)
		osd_close(file->file);
	if (file->data != NULL && file->data_allocated)
		free(file->data);
	free(file);
}


/*-------------------------------------------------
    core_fcompress - enable/disable streaming file
    compression via zlib; level is 0 to disable
    compression, or up to 9 for max compression
-------------------------------------------------*/

file_error core_fcompress(core_file *file, int level)
{
	file_error result = FILERR_NONE;

	/* can only do this for read-only and write-only cases */
	if ((file->openflags & OPEN_FLAG_WRITE) != 0 && (file->openflags & OPEN_FLAG_READ) != 0)
		return FILERR_INVALID_ACCESS;

	/* if we have been compressing, flush and free the data */
	if (file->zdata != NULL && level == FCOMPRESS_NONE)
	{
		int zerr = Z_OK;

		/* flush any remaining data if we are writing */
		while ((file->openflags & OPEN_FLAG_WRITE) != 0 && zerr != Z_STREAM_END)
		{
			UINT32 actualdata;
			file_error filerr;

			/* deflate some more */
			zerr = deflate(&file->zdata->stream, Z_FINISH);
			if (zerr != Z_STREAM_END && zerr != Z_OK)
			{
				result = FILERR_INVALID_DATA;
				break;
			}

			/* write the data */
			if (file->zdata->stream.avail_out != sizeof(file->zdata->buffer))
			{
				filerr = osd_write(file->file, file->zdata->buffer, file->zdata->realoffset, sizeof(file->zdata->buffer) - file->zdata->stream.avail_out, &actualdata);
				if (filerr != FILERR_NONE)
					break;
				file->zdata->realoffset += actualdata;
				file->zdata->stream.next_out = file->zdata->buffer;
				file->zdata->stream.avail_out = sizeof(file->zdata->buffer);
			}
		}

		/* end the appropriate operation */
		if ((file->openflags & OPEN_FLAG_WRITE) != 0)
			deflateEnd(&file->zdata->stream);
		else
			inflateEnd(&file->zdata->stream);

		/* free memory */
		free(file->zdata);
		file->zdata = NULL;
	}

	/* if we are just starting to compress, allocate a new buffer */
	if (file->zdata == NULL && level > FCOMPRESS_NONE)
	{
		int zerr;

		/* allocate memory */
		file->zdata = (zlib_data *)malloc(sizeof(*file->zdata));
		if (file->zdata == NULL)
			return FILERR_OUT_OF_MEMORY;
		memset(file->zdata, 0, sizeof(*file->zdata));

		/* initialize the stream and compressor */
		if ((file->openflags & OPEN_FLAG_WRITE) != 0)
		{
			file->zdata->stream.next_out = file->zdata->buffer;
			file->zdata->stream.avail_out = sizeof(file->zdata->buffer);
			zerr = deflateInit(&file->zdata->stream, level);
		}
		else
			zerr = inflateInit(&file->zdata->stream);

		/* on error, return an error */
		if (zerr != Z_OK)
		{
			free(file->zdata);
			file->zdata = NULL;
			return FILERR_OUT_OF_MEMORY;
		}

		/* flush buffers */
		file->bufferbytes = 0;

		/* set the initial offset */
		file->zdata->realoffset = file->offset;
		file->zdata->nextoffset = file->offset;
	}

	return result;
}



/***************************************************************************
    FILE POSITIONING
***************************************************************************/

/*-------------------------------------------------
    core_fseek - seek within a file
-------------------------------------------------*/

int core_fseek(core_file *file, INT64 offset, int whence)
{
	int err = 0;

	/* error if compressing */
	if (file->zdata != NULL)
		return 1;

	/* flush any buffered char */
	file->back_char_head = 0;
	file->back_char_tail = 0;

	/* switch off the relative location */
	switch (whence)
	{
		case SEEK_SET:
			file->offset = offset;
			break;

		case SEEK_CUR:
			file->offset += offset;
			break;

		case SEEK_END:
			file->offset = file->length + offset;
			break;
	}
	return err;
}


/*-------------------------------------------------
    core_ftell - return the current file position
-------------------------------------------------*/

UINT64 core_ftell(core_file *file)
{
	/* return the current offset */
	return file->offset;
}


/*-------------------------------------------------
    core_feof - return 1 if we're at the end
    of file
-------------------------------------------------*/

int core_feof(core_file *file)
{
	/* check for buffered chars */
	if (file->back_char_head != file->back_char_tail)
		return 0;

	/* if the offset == length, we're at EOF */
	return (file->offset >= file->length);
}


/*-------------------------------------------------
    core_fsize - returns the size of a file
-------------------------------------------------*/

UINT64 core_fsize(core_file *file)
{
	return file->length;
}



/***************************************************************************
    FILE READ
***************************************************************************/

/*-------------------------------------------------
    core_fread - read from a file
-------------------------------------------------*/

UINT32 core_fread(core_file *file, void *buffer, UINT32 length)
{
	file_error filerr;
	UINT32 bytes_read = 0;

	/* flush any buffered char */
	file->back_char_head = 0;
	file->back_char_tail = 0;

	/* handle real files */
	if (file->data == NULL)
	{
		/* if we're within the buffer, consume that first */
		if (file->offset >= file->bufferbase && file->offset < file->bufferbase + file->bufferbytes)
			bytes_read += safe_buffer_copy(file->buffer, file->offset - file->bufferbase, file->bufferbytes, buffer, bytes_read, length);

		/* if we've got a small amount left, read it into the buffer first */
		if (bytes_read < length)
		{
			if (length - bytes_read < sizeof(file->buffer) / 2)
			{
				/* read as much as makes sense into the buffer */
				file->bufferbase = file->offset + bytes_read;
				file->bufferbytes = 0;
				filerr = osd_or_zlib_read(file, file->buffer, file->bufferbase, sizeof(file->buffer), &file->bufferbytes);

				/* do a bounded copy from the buffer to the destination */
				bytes_read += safe_buffer_copy(file->buffer, 0, file->bufferbytes, buffer, bytes_read, length);
			}

			/* read the remainder directly from the file */
			else
			{
				UINT32 new_bytes_read = 0;
				filerr = osd_or_zlib_read(file, (UINT8 *)buffer + bytes_read, file->offset + bytes_read, length - bytes_read, &new_bytes_read);
				bytes_read += new_bytes_read;
			}
		}
	}

	/* handle RAM-based files */
	else
		bytes_read += safe_buffer_copy(file->data, (UINT32)file->offset, file->length, buffer, bytes_read, length);

	/* return the number of bytes read */
	file->offset += bytes_read;
	return bytes_read;
}


/*-------------------------------------------------
    core_fgetc - read a character from a file
-------------------------------------------------*/

int core_fgetc(core_file *file)
{
	int result;

	/* refresh buffer, if necessary */
	if (file->back_char_head == file->back_char_tail)
	{
		utf16_char utf16_buffer[UTF16_CHAR_MAX];
		char utf8_buffer[UTF8_CHAR_MAX];
		char default_buffer[16];
		unicode_char uchar = (unicode_char) ~0;
		int readlen, charlen;

		/* do we need to check the byte order marks? */
		if (file->offset == 0)
		{
			UINT8 bom[4];
			int pos = 0;

			if (core_fread(file, bom, 4) == 4)
			{
				if (bom[0] == 0xef && bom[1] == 0xbb && bom[2] == 0xbf)
				{
					file->text_type = TFT_UTF8;
					pos = 3;
				}
				else if (bom[0] == 0x00 && bom[1] == 0x00 && bom[2] == 0xfe && bom[3] == 0xff)
				{
					file->text_type = TFT_UTF32BE;
					pos = 4;
				}
				else if (bom[0] == 0xff && bom[1] == 0xfe && bom[2] == 0x00 && bom[3] == 0x00)
				{
					file->text_type = TFT_UTF32LE;
					pos = 4;
				}
				else if (bom[0] == 0xfe && bom[1] == 0xff)
				{
					file->text_type = TFT_UTF16BE;
					pos = 2;
				}
				else if (bom[0] == 0xff && bom[1] == 0xfe)
				{
					file->text_type = TFT_UTF16LE;
					pos = 2;
				}
				else
				{
					file->text_type = TFT_OSD;
					pos = 0;
				}
			}
			core_fseek(file, pos, SEEK_SET);
		}

		/* fetch the next character */
		switch (file->text_type)
		{
			default:
			case TFT_OSD:
				readlen = core_fread(file, default_buffer, sizeof(default_buffer));
				if (readlen > 0)
				{
					charlen = osd_uchar_from_osdchar(&uchar, default_buffer, readlen / sizeof(default_buffer[0]));
					core_fseek(file, (INT64) (charlen * sizeof(default_buffer[0])) - readlen, SEEK_CUR);
				}
				break;

			case TFT_UTF8:
				readlen = core_fread(file, utf8_buffer, sizeof(utf8_buffer));
				if (readlen > 0)
				{
					charlen = uchar_from_utf8(&uchar, utf8_buffer, readlen / sizeof(utf8_buffer[0]));
					core_fseek(file, (INT64) (charlen * sizeof(utf8_buffer[0])) - readlen, SEEK_CUR);
				}
				break;

			case TFT_UTF16BE:
				readlen = core_fread(file, utf16_buffer, sizeof(utf16_buffer));
				if (readlen > 0)
				{
					charlen = uchar_from_utf16be(&uchar, utf16_buffer, readlen / sizeof(utf16_buffer[0]));
					core_fseek(file, (INT64) (charlen * sizeof(utf16_buffer[0])) - readlen, SEEK_CUR);
				}
				break;

			case TFT_UTF16LE:
				readlen = core_fread(file, utf16_buffer, sizeof(utf16_buffer));
				if (readlen > 0)
				{
					charlen = uchar_from_utf16le(&uchar, utf16_buffer, readlen / sizeof(utf16_buffer[0]));
					core_fseek(file, (INT64) (charlen * sizeof(utf16_buffer[0])) - readlen, SEEK_CUR);
				}
				break;

			case TFT_UTF32BE:
				if (core_fread(file, &uchar, sizeof(uchar)) == sizeof(uchar))
					uchar = BIG_ENDIANIZE_INT32(uchar);
				break;

			case TFT_UTF32LE:
				if (core_fread(file, &uchar, sizeof(uchar)) == sizeof(uchar))
					uchar = LITTLE_ENDIANIZE_INT32(uchar);
				break;
		}

		if (uchar != ~0)
		{
			/* place the new character in the ring buffer */
			file->back_char_head = 0;
			file->back_char_tail = utf8_from_uchar(file->back_chars, ARRAY_LENGTH(file->back_chars), uchar);
/*          assert(file->back_char_tail != -1);*/
		}
	}

	/* now read from the ring buffer */
	if (file->back_char_head != file->back_char_tail)
	{
		result = file->back_chars[file->back_char_head++];
		file->back_char_head %= ARRAY_LENGTH(file->back_chars);
	}
	else
		result = EOF;

	return result;
}


/*-------------------------------------------------
    core_ungetc - put back a character read from
    a file
-------------------------------------------------*/

int core_ungetc(int c, core_file *file)
{
	file->back_chars[file->back_char_tail++] = (char) c;
	file->back_char_tail %= ARRAY_LENGTH(file->back_chars);
	return c;
}


/*-------------------------------------------------
    core_fgets - read a line from a text file
-------------------------------------------------*/

char *core_fgets(char *s, int n, core_file *file)
{
	char *cur = s;

	/* loop while we have characters */
	while (n > 0)
	{
		int c = core_fgetc(file);
		if (c == EOF)
			break;

		/* if there's a CR, look for an LF afterwards */
		if (c == 0x0d)
		{
			int c2 = core_fgetc(file);
			if (c2 != 0x0a)
				core_ungetc(c2, file);
			*cur++ = 0x0d;
			n--;
			break;
		}

		/* if there's an LF, reinterp as a CR for consistency */
		else if (c == 0x0a)
		{
			*cur++ = 0x0d;
			n--;
			break;
		}

		/* otherwise, pop the character in and continue */
		*cur++ = c;
		n--;
	}

	/* if we put nothing in, return NULL */
	if (cur == s)
		return NULL;

	/* otherwise, terminate */
	if (n > 0)
		*cur++ = 0;
	return s;
}


/*-------------------------------------------------
    core_fbuffer - return a pointer to the file
    buffer; if it doesn't yet exist, load the
    file into RAM first
-------------------------------------------------*/

const void *core_fbuffer(core_file *file)
{
	file_error filerr;
	UINT32 read_length;

	/* if we already have data, just return it */
	if (file->data != NULL)
		return file->data;

	/* allocate some memory */
	file->data = (UINT8 *)malloc(file->length);
	if (file->data == NULL)
		return NULL;
	file->data_allocated = TRUE;

	/* read the file */
	filerr = osd_or_zlib_read(file, file->data, 0, file->length, &read_length);
	if (filerr != FILERR_NONE || read_length != file->length)
	{
		free(file->data);
		file->data = NULL;
		return NULL;
	}

	/* close the file because we don't need it anymore */
	osd_close(file->file);
	file->file = NULL;
	return file->data;
}


/*-------------------------------------------------
    core_fload - open a file with the specified
    filename, read it into memory, and return a
    pointer
-------------------------------------------------*/

file_error core_fload(const char *filename, void **data, UINT32 *length)
{
	core_file *file = NULL;
	file_error err;
	UINT64 size;

	/* attempt to open the file */
	err = core_fopen(filename, OPEN_FLAG_READ, &file);
	if (err != FILERR_NONE)
		return err;

	/* get the size */
	size = core_fsize(file);
	if ((UINT32)size != size)
	{
		core_fclose(file);
		return FILERR_OUT_OF_MEMORY;
	}

	/* allocate memory */
	*data = malloc(size);
	if (length != NULL)
		*length = (UINT32)size;

	/* read the data */
	if (core_fread(file, *data, size) != size)
	{
		core_fclose(file);
		free(*data);
		return FILERR_FAILURE;
	}

	/* close the file and return data */
	core_fclose(file);
	return FILERR_NONE;
}



/***************************************************************************
    FILE WRITE
***************************************************************************/

/*-------------------------------------------------
    core_fwrite - write to a file
-------------------------------------------------*/

UINT32 core_fwrite(core_file *file, const void *buffer, UINT32 length)
{
	UINT32 bytes_written = 0;
	file_error filerr;

	/* can't write to RAM-based stuff */
	if (file->data != NULL)
		return 0;

	/* flush any buffered char */
	file->back_char_head = 0;
	file->back_char_tail = 0;

	/* invalidate any buffered data */
	file->bufferbytes = 0;

	/* do the write */
	filerr = osd_or_zlib_write(file, buffer, file->offset, length, &bytes_written);

	/* return the number of bytes read */
	file->offset += bytes_written;
	file->length = MAX(file->length, file->offset);
	return bytes_written;
}


/*-------------------------------------------------
    core_fputs - write a line to a text file
-------------------------------------------------*/

int core_fputs(core_file *f, const char *s)
{
	char convbuf[1024];
	char *pconvbuf = convbuf;
	int count = 0;

	/* is this the beginning of the file?  if so, write a byte order mark */
	if (f->offset == 0 && !(f->openflags & OPEN_FLAG_NO_BOM))
	{
		*pconvbuf++ = (char)0xef;
		*pconvbuf++ = (char)0xbb;
		*pconvbuf++ = (char)0xbf;
	}

	/* convert '\n' to platform dependant line endings */
	while (*s != 0)
	{
		if (*s == '\n')
		{
			if (CRLF == 1)		/* CR only */
				*pconvbuf++ = 13;
			else if (CRLF == 2)	/* LF only */
				*pconvbuf++ = 10;
			else if (CRLF == 3)	/* CR+LF */
			{
				*pconvbuf++ = 13;
				*pconvbuf++ = 10;
			}
		}
		else
			*pconvbuf++ = *s;
		s++;

		/* if we overflow, break into chunks */
		if (pconvbuf >= convbuf + ARRAY_LENGTH(convbuf) - 10)
		{
			count += core_fwrite(f, convbuf, pconvbuf - convbuf);
			pconvbuf = convbuf;
		}
	}

	/* final flush */
	if (pconvbuf != convbuf)
		count += core_fwrite(f, convbuf, pconvbuf - convbuf);

	return count;
}


/*-------------------------------------------------
    core_vfprintf - vfprintf to a text file
-------------------------------------------------*/

int core_vfprintf(core_file *f, const char *fmt, va_list va)
{
	char buf[1024];
	vsnprintf(buf, sizeof(buf), fmt, va);
	return core_fputs(f, buf);
}


/*-------------------------------------------------
    core_fprintf - vfprintf to a text file
-------------------------------------------------*/

int CLIB_DECL core_fprintf(core_file *f, const char *fmt, ...)
{
	int rc;
	va_list va;
	va_start(va, fmt);
	rc = core_vfprintf(f, fmt, va);
	va_end(va);
	return rc;
}



/***************************************************************************
    FILENAME UTILITIES
***************************************************************************/

/*-------------------------------------------------
    core_filename_extract_base - extract the base
    name from a filename; note that this makes
    assumptions about path separators
-------------------------------------------------*/

astring *core_filename_extract_base(astring *result, const char *name, int strip_extension)
{
	/* find the start of the name */
	const char *start = name + strlen(name);
	while (start > name && !is_directory_separator(start[-1]))
		start--;

	/* copy the rest into an astring */
	astring_cpyc(result, start);

	/* chop the extension if present */
	if (strip_extension)
		astring_substr(result, 0, astring_rchr(result, 0, '.'));
	return result;
}


/*-------------------------------------------------
    core_filename_ends_with - does the given
    filename end with the specified extension?
-------------------------------------------------*/

int core_filename_ends_with(const char *filename, const char *extension)
{
	int namelen = strlen(filename);
	int extlen = strlen(extension);
	int matches = TRUE;

	/* work backwards checking for a match */
	while (extlen > 0)
		if (tolower((UINT8)filename[--namelen]) != tolower((UINT8)extension[--extlen]))
		{
			matches = FALSE;
			break;
		}

	return matches;
}



/***************************************************************************
    MISC HELPERS
***************************************************************************/

/*-------------------------------------------------
    safe_buffer_copy - copy safely from one
    bounded buffer to another
-------------------------------------------------*/

static UINT32 safe_buffer_copy(const void *source, UINT32 sourceoffs, UINT32 sourcelen, void *dest, UINT32 destoffs, UINT32 destlen)
{
	UINT32 sourceavail = sourcelen - sourceoffs;
	UINT32 destavail = destlen - destoffs;
	UINT32 bytes_to_copy = MIN(sourceavail, destavail);
	if (bytes_to_copy > 0)
		memcpy((UINT8 *)dest + destoffs, (const UINT8 *)source + sourceoffs, bytes_to_copy);
	return bytes_to_copy;
}


/*-------------------------------------------------
    osd_or_zlib_read - wrapper for osd_read that
    handles zlib-compressed data
-------------------------------------------------*/

static file_error osd_or_zlib_read(core_file *file, void *buffer, UINT64 offset, UINT32 length, UINT32 *actual)
{
	/* if no compression, just pass through */
	if (file->zdata == NULL)
		return osd_read(file->file, buffer, offset, length, actual);

	/* if the offset doesn't match the next offset, fail */
	if (offset != file->zdata->nextoffset)
		return FILERR_INVALID_ACCESS;

	/* set up the destination */
	file->zdata->stream.next_out = (Bytef *)buffer;
	file->zdata->stream.avail_out = length;
	while (file->zdata->stream.avail_out != 0)
	{
		file_error filerr;
		UINT32 actualdata;
		int zerr = Z_OK;

		/* if we didn't make progress, report an error or the end */
		if (file->zdata->stream.avail_in != 0)
			zerr = inflate(&file->zdata->stream, Z_SYNC_FLUSH);
		if (zerr != Z_OK)
		{
			*actual = length - file->zdata->stream.avail_out;
			file->zdata->nextoffset += *actual;
			return (zerr == Z_STREAM_END) ? FILERR_NONE : FILERR_INVALID_DATA;
		}

		/* fetch more data if needed */
		if (file->zdata->stream.avail_in == 0)
		{
			filerr = osd_read(file->file, file->zdata->buffer, file->zdata->realoffset, sizeof(file->zdata->buffer), &actualdata);
			if (filerr != FILERR_NONE)
				return filerr;
			file->zdata->realoffset += actualdata;
			file->zdata->stream.next_in = file->zdata->buffer;
			file->zdata->stream.avail_in = sizeof(file->zdata->buffer);
		}
	}

	/* we read everything */
	*actual = length;
	file->zdata->nextoffset += *actual;
	return FILERR_NONE;
}


/*-------------------------------------------------
    osd_or_zlib_write - wrapper for osd_write that
    handles zlib-compressed data
-------------------------------------------------*/

static file_error osd_or_zlib_write(core_file *file, const void *buffer, UINT64 offset, UINT32 length, UINT32 *actual)
{
	/* if no compression, just pass through */
	if (file->zdata == NULL)
		return osd_write(file->file, buffer, offset, length, actual);

	/* if the offset doesn't match the next offset, fail */
	if (offset != file->zdata->nextoffset)
		return FILERR_INVALID_ACCESS;

	/* set up the source */
	file->zdata->stream.next_in = (Bytef *)buffer;
	file->zdata->stream.avail_in = length;
	while (file->zdata->stream.avail_in != 0)
	{
		file_error filerr;
		UINT32 actualdata;
		int zerr;

		/* if we didn't make progress, report an error or the end */
		zerr = deflate(&file->zdata->stream, Z_NO_FLUSH);
		if (zerr != Z_OK)
		{
			*actual = length - file->zdata->stream.avail_in;
			file->zdata->nextoffset += *actual;
			return FILERR_INVALID_DATA;
		}

		/* write more data if we are full up */
		if (file->zdata->stream.avail_out == 0)
		{
			filerr = osd_write(file->file, file->zdata->buffer, file->zdata->realoffset, sizeof(file->zdata->buffer), &actualdata);
			if (filerr != FILERR_NONE)
				return filerr;
			file->zdata->realoffset += actualdata;
			file->zdata->stream.next_out = file->zdata->buffer;
			file->zdata->stream.avail_out = sizeof(file->zdata->buffer);
		}
	}

	/* we read everything */
	*actual = length;
	file->zdata->nextoffset += *actual;
	return FILERR_NONE;
}


