diff options
Diffstat (limited to 'src/emu/fileio.cpp')
-rw-r--r-- | src/emu/fileio.cpp | 910 |
1 files changed, 910 insertions, 0 deletions
diff --git a/src/emu/fileio.cpp b/src/emu/fileio.cpp new file mode 100644 index 00000000000..68efdb021a3 --- /dev/null +++ b/src/emu/fileio.cpp @@ -0,0 +1,910 @@ +// license:BSD-3-Clause +// copyright-holders:Aaron Giles +/*************************************************************************** + + fileio.c + + File access functions. + +***************************************************************************/ + +#include "emu.h" +#include "unzip.h" +#include "un7z.h" +#include "fileio.h" + + +const UINT32 OPEN_FLAG_HAS_CRC = 0x10000; + + + +//************************************************************************** +// PATH ITERATOR +//************************************************************************** + +//------------------------------------------------- +// path_iterator - constructor +//------------------------------------------------- + +path_iterator::path_iterator(const char *rawsearchpath) + : m_base(rawsearchpath), + m_current(m_base), + m_index(0) +{ +} + + +//------------------------------------------------- +// path_iterator_get_next - get the next entry +// in a multipath sequence +//------------------------------------------------- + +bool path_iterator::next(std::string &buffer, const char *name) +{ + // if none left, return FALSE to indicate we are done + if (m_index != 0 && *m_current == 0) + return false; + + // copy up to the next semicolon + const char *semi = strchr(m_current, ';'); + if (semi == NULL) + semi = m_current + strlen(m_current); + buffer.assign(m_current, semi - m_current); + m_current = (*semi == 0) ? semi : semi + 1; + + // append the name if we have one + if (name != NULL) + { + // compute the full pathname + if (buffer.length() > 0) + buffer.append(PATH_SEPARATOR); + buffer.append(name); + } + + // bump the index and return TRUE + m_index++; + return true; +} + + + +//************************************************************************** +// FILE ENUMERATOR +//************************************************************************** + +//------------------------------------------------- +// file_enumerator - constructor +//------------------------------------------------- + +file_enumerator::file_enumerator(const char *searchpath) + : m_iterator(searchpath), + m_curdir(NULL)/*, + m_buflen(0)*/ +{ +} + + +//------------------------------------------------- +// ~file_enumerator - destructor +//------------------------------------------------- + +file_enumerator::~file_enumerator() +{ + // close anything open + if (m_curdir != NULL) + osd_closedir(m_curdir); +} + + +//------------------------------------------------- +// next - return information about the next file +// in the search path +//------------------------------------------------- + +const osd_directory_entry *file_enumerator::next() +{ + // loop over potentially empty directories + while (1) + { + // if no open directory, get the next path + while (m_curdir == NULL) + { + // if we fail to get anything more, we're done + if (!m_iterator.next(m_pathbuffer)) + return NULL; + + // open the path + m_curdir = osd_opendir(m_pathbuffer.c_str()); + } + + // get the next entry from the current directory + const osd_directory_entry *result = osd_readdir(m_curdir); + if (result != NULL) + return result; + + // we're done; close this directory + osd_closedir(m_curdir); + m_curdir = NULL; + } +} + + + +//************************************************************************** +// EMU FILE +//************************************************************************** + +//------------------------------------------------- +// emu_file - constructor +//------------------------------------------------- + +emu_file::emu_file(UINT32 openflags) + : m_file(NULL), + m_iterator(""), + m_mediapaths(""), + m_crc(0), + m_openflags(openflags), + m_zipfile(NULL), + m_ziplength(0), + m__7zfile(NULL), + m__7zlength(0), + m_remove_on_close(false), + m_restrict_to_mediapath(false) +{ + // sanity check the open flags + if ((m_openflags & OPEN_FLAG_HAS_CRC) && (m_openflags & OPEN_FLAG_WRITE)) + throw emu_fatalerror("Attempted to open a file for write with OPEN_FLAG_HAS_CRC"); +} + +emu_file::emu_file(const char *searchpath, UINT32 openflags) + : m_file(NULL), + m_iterator(searchpath), + m_mediapaths(searchpath), + m_crc(0), + m_openflags(openflags), + m_zipfile(NULL), + m_ziplength(0), + m__7zfile(NULL), + m__7zlength(0), + m_remove_on_close(false), + m_restrict_to_mediapath(false) +{ + // sanity check the open flags + if ((m_openflags & OPEN_FLAG_HAS_CRC) && (m_openflags & OPEN_FLAG_WRITE)) + throw emu_fatalerror("Attempted to open a file for write with OPEN_FLAG_HAS_CRC"); +} + + +//------------------------------------------------- +// ~emu_file - destructor +//------------------------------------------------- + +emu_file::~emu_file() +{ + // close in the standard way + close(); +} + + +//------------------------------------------------- +// operator core_file - automatically convert +// ourselves to a core_file pointer +//------------------------------------------------- + +emu_file::operator core_file *() +{ + // load the ZIP file now if we haven't yet + if (compressed_file_ready()) + return NULL; + + // return the core file + return m_file; +} + +emu_file::operator core_file &() +{ + // load the ZIP file now if we haven't yet + if (compressed_file_ready()) + throw emu_fatalerror("operator core_file & used on invalid file"); + + // return the core file + return *m_file; +} + + +//------------------------------------------------- +// hash - returns the hash for a file +//------------------------------------------------- + +hash_collection &emu_file::hashes(const char *types) +{ + // determine the hashes we already have + std::string already_have; + m_hashes.hash_types(already_have); + + // determine which hashes we need + std::string needed; + for (const char *scan = types; *scan != 0; scan++) + if (already_have.find_first_of(*scan) == -1) + needed.push_back(*scan); + + // if we need nothing, skip it + if (needed.empty()) + return m_hashes; + + // load the ZIP file if needed + if (compressed_file_ready()) + return m_hashes; + if (m_file == NULL) + return m_hashes; + + // if we have ZIP data, just hash that directly + if (!m__7zdata.empty()) + { + m_hashes.compute(&m__7zdata[0], m__7zdata.size(), needed.c_str()); + return m_hashes; + } + + if (!m_zipdata.empty()) + { + m_hashes.compute(&m_zipdata[0], m_zipdata.size(), needed.c_str()); + return m_hashes; + } + + // read the data if we can + const UINT8 *filedata = (const UINT8 *)core_fbuffer(m_file); + if (filedata == NULL) + return m_hashes; + + // compute the hash + m_hashes.compute(filedata, core_fsize(m_file), needed.c_str()); + return m_hashes; +} + + +//------------------------------------------------- +// open - open a file by searching paths +//------------------------------------------------- + +file_error emu_file::open(const char *name) +{ + // remember the filename and CRC info + m_filename = name; + m_crc = 0; + m_openflags &= ~OPEN_FLAG_HAS_CRC; + + // reset the iterator and open_next + m_iterator.reset(); + return open_next(); +} + +file_error emu_file::open(const char *name1, const char *name2) +{ + // concatenate the strings and do a standard open + std::string name = std::string(name1).append(name2); + return open(name.c_str()); +} + +file_error emu_file::open(const char *name1, const char *name2, const char *name3) +{ + // concatenate the strings and do a standard open + std::string name = std::string(name1).append(name2).append(name3); + return open(name.c_str()); +} + +file_error emu_file::open(const char *name1, const char *name2, const char *name3, const char *name4) +{ + // concatenate the strings and do a standard open + std::string name = std::string(name1).append(name2).append(name3).append(name4); + return open(name.c_str()); +} + +file_error emu_file::open(const char *name, UINT32 crc) +{ + // remember the filename and CRC info + m_filename = name; + m_crc = crc; + m_openflags |= OPEN_FLAG_HAS_CRC; + + // reset the iterator and open_next + m_iterator.reset(); + return open_next(); +} + +file_error emu_file::open(const char *name1, const char *name2, UINT32 crc) +{ + // concatenate the strings and do a standard open + std::string name = std::string(name1).append(name2); + return open(name.c_str(), crc); +} + +file_error emu_file::open(const char *name1, const char *name2, const char *name3, UINT32 crc) +{ + // concatenate the strings and do a standard open + std::string name = std::string(name1).append(name2).append(name3); + return open(name.c_str(), crc); +} + +file_error emu_file::open(const char *name1, const char *name2, const char *name3, const char *name4, UINT32 crc) +{ + // concatenate the strings and do a standard open + std::string name = std::string(name1).append(name2).append(name3).append(name4); + return open(name.c_str(), crc); +} + + +//------------------------------------------------- +// open_next - open the next file that matches +// the filename by iterating over paths +//------------------------------------------------- + +file_error emu_file::open_next() +{ + // if we're open from a previous attempt, close up now + if (m_file != NULL) + close(); + + // loop over paths + file_error filerr = FILERR_NOT_FOUND; + while (m_iterator.next(m_fullpath, m_filename.c_str())) + { + // attempt to open the file directly + filerr = core_fopen(m_fullpath.c_str(), m_openflags, &m_file); + if (filerr == FILERR_NONE) + break; + + // if we're opening for read-only we have other options + if ((m_openflags & (OPEN_FLAG_READ | OPEN_FLAG_WRITE)) == OPEN_FLAG_READ) + { + std::string tempfullpath = m_fullpath; + + filerr = attempt_zipped(); + if (filerr == FILERR_NONE) + break; + + m_fullpath = tempfullpath; + + filerr = attempt__7zped(); + if (filerr == FILERR_NONE) + break; + } + } + return filerr; +} + + +//------------------------------------------------- +// open_ram - open a "file" which is actually +// just an array of data in RAM +//------------------------------------------------- + +file_error emu_file::open_ram(const void *data, UINT32 length) +{ + // set a fake filename and CRC + m_filename = "RAM"; + m_crc = 0; + + // use the core_file's built-in RAM support + return core_fopen_ram(data, length, m_openflags, &m_file); +} + + +//------------------------------------------------- +// close - close a file and free all data; also +// remove the file if requested +//------------------------------------------------- + +void emu_file::close() +{ + // close files and free memory + if (m__7zfile != NULL) + _7z_file_close(m__7zfile); + m__7zfile = NULL; + + if (m_zipfile != NULL) + zip_file_close(m_zipfile); + m_zipfile = NULL; + + if (m_file != NULL) + core_fclose(m_file); + m_file = NULL; + + m__7zdata.clear(); + m_zipdata.clear(); + + if (m_remove_on_close) + osd_rmfile(m_fullpath.c_str()); + m_remove_on_close = false; + + // reset our hashes and path as well + m_hashes.reset(); + m_fullpath.clear(); +} + + +//------------------------------------------------- +// compress - enable/disable streaming file +// compression via zlib; level is 0 to disable +// compression, or up to 9 for max compression +//------------------------------------------------- + +file_error emu_file::compress(int level) +{ + return core_fcompress(m_file, level); +} + + +//------------------------------------------------- +// compressed_file_ready - ensure our zip is ready +// loading if needed +//------------------------------------------------- + +bool emu_file::compressed_file_ready(void) +{ + // load the ZIP file now if we haven't yet + if (m__7zfile != NULL && load__7zped_file() != FILERR_NONE) + return true; + + if (m_zipfile != NULL && load_zipped_file() != FILERR_NONE) + return true; + + return false; +} + +//------------------------------------------------- +// seek - seek within a file +//------------------------------------------------- + +int emu_file::seek(INT64 offset, int whence) +{ + // load the ZIP file now if we haven't yet + if (compressed_file_ready()) + return 1; + + // seek if we can + if (m_file != NULL) + return core_fseek(m_file, offset, whence); + + return 1; +} + + +//------------------------------------------------- +// tell - return the current file position +//------------------------------------------------- + +UINT64 emu_file::tell() +{ + // load the ZIP file now if we haven't yet + if (compressed_file_ready()) + return 0; + + // tell if we can + if (m_file != NULL) + return core_ftell(m_file); + + return 0; +} + + +//------------------------------------------------- +// eof - return true if we're at the end of file +//------------------------------------------------- + +bool emu_file::eof() +{ + // load the ZIP file now if we haven't yet + if (compressed_file_ready()) + return 0; + + // return EOF if we can + if (m_file != NULL) + return core_feof(m_file); + + return 0; +} + + +//------------------------------------------------- +// size - returns the size of a file +//------------------------------------------------- + +UINT64 emu_file::size() +{ + // use the ZIP length if present + if (m__7zfile != NULL) + return m__7zlength; + + if (m_zipfile != NULL) + return m_ziplength; + + // return length if we can + if (m_file != NULL) + return core_fsize(m_file); + + return 0; +} + + +//------------------------------------------------- +// read - read from a file +//------------------------------------------------- + +UINT32 emu_file::read(void *buffer, UINT32 length) +{ + // load the ZIP file now if we haven't yet + if (compressed_file_ready()) + return 0; + + // read the data if we can + if (m_file != NULL) + return core_fread(m_file, buffer, length); + + return 0; +} + + +//------------------------------------------------- +// getc - read a character from a file +//------------------------------------------------- + +int emu_file::getc() +{ + // load the ZIP file now if we haven't yet + if (compressed_file_ready()) + return EOF; + + // read the data if we can + if (m_file != NULL) + return core_fgetc(m_file); + + return EOF; +} + + +//------------------------------------------------- +// ungetc - put back a character read from a file +//------------------------------------------------- + +int emu_file::ungetc(int c) +{ + // load the ZIP file now if we haven't yet + if (compressed_file_ready()) + return 1; + + // read the data if we can + if (m_file != NULL) + return core_ungetc(c, m_file); + + return 1; +} + + +//------------------------------------------------- +// gets - read a line from a text file +//------------------------------------------------- + +char *emu_file::gets(char *s, int n) +{ + // load the ZIP file now if we haven't yet + if (compressed_file_ready()) + return NULL; + + // read the data if we can + if (m_file != NULL) + return core_fgets(s, n, m_file); + + return NULL; +} + + +//------------------------------------------------- +// write - write to a file +//------------------------------------------------- + +UINT32 emu_file::write(const void *buffer, UINT32 length) +{ + // write the data if we can + if (m_file != NULL) + return core_fwrite(m_file, buffer, length); + + return 0; +} + + +//------------------------------------------------- +// puts - write a line to a text file +//------------------------------------------------- + +int emu_file::puts(const char *s) +{ + // write the data if we can + if (m_file != NULL) + return core_fputs(m_file, s); + + return 0; +} + + +//------------------------------------------------- +// printf - vfprintf to a text file +//------------------------------------------------- + +int CLIB_DECL emu_file::printf(const char *fmt, ...) +{ + int rc; + va_list va; + va_start(va, fmt); + rc = vprintf(fmt, va); + va_end(va); + return rc; +} + + +//------------------------------------------------- +// mame_vfprintf - vfprintf to a text file +//------------------------------------------------- + +int emu_file::vprintf(const char *fmt, va_list va) +{ + // write the data if we can + return (m_file != NULL) ? core_vfprintf(m_file, fmt, va) : 0; +} + + +//------------------------------------------------- +// part_of_mediapath - checks if 'path' is part of +// any media path +//------------------------------------------------- + +bool emu_file::part_of_mediapath(std::string path) +{ + bool result = false; + std::string mediapath; + m_mediapaths.reset(); + while (m_mediapaths.next(mediapath, NULL) && !result) { + if (path.compare(mediapath.substr(0, mediapath.length()))) + result = true; + } + return result; +} + +//------------------------------------------------- +// attempt_zipped - attempt to open a ZIPped file +//------------------------------------------------- + +file_error emu_file::attempt_zipped() +{ + std::string filename; + + // loop over directory parts up to the start of filename + while (1) + { + // find the final path separator + int dirsep = m_fullpath.find_last_of(PATH_SEPARATOR[0]); + if (dirsep == -1) + return FILERR_NOT_FOUND; + + if (restrict_to_mediapath()) + if ( !part_of_mediapath(m_fullpath) ) + return FILERR_NOT_FOUND; + + // insert the part from the right of the separator into the head of the filename + if (filename.length() > 0) + filename.insert(0, "/"); + filename.insert(0, m_fullpath.substr(dirsep + 1, -1)); + + // remove this part of the filename and append a .zip extension + m_fullpath = m_fullpath.substr(0, dirsep).append(".zip"); + + // attempt to open the ZIP file + zip_file *zip; + zip_error ziperr = zip_file_open(m_fullpath.c_str(), &zip); + + // chop the .zip back off the filename before continuing + m_fullpath = m_fullpath.substr(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 + const zip_file_header *header; + for (header = zip_file_first_file(zip); header != NULL; header = zip_file_next_file(zip)) + if (zip_filename_match(*header, filename) && (!(m_openflags & OPEN_FLAG_HAS_CRC) || header->crc == m_crc)) + break; + + // if that failed, look for a file with the right crc, but the wrong filename + if (header == NULL && (m_openflags & OPEN_FLAG_HAS_CRC)) + for (header = zip_file_first_file(zip); header != NULL; header = zip_file_next_file(zip)) + if (header->crc == m_crc && !zip_header_is_path(*header)) + 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) + { + m_zipfile = zip; + m_ziplength = header->uncompressed_length; + + // build a hash with just the CRC + m_hashes.reset(); + m_hashes.add_crc(header->crc); + return (m_openflags & OPEN_FLAG_NO_PRELOAD) ? FILERR_NONE : load_zipped_file(); + } + + // close up the ZIP file and try the next level + zip_file_close(zip); + } +} + + +//------------------------------------------------- +// load_zipped_file - load a ZIPped file +//------------------------------------------------- + +file_error emu_file::load_zipped_file() +{ + assert(m_file == NULL); + assert(m_zipdata.empty()); + assert(m_zipfile != NULL); + + // allocate some memory + m_zipdata.resize(m_ziplength); + + // read the data into our buffer and return + zip_error ziperr = zip_file_decompress(m_zipfile, &m_zipdata[0], m_zipdata.size()); + if (ziperr != ZIPERR_NONE) + { + m_zipdata.clear(); + return FILERR_FAILURE; + } + + // convert to RAM file + file_error filerr = core_fopen_ram(&m_zipdata[0], m_zipdata.size(), m_openflags, &m_file); + if (filerr != FILERR_NONE) + { + m_zipdata.clear(); + return FILERR_FAILURE; + } + + // close out the ZIP file + zip_file_close(m_zipfile); + m_zipfile = NULL; + return FILERR_NONE; +} + + +//------------------------------------------------- +// zip_filename_match - compare zip filename +// to expected filename, ignoring any directory +//------------------------------------------------- + +bool emu_file::zip_filename_match(const zip_file_header &header, const std::string &filename) +{ + const char *zipfile = header.filename + header.filename_length - filename.length(); + return (zipfile >= header.filename && core_stricmp(filename.c_str(),zipfile) == 0 && (zipfile == header.filename || zipfile[-1] == '/')); +} + + +//------------------------------------------------- +// zip_header_is_path - check whether filename +// in header is a path +//------------------------------------------------- + +bool emu_file::zip_header_is_path(const zip_file_header &header) +{ + const char *zipfile = header.filename + header.filename_length - 1; + return (zipfile >= header.filename && zipfile[0] == '/'); +} + +//------------------------------------------------- +// attempt__7zped - attempt to open a .7z file +//------------------------------------------------- + +file_error emu_file::attempt__7zped() +{ + std::string filename; + + // loop over directory parts up to the start of filename + while (1) + { + // find the final path separator + int dirsep = m_fullpath.find_last_of(PATH_SEPARATOR[0]); + if (dirsep == -1) + return FILERR_NOT_FOUND; + + if (restrict_to_mediapath()) + if ( !part_of_mediapath(m_fullpath) ) + return FILERR_NOT_FOUND; + + // insert the part from the right of the separator into the head of the filename + if (filename.length() > 0) + filename.insert(0, "/"); + filename.insert(0, m_fullpath.substr(dirsep + 1, -1)); + + // remove this part of the filename and append a .7z extension + m_fullpath = m_fullpath.substr(0, dirsep).append(".7z"); + + // attempt to open the _7Z file + _7z_file *_7z; + _7z_error _7zerr = _7z_file_open(m_fullpath.c_str(), &_7z); + + // chop the ._7z back off the filename before continuing + m_fullpath = m_fullpath.substr(0, dirsep); + + // if we failed to open this file, continue scanning + if (_7zerr != _7ZERR_NONE) + continue; + + int fileno = -1; + + // see if we can find a file with the right name and (if available) crc + if (m_openflags & OPEN_FLAG_HAS_CRC) fileno = _7z_search_crc_match(_7z, m_crc, filename.c_str(), filename.length(), true, true); + + // if that failed, look for a file with the right crc, but the wrong filename + if (fileno==-1) + if (m_openflags & OPEN_FLAG_HAS_CRC) fileno = _7z_search_crc_match(_7z, m_crc, filename.c_str(), filename.length(), true, false); + + // 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 (fileno==-1) + fileno = _7z_search_crc_match(_7z, m_crc, filename.c_str(), filename.length(), false, true); + + if (fileno != -1) + { + m__7zfile = _7z; + m__7zlength = _7z->uncompressed_length; + + // build a hash with just the CRC + m_hashes.reset(); + m_hashes.add_crc(_7z->crc); + return (m_openflags & OPEN_FLAG_NO_PRELOAD) ? FILERR_NONE : load__7zped_file(); + } + + // close up the _7Z file and try the next level + _7z_file_close(_7z); + } +} + + +//------------------------------------------------- +// load__7zped_file - load a _7Zped file +//------------------------------------------------- + +file_error emu_file::load__7zped_file() +{ + assert(m_file == NULL); + assert(m__7zdata.empty()); + assert(m__7zfile != NULL); + + // allocate some memory + m__7zdata.resize(m__7zlength); + + // read the data into our buffer and return + _7z_error _7zerr = _7z_file_decompress(m__7zfile, &m__7zdata[0], m__7zdata.size()); + if (_7zerr != _7ZERR_NONE) + { + m__7zdata.clear(); + return FILERR_FAILURE; + } + + // convert to RAM file + file_error filerr = core_fopen_ram(&m__7zdata[0], m__7zdata.size(), m_openflags, &m_file); + if (filerr != FILERR_NONE) + { + m__7zdata.clear(); + return FILERR_FAILURE; + } + + // close out the _7Z file + _7z_file_close(m__7zfile); + m__7zfile = NULL; + return FILERR_NONE; +} |