summaryrefslogtreecommitdiffstatshomepage
path: root/src/lib/util/chd.c
diff options
context:
space:
mode:
author Miodrag Milanovic <mmicko@gmail.com>2015-11-08 12:56:12 +0100
committer Miodrag Milanovic <mmicko@gmail.com>2015-11-08 12:56:12 +0100
commit7c19aac60e12d6f5ea301bdb34d7826a01e0b06f (patch)
treef310d86aa2c6bfc19d115307dedde4eb0cd52dad /src/lib/util/chd.c
parenta57b46ae933badd7441ce1644711dbb851e2b504 (diff)
Rename *.c -> *.cpp in our source (nw)
Diffstat (limited to 'src/lib/util/chd.c')
-rw-r--r--src/lib/util/chd.c3279
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;
-}