mirror of
https://github.com/holub/mame
synced 2025-10-06 17:08:28 +03:00
1323 lines
34 KiB
C++
1323 lines
34 KiB
C++
// license:BSD-3-Clause
|
|
// copyright-holders:Aaron Giles, Vas Crabb
|
|
/***************************************************************************
|
|
|
|
corefile.c
|
|
|
|
File access functions.
|
|
|
|
***************************************************************************/
|
|
|
|
#include <assert.h>
|
|
|
|
#include "corefile.h"
|
|
#include "unicode.h"
|
|
#include <zlib.h>
|
|
|
|
#include <algorithm>
|
|
#include <cstdarg>
|
|
#include <cstring>
|
|
#include <ctype.h>
|
|
|
|
|
|
|
|
namespace util {
|
|
|
|
namespace {
|
|
|
|
/***************************************************************************
|
|
VALIDATION
|
|
***************************************************************************/
|
|
|
|
#if !defined(CRLF) || (CRLF < 1) || (CRLF > 3)
|
|
#error CRLF undefined: must be 1 (CR), 2 (LF) or 3 (CR/LF)
|
|
#endif
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
TYPE DEFINITIONS
|
|
***************************************************************************/
|
|
|
|
class zlib_data
|
|
{
|
|
public:
|
|
typedef std::unique_ptr<zlib_data> ptr;
|
|
|
|
static int start_compression(int level, std::uint64_t offset, ptr &data)
|
|
{
|
|
ptr result(new zlib_data(offset));
|
|
result->reset_output();
|
|
auto const zerr = deflateInit(&result->m_stream, level);
|
|
result->m_compress = (Z_OK == zerr);
|
|
if (result->m_compress) data = std::move(result);
|
|
return zerr;
|
|
}
|
|
static int start_decompression(std::uint64_t offset, ptr &data)
|
|
{
|
|
ptr result(new zlib_data(offset));
|
|
auto const zerr = inflateInit(&result->m_stream);
|
|
result->m_decompress = (Z_OK == zerr);
|
|
if (result->m_decompress) data = std::move(result);
|
|
return zerr;
|
|
}
|
|
|
|
~zlib_data()
|
|
{
|
|
if (m_compress) deflateEnd(&m_stream);
|
|
else if (m_decompress) inflateEnd(&m_stream);
|
|
}
|
|
|
|
std::size_t buffer_size() const { return sizeof(m_buffer); }
|
|
void const *buffer_data() const { return m_buffer; }
|
|
void *buffer_data() { return m_buffer; }
|
|
|
|
// general-purpose output buffer manipulation
|
|
bool output_full() const { return 0 == m_stream.avail_out; }
|
|
std::size_t output_space() const { return m_stream.avail_out; }
|
|
void set_output(void *data, std::uint32_t size)
|
|
{
|
|
m_stream.next_out = reinterpret_cast<Bytef *>(data);
|
|
m_stream.avail_out = size;
|
|
}
|
|
|
|
// working with output to the internal buffer
|
|
bool has_output() const { return m_stream.avail_out != sizeof(m_buffer); }
|
|
std::size_t output_size() const { return sizeof(m_buffer) - m_stream.avail_out; }
|
|
void reset_output()
|
|
{
|
|
m_stream.next_out = m_buffer;
|
|
m_stream.avail_out = sizeof(m_buffer);
|
|
}
|
|
|
|
// general-purpose input buffer manipulation
|
|
bool has_input() const { return 0 != m_stream.avail_in; }
|
|
std::size_t input_size() const { return m_stream.avail_in; }
|
|
void set_input(void const *data, std::uint32_t size)
|
|
{
|
|
m_stream.next_in = const_cast<Bytef *>(reinterpret_cast<Bytef const *>(data));
|
|
m_stream.avail_in = size;
|
|
}
|
|
|
|
// working with input from the internal buffer
|
|
void reset_input(std::uint32_t size)
|
|
{
|
|
m_stream.next_in = m_buffer;
|
|
m_stream.avail_in = size;
|
|
}
|
|
|
|
int compress() { assert(m_compress); return deflate(&m_stream, Z_NO_FLUSH); }
|
|
int finalise() { assert(m_compress); return deflate(&m_stream, Z_FINISH); }
|
|
int decompress() { assert(m_decompress); return inflate(&m_stream, Z_SYNC_FLUSH); }
|
|
|
|
std::uint64_t realoffset() const { return m_realoffset; }
|
|
void add_realoffset(std::uint32_t increment) { m_realoffset += increment; }
|
|
|
|
bool is_nextoffset(std::uint64_t value) const { return m_nextoffset == value; }
|
|
void add_nextoffset(std::uint32_t increment) { m_nextoffset += increment; }
|
|
|
|
private:
|
|
zlib_data(std::uint64_t offset)
|
|
: m_compress(false)
|
|
, m_decompress(false)
|
|
, m_realoffset(offset)
|
|
, m_nextoffset(offset)
|
|
{
|
|
m_stream.zalloc = Z_NULL;
|
|
m_stream.zfree = Z_NULL;
|
|
m_stream.opaque = Z_NULL;
|
|
}
|
|
|
|
bool m_compress, m_decompress;
|
|
z_stream m_stream;
|
|
std::uint8_t m_buffer[1024];
|
|
std::uint64_t m_realoffset;
|
|
std::uint64_t m_nextoffset;
|
|
};
|
|
|
|
|
|
class core_proxy_file : public core_file
|
|
{
|
|
public:
|
|
core_proxy_file(core_file &file) : m_file(file) { }
|
|
virtual ~core_proxy_file() override { }
|
|
virtual file_error compress(int level) override { return m_file.compress(level); }
|
|
|
|
virtual int seek(std::int64_t offset, int whence) override { return m_file.seek(offset, whence); }
|
|
virtual std::uint64_t tell() const override { return m_file.tell(); }
|
|
virtual bool eof() const override { return m_file.eof(); }
|
|
virtual std::uint64_t size() const override { return m_file.size(); }
|
|
|
|
virtual std::uint32_t read(void *buffer, std::uint32_t length) override { return m_file.read(buffer, length); }
|
|
virtual int getc() override { return m_file.getc(); }
|
|
virtual int ungetc(int c) override { return m_file.ungetc(c); }
|
|
virtual char *gets(char *s, int n) override { return m_file.gets(s, n); }
|
|
virtual const void *buffer() override { return m_file.buffer(); }
|
|
|
|
virtual std::uint32_t write(const void *buffer, std::uint32_t length) override { return m_file.write(buffer, length); }
|
|
virtual int puts(const char *s) override { return m_file.puts(s); }
|
|
virtual int vprintf(const char *fmt, va_list va) override { return m_file.vprintf(fmt, va); }
|
|
virtual file_error truncate(std::uint64_t offset) override { return m_file.truncate(offset); }
|
|
|
|
virtual file_error flush() override { return m_file.flush(); }
|
|
|
|
private:
|
|
core_file &m_file;
|
|
};
|
|
|
|
|
|
class core_text_file : public core_file
|
|
{
|
|
public:
|
|
enum class text_file_type
|
|
{
|
|
OSD, // OSD dependent encoding format used when BOMs missing
|
|
UTF8, // UTF-8
|
|
UTF16BE, // UTF-16 (big endian)
|
|
UTF16LE, // UTF-16 (little endian)
|
|
UTF32BE, // UTF-32 (UCS-4) (big endian)
|
|
UTF32LE // UTF-32 (UCS-4) (little endian)
|
|
};
|
|
|
|
virtual int getc() override;
|
|
virtual int ungetc(int c) override;
|
|
virtual char *gets(char *s, int n) override;
|
|
virtual int puts(char const *s) override;
|
|
virtual int vprintf(char const *fmt, va_list va) override;
|
|
|
|
protected:
|
|
core_text_file(std::uint32_t openflags)
|
|
: m_openflags(openflags)
|
|
, m_text_type(text_file_type::OSD)
|
|
, m_back_char_head(0)
|
|
, m_back_char_tail(0)
|
|
{
|
|
}
|
|
|
|
bool read_access() const { return 0U != (m_openflags & OPEN_FLAG_READ); }
|
|
bool write_access() const { return 0U != (m_openflags & OPEN_FLAG_WRITE); }
|
|
bool no_bom() const { return 0U != (m_openflags & OPEN_FLAG_NO_BOM); }
|
|
|
|
bool has_putback() const { return m_back_char_head != m_back_char_tail; }
|
|
void clear_putback() { m_back_char_head = m_back_char_tail = 0; }
|
|
|
|
private:
|
|
std::uint32_t const m_openflags; // flags we were opened with
|
|
text_file_type m_text_type; // text output format
|
|
char m_back_chars[UTF8_CHAR_MAX]; // buffer to hold characters for ungetc
|
|
int m_back_char_head; // head of ungetc buffer
|
|
int m_back_char_tail; // tail of ungetc buffer
|
|
};
|
|
|
|
|
|
class core_in_memory_file : public core_text_file
|
|
{
|
|
public:
|
|
core_in_memory_file(std::uint32_t openflags, void const *data, std::size_t length, bool copy)
|
|
: core_text_file(openflags)
|
|
, m_data_allocated(false)
|
|
, m_data(copy ? nullptr : data)
|
|
, m_offset(0)
|
|
, m_length(length)
|
|
{
|
|
if (copy)
|
|
{
|
|
void *const buf = allocate();
|
|
if (buf) std::memcpy(buf, data, length);
|
|
}
|
|
}
|
|
|
|
~core_in_memory_file() override { purge(); }
|
|
virtual file_error compress(int level) override { return FILERR_INVALID_ACCESS; }
|
|
|
|
virtual int seek(std::int64_t offset, int whence) override;
|
|
virtual std::uint64_t tell() const override { return m_offset; }
|
|
virtual bool eof() const override;
|
|
virtual std::uint64_t size() const override { return m_length; }
|
|
|
|
virtual std::uint32_t read(void *buffer, std::uint32_t length) override;
|
|
virtual void const *buffer() override { return m_data; }
|
|
|
|
virtual std::uint32_t write(void const *buffer, std::uint32_t length) override { return 0; }
|
|
virtual file_error truncate(std::uint64_t offset) override;
|
|
virtual file_error flush() override { clear_putback(); return FILERR_NONE; }
|
|
|
|
protected:
|
|
core_in_memory_file(std::uint32_t openflags, std::uint64_t length)
|
|
: core_text_file(openflags)
|
|
, m_data_allocated(false)
|
|
, m_data(nullptr)
|
|
, m_offset(0)
|
|
, m_length(length)
|
|
{
|
|
}
|
|
|
|
bool is_loaded() const { return nullptr != m_data; }
|
|
void *allocate()
|
|
{
|
|
if (m_data) return nullptr;
|
|
void *data = malloc(m_length);
|
|
if (data)
|
|
{
|
|
m_data_allocated = true;
|
|
m_data = data;
|
|
}
|
|
return data;
|
|
}
|
|
void purge()
|
|
{
|
|
if (m_data && m_data_allocated) free(const_cast<void *>(m_data));
|
|
m_data_allocated = false;
|
|
m_data = nullptr;
|
|
}
|
|
|
|
std::uint64_t offset() const { return m_offset; }
|
|
void add_offset(std::uint32_t increment) { m_offset += increment; m_length = (std::max)(m_length, m_offset); }
|
|
std::uint64_t length() const { return m_length; }
|
|
void set_length(std::uint64_t value) { m_length = value; m_offset = (std::min)(m_offset, m_length); }
|
|
|
|
static std::size_t safe_buffer_copy(
|
|
void const *source, std::size_t sourceoffs, std::size_t sourcelen,
|
|
void *dest, std::size_t destoffs, std::size_t destlen);
|
|
|
|
private:
|
|
bool m_data_allocated; // was the data allocated by us?
|
|
void const * m_data; // file data, if RAM-based
|
|
std::uint64_t m_offset; // current file offset
|
|
std::uint64_t m_length; // total file length
|
|
};
|
|
|
|
|
|
class core_osd_file : public core_in_memory_file
|
|
{
|
|
public:
|
|
core_osd_file(std::uint32_t openmode, osd_file *file, std::uint64_t length)
|
|
: core_in_memory_file(openmode, length)
|
|
, m_file(file)
|
|
, m_zdata()
|
|
, m_bufferbase(0)
|
|
, m_bufferbytes(0)
|
|
{
|
|
}
|
|
~core_osd_file() override;
|
|
|
|
virtual file_error compress(int level) override;
|
|
|
|
virtual int seek(std::int64_t offset, int whence) override;
|
|
|
|
virtual std::uint32_t read(void *buffer, std::uint32_t length) override;
|
|
virtual void const *buffer() override;
|
|
|
|
virtual std::uint32_t write(void const *buffer, std::uint32_t length) override;
|
|
virtual file_error truncate(std::uint64_t offset) override;
|
|
virtual file_error flush() override;
|
|
|
|
protected:
|
|
|
|
bool is_buffered() const { return (offset() >= m_bufferbase) && (offset() < (m_bufferbase + m_bufferbytes)); }
|
|
|
|
private:
|
|
static constexpr std::size_t FILE_BUFFER_SIZE = 512;
|
|
|
|
file_error osd_or_zlib_read(void *buffer, std::uint64_t offset, std::uint32_t length, std::uint32_t &actual);
|
|
file_error osd_or_zlib_write(void const *buffer, std::uint64_t offset, std::uint32_t length, std::uint32_t &actual);
|
|
|
|
osd_file * m_file; // OSD file handle
|
|
zlib_data::ptr m_zdata; // compression data
|
|
std::uint64_t m_bufferbase; // base offset of internal buffer
|
|
std::uint32_t m_bufferbytes; // bytes currently loaded into buffer
|
|
std::uint8_t m_buffer[FILE_BUFFER_SIZE]; // buffer data
|
|
};
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
INLINE FUNCTIONS
|
|
***************************************************************************/
|
|
|
|
/*-------------------------------------------------
|
|
is_directory_separator - is a given character
|
|
a directory separator? The following logic
|
|
works for most platforms
|
|
-------------------------------------------------*/
|
|
|
|
static inline int is_directory_separator(char c)
|
|
{
|
|
return (c == '\\' || c == '/' || c == ':');
|
|
}
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
core_text_file
|
|
***************************************************************************/
|
|
|
|
/*-------------------------------------------------
|
|
getc - read a character from a file
|
|
-------------------------------------------------*/
|
|
|
|
int core_text_file::getc()
|
|
{
|
|
int result;
|
|
|
|
// refresh buffer, if necessary
|
|
if (m_back_char_head == m_back_char_tail)
|
|
{
|
|
// do we need to check the byte order marks?
|
|
if (tell() == 0)
|
|
{
|
|
std::uint8_t bom[4];
|
|
int pos = 0;
|
|
|
|
if (read(bom, 4) == 4)
|
|
{
|
|
if (bom[0] == 0xef && bom[1] == 0xbb && bom[2] == 0xbf)
|
|
{
|
|
m_text_type = text_file_type::UTF8;
|
|
pos = 3;
|
|
}
|
|
else if (bom[0] == 0x00 && bom[1] == 0x00 && bom[2] == 0xfe && bom[3] == 0xff)
|
|
{
|
|
m_text_type = text_file_type::UTF32BE;
|
|
pos = 4;
|
|
}
|
|
else if (bom[0] == 0xff && bom[1] == 0xfe && bom[2] == 0x00 && bom[3] == 0x00)
|
|
{
|
|
m_text_type = text_file_type::UTF32LE;
|
|
pos = 4;
|
|
}
|
|
else if (bom[0] == 0xfe && bom[1] == 0xff)
|
|
{
|
|
m_text_type = text_file_type::UTF16BE;
|
|
pos = 2;
|
|
}
|
|
else if (bom[0] == 0xff && bom[1] == 0xfe)
|
|
{
|
|
m_text_type = text_file_type::UTF16LE;
|
|
pos = 2;
|
|
}
|
|
else
|
|
{
|
|
m_text_type = text_file_type::OSD;
|
|
pos = 0;
|
|
}
|
|
}
|
|
seek(pos, SEEK_SET);
|
|
}
|
|
|
|
// fetch the next character
|
|
utf16_char utf16_buffer[UTF16_CHAR_MAX];
|
|
unicode_char uchar = unicode_char(~0);
|
|
switch (m_text_type)
|
|
{
|
|
default:
|
|
case text_file_type::OSD:
|
|
{
|
|
char default_buffer[16];
|
|
auto const readlen = read(default_buffer, sizeof(default_buffer));
|
|
if (readlen > 0)
|
|
{
|
|
auto const charlen = osd_uchar_from_osdchar(&uchar, default_buffer, readlen / sizeof(default_buffer[0]));
|
|
seek(std::int64_t(charlen * sizeof(default_buffer[0])) - readlen, SEEK_CUR);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case text_file_type::UTF8:
|
|
{
|
|
char utf8_buffer[UTF8_CHAR_MAX];
|
|
auto const readlen = read(utf8_buffer, sizeof(utf8_buffer));
|
|
if (readlen > 0)
|
|
{
|
|
auto const charlen = uchar_from_utf8(&uchar, utf8_buffer, readlen / sizeof(utf8_buffer[0]));
|
|
seek(std::int64_t(charlen * sizeof(utf8_buffer[0])) - readlen, SEEK_CUR);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case text_file_type::UTF16BE:
|
|
{
|
|
auto const readlen = read(utf16_buffer, sizeof(utf16_buffer));
|
|
if (readlen > 0)
|
|
{
|
|
auto const charlen = uchar_from_utf16be(&uchar, utf16_buffer, readlen / sizeof(utf16_buffer[0]));
|
|
seek(std::int64_t(charlen * sizeof(utf16_buffer[0])) - readlen, SEEK_CUR);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case text_file_type::UTF16LE:
|
|
{
|
|
auto const readlen = read(utf16_buffer, sizeof(utf16_buffer));
|
|
if (readlen > 0)
|
|
{
|
|
auto const charlen = uchar_from_utf16le(&uchar, utf16_buffer, readlen / sizeof(utf16_buffer[0]));
|
|
seek(std::int64_t(charlen * sizeof(utf16_buffer[0])) - readlen, SEEK_CUR);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case text_file_type::UTF32BE:
|
|
if (read(&uchar, sizeof(uchar)) == sizeof(uchar))
|
|
uchar = BIG_ENDIANIZE_INT32(uchar);
|
|
break;
|
|
|
|
case text_file_type::UTF32LE:
|
|
if (read(&uchar, sizeof(uchar)) == sizeof(uchar))
|
|
uchar = LITTLE_ENDIANIZE_INT32(uchar);
|
|
break;
|
|
}
|
|
|
|
if (uchar != ~0)
|
|
{
|
|
// place the new character in the ring buffer
|
|
m_back_char_head = 0;
|
|
m_back_char_tail = utf8_from_uchar(m_back_chars, ARRAY_LENGTH(m_back_chars), uchar);
|
|
//assert(file->back_char_tail != -1);
|
|
}
|
|
}
|
|
|
|
// now read from the ring buffer
|
|
if (m_back_char_head == m_back_char_tail)
|
|
result = EOF;
|
|
else
|
|
{
|
|
result = m_back_chars[m_back_char_head++];
|
|
m_back_char_head %= ARRAY_LENGTH(m_back_chars);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
ungetc - put back a character read from a
|
|
file
|
|
-------------------------------------------------*/
|
|
|
|
int core_text_file::ungetc(int c)
|
|
{
|
|
m_back_chars[m_back_char_tail++] = char(c);
|
|
m_back_char_tail %= ARRAY_LENGTH(m_back_chars);
|
|
return c;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
gets - read a line from a text file
|
|
-------------------------------------------------*/
|
|
|
|
char *core_text_file::gets(char *s, int n)
|
|
{
|
|
char *cur = s;
|
|
|
|
// loop while we have characters
|
|
while (n > 0)
|
|
{
|
|
int const c = getc();
|
|
if (c == EOF)
|
|
break;
|
|
else if (c == 0x0d) // if there's a CR, look for an LF afterwards
|
|
{
|
|
int const c2 = getc();
|
|
if (c2 != 0x0a)
|
|
ungetc(c2);
|
|
*cur++ = 0x0d;
|
|
n--;
|
|
break;
|
|
}
|
|
else if (c == 0x0a) // if there's an LF, reinterp as a CR for consistency
|
|
{
|
|
*cur++ = 0x0d;
|
|
n--;
|
|
break;
|
|
}
|
|
else // otherwise, pop the character in and continue
|
|
{
|
|
*cur++ = c;
|
|
n--;
|
|
}
|
|
}
|
|
|
|
// if we put nothing in, return NULL
|
|
if (cur == s)
|
|
return nullptr;
|
|
|
|
/* otherwise, terminate */
|
|
if (n > 0)
|
|
*cur++ = 0;
|
|
return s;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
puts - write a line to a text file
|
|
-------------------------------------------------*/
|
|
|
|
int core_text_file::puts(char const *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 (tell() == 0 && !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 += write(convbuf, pconvbuf - convbuf);
|
|
pconvbuf = convbuf;
|
|
}
|
|
}
|
|
|
|
// final flush
|
|
if (pconvbuf != convbuf)
|
|
count += write(convbuf, pconvbuf - convbuf);
|
|
|
|
return count;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
vprintf - vfprintf to a text file
|
|
-------------------------------------------------*/
|
|
|
|
int core_text_file::vprintf(char const *fmt, va_list va)
|
|
{
|
|
char buf[1024];
|
|
vsnprintf(buf, sizeof(buf), fmt, va);
|
|
return puts(buf);
|
|
}
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
core_in_memory_file
|
|
***************************************************************************/
|
|
|
|
/*-------------------------------------------------
|
|
seek - seek within a file
|
|
-------------------------------------------------*/
|
|
|
|
int core_in_memory_file::seek(std::int64_t offset, int whence)
|
|
{
|
|
// flush any buffered char
|
|
clear_putback();
|
|
|
|
// switch off the relative location
|
|
switch (whence)
|
|
{
|
|
case SEEK_SET:
|
|
m_offset = offset;
|
|
break;
|
|
|
|
case SEEK_CUR:
|
|
m_offset += offset;
|
|
break;
|
|
|
|
case SEEK_END:
|
|
m_offset = m_length + offset;
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
eof - return 1 if we're at the end of file
|
|
-------------------------------------------------*/
|
|
|
|
bool core_in_memory_file::eof() const
|
|
{
|
|
// check for buffered chars
|
|
if (has_putback())
|
|
return 0;
|
|
|
|
// if the offset == length, we're at EOF
|
|
return (m_offset >= m_length);
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
read - read from a file
|
|
-------------------------------------------------*/
|
|
|
|
std::uint32_t core_in_memory_file::read(void *buffer, std::uint32_t length)
|
|
{
|
|
clear_putback();
|
|
|
|
// handle RAM-based files
|
|
auto const bytes_read = safe_buffer_copy(m_data, std::size_t(m_offset), std::size_t(m_length), buffer, 0, length);
|
|
m_offset += bytes_read;
|
|
return bytes_read;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
truncate - truncate a file
|
|
-------------------------------------------------*/
|
|
|
|
file_error core_in_memory_file::truncate(std::uint64_t offset)
|
|
{
|
|
if (m_length < offset)
|
|
return FILERR_FAILURE;
|
|
|
|
// adjust to new length and offset
|
|
set_length(offset);
|
|
return FILERR_NONE;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
safe_buffer_copy - copy safely from one
|
|
bounded buffer to another
|
|
-------------------------------------------------*/
|
|
|
|
std::size_t core_in_memory_file::safe_buffer_copy(
|
|
void const *source, std::size_t sourceoffs, std::size_t sourcelen,
|
|
void *dest, std::size_t destoffs, std::size_t destlen)
|
|
{
|
|
auto const sourceavail = sourcelen - sourceoffs;
|
|
auto const destavail = destlen - destoffs;
|
|
auto const bytes_to_copy = (std::min)(sourceavail, destavail);
|
|
if (bytes_to_copy > 0)
|
|
{
|
|
std::memcpy(
|
|
reinterpret_cast<std::uint8_t *>(dest) + destoffs,
|
|
reinterpret_cast<std::uint8_t const *>(source) + sourceoffs,
|
|
bytes_to_copy);
|
|
}
|
|
return bytes_to_copy;
|
|
}
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
core_osd_file
|
|
***************************************************************************/
|
|
|
|
/*-------------------------------------------------
|
|
closes a file
|
|
-------------------------------------------------*/
|
|
|
|
core_osd_file::~core_osd_file()
|
|
{
|
|
// close files and free memory
|
|
if (m_zdata)
|
|
compress(FCOMPRESS_NONE);
|
|
if (m_file)
|
|
osd_close(m_file);
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
compress - enable/disable streaming file
|
|
compression via zlib; level is 0 to disable
|
|
compression, or up to 9 for max compression
|
|
-------------------------------------------------*/
|
|
|
|
file_error core_osd_file::compress(int level)
|
|
{
|
|
file_error result = FILERR_NONE;
|
|
|
|
// can only do this for read-only and write-only cases
|
|
if (read_access() && write_access())
|
|
return FILERR_INVALID_ACCESS;
|
|
|
|
// if we have been compressing, flush and free the data
|
|
if (m_zdata && (level == FCOMPRESS_NONE))
|
|
{
|
|
int zerr = Z_OK;
|
|
|
|
// flush any remaining data if we are writing
|
|
while (write_access() != 0 && (zerr != Z_STREAM_END))
|
|
{
|
|
// deflate some more
|
|
zerr = m_zdata->finalise();
|
|
if (zerr != Z_STREAM_END && zerr != Z_OK)
|
|
{
|
|
result = FILERR_INVALID_DATA;
|
|
break;
|
|
}
|
|
|
|
// write the data
|
|
if (m_zdata->has_output())
|
|
{
|
|
std::uint32_t actualdata;
|
|
auto const filerr = osd_write(m_file, m_zdata->buffer_data(), m_zdata->realoffset(), m_zdata->output_size(), &actualdata);
|
|
if (filerr != FILERR_NONE)
|
|
break;
|
|
m_zdata->add_realoffset(actualdata);
|
|
m_zdata->reset_output();
|
|
}
|
|
}
|
|
|
|
// free memory
|
|
m_zdata.reset();
|
|
}
|
|
|
|
// if we are just starting to compress, allocate a new buffer
|
|
if (!m_zdata && (level > FCOMPRESS_NONE))
|
|
{
|
|
int zerr;
|
|
|
|
// initialize the stream and compressor
|
|
if (write_access())
|
|
zerr = zlib_data::start_compression(level, offset(), m_zdata);
|
|
else
|
|
zerr = zlib_data::start_decompression(offset(), m_zdata);
|
|
|
|
// on error, return an error
|
|
if (zerr != Z_OK)
|
|
return FILERR_OUT_OF_MEMORY;
|
|
|
|
// flush buffers
|
|
m_bufferbytes = 0;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
seek - seek within a file
|
|
-------------------------------------------------*/
|
|
|
|
int core_osd_file::seek(std::int64_t offset, int whence)
|
|
{
|
|
// error if compressing
|
|
if (m_zdata)
|
|
return 1;
|
|
else
|
|
return core_in_memory_file::seek(offset, whence);
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
read - read from a file
|
|
-------------------------------------------------*/
|
|
|
|
std::uint32_t core_osd_file::read(void *buffer, std::uint32_t length)
|
|
{
|
|
if (!m_file || is_loaded())
|
|
return core_in_memory_file::read(buffer, length);
|
|
|
|
// flush any buffered char
|
|
clear_putback();
|
|
|
|
std::uint32_t bytes_read = 0;
|
|
|
|
// if we're within the buffer, consume that first
|
|
if (is_buffered())
|
|
bytes_read += safe_buffer_copy(m_buffer, offset() - m_bufferbase, m_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(m_buffer) / 2))
|
|
{
|
|
// read as much as makes sense into the buffer
|
|
m_bufferbase = offset() + bytes_read;
|
|
m_bufferbytes = 0;
|
|
osd_or_zlib_read(m_buffer, m_bufferbase, sizeof(m_buffer), m_bufferbytes);
|
|
|
|
// do a bounded copy from the buffer to the destination
|
|
bytes_read += safe_buffer_copy(m_buffer, 0, m_bufferbytes, buffer, bytes_read, length);
|
|
}
|
|
else
|
|
{
|
|
// read the remainder directly from the file
|
|
std::uint32_t new_bytes_read = 0;
|
|
osd_or_zlib_read(reinterpret_cast<std::uint8_t *>(buffer) + bytes_read, offset() + bytes_read, length - bytes_read, new_bytes_read);
|
|
bytes_read += new_bytes_read;
|
|
}
|
|
}
|
|
|
|
// return the number of bytes read
|
|
add_offset(bytes_read);
|
|
return bytes_read;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
buffer - return a pointer to the file buffer;
|
|
if it doesn't yet exist, load the file into
|
|
RAM first
|
|
-------------------------------------------------*/
|
|
|
|
void const *core_osd_file::buffer()
|
|
{
|
|
// if we already have data, just return it
|
|
if (!is_loaded() && length())
|
|
{
|
|
// allocate some memory
|
|
void *buf = allocate();
|
|
if (!buf) return nullptr;
|
|
|
|
// read the file
|
|
std::uint32_t read_length = 0;
|
|
auto const filerr = osd_or_zlib_read(buf, 0, length(), read_length);
|
|
if ((filerr != FILERR_NONE) || (read_length != length()))
|
|
purge();
|
|
else
|
|
{
|
|
// close the file because we don't need it anymore
|
|
osd_close(m_file);
|
|
m_file = nullptr;
|
|
}
|
|
}
|
|
return core_in_memory_file::buffer();
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
write - write to a file
|
|
-------------------------------------------------*/
|
|
|
|
std::uint32_t core_osd_file::write(void const *buffer, std::uint32_t length)
|
|
{
|
|
// can't write to RAM-based stuff
|
|
if (is_loaded())
|
|
return core_in_memory_file::write(buffer, length);
|
|
|
|
// flush any buffered char
|
|
clear_putback();
|
|
|
|
// invalidate any buffered data
|
|
m_bufferbytes = 0;
|
|
|
|
// do the write
|
|
std::uint32_t bytes_written = 0;
|
|
osd_or_zlib_write(buffer, offset(), length, bytes_written);
|
|
|
|
// return the number of bytes written
|
|
add_offset(bytes_written);
|
|
return bytes_written;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
truncate - truncate a file
|
|
-------------------------------------------------*/
|
|
|
|
file_error core_osd_file::truncate(std::uint64_t offset)
|
|
{
|
|
if (is_loaded())
|
|
return core_in_memory_file::truncate(offset);
|
|
|
|
// truncate file
|
|
auto const err = osd_truncate(m_file, offset);
|
|
if (err != FILERR_NONE)
|
|
return err;
|
|
|
|
// and adjust to new length and offset
|
|
set_length(offset);
|
|
return FILERR_NONE;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
flush - flush file buffers
|
|
-------------------------------------------------*/
|
|
|
|
file_error core_osd_file::flush()
|
|
{
|
|
if (is_loaded())
|
|
return core_in_memory_file::flush();
|
|
|
|
// flush any buffered char
|
|
clear_putback();
|
|
|
|
// invalidate any buffered data
|
|
m_bufferbytes = 0;
|
|
|
|
return osd_fflush(m_file);
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
osd_or_zlib_read - wrapper for osd_read that
|
|
handles zlib-compressed data
|
|
-------------------------------------------------*/
|
|
|
|
file_error core_osd_file::osd_or_zlib_read(void *buffer, std::uint64_t offset, std::uint32_t length, std::uint32_t &actual)
|
|
{
|
|
// if no compression, just pass through
|
|
if (!m_zdata)
|
|
return osd_read(m_file, buffer, offset, length, &actual);
|
|
|
|
// if the offset doesn't match the next offset, fail
|
|
if (m_zdata->is_nextoffset(offset))
|
|
return FILERR_INVALID_ACCESS;
|
|
|
|
// set up the destination
|
|
m_zdata->set_output(buffer, length);
|
|
while (!m_zdata->output_full())
|
|
{
|
|
int zerr = Z_OK;
|
|
|
|
// if we didn't make progress, report an error or the end
|
|
if (m_zdata->has_input())
|
|
zerr = m_zdata->decompress();
|
|
if (zerr != Z_OK)
|
|
{
|
|
actual = length - m_zdata->output_space();
|
|
m_zdata->add_nextoffset(actual);
|
|
return (zerr == Z_STREAM_END) ? FILERR_NONE : FILERR_INVALID_DATA;
|
|
}
|
|
|
|
// fetch more data if needed
|
|
if (!m_zdata->has_input())
|
|
{
|
|
std::uint32_t actualdata;
|
|
auto const filerr = osd_read(m_file, m_zdata->buffer_data(), m_zdata->realoffset(), m_zdata->buffer_size(), &actualdata);
|
|
if (filerr != FILERR_NONE)
|
|
return filerr;
|
|
m_zdata->add_realoffset(actual);
|
|
m_zdata->reset_input(actualdata);
|
|
}
|
|
}
|
|
|
|
// we read everything
|
|
actual = length;
|
|
m_zdata->add_nextoffset(actual);
|
|
return FILERR_NONE;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
osd_or_zlib_write - wrapper for osd_write that
|
|
handles zlib-compressed data
|
|
-------------------------------------------------*/
|
|
|
|
/**
|
|
* @fn file_error osd_or_zlib_write(void const *buffer, std::uint64_t offset, std::uint32_t length, std::uint32_t actual)
|
|
*
|
|
* @brief OSD or zlib write.
|
|
*
|
|
* @param buffer The buffer.
|
|
* @param offset The offset.
|
|
* @param length The length.
|
|
* @param [in,out] actual The actual.
|
|
*
|
|
* @return A file_error.
|
|
*/
|
|
|
|
file_error core_osd_file::osd_or_zlib_write(void const *buffer, std::uint64_t offset, std::uint32_t length, std::uint32_t &actual)
|
|
{
|
|
// if no compression, just pass through
|
|
if (!m_zdata)
|
|
return osd_write(m_file, buffer, offset, length, &actual);
|
|
|
|
// if the offset doesn't match the next offset, fail
|
|
if (!m_zdata->is_nextoffset(offset))
|
|
return FILERR_INVALID_ACCESS;
|
|
|
|
// set up the source
|
|
m_zdata->set_input(buffer, length);
|
|
while (m_zdata->has_input())
|
|
{
|
|
// if we didn't make progress, report an error or the end
|
|
auto const zerr = m_zdata->compress();
|
|
if (zerr != Z_OK)
|
|
{
|
|
actual = length - m_zdata->input_size();
|
|
m_zdata->add_nextoffset(actual);
|
|
return FILERR_INVALID_DATA;
|
|
}
|
|
|
|
// write more data if we are full up
|
|
if (m_zdata->output_full())
|
|
{
|
|
std::uint32_t actualdata;
|
|
auto const filerr = osd_write(m_file, m_zdata->buffer_data(), m_zdata->realoffset(), m_zdata->output_size(), &actualdata);
|
|
if (filerr != FILERR_NONE)
|
|
return filerr;
|
|
m_zdata->add_realoffset(actualdata);
|
|
m_zdata->reset_output();
|
|
}
|
|
}
|
|
|
|
// we wrote everything
|
|
actual = length;
|
|
m_zdata->add_nextoffset(actual);
|
|
return FILERR_NONE;
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
core_file
|
|
***************************************************************************/
|
|
|
|
/*-------------------------------------------------
|
|
open - open a file for access and
|
|
return an error code
|
|
-------------------------------------------------*/
|
|
|
|
file_error core_file::open(char const *filename, std::uint32_t openflags, ptr &file)
|
|
{
|
|
// attempt to open the file
|
|
osd_file *f = nullptr;
|
|
std::uint64_t length = 0;
|
|
auto const filerr = osd_open(filename, openflags, &f, &length);
|
|
if (filerr != FILERR_NONE)
|
|
return filerr;
|
|
|
|
try
|
|
{
|
|
file = std::make_unique<core_osd_file>(openflags, f, length);
|
|
return FILERR_NONE;
|
|
}
|
|
catch (...)
|
|
{
|
|
osd_close(f);
|
|
return FILERR_OUT_OF_MEMORY;
|
|
}
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
open_ram - open a RAM-based buffer for file-
|
|
like access and return an error code
|
|
-------------------------------------------------*/
|
|
|
|
file_error core_file::open_ram(void const *data, std::size_t length, std::uint32_t openflags, ptr &file)
|
|
{
|
|
// can only do this for read access
|
|
if ((openflags & OPEN_FLAG_WRITE) || (openflags & OPEN_FLAG_CREATE))
|
|
return FILERR_INVALID_ACCESS;
|
|
|
|
try
|
|
{
|
|
file.reset(new core_in_memory_file(openflags, data, length, false));
|
|
return FILERR_NONE;
|
|
}
|
|
catch (...)
|
|
{
|
|
return FILERR_OUT_OF_MEMORY;
|
|
}
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
open_ram_copy - open a copy of a RAM-based
|
|
buffer for file-like access and return an
|
|
error code
|
|
-------------------------------------------------*/
|
|
|
|
file_error core_file::open_ram_copy(void const *data, std::size_t length, std::uint32_t openflags, ptr &file)
|
|
{
|
|
// can only do this for read access
|
|
if ((openflags & OPEN_FLAG_WRITE) || (openflags & OPEN_FLAG_CREATE))
|
|
return FILERR_INVALID_ACCESS;
|
|
|
|
try
|
|
{
|
|
ptr result(new core_in_memory_file(openflags, data, length, true));
|
|
if (!result->buffer())
|
|
return FILERR_OUT_OF_MEMORY;
|
|
|
|
return FILERR_NONE;
|
|
}
|
|
catch (...)
|
|
{
|
|
return FILERR_OUT_OF_MEMORY;
|
|
}
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
open_proxy - open a proxy to an existing file
|
|
object and return an error code
|
|
-------------------------------------------------*/
|
|
|
|
file_error core_file::open_proxy(core_file &file, ptr &proxy)
|
|
{
|
|
try
|
|
{
|
|
proxy = std::make_unique<core_proxy_file>(file);
|
|
return FILERR_NONE;
|
|
}
|
|
catch (...)
|
|
{
|
|
return FILERR_OUT_OF_MEMORY;
|
|
}
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
closes a file
|
|
-------------------------------------------------*/
|
|
|
|
core_file::~core_file()
|
|
{
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
load - open a file with the specified
|
|
filename, read it into memory, and return a
|
|
pointer
|
|
-------------------------------------------------*/
|
|
|
|
file_error core_file::load(char const *filename, void **data, std::uint32_t &length)
|
|
{
|
|
ptr file;
|
|
|
|
// attempt to open the file
|
|
auto const err = open(filename, OPEN_FLAG_READ, file);
|
|
if (err != FILERR_NONE)
|
|
return err;
|
|
|
|
// get the size
|
|
auto const size = file->size();
|
|
if (std::uint32_t(size) != size)
|
|
return FILERR_OUT_OF_MEMORY;
|
|
|
|
// allocate memory
|
|
*data = osd_malloc(size);
|
|
length = std::uint32_t(size);
|
|
|
|
// read the data
|
|
if (file->read(*data, size) != size)
|
|
{
|
|
free(*data);
|
|
return FILERR_FAILURE;
|
|
}
|
|
|
|
// close the file and return data
|
|
return FILERR_NONE;
|
|
}
|
|
|
|
file_error core_file::load(char const *filename, dynamic_buffer &data)
|
|
{
|
|
ptr file;
|
|
|
|
// attempt to open the file
|
|
auto const err = open(filename, OPEN_FLAG_READ, file);
|
|
if (err != FILERR_NONE)
|
|
return err;
|
|
|
|
// get the size
|
|
auto const size = file->size();
|
|
if (std::uint32_t(size) != size)
|
|
return FILERR_OUT_OF_MEMORY;
|
|
|
|
// allocate memory
|
|
data.resize(size);
|
|
|
|
// read the data
|
|
if (file->read(&data[0], size) != size)
|
|
{
|
|
data.clear();
|
|
return FILERR_FAILURE;
|
|
}
|
|
|
|
// close the file and return data
|
|
return FILERR_NONE;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
printf - printf to a text file
|
|
-------------------------------------------------*/
|
|
|
|
int CLIB_DECL core_file::printf(char const *fmt, ...)
|
|
{
|
|
va_list va;
|
|
va_start(va, fmt);
|
|
auto const rc = vprintf(fmt, va);
|
|
va_end(va);
|
|
return rc;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
protected constructor
|
|
-------------------------------------------------*/
|
|
|
|
core_file::core_file()
|
|
{
|
|
}
|
|
|
|
} // namespace util
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
FILENAME UTILITIES
|
|
***************************************************************************/
|
|
|
|
/*-------------------------------------------------
|
|
core_filename_extract_base - extract the base
|
|
name from a filename; note that this makes
|
|
assumptions about path separators
|
|
-------------------------------------------------*/
|
|
|
|
std::string core_filename_extract_base(const char *name, bool strip_extension)
|
|
{
|
|
/* find the start of the name */
|
|
const char *start = name + strlen(name);
|
|
while (start > name && !util::is_directory_separator(start[-1]))
|
|
start--;
|
|
|
|
/* copy the rest into an astring */
|
|
std::string result(start);
|
|
|
|
/* chop the extension if present */
|
|
if (strip_extension)
|
|
result = result.substr(0, result.find_last_of('.'));
|
|
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;
|
|
}
|