diff options
Diffstat (limited to 'trunk/src/lib/util/chd.c')
-rw-r--r-- | trunk/src/lib/util/chd.c | 3089 |
1 files changed, 3089 insertions, 0 deletions
diff --git a/trunk/src/lib/util/chd.c b/trunk/src/lib/util/chd.c new file mode 100644 index 00000000000..4cbe8b5c6d8 --- /dev/null +++ b/trunk/src/lib/util/chd.c @@ -0,0 +1,3089 @@ +/*************************************************************************** + + chd.c + + MAME Compressed Hunks of Data file format + +**************************************************************************** + + Copyright Aaron Giles + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + * Neither the name 'MAME' nor the names of its contributors may be + used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY AARON GILES ''AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL AARON GILES BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + +***************************************************************************/ + +#include "chd.h" +#include "avcomp.h" +#include "md5.h" +#include "sha1.h" +#include <zlib.h> +#include <time.h> +#include <stddef.h> +#include <stdlib.h> + + + + +/*************************************************************************** + DEBUGGING +***************************************************************************/ + +#define PRINTF_MAX_HUNK (0) + + + +/*************************************************************************** + CONSTANTS +***************************************************************************/ + +#define MAP_STACK_ENTRIES 512 /* max number of entries to use on the stack */ +#define MAP_ENTRY_SIZE 16 /* V3 and later */ +#define OLD_MAP_ENTRY_SIZE 8 /* V1-V2 */ +#define METADATA_HEADER_SIZE 16 /* metadata header size */ +#define CRCMAP_HASH_SIZE 4095 /* number of CRC hashtable entries */ + +#define MAP_ENTRY_FLAG_TYPE_MASK 0x0f /* what type of hunk */ +#define MAP_ENTRY_FLAG_NO_CRC 0x10 /* no CRC is present */ + +#define MAP_ENTRY_TYPE_INVALID 0x00 /* invalid type */ +#define MAP_ENTRY_TYPE_COMPRESSED 0x01 /* standard compression */ +#define MAP_ENTRY_TYPE_UNCOMPRESSED 0x02 /* uncompressed data */ +#define MAP_ENTRY_TYPE_MINI 0x03 /* mini: use offset as raw data */ +#define MAP_ENTRY_TYPE_SELF_HUNK 0x04 /* same as another hunk in this file */ +#define MAP_ENTRY_TYPE_PARENT_HUNK 0x05 /* same as a hunk in the parent file */ + +#define CHD_V1_SECTOR_SIZE 512 /* size of a "sector" in the V1 header */ + +#define COOKIE_VALUE 0xbaadf00d +#define MAX_ZLIB_ALLOCS 64 + +#define END_OF_LIST_COOKIE "EndOfListCookie" + +#define NO_MATCH (~0) + + + +/*************************************************************************** + MACROS +***************************************************************************/ + +#define EARLY_EXIT(x) do { (void)(x); goto cleanup; } while (0) + + + +/*************************************************************************** + TYPE DEFINITIONS +***************************************************************************/ + +/* interface to a codec */ +typedef struct _codec_interface codec_interface; +struct _codec_interface +{ + UINT32 compression; /* type of compression */ + const char *compname; /* name of the algorithm */ + UINT8 lossy; /* is this a lossy algorithm? */ + chd_error (*init)(chd_file *chd); /* codec initialize */ + void (*free)(chd_file *chd); /* codec free */ + chd_error (*compress)(chd_file *chd, const void *src, UINT32 *complen); /* compress data */ + chd_error (*decompress)(chd_file *chd, UINT32 complen, void *dst); /* decompress data */ + chd_error (*config)(chd_file *chd, int param, void *config); /* configure */ +}; + + +/* a single map entry */ +typedef struct _map_entry map_entry; +struct _map_entry +{ + UINT64 offset; /* offset within the file of the data */ + UINT32 crc; /* 32-bit CRC of the data */ + UINT32 length; /* length of the data */ + UINT8 flags; /* misc flags */ +}; + + +/* simple linked-list of hunks used for our CRC map */ +typedef struct _crcmap_entry crcmap_entry; +struct _crcmap_entry +{ + UINT32 hunknum; /* hunk number */ + crcmap_entry * next; /* next entry in list */ +}; + + +/* a single metadata entry */ +typedef struct _metadata_entry metadata_entry; +struct _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 */ +}; + + +/* internal representation of an open CHD file */ +struct _chd_file +{ + UINT32 cookie; /* cookie, should equal COOKIE_VALUE */ + + core_file * file; /* handle to the open core file */ + UINT8 owns_file; /* flag indicating if this file should be closed on chd_close() */ + chd_header header; /* header, extracted from file */ + + chd_file * parent; /* pointer to parent file, or NULL */ + + map_entry * map; /* array of map entries */ + + UINT8 * cache; /* hunk cache pointer */ + UINT32 cachehunk; /* index of currently cached hunk */ + + UINT8 * compare; /* hunk compare pointer */ + UINT32 comparehunk; /* index of current compare data */ + + UINT8 * compressed; /* pointer to buffer for compressed data */ + const codec_interface * codecintf; /* interface to the codec */ + void * codecdata; /* opaque pointer to codec data */ + + crcmap_entry * crcmap; /* CRC map entries */ + crcmap_entry * crcfree; /* free list CRC entries */ + crcmap_entry ** crctable; /* table of CRC entries */ + + UINT32 maxhunk; /* maximum hunk accessed */ + + UINT8 compressing; /* are we compressing? */ + struct MD5Context compmd5; /* running MD5 during compression */ + struct sha1_ctx compsha1; /* running SHA1 during compression */ + UINT32 comphunk; /* next hunk we will compress */ + + UINT8 verifying; /* are we verifying? */ + struct MD5Context vermd5; /* running MD5 during verification */ + struct sha1_ctx versha1; /* running SHA1 during verification */ + UINT32 verhunk; /* next hunk we will verify */ + + osd_work_queue * workqueue; /* pointer to work queue for async operations */ + osd_work_item * workitem; /* active work item, or NULL if none */ + UINT32 async_hunknum; /* hunk index for asynchronous operations */ + void * async_buffer; /* buffer pointer for asynchronous operations */ +}; + + +/* codec-private data for the ZLIB codec */ +typedef struct _zlib_codec_data zlib_codec_data; +struct _zlib_codec_data +{ + z_stream inflater; + z_stream deflater; + UINT32 * allocptr[MAX_ZLIB_ALLOCS]; +}; + + +/* codec-private data for the A/V codec */ +typedef struct _av_codec_data av_codec_data; +struct _av_codec_data +{ + avcomp_state * compstate; + av_codec_compress_config compress; + av_codec_decompress_config decompress; +}; + + +/* a single metadata hash entry */ +typedef struct _metadata_hash metadata_hash; +struct _metadata_hash +{ + UINT8 tag[4]; /* tag of the metadata in big-endian */ + UINT8 sha1[CHD_SHA1_BYTES]; /* hash */ +}; + + + +/*************************************************************************** + GLOBAL VARIABLES +***************************************************************************/ + +static const UINT8 nullmd5[CHD_MD5_BYTES] = { 0 }; +static const UINT8 nullsha1[CHD_SHA1_BYTES] = { 0 }; + + + +/*************************************************************************** + PROTOTYPES +***************************************************************************/ + +/* internal async operations */ +static void *async_read_callback(void *param, int threadid); +static void *async_write_callback(void *param, int threadid); + +/* internal header operations */ +static chd_error header_validate(const chd_header *header); +static chd_error header_read(core_file *file, chd_header *header); +static chd_error header_write(core_file *file, const chd_header *header); + +/* internal hunk read/write */ +static chd_error hunk_read_into_cache(chd_file *chd, UINT32 hunknum); +static chd_error hunk_read_into_memory(chd_file *chd, UINT32 hunknum, UINT8 *dest); +static chd_error hunk_write_from_memory(chd_file *chd, UINT32 hunknum, const UINT8 *src); + +/* internal map access */ +static chd_error map_write_initial(core_file *file, chd_file *parent, const chd_header *header); +static chd_error map_read(chd_file *chd); + +/* internal CRC map access */ +static void crcmap_init(chd_file *chd, int prepopulate); +static void crcmap_add_entry(chd_file *chd, UINT32 hunknum); +static UINT32 crcmap_find_hunk(chd_file *chd, UINT32 hunknum, UINT32 crc, const UINT8 *rawdata); + +/* metadata management */ +static chd_error metadata_find_entry(chd_file *chd, UINT32 metatag, UINT32 metaindex, metadata_entry *metaentry); +static chd_error metadata_set_previous_next(chd_file *chd, UINT64 prevoffset, UINT64 nextoffset); +static chd_error metadata_set_length(chd_file *chd, UINT64 offset, UINT32 length); +static chd_error metadata_compute_hash(chd_file *chd, const UINT8 *rawsha1, UINT8 *finalsha1); +static int CLIB_DECL metadata_hash_compare(const void *elem1, const void *elem2); + +/* zlib compression codec */ +static chd_error zlib_codec_init(chd_file *chd); +static void zlib_codec_free(chd_file *chd); +static chd_error zlib_codec_compress(chd_file *chd, const void *src, UINT32 *length); +static chd_error zlib_codec_decompress(chd_file *chd, UINT32 srclength, void *dest); +static voidpf zlib_fast_alloc(voidpf opaque, uInt items, uInt size); +static void zlib_fast_free(voidpf opaque, voidpf address); + +/* A/V compression codec */ +static chd_error av_codec_init(chd_file *chd); +static void av_codec_free(chd_file *chd); +static chd_error av_codec_compress(chd_file *chd, const void *src, UINT32 *length); +static chd_error av_codec_decompress(chd_file *chd, UINT32 srclength, void *dest); +static chd_error av_codec_config(chd_file *chd, int param, void *config); +static chd_error av_codec_postinit(chd_file *chd); + + + +/*************************************************************************** + CODEC INTERFACES +***************************************************************************/ + +static const codec_interface codec_interfaces[] = +{ + /* "none" or no compression */ + { + CHDCOMPRESSION_NONE, + "none", + FALSE, + NULL, + NULL, + NULL, + NULL, + NULL + }, + + /* standard zlib compression */ + { + CHDCOMPRESSION_ZLIB, + "zlib", + FALSE, + zlib_codec_init, + zlib_codec_free, + zlib_codec_compress, + zlib_codec_decompress, + NULL + }, + + /* zlib+ compression */ + { + CHDCOMPRESSION_ZLIB_PLUS, + "zlib+", + FALSE, + zlib_codec_init, + zlib_codec_free, + zlib_codec_compress, + zlib_codec_decompress, + NULL + }, + + /* a/v compression */ + { + CHDCOMPRESSION_AV, + "A/V", + TRUE, + av_codec_init, + av_codec_free, + av_codec_compress, + av_codec_decompress, + av_codec_config + } +}; + + + +/*************************************************************************** + INLINE FUNCTIONS +***************************************************************************/ + +/*------------------------------------------------- + get_bigendian_uint64 - fetch a UINT64 from + the data stream in bigendian order +-------------------------------------------------*/ + +INLINE UINT64 get_bigendian_uint64(const UINT8 *base) +{ + return ((UINT64)base[0] << 56) | ((UINT64)base[1] << 48) | ((UINT64)base[2] << 40) | ((UINT64)base[3] << 32) | + ((UINT64)base[4] << 24) | ((UINT64)base[5] << 16) | ((UINT64)base[6] << 8) | (UINT64)base[7]; +} + + +/*------------------------------------------------- + put_bigendian_uint64 - write a UINT64 to + the data stream in bigendian order +-------------------------------------------------*/ + +INLINE void put_bigendian_uint64(UINT8 *base, UINT64 value) +{ + base[0] = value >> 56; + base[1] = value >> 48; + base[2] = value >> 40; + base[3] = value >> 32; + base[4] = value >> 24; + base[5] = value >> 16; + base[6] = value >> 8; + base[7] = value; +} + + +/*------------------------------------------------- + get_bigendian_uint32 - fetch a UINT32 from + the data stream in bigendian order +-------------------------------------------------*/ + +INLINE UINT32 get_bigendian_uint32(const UINT8 *base) +{ + return (base[0] << 24) | (base[1] << 16) | (base[2] << 8) | base[3]; +} + + +/*------------------------------------------------- + put_bigendian_uint32 - write a UINT32 to + the data stream in bigendian order +-------------------------------------------------*/ + +INLINE void put_bigendian_uint32(UINT8 *base, UINT32 value) +{ + base[0] = value >> 24; + base[1] = value >> 16; + base[2] = value >> 8; + base[3] = value; +} + + +/*------------------------------------------------- + get_bigendian_uint16 - fetch a UINT16 from + the data stream in bigendian order +-------------------------------------------------*/ + +INLINE UINT16 get_bigendian_uint16(const UINT8 *base) +{ + return (base[0] << 8) | base[1]; +} + + +/*------------------------------------------------- + put_bigendian_uint16 - write a UINT16 to + the data stream in bigendian order +-------------------------------------------------*/ + +INLINE void put_bigendian_uint16(UINT8 *base, UINT16 value) +{ + base[0] = value >> 8; + base[1] = value; +} + + +/*------------------------------------------------- + map_extract - extract a single map + entry from the datastream +-------------------------------------------------*/ + +INLINE void map_extract(const UINT8 *base, map_entry *entry) +{ + entry->offset = get_bigendian_uint64(&base[0]); + entry->crc = get_bigendian_uint32(&base[8]); + entry->length = get_bigendian_uint16(&base[12]) | (base[14] << 16); + entry->flags = base[15]; +} + + +/*------------------------------------------------- + map_assemble - write a single map + entry to the datastream +-------------------------------------------------*/ + +INLINE void map_assemble(UINT8 *base, map_entry *entry) +{ + put_bigendian_uint64(&base[0], entry->offset); + put_bigendian_uint32(&base[8], entry->crc); + put_bigendian_uint16(&base[12], entry->length); + base[14] = entry->length >> 16; + base[15] = entry->flags; +} + + +/*------------------------------------------------- + map_extract_old - extract a single map + entry in old format from the datastream +-------------------------------------------------*/ + +INLINE void map_extract_old(const UINT8 *base, map_entry *entry, UINT32 hunkbytes) +{ + entry->offset = get_bigendian_uint64(&base[0]); + entry->crc = 0; + entry->length = entry->offset >> 44; + entry->flags = MAP_ENTRY_FLAG_NO_CRC | ((entry->length == hunkbytes) ? MAP_ENTRY_TYPE_UNCOMPRESSED : MAP_ENTRY_TYPE_COMPRESSED); +#ifdef __MWERKS__ + entry->offset = entry->offset & 0x00000FFFFFFFFFFFLL; +#else + entry->offset = (entry->offset << 20) >> 20; +#endif +} + + +/*------------------------------------------------- + queue_async_operation - queue a new work + item +-------------------------------------------------*/ + +INLINE int queue_async_operation(chd_file *chd, osd_work_callback callback) +{ + /* if no queue yet, create one on the fly */ + if (chd->workqueue == NULL) + { + chd->workqueue = osd_work_queue_alloc(WORK_QUEUE_FLAG_IO); + if (chd->workqueue == NULL) + return FALSE; + } + + /* make sure we cleared out the previous item */ + if (chd->workitem != NULL) + return FALSE; + + /* create a new work item to run the job */ + chd->workitem = osd_work_item_queue(chd->workqueue, callback, chd, 0); + if (chd->workitem == NULL) + return FALSE; + + return TRUE; +} + + +/*------------------------------------------------- + wait_for_pending_async - wait for any pending + async +-------------------------------------------------*/ + +INLINE void wait_for_pending_async(chd_file *chd) +{ + /* if something is pending, wait for it */ + if (chd->workitem != NULL) + { + /* 10 seconds should be enough for anything! */ + int wait_successful = osd_work_item_wait(chd->workitem, 10 * osd_ticks_per_second()); + if (!wait_successful) + osd_break_into_debugger("Pending async operation never completed!"); + } +} + + + +/*************************************************************************** + CHD FILE MANAGEMENT +***************************************************************************/ + +/*------------------------------------------------- + chd_create_file - create a new CHD file +-------------------------------------------------*/ + +chd_error chd_create_file(core_file *file, UINT64 logicalbytes, UINT32 hunkbytes, UINT32 compression, chd_file *parent) +{ + chd_file *newchd = NULL; + chd_header header; + chd_error err; + int intfnum; + + /* verify parameters */ + if (file == NULL) + EARLY_EXIT(err = CHDERR_INVALID_PARAMETER); + if (parent == NULL && (logicalbytes == 0 || hunkbytes == 0)) + EARLY_EXIT(err = CHDERR_INVALID_PARAMETER); + + /* verify the compression type */ + for (intfnum = 0; intfnum < ARRAY_LENGTH(codec_interfaces); intfnum++) + if (codec_interfaces[intfnum].compression == compression) + break; + if (intfnum == ARRAY_LENGTH(codec_interfaces)) + EARLY_EXIT(err = CHDERR_INVALID_PARAMETER); + + /* if we have a parent, the sizes come from there */ + if (parent != NULL) + { + logicalbytes = parent->header.logicalbytes; + hunkbytes = parent->header.hunkbytes; + } + + /* if we have a parent, it must be V3 or later */ + if (parent != NULL && parent->header.version < 3) + EARLY_EXIT(err = CHDERR_UNSUPPORTED_VERSION); + + /* build the header */ + memset(&header, 0, sizeof(header)); + header.length = CHD_V4_HEADER_SIZE; + header.version = CHD_HEADER_VERSION; + header.flags = CHDFLAGS_IS_WRITEABLE; + header.compression = compression; + header.hunkbytes = hunkbytes; + header.totalhunks = (logicalbytes + hunkbytes - 1) / hunkbytes; + header.logicalbytes = logicalbytes; + + /* tweaks if there is a parent */ + if (parent != NULL) + { + header.flags |= CHDFLAGS_HAS_PARENT; + memcpy(&header.parentmd5[0], &parent->header.md5[0], sizeof(header.parentmd5)); + memcpy(&header.parentsha1[0], &parent->header.sha1[0], sizeof(header.parentsha1)); + } + + /* validate it */ + err = header_validate(&header); + if (err != CHDERR_NONE) + EARLY_EXIT(err); + + /* write the resulting header */ + err = header_write(file, &header); + if (err != CHDERR_NONE) + EARLY_EXIT(err); + + /* create an initial map */ + err = map_write_initial(file, parent, &header); + if (err != CHDERR_NONE) + EARLY_EXIT(err); + + /* if we have a parent, clone the metadata */ + if (parent != NULL) + { + /* open the new CHD via the standard mechanism */ + err = chd_open_file(file, CHD_OPEN_READWRITE, parent, &newchd); + if (err != CHDERR_NONE) + EARLY_EXIT(err); + + /* close the metadata */ + err = chd_clone_metadata(parent, newchd); + if (err != CHDERR_NONE) + EARLY_EXIT(err); + + /* close the CHD */ + chd_close(newchd); + } + + return CHDERR_NONE; + +cleanup: + if (newchd != NULL) + chd_close(newchd); + return err; +} + + +/*------------------------------------------------- + chd_open_file - open a CHD file for access +-------------------------------------------------*/ + +chd_error chd_open_file(core_file *file, int mode, chd_file *parent, chd_file **chd) +{ + chd_file *newchd = NULL; + chd_error err; + int intfnum; + + /* verify parameters */ + if (file == NULL) + EARLY_EXIT(err = CHDERR_INVALID_PARAMETER); + + /* punt if invalid parent */ + if (parent != NULL && parent->cookie != COOKIE_VALUE) + EARLY_EXIT(err = CHDERR_INVALID_PARAMETER); + + /* allocate memory for the final result */ + newchd = (chd_file *)malloc(sizeof(**chd)); + if (newchd == NULL) + EARLY_EXIT(err = CHDERR_OUT_OF_MEMORY); + memset(newchd, 0, sizeof(*newchd)); + newchd->cookie = COOKIE_VALUE; + newchd->parent = parent; + newchd->file = file; + + /* now attempt to read the header */ + err = header_read(newchd->file, &newchd->header); + if (err != CHDERR_NONE) + EARLY_EXIT(err); + + /* validate the header */ + err = header_validate(&newchd->header); + if (err != CHDERR_NONE) + EARLY_EXIT(err); + + /* make sure we don't open a read-only file writeable */ + if (mode == CHD_OPEN_READWRITE && !(newchd->header.flags & CHDFLAGS_IS_WRITEABLE)) + EARLY_EXIT(err = CHDERR_FILE_NOT_WRITEABLE); + + /* also, never open an older version writeable */ + if (mode == CHD_OPEN_READWRITE && newchd->header.version < CHD_HEADER_VERSION) + EARLY_EXIT(err = CHDERR_UNSUPPORTED_VERSION); + + /* if we need a parent, make sure we have one */ + if (parent == NULL && (newchd->header.flags & CHDFLAGS_HAS_PARENT)) + EARLY_EXIT(err = CHDERR_REQUIRES_PARENT); + + /* make sure we have a valid parent */ + if (parent != NULL) + { + /* check MD5 if it isn't empty */ + if (memcmp(nullmd5, newchd->header.parentmd5, sizeof(newchd->header.parentmd5)) != 0 && + memcmp(nullmd5, newchd->parent->header.md5, sizeof(newchd->parent->header.md5)) != 0 && + memcmp(newchd->parent->header.md5, newchd->header.parentmd5, sizeof(newchd->header.parentmd5)) != 0) + EARLY_EXIT(err = CHDERR_INVALID_PARENT); + + /* check SHA1 if it isn't empty */ + if (memcmp(nullsha1, newchd->header.parentsha1, sizeof(newchd->header.parentsha1)) != 0 && + memcmp(nullsha1, newchd->parent->header.sha1, sizeof(newchd->parent->header.sha1)) != 0 && + memcmp(newchd->parent->header.sha1, newchd->header.parentsha1, sizeof(newchd->header.parentsha1)) != 0) + EARLY_EXIT(err = CHDERR_INVALID_PARENT); + } + + /* now read the hunk map */ + err = map_read(newchd); + if (err != CHDERR_NONE) + EARLY_EXIT(err); + + /* allocate and init the hunk cache */ + newchd->cache = (UINT8 *)malloc(newchd->header.hunkbytes); + newchd->compare = (UINT8 *)malloc(newchd->header.hunkbytes); + if (newchd->cache == NULL || newchd->compare == NULL) + EARLY_EXIT(err = CHDERR_OUT_OF_MEMORY); + newchd->cachehunk = ~0; + newchd->comparehunk = ~0; + + /* allocate the temporary compressed buffer */ + newchd->compressed = (UINT8 *)malloc(newchd->header.hunkbytes); + if (newchd->compressed == NULL) + EARLY_EXIT(err = CHDERR_OUT_OF_MEMORY); + + /* find the codec interface */ + for (intfnum = 0; intfnum < ARRAY_LENGTH(codec_interfaces); intfnum++) + if (codec_interfaces[intfnum].compression == newchd->header.compression) + { + newchd->codecintf = &codec_interfaces[intfnum]; + break; + } + if (intfnum == ARRAY_LENGTH(codec_interfaces)) + EARLY_EXIT(err = CHDERR_UNSUPPORTED_FORMAT); + + /* initialize the codec */ + if (newchd->codecintf->init != NULL) + err = (*newchd->codecintf->init)(newchd); + if (err != CHDERR_NONE) + EARLY_EXIT(err); + + /* all done */ + *chd = newchd; + return CHDERR_NONE; + +cleanup: + if (newchd != NULL) + chd_close(newchd); + return err; +} + + +/*------------------------------------------------- + chd_create - create a CHD file by + filename +-------------------------------------------------*/ + +chd_error chd_create(const char *filename, UINT64 logicalbytes, UINT32 hunkbytes, UINT32 compression, chd_file *parent) +{ + core_file *file = NULL; + file_error filerr; + chd_error chderr; + + filerr = core_fopen(filename, OPEN_FLAG_READ | OPEN_FLAG_WRITE | OPEN_FLAG_CREATE, &file); + if (filerr != FILERR_NONE) + { + chderr = CHDERR_FILE_NOT_FOUND; + goto cleanup; + } + + chderr = chd_create_file(file, logicalbytes, hunkbytes, compression, parent); + if (chderr != CHDERR_NONE) + goto cleanup; + +cleanup: + if (file != NULL) + core_fclose(file); + return chderr; +} + + +/*------------------------------------------------- + chd_open - open a CHD file by + filename +-------------------------------------------------*/ + +chd_error chd_open(const char *filename, int mode, chd_file *parent, chd_file **chd) +{ + chd_error err; + file_error filerr; + core_file *file = NULL; + UINT32 openflags; + + /* choose the proper mode */ + switch(mode) + { + case CHD_OPEN_READ: + openflags = OPEN_FLAG_READ; + break; + + case CHD_OPEN_READWRITE: + openflags = OPEN_FLAG_READ | OPEN_FLAG_WRITE; + break; + + default: + err = CHDERR_INVALID_PARAMETER; + goto cleanup; + } + + /* open the file */ + filerr = core_fopen(filename, openflags, &file); + if (filerr != FILERR_NONE) + { + err = CHDERR_FILE_NOT_FOUND; + goto cleanup; + } + + /* now open the CHD */ + err = chd_open_file(file, mode, parent, chd); + if (err != CHDERR_NONE) + goto cleanup; + + /* we now own this file */ + (*chd)->owns_file = TRUE; + +cleanup: + if ((err != CHDERR_NONE) && (file != NULL)) + core_fclose(file); + return err; +} + + +/*------------------------------------------------- + chd_close - close a CHD file for access +-------------------------------------------------*/ + +void chd_close(chd_file *chd) +{ + /* punt if NULL or invalid */ + if (chd == NULL || chd->cookie != COOKIE_VALUE) + return; + + /* wait for any pending async operations */ + wait_for_pending_async(chd); + + /* kill the work queue and any work item */ + if (chd->workitem != NULL) + osd_work_item_release(chd->workitem); + if (chd->workqueue != NULL) + osd_work_queue_free(chd->workqueue); + + /* deinit the codec */ + if (chd->codecintf != NULL && chd->codecintf->free != NULL) + (*chd->codecintf->free)(chd); + + /* free the compressed data buffer */ + if (chd->compressed != NULL) + free(chd->compressed); + + /* free the hunk cache and compare data */ + if (chd->compare != NULL) + free(chd->compare); + if (chd->cache != NULL) + free(chd->cache); + + /* free the hunk map */ + if (chd->map != NULL) + free(chd->map); + + /* free the CRC table */ + if (chd->crctable != NULL) + free(chd->crctable); + + /* free the CRC map */ + if (chd->crcmap != NULL) + free(chd->crcmap); + + /* close the file */ + if (chd->owns_file && chd->file != NULL) + core_fclose(chd->file); + + if (PRINTF_MAX_HUNK) printf("Max hunk = %d/%d\n", chd->maxhunk, chd->header.totalhunks); + + /* free our memory */ + free(chd); +} + + +/*------------------------------------------------- + chd_core_file - return the associated + core_file +-------------------------------------------------*/ + +core_file *chd_core_file(chd_file *chd) +{ + return chd->file; +} + + +/*------------------------------------------------- + chd_error_string - return an error string for + the given CHD error +-------------------------------------------------*/ + +const char *chd_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_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 "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_NO_ASYNC_OPERATION: return "no async operation in progress"; + case CHDERR_UNSUPPORTED_FORMAT: return "unsupported format"; + default: return "undocumented error"; + } +} + + + +/*************************************************************************** + CHD HEADER MANAGEMENT +***************************************************************************/ + +/*------------------------------------------------- + chd_get_header - return a pointer to the + extracted header data +-------------------------------------------------*/ + +const chd_header *chd_get_header(chd_file *chd) +{ + /* punt if NULL or invalid */ + if (chd == NULL || chd->cookie != COOKIE_VALUE) + return NULL; + + return &chd->header; +} + + +/*------------------------------------------------- + chd_set_header_file - write the current header to + the file +-------------------------------------------------*/ + +chd_error chd_set_header_file(core_file *file, const chd_header *header) +{ + chd_header oldheader; + chd_error err; + + /* validate the header */ + err = header_validate(header); + if (err != CHDERR_NONE) + EARLY_EXIT(err); + + /* read the old header */ + err = header_read(file, &oldheader); + if (err != CHDERR_NONE) + EARLY_EXIT(err); + + /* make sure we're only making valid changes */ + if (header->length != oldheader.length) + EARLY_EXIT(err = CHDERR_INVALID_PARAMETER); + if (header->version != oldheader.version) + EARLY_EXIT(err = CHDERR_INVALID_PARAMETER); + if (header->compression != oldheader.compression) + EARLY_EXIT(err = CHDERR_INVALID_PARAMETER); + if (header->hunkbytes != oldheader.hunkbytes) + EARLY_EXIT(err = CHDERR_INVALID_PARAMETER); + if (header->totalhunks != oldheader.totalhunks) + EARLY_EXIT(err = CHDERR_INVALID_PARAMETER); + if (header->metaoffset != oldheader.metaoffset) + EARLY_EXIT(err = CHDERR_INVALID_PARAMETER); + if (header->obsolete_hunksize != oldheader.obsolete_hunksize) + EARLY_EXIT(err = CHDERR_INVALID_PARAMETER); + + /* write the new header */ + err = header_write(file, header); + if (err != CHDERR_NONE) + EARLY_EXIT(err); + + return CHDERR_NONE; + +cleanup: + return err; +} + + +/*------------------------------------------------- + chd_set_header - write the current + header to the file +-------------------------------------------------*/ + +chd_error chd_set_header(const char *filename, const chd_header *header) +{ + core_file *file = NULL; + file_error filerr; + chd_error err; + + filerr = core_fopen(filename, OPEN_FLAG_READ | OPEN_FLAG_WRITE, &file); + if (filerr != FILERR_NONE) + { + err = CHDERR_FILE_NOT_FOUND; + goto cleanup; + } + + err = chd_set_header_file(file, header); + if (err != CHDERR_NONE) + goto cleanup; + +cleanup: + if (file != NULL) + core_fclose(file); + return err; +} + + + +/*************************************************************************** + CORE DATA READ/WRITE +***************************************************************************/ + +/*------------------------------------------------- + chd_read - read a single hunk from the CHD + file +-------------------------------------------------*/ + +chd_error chd_read(chd_file *chd, UINT32 hunknum, void *buffer) +{ + /* punt if NULL or invalid */ + if (chd == NULL || chd->cookie != COOKIE_VALUE) + return CHDERR_INVALID_PARAMETER; + + /* if we're past the end, fail */ + if (hunknum >= chd->header.totalhunks) + return CHDERR_HUNK_OUT_OF_RANGE; + + /* wait for any pending async operations */ + wait_for_pending_async(chd); + + /* perform the read */ + return hunk_read_into_memory(chd, hunknum, (UINT8 *)buffer); +} + + +/*------------------------------------------------- + chd_read_async - read a single hunk from the + CHD file asynchronously +-------------------------------------------------*/ + +chd_error chd_read_async(chd_file *chd, UINT32 hunknum, void *buffer) +{ + /* punt if NULL or invalid */ + if (chd == NULL || chd->cookie != COOKIE_VALUE) + return CHDERR_INVALID_PARAMETER; + + /* if we're past the end, fail */ + if (hunknum >= chd->header.totalhunks) + return CHDERR_HUNK_OUT_OF_RANGE; + + /* wait for any pending async operations */ + wait_for_pending_async(chd); + + /* set the async parameters */ + chd->async_hunknum = hunknum; + chd->async_buffer = buffer; + + /* queue the work item */ + if (queue_async_operation(chd, async_read_callback)) + return CHDERR_OPERATION_PENDING; + + /* if we fail, fall back on the sync version */ + return chd_read(chd, hunknum, buffer); +} + + +/*------------------------------------------------- + chd_write - write a single hunk to the CHD + file +-------------------------------------------------*/ + +chd_error chd_write(chd_file *chd, UINT32 hunknum, const void *buffer) +{ + /* punt if NULL or invalid */ + if (chd == NULL || chd->cookie != COOKIE_VALUE) + return CHDERR_INVALID_PARAMETER; + + /* if we're past the end, fail */ + if (hunknum >= chd->header.totalhunks) + return CHDERR_HUNK_OUT_OF_RANGE; + + /* wait for any pending async operations */ + wait_for_pending_async(chd); + + /* then write out the hunk */ + return hunk_write_from_memory(chd, hunknum, (const UINT8 *)buffer); +} + + +/*------------------------------------------------- + chd_write_async - write a single hunk to the + CHD file asynchronously +-------------------------------------------------*/ + +chd_error chd_write_async(chd_file *chd, UINT32 hunknum, const void *buffer) +{ + /* punt if NULL or invalid */ + if (chd == NULL || chd->cookie != COOKIE_VALUE) + return CHDERR_INVALID_PARAMETER; + + /* if we're past the end, fail */ + if (hunknum >= chd->header.totalhunks) + return CHDERR_HUNK_OUT_OF_RANGE; + + /* wait for any pending async operations */ + wait_for_pending_async(chd); + + /* set the async parameters */ + chd->async_hunknum = hunknum; + chd->async_buffer = (void *)buffer; + + /* queue the work item */ + if (queue_async_operation(chd, async_write_callback)) + return CHDERR_OPERATION_PENDING; + + /* if we fail, fall back on the sync version */ + return chd_write(chd, hunknum, buffer); +} + + +/*------------------------------------------------- + chd_async_complete - get the result of a + completed work item and clear it out of the + system +-------------------------------------------------*/ + +chd_error chd_async_complete(chd_file *chd) +{ + void *result; + + /* if nothing present, return an error */ + if (chd->workitem == NULL) + return CHDERR_NO_ASYNC_OPERATION; + + /* wait for the work to complete */ + wait_for_pending_async(chd); + + /* get the result and free the work item */ + result = osd_work_item_result(chd->workitem); + osd_work_item_release(chd->workitem); + chd->workitem = NULL; + + return (chd_error)(ptrdiff_t)result; +} + + + +/*************************************************************************** + METADATA MANAGEMENT +***************************************************************************/ + +/*------------------------------------------------- + chd_get_metadata - get the indexed metadata + of the given type +-------------------------------------------------*/ + +chd_error chd_get_metadata(chd_file *chd, UINT32 searchtag, UINT32 searchindex, void *output, UINT32 outputlen, UINT32 *resultlen, UINT32 *resulttag, UINT8 *resultflags) +{ + metadata_entry metaentry; + chd_error err; + UINT32 count; + + /* wait for any pending async operations */ + wait_for_pending_async(chd); + + /* if we didn't find it, just return */ + err = metadata_find_entry(chd, searchtag, searchindex, &metaentry); + if (err != CHDERR_NONE) + { + /* unless we're an old version and they are requesting hard disk metadata */ + if (chd->header.version < 3 && (searchtag == HARD_DISK_METADATA_TAG || searchtag == CHDMETATAG_WILDCARD) && searchindex == 0) + { + char faux_metadata[256]; + UINT32 faux_length; + + /* fill in the faux metadata */ + sprintf(faux_metadata, HARD_DISK_METADATA_FORMAT, chd->header.obsolete_cylinders, chd->header.obsolete_heads, chd->header.obsolete_sectors, chd->header.hunkbytes / chd->header.obsolete_hunksize); + faux_length = (UINT32)strlen(faux_metadata) + 1; + + /* copy the metadata itself */ + memcpy(output, faux_metadata, MIN(outputlen, faux_length)); + + /* return the length of the data and the tag */ + if (resultlen != NULL) + *resultlen = faux_length; + if (resulttag != NULL) + *resulttag = HARD_DISK_METADATA_TAG; + return CHDERR_NONE; + } + return err; + } + + /* read the metadata */ + outputlen = MIN(outputlen, metaentry.length); + core_fseek(chd->file, metaentry.offset + METADATA_HEADER_SIZE, SEEK_SET); + count = core_fread(chd->file, output, outputlen); + if (count != outputlen) + return CHDERR_READ_ERROR; + + /* return the length of the data and the tag */ + if (resultlen != NULL) + *resultlen = metaentry.length; + if (resulttag != NULL) + *resulttag = metaentry.metatag; + if (resultflags != NULL) + *resultflags = metaentry.flags; + return CHDERR_NONE; +} + + +/*------------------------------------------------- + chd_set_metadata - write the indexed metadata + of the given type +-------------------------------------------------*/ + +chd_error chd_set_metadata(chd_file *chd, UINT32 metatag, UINT32 metaindex, const void *inputbuf, UINT32 inputlen, UINT8 flags) +{ + UINT8 raw_meta_header[METADATA_HEADER_SIZE]; + metadata_entry metaentry = { 0 }; + chd_error err; + UINT64 offset; + UINT32 count; + + /* if the disk is an old version, punt */ + if (chd->header.version < 3) + return CHDERR_NOT_SUPPORTED; + + /* if the disk isn't writeable, punt */ + if (!(chd->header.flags & CHDFLAGS_IS_WRITEABLE)) + return CHDERR_FILE_NOT_WRITEABLE; + + /* must write at least 1 byte */ + if (inputlen < 1) + return CHDERR_INVALID_PARAMETER; + + /* no more than 16MB */ + if (inputlen >= 16 * 1024 * 1024) + return CHDERR_INVALID_PARAMETER; + + /* wait for any pending async operations */ + wait_for_pending_async(chd); + + /* find the entry if it already exists */ + err = metadata_find_entry(chd, metatag, metaindex, &metaentry); + + /* if it's there and it fits, just overwrite */ + if (err == CHDERR_NONE && inputlen <= metaentry.length) + { + /* overwrite the original data with our new input data */ + core_fseek(chd->file, metaentry.offset + METADATA_HEADER_SIZE, SEEK_SET); + count = core_fwrite(chd->file, inputbuf, inputlen); + if (count != inputlen) + { + err = CHDERR_WRITE_ERROR; + goto update; + } + + /* if the lengths don't match, we need to update the length in our header */ + if (inputlen != metaentry.length) + err = metadata_set_length(chd, metaentry.offset, inputlen); + goto update; + } + + /* if we already have an entry, unlink it */ + if (err == CHDERR_NONE) + { + err = metadata_set_previous_next(chd, metaentry.prev, metaentry.next); + if (err != CHDERR_NONE) + goto update; + } + + /* now build us a new entry */ + put_bigendian_uint32(&raw_meta_header[0], metatag); + put_bigendian_uint32(&raw_meta_header[4], (inputlen & 0x00ffffff) | (flags << 24)); + put_bigendian_uint64(&raw_meta_header[8], (err == CHDERR_NONE) ? metaentry.next : 0); + + /* write out the new header */ + offset = core_fsize(chd->file); + core_fseek(chd->file, offset, SEEK_SET); + count = core_fwrite(chd->file, raw_meta_header, sizeof(raw_meta_header)); + if (count != sizeof(raw_meta_header)) + { + err = CHDERR_WRITE_ERROR; + goto update; + } + + /* follow that with the data */ + core_fseek(chd->file, offset + METADATA_HEADER_SIZE, SEEK_SET); + count = core_fwrite(chd->file, inputbuf, inputlen); + if (count != inputlen) + { + err = CHDERR_WRITE_ERROR; + goto update; + } + + /* set the previous entry to point to us */ + err = metadata_set_previous_next(chd, metaentry.prev, offset); + +update: + /* update the hash */ + if (metadata_compute_hash(chd, chd->header.rawsha1, chd->header.sha1) == CHDERR_NONE) + err = header_write(chd->file, &chd->header); + return err; +} + + +/*------------------------------------------------- + chd_clone_metadata - clone the metadata from + one CHD to a second +-------------------------------------------------*/ + +chd_error chd_clone_metadata(chd_file *source, chd_file *dest) +{ + UINT32 metatag, metasize, metaindex; + UINT8 metabuffer[1024]; + UINT8 metaflags; + chd_error err; + + /* clone the metadata */ + for (metaindex = 0; ; metaindex++) + { + /* fetch the next piece of metadata */ + err = chd_get_metadata(source, CHDMETATAG_WILDCARD, metaindex, metabuffer, sizeof(metabuffer), &metasize, &metatag, &metaflags); + if (err != CHDERR_NONE) + { + if (err == CHDERR_METADATA_NOT_FOUND) + err = CHDERR_NONE; + break; + } + + /* if that fit, just write it back from the temporary buffer */ + if (metasize <= sizeof(metabuffer)) + { + /* write it to the target */ + err = chd_set_metadata(dest, metatag, CHD_METAINDEX_APPEND, metabuffer, metasize, metaflags); + if (err != CHDERR_NONE) + break; + } + + /* otherwise, allocate a bigger temporary buffer */ + else + { + UINT8 *allocbuffer = (UINT8 *)malloc(metasize); + if (allocbuffer == NULL) + { + err = CHDERR_OUT_OF_MEMORY; + break; + } + + /* re-read the whole thing */ + err = chd_get_metadata(source, CHDMETATAG_WILDCARD, metaindex, allocbuffer, metasize, &metasize, &metatag, &metaflags); + if (err != CHDERR_NONE) + { + free(allocbuffer); + break; + } + + /* write it to the target */ + err = chd_set_metadata(dest, metatag, CHD_METAINDEX_APPEND, allocbuffer, metasize, metaflags); + free(allocbuffer); + if (err != CHDERR_NONE) + break; + } + } + return err; +} + + + +/*************************************************************************** + COMPRESSION MANAGEMENT +***************************************************************************/ + +/*------------------------------------------------- + chd_compress_begin - begin compressing data + into a CHD +-------------------------------------------------*/ + +chd_error chd_compress_begin(chd_file *chd) +{ + chd_error err; + + /* verify parameters */ + if (chd == NULL) + return CHDERR_INVALID_PARAMETER; + + /* wait for any pending async operations */ + wait_for_pending_async(chd); + + /* mark the CHD writeable and write the updated header */ + chd->header.flags |= CHDFLAGS_IS_WRITEABLE; + err = header_write(chd->file, &chd->header); + if (err != CHDERR_NONE) + return err; + + /* create CRC maps for the new CHD and the parent */ + crcmap_init(chd, FALSE); + if (chd->parent != NULL) + crcmap_init(chd->parent, TRUE); + + /* init the MD5/SHA1 computations */ + MD5Init(&chd->compmd5); + sha1_init(&chd->compsha1); + chd->compressing = TRUE; + chd->comphunk = 0; + + return CHDERR_NONE; +} + + +/*------------------------------------------------- + chd_compress_hunk - append data to a CHD + that is being compressed +-------------------------------------------------*/ + +chd_error chd_compress_hunk(chd_file *chd, const void *data, double *curratio) +{ + UINT32 thishunk = chd->comphunk++; + UINT64 sourceoffset = (UINT64)thishunk * (UINT64)chd->header.hunkbytes; + UINT32 bytestochecksum; + const void *crcdata; + chd_error err; + + /* error if in the wrong state */ + if (!chd->compressing) + return CHDERR_INVALID_STATE; + + /* write out the hunk */ + err = hunk_write_from_memory(chd, thishunk, (const UINT8 *)data); + if (err != CHDERR_NONE) + return err; + + /* if we are lossy, then we need to use the decompressed version in */ + /* the cache as our MD5/SHA1 source */ + crcdata = (chd->codecintf->lossy || data == NULL) ? chd->cache : data; + + /* update the MD5/SHA1 */ + bytestochecksum = chd->header.hunkbytes; + if (sourceoffset + chd->header.hunkbytes > chd->header.logicalbytes) + { + if (sourceoffset >= chd->header.logicalbytes) + bytestochecksum = 0; + else + bytestochecksum = chd->header.logicalbytes - sourceoffset; + } + if (bytestochecksum > 0) + { + MD5Update(&chd->compmd5, (const unsigned char *)crcdata, bytestochecksum); + sha1_update(&chd->compsha1, bytestochecksum, (const UINT8 *)crcdata); + } + + /* update our CRC map */ + if ((chd->map[thishunk].flags & MAP_ENTRY_FLAG_TYPE_MASK) != MAP_ENTRY_TYPE_SELF_HUNK && + (chd->map[thishunk].flags & MAP_ENTRY_FLAG_TYPE_MASK) != MAP_ENTRY_TYPE_PARENT_HUNK) + crcmap_add_entry(chd, thishunk); + + /* update the ratio */ + if (curratio != NULL) + { + UINT64 curlength = core_fsize(chd->file); + *curratio = 1.0 - (double)curlength / (double)((UINT64)chd->comphunk * (UINT64)chd->header.hunkbytes); + } + + return CHDERR_NONE; +} + + +/*------------------------------------------------- + chd_compress_finish - complete compression of + a CHD +-------------------------------------------------*/ + +chd_error chd_compress_finish(chd_file *chd, int write_protect) +{ + /* error if in the wrong state */ + if (!chd->compressing) + return CHDERR_INVALID_STATE; + + /* compute the final MD5/SHA1 values */ + MD5Final(chd->header.md5, &chd->compmd5); + sha1_final(&chd->compsha1); + sha1_digest(&chd->compsha1, SHA1_DIGEST_SIZE, chd->header.rawsha1); + metadata_compute_hash(chd, chd->header.rawsha1, chd->header.sha1); + + /* turn off the writeable flag and re-write the header */ + if (chd->header.compression != CHDCOMPRESSION_NONE || write_protect) + chd->header.flags &= ~CHDFLAGS_IS_WRITEABLE; + chd->compressing = FALSE; + return header_write(chd->file, &chd->header); +} + + + +/*************************************************************************** + VERIFICATION +***************************************************************************/ + +/*------------------------------------------------- + chd_verify_begin - begin compressing data + into a CHD +-------------------------------------------------*/ + +chd_error chd_verify_begin(chd_file *chd) +{ + /* verify parameters */ + if (chd == NULL) + return CHDERR_INVALID_PARAMETER; + + /* if this is a writeable file image, we can't verify */ + if (chd->header.flags & CHDFLAGS_IS_WRITEABLE) + return CHDERR_CANT_VERIFY; + + /* wait for any pending async operations */ + wait_for_pending_async(chd); + + /* init the MD5/SHA1 computations */ + MD5Init(&chd->vermd5); + sha1_init(&chd->versha1); + chd->verifying = TRUE; + chd->verhunk = 0; + + return CHDERR_NONE; +} + + +/*------------------------------------------------- + chd_verify_hunk - verify the next hunk in + the CHD +-------------------------------------------------*/ + +chd_error chd_verify_hunk(chd_file *chd) +{ + UINT32 thishunk = chd->verhunk++; + UINT64 hunkoffset = (UINT64)thishunk * (UINT64)chd->header.hunkbytes; + map_entry *entry; + chd_error err; + + /* error if in the wrong state */ + if (!chd->verifying) + return CHDERR_INVALID_STATE; + + /* read the hunk into the cache */ + err = hunk_read_into_cache(chd, thishunk); + if (err != CHDERR_NONE) + return err; + + /* update the MD5/SHA1 */ + if (hunkoffset < chd->header.logicalbytes) + { + UINT64 bytestochecksum = MIN(chd->header.hunkbytes, chd->header.logicalbytes - hunkoffset); + if (bytestochecksum > 0) + { + MD5Update(&chd->vermd5, chd->cache, bytestochecksum); + sha1_update(&chd->versha1, bytestochecksum, chd->cache); + } + } + + /* validate the CRC if we have one */ + entry = &chd->map[thishunk]; + if (!(entry->flags & MAP_ENTRY_FLAG_NO_CRC) && entry->crc != crc32(0, chd->cache, chd->header.hunkbytes)) + return CHDERR_DECOMPRESSION_ERROR; + + return CHDERR_NONE; +} + + +/*------------------------------------------------- + chd_verify_finish - finish verification of + the CHD +-------------------------------------------------*/ + +chd_error chd_verify_finish(chd_file *chd, chd_verify_result *result) +{ + /* error if in the wrong state */ + if (!chd->verifying) + return CHDERR_INVALID_STATE; + + /* compute the final MD5 */ + MD5Final(result->md5, &chd->vermd5); + + /* compute the final SHA1 */ + sha1_final(&chd->versha1); + sha1_digest(&chd->versha1, SHA1_DIGEST_SIZE, result->rawsha1); + + /* compute the overall hash including metadata */ + metadata_compute_hash(chd, result->rawsha1, result->sha1); + + /* return an error */ + chd->verifying = FALSE; + return (chd->verhunk < chd->header.totalhunks) ? CHDERR_VERIFY_INCOMPLETE : CHDERR_NONE; +} + + + +/*************************************************************************** + CODEC INTERFACES +***************************************************************************/ + +/*------------------------------------------------- + chd_codec_config - set internal codec + parameters +-------------------------------------------------*/ + +chd_error chd_codec_config(chd_file *chd, int param, void *config) +{ + /* wait for any pending async operations */ + wait_for_pending_async(chd); + + /* if the codec has a configuration callback, call through to it */ + if (chd->codecintf->config != NULL) + return (*chd->codecintf->config)(chd, param, config); + + return CHDERR_INVALID_PARAMETER; +} + + +/*------------------------------------------------- + chd_get_codec_name - get the name of a + particular codec +-------------------------------------------------*/ + +const char *chd_get_codec_name(UINT32 codec) +{ + int intfnum; + + /* look for a matching codec and return its string */ + for (intfnum = 0; intfnum < ARRAY_LENGTH(codec_interfaces); intfnum++) + if (codec_interfaces[intfnum].compression == codec) + return codec_interfaces[intfnum].compname; + + return "Unknown"; +} + + + +/*************************************************************************** + INTERNAL ASYNC OPERATIONS +***************************************************************************/ + +/*------------------------------------------------- + async_read_callback - asynchronous reading + callback +-------------------------------------------------*/ + +static void *async_read_callback(void *param, int threadid) +{ + chd_file *chd = (chd_file *)param; + chd_error err; + + /* read the hunk into the cache */ + err = hunk_read_into_memory(chd, chd->async_hunknum, (UINT8 *)chd->async_buffer); + + /* return the error */ + return (void *)err; +} + + +/*------------------------------------------------- + async_write_callback - asynchronous writing + callback +-------------------------------------------------*/ + +static void *async_write_callback(void *param, int threadid) +{ + chd_file *chd = (chd_file *)param; + chd_error err; + + /* write the hunk from memory */ + err = hunk_write_from_memory(chd, chd->async_hunknum, (const UINT8 *)chd->async_buffer); + + /* return the error */ + return (void *)err; +} + + + +/*************************************************************************** + INTERNAL HEADER OPERATIONS +***************************************************************************/ + +/*------------------------------------------------- + header_validate - check the validity of a + CHD header +-------------------------------------------------*/ + +static chd_error header_validate(const chd_header *header) +{ + int intfnum; + + /* require a valid version */ + if (header->version == 0 || header->version > CHD_HEADER_VERSION) + return CHDERR_UNSUPPORTED_VERSION; + + /* require a valid length */ + if ((header->version == 1 && header->length != CHD_V1_HEADER_SIZE) || + (header->version == 2 && header->length != CHD_V2_HEADER_SIZE) || + (header->version == 3 && header->length != CHD_V3_HEADER_SIZE) || + (header->version == 4 && header->length != CHD_V4_HEADER_SIZE)) + return CHDERR_INVALID_PARAMETER; + + /* require valid flags */ + if (header->flags & CHDFLAGS_UNDEFINED) + return CHDERR_INVALID_PARAMETER; + + /* require a supported compression mechanism */ + for (intfnum = 0; intfnum < ARRAY_LENGTH(codec_interfaces); intfnum++) + if (codec_interfaces[intfnum].compression == header->compression) + break; + if (intfnum == ARRAY_LENGTH(codec_interfaces)) + return CHDERR_INVALID_PARAMETER; + + /* require a valid hunksize */ + if (header->hunkbytes == 0 || header->hunkbytes >= 65536 * 256) + return CHDERR_INVALID_PARAMETER; + + /* require a valid hunk count */ + if (header->totalhunks == 0) + return CHDERR_INVALID_PARAMETER; + + /* require a valid MD5 and/or SHA1 if we're using a parent */ + if ((header->flags & CHDFLAGS_HAS_PARENT) && memcmp(header->parentmd5, nullmd5, sizeof(nullmd5)) == 0 && memcmp(header->parentsha1, nullsha1, sizeof(nullsha1)) == 0) + return CHDERR_INVALID_PARAMETER; + + /* if we're V3 or later, the obsolete fields must be 0 */ + if (header->version >= 3 && + (header->obsolete_cylinders != 0 || header->obsolete_sectors != 0 || + header->obsolete_heads != 0 || header->obsolete_hunksize != 0)) + return CHDERR_INVALID_PARAMETER; + + /* if we're pre-V3, the obsolete fields must NOT be 0 */ + if (header->version < 3 && + (header->obsolete_cylinders == 0 || header->obsolete_sectors == 0 || + header->obsolete_heads == 0 || header->obsolete_hunksize == 0)) + return CHDERR_INVALID_PARAMETER; + + return CHDERR_NONE; +} + + +/*------------------------------------------------- + header_read - read a CHD header into the + internal data structure +-------------------------------------------------*/ + +static chd_error header_read(core_file *file, chd_header *header) +{ + UINT8 rawheader[CHD_MAX_HEADER_SIZE]; + UINT32 count; + + /* punt if NULL */ + if (header == NULL) + return CHDERR_INVALID_PARAMETER; + + /* punt if invalid file */ + if (file == NULL) + return CHDERR_INVALID_FILE; + + /* seek and read */ + core_fseek(file, 0, SEEK_SET); + count = core_fread(file, rawheader, sizeof(rawheader)); + if (count != sizeof(rawheader)) + return CHDERR_READ_ERROR; + + /* verify the tag */ + if (strncmp((char *)rawheader, "MComprHD", 8) != 0) + return CHDERR_INVALID_DATA; + + /* extract the direct data */ + memset(header, 0, sizeof(*header)); + header->length = get_bigendian_uint32(&rawheader[8]); + header->version = get_bigendian_uint32(&rawheader[12]); + + /* make sure it's a version we understand */ + if (header->version == 0 || header->version > CHD_HEADER_VERSION) + return CHDERR_UNSUPPORTED_VERSION; + + /* make sure the length is expected */ + if ((header->version == 1 && header->length != CHD_V1_HEADER_SIZE) || + (header->version == 2 && header->length != CHD_V2_HEADER_SIZE) || + (header->version == 3 && header->length != CHD_V3_HEADER_SIZE) || + (header->version == 4 && header->length != CHD_V4_HEADER_SIZE)) + return CHDERR_INVALID_DATA; + + /* extract the common data */ + header->flags = get_bigendian_uint32(&rawheader[16]); + header->compression = get_bigendian_uint32(&rawheader[20]); + + /* extract the V1/V2-specific data */ + if (header->version < 3) + { + int seclen = (header->version == 1) ? CHD_V1_SECTOR_SIZE : get_bigendian_uint32(&rawheader[76]); + header->obsolete_hunksize = get_bigendian_uint32(&rawheader[24]); + header->totalhunks = get_bigendian_uint32(&rawheader[28]); + header->obsolete_cylinders = get_bigendian_uint32(&rawheader[32]); + header->obsolete_heads = get_bigendian_uint32(&rawheader[36]); + header->obsolete_sectors = get_bigendian_uint32(&rawheader[40]); + memcpy(header->md5, &rawheader[44], CHD_MD5_BYTES); + memcpy(header->parentmd5, &rawheader[60], CHD_MD5_BYTES); + header->logicalbytes = (UINT64)header->obsolete_cylinders * (UINT64)header->obsolete_heads * (UINT64)header->obsolete_sectors * (UINT64)seclen; + header->hunkbytes = seclen * header->obsolete_hunksize; + header->metaoffset = 0; + } + + /* extract the V3-specific data */ + else if (header->version == 3) + { + header->totalhunks = get_bigendian_uint32(&rawheader[24]); + header->logicalbytes = get_bigendian_uint64(&rawheader[28]); + header->metaoffset = get_bigendian_uint64(&rawheader[36]); + memcpy(header->md5, &rawheader[44], CHD_MD5_BYTES); + memcpy(header->parentmd5, &rawheader[60], CHD_MD5_BYTES); + header->hunkbytes = get_bigendian_uint32(&rawheader[76]); + memcpy(header->sha1, &rawheader[80], CHD_SHA1_BYTES); + memcpy(header->parentsha1, &rawheader[100], CHD_SHA1_BYTES); + } + + /* extract the V4-specific data */ + else + { + header->totalhunks = get_bigendian_uint32(&rawheader[24]); + header->logicalbytes = get_bigendian_uint64(&rawheader[28]); + header->metaoffset = get_bigendian_uint64(&rawheader[36]); + header->hunkbytes = get_bigendian_uint32(&rawheader[44]); + memcpy(header->sha1, &rawheader[48], CHD_SHA1_BYTES); + memcpy(header->parentsha1, &rawheader[68], CHD_SHA1_BYTES); + memcpy(header->rawsha1, &rawheader[88], CHD_SHA1_BYTES); + } + + /* guess it worked */ + return CHDERR_NONE; +} + + +/*------------------------------------------------- + header_write - write a CHD header from the + internal data structure +-------------------------------------------------*/ + +static chd_error header_write(core_file *file, const chd_header *header) +{ + UINT8 rawheader[CHD_MAX_HEADER_SIZE]; + UINT32 count; + + /* punt if NULL */ + if (header == NULL) + return CHDERR_INVALID_PARAMETER; + + /* punt if invalid file */ + if (file == NULL) + return CHDERR_INVALID_FILE; + + /* only support writing modern headers */ + if (header->version != 4) + return CHDERR_INVALID_PARAMETER; + + /* assemble the data */ + memset(rawheader, 0, sizeof(rawheader)); + memcpy(rawheader, "MComprHD", 8); + + put_bigendian_uint32(&rawheader[8], CHD_V4_HEADER_SIZE); + put_bigendian_uint32(&rawheader[12], header->version); + put_bigendian_uint32(&rawheader[16], header->flags); + put_bigendian_uint32(&rawheader[20], header->compression); + put_bigendian_uint32(&rawheader[24], header->totalhunks); + put_bigendian_uint64(&rawheader[28], header->logicalbytes); + put_bigendian_uint64(&rawheader[36], header->metaoffset); + put_bigendian_uint32(&rawheader[44], header->hunkbytes); + memcpy(&rawheader[48], header->sha1, CHD_SHA1_BYTES); + memcpy(&rawheader[68], header->parentsha1, CHD_SHA1_BYTES); + memcpy(&rawheader[88], header->rawsha1, CHD_SHA1_BYTES); + + /* seek and write */ + core_fseek(file, 0, SEEK_SET); + count = core_fwrite(file, rawheader, CHD_V4_HEADER_SIZE); + if (count != CHD_V4_HEADER_SIZE) + return CHDERR_WRITE_ERROR; + + return CHDERR_NONE; +} + + + +/*************************************************************************** + INTERNAL HUNK READ/WRITE +***************************************************************************/ + +/*------------------------------------------------- + hunk_read_into_cache - read a hunk into + the CHD's hunk cache +-------------------------------------------------*/ + +static chd_error hunk_read_into_cache(chd_file *chd, UINT32 hunknum) +{ + chd_error err; + + /* track the max */ + if (hunknum > chd->maxhunk) + chd->maxhunk = hunknum; + + /* if we're already in the cache, we're done */ + if (chd->cachehunk == hunknum) + return CHDERR_NONE; + chd->cachehunk = ~0; + + /* otherwise, read the data */ + err = hunk_read_into_memory(chd, hunknum, chd->cache); + if (err != CHDERR_NONE) + return err; + + /* mark the hunk successfully cached in */ + chd->cachehunk = hunknum; + return CHDERR_NONE; +} + + +/*------------------------------------------------- + hunk_read_into_memory - read a hunk into + memory at the given location +-------------------------------------------------*/ + +static chd_error hunk_read_into_memory(chd_file *chd, UINT32 hunknum, UINT8 *dest) +{ + map_entry *entry = &chd->map[hunknum]; + chd_error err; + UINT32 bytes; + + /* return an error if out of range */ + if (hunknum >= chd->header.totalhunks) + return CHDERR_HUNK_OUT_OF_RANGE; + + /* switch off the entry type */ + switch (entry->flags & MAP_ENTRY_FLAG_TYPE_MASK) + { + /* compressed data */ + case MAP_ENTRY_TYPE_COMPRESSED: + + /* read it into the decompression buffer */ + core_fseek(chd->file, entry->offset, SEEK_SET); + bytes = core_fread(chd->file, chd->compressed, entry->length); + if (bytes != entry->length) + return CHDERR_READ_ERROR; + + /* now decompress using the codec */ + err = CHDERR_NONE; + if (chd->codecintf->decompress != NULL) + err = (*chd->codecintf->decompress)(chd, entry->length, dest); + if (err != CHDERR_NONE) + return err; + break; + + /* uncompressed data */ + case MAP_ENTRY_TYPE_UNCOMPRESSED: + core_fseek(chd->file, entry->offset, SEEK_SET); + bytes = core_fread(chd->file, dest, chd->header.hunkbytes); + if (bytes != chd->header.hunkbytes) + return CHDERR_READ_ERROR; + break; + + /* mini-compressed data */ + case MAP_ENTRY_TYPE_MINI: + put_bigendian_uint64(&dest[0], entry->offset); + for (bytes = 8; bytes < chd->header.hunkbytes; bytes++) + dest[bytes] = dest[bytes - 8]; + break; + + /* self-referenced data */ + case MAP_ENTRY_TYPE_SELF_HUNK: + if (chd->cachehunk == entry->offset && dest == chd->cache) + break; + return hunk_read_into_memory(chd, entry->offset, dest); + + /* parent-referenced data */ + case MAP_ENTRY_TYPE_PARENT_HUNK: + err = hunk_read_into_memory(chd->parent, entry->offset, dest); + if (err != CHDERR_NONE) + return err; + break; + } + return CHDERR_NONE; +} + + +/*------------------------------------------------- + hunk_write_from_memory - write a hunk from + memory into a CHD +-------------------------------------------------*/ + +static chd_error hunk_write_from_memory(chd_file *chd, UINT32 hunknum, const UINT8 *src) +{ + map_entry *entry = &chd->map[hunknum]; + map_entry newentry; + UINT8 fileentry[MAP_ENTRY_SIZE]; + const void *data = src; + UINT32 bytes = 0, match; + chd_error err; + + /* track the max */ + if (hunknum > chd->maxhunk) + chd->maxhunk = hunknum; + + /* first compute the CRC of the original data */ + newentry.crc = 0; + if (src != NULL) + newentry.crc = crc32(0, &src[0], chd->header.hunkbytes); + + /* if we're not a lossy codec, compute the CRC and look for matches */ + if (!chd->codecintf->lossy && src != NULL) + { + /* some extra stuff for zlib+ compression */ + if (chd->header.compression >= CHDCOMPRESSION_ZLIB_PLUS) + { + /* see if we can mini-compress first */ + for (bytes = 8; bytes < chd->header.hunkbytes; bytes++) + if (src[bytes] != src[bytes - 8]) + break; + + /* if so, we don't need to write any data */ + if (bytes == chd->header.hunkbytes) + { + newentry.offset = get_bigendian_uint64(&src[0]); + newentry.length = 0; + newentry.flags = MAP_ENTRY_TYPE_MINI; + goto write_entry; + } + + /* otherwise, see if we can find a match in the current file */ + match = crcmap_find_hunk(chd, hunknum, newentry.crc, &src[0]); + if (match != NO_MATCH) + { + newentry.offset = match; + newentry.length = 0; + newentry.flags = MAP_ENTRY_TYPE_SELF_HUNK; + goto write_entry; + } + + /* if we have a parent, see if we can find a match in there */ + if (chd->header.flags & CHDFLAGS_HAS_PARENT) + { + match = crcmap_find_hunk(chd->parent, ~0, newentry.crc, &src[0]); + if (match != NO_MATCH) + { + newentry.offset = match; + newentry.length = 0; + newentry.flags = MAP_ENTRY_TYPE_PARENT_HUNK; + goto write_entry; + } + } + } + } + + /* now try compressing the data */ + err = CHDERR_COMPRESSION_ERROR; + if (chd->codecintf->compress != NULL) + err = (*chd->codecintf->compress)(chd, src, &bytes); + + /* if that worked, and we're lossy, decompress and CRC the result */ + if (err == CHDERR_NONE && (chd->codecintf->lossy || src == NULL)) + { + err = (*chd->codecintf->decompress)(chd, bytes, chd->cache); + if (err == CHDERR_NONE) + newentry.crc = crc32(0, chd->cache, chd->header.hunkbytes); + } + + /* if we succeeded in compressing the data, replace our data pointer and mark it so */ + if (err == CHDERR_NONE) + { + data = chd->compressed; + newentry.length = bytes; + newentry.flags = MAP_ENTRY_TYPE_COMPRESSED; + } + + /* otherwise, mark it uncompressed and use the original data */ + else + { + newentry.length = chd->header.hunkbytes; + newentry.flags = MAP_ENTRY_TYPE_UNCOMPRESSED; + } + + /* if the data doesn't fit into the previous entry, make a new one at the eof */ + newentry.offset = entry->offset; + if (newentry.offset == 0 || newentry.length > entry->length) + newentry.offset = core_fsize(chd->file); + + /* write the data */ + core_fseek(chd->file, newentry.offset, SEEK_SET); + bytes = core_fwrite(chd->file, data, newentry.length); + if (bytes != newentry.length) + return CHDERR_WRITE_ERROR; + + /* update the entry in memory */ +write_entry: + *entry = newentry; + + /* update the map on file */ + map_assemble(&fileentry[0], &chd->map[hunknum]); + core_fseek(chd->file, chd->header.length + hunknum * sizeof(fileentry), SEEK_SET); + bytes = core_fwrite(chd->file, &fileentry[0], sizeof(fileentry)); + if (bytes != sizeof(fileentry)) + return CHDERR_WRITE_ERROR; + + return CHDERR_NONE; +} + + + +/*************************************************************************** + INTERNAL MAP ACCESS +***************************************************************************/ + +/*------------------------------------------------- + map_write_initial - write an initial map to + a new CHD file +-------------------------------------------------*/ + +static chd_error map_write_initial(core_file *file, chd_file *parent, const chd_header *header) +{ + UINT8 blank_map_entries[MAP_STACK_ENTRIES * MAP_ENTRY_SIZE]; + int fullchunks, remainder, count, i, j; + map_entry mapentry; + UINT64 fileoffset; + + /* create a mini hunk of 0's */ + mapentry.offset = 0; + mapentry.crc = 0; + mapentry.length = 0; + mapentry.flags = MAP_ENTRY_TYPE_MINI | MAP_ENTRY_FLAG_NO_CRC; + for (i = 0; i < MAP_STACK_ENTRIES; i++) + map_assemble(&blank_map_entries[i * MAP_ENTRY_SIZE], &mapentry); + + /* prepare to write a blank hunk map immediately following */ + fileoffset = header->length; + fullchunks = header->totalhunks / MAP_STACK_ENTRIES; + remainder = header->totalhunks % MAP_STACK_ENTRIES; + + /* first write full chunks of blank entries */ + for (i = 0; i < fullchunks; i++) + { + /* parent drives need to be mapped through */ + if (parent != NULL) + for (j = 0; j < MAP_STACK_ENTRIES; j++) + { + mapentry.offset = i * MAP_STACK_ENTRIES + j; + mapentry.crc = parent->map[i * MAP_STACK_ENTRIES + j].crc; + mapentry.flags = MAP_ENTRY_TYPE_PARENT_HUNK; + map_assemble(&blank_map_entries[j * MAP_ENTRY_SIZE], &mapentry); + } + + /* write the chunks */ + core_fseek(file, fileoffset, SEEK_SET); + count = core_fwrite(file, blank_map_entries, sizeof(blank_map_entries)); + if (count != sizeof(blank_map_entries)) + return CHDERR_WRITE_ERROR; + fileoffset += sizeof(blank_map_entries); + } + + /* then write the remainder */ + if (remainder > 0) + { + /* parent drives need to be mapped through */ + if (parent != NULL) + for (j = 0; j < remainder; j++) + { + mapentry.offset = i * MAP_STACK_ENTRIES + j; + mapentry.crc = parent->map[i * MAP_STACK_ENTRIES + j].crc; + mapentry.flags = MAP_ENTRY_TYPE_PARENT_HUNK; + map_assemble(&blank_map_entries[j * MAP_ENTRY_SIZE], &mapentry); + } + + /* write the chunks */ + core_fseek(file, fileoffset, SEEK_SET); + count = core_fwrite(file, blank_map_entries, remainder * MAP_ENTRY_SIZE); + if (count != remainder * MAP_ENTRY_SIZE) + return CHDERR_WRITE_ERROR; + fileoffset += remainder * MAP_ENTRY_SIZE; + } + + /* then write a special end-of-list cookie */ + memcpy(&blank_map_entries[0], END_OF_LIST_COOKIE, MAP_ENTRY_SIZE); + core_fseek(file, fileoffset, SEEK_SET); + count = core_fwrite(file, blank_map_entries, MAP_ENTRY_SIZE); + if (count != MAP_ENTRY_SIZE) + return CHDERR_WRITE_ERROR; + + return CHDERR_NONE; +} + + +/*------------------------------------------------- + map_read - read the initial sector map +-------------------------------------------------*/ + +static chd_error map_read(chd_file *chd) +{ + UINT32 entrysize = (chd->header.version < 3) ? OLD_MAP_ENTRY_SIZE : MAP_ENTRY_SIZE; + UINT8 raw_map_entries[MAP_STACK_ENTRIES * MAP_ENTRY_SIZE]; + UINT64 fileoffset, maxoffset = 0; + UINT8 cookie[MAP_ENTRY_SIZE]; + UINT32 count; + chd_error err; + int i; + + /* first allocate memory */ + chd->map = (map_entry *)malloc(sizeof(chd->map[0]) * chd->header.totalhunks); + if (!chd->map) + return CHDERR_OUT_OF_MEMORY; + + /* read the map entries in in chunks and extract to the map list */ + fileoffset = chd->header.length; + for (i = 0; i < chd->header.totalhunks; i += MAP_STACK_ENTRIES) + { + /* compute how many entries this time */ + int entries = chd->header.totalhunks - i, j; + if (entries > MAP_STACK_ENTRIES) + entries = MAP_STACK_ENTRIES; + + /* read that many */ + core_fseek(chd->file, fileoffset, SEEK_SET); + count = core_fread(chd->file, raw_map_entries, entries * entrysize); + if (count != entries * entrysize) + { + err = CHDERR_READ_ERROR; + goto cleanup; + } + fileoffset += entries * entrysize; + + /* process that many */ + if (entrysize == MAP_ENTRY_SIZE) + { + for (j = 0; j < entries; j++) + map_extract(&raw_map_entries[j * MAP_ENTRY_SIZE], &chd->map[i + j]); + } + else + { + for (j = 0; j < entries; j++) + map_extract_old(&raw_map_entries[j * OLD_MAP_ENTRY_SIZE], &chd->map[i + j], chd->header.hunkbytes); + } + + /* track the maximum offset */ + for (j = 0; j < entries; j++) + if ((chd->map[i + j].flags & MAP_ENTRY_FLAG_TYPE_MASK) == MAP_ENTRY_TYPE_COMPRESSED || + (chd->map[i + j].flags & MAP_ENTRY_FLAG_TYPE_MASK) == MAP_ENTRY_TYPE_UNCOMPRESSED) + maxoffset = MAX(maxoffset, chd->map[i + j].offset + chd->map[i + j].length); + } + + /* verify the cookie */ + core_fseek(chd->file, fileoffset, SEEK_SET); + count = core_fread(chd->file, &cookie, entrysize); + if (count != entrysize || memcmp(&cookie, END_OF_LIST_COOKIE, entrysize)) + { + err = CHDERR_INVALID_FILE; + goto cleanup; + } + + /* verify the length */ + if (maxoffset > core_fsize(chd->file)) + { + err = CHDERR_INVALID_FILE; + goto cleanup; + } + return CHDERR_NONE; + +cleanup: + if (chd->map) + free(chd->map); + chd->map = NULL; + return err; +} + + + +/*************************************************************************** + INTERNAL CRC MAP ACCESS +***************************************************************************/ + +/*------------------------------------------------- + crcmap_init - initialize the CRC map +-------------------------------------------------*/ + +static void crcmap_init(chd_file *chd, int prepopulate) +{ + int i; + + /* if we already have one, bail */ + if (chd->crcmap != NULL) + return; + + /* reset all pointers */ + chd->crcmap = NULL; + chd->crcfree = NULL; + chd->crctable = NULL; + + /* allocate a list; one for each hunk */ + chd->crcmap = (crcmap_entry *)malloc(chd->header.totalhunks * sizeof(chd->crcmap[0])); + if (chd->crcmap == NULL) + return; + + /* allocate a CRC map table */ + chd->crctable = (crcmap_entry **)malloc(CRCMAP_HASH_SIZE * sizeof(chd->crctable[0])); + if (chd->crctable == NULL) + { + free(chd->crcmap); + chd->crcmap = NULL; + return; + } + + /* initialize the free list */ + for (i = 0; i < chd->header.totalhunks; i++) + { + chd->crcmap[i].next = chd->crcfree; + chd->crcfree = &chd->crcmap[i]; + } + + /* initialize the table */ + memset(chd->crctable, 0, CRCMAP_HASH_SIZE * sizeof(chd->crctable[0])); + + /* if we're to prepopulate, go for it */ + if (prepopulate) + for (i = 0; i < chd->header.totalhunks; i++) + crcmap_add_entry(chd, i); +} + + +/*------------------------------------------------- + crcmap_add_entry - log a CRC entry +-------------------------------------------------*/ + +static void crcmap_add_entry(chd_file *chd, UINT32 hunknum) +{ + UINT32 hash = chd->map[hunknum].crc % CRCMAP_HASH_SIZE; + crcmap_entry *crcmap; + + /* pull a free entry off the list */ + crcmap = chd->crcfree; + chd->crcfree = crcmap->next; + + /* set up the entry and link it into the hash table */ + crcmap->hunknum = hunknum; + crcmap->next = chd->crctable[hash]; + chd->crctable[hash] = crcmap; +} + + +/*------------------------------------------------- + crcmap_verify_hunk_match - verify that a + hunk really matches by doing a byte-for-byte + compare +-------------------------------------------------*/ + +static int crcmap_verify_hunk_match(chd_file *chd, UINT32 hunknum, const UINT8 *rawdata) +{ + /* we have a potential match -- better be sure */ + /* read the hunk from disk and compare byte-for-byte */ + if (hunknum != chd->comparehunk) + { + chd->comparehunk = ~0; + if (hunk_read_into_memory(chd, hunknum, chd->compare) == CHDERR_NONE) + chd->comparehunk = hunknum; + } + return (hunknum == chd->comparehunk && memcmp(rawdata, chd->compare, chd->header.hunkbytes) == 0); +} + + +/*------------------------------------------------- + crcmap_find_hunk - find a hunk with a matching + CRC in the map +-------------------------------------------------*/ + +static UINT32 crcmap_find_hunk(chd_file *chd, UINT32 hunknum, UINT32 crc, const UINT8 *rawdata) +{ + UINT32 lasthunk = (hunknum < chd->header.totalhunks) ? hunknum : chd->header.totalhunks; + int curhunk; + + /* if we have a CRC map, use that */ + if (chd->crctable) + { + crcmap_entry *curentry; + for (curentry = chd->crctable[crc % CRCMAP_HASH_SIZE]; curentry; curentry = curentry->next) + { + curhunk = curentry->hunknum; + if (chd->map[curhunk].crc == crc && !(chd->map[curhunk].flags & MAP_ENTRY_FLAG_NO_CRC) && crcmap_verify_hunk_match(chd, curhunk, rawdata)) + return curhunk; + } + return NO_MATCH; + } + + /* first see if the last match is a valid one */ + if (chd->comparehunk < chd->header.totalhunks && chd->map[chd->comparehunk].crc == crc && !(chd->map[chd->comparehunk].flags & MAP_ENTRY_FLAG_NO_CRC) && + memcmp(rawdata, chd->compare, chd->header.hunkbytes) == 0) + return chd->comparehunk; + + /* scan through the CHD's hunk map looking for a match */ + for (curhunk = 0; curhunk < lasthunk; curhunk++) + if (chd->map[curhunk].crc == crc && !(chd->map[curhunk].flags & MAP_ENTRY_FLAG_NO_CRC) && crcmap_verify_hunk_match(chd, curhunk, rawdata)) + return curhunk; + + return NO_MATCH; +} + + + +/*************************************************************************** + INTERNAL METADATA ACCESS +***************************************************************************/ + +/*------------------------------------------------- + metadata_find_entry - find a metadata entry +-------------------------------------------------*/ + +static chd_error metadata_find_entry(chd_file *chd, UINT32 metatag, UINT32 metaindex, metadata_entry *metaentry) +{ + /* start at the beginning */ + metaentry->offset = chd->header.metaoffset; + metaentry->prev = 0; + + /* loop until we run out of options */ + while (metaentry->offset != 0) + { + UINT8 raw_meta_header[METADATA_HEADER_SIZE]; + UINT32 count; + + /* read the raw header */ + core_fseek(chd->file, metaentry->offset, SEEK_SET); + count = core_fread(chd->file, raw_meta_header, sizeof(raw_meta_header)); + if (count != sizeof(raw_meta_header)) + break; + + /* extract the data */ + metaentry->metatag = get_bigendian_uint32(&raw_meta_header[0]); + metaentry->length = get_bigendian_uint32(&raw_meta_header[4]); + metaentry->next = get_bigendian_uint64(&raw_meta_header[8]); + + /* flags are encoded in the high byte of length */ + metaentry->flags = metaentry->length >> 24; + metaentry->length &= 0x00ffffff; + + /* if we got a match, proceed */ + if (metatag == CHDMETATAG_WILDCARD || metaentry->metatag == metatag) + if (metaindex-- == 0) + return CHDERR_NONE; + + /* no match, fetch the next link */ + metaentry->prev = metaentry->offset; + metaentry->offset = metaentry->next; + } + + /* if we get here, we didn't find it */ + return CHDERR_METADATA_NOT_FOUND; +} + + +/*------------------------------------------------- + metadata_set_previous_next - set the 'next' + offset of a piece of metadata +-------------------------------------------------*/ + +static chd_error metadata_set_previous_next(chd_file *chd, UINT64 prevoffset, UINT64 nextoffset) +{ + UINT8 raw_meta_header[METADATA_HEADER_SIZE]; + chd_error err; + UINT32 count; + + /* if we were the first entry, make the next entry the first */ + if (prevoffset == 0) + { + chd->header.metaoffset = nextoffset; + err = header_write(chd->file, &chd->header); + if (err != CHDERR_NONE) + return err; + } + + /* otherwise, update the link in the previous pointer */ + else + { + /* read the previous raw header */ + core_fseek(chd->file, prevoffset, SEEK_SET); + count = core_fread(chd->file, raw_meta_header, sizeof(raw_meta_header)); + if (count != sizeof(raw_meta_header)) + return CHDERR_READ_ERROR; + + /* copy our next pointer into the previous->next offset */ + put_bigendian_uint64(&raw_meta_header[8], nextoffset); + + /* write the previous raw header */ + core_fseek(chd->file, prevoffset, SEEK_SET); + count = core_fwrite(chd->file, raw_meta_header, sizeof(raw_meta_header)); + if (count != sizeof(raw_meta_header)) + return CHDERR_WRITE_ERROR; + } + + return CHDERR_NONE; +} + + +/*------------------------------------------------- + metadata_set_length - set the length field of + a piece of metadata +-------------------------------------------------*/ + +static chd_error metadata_set_length(chd_file *chd, UINT64 offset, UINT32 length) +{ + UINT8 raw_meta_header[METADATA_HEADER_SIZE]; + UINT32 oldlength; + UINT32 count; + + /* read the raw header */ + core_fseek(chd->file, offset, SEEK_SET); + count = core_fread(chd->file, raw_meta_header, sizeof(raw_meta_header)); + if (count != sizeof(raw_meta_header)) + return CHDERR_READ_ERROR; + + /* update the length at offset 4, preserving the flags in the upper byte */ + oldlength = get_bigendian_uint32(&raw_meta_header[4]); + length = (length & 0x00ffffff) | (oldlength & 0xff000000); + put_bigendian_uint32(&raw_meta_header[4], length); + + /* write the raw header */ + core_fseek(chd->file, offset, SEEK_SET); + count = core_fwrite(chd->file, raw_meta_header, sizeof(raw_meta_header)); + if (count != sizeof(raw_meta_header)) + return CHDERR_WRITE_ERROR; + + return CHDERR_NONE; +} + + +/*------------------------------------------------- + metadata_compute_hash - compute the SHA1 + hash of all metadata that requests it +-------------------------------------------------*/ + +static chd_error metadata_compute_hash(chd_file *chd, const UINT8 *rawsha1, UINT8 *finalsha1) +{ + metadata_hash *hasharray = NULL; + chd_error err = CHDERR_NONE; + struct sha1_ctx sha1; + UINT32 hashindex = 0; + UINT32 hashalloc = 0; + UINT64 offset, next; + + /* only works for V4 and above */ + if (chd->header.version < 4) + { + memcpy(finalsha1, rawsha1, SHA1_DIGEST_SIZE); + return CHDERR_NONE; + } + + /* loop until we run out of data */ + for (offset = chd->header.metaoffset; offset != 0; offset = next) + { + UINT8 raw_meta_header[METADATA_HEADER_SIZE]; + UINT32 count, metalength, metatag; + UINT8 *tempbuffer; + UINT8 metaflags; + + /* read the raw header */ + core_fseek(chd->file, offset, SEEK_SET); + count = core_fread(chd->file, raw_meta_header, sizeof(raw_meta_header)); + if (count != sizeof(raw_meta_header)) + break; + + /* extract the data */ + metatag = get_bigendian_uint32(&raw_meta_header[0]); + metalength = get_bigendian_uint32(&raw_meta_header[4]); + next = get_bigendian_uint64(&raw_meta_header[8]); + + /* flags are encoded in the high byte of length */ + metaflags = metalength >> 24; + metalength &= 0x00ffffff; + + /* if not checksumming, continue */ + if (!(metaflags & CHD_MDFLAGS_CHECKSUM)) + continue; + + /* allocate memory */ + tempbuffer = (UINT8 *)malloc(metalength); + if (tempbuffer == NULL) + { + err = CHDERR_OUT_OF_MEMORY; + goto cleanup; + } + + /* seek and read the metadata */ + core_fseek(chd->file, offset + METADATA_HEADER_SIZE, SEEK_SET); + count = core_fread(chd->file, tempbuffer, metalength); + if (count != metalength) + { + free(tempbuffer); + err = CHDERR_READ_ERROR; + goto cleanup; + } + + /* compute this entry's hash */ + sha1_init(&sha1); + sha1_update(&sha1, metalength, tempbuffer); + sha1_final(&sha1); + free(tempbuffer); + + /* expand the hasharray if necessary */ + if (hashindex >= hashalloc) + { + hashalloc += 256; + hasharray = (metadata_hash *)realloc(hasharray, hashalloc * sizeof(hasharray[0])); + if (hasharray == NULL) + { + err = CHDERR_OUT_OF_MEMORY; + goto cleanup; + } + } + + /* fill in the entry */ + put_bigendian_uint32(hasharray[hashindex].tag, metatag); + sha1_digest(&sha1, SHA1_DIGEST_SIZE, hasharray[hashindex].sha1); + hashindex++; + } + + /* sort the array */ + qsort(hasharray, hashindex, sizeof(hasharray[0]), metadata_hash_compare); + + /* compute the SHA1 of the raw plus the various metadata */ + sha1_init(&sha1); + sha1_update(&sha1, CHD_SHA1_BYTES, rawsha1); + sha1_update(&sha1, hashindex * sizeof(hasharray[0]), (const UINT8 *)hasharray); + sha1_final(&sha1); + sha1_digest(&sha1, SHA1_DIGEST_SIZE, finalsha1); + +cleanup: + if (hasharray != NULL) + free(hasharray); + return err; +} + + +/*------------------------------------------------- + metadata_hash_compare - compare two hash + entries +-------------------------------------------------*/ + +static int CLIB_DECL metadata_hash_compare(const void *elem1, const void *elem2) +{ + return memcmp(elem1, elem2, sizeof(metadata_hash)); +} + + + +/*************************************************************************** + ZLIB COMPRESSION CODEC +***************************************************************************/ + +/*------------------------------------------------- + zlib_codec_init - initialize the ZLIB codec +-------------------------------------------------*/ + +static chd_error zlib_codec_init(chd_file *chd) +{ + zlib_codec_data *data; + chd_error err; + int zerr; + + /* allocate memory for the 2 stream buffers */ + data = (zlib_codec_data *)malloc(sizeof(*data)); + if (data == NULL) + return CHDERR_OUT_OF_MEMORY; + + /* clear the buffers */ + memset(data, 0, sizeof(*data)); + + /* init the inflater first */ + data->inflater.next_in = (Bytef *)data; /* bogus, but that's ok */ + data->inflater.avail_in = 0; + data->inflater.zalloc = zlib_fast_alloc; + data->inflater.zfree = zlib_fast_free; + data->inflater.opaque = data; + zerr = inflateInit2(&data->inflater, -MAX_WBITS); + + /* if that worked, initialize the deflater */ + if (zerr == Z_OK) + { + data->deflater.next_in = (Bytef *)data; /* bogus, but that's ok */ + data->deflater.avail_in = 0; + data->deflater.zalloc = zlib_fast_alloc; + data->deflater.zfree = zlib_fast_free; + data->deflater.opaque = data; + zerr = deflateInit2(&data->deflater, Z_BEST_COMPRESSION, Z_DEFLATED, -MAX_WBITS, 8, Z_DEFAULT_STRATEGY); + } + + /* convert errors */ + if (zerr == Z_MEM_ERROR) + err = CHDERR_OUT_OF_MEMORY; + else if (zerr != Z_OK) + err = CHDERR_CODEC_ERROR; + else + err = CHDERR_NONE; + + /* handle an error */ + if (err == CHDERR_NONE) + chd->codecdata = data; + else + free(data); + + return err; +} + + +/*------------------------------------------------- + zlib_codec_free - free data for the ZLIB + codec +-------------------------------------------------*/ + +static void zlib_codec_free(chd_file *chd) +{ + zlib_codec_data *data = (zlib_codec_data *)chd->codecdata; + + /* deinit the streams */ + if (data != NULL) + { + int i; + + inflateEnd(&data->inflater); + deflateEnd(&data->deflater); + + /* free our fast memory */ + for (i = 0; i < MAX_ZLIB_ALLOCS; i++) + if (data->allocptr[i]) + free(data->allocptr[i]); + free(data); + } +} + + +/*------------------------------------------------- + zlib_codec_compress - compress data using the + ZLIB codec +-------------------------------------------------*/ + +static chd_error zlib_codec_compress(chd_file *chd, const void *src, UINT32 *length) +{ + zlib_codec_data *data = (zlib_codec_data *)chd->codecdata; + int zerr; + + /* reset the decompressor */ + data->deflater.next_in = (Bytef *)src; + data->deflater.avail_in = chd->header.hunkbytes; + data->deflater.total_in = 0; + data->deflater.next_out = chd->compressed; + data->deflater.avail_out = chd->header.hunkbytes; + data->deflater.total_out = 0; + zerr = deflateReset(&data->deflater); + if (zerr != Z_OK) + return CHDERR_COMPRESSION_ERROR; + + /* do it */ + zerr = deflate(&data->deflater, Z_FINISH); + + /* if we ended up with more data than we started with, return an error */ + if (zerr != Z_STREAM_END || data->deflater.total_out >= chd->header.hunkbytes) + return CHDERR_COMPRESSION_ERROR; + + /* otherwise, fill in the length and return success */ + *length = data->deflater.total_out; + return CHDERR_NONE; +} + + +/*------------------------------------------------- + zlib_codec_decompress - decomrpess data using + the ZLIB codec +-------------------------------------------------*/ + +static chd_error zlib_codec_decompress(chd_file *chd, UINT32 srclength, void *dest) +{ + zlib_codec_data *data = (zlib_codec_data *)chd->codecdata; + int zerr; + + /* reset the decompressor */ + data->inflater.next_in = chd->compressed; + data->inflater.avail_in = srclength; + data->inflater.total_in = 0; + data->inflater.next_out = (Bytef *)dest; + data->inflater.avail_out = chd->header.hunkbytes; + data->inflater.total_out = 0; + zerr = inflateReset(&data->inflater); + if (zerr != Z_OK) + return CHDERR_DECOMPRESSION_ERROR; + + /* do it */ + zerr = inflate(&data->inflater, Z_FINISH); + if (data->inflater.total_out != chd->header.hunkbytes) + return CHDERR_DECOMPRESSION_ERROR; + + return CHDERR_NONE; +} + + +/*------------------------------------------------- + zlib_fast_alloc - fast malloc for ZLIB, which + allocates and frees memory frequently +-------------------------------------------------*/ + +static voidpf zlib_fast_alloc(voidpf opaque, uInt items, uInt size) +{ + zlib_codec_data *data = (zlib_codec_data *)opaque; + UINT32 *ptr; + int i; + + /* compute the size, rounding to the nearest 1k */ + size = (size * items + 0x3ff) & ~0x3ff; + + /* reuse a hunk if we can */ + for (i = 0; i < MAX_ZLIB_ALLOCS; i++) + { + ptr = data->allocptr[i]; + if (ptr && size == *ptr) + { + /* set the low bit of the size so we don't match next time */ + *ptr |= 1; + return ptr + 1; + } + } + + /* alloc a new one */ + ptr = (UINT32 *)malloc(size + sizeof(UINT32)); + if (!ptr) + return NULL; + + /* put it into the list */ + for (i = 0; i < MAX_ZLIB_ALLOCS; i++) + if (!data->allocptr[i]) + { + data->allocptr[i] = ptr; + break; + } + + /* set the low bit of the size so we don't match next time */ + *ptr = size | 1; + return ptr + 1; +} + + +/*------------------------------------------------- + zlib_fast_free - fast free for ZLIB, which + allocates and frees memory frequently +-------------------------------------------------*/ + +static void zlib_fast_free(voidpf opaque, voidpf address) +{ + zlib_codec_data *data = (zlib_codec_data *)opaque; + UINT32 *ptr = (UINT32 *)address - 1; + int i; + + /* find the hunk */ + for (i = 0; i < MAX_ZLIB_ALLOCS; i++) + if (ptr == data->allocptr[i]) + { + /* clear the low bit of the size to allow matches */ + *ptr &= ~1; + return; + } +} + + + +/*************************************************************************** + AV COMPRESSION CODEC +***************************************************************************/ + +/*------------------------------------------------- + av_raw_data_size - compute the raw data size +-------------------------------------------------*/ + +INLINE UINT32 av_raw_data_size(const UINT8 *data) +{ + int size = 0; + + /* make sure we have a correct header */ + if (data[0] == 'c' && data[1] == 'h' && data[2] == 'a' && data[3] == 'v') + { + /* add in header size plus metadata length */ + size = 12 + data[4]; + + /* add in channels * samples */ + size += 2 * data[5] * ((data[6] << 8) + data[7]); + + /* add in 2 * width * height */ + size += 2 * ((data[8] << 8) + data[9]) * (((data[10] << 8) + data[11]) & 0x7fff); + } + return size; +} + + +/*------------------------------------------------- + av_codec_init - initialize the A/V codec +-------------------------------------------------*/ + +static chd_error av_codec_init(chd_file *chd) +{ + av_codec_data *data; + + /* allocate memory for the 2 stream buffers */ + data = (av_codec_data *)malloc(sizeof(*data)); + if (data == NULL) + return CHDERR_OUT_OF_MEMORY; + + /* clear the buffers */ + memset(data, 0, sizeof(*data)); + chd->codecdata = data; + + /* attempt to do a post-init now; if we're creating a new CHD, this won't work */ + /* but that's ok */ + av_codec_postinit(chd); + return CHDERR_NONE; +} + + +/*------------------------------------------------- + av_codec_free - free data for the A/V + codec +-------------------------------------------------*/ + +static void av_codec_free(chd_file *chd) +{ + av_codec_data *data = (av_codec_data *)chd->codecdata; + + /* deinit avcomp */ + if (data != NULL) + { + if (data->compstate != NULL) + avcomp_free(data->compstate); + free(data); + } +} + + +/*------------------------------------------------- + av_codec_compress - compress data using the + A/V codec +-------------------------------------------------*/ + +static chd_error av_codec_compress(chd_file *chd, const void *src, UINT32 *length) +{ + av_codec_data *data = (av_codec_data *)chd->codecdata; + int averr; + int size; + + /* if we haven't yet set up the avcomp code, do it now */ + if (data->compstate == NULL) + { + chd_error chderr = av_codec_postinit(chd); + if (chderr != CHDERR_NONE) + return chderr; + } + + /* make sure short frames are padded with 0 */ + if (src != NULL) + { + size = av_raw_data_size((const UINT8 *)src); + while (size < chd->header.hunkbytes) + if (((const UINT8 *)src)[size++] != 0) + return CHDERR_INVALID_DATA; + } + + /* encode the audio and video */ + averr = avcomp_encode_data(data->compstate, (const UINT8 *)src, chd->compressed, length); + if (averr != AVCERR_NONE || *length > chd->header.hunkbytes) + return CHDERR_COMPRESSION_ERROR; + + return CHDERR_NONE; +} + + +/*------------------------------------------------- + av_codec_decompress - decomrpess data using + the A/V codec +-------------------------------------------------*/ + +static chd_error av_codec_decompress(chd_file *chd, UINT32 srclength, void *dest) +{ + av_codec_data *data = (av_codec_data *)chd->codecdata; + const UINT8 *source; + avcomp_error averr; + int size; + + /* if we haven't yet set up the avcomp code, do it now */ + if (data->compstate == NULL) + { + chd_error chderr = av_codec_postinit(chd); + if (chderr != CHDERR_NONE) + return chderr; + } + + /* decode the audio and video */ + source = chd->compressed; + averr = avcomp_decode_data(data->compstate, source, srclength, (UINT8 *)dest); + if (averr != AVCERR_NONE) + return CHDERR_DECOMPRESSION_ERROR; + + /* pad short frames with 0 */ + if (dest != NULL) + { + size = av_raw_data_size((const UINT8 *)dest); + while (size < chd->header.hunkbytes) + ((UINT8 *)dest)[size++] = 0; + } + + return CHDERR_NONE; +} + + +/*------------------------------------------------- + av_codec_config - codec-specific configuration + for the A/V codec +-------------------------------------------------*/ + +static chd_error av_codec_config(chd_file *chd, int param, void *config) +{ + av_codec_data *data = (av_codec_data *)chd->codecdata; + + /* if we're getting the compression configuration, apply it now */ + if (param == AV_CODEC_COMPRESS_CONFIG) + { + data->compress = *(av_codec_compress_config *)config; + if (data->compstate != NULL) + avcomp_config_compress(data->compstate, &data->compress); + return CHDERR_NONE; + } + + /* if we're getting the decompression configuration, apply it now */ + else if (param == AV_CODEC_DECOMPRESS_CONFIG) + { + data->decompress = *(av_codec_decompress_config *)config; + if (data->compstate != NULL) + avcomp_config_decompress(data->compstate, &data->decompress); + return CHDERR_NONE; + } + + /* anything else is invalid */ + return CHDERR_INVALID_PARAMETER; +} + + +/*------------------------------------------------- + av_codec_postinit - actual initialization of + avcomp happens here, on the first attempt + to compress or decompress data +-------------------------------------------------*/ + +static chd_error av_codec_postinit(chd_file *chd) +{ + int fps, fpsfrac, width, height, interlaced, channels, rate; + UINT32 fps_times_1million, max_samples_per_frame, bytes_per_frame; + av_codec_data *data = (av_codec_data *)chd->codecdata; + char metadata[256]; + chd_error err; + + /* the code below won't work asynchronously */ + if (chd->workitem != NULL) + return CHDERR_OPERATION_PENDING; + + /* get the metadata */ + err = chd_get_metadata(chd, AV_METADATA_TAG, 0, metadata, sizeof(metadata), NULL, NULL, NULL); + if (err != CHDERR_NONE) + return err; + + /* extract the info */ + if (sscanf(metadata, AV_METADATA_FORMAT, &fps, &fpsfrac, &width, &height, &interlaced, &channels, &rate) != 7) + return CHDERR_INVALID_METADATA; + + /* compute the bytes per frame */ + fps_times_1million = fps * 1000000 + fpsfrac; + max_samples_per_frame = ((UINT64)rate * 1000000 + fps_times_1million - 1) / fps_times_1million; + bytes_per_frame = 12 + channels * max_samples_per_frame * 2 + width * height * 2; + if (bytes_per_frame > chd->header.hunkbytes) + return CHDERR_INVALID_METADATA; + + /* create the avcomp state */ + data->compstate = avcomp_init(width, height, channels); + + /* configure the codec */ + avcomp_config_compress(data->compstate, &data->compress); + avcomp_config_decompress(data->compstate, &data->decompress); + return CHDERR_NONE; +} |