/***************************************************************************
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) */
};
struct zlib_data
{
z_stream stream;
UINT8 buffer[1024];
UINT64 realoffset;
UINT64 nextoffset;
};
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)
{
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;
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;
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);