diff options
| author | 2015-11-08 12:56:12 +0100 | |
|---|---|---|
| committer | 2015-11-08 12:56:12 +0100 | |
| commit | 7c19aac60e12d6f5ea301bdb34d7826a01e0b06f (patch) | |
| tree | f310d86aa2c6bfc19d115307dedde4eb0cd52dad /src/lib/util/chd.c | |
| parent | a57b46ae933badd7441ce1644711dbb851e2b504 (diff) | |
Rename *.c -> *.cpp in our source (nw)
Diffstat (limited to 'src/lib/util/chd.c')
| -rw-r--r-- | src/lib/util/chd.c | 3279 |
1 files changed, 0 insertions, 3279 deletions
diff --git a/src/lib/util/chd.c b/src/lib/util/chd.c deleted file mode 100644 index 07e98d03b85..00000000000 --- a/src/lib/util/chd.c +++ /dev/null @@ -1,3279 +0,0 @@ -// license:BSD-3-Clause -// copyright-holders:Aaron Giles -/*************************************************************************** - - chd.c - - MAME Compressed Hunks of Data file format - -***************************************************************************/ - -#include <assert.h> - -#include "chd.h" -#include "avhuff.h" -#include "hashing.h" -#include "flac.h" -#include "cdrom.h" -#include "coretmpl.h" -#include <zlib.h> -#include <time.h> -#include <stddef.h> -#include <stdlib.h> -#include <new> -#include "eminline.h" - - -//************************************************************************** -// CONSTANTS -//************************************************************************** - -// standard metadata formats -const char *HARD_DISK_METADATA_FORMAT = "CYLS:%d,HEADS:%d,SECS:%d,BPS:%d"; -const char *CDROM_TRACK_METADATA_FORMAT = "TRACK:%d TYPE:%s SUBTYPE:%s FRAMES:%d"; -const char *CDROM_TRACK_METADATA2_FORMAT = "TRACK:%d TYPE:%s SUBTYPE:%s FRAMES:%d PREGAP:%d PGTYPE:%s PGSUB:%s POSTGAP:%d"; -const char *GDROM_TRACK_METADATA_FORMAT = "TRACK:%d TYPE:%s SUBTYPE:%s FRAMES:%d PAD:%d PREGAP:%d PGTYPE:%s PGSUB:%s POSTGAP:%d"; -const char *AV_METADATA_FORMAT = "FPS:%d.%06d WIDTH:%d HEIGHT:%d INTERLACED:%d CHANNELS:%d SAMPLERATE:%d"; - -static const UINT32 METADATA_HEADER_SIZE = 16; // metadata header size - -static const UINT8 V34_MAP_ENTRY_FLAG_TYPE_MASK = 0x0f; // what type of hunk -static const UINT8 V34_MAP_ENTRY_FLAG_NO_CRC = 0x10; // no CRC is present - - - -// V3-V4 entry types -enum -{ - V34_MAP_ENTRY_TYPE_INVALID = 0, // invalid type - V34_MAP_ENTRY_TYPE_COMPRESSED = 1, // standard compression - V34_MAP_ENTRY_TYPE_UNCOMPRESSED = 2, // uncompressed data - V34_MAP_ENTRY_TYPE_MINI = 3, // mini: use offset as raw data - V34_MAP_ENTRY_TYPE_SELF_HUNK = 4, // same as another hunk in this file - V34_MAP_ENTRY_TYPE_PARENT_HUNK = 5, // same as a hunk in the parent file - V34_MAP_ENTRY_TYPE_2ND_COMPRESSED = 6 // compressed with secondary algorithm (usually FLAC CDDA) -}; - -// V5 compression types -enum -{ - ///< codec #0 - // these types are live when running - COMPRESSION_TYPE_0 = 0, - ///< codec #1 - COMPRESSION_TYPE_1 = 1, - ///< codec #2 - COMPRESSION_TYPE_2 = 2, - ///< codec #3 - COMPRESSION_TYPE_3 = 3, - ///< no compression; implicit length = hunkbytes - COMPRESSION_NONE = 4, - ///< same as another block in this chd - COMPRESSION_SELF = 5, - ///< same as a hunk's worth of units in the parent chd - COMPRESSION_PARENT = 6, - - ///< start of small RLE run (4-bit length) - // these additional pseudo-types are used for compressed encodings: - COMPRESSION_RLE_SMALL, - ///< start of large RLE run (8-bit length) - COMPRESSION_RLE_LARGE, - ///< same as the last COMPRESSION_SELF block - COMPRESSION_SELF_0, - ///< same as the last COMPRESSION_SELF block + 1 - COMPRESSION_SELF_1, - ///< same block in the parent - COMPRESSION_PARENT_SELF, - ///< same as the last COMPRESSION_PARENT block - COMPRESSION_PARENT_0, - ///< same as the last COMPRESSION_PARENT block + 1 - COMPRESSION_PARENT_1 -}; - - - -//************************************************************************** -// TYPE DEFINITIONS -//************************************************************************** - -// ======================> metadata_entry - -// description of where a metadata entry lives within the file -struct chd_file::metadata_entry -{ - UINT64 offset; // offset within the file of the header - UINT64 next; // offset within the file of the next header - UINT64 prev; // offset within the file of the previous header - UINT32 length; // length of the metadata - UINT32 metatag; // metadata tag - UINT8 flags; // flag bits -}; - - -// ======================> metadata_hash - -struct chd_file::metadata_hash -{ - UINT8 tag[4]; // tag of the metadata in big-endian - sha1_t sha1; // hash data -}; - - - -//************************************************************************** -// INLINE FUNCTIONS -//************************************************************************** - -//------------------------------------------------- -// be_read - extract a big-endian number from -// a byte buffer -//------------------------------------------------- - -inline UINT64 chd_file::be_read(const UINT8 *base, int numbytes) -{ - UINT64 result = 0; - while (numbytes--) - result = (result << 8) | *base++; - return result; -} - - -//------------------------------------------------- -// be_write - write a big-endian number to a byte -// buffer -//------------------------------------------------- - -inline void chd_file::be_write(UINT8 *base, UINT64 value, int numbytes) -{ - base += numbytes; - while (numbytes--) - { - *--base = value; - value >>= 8; - } -} - - -//------------------------------------------------- -// be_read_sha1 - fetch a sha1_t from a data -// stream in bigendian order -//------------------------------------------------- - -inline sha1_t chd_file::be_read_sha1(const UINT8 *base) -{ - sha1_t result; - memcpy(&result.m_raw[0], base, sizeof(result.m_raw)); - return result; -} - - -//------------------------------------------------- -// be_write_sha1 - write a sha1_t to a data -// stream in bigendian order -//------------------------------------------------- - -inline void chd_file::be_write_sha1(UINT8 *base, sha1_t value) -{ - memcpy(base, &value.m_raw[0], sizeof(value.m_raw)); -} - - -//------------------------------------------------- -// file_read - read from the file at the given -// offset; on failure throw an error -//------------------------------------------------- - -inline void chd_file::file_read(UINT64 offset, void *dest, UINT32 length) -{ - // no file = failure - if (m_file == NULL) - throw CHDERR_NOT_OPEN; - - // seek and read - core_fseek(m_file, offset, SEEK_SET); - UINT32 count = core_fread(m_file, dest, length); - if (count != length) - throw CHDERR_READ_ERROR; -} - - -//------------------------------------------------- -// file_write - write to the file at the given -// offset; on failure throw an error -//------------------------------------------------- - -inline void chd_file::file_write(UINT64 offset, const void *source, UINT32 length) -{ - // no file = failure - if (m_file == NULL) - throw CHDERR_NOT_OPEN; - - // seek and write - core_fseek(m_file, offset, SEEK_SET); - UINT32 count = core_fwrite(m_file, source, length); - if (count != length) - throw CHDERR_WRITE_ERROR; -} - - -//------------------------------------------------- -// file_append - append to the file at the given -// offset, ensuring we start at the given -// alignment; on failure throw an error -//------------------------------------------------- - -inline UINT64 chd_file::file_append(const void *source, UINT32 length, UINT32 alignment) -{ - // no file = failure - if (m_file == NULL) - throw CHDERR_NOT_OPEN; - - // seek to the end and align if necessary - core_fseek(m_file, 0, SEEK_END); - if (alignment != 0) - { - UINT64 offset = core_ftell(m_file); - UINT32 delta = offset % alignment; - if (delta != 0) - { - // pad with 0's from a local buffer - UINT8 buffer[1024]; - memset(buffer, 0, sizeof(buffer)); - delta = alignment - delta; - while (delta != 0) - { - UINT32 bytes_to_write = MIN(sizeof(buffer), delta); - UINT32 count = core_fwrite(m_file, buffer, bytes_to_write); - if (count != bytes_to_write) - throw CHDERR_WRITE_ERROR; - delta -= bytes_to_write; - } - } - } - - // write the real data - UINT64 offset = core_ftell(m_file); - UINT32 count = core_fwrite(m_file, source, length); - if (count != length) - throw CHDERR_READ_ERROR; - return offset; -} - - -//------------------------------------------------- -// bits_for_value - return the number of bits -// necessary to represent all numbers 0..value -//------------------------------------------------- - -inline UINT8 chd_file::bits_for_value(UINT64 value) -{ - UINT8 result = 0; - while (value != 0) - value >>= 1, result++; - return result; -} - - - -//************************************************************************** -// CHD FILE MANAGEMENT -//************************************************************************** - -/** - * @fn chd_file::chd_file() - * - * @brief ------------------------------------------------- - * chd_file - constructor - * -------------------------------------------------. - */ - -chd_file::chd_file() - : m_file(NULL), - m_owns_file(false) -{ - // reset state - memset(m_decompressor, 0, sizeof(m_decompressor)); - close(); -} - -/** - * @fn chd_file::~chd_file() - * - * @brief ------------------------------------------------- - * ~chd_file - destructor - * -------------------------------------------------. - */ - -chd_file::~chd_file() -{ - // close any open files - close(); -} - -/** - * @fn sha1_t chd_file::sha1() - * - * @brief ------------------------------------------------- - * sha1 - return our SHA1 value - * -------------------------------------------------. - * - * @return A sha1_t. - */ - -sha1_t chd_file::sha1() -{ - try - { - // read the big-endian version - UINT8 rawbuf[sizeof(sha1_t)]; - file_read(m_sha1_offset, rawbuf, sizeof(rawbuf)); - return be_read_sha1(rawbuf); - } - catch (chd_error &) - { - // on failure, return NULL - return sha1_t::null; - } -} - -/** - * @fn sha1_t chd_file::raw_sha1() - * - * @brief ------------------------------------------------- - * raw_sha1 - return our raw SHA1 value - * -------------------------------------------------. - * - * @exception CHDERR_UNSUPPORTED_VERSION Thrown when a chderr unsupported version error - * condition occurs. - * - * @return A sha1_t. - */ - -sha1_t chd_file::raw_sha1() -{ - try - { - // determine offset within the file for data-only - if (m_rawsha1_offset == 0) - throw CHDERR_UNSUPPORTED_VERSION; - - // read the big-endian version - UINT8 rawbuf[sizeof(sha1_t)]; - file_read(m_rawsha1_offset, rawbuf, sizeof(rawbuf)); - return be_read_sha1(rawbuf); - } - catch (chd_error &) - { - // on failure, return NULL - return sha1_t::null; - } -} - -/** - * @fn sha1_t chd_file::parent_sha1() - * - * @brief ------------------------------------------------- - * parent_sha1 - return our parent's SHA1 value - * -------------------------------------------------. - * - * @exception CHDERR_UNSUPPORTED_VERSION Thrown when a chderr unsupported version error - * condition occurs. - * - * @return A sha1_t. - */ - -sha1_t chd_file::parent_sha1() -{ - try - { - // determine offset within the file - if (m_parentsha1_offset == 0) - throw CHDERR_UNSUPPORTED_VERSION; - - // read the big-endian version - UINT8 rawbuf[sizeof(sha1_t)]; - file_read(m_parentsha1_offset, rawbuf, sizeof(rawbuf)); - return be_read_sha1(rawbuf); - } - catch (chd_error &) - { - // on failure, return NULL - return sha1_t::null; - } -} - -/** - * @fn chd_error chd_file::hunk_info(UINT32 hunknum, chd_codec_type &compressor, UINT32 &compbytes) - * - * @brief ------------------------------------------------- - * hunk_info - return information about this hunk - * -------------------------------------------------. - * - * @param hunknum The hunknum. - * @param [in,out] compressor The compressor. - * @param [in,out] compbytes The compbytes. - * - * @return A chd_error. - */ - -chd_error chd_file::hunk_info(UINT32 hunknum, chd_codec_type &compressor, UINT32 &compbytes) -{ - // error if invalid - if (hunknum >= m_hunkcount) - return CHDERR_HUNK_OUT_OF_RANGE; - - // get the map pointer - UINT8 *rawmap; - switch (m_version) - { - // v3/v4 map entries - case 3: - case 4: - rawmap = &m_rawmap[16 * hunknum]; - switch (rawmap[15] & V34_MAP_ENTRY_FLAG_TYPE_MASK) - { - case V34_MAP_ENTRY_TYPE_COMPRESSED: - compressor = CHD_CODEC_ZLIB; - compbytes = be_read(&rawmap[12], 2) + (rawmap[14] << 16); - break; - - case V34_MAP_ENTRY_TYPE_UNCOMPRESSED: - compressor = CHD_CODEC_NONE; - compbytes = m_hunkbytes; - break; - - case V34_MAP_ENTRY_TYPE_MINI: - compressor = CHD_CODEC_MINI; - compbytes = 0; - break; - - case V34_MAP_ENTRY_TYPE_SELF_HUNK: - compressor = CHD_CODEC_SELF; - compbytes = 0; - break; - - case V34_MAP_ENTRY_TYPE_PARENT_HUNK: - compressor = CHD_CODEC_PARENT; - compbytes = 0; - break; - } - break; - - // v5 map entries - case 5: - rawmap = &m_rawmap[m_mapentrybytes * hunknum]; - - // uncompressed case - if (!compressed()) - { - if (be_read(&rawmap[0], 4) == 0) - { - compressor = CHD_CODEC_PARENT; - compbytes = 0; - } - else - { - compressor = CHD_CODEC_NONE; - compbytes = m_hunkbytes; - } - break; - } - - // compressed case - switch (rawmap[0]) - { - case COMPRESSION_TYPE_0: - case COMPRESSION_TYPE_1: - case COMPRESSION_TYPE_2: - case COMPRESSION_TYPE_3: - compressor = m_compression[rawmap[0]]; - compbytes = be_read(&rawmap[1], 3); - break; - - case COMPRESSION_NONE: - compressor = CHD_CODEC_NONE; - compbytes = m_hunkbytes; - break; - - case COMPRESSION_SELF: - compressor = CHD_CODEC_SELF; - compbytes = 0; - break; - - case COMPRESSION_PARENT: - compressor = CHD_CODEC_PARENT; - compbytes = 0; - break; - - default: - return CHDERR_UNKNOWN_COMPRESSION; - } - break; - } - return CHDERR_NONE; -} - -/** - * @fn void chd_file::set_raw_sha1(sha1_t rawdata) - * - * @brief ------------------------------------------------- - * set_raw_sha1 - set our SHA1 values - * -------------------------------------------------. - * - * @param rawdata The rawdata. - */ - -void chd_file::set_raw_sha1(sha1_t rawdata) -{ - // create a big-endian version - UINT8 rawbuf[sizeof(sha1_t)]; - be_write_sha1(rawbuf, rawdata); - - // write to the header - UINT64 offset = (m_rawsha1_offset != 0) ? m_rawsha1_offset : m_sha1_offset; - assert(offset != 0); - file_write(offset, rawbuf, sizeof(rawbuf)); - - // if we have a separate rawsha1_offset, update the full sha1 as well - if (m_rawsha1_offset != 0) - metadata_update_hash(); -} - -/** - * @fn void chd_file::set_parent_sha1(sha1_t parent) - * - * @brief ------------------------------------------------- - * set_parent_sha1 - set the parent SHA1 value - * -------------------------------------------------. - * - * @exception CHDERR_INVALID_FILE Thrown when a chderr invalid file error condition occurs. - * - * @param parent The parent. - */ - -void chd_file::set_parent_sha1(sha1_t parent) -{ - // if no file, fail - if (m_file == NULL) - throw CHDERR_INVALID_FILE; - - // create a big-endian version - UINT8 rawbuf[sizeof(sha1_t)]; - be_write_sha1(rawbuf, parent); - - // write to the header - assert(m_parentsha1_offset != 0); - file_write(m_parentsha1_offset, rawbuf, sizeof(rawbuf)); -} - -/** - * @fn chd_error chd_file::create(core_file &file, UINT64 logicalbytes, UINT32 hunkbytes, UINT32 unitbytes, chd_codec_type compression[4]) - * - * @brief ------------------------------------------------- - * create - create a new file with no parent using an existing opened file handle - * -------------------------------------------------. - * - * @param [in,out] file The file. - * @param logicalbytes The logicalbytes. - * @param hunkbytes The hunkbytes. - * @param unitbytes The unitbytes. - * @param compression The compression. - * - * @return A chd_error. - */ - -chd_error chd_file::create(core_file &file, UINT64 logicalbytes, UINT32 hunkbytes, UINT32 unitbytes, chd_codec_type compression[4]) -{ - // make sure we don't already have a file open - if (m_file != NULL) - return CHDERR_ALREADY_OPEN; - - // set the header parameters - m_logicalbytes = logicalbytes; - m_hunkbytes = hunkbytes; - m_unitbytes = unitbytes; - memcpy(m_compression, compression, sizeof(m_compression)); - m_parent = NULL; - - // take ownership of the file - m_file = &file; - m_owns_file = false; - return create_common(); -} - -/** - * @fn chd_error chd_file::create(core_file &file, UINT64 logicalbytes, UINT32 hunkbytes, chd_codec_type compression[4], chd_file &parent) - * - * @brief ------------------------------------------------- - * create - create a new file with a parent using an existing opened file handle - * -------------------------------------------------. - * - * @param [in,out] file The file. - * @param logicalbytes The logicalbytes. - * @param hunkbytes The hunkbytes. - * @param compression The compression. - * @param [in,out] parent The parent. - * - * @return A chd_error. - */ - -chd_error chd_file::create(core_file &file, UINT64 logicalbytes, UINT32 hunkbytes, chd_codec_type compression[4], chd_file &parent) -{ - // make sure we don't already have a file open - if (m_file != NULL) - return CHDERR_ALREADY_OPEN; - - // set the header parameters - m_logicalbytes = logicalbytes; - m_hunkbytes = hunkbytes; - m_unitbytes = parent.unit_bytes(); - memcpy(m_compression, compression, sizeof(m_compression)); - m_parent = &parent; - - // take ownership of the file - m_file = &file; - m_owns_file = false; - return create_common(); -} - -/** - * @fn chd_error chd_file::create(const char *filename, UINT64 logicalbytes, UINT32 hunkbytes, UINT32 unitbytes, chd_codec_type compression[4]) - * - * @brief ------------------------------------------------- - * create - create a new file with no parent using a filename - * -------------------------------------------------. - * - * @param filename Filename of the file. - * @param logicalbytes The logicalbytes. - * @param hunkbytes The hunkbytes. - * @param unitbytes The unitbytes. - * @param compression The compression. - * - * @return A chd_error. - */ - -chd_error chd_file::create(const char *filename, UINT64 logicalbytes, UINT32 hunkbytes, UINT32 unitbytes, chd_codec_type compression[4]) -{ - // make sure we don't already have a file open - if (m_file != NULL) - return CHDERR_ALREADY_OPEN; - - // create the new file - core_file *file = NULL; - file_error filerr = core_fopen(filename, OPEN_FLAG_READ | OPEN_FLAG_WRITE | OPEN_FLAG_CREATE, &file); - if (filerr != FILERR_NONE) - return CHDERR_FILE_NOT_FOUND; - - // create the file normally, then claim the file - chd_error chderr = create(*file, logicalbytes, hunkbytes, unitbytes, compression); - m_owns_file = true; - - // if an error happened, close and delete the file - if (chderr != CHDERR_NONE) - { - core_fclose(file); - osd_rmfile(filename); - } - return chderr; -} - -/** - * @fn chd_error chd_file::create(const char *filename, UINT64 logicalbytes, UINT32 hunkbytes, chd_codec_type compression[4], chd_file &parent) - * - * @brief ------------------------------------------------- - * create - create a new file with a parent using a filename - * -------------------------------------------------. - * - * @param filename Filename of the file. - * @param logicalbytes The logicalbytes. - * @param hunkbytes The hunkbytes. - * @param compression The compression. - * @param [in,out] parent The parent. - * - * @return A chd_error. - */ - -chd_error chd_file::create(const char *filename, UINT64 logicalbytes, UINT32 hunkbytes, chd_codec_type compression[4], chd_file &parent) -{ - // make sure we don't already have a file open - if (m_file != NULL) - return CHDERR_ALREADY_OPEN; - - // create the new file - core_file *file = NULL; - file_error filerr = core_fopen(filename, OPEN_FLAG_READ | OPEN_FLAG_WRITE | OPEN_FLAG_CREATE, &file); - if (filerr != FILERR_NONE) - return CHDERR_FILE_NOT_FOUND; - - // create the file normally, then claim the file - chd_error chderr = create(*file, logicalbytes, hunkbytes, compression, parent); - m_owns_file = true; - - // if an error happened, close and delete the file - if (chderr != CHDERR_NONE) - { - core_fclose(file); - osd_rmfile(filename); - } - return chderr; -} - -/** - * @fn chd_error chd_file::open(const char *filename, bool writeable, chd_file *parent) - * - * @brief ------------------------------------------------- - * open - open an existing file for read or read/write - * -------------------------------------------------. - * - * @param filename Filename of the file. - * @param writeable true if writeable. - * @param [in,out] parent If non-null, the parent. - * - * @return A chd_error. - */ - -chd_error chd_file::open(const char *filename, bool writeable, chd_file *parent) -{ - // make sure we don't already have a file open - if (m_file != NULL) - return CHDERR_ALREADY_OPEN; - - // open the file - UINT32 openflags = writeable ? (OPEN_FLAG_READ | OPEN_FLAG_WRITE) : OPEN_FLAG_READ; - core_file *file = NULL; - file_error filerr = core_fopen(filename, openflags, &file); - if (filerr != FILERR_NONE) - return CHDERR_FILE_NOT_FOUND; - - // now open the CHD - chd_error err = open(*file, writeable, parent); - if (err != CHDERR_NONE) - { - core_fclose(file); - return err; - } - - // we now own this file - m_owns_file = true; - return err; -} - -/** - * @fn chd_error chd_file::open(core_file &file, bool writeable, chd_file *parent) - * - * @brief ------------------------------------------------- - * open - open an existing file for read or read/write - * -------------------------------------------------. - * - * @param [in,out] file The file. - * @param writeable true if writeable. - * @param [in,out] parent If non-null, the parent. - * - * @return A chd_error. - */ - -chd_error chd_file::open(core_file &file, bool writeable, chd_file *parent) -{ - // make sure we don't already have a file open - if (m_file != NULL) - return CHDERR_ALREADY_OPEN; - - // open the file - m_file = &file; - m_owns_file = false; - m_parent = parent; - return open_common(writeable); -} - -/** - * @fn void chd_file::close() - * - * @brief ------------------------------------------------- - * close - close a CHD file for access - * -------------------------------------------------. - */ - -void chd_file::close() -{ - // reset file characteristics - if (m_owns_file && m_file != NULL) - core_fclose(m_file); - m_file = NULL; - m_owns_file = false; - m_allow_reads = false; - m_allow_writes = false; - - // reset core parameters from the header - m_version = HEADER_VERSION; - m_logicalbytes = 0; - m_mapoffset = 0; - m_metaoffset = 0; - m_hunkbytes = 0; - m_hunkcount = 0; - m_unitbytes = 0; - m_unitcount = 0; - memset(m_compression, 0, sizeof(m_compression)); - m_parent = NULL; - m_parent_missing = false; - - // reset key offsets within the header - m_mapoffset_offset = 0; - m_metaoffset_offset = 0; - m_sha1_offset = 0; - m_rawsha1_offset = 0; - m_parentsha1_offset = 0; - - // reset map information - m_mapentrybytes = 0; - m_rawmap.clear(); - - // reset compression management - for (int decompnum = 0; decompnum < ARRAY_LENGTH(m_decompressor); decompnum++) - { - delete m_decompressor[decompnum]; - m_decompressor[decompnum] = NULL; - } - m_compressed.clear(); - - // reset caching - m_cache.clear(); - m_cachehunk = ~0; -} - -/** - * @fn chd_error chd_file::read_hunk(UINT32 hunknum, void *buffer) - * - * @brief ------------------------------------------------- - * read - read a single hunk from the CHD file - * -------------------------------------------------. - * - * @exception CHDERR_NOT_OPEN Thrown when a chderr not open error condition occurs. - * @exception CHDERR_HUNK_OUT_OF_RANGE Thrown when a chderr hunk out of range error - * condition occurs. - * @exception CHDERR_DECOMPRESSION_ERROR Thrown when a chderr decompression error error - * condition occurs. - * @exception CHDERR_REQUIRES_PARENT Thrown when a chderr requires parent error condition - * occurs. - * @exception CHDERR_READ_ERROR Thrown when a chderr read error error condition - * occurs. - * - * @param hunknum The hunknum. - * @param [in,out] buffer If non-null, the buffer. - * - * @return The hunk. - */ - -chd_error chd_file::read_hunk(UINT32 hunknum, void *buffer) -{ - // wrap this for clean reporting - try - { - // punt if no file - if (m_file == NULL) - throw CHDERR_NOT_OPEN; - - // return an error if out of range - if (hunknum >= m_hunkcount) - throw CHDERR_HUNK_OUT_OF_RANGE; - - // get a pointer to the map entry - UINT64 blockoffs; - UINT32 blocklen; - UINT32 blockcrc; - UINT8 *rawmap; - UINT8 *dest = reinterpret_cast<UINT8 *>(buffer); - switch (m_version) - { - // v3/v4 map entries - case 3: - case 4: - rawmap = &m_rawmap[16 * hunknum]; - blockoffs = be_read(&rawmap[0], 8); - blockcrc = be_read(&rawmap[8], 4); - switch (rawmap[15] & V34_MAP_ENTRY_FLAG_TYPE_MASK) - { - case V34_MAP_ENTRY_TYPE_COMPRESSED: - blocklen = be_read(&rawmap[12], 2) + (rawmap[14] << 16); - file_read(blockoffs, &m_compressed[0], blocklen); - m_decompressor[0]->decompress(&m_compressed[0], blocklen, dest, m_hunkbytes); - if (!(rawmap[15] & V34_MAP_ENTRY_FLAG_NO_CRC) && dest != NULL && crc32_creator::simple(dest, m_hunkbytes) != blockcrc) - throw CHDERR_DECOMPRESSION_ERROR; - return CHDERR_NONE; - - case V34_MAP_ENTRY_TYPE_UNCOMPRESSED: - file_read(blockoffs, dest, m_hunkbytes); - if (!(rawmap[15] & V34_MAP_ENTRY_FLAG_NO_CRC) && crc32_creator::simple(dest, m_hunkbytes) != blockcrc) - throw CHDERR_DECOMPRESSION_ERROR; - return CHDERR_NONE; - - case V34_MAP_ENTRY_TYPE_MINI: - be_write(dest, blockoffs, 8); - for (UINT32 bytes = 8; bytes < m_hunkbytes; bytes++) - dest[bytes] = dest[bytes - 8]; - if (!(rawmap[15] & V34_MAP_ENTRY_FLAG_NO_CRC) && crc32_creator::simple(dest, m_hunkbytes) != blockcrc) - throw CHDERR_DECOMPRESSION_ERROR; - return CHDERR_NONE; - - case V34_MAP_ENTRY_TYPE_SELF_HUNK: - return read_hunk(blockoffs, dest); - - case V34_MAP_ENTRY_TYPE_PARENT_HUNK: - if (m_parent_missing) - throw CHDERR_REQUIRES_PARENT; - return m_parent->read_hunk(blockoffs, dest); - } - break; - - // v5 map entries - case 5: - rawmap = &m_rawmap[m_mapentrybytes * hunknum]; - - // uncompressed case - if (!compressed()) - { - blockoffs = UINT64(be_read(rawmap, 4)) * UINT64(m_hunkbytes); - if (blockoffs != 0) - file_read(blockoffs, dest, m_hunkbytes); - else if (m_parent_missing) - throw CHDERR_REQUIRES_PARENT; - else if (m_parent != NULL) - m_parent->read_hunk(hunknum, dest); - else - memset(dest, 0, m_hunkbytes); - return CHDERR_NONE; - } - - // compressed case - blocklen = be_read(&rawmap[1], 3); - blockoffs = be_read(&rawmap[4], 6); - blockcrc = be_read(&rawmap[10], 2); - switch (rawmap[0]) - { - case COMPRESSION_TYPE_0: - case COMPRESSION_TYPE_1: - case COMPRESSION_TYPE_2: - case COMPRESSION_TYPE_3: - file_read(blockoffs, &m_compressed[0], blocklen); - m_decompressor[rawmap[0]]->decompress(&m_compressed[0], blocklen, dest, m_hunkbytes); - if (!m_decompressor[rawmap[0]]->lossy() && dest != NULL && crc16_creator::simple(dest, m_hunkbytes) != blockcrc) - throw CHDERR_DECOMPRESSION_ERROR; - if (m_decompressor[rawmap[0]]->lossy() && crc16_creator::simple(&m_compressed[0], blocklen) != blockcrc) - throw CHDERR_DECOMPRESSION_ERROR; - return CHDERR_NONE; - - case COMPRESSION_NONE: - file_read(blockoffs, dest, m_hunkbytes); - if (crc16_creator::simple(dest, m_hunkbytes) != blockcrc) - throw CHDERR_DECOMPRESSION_ERROR; - return CHDERR_NONE; - - case COMPRESSION_SELF: - return read_hunk(blockoffs, dest); - - case COMPRESSION_PARENT: - if (m_parent_missing) - throw CHDERR_REQUIRES_PARENT; - return m_parent->read_bytes(UINT64(blockoffs) * UINT64(m_parent->unit_bytes()), dest, m_hunkbytes); - } - break; - } - - // if we get here, something was wrong - throw CHDERR_READ_ERROR; - } - - // just return errors - catch (chd_error &err) - { - return err; - } -} - -/** - * @fn chd_error chd_file::write_hunk(UINT32 hunknum, const void *buffer) - * - * @brief ------------------------------------------------- - * write - write a single hunk to the CHD file - * -------------------------------------------------. - * - * @exception CHDERR_NOT_OPEN Thrown when a chderr not open error condition occurs. - * @exception CHDERR_HUNK_OUT_OF_RANGE Thrown when a chderr hunk out of range error - * condition occurs. - * @exception CHDERR_FILE_NOT_WRITEABLE Thrown when a chderr file not writeable error - * condition occurs. - * - * @param hunknum The hunknum. - * @param buffer The buffer. - * - * @return A chd_error. - */ - -chd_error chd_file::write_hunk(UINT32 hunknum, const void *buffer) -{ - // wrap this for clean reporting - try - { - // punt if no file - if (m_file == NULL) - throw CHDERR_NOT_OPEN; - - // return an error if out of range - if (hunknum >= m_hunkcount) - throw CHDERR_HUNK_OUT_OF_RANGE; - - // if not writeable, fail - if (!m_allow_writes) - throw CHDERR_FILE_NOT_WRITEABLE; - - // uncompressed writes only via this interface - if (compressed()) - throw CHDERR_FILE_NOT_WRITEABLE; - - // see if we have allocated the space on disk for this hunk - UINT8 *rawmap = &m_rawmap[hunknum * 4]; - UINT32 rawentry = be_read(rawmap, 4); - - // if not, allocate one now - if (rawentry == 0) - { - // first make sure we need to allocate it - bool all_zeros = true; - const UINT32 *scan = reinterpret_cast<const UINT32 *>(buffer); - for (UINT32 index = 0; index < m_hunkbytes / 4; index++) - if (scan[index] != 0) - { - all_zeros = false; - break; - } - - // if it's all zeros, do nothing more - if (all_zeros) - return CHDERR_NONE; - - // append new data to the end of the file, aligning the first chunk - rawentry = file_append(buffer, m_hunkbytes, m_hunkbytes) / m_hunkbytes; - - // write the map entry back - be_write(rawmap, rawentry, 4); - file_write(m_mapoffset + hunknum * 4, rawmap, 4); - - // update the cached hunk if we just wrote it - if (hunknum == m_cachehunk && buffer != &m_cache[0]) - memcpy(&m_cache[0], buffer, m_hunkbytes); - } - - // otherwise, just overwrite - else - file_write(UINT64(rawentry) * UINT64(m_hunkbytes), buffer, m_hunkbytes); - return CHDERR_NONE; - } - - // just return errors - catch (chd_error &err) - { - return err; - } -} - -/** - * @fn chd_error chd_file::read_units(UINT64 unitnum, void *buffer, UINT32 count) - * - * @brief ------------------------------------------------- - * read_units - read the given number of units from the CHD - * -------------------------------------------------. - * - * @param unitnum The unitnum. - * @param [in,out] buffer If non-null, the buffer. - * @param count Number of. - * - * @return The units. - */ - -chd_error chd_file::read_units(UINT64 unitnum, void *buffer, UINT32 count) -{ - return read_bytes(unitnum * UINT64(m_unitbytes), buffer, count * m_unitbytes); -} - -/** - * @fn chd_error chd_file::write_units(UINT64 unitnum, const void *buffer, UINT32 count) - * - * @brief ------------------------------------------------- - * write_units - write the given number of units to the CHD - * -------------------------------------------------. - * - * @param unitnum The unitnum. - * @param buffer The buffer. - * @param count Number of. - * - * @return A chd_error. - */ - -chd_error chd_file::write_units(UINT64 unitnum, const void *buffer, UINT32 count) -{ - return write_bytes(unitnum * UINT64(m_unitbytes), buffer, count * m_unitbytes); -} - -/** - * @fn chd_error chd_file::read_bytes(UINT64 offset, void *buffer, UINT32 bytes) - * - * @brief ------------------------------------------------- - * read_bytes - read from the CHD at a byte level, using the cache to handle partial - * hunks - * -------------------------------------------------. - * - * @param offset The offset. - * @param [in,out] buffer If non-null, the buffer. - * @param bytes The bytes. - * - * @return The bytes. - */ - -chd_error chd_file::read_bytes(UINT64 offset, void *buffer, UINT32 bytes) -{ - // iterate over hunks - UINT32 first_hunk = offset / m_hunkbytes; - UINT32 last_hunk = (offset + bytes - 1) / m_hunkbytes; - UINT8 *dest = reinterpret_cast<UINT8 *>(buffer); - for (UINT32 curhunk = first_hunk; curhunk <= last_hunk; curhunk++) - { - // determine start/end boundaries - UINT32 startoffs = (curhunk == first_hunk) ? (offset % m_hunkbytes) : 0; - UINT32 endoffs = (curhunk == last_hunk) ? ((offset + bytes - 1) % m_hunkbytes) : (m_hunkbytes - 1); - - // if it's a full block, just read directly from disk unless it's the cached hunk - chd_error err = CHDERR_NONE; - if (startoffs == 0 && endoffs == m_hunkbytes - 1 && curhunk != m_cachehunk) - err = read_hunk(curhunk, dest); - - // otherwise, read from the cache - else - { - if (curhunk != m_cachehunk) - { - err = read_hunk(curhunk, &m_cache[0]); - if (err != CHDERR_NONE) - return err; - m_cachehunk = curhunk; - } - memcpy(dest, &m_cache[startoffs], endoffs + 1 - startoffs); - } - - // handle errors and advance - if (err != CHDERR_NONE) - return err; - dest += endoffs + 1 - startoffs; - } - return CHDERR_NONE; -} - -/** - * @fn chd_error chd_file::write_bytes(UINT64 offset, const void *buffer, UINT32 bytes) - * - * @brief ------------------------------------------------- - * write_bytes - write to the CHD at a byte level, using the cache to handle partial - * hunks - * -------------------------------------------------. - * - * @param offset The offset. - * @param buffer The buffer. - * @param bytes The bytes. - * - * @return A chd_error. - */ - -chd_error chd_file::write_bytes(UINT64 offset, const void *buffer, UINT32 bytes) -{ - // iterate over hunks - UINT32 first_hunk = offset / m_hunkbytes; - UINT32 last_hunk = (offset + bytes - 1) / m_hunkbytes; - const UINT8 *source = reinterpret_cast<const UINT8 *>(buffer); - for (UINT32 curhunk = first_hunk; curhunk <= last_hunk; curhunk++) - { - // determine start/end boundaries - UINT32 startoffs = (curhunk == first_hunk) ? (offset % m_hunkbytes) : 0; - UINT32 endoffs = (curhunk == last_hunk) ? ((offset + bytes - 1) % m_hunkbytes) : (m_hunkbytes - 1); - - // if it's a full block, just write directly to disk unless it's the cached hunk - chd_error err = CHDERR_NONE; - if (startoffs == 0 && endoffs == m_hunkbytes - 1 && curhunk != m_cachehunk) - err = write_hunk(curhunk, source); - - // otherwise, write from the cache - else - { - if (curhunk != m_cachehunk) - { - err = read_hunk(curhunk, &m_cache[0]); - if (err != CHDERR_NONE) - return err; - m_cachehunk = curhunk; - } - memcpy(&m_cache[startoffs], source, endoffs + 1 - startoffs); - err = write_hunk(curhunk, &m_cache[0]); - } - - // handle errors and advance - if (err != CHDERR_NONE) - return err; - source += endoffs + 1 - startoffs; - } - return CHDERR_NONE; -} - -/** - * @fn chd_error chd_file::read_metadata(chd_metadata_tag searchtag, UINT32 searchindex, std::string &output) - * - * @brief ------------------------------------------------- - * read_metadata - read the indexed metadata of the given type - * -------------------------------------------------. - * - * @exception CHDERR_METADATA_NOT_FOUND Thrown when a chderr metadata not found error - * condition occurs. - * - * @param searchtag The searchtag. - * @param searchindex The searchindex. - * @param [in,out] output The output. - * - * @return The metadata. - */ - -chd_error chd_file::read_metadata(chd_metadata_tag searchtag, UINT32 searchindex, std::string &output) -{ - // wrap this for clean reporting - try - { - // if we didn't find it, just return - metadata_entry metaentry; - if (!metadata_find(searchtag, searchindex, metaentry)) - throw CHDERR_METADATA_NOT_FOUND; - - // read the metadata - // TODO: how to properly allocate a dynamic char buffer? - char* metabuf = new char[metaentry.length+1]; - memset(metabuf, 0x00, metaentry.length+1); - file_read(metaentry.offset + METADATA_HEADER_SIZE, metabuf, metaentry.length); - output.assign(metabuf); - delete[] metabuf; - return CHDERR_NONE; - } - - // just return errors - catch (chd_error &err) - { - return err; - } -} - -/** - * @fn chd_error chd_file::read_metadata(chd_metadata_tag searchtag, UINT32 searchindex, dynamic_buffer &output) - * - * @brief Reads a metadata. - * - * @exception CHDERR_METADATA_NOT_FOUND Thrown when a chderr metadata not found error - * condition occurs. - * - * @param searchtag The searchtag. - * @param searchindex The searchindex. - * @param [in,out] output The output. - * - * @return The metadata. - */ - -chd_error chd_file::read_metadata(chd_metadata_tag searchtag, UINT32 searchindex, dynamic_buffer &output) -{ - // wrap this for clean reporting - try - { - // if we didn't find it, just return - metadata_entry metaentry; - if (!metadata_find(searchtag, searchindex, metaentry)) - throw CHDERR_METADATA_NOT_FOUND; - - // read the metadata - output.resize(metaentry.length); - file_read(metaentry.offset + METADATA_HEADER_SIZE, &output[0], metaentry.length); - return CHDERR_NONE; - } - - // just return errors - catch (chd_error &err) - { - return err; - } -} - -/** - * @fn chd_error chd_file::read_metadata(chd_metadata_tag searchtag, UINT32 searchindex, void *output, UINT32 outputlen, UINT32 &resultlen) - * - * @brief Reads a metadata. - * - * @exception CHDERR_METADATA_NOT_FOUND Thrown when a chderr metadata not found error - * condition occurs. - * - * @param searchtag The searchtag. - * @param searchindex The searchindex. - * @param [in,out] output If non-null, the output. - * @param outputlen The outputlen. - * @param [in,out] resultlen The resultlen. - * - * @return The metadata. - */ - -chd_error chd_file::read_metadata(chd_metadata_tag searchtag, UINT32 searchindex, void *output, UINT32 outputlen, UINT32 &resultlen) -{ - // wrap this for clean reporting - try - { - // if we didn't find it, just return - metadata_entry metaentry; - if (!metadata_find(searchtag, searchindex, metaentry)) - throw CHDERR_METADATA_NOT_FOUND; - - // read the metadata - resultlen = metaentry.length; - file_read(metaentry.offset + METADATA_HEADER_SIZE, output, MIN(outputlen, resultlen)); - return CHDERR_NONE; - } - - // just return errors - catch (chd_error &err) - { - return err; - } -} - -/** - * @fn chd_error chd_file::read_metadata(chd_metadata_tag searchtag, UINT32 searchindex, dynamic_buffer &output, chd_metadata_tag &resulttag, UINT8 &resultflags) - * - * @brief Reads a metadata. - * - * @exception CHDERR_METADATA_NOT_FOUND Thrown when a chderr metadata not found error - * condition occurs. - * - * @param searchtag The searchtag. - * @param searchindex The searchindex. - * @param [in,out] output The output. - * @param [in,out] resulttag The resulttag. - * @param [in,out] resultflags The resultflags. - * - * @return The metadata. - */ - -chd_error chd_file::read_metadata(chd_metadata_tag searchtag, UINT32 searchindex, dynamic_buffer &output, chd_metadata_tag &resulttag, UINT8 &resultflags) -{ - // wrap this for clean reporting - try - { - // if we didn't find it, just return - metadata_entry metaentry; - if (!metadata_find(searchtag, searchindex, metaentry)) - throw CHDERR_METADATA_NOT_FOUND; - - // read the metadata - output.resize(metaentry.length); - file_read(metaentry.offset + METADATA_HEADER_SIZE, &output[0], metaentry.length); - resulttag = metaentry.metatag; - resultflags = metaentry.flags; - return CHDERR_NONE; - } - - // just return errors - catch (chd_error &err) - { - return err; - } -} - -/** - * @fn chd_error chd_file::write_metadata(chd_metadata_tag metatag, UINT32 metaindex, const void *inputbuf, UINT32 inputlen, UINT8 flags) - * - * @brief ------------------------------------------------- - * write_metadata - write the indexed metadata of the given type - * -------------------------------------------------. - * - * @param metatag The metatag. - * @param metaindex The metaindex. - * @param inputbuf The inputbuf. - * @param inputlen The inputlen. - * @param flags The flags. - * - * @return A chd_error. - */ - -chd_error chd_file::write_metadata(chd_metadata_tag metatag, UINT32 metaindex, const void *inputbuf, UINT32 inputlen, UINT8 flags) -{ - // wrap this for clean reporting - try - { - // must write at least 1 byte and no more than 16MB - if (inputlen < 1 || inputlen >= 16 * 1024 * 1024) - return CHDERR_INVALID_PARAMETER; - - // find the entry if it already exists - metadata_entry metaentry; - bool finished = false; - if (metadata_find(metatag, metaindex, metaentry)) - { - // if the new data fits over the old data, just overwrite - if (inputlen <= metaentry.length) - { - file_write(metaentry.offset + METADATA_HEADER_SIZE, inputbuf, inputlen); - - // if the lengths don't match, we need to update the length in our header - if (inputlen != metaentry.length) - { - UINT8 length[3]; - be_write(length, inputlen, 3); - file_write(metaentry.offset + 5, length, sizeof(length)); - } - - // indicate we did everything - finished = true; - } - - // if it doesn't fit, unlink the current entry - else - metadata_set_previous_next(metaentry.prev, metaentry.next); - } - - // if not yet done, create a new entry and append - if (!finished) - { - // now build us a new entry - UINT8 raw_meta_header[METADATA_HEADER_SIZE]; - be_write(&raw_meta_header[0], metatag, 4); - raw_meta_header[4] = flags; - be_write(&raw_meta_header[5], (inputlen & 0x00ffffff) | (flags << 24), 3); - be_write(&raw_meta_header[8], 0, 8); - - // append the new header, then the data - UINT64 offset = file_append(raw_meta_header, sizeof(raw_meta_header)); - file_append(inputbuf, inputlen); - - // set the previous entry to point to us - metadata_set_previous_next(metaentry.prev, offset); - } - - // update the hash - metadata_update_hash(); - return CHDERR_NONE; - } - - // return any errors - catch (chd_error &err) - { - return err; - } -} - -/** - * @fn chd_error chd_file::delete_metadata(chd_metadata_tag metatag, UINT32 metaindex) - * - * @brief ------------------------------------------------- - * delete_metadata - remove the given metadata from the list - * -------------------------------------------------. - * - * @exception CHDERR_METADATA_NOT_FOUND Thrown when a chderr metadata not found error - * condition occurs. - * - * @param metatag The metatag. - * @param metaindex The metaindex. - * - * @return A chd_error. - */ - -chd_error chd_file::delete_metadata(chd_metadata_tag metatag, UINT32 metaindex) -{ - // wrap this for clean reporting - try - { - // find the entry - metadata_entry metaentry; - if (!metadata_find(metatag, metaindex, metaentry)) - throw CHDERR_METADATA_NOT_FOUND; - - // point the previous to the next, unlinking us - metadata_set_previous_next(metaentry.prev, metaentry.next); - return CHDERR_NONE; - } - - // return any errors - catch (chd_error &err) - { - return err; - } -} - -/** - * @fn chd_error chd_file::clone_all_metadata(chd_file &source) - * - * @brief ------------------------------------------------- - * clone_all_metadata - clone the metadata from one CHD to a second - * -------------------------------------------------. - * - * @exception err Thrown when an error error condition occurs. - * - * @param [in,out] source Another instance to copy. - * - * @return A chd_error. - */ - -chd_error chd_file::clone_all_metadata(chd_file &source) -{ - // wrap this for clean reporting - try - { - // iterate over metadata entries in the source - dynamic_buffer filedata; - metadata_entry metaentry; - metaentry.metatag = 0; - metaentry.length = 0; - metaentry.next = 0; - metaentry.flags = 0; - for (bool has_data = source.metadata_find(CHDMETATAG_WILDCARD, 0, metaentry); has_data; has_data = source.metadata_find(CHDMETATAG_WILDCARD, 0, metaentry, true)) - { - // read the metadata item - filedata.resize(metaentry.length); - source.file_read(metaentry.offset + METADATA_HEADER_SIZE, &filedata[0], metaentry.length); - - // write it to the destination - chd_error err = write_metadata(metaentry.metatag, (UINT32)-1, &filedata[0], metaentry.length, metaentry.flags); - if (err != CHDERR_NONE) - throw err; - } - return CHDERR_NONE; - } - - // return any errors - catch (chd_error &err) - { - return err; - } -} - -/** - * @fn sha1_t chd_file::compute_overall_sha1(sha1_t rawsha1) - * - * @brief ------------------------------------------------- - * compute_overall_sha1 - iterate through the metadata and compute the overall hash of - * the CHD file - * -------------------------------------------------. - * - * @param rawsha1 The first rawsha. - * - * @return The calculated overall sha 1. - */ - -sha1_t chd_file::compute_overall_sha1(sha1_t rawsha1) -{ - // only works for v4 and above - if (m_version < 4) - return rawsha1; - - // iterate over metadata - dynamic_buffer filedata; - std::vector<metadata_hash> hasharray; - metadata_entry metaentry; - for (bool has_data = metadata_find(CHDMETATAG_WILDCARD, 0, metaentry); has_data; has_data = metadata_find(CHDMETATAG_WILDCARD, 0, metaentry, true)) - { - // if not checksumming, continue - if ((metaentry.flags & CHD_MDFLAGS_CHECKSUM) == 0) - continue; - - // allocate memory and read the data - filedata.resize(metaentry.length); - file_read(metaentry.offset + METADATA_HEADER_SIZE, &filedata[0], metaentry.length); - - // create an entry for this metadata and add it - metadata_hash hashentry; - be_write(hashentry.tag, metaentry.metatag, 4); - hashentry.sha1 = sha1_creator::simple(&filedata[0], metaentry.length); - hasharray.push_back(hashentry); - } - - // sort the array - if (!hasharray.empty()) - qsort(&hasharray[0], hasharray.size(), sizeof(hasharray[0]), metadata_hash_compare); - - // read the raw data hash from our header and start a new SHA1 with that data - sha1_creator overall_sha1; - overall_sha1.append(&rawsha1, sizeof(rawsha1)); - if (!hasharray.empty()) - overall_sha1.append(&hasharray[0], hasharray.size() * sizeof(hasharray[0])); - return overall_sha1.finish(); -} - -/** - * @fn chd_error chd_file::codec_configure(chd_codec_type codec, int param, void *config) - * - * @brief ------------------------------------------------- - * codec_config - set internal codec parameters - * -------------------------------------------------. - * - * @param codec The codec. - * @param param The parameter. - * @param [in,out] config If non-null, the configuration. - * - * @return A chd_error. - */ - -chd_error chd_file::codec_configure(chd_codec_type codec, int param, void *config) -{ - // wrap this for clean reporting - try - { - // find the codec and call its configuration - for (int codecnum = 0; codecnum < ARRAY_LENGTH(m_compression); codecnum++) - if (m_compression[codecnum] == codec) - { - m_decompressor[codecnum]->configure(param, config); - return CHDERR_NONE; - } - return CHDERR_INVALID_PARAMETER; - } - - // return any errors - catch (chd_error &err) - { - return err; - } -} - -/** - * @fn const char *chd_file::error_string(chd_error err) - * - * @brief ------------------------------------------------- - * error_string - return an error string for the given CHD error - * -------------------------------------------------. - * - * @param err The error. - * - * @return null if it fails, else a char*. - */ - -const char *chd_file::error_string(chd_error err) -{ - switch (err) - { - case CHDERR_NONE: return "no error"; - case CHDERR_NO_INTERFACE: return "no drive interface"; - case CHDERR_OUT_OF_MEMORY: return "out of memory"; - case CHDERR_NOT_OPEN: return "file not open"; - case CHDERR_ALREADY_OPEN: return "file already open"; - case CHDERR_INVALID_FILE: return "invalid file"; - case CHDERR_INVALID_PARAMETER: return "invalid parameter"; - case CHDERR_INVALID_DATA: return "invalid data"; - case CHDERR_FILE_NOT_FOUND: return "file not found"; - case CHDERR_REQUIRES_PARENT: return "requires parent"; - case CHDERR_FILE_NOT_WRITEABLE: return "file not writeable"; - case CHDERR_READ_ERROR: return "read error"; - case CHDERR_WRITE_ERROR: return "write error"; - case CHDERR_CODEC_ERROR: return "codec error"; - case CHDERR_INVALID_PARENT: return "invalid parent"; - case CHDERR_HUNK_OUT_OF_RANGE: return "hunk out of range"; - case CHDERR_DECOMPRESSION_ERROR: return "decompression error"; - case CHDERR_COMPRESSION_ERROR: return "compression error"; - case CHDERR_CANT_CREATE_FILE: return "can't create file"; - case CHDERR_CANT_VERIFY: return "can't verify file"; - case CHDERR_NOT_SUPPORTED: return "operation not supported"; - case CHDERR_METADATA_NOT_FOUND: return "can't find metadata"; - case CHDERR_INVALID_METADATA_SIZE: return "invalid metadata size"; - case CHDERR_UNSUPPORTED_VERSION: return "mismatched DIFF and CHD or unsupported CHD version"; - case CHDERR_VERIFY_INCOMPLETE: return "incomplete verify"; - case CHDERR_INVALID_METADATA: return "invalid metadata"; - case CHDERR_INVALID_STATE: return "invalid state"; - case CHDERR_OPERATION_PENDING: return "operation pending"; - case CHDERR_UNSUPPORTED_FORMAT: return "unsupported format"; - case CHDERR_UNKNOWN_COMPRESSION: return "unknown compression type"; - case CHDERR_WALKING_PARENT: return "currently examining parent"; - case CHDERR_COMPRESSING: return "currently compressing"; - default: return "undocumented error"; - } -} - - - -//************************************************************************** -// INTERNAL HELPERS -//************************************************************************** - -/** - * @fn UINT32 chd_file::guess_unitbytes() - * - * @brief ------------------------------------------------- - * guess_unitbytes - for older CHD formats, take a guess at the bytes/unit based on - * metadata - * -------------------------------------------------. - * - * @return An UINT32. - */ - -UINT32 chd_file::guess_unitbytes() -{ - // look for hard disk metadata; if found, then the unit size == sector size - std::string metadata; - int i0, i1, i2, i3; - if (read_metadata(HARD_DISK_METADATA_TAG, 0, metadata) == CHDERR_NONE && sscanf(metadata.c_str(), HARD_DISK_METADATA_FORMAT, &i0, &i1, &i2, &i3) == 4) - return i3; - - // look for CD-ROM metadata; if found, then the unit size == CD frame size - if (read_metadata(CDROM_OLD_METADATA_TAG, 0, metadata) == CHDERR_NONE || - read_metadata(CDROM_TRACK_METADATA_TAG, 0, metadata) == CHDERR_NONE || - read_metadata(CDROM_TRACK_METADATA2_TAG, 0, metadata) == CHDERR_NONE || - read_metadata(GDROM_OLD_METADATA_TAG, 0, metadata) == CHDERR_NONE || - read_metadata(GDROM_TRACK_METADATA_TAG, 0, metadata) == CHDERR_NONE) - return CD_FRAME_SIZE; - - // otherwise, just map 1:1 with the hunk size - return m_hunkbytes; -} - -/** - * @fn void chd_file::parse_v3_header(UINT8 *rawheader, sha1_t &parentsha1) - * - * @brief ------------------------------------------------- - * parse_v3_header - parse the header from a v3 file and configure core parameters - * -------------------------------------------------. - * - * @exception CHDERR_INVALID_FILE Thrown when a chderr invalid file error condition - * occurs. - * @exception CHDERR_UNKNOWN_COMPRESSION Thrown when a chderr unknown compression error - * condition occurs. - * - * @param [in,out] rawheader If non-null, the rawheader. - * @param [in,out] parentsha1 The first parentsha. - */ - -void chd_file::parse_v3_header(UINT8 *rawheader, sha1_t &parentsha1) -{ - // verify header length - if (be_read(&rawheader[8], 4) != V3_HEADER_SIZE) - throw CHDERR_INVALID_FILE; - - // extract core info - m_logicalbytes = be_read(&rawheader[28], 8); - m_mapoffset = 120; - m_metaoffset = be_read(&rawheader[36], 8); - m_hunkbytes = be_read(&rawheader[76], 4); - m_hunkcount = be_read(&rawheader[24], 4); - - // extract parent SHA-1 - UINT32 flags = be_read(&rawheader[16], 4); - m_allow_writes = (flags & 2) == 0; - - // determine compression - switch (be_read(&rawheader[20], 4)) - { - case 0: m_compression[0] = CHD_CODEC_NONE; break; - case 1: m_compression[0] = CHD_CODEC_ZLIB; break; - case 2: m_compression[0] = CHD_CODEC_ZLIB; break; - case 3: m_compression[0] = CHD_CODEC_AVHUFF; break; - default: throw CHDERR_UNKNOWN_COMPRESSION; - } - m_compression[1] = m_compression[2] = m_compression[3] = CHD_CODEC_NONE; - - // describe the format - m_mapoffset_offset = 0; - m_metaoffset_offset = 36; - m_sha1_offset = 80; - m_rawsha1_offset = 0; - m_parentsha1_offset = 100; - - // determine properties of map entries - m_mapentrybytes = 16; - - // extract parent SHA-1 - if (flags & 1) - parentsha1 = be_read_sha1(&rawheader[m_parentsha1_offset]); - - // guess at the units based on snooping the metadata - m_unitbytes = guess_unitbytes(); - m_unitcount = (m_logicalbytes + m_unitbytes - 1) / m_unitbytes; -} - -/** - * @fn void chd_file::parse_v4_header(UINT8 *rawheader, sha1_t &parentsha1) - * - * @brief ------------------------------------------------- - * parse_v4_header - parse the header from a v4 file and configure core parameters - * -------------------------------------------------. - * - * @exception CHDERR_INVALID_FILE Thrown when a chderr invalid file error condition - * occurs. - * @exception CHDERR_UNKNOWN_COMPRESSION Thrown when a chderr unknown compression error - * condition occurs. - * - * @param [in,out] rawheader If non-null, the rawheader. - * @param [in,out] parentsha1 The first parentsha. - */ - -void chd_file::parse_v4_header(UINT8 *rawheader, sha1_t &parentsha1) -{ - // verify header length - if (be_read(&rawheader[8], 4) != V4_HEADER_SIZE) - throw CHDERR_INVALID_FILE; - - // extract core info - m_logicalbytes = be_read(&rawheader[28], 8); - m_mapoffset = 108; - m_metaoffset = be_read(&rawheader[36], 8); - m_hunkbytes = be_read(&rawheader[44], 4); - m_hunkcount = be_read(&rawheader[24], 4); - - // extract parent SHA-1 - UINT32 flags = be_read(&rawheader[16], 4); - m_allow_writes = (flags & 2) == 0; - - // determine compression - switch (be_read(&rawheader[20], 4)) - { - case 0: m_compression[0] = CHD_CODEC_NONE; break; - case 1: m_compression[0] = CHD_CODEC_ZLIB; break; - case 2: m_compression[0] = CHD_CODEC_ZLIB; break; - case 3: m_compression[0] = CHD_CODEC_AVHUFF; break; - default: throw CHDERR_UNKNOWN_COMPRESSION; - } - m_compression[1] = m_compression[2] = m_compression[3] = CHD_CODEC_NONE; - - // describe the format - m_mapoffset_offset = 0; - m_metaoffset_offset = 36; - m_sha1_offset = 48; - m_rawsha1_offset = 88; - m_parentsha1_offset = 68; - - // determine properties of map entries - m_mapentrybytes = 16; - - // extract parent SHA-1 - if (flags & 1) - parentsha1 = be_read_sha1(&rawheader[m_parentsha1_offset]); - - // guess at the units based on snooping the metadata - m_unitbytes = guess_unitbytes(); - m_unitcount = (m_logicalbytes + m_unitbytes - 1) / m_unitbytes; -} - -/** - * @fn void chd_file::parse_v5_header(UINT8 *rawheader, sha1_t &parentsha1) - * - * @brief ------------------------------------------------- - * parse_v5_header - read the header from a v5 file and configure core parameters - * -------------------------------------------------. - * - * @exception CHDERR_INVALID_FILE Thrown when a chderr invalid file error condition occurs. - * - * @param [in,out] rawheader If non-null, the rawheader. - * @param [in,out] parentsha1 The first parentsha. - */ - -void chd_file::parse_v5_header(UINT8 *rawheader, sha1_t &parentsha1) -{ - // verify header length - if (be_read(&rawheader[8], 4) != V5_HEADER_SIZE) - throw CHDERR_INVALID_FILE; - - // extract core info - m_logicalbytes = be_read(&rawheader[32], 8); - m_mapoffset = be_read(&rawheader[40], 8); - m_metaoffset = be_read(&rawheader[48], 8); - m_hunkbytes = be_read(&rawheader[56], 4); - m_hunkcount = (m_logicalbytes + m_hunkbytes - 1) / m_hunkbytes; - m_unitbytes = be_read(&rawheader[60], 4); - m_unitcount = (m_logicalbytes + m_unitbytes - 1) / m_unitbytes; - - // determine compression - m_compression[0] = be_read(&rawheader[16], 4); - m_compression[1] = be_read(&rawheader[20], 4); - m_compression[2] = be_read(&rawheader[24], 4); - m_compression[3] = be_read(&rawheader[28], 4); - - m_allow_writes = !compressed(); - - // describe the format - m_mapoffset_offset = 40; - m_metaoffset_offset = 48; - m_sha1_offset = 84; - m_rawsha1_offset = 64; - m_parentsha1_offset = 104; - - // determine properties of map entries - m_mapentrybytes = compressed() ? 12 : 4; - - // extract parent SHA-1 - parentsha1 = be_read_sha1(&rawheader[m_parentsha1_offset]); -} - -/** - * @fn chd_error chd_file::compress_v5_map() - * - * @brief ------------------------------------------------- - * compress_v5_map - compress the v5 map and write it to the end of the file - * -------------------------------------------------. - * - * @exception CHDERR_COMPRESSION_ERROR Thrown when a chderr compression error error - * condition occurs. - * - * @return A chd_error. - */ - -chd_error chd_file::compress_v5_map() -{ - try - { - // first get a CRC-16 of the original rawmap - crc16_t mapcrc = crc16_creator::simple(&m_rawmap[0], m_hunkcount * 12); - - // create a buffer to hold the RLE data - dynamic_buffer compression_rle(m_hunkcount); - UINT8 *dest = &compression_rle[0]; - - // use a huffman encoder for 16 different codes, maximum length is 8 bits - huffman_encoder<16, 8> encoder; - encoder.histo_reset(); - - // RLE-compress the compression type since we expect runs of the same - UINT32 max_self = 0; - UINT32 last_self = 0; - UINT64 max_parent = 0; - UINT64 last_parent = 0; - UINT32 max_complen = 0; - UINT8 lastcomp = 0; - int count = 0; - for (int hunknum = 0; hunknum < m_hunkcount; hunknum++) - { - UINT8 curcomp = m_rawmap[hunknum * 12 + 0]; - - // promote self block references to more compact forms - if (curcomp == COMPRESSION_SELF) - { - UINT32 refhunk = be_read(&m_rawmap[hunknum * 12 + 4], 6); - if (refhunk == last_self) - curcomp = COMPRESSION_SELF_0; - else if (refhunk == last_self + 1) - curcomp = COMPRESSION_SELF_1; - else - max_self = MAX(max_self, refhunk); - last_self = refhunk; - } - - // promote parent block references to more compact forms - else if (curcomp == COMPRESSION_PARENT) - { - UINT32 refunit = be_read(&m_rawmap[hunknum * 12 + 4], 6); - if (refunit == (UINT64(hunknum) * UINT64(m_hunkbytes)) / m_unitbytes) - curcomp = COMPRESSION_PARENT_SELF; - else if (refunit == last_parent) - curcomp = COMPRESSION_PARENT_0; - else if (refunit == last_parent + m_hunkbytes / m_unitbytes) - curcomp = COMPRESSION_PARENT_1; - else - max_parent = MAX(max_parent, refunit); - last_parent = refunit; - } - - // track maximum compressed length - else //if (curcomp >= COMPRESSION_TYPE_0 && curcomp <= COMPRESSION_TYPE_3) - max_complen = MAX(max_complen, be_read(&m_rawmap[hunknum * 12 + 1], 3)); - - // track repeats - if (curcomp == lastcomp) - count++; - - // if no repeat, or we're at the end, flush it - if (curcomp != lastcomp || hunknum == m_hunkcount - 1) - { - while (count != 0) - { - if (count < 3) - encoder.histo_one(*dest++ = lastcomp), count--; - else if (count <= 3+15) - { - encoder.histo_one(*dest++ = COMPRESSION_RLE_SMALL); - encoder.histo_one(*dest++ = count - 3); - count = 0; - } - else - { - int this_count = MIN(count, 3+16+255); - encoder.histo_one(*dest++ = COMPRESSION_RLE_LARGE); - encoder.histo_one(*dest++ = (this_count - 3 - 16) >> 4); - encoder.histo_one(*dest++ = (this_count - 3 - 16) & 15); - count -= this_count; - } - } - if (curcomp != lastcomp) - encoder.histo_one(*dest++ = lastcomp = curcomp); - } - } - - // compute a tree and export it to the buffer - dynamic_buffer compressed(m_hunkcount * 6); - bitstream_out bitbuf(&compressed[16], compressed.size() - 16); - huffman_error err = encoder.compute_tree_from_histo(); - if (err != HUFFERR_NONE) - throw CHDERR_COMPRESSION_ERROR; - err = encoder.export_tree_rle(bitbuf); - if (err != HUFFERR_NONE) - throw CHDERR_COMPRESSION_ERROR; - - // encode the data - for (UINT8 *src = &compression_rle[0]; src < dest; src++) - encoder.encode_one(bitbuf, *src); - - // determine the number of bits we need to hold the a length - // and a hunk index - UINT8 lengthbits = bits_for_value(max_complen); - UINT8 selfbits = bits_for_value(max_self); - UINT8 parentbits = bits_for_value(max_parent); - - // for each compression type, output the relevant data - lastcomp = 0; - count = 0; - UINT8 *src = &compression_rle[0]; - UINT64 firstoffs = 0; - for (int hunknum = 0; hunknum < m_hunkcount; hunknum++) - { - UINT8 *rawmap = &m_rawmap[hunknum * 12]; - UINT32 length = be_read(&rawmap[1], 3); - UINT64 offset = be_read(&rawmap[4], 6); - UINT16 crc = be_read(&rawmap[10], 2); - - // if no count remaining, fetch the next entry - if (count == 0) - { - UINT8 val = *src++; - if (val == COMPRESSION_RLE_SMALL) - count = 2 + *src++; - else if (val == COMPRESSION_RLE_LARGE) - count = 2 + 16 + (*src++ << 4), count += *src++; - else - lastcomp = val; - } - else - count--; - - // output additional data needed for this entry - switch (lastcomp) - { - case COMPRESSION_TYPE_0: - case COMPRESSION_TYPE_1: - case COMPRESSION_TYPE_2: - case COMPRESSION_TYPE_3: - assert(length < (1 << lengthbits)); - bitbuf.write(length, lengthbits); - bitbuf.write(crc, 16); - if (firstoffs == 0) - firstoffs = offset; - break; - - case COMPRESSION_NONE: - bitbuf.write(crc, 16); - if (firstoffs == 0) - firstoffs = offset; - break; - - case COMPRESSION_SELF: - assert(offset < (UINT64(1) << selfbits)); - bitbuf.write(offset, selfbits); - break; - - case COMPRESSION_PARENT: - assert(offset < (UINT64(1) << parentbits)); - bitbuf.write(offset, parentbits); - break; - - case COMPRESSION_SELF_0: - case COMPRESSION_SELF_1: - case COMPRESSION_PARENT_SELF: - case COMPRESSION_PARENT_0: - case COMPRESSION_PARENT_1: - break; - } - } - - // write the map header - UINT32 complen = bitbuf.flush(); - assert(!bitbuf.overflow()); - be_write(&compressed[0], complen, 4); - be_write(&compressed[4], firstoffs, 6); - be_write(&compressed[10], mapcrc, 2); - compressed[12] = lengthbits; - compressed[13] = selfbits; - compressed[14] = parentbits; - compressed[15] = 0; - - // write the result - m_mapoffset = file_append(&compressed[0], complen + 16); - - // then write the map offset - UINT8 rawbuf[sizeof(UINT64)]; - be_write(rawbuf, m_mapoffset, 8); - file_write(m_mapoffset_offset, rawbuf, sizeof(rawbuf)); - return CHDERR_NONE; - } - catch (chd_error &err) - { - return err; - } -} - -/** - * @fn void chd_file::decompress_v5_map() - * - * @brief ------------------------------------------------- - * decompress_v5_map - decompress the v5 map - * -------------------------------------------------. - * - * @exception CHDERR_DECOMPRESSION_ERROR Thrown when a chderr decompression error error - * condition occurs. - */ - -void chd_file::decompress_v5_map() -{ - // if no offset, we haven't written it yet - if (m_mapoffset == 0) - { - memset(&m_rawmap[0], 0xff, m_rawmap.size()); - return; - } - - // read the reader - UINT8 rawbuf[16]; - file_read(m_mapoffset, rawbuf, sizeof(rawbuf)); - UINT32 mapbytes = be_read(&rawbuf[0], 4); - UINT64 firstoffs = be_read(&rawbuf[4], 6); - UINT16 mapcrc = be_read(&rawbuf[10], 2); - UINT8 lengthbits = rawbuf[12]; - UINT8 selfbits = rawbuf[13]; - UINT8 parentbits = rawbuf[14]; - - // now read the map - dynamic_buffer compressed(mapbytes); - file_read(m_mapoffset + 16, &compressed[0], mapbytes); - bitstream_in bitbuf(&compressed[0], compressed.size()); - - // first decode the compression types - huffman_decoder<16, 8> decoder; - huffman_error err = decoder.import_tree_rle(bitbuf); - if (err != HUFFERR_NONE) - throw CHDERR_DECOMPRESSION_ERROR; - UINT8 lastcomp = 0; - int repcount = 0; - for (int hunknum = 0; hunknum < m_hunkcount; hunknum++) - { - UINT8 *rawmap = &m_rawmap[hunknum * 12]; - if (repcount > 0) - rawmap[0] = lastcomp, repcount--; - else - { - UINT8 val = decoder.decode_one(bitbuf); - if (val == COMPRESSION_RLE_SMALL) - rawmap[0] = lastcomp, repcount = 2 + decoder.decode_one(bitbuf); - else if (val == COMPRESSION_RLE_LARGE) - rawmap[0] = lastcomp, repcount = 2 + 16 + (decoder.decode_one(bitbuf) << 4), repcount += decoder.decode_one(bitbuf); - else - rawmap[0] = lastcomp = val; - } - } - - // then iterate through the hunks and extract the needed data - UINT64 curoffset = firstoffs; - UINT32 last_self = 0; - UINT64 last_parent = 0; - for (int hunknum = 0; hunknum < m_hunkcount; hunknum++) - { - UINT8 *rawmap = &m_rawmap[hunknum * 12]; - UINT64 offset = curoffset; - UINT32 length = 0; - UINT16 crc = 0; - switch (rawmap[0]) - { - // base types - case COMPRESSION_TYPE_0: - case COMPRESSION_TYPE_1: - case COMPRESSION_TYPE_2: - case COMPRESSION_TYPE_3: - curoffset += length = bitbuf.read(lengthbits); - crc = bitbuf.read(16); - break; - - case COMPRESSION_NONE: - curoffset += length = m_hunkbytes; - crc = bitbuf.read(16); - break; - - case COMPRESSION_SELF: - last_self = offset = bitbuf.read(selfbits); - break; - - case COMPRESSION_PARENT: - offset = bitbuf.read(parentbits); - last_parent = offset; - break; - - // pseudo-types; convert into base types - case COMPRESSION_SELF_1: - last_self++; - case COMPRESSION_SELF_0: - rawmap[0] = COMPRESSION_SELF; - offset = last_self; - break; - - case COMPRESSION_PARENT_SELF: - rawmap[0] = COMPRESSION_PARENT; - last_parent = offset = (UINT64(hunknum) * UINT64(m_hunkbytes)) / m_unitbytes; - break; - - case COMPRESSION_PARENT_1: - last_parent += m_hunkbytes / m_unitbytes; - case COMPRESSION_PARENT_0: - rawmap[0] = COMPRESSION_PARENT; - offset = last_parent; - break; - } - be_write(&rawmap[1], length, 3); - be_write(&rawmap[4], offset, 6); - be_write(&rawmap[10], crc, 2); - } - - // verify the final CRC - if (crc16_creator::simple(&m_rawmap[0], m_hunkcount * 12) != mapcrc) - throw CHDERR_DECOMPRESSION_ERROR; -} - -/** - * @fn chd_error chd_file::create_common() - * - * @brief ------------------------------------------------- - * create_common - command path when creating a new CHD file - * -------------------------------------------------. - * - * @exception CHDERR_UNSUPPORTED_VERSION Thrown when a chderr unsupported version error - * condition occurs. - * @exception CHDERR_INVALID_PARAMETER Thrown when a chderr invalid parameter error - * condition occurs. - * @exception CHDERR_UNKNOWN_COMPRESSION Thrown when a chderr unknown compression error - * condition occurs. - * - * @return The new common. - */ - -chd_error chd_file::create_common() -{ - // wrap in try for proper error handling - try - { - m_version = HEADER_VERSION; - m_metaoffset = 0; - - // if we have a parent, it must be V3 or later - if (m_parent != NULL && m_parent->version() < 3) - throw CHDERR_UNSUPPORTED_VERSION; - - // must be an even number of units per hunk - if (m_hunkbytes % m_unitbytes != 0) - throw CHDERR_INVALID_PARAMETER; - if (m_parent != NULL && m_unitbytes != m_parent->unit_bytes()) - throw CHDERR_INVALID_PARAMETER; - - // verify the compression types - bool found_zero = false; - for (int codecnum = 0; codecnum < ARRAY_LENGTH(m_compression); codecnum++) - { - // once we hit an empty slot, all later slots must be empty as well - if (m_compression[codecnum] == CHD_CODEC_NONE) - found_zero = true; - else if (found_zero) - throw CHDERR_INVALID_PARAMETER; - else if (!chd_codec_list::codec_exists(m_compression[codecnum])) - throw CHDERR_UNKNOWN_COMPRESSION; - } - - // create our V5 header - UINT8 rawheader[V5_HEADER_SIZE]; - memcpy(&rawheader[0], "MComprHD", 8); - be_write(&rawheader[8], V5_HEADER_SIZE, 4); - be_write(&rawheader[12], m_version, 4); - be_write(&rawheader[16], m_compression[0], 4); - be_write(&rawheader[20], m_compression[1], 4); - be_write(&rawheader[24], m_compression[2], 4); - be_write(&rawheader[28], m_compression[3], 4); - be_write(&rawheader[32], m_logicalbytes, 8); - be_write(&rawheader[40], compressed() ? 0 : V5_HEADER_SIZE, 8); - be_write(&rawheader[48], m_metaoffset, 8); - be_write(&rawheader[56], m_hunkbytes, 4); - be_write(&rawheader[60], m_unitbytes, 4); - be_write_sha1(&rawheader[64], sha1_t::null); - be_write_sha1(&rawheader[84], sha1_t::null); - be_write_sha1(&rawheader[104], (m_parent != NULL) ? m_parent->sha1() : sha1_t::null); - - // write the resulting header - file_write(0, rawheader, sizeof(rawheader)); - - // parse it back out to set up fields appropriately - sha1_t parentsha1; - parse_v5_header(rawheader, parentsha1); - - // writes are obviously permitted; reads only if uncompressed - m_allow_writes = true; - m_allow_reads = !compressed(); - - // write out the map (if not compressed) - if (!compressed()) - { - UINT32 mapsize = m_mapentrybytes * m_hunkcount; - UINT8 buffer[4096] = { 0 }; - UINT64 offset = m_mapoffset; - while (mapsize != 0) - { - UINT32 bytes_to_write = MIN(mapsize, sizeof(buffer)); - file_write(offset, buffer, bytes_to_write); - offset += bytes_to_write; - mapsize -= bytes_to_write; - } - } - - // finish opening the file - create_open_common(); - } - - // handle errors by closing ourself - catch (chd_error &err) - { - close(); - return err; - } - catch (...) - { - close(); - throw; - } - return CHDERR_NONE; -} - -/** - * @fn chd_error chd_file::open_common(bool writeable) - * - * @brief ------------------------------------------------- - * open_common - common path when opening an existing CHD file for input - * -------------------------------------------------. - * - * @exception CHDERR_INVALID_FILE Thrown when a chderr invalid file error condition - * occurs. - * @exception CHDERR_UNSUPPORTED_VERSION Thrown when a chderr unsupported version error - * condition occurs. - * @exception CHDERR_FILE_NOT_WRITEABLE Thrown when a chderr file not writeable error - * condition occurs. - * @exception CHDERR_INVALID_PARENT Thrown when a chderr invalid parent error condition - * occurs. - * @exception CHDERR_INVALID_PARAMETER Thrown when a chderr invalid parameter error - * condition occurs. - * - * @param writeable true if writeable. - * - * @return A chd_error. - */ - -chd_error chd_file::open_common(bool writeable) -{ - // wrap in try for proper error handling - try - { - // reads are always permitted - m_allow_reads = true; - - // read the raw header - UINT8 rawheader[MAX_HEADER_SIZE]; - file_read(0, rawheader, sizeof(rawheader)); - - // verify the signature - if (memcmp(rawheader, "MComprHD", 8) != 0) - throw CHDERR_INVALID_FILE; - - // only allow writes to the most recent version - m_version = be_read(&rawheader[12], 4); - if (writeable && m_version < HEADER_VERSION) - throw CHDERR_UNSUPPORTED_VERSION; - - // read the header if we support it - sha1_t parentsha1 = sha1_t::null; - switch (m_version) - { - case 3: parse_v3_header(rawheader, parentsha1); break; - case 4: parse_v4_header(rawheader, parentsha1); break; - case 5: parse_v5_header(rawheader, parentsha1); break; - default: throw CHDERR_UNSUPPORTED_VERSION; - } - - if (writeable && !m_allow_writes) - throw CHDERR_FILE_NOT_WRITEABLE; - - // make sure we have a parent if we need one (and don't if we don't) - if (parentsha1 != sha1_t::null) - { - if (m_parent == NULL) - m_parent_missing = true; - else if (m_parent->sha1() != parentsha1) - throw CHDERR_INVALID_PARENT; - } - else if (m_parent != NULL) - throw CHDERR_INVALID_PARAMETER; - - // finish opening the file - create_open_common(); - return CHDERR_NONE; - } - - // handle errors by closing ourself - catch (chd_error &err) - { - close(); - return err; - } -} - -/** - * @fn void chd_file::create_open_common() - * - * @brief ------------------------------------------------- - * create_open_common - common code for handling creation and opening of a file - * -------------------------------------------------. - * - * @exception CHDERR_UNKNOWN_COMPRESSION Thrown when a chderr unknown compression error - * condition occurs. - */ - -void chd_file::create_open_common() -{ - // verify the compression types and initialize the codecs - for (int decompnum = 0; decompnum < ARRAY_LENGTH(m_compression); decompnum++) - { - m_decompressor[decompnum] = chd_codec_list::new_decompressor(m_compression[decompnum], *this); - if (m_decompressor[decompnum] == NULL && m_compression[decompnum] != 0) - throw CHDERR_UNKNOWN_COMPRESSION; - } - - // read the map; v5+ compressed drives need to read and decompress their map - m_rawmap.resize(m_hunkcount * m_mapentrybytes); - if (m_version >= 5 && compressed()) - decompress_v5_map(); - else - file_read(m_mapoffset, &m_rawmap[0], m_rawmap.size()); - - // allocate the temporary compressed buffer and a buffer for caching - m_compressed.resize(m_hunkbytes); - m_cache.resize(m_hunkbytes); -} - -/** - * @fn void chd_file::verify_proper_compression_append(UINT32 hunknum) - * - * @brief ------------------------------------------------- - * verify_proper_compression_append - verify that the given hunk is a proper candidate - * for appending to a compressed CHD - * -------------------------------------------------. - * - * @exception CHDERR_NOT_OPEN Thrown when a chderr not open error condition occurs. - * @exception CHDERR_HUNK_OUT_OF_RANGE Thrown when a chderr hunk out of range error - * condition occurs. - * @exception CHDERR_FILE_NOT_WRITEABLE Thrown when a chderr file not writeable error - * condition occurs. - * @exception CHDERR_COMPRESSION_ERROR Thrown when a chderr compression error error - * condition occurs. - * - * @param hunknum The hunknum. - */ - -void chd_file::verify_proper_compression_append(UINT32 hunknum) -{ - // punt if no file - if (m_file == NULL) - throw CHDERR_NOT_OPEN; - - // return an error if out of range - if (hunknum >= m_hunkcount) - throw CHDERR_HUNK_OUT_OF_RANGE; - - // if not writeable, fail - if (!m_allow_writes) - throw CHDERR_FILE_NOT_WRITEABLE; - - // compressed writes only via this interface - if (!compressed()) - throw CHDERR_FILE_NOT_WRITEABLE; - - // only permitted to write new blocks - UINT8 *rawmap = &m_rawmap[hunknum * 12]; - if (rawmap[0] != 0xff) - throw CHDERR_COMPRESSION_ERROR; - - // if this isn't the first block, only permitted to write immediately - // after the previous one - if (hunknum != 0 && rawmap[-12] == 0xff) - throw CHDERR_COMPRESSION_ERROR; -} - -/** - * @fn void chd_file::hunk_write_compressed(UINT32 hunknum, INT8 compression, const UINT8 *compressed, UINT32 complength, crc16_t crc16) - * - * @brief ------------------------------------------------- - * hunk_write_compressed - write a hunk to a compressed CHD, discovering the best - * technique - * -------------------------------------------------. - * - * @param hunknum The hunknum. - * @param compression The compression. - * @param compressed The compressed. - * @param complength The complength. - * @param crc16 The CRC 16. - */ - -void chd_file::hunk_write_compressed(UINT32 hunknum, INT8 compression, const UINT8 *compressed, UINT32 complength, crc16_t crc16) -{ - // verify that we are appending properly to a compressed file - verify_proper_compression_append(hunknum); - - // write the final result - UINT64 offset = file_append(compressed, complength); - - // update the map entry - UINT8 *rawmap = &m_rawmap[hunknum * 12]; - rawmap[0] = (compression == -1) ? COMPRESSION_NONE : compression; - be_write(&rawmap[1], complength, 3); - be_write(&rawmap[4], offset, 6); - be_write(&rawmap[10], crc16, 2); -} - -/** - * @fn void chd_file::hunk_copy_from_self(UINT32 hunknum, UINT32 otherhunk) - * - * @brief ------------------------------------------------- - * hunk_copy_from_self - mark a hunk as being a copy of another hunk in the same CHD - * -------------------------------------------------. - * - * @exception CHDERR_INVALID_PARAMETER Thrown when a chderr invalid parameter error - * condition occurs. - * - * @param hunknum The hunknum. - * @param otherhunk The otherhunk. - */ - -void chd_file::hunk_copy_from_self(UINT32 hunknum, UINT32 otherhunk) -{ - // verify that we are appending properly to a compressed file - verify_proper_compression_append(hunknum); - - // only permitted to reference prior hunks - if (otherhunk >= hunknum) - throw CHDERR_INVALID_PARAMETER; - - // update the map entry - UINT8 *rawmap = &m_rawmap[hunknum * 12]; - rawmap[0] = COMPRESSION_SELF; - be_write(&rawmap[1], 0, 3); - be_write(&rawmap[4], otherhunk, 6); - be_write(&rawmap[10], 0, 2); -} - -/** - * @fn void chd_file::hunk_copy_from_parent(UINT32 hunknum, UINT64 parentunit) - * - * @brief ------------------------------------------------- - * hunk_copy_from_parent - mark a hunk as being a copy of a hunk from a parent CHD - * -------------------------------------------------. - * - * @param hunknum The hunknum. - * @param parentunit The parentunit. - */ - -void chd_file::hunk_copy_from_parent(UINT32 hunknum, UINT64 parentunit) -{ - // verify that we are appending properly to a compressed file - verify_proper_compression_append(hunknum); - - // update the map entry - UINT8 *rawmap = &m_rawmap[hunknum * 12]; - rawmap[0] = COMPRESSION_PARENT; - be_write(&rawmap[1], 0, 3); - be_write(&rawmap[4], parentunit, 6); - be_write(&rawmap[10], 0, 2); -} - -/** - * @fn bool chd_file::metadata_find(chd_metadata_tag metatag, INT32 metaindex, metadata_entry &metaentry, bool resume) - * - * @brief ------------------------------------------------- - * metadata_find - find a metadata entry - * -------------------------------------------------. - * - * @param metatag The metatag. - * @param metaindex The metaindex. - * @param [in,out] metaentry The metaentry. - * @param resume true to resume. - * - * @return true if it succeeds, false if it fails. - */ - -bool chd_file::metadata_find(chd_metadata_tag metatag, INT32 metaindex, metadata_entry &metaentry, bool resume) -{ - // start at the beginning unless we're resuming a previous search - if (!resume) - { - metaentry.offset = m_metaoffset; - metaentry.prev = 0; - } - else - { - metaentry.prev = metaentry.offset; - metaentry.offset = metaentry.next; - } - - // loop until we run out of options - while (metaentry.offset != 0) - { - // read the raw header - UINT8 raw_meta_header[METADATA_HEADER_SIZE]; - file_read(metaentry.offset, raw_meta_header, sizeof(raw_meta_header)); - - // extract the data - metaentry.metatag = be_read(&raw_meta_header[0], 4); - metaentry.flags = raw_meta_header[4]; - metaentry.length = be_read(&raw_meta_header[5], 3); - metaentry.next = be_read(&raw_meta_header[8], 8); - - // if we got a match, proceed - if (metatag == CHDMETATAG_WILDCARD || metaentry.metatag == metatag) - if (metaindex-- == 0) - return true; - - // no match, fetch the next link - metaentry.prev = metaentry.offset; - metaentry.offset = metaentry.next; - } - - // if we get here, we didn't find it - return false; -} - -/** - * @fn void chd_file::metadata_set_previous_next(UINT64 prevoffset, UINT64 nextoffset) - * - * @brief ------------------------------------------------- - * metadata_set_previous_next - set the 'next' offset of a piece of metadata - * -------------------------------------------------. - * - * @param prevoffset The prevoffset. - * @param nextoffset The nextoffset. - */ - -void chd_file::metadata_set_previous_next(UINT64 prevoffset, UINT64 nextoffset) -{ - UINT64 offset = 0; - - // if we were the first entry, make the next entry the first - if (prevoffset == 0) - { - offset = m_metaoffset_offset; - m_metaoffset = nextoffset; - } - - // otherwise, update the link in the previous header - else - offset = prevoffset + 8; - - // create a big-endian version - UINT8 rawbuf[sizeof(UINT64)]; - be_write(rawbuf, nextoffset, 8); - - // write to the header and update our local copy - file_write(offset, rawbuf, sizeof(rawbuf)); -} - -/** - * @fn void chd_file::metadata_update_hash() - * - * @brief ------------------------------------------------- - * metadata_update_hash - compute the SHA1 hash of all metadata that requests it - * -------------------------------------------------. - */ - -void chd_file::metadata_update_hash() -{ - // only works for V4 and above, and only for compressed CHDs - if (m_version < 4 || !compressed()) - return; - - // compute the new overall hash - sha1_t fullsha1 = compute_overall_sha1(raw_sha1()); - - // create a big-endian version - UINT8 rawbuf[sizeof(sha1_t)]; - be_write_sha1(&rawbuf[0], fullsha1); - - // write to the header - file_write(m_sha1_offset, rawbuf, sizeof(rawbuf)); -} - -/** - * @fn int CLIB_DECL chd_file::metadata_hash_compare(const void *elem1, const void *elem2) - * - * @brief ------------------------------------------------- - * metadata_hash_compare - compare two hash entries - * -------------------------------------------------. - * - * @param elem1 The first element. - * @param elem2 The second element. - * - * @return A CLIB_DECL. - */ - -int CLIB_DECL chd_file::metadata_hash_compare(const void *elem1, const void *elem2) -{ - return memcmp(elem1, elem2, sizeof(metadata_hash)); -} - - - -//************************************************************************** -// CHD COMPRESSOR -//************************************************************************** - -/** - * @fn chd_file_compressor::chd_file_compressor() - * - * @brief ------------------------------------------------- - * chd_file_compressor - constructor - * -------------------------------------------------. - */ - -chd_file_compressor::chd_file_compressor() - : m_walking_parent(false), - m_total_in(0), - m_total_out(0), - m_read_queue(NULL), - m_read_queue_offset(0), - m_read_done_offset(0), - m_read_error(false), - m_work_queue(NULL), - m_write_hunk(0) -{ - // zap arrays - memset(m_codecs, 0, sizeof(m_codecs)); - - // allocate work queues - m_read_queue = osd_work_queue_alloc(WORK_QUEUE_FLAG_IO); - m_work_queue = osd_work_queue_alloc(WORK_QUEUE_FLAG_MULTI); -} - -/** - * @fn chd_file_compressor::~chd_file_compressor() - * - * @brief ------------------------------------------------- - * ~chd_file_compressor - destructor - * -------------------------------------------------. - */ - -chd_file_compressor::~chd_file_compressor() -{ - // free the work queues - osd_work_queue_free(m_read_queue); - osd_work_queue_free(m_work_queue); - - // delete allocated arrays - for (int codecnum = 0; codecnum < ARRAY_LENGTH(m_codecs); codecnum++) - delete m_codecs[codecnum]; -} - -/** - * @fn void chd_file_compressor::compress_begin() - * - * @brief ------------------------------------------------- - * compress_begin - initiate compression - * -------------------------------------------------. - */ - -void chd_file_compressor::compress_begin() -{ - // reset state - m_walking_parent = (m_parent != NULL); - m_total_in = 0; - m_total_out = 0; - m_compsha1.reset(); - - // reset our maps - m_parent_map.reset(); - m_current_map.reset(); - - // reset read state - m_read_queue_offset = 0; - m_read_done_offset = 0; - m_read_error = false; - - // reset work item state - m_work_buffer.resize(hunk_bytes() * (WORK_BUFFER_HUNKS + 1)); - memset(&m_work_buffer[0], 0, m_work_buffer.size()); - m_compressed_buffer.resize(hunk_bytes() * WORK_BUFFER_HUNKS); - for (int itemnum = 0; itemnum < WORK_BUFFER_HUNKS; itemnum++) - { - work_item &item = m_work_item[itemnum]; - item.m_compressor = this; - item.m_data = &m_work_buffer[hunk_bytes() * itemnum]; - item.m_compressed = &m_compressed_buffer[hunk_bytes() * itemnum]; - item.m_hash.resize(hunk_bytes() / unit_bytes()); - } - - // initialize codec instances - for (int instance = 0; instance < ARRAY_LENGTH(m_codecs); instance++) - { - delete m_codecs[instance]; - m_codecs[instance] = new chd_compressor_group(*this, m_compression); - } - - // reset write state - m_write_hunk = 0; -} - -/** - * @fn chd_error chd_file_compressor::compress_continue(double &progress, double &ratio) - * - * @brief ------------------------------------------------- - * compress_continue - continue compression - * -------------------------------------------------. - * - * @param [in,out] progress The progress. - * @param [in,out] ratio The ratio. - * - * @return A chd_error. - */ - -chd_error chd_file_compressor::compress_continue(double &progress, double &ratio) -{ - // if we got an error, return an error - if (m_read_error) - return CHDERR_READ_ERROR; - - // if done reading, queue some more - while (m_read_queue_offset < m_logicalbytes && osd_work_queue_items(m_read_queue) < 2) - { - // see if we have enough free work items to read the next half of a buffer - UINT32 startitem = m_read_queue_offset / hunk_bytes(); - UINT32 enditem = startitem + WORK_BUFFER_HUNKS / 2; - UINT32 curitem; - for (curitem = startitem; curitem < enditem; curitem++) - if (m_work_item[curitem % WORK_BUFFER_HUNKS].m_status != WS_READY) - break; - - // if it's not all clear, defer - if (curitem != enditem) - break; - - // if we're walking the parent, we want one more item to have cleared so we - // can read an extra hunk there - if (m_walking_parent && m_work_item[curitem % WORK_BUFFER_HUNKS].m_status != WS_READY) - break; - - // queue the next read - for (curitem = startitem; curitem < enditem; curitem++) - atomic_exchange32(&m_work_item[curitem % WORK_BUFFER_HUNKS].m_status, WS_READING); - osd_work_item_queue(m_read_queue, async_read_static, this, WORK_ITEM_FLAG_AUTO_RELEASE); - m_read_queue_offset += WORK_BUFFER_HUNKS * hunk_bytes() / 2; - } - - // flush out any finished items - while (m_work_item[m_write_hunk % WORK_BUFFER_HUNKS].m_status == WS_COMPLETE) - { - work_item &item = m_work_item[m_write_hunk % WORK_BUFFER_HUNKS]; - - // free any OSD work item - if (item.m_osd != NULL) - osd_work_item_release(item.m_osd); - item.m_osd = NULL; - - // for parent walking, just add to the hashmap - if (m_walking_parent) - { - UINT32 uph = hunk_bytes() / unit_bytes(); - UINT32 units = uph; - if (item.m_hunknum == hunk_count() - 1 || !compressed()) - units = 1; - for (UINT32 unit = 0; unit < units; unit++) - if (m_parent_map.find(item.m_hash[unit].m_crc16, item.m_hash[unit].m_sha1) == hashmap::NOT_FOUND) - m_parent_map.add(item.m_hunknum * uph + unit, item.m_hash[unit].m_crc16, item.m_hash[unit].m_sha1); - } - - // if we're uncompressed, use regular writes - else if (!compressed()) - { - chd_error err = write_hunk(item.m_hunknum, item.m_data); - if (err != CHDERR_NONE) - return err; - - // writes of all-0 data don't actually take space, so see if we count this - chd_codec_type codec = CHD_CODEC_NONE; - UINT32 complen; - hunk_info(item.m_hunknum, codec, complen); - if (codec == CHD_CODEC_NONE) - m_total_out += m_hunkbytes; - } - - // for compressing, process the result - else do - { - // first see if the hunk is in the parent or self maps - UINT64 selfhunk = m_current_map.find(item.m_hash[0].m_crc16, item.m_hash[0].m_sha1); - if (selfhunk != hashmap::NOT_FOUND) - { - hunk_copy_from_self(item.m_hunknum, selfhunk); - break; - } - - // if not, see if it's in the parent map - if (m_parent != NULL) - { - UINT64 parentunit = m_parent_map.find(item.m_hash[0].m_crc16, item.m_hash[0].m_sha1); - if (parentunit != hashmap::NOT_FOUND) - { - hunk_copy_from_parent(item.m_hunknum, parentunit); - break; - } - } - - // otherwise, append it compressed and add to the self map - hunk_write_compressed(item.m_hunknum, item.m_compression, item.m_compressed, item.m_complen, item.m_hash[0].m_crc16); - m_total_out += item.m_complen; - m_current_map.add(item.m_hunknum, item.m_hash[0].m_crc16, item.m_hash[0].m_sha1); - } while (0); - - // reset the item and advance - atomic_exchange32(&item.m_status, WS_READY); - m_write_hunk++; - - // if we hit the end, finalize - if (m_write_hunk == m_hunkcount) - { - // if this is just walking the parent, reset and get ready for compression - if (m_walking_parent) - { - m_walking_parent = false; - m_read_queue_offset = m_read_done_offset = 0; - m_write_hunk = 0; - for (int itemnum = 0; itemnum < WORK_BUFFER_HUNKS; itemnum++) - atomic_exchange32(&m_work_item[itemnum].m_status, WS_READY); - } - - // wait for all reads to finish and if we're compressed, write the final SHA1 and map - else - { - osd_work_queue_wait(m_read_queue, 30 * osd_ticks_per_second()); - if (!compressed()) - return CHDERR_NONE; - set_raw_sha1(m_compsha1.finish()); - return compress_v5_map(); - } - } - } - - // update progress and ratio - if (m_walking_parent) - progress = double(m_read_done_offset) / double(logical_bytes()); - else - progress = double(m_write_hunk) / double(m_hunkcount); - ratio = (m_total_in == 0) ? 1.0 : double(m_total_out) / double(m_total_in); - - // if we're waiting for work, wait - while (m_work_item[m_write_hunk % WORK_BUFFER_HUNKS].m_status != WS_COMPLETE && m_work_item[m_write_hunk % WORK_BUFFER_HUNKS].m_osd != NULL) - osd_work_item_wait(m_work_item[m_write_hunk % WORK_BUFFER_HUNKS].m_osd, osd_ticks_per_second()); - - return m_walking_parent ? CHDERR_WALKING_PARENT : CHDERR_COMPRESSING; -} - -/** - * @fn void *chd_file_compressor::async_walk_parent_static(void *param, int threadid) - * - * @brief ------------------------------------------------- - * async_walk_parent - handle asynchronous parent walking operations - * -------------------------------------------------. - * - * @param [in,out] param If non-null, the parameter. - * @param threadid The threadid. - * - * @return null if it fails, else a void*. - */ - -void *chd_file_compressor::async_walk_parent_static(void *param, int threadid) -{ - work_item *item = reinterpret_cast<work_item *>(param); - item->m_compressor->async_walk_parent(*item); - return NULL; -} - -/** - * @fn void chd_file_compressor::async_walk_parent(work_item &item) - * - * @brief Asynchronous walk parent. - * - * @param [in,out] item The item. - */ - -void chd_file_compressor::async_walk_parent(work_item &item) -{ - // compute CRC-16 and SHA-1 hashes for each unit, unless we're the last one or we're uncompressed - UINT32 units = hunk_bytes() / unit_bytes(); - if (item.m_hunknum == m_hunkcount - 1 || !compressed()) - units = 1; - for (UINT32 unit = 0; unit < units; unit++) - { - item.m_hash[unit].m_crc16 = crc16_creator::simple(item.m_data + unit * unit_bytes(), hunk_bytes()); - item.m_hash[unit].m_sha1 = sha1_creator::simple(item.m_data + unit * unit_bytes(), hunk_bytes()); - } - atomic_exchange32(&item.m_status, WS_COMPLETE); -} - -/** - * @fn void *chd_file_compressor::async_compress_hunk_static(void *param, int threadid) - * - * @brief ------------------------------------------------- - * async_compress_hunk - handle asynchronous hunk compression - * -------------------------------------------------. - * - * @param [in,out] param If non-null, the parameter. - * @param threadid The threadid. - * - * @return null if it fails, else a void*. - */ - -void *chd_file_compressor::async_compress_hunk_static(void *param, int threadid) -{ - work_item *item = reinterpret_cast<work_item *>(param); - item->m_compressor->async_compress_hunk(*item, threadid); - return NULL; -} - -/** - * @fn void chd_file_compressor::async_compress_hunk(work_item &item, int threadid) - * - * @brief Asynchronous compress hunk. - * - * @param [in,out] item The item. - * @param threadid The threadid. - */ - -void chd_file_compressor::async_compress_hunk(work_item &item, int threadid) -{ - // use our thread's codec - assert(threadid < ARRAY_LENGTH(m_codecs)); - item.m_codecs = m_codecs[threadid]; - - // compute CRC-16 and SHA-1 hashes - item.m_hash[0].m_crc16 = crc16_creator::simple(item.m_data, hunk_bytes()); - item.m_hash[0].m_sha1 = sha1_creator::simple(item.m_data, hunk_bytes()); - - // find the best compression scheme, unless we already have a self or parent match - // (note we may miss a self match from blocks not yet added, but this just results in extra work) - // TODO: data race - if (m_current_map.find(item.m_hash[0].m_crc16, item.m_hash[0].m_sha1) == hashmap::NOT_FOUND && - m_parent_map.find(item.m_hash[0].m_crc16, item.m_hash[0].m_sha1) == hashmap::NOT_FOUND) - item.m_compression = item.m_codecs->find_best_compressor(item.m_data, item.m_compressed, item.m_complen); - - // mark us complete - atomic_exchange32(&item.m_status, WS_COMPLETE); -} - -/** - * @fn void *chd_file_compressor::async_read_static(void *param, int threadid) - * - * @brief ------------------------------------------------- - * async_read - handle asynchronous source file reading - * -------------------------------------------------. - * - * @param [in,out] param If non-null, the parameter. - * @param threadid The threadid. - * - * @return null if it fails, else a void*. - */ - -void *chd_file_compressor::async_read_static(void *param, int threadid) -{ - reinterpret_cast<chd_file_compressor *>(param)->async_read(); - return NULL; -} - -/** - * @fn void chd_file_compressor::async_read() - * - * @brief Asynchronous read. - */ - -void chd_file_compressor::async_read() -{ - // if in the error or complete state, stop - if (m_read_error) - return; - - // determine parameters for the read - UINT32 work_buffer_bytes = WORK_BUFFER_HUNKS * hunk_bytes(); - UINT32 numbytes = work_buffer_bytes / 2; - if (m_read_done_offset + numbytes > logical_bytes()) - numbytes = logical_bytes() - m_read_done_offset; - - // catch any exceptions coming out of here - try - { - // do the read - UINT8 *dest = &m_work_buffer[0] + (m_read_done_offset % work_buffer_bytes); - assert(dest == &m_work_buffer[0] || dest == &m_work_buffer[work_buffer_bytes/2]); - UINT64 end_offset = m_read_done_offset + numbytes; - - // if walking the parent, read in hunks from the parent CHD - if (m_walking_parent) - { - UINT8 *curdest = dest; - for (UINT64 curoffs = m_read_done_offset; curoffs < end_offset + 1; curoffs += hunk_bytes()) - { - m_parent->read_hunk(curoffs / hunk_bytes(), curdest); - curdest += hunk_bytes(); - } - } - - // otherwise, call the virtual function - else - read_data(dest, m_read_done_offset, numbytes); - - // spawn off work for each hunk - for (UINT64 curoffs = m_read_done_offset; curoffs < end_offset; curoffs += hunk_bytes()) - { - UINT32 hunknum = curoffs / hunk_bytes(); - work_item &item = m_work_item[hunknum % WORK_BUFFER_HUNKS]; - assert(item.m_status == WS_READING); - atomic_exchange32(&item.m_status, WS_QUEUED); - item.m_hunknum = hunknum; - item.m_osd = osd_work_item_queue(m_work_queue, m_walking_parent ? async_walk_parent_static : async_compress_hunk_static, &item, 0); - } - - // continue the running SHA-1 - if (!m_walking_parent) - { - if (compressed()) - m_compsha1.append(dest, numbytes); - m_total_in += numbytes; - } - - // advance the read pointer - m_read_done_offset += numbytes; - } - catch (chd_error& err) - { - fprintf(stderr, "CHD error occurred: %s\n", chd_file::error_string(err)); - m_read_error = true; - } - catch (std::exception& ex) - { - fprintf(stderr, "exception occurred: %s\n", ex.what()); - m_read_error = true; - } -} - - - -//************************************************************************** -// CHD COMPRESSOR HASHMAP -//************************************************************************** - -/** - * @fn chd_file_compressor::hashmap::hashmap() - * - * @brief ------------------------------------------------- - * hashmap - constructor - * -------------------------------------------------. - */ - -chd_file_compressor::hashmap::hashmap() - : m_block_list(new entry_block(NULL)) -{ - // initialize the map to empty - memset(m_map, 0, sizeof(m_map)); -} - -/** - * @fn chd_file_compressor::hashmap::~hashmap() - * - * @brief ------------------------------------------------- - * ~hashmap - destructor - * -------------------------------------------------. - */ - -chd_file_compressor::hashmap::~hashmap() -{ - reset(); - delete m_block_list; -} - -/** - * @fn void chd_file_compressor::hashmap::reset() - * - * @brief ------------------------------------------------- - * reset - reset the state of the map - * -------------------------------------------------. - */ - -void chd_file_compressor::hashmap::reset() -{ - // delete all the blocks - while (m_block_list->m_next != NULL) - { - entry_block *block = m_block_list; - m_block_list = block->m_next; - delete block; - } - m_block_list->m_nextalloc = 0; - - // reset the hash - memset(m_map, 0, sizeof(m_map)); -} - -/** - * @fn UINT64 chd_file_compressor::hashmap::find(crc16_t crc16, sha1_t sha1) - * - * @brief ------------------------------------------------- - * find - find an item in the CRC map - * -------------------------------------------------. - * - * @param crc16 The CRC 16. - * @param sha1 The first sha. - * - * @return An UINT64. - */ - -UINT64 chd_file_compressor::hashmap::find(crc16_t crc16, sha1_t sha1) -{ - // look up the entry in the map - for (entry_t *entry = m_map[crc16]; entry != NULL; entry = entry->m_next) - if (entry->m_sha1 == sha1) - return entry->m_itemnum; - return NOT_FOUND; -} - -/** - * @fn void chd_file_compressor::hashmap::add(UINT64 itemnum, crc16_t crc16, sha1_t sha1) - * - * @brief ------------------------------------------------- - * add - add an item to the CRC map - * -------------------------------------------------. - * - * @param itemnum The itemnum. - * @param crc16 The CRC 16. - * @param sha1 The first sha. - */ - -void chd_file_compressor::hashmap::add(UINT64 itemnum, crc16_t crc16, sha1_t sha1) -{ - // add to the appropriate map - if (m_block_list->m_nextalloc == ARRAY_LENGTH(m_block_list->m_array)) - m_block_list = new entry_block(m_block_list); - entry_t *entry = &m_block_list->m_array[m_block_list->m_nextalloc++]; - entry->m_itemnum = itemnum; - entry->m_sha1 = sha1; - entry->m_next = m_map[crc16]; - m_map[crc16] = entry; -} |
