summaryrefslogtreecommitdiffstatshomepage
path: root/src/lib/util/chd.c
diff options
context:
space:
mode:
author Aaron Giles <aaron@aarongiles.com>2012-02-16 09:47:18 +0000
committer Aaron Giles <aaron@aarongiles.com>2012-02-16 09:47:18 +0000
commitf0823886a66100e193d6eeb0402eb872a67fa07d (patch)
treea68b35942d63e5fcaf2311812dba976ad18cd5b6 /src/lib/util/chd.c
parente6dad3759374373e3ce69a76869f16e83ba74df5 (diff)
Major CHD/chdman update. The CHD version number has been increased
from 4 to 5. This means any diff CHDs will no longer work. If you absolutely need to keep the data for any existing ones you have, find both the diff CHD and the original CHD for the game in question and upgrade using these commands: rename diff\game.dif diff\game-old.dif chdman copy -i diff\game-old.dif -ip roms\game.chd -o diff\game.dif -op roms\game.chd -c none Specifics regarding this change: Defined a new CHD version 5. New features/behaviors of this version: - support for up to 4 codecs; each block can use 1 of the 4 - new LZMA codec, which tends to do better than zlib overall - new FLAC codec, primarily used for CDs (but can be applied anywhere) - upgraded AVHuff codec now uses FLAC for encoding audio - new Huffman codec, used to catch more nearly-uncompressable blocks - compressed CHDs now use a compressed map for significant savings - CHDs now are aware of a "unit" size; each hunk holds 1 or more units (in general units map to sectors for hard disks/CDs) - diff'ing against a parent now diffs at the unit level, greatly improving compression Rewrote and modernized chd.c. CHD versions prior to 3 are unsupported, and version 3/4 CHDs are only supported for reading. Creating a new CHD now leaves the file open. Added methods to read and write at the unit and byte level, removing the need to handle this manually. Added metadata access methods that pass astrings and dynamic_buffers to simplify the interfaces. A companion class chd_compressor now implements full multithreaded compression, analyzing and compressing multiple hunks independently in parallel. Split the codec implementations out into a separate file chdcodec.* Updated harddisk.c and cdrom.c to rely on the caching/byte-level read/ write capabilities of the chd_file class. cdrom.c (and chdman) now also pad CDs to 4-frame boundaries instead of hunk boundaries, ensuring that the same SHA1 hashes are produced regardless of the hunk size. Rewrote chdman.exe entirely, switching from positional parameters to proper options. Use "chdman help" to get a list of commands, and "chdman help <command>" to get help for any particular command. Many redundant commands were removed now that additional flexibility is available. Some basic mappings: Old: chdman -createblankhd <out.chd> <cyls> <heads> <secs> New: chdman createhd -o <out.chd> -chs <cyls>,<heads>,<secs> Old: chdman -createuncomphd <in.raw> <out.chd> .... New: chdman createhd -i <in.raw> -o <out.chd> -c none .... Old: chdman -verifyfix <in.chd> New: chdman verify -i <in.chd> -f Old: chdman -merge <parent.chd> <diff.chd> <out.chd> New: chdman copy -i <diff.chd> -ip <parent.chd> -o <out.chd> Old: chdman -diff <parent.chd> <compare.chd> <diff.chd> New: chdman copy -i <compare.chd> -o <diff.chd> -op <parent.chd> Old: chdman -update <in.chd> <out.chd> New: chdman copy -i <in.chd> -o <out.chd> Added new core file coretmpl.h to hold core template classes. For now just one class, dynamic_array<> is defined, which acts like an array of a given object but which can be appended to and/or resized. Also defines dynamic_buffer as dynamic_array<UINT8> for holding an arbitrary buffer of bytes. Expect to see these used a lot. Added new core helper hashing.c/.h which defines classes for each of the common hashing methods and creator classes to wrap the computation of these hashes. A future work item is to reimplement the core emulator hashing code using these. Split bit buffer helpers out into C++ classes and into their own public header in bitstream.h. Updated huffman.c/.h to C++, and changed the interface to make it more flexible to use in nonstandard ways. Also added huffman compression of the static tree for slightly better compression rates. Created flac.c/.h as simplified C++ wrappers around the FLAC interface. A future work item is to convert the samples sound device to a modern device and leverage this for reading FLAC files. Renamed avcomp.* to avhuff.*, updated to C++, and added support for FLAC as the audio encoding mechanism. The old huffman audio is still supported for decode only. Added a variant of core_fload that loads to a dynamic_buffer. Tweaked winwork.c a bit to not limit the maximum number of processors unless the work queue was created with the WORK_QUEUE_FLAG_HIGH_FREQ option. Further adjustments here are likely going to be necessary. Fixed bug in aviio.c which caused errors when reading some AVI files.
Diffstat (limited to 'src/lib/util/chd.c')
-rw-r--r--src/lib/util/chd.c5198
1 files changed, 2191 insertions, 3007 deletions
diff --git a/src/lib/util/chd.c b/src/lib/util/chd.c
index 3f3424acb3c..dfb870f849c 100644
--- a/src/lib/util/chd.c
+++ b/src/lib/util/chd.c
@@ -38,3532 +38,2716 @@
***************************************************************************/
#include "chd.h"
-#include "avcomp.h"
-#include "md5.h"
-#include "sha1.h"
+#include "avhuff.h"
+#include "hashing.h"
+#include "flac.h"
#include "cdrom.h"
+#include "coretmpl.h"
#include <zlib.h>
#include <time.h>
#include <stddef.h>
#include <stdlib.h>
#include <new>
-#include "../../lib/libflac/include/flac/all.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_FLAG_HALF_HUNK 0x20 /* only the first half of this hunk is included in the SHA1 calculation (workaround for CD track padding issue) */
-
-#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 MAP_ENTRY_TYPE_2ND_COMPRESSED 0x06 /* compressed with secondary algorithm (usually FLAC CDDA) */
-
-#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)
+//**************************************************************************
+// CONSTANTS
+//**************************************************************************
+// standard metadata formats
+const char *HARD_DISK_METADATA_FORMAT = "CYLS:%d,HEADS:%d,SECS:%d,BPS:%d";
+const char *CDROM_TRACK_METADATA_FORMAT = "TRACK:%d TYPE:%s SUBTYPE:%s FRAMES:%d";
+const char *CDROM_TRACK_METADATA2_FORMAT = "TRACK:%d TYPE:%s SUBTYPE:%s FRAMES:%d PREGAP:%d PGTYPE:%s PGSUB:%s POSTGAP:%d";
+const char *AV_METADATA_FORMAT = "FPS:%d.%06d WIDTH:%d HEIGHT:%d INTERLACED:%d CHANNELS:%d SAMPLERATE:%d";
-/***************************************************************************
- 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 */
-
- chd_error (*secondary_compress)(chd_file *chd, const void *src, UINT32 *complen); /* secondary compress data */
- chd_error (*secondary_decompress)(chd_file *chd, UINT32 complen, void *dst); /* secondary decompress data */
-
-};
-
-
-/* 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 */
-};
+static const UINT32 METADATA_HEADER_SIZE = 16; // metadata header size
+static const UINT8 V34_MAP_ENTRY_FLAG_TYPE_MASK = 0x0f; // what type of hunk
+static const UINT8 V34_MAP_ENTRY_FLAG_NO_CRC = 0x10; // no CRC is present
-/* 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
+// V3-V4 entry types
+enum
{
- 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 */
+ V34_MAP_ENTRY_TYPE_INVALID = 0, // invalid type
+ V34_MAP_ENTRY_TYPE_COMPRESSED = 1, // standard compression
+ V34_MAP_ENTRY_TYPE_UNCOMPRESSED = 2, // uncompressed data
+ V34_MAP_ENTRY_TYPE_MINI = 3, // mini: use offset as raw data
+ V34_MAP_ENTRY_TYPE_SELF_HUNK = 4, // same as another hunk in this file
+ V34_MAP_ENTRY_TYPE_PARENT_HUNK = 5, // same as a hunk in the parent file
+ V34_MAP_ENTRY_TYPE_2ND_COMPRESSED = 6 // compressed with secondary algorithm (usually FLAC CDDA)
};
-
-/* 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 */
+// V5 compression types
+enum
+{
+ // these types are live when running
+ COMPRESSION_TYPE_0 = 0, // codec #0
+ COMPRESSION_TYPE_1 = 1, // codec #1
+ COMPRESSION_TYPE_2 = 2, // codec #2
+ COMPRESSION_TYPE_3 = 3, // codec #3
+ COMPRESSION_NONE = 4, // no compression; implicit length = hunkbytes
+ COMPRESSION_SELF = 5, // same as another block in this chd
+ COMPRESSION_PARENT = 6, // same as a hunk's worth of units in the parent chd
+
+ // these additional pseudo-types are used for compressed encodings:
+ COMPRESSION_RLE_SMALL, // start of small RLE run (4-bit length)
+ COMPRESSION_RLE_LARGE, // start of large RLE run (8-bit length)
+ COMPRESSION_SELF_0, // same as the last COMPRESSION_SELF block
+ COMPRESSION_SELF_1, // same as the last COMPRESSION_SELF block + 1
+ COMPRESSION_PARENT_SELF, // same block in the parent
+ COMPRESSION_PARENT_0, // same as the last COMPRESSION_PARENT block
+ COMPRESSION_PARENT_1 // same as the last COMPRESSION_PARENT block + 1
};
-/* 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 */
-struct av_codec_data
-{
- avcomp_state * compstate;
- av_codec_compress_config compress;
- av_codec_decompress_config decompress;
-};
+//**************************************************************************
+// TYPE DEFINITIONS
+//**************************************************************************
+// ======================> metadata_entry
-/* a single metadata hash entry */
-typedef struct _metadata_hash metadata_hash;
-struct _metadata_hash
+// description of where a metadata entry lives within the file
+struct chd_file::metadata_entry
{
- UINT8 tag[4]; /* tag of the metadata in big-endian */
- UINT8 sha1[CHD_SHA1_BYTES]; /* hash */
+ UINT64 offset; // offset within the file of the header
+ UINT64 next; // offset within the file of the next header
+ UINT64 prev; // offset within the file of the previous header
+ UINT32 length; // length of the metadata
+ UINT32 metatag; // metadata tag
+ UINT8 flags; // flag bits
};
+// ======================> metadata_hash
-/***************************************************************************
- 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, int is_half_hunk = 0);
-
-/* 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);
-
-/* flac compression codec */
-static chd_error flac_codec_compress(chd_file *chd, const void *src, UINT32 *length, int swap);
-static chd_error flac_codec_compress_normal(chd_file *chd, const void *src, UINT32 *length);
-static chd_error flac_codec_decompress(chd_file *chd, UINT32 srclength, void *dest);
-
-
-/* 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[] =
+struct chd_file::metadata_hash
{
- /* "none" or no compression */
- {
- CHDCOMPRESSION_NONE,
- "none",
- FALSE,
- NULL,
- NULL,
- 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,
- NULL,
- NULL
- },
-
- /* zlib+ compression */
- {
- CHDCOMPRESSION_ZLIB_PLUS,
- "zlib+",
- FALSE,
- zlib_codec_init,
- zlib_codec_free,
- zlib_codec_compress,
- zlib_codec_decompress,
- NULL,
- NULL,
- NULL
- },
-
- /* a/v compression */
- {
- CHDCOMPRESSION_AV,
- "A/V",
- TRUE,
- av_codec_init,
- av_codec_free,
- av_codec_compress,
- av_codec_decompress,
- av_codec_config,
- NULL,
- NULL
- },
-
- /* zlib+ with FLAC compression */
- {
- CHDCOMPRESSION_ZLIB_PLUS_WITH_FLAC,
- "zlib+ with FLAC",
- FALSE,
- zlib_codec_init,
- zlib_codec_free,
- zlib_codec_compress,
- zlib_codec_decompress,
- NULL,
- flac_codec_compress_normal,
- flac_codec_decompress,
- },
+ UINT8 tag[4]; // tag of the metadata in big-endian
+ sha1_t sha1; // hash data
};
-/***************************************************************************
- INLINE FUNCTIONS
-***************************************************************************/
+//**************************************************************************
+// INLINE FUNCTIONS
+//**************************************************************************
-/*-------------------------------------------------
- get_bigendian_uint64 - fetch a UINT64 from
- the data stream in bigendian order
--------------------------------------------------*/
+//-------------------------------------------------
+// be_read - extract a big-endian number from
+// a byte buffer
+//-------------------------------------------------
-INLINE UINT64 get_bigendian_uint64(const UINT8 *base)
+inline UINT64 chd_file::be_read(const UINT8 *base, int numbytes)
{
- 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];
+ UINT64 result = 0;
+ while (numbytes--)
+ result = (result << 8) | *base++;
+ return result;
}
-/*-------------------------------------------------
- put_bigendian_uint64 - write a UINT64 to
- the data stream in bigendian order
--------------------------------------------------*/
+//-------------------------------------------------
+// be_write - write a big-endian number to a byte
+// buffer
+//-------------------------------------------------
-INLINE void put_bigendian_uint64(UINT8 *base, UINT64 value)
+inline void chd_file::be_write(UINT8 *base, UINT64 value, int numbytes)
{
- 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;
+ base += numbytes;
+ while (numbytes--)
+ {
+ *--base = value;
+ value >>= 8;
+ }
}
-/*-------------------------------------------------
- get_bigendian_uint32 - fetch a UINT32 from
- the data stream in bigendian order
--------------------------------------------------*/
+//-------------------------------------------------
+// be_read_sha1 - fetch a sha1_t from a data
+// stream in bigendian order
+//-------------------------------------------------
-INLINE UINT32 get_bigendian_uint32(const UINT8 *base)
+inline sha1_t chd_file::be_read_sha1(const UINT8 *base)
{
- return (base[0] << 24) | (base[1] << 16) | (base[2] << 8) | base[3];
+ sha1_t result;
+ memcpy(&result.m_raw[0], base, sizeof(result.m_raw));
+ return result;
}
-/*-------------------------------------------------
- put_bigendian_uint32 - write a UINT32 to
- the data stream in bigendian order
--------------------------------------------------*/
+//-------------------------------------------------
+// be_write_sha1 - write a sha1_t to a data
+// stream in bigendian order
+//-------------------------------------------------
-INLINE void put_bigendian_uint32(UINT8 *base, UINT32 value)
+inline void chd_file::be_write_sha1(UINT8 *base, sha1_t value)
{
- base[0] = value >> 24;
- base[1] = value >> 16;
- base[2] = value >> 8;
- base[3] = value;
+ memcpy(base, &value.m_raw[0], sizeof(value.m_raw));
}
-/*-------------------------------------------------
- get_bigendian_uint16 - fetch a UINT16 from
- the data stream in bigendian order
--------------------------------------------------*/
+//-------------------------------------------------
+// file_read - read from the file at the given
+// offset; on failure throw an error
+//-------------------------------------------------
-INLINE UINT16 get_bigendian_uint16(const UINT8 *base)
+inline void chd_file::file_read(UINT64 offset, void *dest, UINT32 length)
{
- return (base[0] << 8) | base[1];
+ // no file = failure
+ if (m_file == NULL)
+ throw CHDERR_NOT_OPEN;
+
+ // seek and read
+ core_fseek(m_file, offset, SEEK_SET);
+ UINT32 count = core_fread(m_file, dest, length);
+ if (count != length)
+ throw CHDERR_READ_ERROR;
}
-/*-------------------------------------------------
- put_bigendian_uint16 - write a UINT16 to
- the data stream in bigendian order
--------------------------------------------------*/
+//-------------------------------------------------
+// file_write - write to the file at the given
+// offset; on failure throw an error
+//-------------------------------------------------
-INLINE void put_bigendian_uint16(UINT8 *base, UINT16 value)
+inline void chd_file::file_write(UINT64 offset, const void *source, UINT32 length)
{
- base[0] = value >> 8;
- base[1] = value;
+ // no file = failure
+ if (m_file == NULL)
+ throw CHDERR_NOT_OPEN;
+
+ // seek and write
+ core_fseek(m_file, offset, SEEK_SET);
+ UINT32 count = core_fwrite(m_file, source, length);
+ if (count != length)
+ throw CHDERR_WRITE_ERROR;
}
-/*-------------------------------------------------
- map_extract - extract a single map
- entry from the datastream
--------------------------------------------------*/
+//-------------------------------------------------
+// file_append - append to the file at the given
+// offset, ensuring we start at the given
+// alignment; on failure throw an error
+//-------------------------------------------------
-INLINE void map_extract(const UINT8 *base, map_entry *entry)
+inline UINT64 chd_file::file_append(const void *source, UINT32 length, UINT32 alignment)
{
- 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];
-}
-
+ // no file = failure
+ if (m_file == NULL)
+ throw CHDERR_NOT_OPEN;
-/*-------------------------------------------------
- map_assemble - write a single map
- entry to the datastream
--------------------------------------------------*/
+ // seek to the end and align if necessary
+ core_fseek(m_file, 0, SEEK_END);
+ if (alignment != 0)
+ {
+ UINT64 offset = core_ftell(m_file);
+ UINT32 delta = offset % alignment;
+ if (delta != 0)
+ {
+ // pad with 0's from a local buffer
+ UINT8 buffer[1024];
+ memset(buffer, 0, sizeof(buffer));
+ delta = alignment - delta;
+ while (delta != 0)
+ {
+ UINT32 bytes_to_write = MIN(sizeof(buffer), delta);
+ UINT32 count = core_fwrite(m_file, buffer, bytes_to_write);
+ if (count != length)
+ throw CHDERR_WRITE_ERROR;
+ delta -= bytes_to_write;
+ }
+ }
+ }
-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;
+ // write the real data
+ UINT64 offset = core_ftell(m_file);
+ UINT32 count = core_fwrite(m_file, source, length);
+ if (count != length)
+ throw CHDERR_READ_ERROR;
+ return offset;
}
-/*-------------------------------------------------
- map_extract_old - extract a single map
- entry in old format from the datastream
--------------------------------------------------*/
+//-------------------------------------------------
+// bits_for_value - return the number of bits
+// necessary to represent all numbers 0..value
+//-------------------------------------------------
-INLINE void map_extract_old(const UINT8 *base, map_entry *entry, UINT32 hunkbytes)
+inline UINT8 chd_file::bits_for_value(UINT64 value)
{
- 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
+ UINT8 result = 0;
+ while (value != 0)
+ value >>= 1, result++;
+ return result;
}
-/*-------------------------------------------------
- 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;
+//**************************************************************************
+// CHD FILE MANAGEMENT
+//**************************************************************************
- /* 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;
+//-------------------------------------------------
+// chd_file - constructor
+//-------------------------------------------------
- return TRUE;
+chd_file::chd_file()
+ : m_file(NULL)
+{
+ // reset state
+ memset(m_decompressor, 0, sizeof(m_decompressor));
+ close();
}
-/*-------------------------------------------------
- wait_for_pending_async - wait for any pending
- async
--------------------------------------------------*/
+//-------------------------------------------------
+// ~chd_file - destructor
+//-------------------------------------------------
-INLINE void wait_for_pending_async(chd_file *chd)
+chd_file::~chd_file()
{
- /* 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!");
- }
+ // close any open files
+ close();
}
+//-------------------------------------------------
+// sha1 - return our SHA1 value
+//-------------------------------------------------
-/***************************************************************************
- 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)
+sha1_t chd_file::sha1()
{
- 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)
+ try
{
- logicalbytes = parent->header.logicalbytes;
- hunkbytes = parent->header.hunkbytes;
+ // read the big-endian version
+ UINT8 rawbuf[sizeof(sha1_t)];
+ file_read(m_sha1_offset, rawbuf, sizeof(rawbuf));
+ return be_read_sha1(rawbuf);
}
-
- /* 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)
+ catch (chd_error &)
{
- 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));
+ // on failure, return NULL
+ return sha1_t::null;
}
-
- /* 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
--------------------------------------------------*/
+//-------------------------------------------------
+// raw_sha1 - return our raw SHA1 value
+//-------------------------------------------------
-chd_error chd_open_file(core_file *file, int mode, chd_file *parent, chd_file **chd)
+sha1_t chd_file::raw_sha1()
{
- 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)
+ try
{
- /* 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);
+ // determine offset within the file for data-only
+ if (m_rawsha1_offset == 0)
+ throw CHDERR_UNSUPPORTED_VERSION;
+
+ // read the big-endian version
+ UINT8 rawbuf[sizeof(sha1_t)];
+ file_read(m_rawsha1_offset, rawbuf, sizeof(rawbuf));
+ return be_read_sha1(rawbuf);
}
-
- /* 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)
+ catch (chd_error &)
{
- chderr = CHDERR_FILE_NOT_FOUND;
- goto cleanup;
+ // on failure, return NULL
+ return sha1_t::null;
}
-
- 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
--------------------------------------------------*/
+//-------------------------------------------------
+// parent_sha1 - return our parent's SHA1 value
+//-------------------------------------------------
-chd_error chd_open(const char *filename, int mode, chd_file *parent, chd_file **chd)
+sha1_t chd_file::parent_sha1()
{
- chd_error err;
- file_error filerr;
- core_file *file = NULL;
- UINT32 openflags;
-
- /* choose the proper mode */
- switch(mode)
+ try
{
- 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;
+ // determine offset within the file
+ if (m_parentsha1_offset == 0)
+ throw CHDERR_UNSUPPORTED_VERSION;
+
+ // read the big-endian version
+ UINT8 rawbuf[sizeof(sha1_t)];
+ file_read(m_parentsha1_offset, rawbuf, sizeof(rawbuf));
+ return be_read_sha1(rawbuf);
}
-
- /* open the file */
- filerr = core_fopen(filename, openflags, &file);
- if (filerr != FILERR_NONE)
+ catch (chd_error &)
{
- err = CHDERR_FILE_NOT_FOUND;
- goto cleanup;
+ // on failure, return NULL
+ return sha1_t::null;
}
-
- /* 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
--------------------------------------------------*/
+//-------------------------------------------------
+// hunk_info - return information about this
+// hunk
+//-------------------------------------------------
-void chd_close(chd_file *chd)
+chd_error chd_file::hunk_info(UINT32 hunknum, chd_codec_type &compressor, UINT32 &compbytes)
{
- /* 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
--------------------------------------------------*/
+ // error if invalid
+ if (hunknum >= m_hunkcount)
+ return CHDERR_HUNK_OUT_OF_RANGE;
+
+ // get the map pointer
+ UINT8 *rawmap;
+ switch (m_version)
+ {
+ // v3/v4 map entries
+ case 3:
+ case 4:
+ rawmap = m_rawmap + 16 * hunknum;
+ switch (rawmap[15] & V34_MAP_ENTRY_FLAG_TYPE_MASK)
+ {
+ case V34_MAP_ENTRY_TYPE_COMPRESSED:
+ compressor = CHD_CODEC_ZLIB;
+ compbytes = be_read(&rawmap[12], 2) + (rawmap[14] << 16);
+ break;
+
+ case V34_MAP_ENTRY_TYPE_UNCOMPRESSED:
+ compressor = CHD_CODEC_NONE;
+ compbytes = m_hunkbytes;
+ break;
+
+ case V34_MAP_ENTRY_TYPE_MINI:
+ compressor = CHD_CODEC_MINI;
+ compbytes = 0;
+ break;
+
+ case V34_MAP_ENTRY_TYPE_SELF_HUNK:
+ compressor = CHD_CODEC_SELF;
+ compbytes = 0;
+ break;
-core_file *chd_core_file(chd_file *chd)
-{
- return chd->file;
-}
+ case V34_MAP_ENTRY_TYPE_PARENT_HUNK:
+ compressor = CHD_CODEC_PARENT;
+ compbytes = 0;
+ break;
+ }
+ break;
+
+ // v5 map entries
+ case 5:
+ rawmap = m_rawmap + m_mapentrybytes * hunknum;
+ // uncompressed case
+ if (!compressed())
+ {
+ if (be_read(&rawmap[0], 4) == 0)
+ {
+ compressor = CHD_CODEC_PARENT;
+ compbytes = 0;
+ }
+ else
+ {
+ compressor = CHD_CODEC_NONE;
+ compbytes = m_hunkbytes;
+ }
+ break;
+ }
+
+ // compressed case
+ switch (rawmap[0])
+ {
+ case COMPRESSION_TYPE_0:
+ case COMPRESSION_TYPE_1:
+ case COMPRESSION_TYPE_2:
+ case COMPRESSION_TYPE_3:
+ compressor = m_compression[rawmap[0]];
+ compbytes = be_read(&rawmap[1], 3);
+ break;
-/*-------------------------------------------------
- chd_error_string - return an error string for
- the given CHD error
--------------------------------------------------*/
+ case COMPRESSION_NONE:
+ compressor = CHD_CODEC_NONE;
+ compbytes = m_hunkbytes;
+ break;
-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";
+ case COMPRESSION_SELF:
+ compressor = CHD_CODEC_SELF;
+ compbytes = 0;
+ break;
+
+ case COMPRESSION_PARENT:
+ compressor = CHD_CODEC_PARENT;
+ compbytes = 0;
+ break;
+ }
+ break;
}
-}
-
-
-
-/***************************************************************************
- 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
--------------------------------------------------*/
+//-------------------------------------------------
+// set_raw_sha1 - set our SHA1 values
+//-------------------------------------------------
-chd_error chd_set_header(const char *filename, const chd_header *header)
+void chd_file::set_raw_sha1(sha1_t rawdata)
{
- 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;
+ // create a big-endian version
+ UINT8 rawbuf[sizeof(sha1_t)];
+ be_write_sha1(rawbuf, rawdata);
+
+ // write to the header
+ UINT64 offset = (m_rawsha1_offset != 0) ? m_rawsha1_offset : m_sha1_offset;
+ assert(offset != 0);
+ file_write(offset, rawbuf, sizeof(rawbuf));
+
+ // if we have a separate rawsha1_offset, update the full sha1 as well
+ if (m_rawsha1_offset != 0)
+ metadata_update_hash();
}
+//-------------------------------------------------
+// set_parent_sha1 - set the parent SHA1 value
+//-------------------------------------------------
-/***************************************************************************
- 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)
+void chd_file::set_parent_sha1(sha1_t parent)
{
- /* 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;
+ // if no file, fail
+ if (m_file == NULL)
+ throw CHDERR_INVALID_FILE;
- /* wait for any pending async operations */
- wait_for_pending_async(chd);
-
- /* perform the read */
- return hunk_read_into_memory(chd, hunknum, (UINT8 *)buffer);
+ // create a big-endian version
+ UINT8 rawbuf[sizeof(sha1_t)];
+ be_write_sha1(rawbuf, parent);
+
+ // write to the header
+ assert(m_parentsha1_offset != 0);
+ file_write(m_parentsha1_offset, rawbuf, sizeof(rawbuf));
}
-/*-------------------------------------------------
- chd_read_async - read a single hunk from the
- CHD file asynchronously
--------------------------------------------------*/
+//-------------------------------------------------
+// create - create a new file with no parent
+// using an existing opened file handle
+//-------------------------------------------------
-chd_error chd_read_async(chd_file *chd, UINT32 hunknum, void *buffer)
+chd_error chd_file::create(core_file &file, UINT64 logicalbytes, UINT32 hunkbytes, UINT32 unitbytes, chd_codec_type compression[4])
{
- /* 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;
+ // make sure we don't already have a file open
+ if (m_file != NULL)
+ return CHDERR_ALREADY_OPEN;
- /* wait for any pending async operations */
- wait_for_pending_async(chd);
+ // set the header parameters
+ m_version = HEADER_VERSION;
+ m_logicalbytes = logicalbytes;
+ m_metaoffset = 0;
+ m_hunkbytes = hunkbytes;
+ m_hunkcount = (m_logicalbytes + m_hunkbytes - 1) / m_hunkbytes;
+ m_unitbytes = unitbytes;
+ memcpy(m_compression, compression, sizeof(m_compression));
+ m_parent = NULL;
- /* 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);
+ // take ownership of the file
+ m_file = &file;
+ m_owns_file = false;
+ return create_common();
}
-/*-------------------------------------------------
- chd_write - write a single hunk to the CHD
- file
--------------------------------------------------*/
+//-------------------------------------------------
+// create - create a new file with a parent
+// using an existing opened file handle
+//-------------------------------------------------
-chd_error chd_write(chd_file *chd, UINT32 hunknum, const void *buffer)
+chd_error chd_file::create(core_file &file, UINT64 logicalbytes, UINT32 hunkbytes, chd_codec_type compression[4], chd_file &parent)
{
- /* 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;
+ // make sure we don't already have a file open
+ if (m_file != NULL)
+ return CHDERR_ALREADY_OPEN;
- /* wait for any pending async operations */
- wait_for_pending_async(chd);
+ // set the header parameters
+ m_version = HEADER_VERSION;
+ m_logicalbytes = logicalbytes;
+ m_metaoffset = 0;
+ m_hunkbytes = hunkbytes;
+ m_hunkcount = (m_logicalbytes + m_hunkbytes - 1) / m_hunkbytes;
+ m_unitbytes = parent.unit_bytes();
+ memcpy(m_compression, compression, sizeof(m_compression));
+ m_parent = &parent;
- /* then write out the hunk */
- return hunk_write_from_memory(chd, hunknum, (const UINT8 *)buffer);
+ // take ownership of the file
+ m_file = &file;
+ m_owns_file = false;
+ return create_common();
}
-/*-------------------------------------------------
- chd_write_async - write a single hunk to the
- CHD file asynchronously
--------------------------------------------------*/
+//-------------------------------------------------
+// create - create a new file with no parent
+// using a filename
+//-------------------------------------------------
-chd_error chd_write_async(chd_file *chd, UINT32 hunknum, const void *buffer)
+chd_error chd_file::create(const char *filename, UINT64 logicalbytes, UINT32 hunkbytes, UINT32 unitbytes, chd_codec_type compression[4])
{
- /* 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);
+ // make sure we don't already have a file open
+ if (m_file != NULL)
+ return CHDERR_ALREADY_OPEN;
- /* 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;
+ // create the new file
+ core_file *file = NULL;
+ file_error filerr = core_fopen(filename, OPEN_FLAG_READ | OPEN_FLAG_WRITE | OPEN_FLAG_CREATE, &file);
+ if (filerr != FILERR_NONE)
+ return CHDERR_FILE_NOT_FOUND;
- /* if we fail, fall back on the sync version */
- return chd_write(chd, hunknum, buffer);
+ // create the file normally, then claim the file
+ chd_error chderr = create(*file, logicalbytes, hunkbytes, unitbytes, compression);
+ m_owns_file = true;
+
+ // if an error happened, close and delete the file
+ if (chderr != CHDERR_NONE)
+ {
+ core_fclose(file);
+ osd_rmfile(filename);
+ }
+ return chderr;
}
-/*-------------------------------------------------
- chd_async_complete - get the result of a
- completed work item and clear it out of the
- system
--------------------------------------------------*/
+//-------------------------------------------------
+// create - create a new file with a parent
+// using a filename
+//-------------------------------------------------
-chd_error chd_async_complete(chd_file *chd)
+chd_error chd_file::create(const char *filename, UINT64 logicalbytes, UINT32 hunkbytes, chd_codec_type compression[4], chd_file &parent)
{
- void *result;
+ // make sure we don't already have a file open
+ if (m_file != NULL)
+ return CHDERR_ALREADY_OPEN;
- /* 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;
+ // create the new file
+ core_file *file = NULL;
+ file_error filerr = core_fopen(filename, OPEN_FLAG_READ | OPEN_FLAG_WRITE | OPEN_FLAG_CREATE, &file);
+ if (filerr != FILERR_NONE)
+ return CHDERR_FILE_NOT_FOUND;
- return (chd_error)(ptrdiff_t)result;
+ // create the file normally, then claim the file
+ chd_error chderr = create(*file, logicalbytes, hunkbytes, compression, parent);
+ m_owns_file = true;
+
+ // if an error happened, close and delete the file
+ if (chderr != CHDERR_NONE)
+ {
+ core_fclose(file);
+ osd_rmfile(filename);
+ }
+ return chderr;
}
+//-------------------------------------------------
+// open - open an existing file for read or
+// read/write
+//-------------------------------------------------
-/***************************************************************************
- 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)
+chd_error chd_file::open(const char *filename, bool writeable, chd_file *parent)
{
- metadata_entry metaentry;
- chd_error err;
- UINT32 count;
+ // make sure we don't already have a file open
+ if (m_file != NULL)
+ return CHDERR_ALREADY_OPEN;
- /* wait for any pending async operations */
- wait_for_pending_async(chd);
+ // open the file
+ UINT32 openflags = writeable ? (OPEN_FLAG_READ | OPEN_FLAG_WRITE) : OPEN_FLAG_READ;
+ core_file *file = NULL;
+ file_error filerr = core_fopen(filename, openflags, &file);
+ if (filerr != FILERR_NONE)
+ return CHDERR_FILE_NOT_FOUND;
- /* if we didn't find it, just return */
- err = metadata_find_entry(chd, searchtag, searchindex, &metaentry);
+ // now open the CHD
+ chd_error err = open(*file, writeable, parent);
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;
- }
+ core_fclose(file);
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;
+
+ // we now own this file
+ m_owns_file = true;
+ return err;
}
-/*-------------------------------------------------
- chd_set_metadata - write the indexed metadata
- of the given type
--------------------------------------------------*/
+//-------------------------------------------------
+// open - open an existing file for read or
+// read/write
+//-------------------------------------------------
-chd_error chd_set_metadata(chd_file *chd, UINT32 metatag, UINT32 metaindex, const void *inputbuf, UINT32 inputlen, UINT8 flags)
+chd_error chd_file::open(core_file &file, bool writeable, chd_file *parent)
{
- 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);
+ // make sure we don't already have a file open
+ if (m_file != NULL)
+ return CHDERR_ALREADY_OPEN;
- /* find the entry if it already exists */
- err = metadata_find_entry(chd, metatag, metaindex, &metaentry);
+ // open the file
+ m_file = &file;
+ m_owns_file = false;
+ m_parent = parent;
+ return open_common(writeable);
+}
- /* 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;
- }
+//-------------------------------------------------
+// close - close a CHD file for access
+//-------------------------------------------------
- /* 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;
- }
+void chd_file::close()
+{
+ // reset file characteristics
+ if (m_owns_file && m_file != NULL)
+ core_fclose(m_file);
+ m_file = NULL;
+ m_owns_file = false;
+ m_allow_reads = false;
+ m_allow_writes = false;
+
+ // reset core parameters from the header
+ m_version = HEADER_VERSION;
+ m_logicalbytes = 0;
+ m_mapoffset = 0;
+ m_metaoffset = 0;
+ m_hunkbytes = 0;
+ m_hunkcount = 0;
+ m_unitbytes = 0;
+ m_unitcount = 0;
+ memset(m_compression, 0, sizeof(m_compression));
+ m_parent = NULL;
+ m_parent_missing = false;
- /* 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);
+ // reset key offsets within the header
+ m_mapoffset_offset = 0;
+ m_metaoffset_offset = 0;
+ m_sha1_offset = 0;
+ m_rawsha1_offset = 0;
+ m_parentsha1_offset = 0;
- /* 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;
- }
+ // reset map information
+ m_mapentrybytes = 0;
+ m_rawmap.reset();
- /* 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)
+ // reset compression management
+ for (int decompnum = 0; decompnum < ARRAY_LENGTH(m_decompressor); decompnum++)
{
- err = CHDERR_WRITE_ERROR;
- goto update;
+ delete m_decompressor[decompnum];
+ m_decompressor[decompnum] = NULL;
}
+ m_compressed.reset();
- /* 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;
+ // reset caching
+ m_cache.reset();
+ m_cachehunk = ~0;
}
-/*-------------------------------------------------
- chd_clone_metadata - clone the metadata from
- one CHD to a second
--------------------------------------------------*/
+//-------------------------------------------------
+// read - read a single hunk from the CHD file
+//-------------------------------------------------
-chd_error chd_clone_metadata(chd_file *source, chd_file *dest)
+chd_error chd_file::read_hunk(UINT32 hunknum, void *buffer)
{
- UINT32 metatag, metasize, metaindex;
- UINT8 metabuffer[1024];
- UINT8 metaflags;
- chd_error err;
-
- /* clone the metadata */
- for (metaindex = 0; ; metaindex++)
+ // wrap this for clean reporting
+ try
{
- /* 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;
- }
+ // punt if no file
+ if (m_file == NULL)
+ throw CHDERR_NOT_OPEN;
+
+ // return an error if out of range
+ if (hunknum >= m_hunkcount)
+ throw CHDERR_HUNK_OUT_OF_RANGE;
- /* otherwise, allocate a bigger temporary buffer */
- else
+ // get a pointer to the map entry
+ UINT64 blockoffs;
+ UINT32 blocklen;
+ UINT32 blockcrc;
+ UINT8 *rawmap;
+ UINT8 *dest = reinterpret_cast<UINT8 *>(buffer);
+ switch (m_version)
{
- 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);
+ // v3/v4 map entries
+ case 3:
+ case 4:
+ rawmap = m_rawmap + 16 * hunknum;
+ blockoffs = be_read(&rawmap[0], 8);
+ blockcrc = be_read(&rawmap[8], 4);
+ switch (rawmap[15] & V34_MAP_ENTRY_FLAG_TYPE_MASK)
+ {
+ case V34_MAP_ENTRY_TYPE_COMPRESSED:
+ blocklen = be_read(&rawmap[12], 2) + (rawmap[14] << 16);
+ file_read(blockoffs, m_compressed, blocklen);
+ m_decompressor[0]->decompress(m_compressed, blocklen, dest, m_hunkbytes);
+ if (!(rawmap[15] & V34_MAP_ENTRY_FLAG_NO_CRC) && dest != NULL && crc32_creator::simple(dest, m_hunkbytes) != blockcrc)
+ throw CHDERR_DECOMPRESSION_ERROR;
+ return CHDERR_NONE;
+
+ case V34_MAP_ENTRY_TYPE_UNCOMPRESSED:
+ file_read(blockoffs, dest, m_hunkbytes);
+ if (!(rawmap[15] & V34_MAP_ENTRY_FLAG_NO_CRC) && crc32_creator::simple(dest, m_hunkbytes) != blockcrc)
+ throw CHDERR_DECOMPRESSION_ERROR;
+ return CHDERR_NONE;
+
+ case V34_MAP_ENTRY_TYPE_MINI:
+ be_write(dest, blockoffs, 8);
+ for (UINT32 bytes = 8; bytes < m_hunkbytes; bytes++)
+ dest[bytes] = dest[bytes - 8];
+ if (!(rawmap[15] & V34_MAP_ENTRY_FLAG_NO_CRC) && crc32_creator::simple(dest, m_hunkbytes) != blockcrc)
+ throw CHDERR_DECOMPRESSION_ERROR;
+ return CHDERR_NONE;
+
+ case V34_MAP_ENTRY_TYPE_SELF_HUNK:
+ return read_hunk(blockoffs, dest);
+
+ case V34_MAP_ENTRY_TYPE_PARENT_HUNK:
+ if (m_parent_missing)
+ throw CHDERR_REQUIRES_PARENT;
+ return m_parent->read_hunk(blockoffs, dest);
+ }
break;
- }
+
+ // v5 map entries
+ case 5:
+ rawmap = m_rawmap + m_mapentrybytes * hunknum;
- /* write it to the target */
- err = chd_set_metadata(dest, metatag, CHD_METAINDEX_APPEND, allocbuffer, metasize, metaflags);
- free(allocbuffer);
- if (err != CHDERR_NONE)
+ // uncompressed case
+ if (!compressed())
+ {
+ blockoffs = UINT64(be_read(rawmap, 4)) * UINT64(m_hunkbytes);
+ if (blockoffs != 0)
+ file_read(blockoffs, dest, m_hunkbytes);
+ else if (m_parent_missing)
+ throw CHDERR_REQUIRES_PARENT;
+ else if (m_parent != NULL)
+ m_parent->read_hunk(hunknum, dest);
+ else
+ memset(dest, 0, m_hunkbytes);
+ return CHDERR_NONE;
+ }
+
+ // compressed case
+ blocklen = be_read(&rawmap[1], 3);
+ blockoffs = be_read(&rawmap[4], 6);
+ blockcrc = be_read(&rawmap[10], 2);
+ switch (rawmap[0])
+ {
+ case COMPRESSION_TYPE_0:
+ case COMPRESSION_TYPE_1:
+ case COMPRESSION_TYPE_2:
+ case COMPRESSION_TYPE_3:
+ file_read(blockoffs, m_compressed, blocklen);
+ m_decompressor[rawmap[0]]->decompress(m_compressed, blocklen, dest, m_hunkbytes);
+ if (!m_decompressor[rawmap[0]]->lossy() && dest != NULL && crc16_creator::simple(dest, m_hunkbytes) != blockcrc)
+ throw CHDERR_DECOMPRESSION_ERROR;
+ if (m_decompressor[rawmap[0]]->lossy() && crc16_creator::simple(m_compressed, blocklen) != blockcrc)
+ throw CHDERR_DECOMPRESSION_ERROR;
+ return CHDERR_NONE;
+
+ case COMPRESSION_NONE:
+ file_read(blockoffs, dest, m_hunkbytes);
+ if (crc16_creator::simple(dest, m_hunkbytes) != blockcrc)
+ throw CHDERR_DECOMPRESSION_ERROR;
+ return CHDERR_NONE;
+
+ case COMPRESSION_SELF:
+ return read_hunk(blockoffs, dest);
+
+ case COMPRESSION_PARENT:
+ if (m_parent_missing)
+ throw CHDERR_REQUIRES_PARENT;
+ return m_parent->read_bytes(UINT64(blockoffs) * UINT64(m_parent->unit_bytes()), dest, m_hunkbytes);
+ }
break;
}
+
+ // if we get here, something was wrong
+ throw CHDERR_READ_ERROR;
}
- 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)
+
+ // just return errors
+ catch (chd_error &err)
+ {
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
--------------------------------------------------*/
+//-------------------------------------------------
+// write - write a single hunk to the CHD file
+//-------------------------------------------------
-chd_error chd_compress_hunk(chd_file *chd, const void *data, double *curratio, int is_half_hunk)
+chd_error chd_file::write_hunk(UINT32 hunknum, const void *buffer)
{
- 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, is_half_hunk);
- if (err != CHDERR_NONE)
- return err;
+ // wrap this for clean reporting
+ try
+ {
+ // punt if no file
+ if (m_file == NULL)
+ throw CHDERR_NOT_OPEN;
- /* 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;
+ // return an error if out of range
+ if (hunknum >= m_hunkcount)
+ throw CHDERR_HUNK_OUT_OF_RANGE;
- /* update the MD5/SHA1 */
- bytestochecksum = chd->header.hunkbytes;
+ // if not writeable, fail
+ if (!m_allow_writes)
+ throw CHDERR_FILE_NOT_WRITEABLE;
- if (is_half_hunk)
- {
- bytestochecksum = bytestochecksum/2;
- }
+ // uncompressed writes only via this interface
+ if (compressed())
+ throw CHDERR_FILE_NOT_WRITEABLE;
+
+ // see if we have allocated the space on disk for this hunk
+ UINT8 *rawmap = m_rawmap + hunknum * 4;
+ UINT32 rawentry = be_read(rawmap, 4);
- if (sourceoffset + chd->header.hunkbytes > chd->header.logicalbytes)
- {
- if (sourceoffset >= chd->header.logicalbytes)
- bytestochecksum = 0;
+ // if not, allocate one now
+ if (rawentry == 0)
+ {
+ // append new data to the end of the file, aligning the first chunk
+ rawentry = file_append(buffer, m_hunkbytes, m_hunkbytes) / m_hunkbytes;
+
+ // write the map entry back
+ be_write(rawmap, rawentry, 4);
+ file_write(m_mapoffset + hunknum * 4, rawmap, 4);
+
+ // update the cached hunk if we just wrote it
+ if (hunknum == m_cachehunk && buffer != m_cache)
+ memcpy(m_cache, buffer, m_hunkbytes);
+ }
+
+ // otherwise, just overwrite
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);
+ file_write(UINT64(rawentry) * UINT64(m_hunkbytes), buffer, m_hunkbytes);
+ return CHDERR_NONE;
}
-
- /* 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)
+
+ // just return errors
+ catch (chd_error &err)
{
- UINT64 curlength = core_fsize(chd->file);
- *curratio = 1.0 - (double)curlength / (double)((UINT64)chd->comphunk * (UINT64)chd->header.hunkbytes);
+ return err;
}
-
- return CHDERR_NONE;
}
-/*-------------------------------------------------
- chd_compress_finish - complete compression of
- a CHD
--------------------------------------------------*/
+//-------------------------------------------------
+// read_units - read the given number of units
+// from the CHD
+//-------------------------------------------------
-chd_error chd_compress_finish(chd_file *chd, int write_protect)
+chd_error chd_file::read_units(UINT64 unitnum, void *buffer, UINT32 count)
{
- /* 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);
+ return read_bytes(unitnum * UINT64(m_unitbytes), buffer, count * m_unitbytes);
}
+//-------------------------------------------------
+// write_units - write the given number of units
+// to the CHD
+//-------------------------------------------------
-/***************************************************************************
- VERIFICATION
-***************************************************************************/
-
-/*-------------------------------------------------
- chd_verify_begin - begin compressing data
- into a CHD
--------------------------------------------------*/
-
-chd_error chd_verify_begin(chd_file *chd)
+chd_error chd_file::write_units(UINT64 unitnum, const void *buffer, UINT32 count)
{
- /* 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;
+ return write_bytes(unitnum * UINT64(m_unitbytes), buffer, count * m_unitbytes);
}
-/*-------------------------------------------------
- chd_verify_hunk - verify the next hunk in
- the CHD
--------------------------------------------------*/
+//-------------------------------------------------
+// read_bytes - read from the CHD at a byte level,
+// using the cache to handle partial hunks
+//-------------------------------------------------
-chd_error chd_verify_hunk(chd_file *chd)
+chd_error chd_file::read_bytes(UINT64 offset, void *buffer, UINT32 bytes)
{
- 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;
-
- entry = &chd->map[thishunk];
-
- /* update the MD5/SHA1 */
- if (hunkoffset < chd->header.logicalbytes)
+ // iterate over hunks
+ UINT32 first_hunk = offset / m_hunkbytes;
+ UINT32 last_hunk = (offset + bytes - 1) / m_hunkbytes;
+ UINT8 *dest = reinterpret_cast<UINT8 *>(buffer);
+ for (UINT32 curhunk = first_hunk; curhunk <= last_hunk; curhunk++)
{
- UINT64 bytestochecksum = MIN(chd->header.hunkbytes, chd->header.logicalbytes - hunkoffset);
-
- if (entry->flags & MAP_ENTRY_FLAG_HALF_HUNK)
+ // determine start/end boundaries
+ UINT32 startoffs = (curhunk == first_hunk) ? (offset % m_hunkbytes) : 0;
+ UINT32 endoffs = (curhunk == last_hunk) ? ((offset + bytes - 1) % m_hunkbytes) : (m_hunkbytes - 1);
+
+ // if it's a full block, just read directly from disk unless it's the cached hunk
+ chd_error err = CHDERR_NONE;
+ if (startoffs == 0 && endoffs == m_hunkbytes - 1 && curhunk != m_cachehunk)
+ err = read_hunk(curhunk, dest);
+
+ // otherwise, read from the cache
+ else
{
- bytestochecksum /= 2;
+ if (curhunk != m_cachehunk)
+ {
+ err = read_hunk(curhunk, m_cache);
+ if (err != CHDERR_NONE)
+ return err;
+ m_cachehunk = curhunk;
+ }
+ memcpy(dest, &m_cache[startoffs], endoffs + 1 - startoffs);
}
- if (bytestochecksum > 0)
- {
- MD5Update(&chd->vermd5, chd->cache, bytestochecksum);
- sha1_update(&chd->versha1, bytestochecksum, chd->cache);
- }
+ // handle errors and advance
+ if (err != CHDERR_NONE)
+ return err;
+ dest += endoffs + 1 - startoffs;
}
-
- /* validate the CRC if we have one */
- 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
--------------------------------------------------*/
+//-------------------------------------------------
+// write_bytes - write to the CHD at a byte level,
+// using the cache to handle partial hunks
+//-------------------------------------------------
-chd_error chd_verify_finish(chd_file *chd, chd_verify_result *result)
+chd_error chd_file::write_bytes(UINT64 offset, const void *buffer, UINT32 bytes)
{
- /* 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);
+ // iterate over hunks
+ UINT32 first_hunk = offset / m_hunkbytes;
+ UINT32 last_hunk = (offset + bytes - 1) / m_hunkbytes;
+ const UINT8 *source = reinterpret_cast<const UINT8 *>(buffer);
+ for (UINT32 curhunk = first_hunk; curhunk <= last_hunk; curhunk++)
+ {
+ // determine start/end boundaries
+ UINT32 startoffs = (curhunk == first_hunk) ? (offset % m_hunkbytes) : 0;
+ UINT32 endoffs = (curhunk == last_hunk) ? ((offset + bytes - 1) % m_hunkbytes) : (m_hunkbytes - 1);
+
+ // if it's a full block, just write directly to disk unless it's the cached hunk
+ chd_error err = CHDERR_NONE;
+ if (startoffs == 0 && endoffs == m_hunkbytes - 1 && curhunk != m_cachehunk)
+ err = write_hunk(curhunk, source);
+
+ // otherwise, write from the cache
+ else
+ {
+ if (curhunk != m_cachehunk)
+ {
+ err = read_hunk(curhunk, m_cache);
+ if (err != CHDERR_NONE)
+ return err;
+ m_cachehunk = curhunk;
+ }
+ memcpy(&m_cache[startoffs], source, endoffs + 1 - startoffs);
+ err = write_hunk(curhunk, m_cache);
+ }
- /* return an error */
- chd->verifying = FALSE;
- return (chd->verhunk < chd->header.totalhunks) ? CHDERR_VERIFY_INCOMPLETE : CHDERR_NONE;
+ // handle errors and advance
+ if (err != CHDERR_NONE)
+ return err;
+ source += endoffs + 1 - startoffs;
+ }
+ return CHDERR_NONE;
}
+//-------------------------------------------------
+// read_metadata - read the indexed metadata
+// of the given type
+//-------------------------------------------------
-/***************************************************************************
- CODEC INTERFACES
-***************************************************************************/
-
-/*-------------------------------------------------
- chd_codec_config - set internal codec
- parameters
--------------------------------------------------*/
-
-chd_error chd_codec_config(chd_file *chd, int param, void *config)
+chd_error chd_file::read_metadata(chd_metadata_tag searchtag, UINT32 searchindex, astring &output)
{
- /* 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);
+ // wrap this for clean reporting
+ try
+ {
+ // if we didn't find it, just return
+ metadata_entry metaentry;
+ if (!metadata_find(searchtag, searchindex, metaentry))
+ throw CHDERR_METADATA_NOT_FOUND;
- return CHDERR_INVALID_PARAMETER;
+ // read the metadata
+ file_read(metaentry.offset + METADATA_HEADER_SIZE, output.stringbuffer(metaentry.length), metaentry.length);
+ return CHDERR_NONE;
+ }
+
+ // just return errors
+ catch (chd_error &err)
+ {
+ return err;
+ }
}
-
-/*-------------------------------------------------
- chd_get_codec_name - get the name of a
- particular codec
--------------------------------------------------*/
-
-const char *chd_get_codec_name(UINT32 codec)
+chd_error chd_file::read_metadata(chd_metadata_tag searchtag, UINT32 searchindex, dynamic_buffer &output)
{
- 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;
+ // wrap this for clean reporting
+ try
+ {
+ // if we didn't find it, just return
+ metadata_entry metaentry;
+ if (!metadata_find(searchtag, searchindex, metaentry))
+ throw CHDERR_METADATA_NOT_FOUND;
- return "Unknown";
+ // read the metadata
+ output.resize(metaentry.length);
+ file_read(metaentry.offset + METADATA_HEADER_SIZE, output, metaentry.length);
+ return CHDERR_NONE;
+ }
+
+ // just return errors
+ catch (chd_error &err)
+ {
+ return err;
+ }
}
-
-
-/***************************************************************************
- INTERNAL ASYNC OPERATIONS
-***************************************************************************/
-
-/*-------------------------------------------------
- async_read_callback - asynchronous reading
- callback
--------------------------------------------------*/
-
-static void *async_read_callback(void *param, int threadid)
+chd_error chd_file::read_metadata(chd_metadata_tag searchtag, UINT32 searchindex, void *output, UINT32 outputlen, UINT32 &resultlen)
{
- 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);
+ // wrap this for clean reporting
+ try
+ {
+ // if we didn't find it, just return
+ metadata_entry metaentry;
+ if (!metadata_find(searchtag, searchindex, metaentry))
+ throw CHDERR_METADATA_NOT_FOUND;
- /* return the error */
- return (void *)err;
+ // read the metadata
+ resultlen = metaentry.length;
+ file_read(metaentry.offset + METADATA_HEADER_SIZE, output, MIN(outputlen, resultlen));
+ return CHDERR_NONE;
+ }
+
+ // just return errors
+ catch (chd_error &err)
+ {
+ return err;
+ }
}
-
-/*-------------------------------------------------
- async_write_callback - asynchronous writing
- callback
--------------------------------------------------*/
-
-static void *async_write_callback(void *param, int threadid)
+chd_error chd_file::read_metadata(chd_metadata_tag searchtag, UINT32 searchindex, dynamic_buffer &output, chd_metadata_tag &resulttag, UINT8 &resultflags)
{
- 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);
+ // wrap this for clean reporting
+ try
+ {
+ // if we didn't find it, just return
+ metadata_entry metaentry;
+ if (!metadata_find(searchtag, searchindex, metaentry))
+ throw CHDERR_METADATA_NOT_FOUND;
- /* return the error */
- return (void *)err;
+ // read the metadata
+ output.resize(metaentry.length);
+ file_read(metaentry.offset + METADATA_HEADER_SIZE, output, metaentry.length);
+ resulttag = metaentry.metatag;
+ resultflags = metaentry.flags;
+ return CHDERR_NONE;
+ }
+
+ // just return errors
+ catch (chd_error &err)
+ {
+ return err;
+ }
}
+//-------------------------------------------------
+// write_metadata - write the indexed metadata
+// of the given type
+//-------------------------------------------------
-/***************************************************************************
- INTERNAL HEADER OPERATIONS
-***************************************************************************/
-
-/*-------------------------------------------------
- header_validate - check the validity of a
- CHD header
--------------------------------------------------*/
-
-static chd_error header_validate(const chd_header *header)
+chd_error chd_file::write_metadata(chd_metadata_tag metatag, UINT32 metaindex, const void *inputbuf, UINT32 inputlen, UINT8 flags)
{
- 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;
+ // wrap this for clean reporting
+ try
+ {
+ // must write at least 1 byte and no more than 16MB
+ if (inputlen < 1 || inputlen >= 16 * 1024 * 1024)
+ return CHDERR_INVALID_PARAMETER;
- /* 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;
+ // find the entry if it already exists
+ metadata_entry metaentry;
+ bool finished = false;
+ if (metadata_find(metatag, metaindex, metaentry))
+ {
+ // if the new data fits over the old data, just overwrite
+ if (inputlen <= metaentry.length)
+ {
+ file_write(metaentry.offset + METADATA_HEADER_SIZE, inputbuf, inputlen);
- /* if 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;
+ // if the lengths don't match, we need to update the length in our header
+ if (inputlen != metaentry.length)
+ {
+ UINT8 length[3];
+ be_write(length, inputlen, 3);
+ file_write(metaentry.offset + 5, length, sizeof(length));
+ }
+
+ // indicate we did everything
+ finished = true;
+ }
+
+ // if it doesn't fit, unlink the current entry
+ else
+ metadata_set_previous_next(metaentry.prev, metaentry.next);
+ }
+
+ // if not yet done, create a new entry and append
+ if (!finished)
+ {
+ // now build us a new entry
+ UINT8 raw_meta_header[METADATA_HEADER_SIZE];
+ be_write(&raw_meta_header[0], metatag, 4);
+ raw_meta_header[4] = flags;
+ be_write(&raw_meta_header[5], (inputlen & 0x00ffffff) | (flags << 24), 3);
+ be_write(&raw_meta_header[8], 0, 8);
+
+ // append the new header, then the data
+ UINT64 offset = file_append(raw_meta_header, sizeof(raw_meta_header));
+ file_append(inputbuf, inputlen);
+
+ // set the previous entry to point to us
+ metadata_set_previous_next(metaentry.prev, offset);
+ }
- return CHDERR_NONE;
+ // update the hash
+ metadata_update_hash();
+ return CHDERR_NONE;
+ }
+
+ // return any errors
+ catch (chd_error &err)
+ {
+ return err;
+ }
}
-/*-------------------------------------------------
- header_read - read a CHD header into the
- internal data structure
--------------------------------------------------*/
+//-------------------------------------------------
+// delete_metadata - remove the given metadata
+// from the list
+//-------------------------------------------------
-static chd_error header_read(core_file *file, chd_header *header)
+chd_error chd_file::delete_metadata(chd_metadata_tag metatag, UINT32 metaindex)
{
- 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)
+ // wrap this for clean reporting
+ try
{
- 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;
+ // find the entry
+ metadata_entry metaentry;
+ if (!metadata_find(metatag, metaindex, metaentry))
+ throw CHDERR_METADATA_NOT_FOUND;
+
+ // point the previous to the next, unlinking us
+ metadata_set_previous_next(metaentry.prev, metaentry.next);
+ return CHDERR_NONE;
}
-
- /* extract the V3-specific data */
- else if (header->version == 3)
+
+ // return any errors
+ catch (chd_error &err)
{
- 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);
+ return err;
}
+}
- /* extract the V4-specific data */
- else
+
+//-------------------------------------------------
+// clone_all_metadata - clone the metadata from
+// one CHD to a second
+//-------------------------------------------------
+
+chd_error chd_file::clone_all_metadata(chd_file &source)
+{
+ // wrap this for clean reporting
+ try
{
- 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);
+ // iterate over metadata entries in the source
+ dynamic_buffer filedata;
+ metadata_entry metaentry;
+ for (bool has_data = source.metadata_find(CHDMETATAG_WILDCARD, 0, metaentry); has_data; has_data = source.metadata_find(CHDMETATAG_WILDCARD, 0, metaentry, true))
+ {
+ // read the metadata item
+ filedata.resize(metaentry.length);
+ source.file_read(metaentry.offset + METADATA_HEADER_SIZE, filedata, metaentry.length);
+
+ // write it to the destination
+ chd_error err = write_metadata(metaentry.metatag, -1, filedata, metaentry.length, metaentry.flags);
+ if (err != CHDERR_NONE)
+ throw err;
+ }
+ return CHDERR_NONE;
+ }
+
+ // return any errors
+ catch (chd_error &err)
+ {
+ return err;
}
-
- /* guess it worked */
- return CHDERR_NONE;
}
-/*-------------------------------------------------
- header_write - write a CHD header from the
- internal data structure
--------------------------------------------------*/
+//-------------------------------------------------
+// compute_overall_sha1 - iterate through the
+// metadata and compute the overall hash of the
+// CHD file
+//-------------------------------------------------
-static chd_error header_write(core_file *file, const chd_header *header)
+sha1_t chd_file::compute_overall_sha1(sha1_t rawsha1)
{
- UINT8 rawheader[CHD_MAX_HEADER_SIZE];
- UINT32 count;
+ // only works for v4 and above
+ if (m_version < 4)
+ return rawsha1;
- /* punt if NULL */
- if (header == NULL)
- return CHDERR_INVALID_PARAMETER;
+ // iterate over metadata
+ dynamic_buffer filedata;
+ dynamic_array<metadata_hash> hasharray;
+ metadata_entry metaentry;
+ for (bool has_data = metadata_find(CHDMETATAG_WILDCARD, 0, metaentry); has_data; has_data = metadata_find(CHDMETATAG_WILDCARD, 0, metaentry, true))
+ {
+ // if not checksumming, continue
+ if ((metaentry.flags & CHD_MDFLAGS_CHECKSUM) == 0)
+ continue;
- /* punt if invalid file */
- if (file == NULL)
- return CHDERR_INVALID_FILE;
+ // allocate memory and read the data
+ filedata.resize(metaentry.length);
+ file_read(metaentry.offset + METADATA_HEADER_SIZE, filedata, metaentry.length);
- /* only support writing modern headers */
- if (header->version != 4)
- return CHDERR_INVALID_PARAMETER;
+ // create an entry for this metadata and add it
+ metadata_hash hashentry;
+ be_write(hashentry.tag, metaentry.metatag, 4);
+ hashentry.sha1 = sha1_creator::simple(filedata, metaentry.length);
+ hasharray.append(hashentry);
+ }
- /* 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;
+ // sort the array
+ if (hasharray.count() != 0)
+ qsort(&hasharray[0], hasharray.count(), sizeof(hasharray[0]), metadata_hash_compare);
- return CHDERR_NONE;
+ // read the raw data hash from our header and start a new SHA1 with that data
+ sha1_creator overall_sha1;
+ overall_sha1.append(&rawsha1, sizeof(rawsha1));
+ if (hasharray.count() != 0)
+ overall_sha1.append(&hasharray[0], hasharray.count() * sizeof(hasharray[0]));
+ return overall_sha1.finish();
}
+//-------------------------------------------------
+// codec_config - set internal codec parameters
+//-------------------------------------------------
-/***************************************************************************
- 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 chd_file::codec_configure(chd_codec_type codec, int param, void *config)
{
- 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)
+ // wrap this for clean reporting
+ try
+ {
+ // find the codec and call its configuration
+ for (int codecnum = 0; codecnum < ARRAY_LENGTH(m_compression); codecnum++)
+ if (m_compression[codecnum] == codec)
+ {
+ m_decompressor[codecnum]->configure(param, config);
+ return CHDERR_NONE;
+ }
+ return CHDERR_INVALID_PARAMETER;
+ }
+
+ // return any errors
+ catch (chd_error &err)
+ {
return err;
-
- /* 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
--------------------------------------------------*/
+//-------------------------------------------------
+// error_string - return an error string for
+// the given CHD error
+//-------------------------------------------------
-static chd_error hunk_read_into_memory(chd_file *chd, UINT32 hunknum, UINT8 *dest)
+const char *chd_file::error_string(chd_error err)
{
- 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)
+ switch (err)
{
- /* 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;
-
- case MAP_ENTRY_TYPE_2ND_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->secondary_decompress != NULL)
- err = (*chd->codecintf->secondary_decompress)(chd, entry->length, dest);
- if (err != CHDERR_NONE)
- return err;
- break;
-
+ 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_UNSUPPORTED_FORMAT: return "unsupported format";
+ default: return "undocumented error";
}
- 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, int is_half_hunk)
-{
- 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;
- bool is_likely_cd = false;
- int strategy = 0;
- /* track the max */
- if (hunknum > chd->maxhunk)
- chd->maxhunk = hunknum;
-
- /* first compute the CRC of the original data */
- newentry.flags = 0;
- newentry.length = 0;
- newentry.offset = 0;
- 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)
+//**************************************************************************
+// INTERNAL HELPERS
+//**************************************************************************
+
+//-------------------------------------------------
+// guess_unitbytes - for older CHD formats, take
+// a guess at the bytes/unit based on metadata
+//-------------------------------------------------
+
+UINT32 chd_file::guess_unitbytes()
+{
+ // look for hard disk metadata; if found, then the unit size == sector size
+ astring metadata;
+ int i0, i1, i2, i3;
+ if (read_metadata(HARD_DISK_METADATA_TAG, 0, metadata) == CHDERR_NONE && sscanf(metadata, HARD_DISK_METADATA_FORMAT, &i0, &i1, &i2, &i3) == 4)
+ return i3;
+
+ // look for CD-ROM metadata; if found, then the unit size == CD frame size
+ if (read_metadata(CDROM_OLD_METADATA_TAG, 0, metadata) == CHDERR_NONE ||
+ read_metadata(CDROM_TRACK_METADATA_TAG, 0, metadata) == CHDERR_NONE ||
+ read_metadata(CDROM_TRACK_METADATA2_TAG, 0, metadata) == CHDERR_NONE)
+ return CD_FRAME_SIZE;
+
+ // otherwise, just map 1:1 with the hunk size
+ return m_hunkbytes;
+}
+
+
+//-------------------------------------------------
+// parse_v3_header - parse the header from a v3
+// file and configure core parameters
+//-------------------------------------------------
+
+void chd_file::parse_v3_header(UINT8 *rawheader, sha1_t &parentsha1)
+{
+ // verify header length
+ if (be_read(&rawheader[8], 4) != V3_HEADER_SIZE)
+ throw CHDERR_INVALID_FILE;
+
+ // extract core info
+ m_logicalbytes = be_read(&rawheader[28], 8);
+ m_mapoffset = 120;
+ m_metaoffset = be_read(&rawheader[36], 8);
+ m_hunkbytes = be_read(&rawheader[76], 4);
+ m_hunkcount = be_read(&rawheader[24], 4);
+
+ // extract parent SHA-1
+ UINT32 flags = be_read(&rawheader[16], 4);
+ if ((flags & 2) && m_allow_writes)
+ throw CHDERR_FILE_NOT_WRITEABLE;
+
+ // determine compression
+ switch (be_read(&rawheader[20], 4))
+ {
+ case 0: m_compression[0] = CHD_CODEC_NONE; break;
+ case 1: m_compression[0] = CHD_CODEC_ZLIB; break;
+ case 2: m_compression[0] = CHD_CODEC_ZLIB; break;
+ case 3: m_compression[0] = CHD_CODEC_AVHUFF; break;
+ default: throw CHDERR_UNKNOWN_COMPRESSION;
+ }
+ m_compression[1] = m_compression[2] = m_compression[3] = CHD_CODEC_NONE;
+
+ // describe the format
+ m_mapoffset_offset = 0;
+ m_metaoffset_offset = 36;
+ m_sha1_offset = 80;
+ m_rawsha1_offset = 0;
+ m_parentsha1_offset = 100;
+
+ // determine properties of map entries
+ m_mapentrybytes = 16;
+
+ // extract parent SHA-1
+ if (flags & 1)
+ parentsha1 = be_read_sha1(&rawheader[m_parentsha1_offset]);
+
+ // guess at the units based on snooping the metadata
+ m_unitbytes = guess_unitbytes();
+ m_unitcount = (m_logicalbytes + m_unitbytes - 1) / m_unitbytes;
+}
+
+
+//-------------------------------------------------
+// parse_v4_header - parse the header from a v4
+// file and configure core parameters
+//-------------------------------------------------
+
+void chd_file::parse_v4_header(UINT8 *rawheader, sha1_t &parentsha1)
+{
+ // verify header length
+ if (be_read(&rawheader[8], 4) != V4_HEADER_SIZE)
+ throw CHDERR_INVALID_FILE;
+
+ // extract core info
+ m_logicalbytes = be_read(&rawheader[28], 8);
+ m_mapoffset = 108;
+ m_metaoffset = be_read(&rawheader[36], 8);
+ m_hunkbytes = be_read(&rawheader[44], 4);
+ m_hunkcount = be_read(&rawheader[24], 4);
+
+ // extract parent SHA-1
+ UINT32 flags = be_read(&rawheader[16], 4);
+ if ((flags & 2) && m_allow_writes)
+ throw CHDERR_FILE_NOT_WRITEABLE;
+
+ // determine compression
+ switch (be_read(&rawheader[20], 4))
+ {
+ case 0: m_compression[0] = CHD_CODEC_NONE; break;
+ case 1: m_compression[0] = CHD_CODEC_ZLIB; break;
+ case 2: m_compression[0] = CHD_CODEC_ZLIB; break;
+ case 3: m_compression[0] = CHD_CODEC_AVHUFF; break;
+ default: throw CHDERR_UNKNOWN_COMPRESSION;
+ }
+ m_compression[1] = m_compression[2] = m_compression[3] = CHD_CODEC_NONE;
+
+ // describe the format
+ m_mapoffset_offset = 0;
+ m_metaoffset_offset = 36;
+ m_sha1_offset = 48;
+ m_rawsha1_offset = 88;
+ m_parentsha1_offset = 68;
+
+ // determine properties of map entries
+ m_mapentrybytes = 16;
+
+ // extract parent SHA-1
+ if (flags & 1)
+ parentsha1 = be_read_sha1(&rawheader[m_parentsha1_offset]);
+
+ // guess at the units based on snooping the metadata
+ m_unitbytes = guess_unitbytes();
+ m_unitcount = (m_logicalbytes + m_unitbytes - 1) / m_unitbytes;
+}
+
+
+//-------------------------------------------------
+// parse_v5_header - read the header from a v5
+// file and configure core parameters
+//-------------------------------------------------
+
+void chd_file::parse_v5_header(UINT8 *rawheader, sha1_t &parentsha1)
+{
+ // verify header length
+ if (be_read(&rawheader[8], 4) != V5_HEADER_SIZE)
+ throw CHDERR_INVALID_FILE;
+
+ // extract core info
+ m_logicalbytes = be_read(&rawheader[32], 8);
+ m_mapoffset = be_read(&rawheader[40], 8);
+ m_metaoffset = be_read(&rawheader[48], 8);
+ m_hunkbytes = be_read(&rawheader[56], 4);
+ m_hunkcount = (m_logicalbytes + m_hunkbytes - 1) / m_hunkbytes;
+ m_unitbytes = be_read(&rawheader[60], 4);
+ m_unitcount = (m_logicalbytes + m_unitbytes - 1) / m_unitbytes;
+
+ // determine compression
+ m_compression[0] = be_read(&rawheader[16], 4);
+ m_compression[1] = be_read(&rawheader[20], 4);
+ m_compression[2] = be_read(&rawheader[24], 4);
+ m_compression[3] = be_read(&rawheader[28], 4);
+
+ // describe the format
+ m_mapoffset_offset = 40;
+ m_metaoffset_offset = 48;
+ m_sha1_offset = 84;
+ m_rawsha1_offset = 64;
+ m_parentsha1_offset = 104;
+
+ // determine properties of map entries
+ m_mapentrybytes = compressed() ? 12 : 4;
+
+ // extract parent SHA-1
+ parentsha1 = be_read_sha1(&rawheader[m_parentsha1_offset]);
+}
+
+
+//-------------------------------------------------
+// compress_v5_map - compress the v5 map and
+// write it to the end of the file
+//-------------------------------------------------
+
+chd_error chd_file::compress_v5_map()
+{
+ try
+ {
+ // first get a CRC-16 of the original rawmap
+ crc16_t mapcrc = crc16_creator::simple(m_rawmap, m_hunkcount * 12);
+
+ // create a buffer to hold the RLE data
+ dynamic_buffer compression_rle(m_hunkcount);
+ UINT8 *dest = compression_rle;
+
+ // use a huffman encoder for 16 different codes, maximum length is 8 bits
+ huffman_encoder<16, 8> encoder;
+ encoder.histo_reset();
+
+ // RLE-compress the compression type since we expect runs of the same
+ UINT32 max_self = 0;
+ UINT32 last_self = 0;
+ UINT64 max_parent = 0;
+ UINT64 last_parent = 0;
+ UINT32 max_complen = 0;
+ UINT8 lastcomp = 0;
+ int count = 0;
+ for (int hunknum = 0; hunknum < m_hunkcount; hunknum++)
{
- /* 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)
+ UINT8 curcomp = m_rawmap[hunknum * 12 + 0];
+
+ // promote self block references to more compact forms
+ if (curcomp == COMPRESSION_SELF)
{
- newentry.offset = get_bigendian_uint64(&src[0]);
- newentry.length = 0;
- newentry.flags = MAP_ENTRY_TYPE_MINI;
- goto write_entry;
+ UINT32 refhunk = be_read(&m_rawmap[hunknum * 12 + 4], 6);
+ if (refhunk == last_self)
+ curcomp = COMPRESSION_SELF_0;
+ else if (refhunk == last_self + 1)
+ curcomp = COMPRESSION_SELF_1;
+ else
+ max_self = MAX(max_self, refhunk);
+ last_self = refhunk;
}
- /* 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)
+ // promote parent block references to more compact forms
+ else if (curcomp == COMPRESSION_PARENT)
{
- newentry.offset = match;
- newentry.length = 0;
- newentry.flags = MAP_ENTRY_TYPE_SELF_HUNK;
- goto write_entry;
+ UINT32 refunit = be_read(&m_rawmap[hunknum * 12 + 4], 6);
+ if (refunit == (UINT64(hunknum) * UINT64(m_hunkbytes)) / m_unitbytes)
+ curcomp = COMPRESSION_PARENT_SELF;
+ else if (refunit == last_parent)
+ curcomp = COMPRESSION_PARENT_0;
+ else if (refunit == last_parent + m_hunkbytes / m_unitbytes)
+ curcomp = COMPRESSION_PARENT_1;
+ else
+ max_parent = MAX(max_parent, refunit);
+ last_parent = refunit;
}
-
- /* if we have a parent, see if we can find a match in there */
- if (chd->header.flags & CHDFLAGS_HAS_PARENT)
+
+ // track maximum compressed length
+ else if (curcomp >= COMPRESSION_TYPE_0 && curcomp <= COMPRESSION_TYPE_3)
+ max_complen = MAX(max_complen, be_read(&m_rawmap[hunknum * 12 + 1], 3));
+
+ // track repeats
+ if (curcomp == lastcomp)
+ count++;
+
+ // if no repeat, or we're at the end, flush it
+ if (curcomp != lastcomp || hunknum == m_hunkcount - 1)
{
- match = crcmap_find_hunk(chd->parent, ~0, newentry.crc, &src[0]);
- if (match != NO_MATCH)
+ while (count != 0)
{
- newentry.offset = match;
- newentry.length = 0;
- newentry.flags = MAP_ENTRY_TYPE_PARENT_HUNK;
- goto write_entry;
+ if (count < 3)
+ encoder.histo_one(*dest++ = lastcomp), count--;
+ else if (count <= 3+15)
+ {
+ encoder.histo_one(*dest++ = COMPRESSION_RLE_SMALL);
+ encoder.histo_one(*dest++ = count - 3);
+ count = 0;
+ }
+ else
+ {
+ int this_count = MIN(count, 3+16+255);
+ encoder.histo_one(*dest++ = COMPRESSION_RLE_LARGE);
+ encoder.histo_one(*dest++ = (this_count - 3 - 16) >> 4);
+ encoder.histo_one(*dest++ = (this_count - 3 - 16) & 15);
+ count -= this_count;
+ }
}
+ if (curcomp != lastcomp)
+ encoder.histo_one(*dest++ = lastcomp = curcomp);
}
}
- }
-
- if (chd->codecintf->secondary_compress != NULL)
- {
- if (chd->header.hunkbytes == (CD_MAX_SECTOR_DATA+CD_MAX_SUBCODE_DATA) * CD_FRAMES_PER_HUNK)
- is_likely_cd = true;
-
- if (is_likely_cd)
+
+ // compute a tree and export it to the buffer
+ dynamic_buffer compressed(m_hunkcount * 6);
+ bitstream_out bitbuf(&compressed[16], compressed.count() - 16);
+ huffman_error err = encoder.compute_tree_from_histo();
+ if (err != HUFFERR_NONE)
+ throw CHDERR_COMPRESSION_ERROR;
+ err = encoder.export_tree_rle(bitbuf);
+ if (err != HUFFERR_NONE)
+ throw CHDERR_COMPRESSION_ERROR;
+
+ // encode the data
+ for (UINT8 *src = compression_rle; src < dest; src++)
+ encoder.encode_one(bitbuf, *src);
+
+ // determine the number of bits we need to hold the a length
+ // and a hunk index
+ UINT8 lengthbits = bits_for_value(max_complen);
+ UINT8 selfbits = bits_for_value(max_self);
+ UINT8 parentbits = bits_for_value(max_parent);
+
+ // for each compression type, output the relevant data
+ lastcomp = 0;
+ count = 0;
+ UINT8 *src = compression_rle;
+ UINT64 firstoffs = 0;
+ for (int hunknum = 0; hunknum < m_hunkcount; hunknum++)
{
- int offset = 0;
- for (int frames=0;frames<CD_FRAMES_PER_HUNK;frames++)
+ UINT8 *rawmap = &m_rawmap[hunknum * 12];
+ UINT32 length = be_read(&rawmap[1], 3);
+ UINT64 offset = be_read(&rawmap[4], 6);
+ UINT16 crc = be_read(&rawmap[10], 2);
+
+ // if no count remaining, fetch the next entry
+ if (count == 0)
{
- int secoff;
- for (secoff=0;secoff<CD_MAX_SECTOR_DATA;secoff++)
- {
- offset++;
- }
- for (secoff=0;secoff<CD_MAX_SUBCODE_DATA;secoff++)
- {
- if (src[offset]!=0x00)
- is_likely_cd = false;
-
- offset++;
- }
+ UINT8 val = *src++;
+ if (val == COMPRESSION_RLE_SMALL)
+ count = 2 + *src++;
+ else if (val == COMPRESSION_RLE_LARGE)
+ count = 2 + 16 + (*src++ << 4), count += *src++;
+ else
+ lastcomp = val;
}
- }
- }
-
- if (is_likely_cd)
- {
- err = CHDERR_COMPRESSION_ERROR;
-
- UINT8* tempram = (UINT8 *)malloc(chd->header.hunkbytes);
- UINT32 tempbytes = 0;
- chd_error temperror = err;
- int tempstrategy = 0;
-
- /* try strategy 0 - zlib */
- if (chd->codecintf->compress != NULL)
- {
- err = (*chd->codecintf->compress)(chd, src, &bytes);
-
- /* store current results and errors */
- memcpy(tempram, chd->compressed, bytes);
- tempbytes = bytes;
- temperror = err;
- tempstrategy = 0;
- }
-
- /* try strategy 1 - flac */
- if (chd->codecintf->secondary_compress != NULL)
- {
- strategy = 1;
-
- err = (*chd->codecintf->secondary_compress)(chd, src, &bytes);
-
- /* check against previous compression attempt if that was successful */
- if (temperror == CHDERR_NONE)
+ else
+ count--;
+
+ // output additional data needed for this entry
+ switch (lastcomp)
{
- /* if the previous compression was better, restore that ... */
- if (bytes>=tempbytes)
- {
- strategy = tempstrategy;
- memcpy(chd->compressed, tempram, tempbytes);
- bytes = tempbytes;
- err = temperror;
- }
+ case COMPRESSION_TYPE_0:
+ case COMPRESSION_TYPE_1:
+ case COMPRESSION_TYPE_2:
+ case COMPRESSION_TYPE_3:
+ assert(length < (1 << lengthbits));
+ bitbuf.write(length, lengthbits);
+ bitbuf.write(crc, 16);
+ if (firstoffs == 0)
+ firstoffs = offset;
+ break;
+
+ case COMPRESSION_NONE:
+ bitbuf.write(crc, 16);
+ if (firstoffs == 0)
+ firstoffs = offset;
+ break;
+
+ case COMPRESSION_SELF:
+ assert(offset < (UINT64(1) << selfbits));
+ bitbuf.write(offset, selfbits);
+ break;
+
+ case COMPRESSION_PARENT:
+ assert(offset < (UINT64(1) << parentbits));
+ bitbuf.write(offset, parentbits);
+ break;
+
+ case COMPRESSION_SELF_0:
+ case COMPRESSION_SELF_1:
+ case COMPRESSION_PARENT_SELF:
+ case COMPRESSION_PARENT_0:
+ case COMPRESSION_PARENT_1:
+ break;
}
}
-
- free(tempram);
+
+ // write the map header
+ UINT32 complen = bitbuf.flush();
+ assert(!bitbuf.overflow());
+ be_write(&compressed[0], complen, 4);
+ be_write(&compressed[4], firstoffs, 6);
+ be_write(&compressed[10], mapcrc, 2);
+ compressed[12] = lengthbits;
+ compressed[13] = selfbits;
+ compressed[14] = parentbits;
+
+ // write the result
+ m_mapoffset = file_append(compressed, complen + 16);
+
+ // then write the map offset
+ UINT8 rawbuf[sizeof(UINT64)];
+ be_write(rawbuf, m_mapoffset, 8);
+ file_write(m_mapoffset_offset, rawbuf, sizeof(rawbuf));
+ return CHDERR_NONE;
}
- else
+ catch (chd_error &err)
{
- /* now try compressing the data */
- err = CHDERR_COMPRESSION_ERROR;
- if (chd->codecintf->compress != NULL)
- err = (*chd->codecintf->compress)(chd, src, &bytes);
+ return err;
}
+}
- /* 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)
+//-------------------------------------------------
+// decompress_v5_map - decompress the v5 map
+//-------------------------------------------------
+
+void chd_file::decompress_v5_map()
+{
+ // if no offset, we haven't written it yet
+ if (m_mapoffset == 0)
{
- data = chd->compressed;
- newentry.length = bytes;
+ memset(m_rawmap, 0xff, m_rawmap.count());
+ return;
+ }
- if (strategy == 0)
+ // read the reader
+ UINT8 rawbuf[16];
+ file_read(m_mapoffset, rawbuf, sizeof(rawbuf));
+ UINT32 mapbytes = be_read(&rawbuf[0], 4);
+ UINT64 firstoffs = be_read(&rawbuf[4], 6);
+ UINT16 mapcrc = be_read(&rawbuf[10], 2);
+ UINT8 lengthbits = rawbuf[12];
+ UINT8 selfbits = rawbuf[13];
+ UINT8 parentbits = rawbuf[14];
+
+ // now read the map
+ dynamic_buffer compressed(mapbytes);
+ file_read(m_mapoffset + 16, compressed, mapbytes);
+ bitstream_in bitbuf(compressed, compressed.count());
+
+ // first decode the compression types
+ huffman_decoder<16, 8> decoder;
+ huffman_error err = decoder.import_tree_rle(bitbuf);
+ if (err != HUFFERR_NONE)
+ throw CHDERR_DECOMPRESSION_ERROR;
+ UINT8 lastcomp = 0;
+ int repcount = 0;
+ for (int hunknum = 0; hunknum < m_hunkcount; hunknum++)
+ {
+ UINT8 *rawmap = &m_rawmap[hunknum * 12];
+ if (repcount > 0)
+ rawmap[0] = lastcomp, repcount--;
+ else
{
- newentry.flags = MAP_ENTRY_TYPE_COMPRESSED;
+ UINT8 val = decoder.decode_one(bitbuf);
+ if (val == COMPRESSION_RLE_SMALL)
+ rawmap[0] = lastcomp, repcount = 2 + decoder.decode_one(bitbuf);
+ else if (val == COMPRESSION_RLE_LARGE)
+ rawmap[0] = lastcomp, repcount = 2 + 16 + (decoder.decode_one(bitbuf) << 4), repcount += decoder.decode_one(bitbuf);
+ else
+ rawmap[0] = lastcomp = val;
}
- else if (strategy == 1)
+ }
+
+ // then iterate through the hunks and extract the needed data
+ UINT64 curoffset = firstoffs;
+ UINT32 last_self = 0;
+ UINT64 last_parent = 0;
+ for (int hunknum = 0; hunknum < m_hunkcount; hunknum++)
+ {
+ UINT8 *rawmap = &m_rawmap[hunknum * 12];
+ UINT64 offset = curoffset;
+ UINT32 length = 0;
+ UINT16 crc = 0;
+ switch (rawmap[0])
{
- newentry.flags = MAP_ENTRY_TYPE_2ND_COMPRESSED;
+ // base types
+ case COMPRESSION_TYPE_0:
+ case COMPRESSION_TYPE_1:
+ case COMPRESSION_TYPE_2:
+ case COMPRESSION_TYPE_3:
+ curoffset += length = bitbuf.read(lengthbits);
+ crc = bitbuf.read(16);
+ break;
+
+ case COMPRESSION_NONE:
+ curoffset += length = m_hunkbytes;
+ crc = bitbuf.read(16);
+ break;
+
+ case COMPRESSION_SELF:
+ last_self = offset = bitbuf.read(selfbits);
+ break;
+
+ case COMPRESSION_PARENT:
+ offset = bitbuf.read(parentbits);
+ last_parent = offset;
+ break;
+
+ // pseudo-types; convert into base types
+ case COMPRESSION_SELF_1:
+ last_self++;
+ case COMPRESSION_SELF_0:
+ rawmap[0] = COMPRESSION_SELF;
+ offset = last_self;
+ break;
+
+ case COMPRESSION_PARENT_SELF:
+ rawmap[0] = COMPRESSION_PARENT;
+ last_parent = offset = (UINT64(hunknum) * UINT64(m_hunkbytes)) / m_unitbytes;
+ break;
+
+ case COMPRESSION_PARENT_1:
+ last_parent += m_hunkbytes / m_unitbytes;
+ case COMPRESSION_PARENT_0:
+ rawmap[0] = COMPRESSION_PARENT;
+ offset = last_parent;
+ break;
+ }
+ be_write(&rawmap[1], length, 3);
+ be_write(&rawmap[4], offset, 6);
+ be_write(&rawmap[10], crc, 2);
+ }
+
+ // verify the final CRC
+ if (crc16_creator::simple(m_rawmap, m_hunkcount * 12) != mapcrc)
+ throw CHDERR_DECOMPRESSION_ERROR;
+}
+
+
+//-------------------------------------------------
+// create_common - command path when creating a
+// new CHD file
+//-------------------------------------------------
+
+chd_error chd_file::create_common()
+{
+ // wrap in try for proper error handling
+ try
+ {
+ // if we have a parent, it must be V3 or later
+ if (m_parent != NULL && m_parent->version() < 3)
+ throw CHDERR_UNSUPPORTED_VERSION;
+
+ // must be an even number of units per hunk
+ if (m_hunkbytes % m_unitbytes != 0)
+ throw CHDERR_INVALID_PARAMETER;
+ if (m_parent != NULL && m_unitbytes != m_parent->unit_bytes())
+ throw CHDERR_INVALID_PARAMETER;
+
+ // writes are obviously permitted; reads only if uncompressed
+ m_allow_writes = true;
+ m_allow_reads = !compressed();
+
+ // verify the compression types
+ bool found_zero = false;
+ for (int codecnum = 0; codecnum < ARRAY_LENGTH(m_compression); codecnum++)
+ {
+ // once we hit an empty slot, all later slots must be empty as well
+ if (m_compression[codecnum] == CHD_CODEC_NONE)
+ found_zero = true;
+ else if (found_zero)
+ throw CHDERR_INVALID_PARAMETER;
+ else if (!chd_codec_list::codec_exists(m_compression[codecnum]))
+ throw CHDERR_UNKNOWN_COMPRESSION;
}
- }
-
- /* 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:
-
- if (is_half_hunk)
- newentry.flags |= MAP_ENTRY_FLAG_HALF_HUNK;
-
- *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++)
+ // create our V5 header
+ assert(m_version == HEADER_VERSION);
+ UINT8 rawheader[V5_HEADER_SIZE];
+ memcpy(&rawheader[0], "MComprHD", 8);
+ be_write(&rawheader[8], V5_HEADER_SIZE, 4);
+ be_write(&rawheader[12], m_version, 4);
+ be_write(&rawheader[16], m_compression[0], 4);
+ be_write(&rawheader[20], m_compression[1], 4);
+ be_write(&rawheader[24], m_compression[2], 4);
+ be_write(&rawheader[28], m_compression[3], 4);
+ be_write(&rawheader[32], m_logicalbytes, 8);
+ be_write(&rawheader[40], compressed() ? 0 : V5_HEADER_SIZE, 8);
+ be_write(&rawheader[48], m_metaoffset, 8);
+ be_write(&rawheader[56], m_hunkbytes, 4);
+ be_write(&rawheader[60], m_unitbytes, 4);
+ be_write_sha1(&rawheader[64], sha1_t::null);
+ be_write_sha1(&rawheader[84], sha1_t::null);
+ be_write_sha1(&rawheader[104], (m_parent != NULL) ? m_parent->sha1() : sha1_t::null);
+
+ // write the resulting header
+ file_write(0, rawheader, sizeof(rawheader));
+
+ // parse it back out to set up fields appropriately
+ sha1_t parentsha1;
+ parse_v5_header(rawheader, parentsha1);
+
+ // write out the map (if not compressed)
+ if (!compressed())
+ {
+ UINT32 mapsize = m_mapentrybytes * m_hunkcount;
+ UINT8 buffer[4096] = { 0 };
+ UINT64 offset = m_mapoffset;
+ while (mapsize != 0)
{
- 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);
+ UINT32 bytes_to_write = MIN(mapsize, sizeof(buffer));
+ file_write(offset, buffer, bytes_to_write);
+ offset += bytes_to_write;
+ mapsize -= bytes_to_write;
}
+ }
- /* 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);
+ // finish opening the file
+ create_open_common();
}
-
- /* then write the remainder */
- if (remainder > 0)
+
+ // handle errors by closing ourself
+ catch (chd_error &err)
{
- /* 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;
+ close();
+ return err;
+ }
+ catch (...)
+ {
+ close();
+ throw;
}
-
- /* 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)
+//-------------------------------------------------
+// open_common - common path when opening an
+// existing CHD file for input
+//-------------------------------------------------
+
+chd_error chd_file::open_common(bool writeable)
+{
+ // wrap in try for proper error handling
+ try
+ {
+ // reads are always permitted; writes possibly as well
+ m_allow_reads = true;
+ m_allow_writes = writeable;
+
+ // read the raw header
+ UINT8 rawheader[MAX_HEADER_SIZE];
+ file_read(0, rawheader, sizeof(rawheader));
+
+ // verify the signature
+ if (memcmp(rawheader, "MComprHD", 8) != 0)
+ throw CHDERR_INVALID_FILE;
+
+ // only allow writes to the most recent version
+ m_version = be_read(&rawheader[12], 4);
+ if (m_allow_writes && m_version < HEADER_VERSION)
+ throw CHDERR_UNSUPPORTED_VERSION;
+
+ // read the header if we support it
+ sha1_t parentsha1 = sha1_t::null;
+ switch (m_version)
{
- for (j = 0; j < entries; j++)
- map_extract(&raw_map_entries[j * MAP_ENTRY_SIZE], &chd->map[i + j]);
+ case 3: parse_v3_header(rawheader, parentsha1); break;
+ case 4: parse_v4_header(rawheader, parentsha1); break;
+ case 5: parse_v5_header(rawheader, parentsha1); break;
+ default: throw CHDERR_UNSUPPORTED_VERSION;
}
- else
+
+ // make sure we have a parent if we need one (and don't if we don't)
+ if (parentsha1 != sha1_t::null)
{
- for (j = 0; j < entries; j++)
- map_extract_old(&raw_map_entries[j * OLD_MAP_ENTRY_SIZE], &chd->map[i + j], chd->header.hunkbytes);
+ if (m_parent == NULL)
+ m_parent_missing = true;
+ else if (m_parent->sha1() != parentsha1)
+ throw CHDERR_INVALID_PARENT;
}
-
- /* 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_2ND_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);
+ else if (parentsha1 == sha1_t::null && m_parent != NULL)
+ throw CHDERR_INVALID_PARAMETER;
+
+ // finish opening the file
+ create_open_common();
+ return CHDERR_NONE;
}
-
- /* 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))
+
+ // handle errors by closing ourself
+ catch (chd_error &err)
{
- err = CHDERR_INVALID_FILE;
- goto cleanup;
- }
-
- /* verify the length */
- if (maxoffset > core_fsize(chd->file))
- {
- err = CHDERR_INVALID_FILE;
- goto cleanup;
+ close();
+ return err;
}
- return CHDERR_NONE;
-
-cleanup:
- if (chd->map)
- free(chd->map);
- chd->map = NULL;
- return err;
}
+//-------------------------------------------------
+// create_open_common - common code for handling
+// creation and opening of a file
+//-------------------------------------------------
-/***************************************************************************
- INTERNAL CRC MAP ACCESS
-***************************************************************************/
-
-/*-------------------------------------------------
- crcmap_init - initialize the CRC map
--------------------------------------------------*/
-
-static void crcmap_init(chd_file *chd, int prepopulate)
+void chd_file::create_open_common()
{
- 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++)
+ // verify the compression types and initialize the codecs
+ for (int decompnum = 0; decompnum < ARRAY_LENGTH(m_compression); decompnum++)
{
- chd->crcmap[i].next = chd->crcfree;
- chd->crcfree = &chd->crcmap[i];
+ m_decompressor[decompnum] = chd_codec_list::new_decompressor(m_compression[decompnum], *this);
+ if (m_decompressor[decompnum] == NULL && m_compression[decompnum] != 0)
+ throw CHDERR_UNKNOWN_COMPRESSION;
}
+
+ // read the map; v5+ compressed drives need to read and decompress their map
+ m_rawmap.resize(m_hunkcount * m_mapentrybytes);
+ if (m_version >= 5 && compressed())
+ decompress_v5_map();
+ else
+ file_read(m_mapoffset, m_rawmap, m_rawmap.count());
- /* 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);
+ // allocate the temporary compressed buffer and a buffer for caching
+ m_compressed.resize(m_hunkbytes);
+ m_cache.resize(m_hunkbytes);
}
-/*-------------------------------------------------
- crcmap_add_entry - log a CRC entry
--------------------------------------------------*/
+//-------------------------------------------------
+// verify_proper_compression_append - verify that
+// the given hunk is a proper candidate for
+// appending to a compressed CHD
+//-------------------------------------------------
-static void crcmap_add_entry(chd_file *chd, UINT32 hunknum)
+void chd_file::verify_proper_compression_append(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;
+ // punt if no file
+ if (m_file == NULL)
+ throw CHDERR_NOT_OPEN;
- /* set up the entry and link it into the hash table */
- crcmap->hunknum = hunknum;
- crcmap->next = chd->crctable[hash];
- chd->crctable[hash] = crcmap;
-}
+ // return an error if out of range
+ if (hunknum >= m_hunkcount)
+ throw CHDERR_HUNK_OUT_OF_RANGE;
+ // if not writeable, fail
+ if (!m_allow_writes)
+ throw CHDERR_FILE_NOT_WRITEABLE;
-/*-------------------------------------------------
- crcmap_verify_hunk_match - verify that a
- hunk really matches by doing a byte-for-byte
- compare
--------------------------------------------------*/
+ // compressed writes only via this interface
+ if (!compressed())
+ throw CHDERR_FILE_NOT_WRITEABLE;
-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);
+ // only permitted to write new blocks
+ UINT8 *rawmap = &m_rawmap[hunknum * 12];
+ if (rawmap[0] != 0xff)
+ throw CHDERR_COMPRESSION_ERROR;
+
+ // if this isn't the first block, only permitted to write immediately
+ // after the previous one
+ if (hunknum != 0 && rawmap[-12] == 0xff)
+ throw CHDERR_COMPRESSION_ERROR;
}
+
+//-------------------------------------------------
+// hunk_write_compressed - write a hunk to a
+// compressed CHD, discovering the best
+// technique
+//-------------------------------------------------
-/*-------------------------------------------------
- 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)
+void chd_file::hunk_write_compressed(UINT32 hunknum, INT8 compression, const UINT8 *compressed, UINT32 complength, crc16_t crc16)
{
- 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) && !(chd->map[curhunk].flags & MAP_ENTRY_FLAG_HALF_HUNK) && 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) && !(chd->map[chd->comparehunk].flags & MAP_ENTRY_FLAG_HALF_HUNK) &&
- 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) && !(chd->map[curhunk].flags & MAP_ENTRY_FLAG_HALF_HUNK) && crcmap_verify_hunk_match(chd, curhunk, rawdata))
- return curhunk;
-
- return NO_MATCH;
+ // verify that we are appending properly to a compressed file
+ verify_proper_compression_append(hunknum);
+
+ // write the final result
+ UINT64 offset = file_append(compressed, complength);
+
+ // update the map entry
+ UINT8 *rawmap = &m_rawmap[hunknum * 12];
+ rawmap[0] = (compression == -1) ? COMPRESSION_NONE : compression;
+ be_write(&rawmap[1], complength, 3);
+ be_write(&rawmap[4], offset, 6);
+ be_write(&rawmap[10], crc16, 2);
}
+//-------------------------------------------------
+// hunk_copy_from_self - mark a hunk as being a
+// copy of another hunk in the same CHD
+//-------------------------------------------------
-/***************************************************************************
- 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)
+void chd_file::hunk_copy_from_self(UINT32 hunknum, UINT32 otherhunk)
{
- /* 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;
+ // verify that we are appending properly to a compressed file
+ verify_proper_compression_append(hunknum);
+
+ // only permitted to reference prior hunks
+ if (otherhunk >= hunknum)
+ throw CHDERR_INVALID_PARAMETER;
- /* 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]);
+ // update the map entry
+ UINT8 *rawmap = &m_rawmap[hunknum * 12];
+ rawmap[0] = COMPRESSION_SELF;
+ be_write(&rawmap[1], 0, 3);
+ be_write(&rawmap[4], otherhunk, 6);
+ be_write(&rawmap[10], 0, 2);
+}
- /* 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;
+//-------------------------------------------------
+// hunk_copy_from_parent - mark a hunk as being a
+// copy of a hunk from a parent CHD
+//-------------------------------------------------
- /* no match, fetch the next link */
- metaentry->prev = metaentry->offset;
- metaentry->offset = metaentry->next;
- }
+void chd_file::hunk_copy_from_parent(UINT32 hunknum, UINT64 parentunit)
+{
+ // verify that we are appending properly to a compressed file
+ verify_proper_compression_append(hunknum);
- /* if we get here, we didn't find it */
- return CHDERR_METADATA_NOT_FOUND;
+ // update the map entry
+ UINT8 *rawmap = &m_rawmap[hunknum * 12];
+ rawmap[0] = COMPRESSION_PARENT;
+ be_write(&rawmap[1], 0, 3);
+ be_write(&rawmap[4], parentunit, 6);
+ be_write(&rawmap[10], 0, 2);
}
-/*-------------------------------------------------
- metadata_set_previous_next - set the 'next'
- offset of a piece of metadata
--------------------------------------------------*/
+//-------------------------------------------------
+// metadata_find - find a metadata entry
+//-------------------------------------------------
-static chd_error metadata_set_previous_next(chd_file *chd, UINT64 prevoffset, UINT64 nextoffset)
+bool chd_file::metadata_find(chd_metadata_tag metatag, INT32 metaindex, metadata_entry &metaentry, bool resume)
{
- 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)
+ // start at the beginning unless we're resuming a previous search
+ if (!resume)
{
- chd->header.metaoffset = nextoffset;
- err = header_write(chd->file, &chd->header);
- if (err != CHDERR_NONE)
- return err;
+ metaentry.offset = m_metaoffset;
+ metaentry.prev = 0;
}
-
- /* 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;
+ metaentry.prev = metaentry.offset;
+ metaentry.offset = metaentry.next;
}
- 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)
+ // loop until we run out of options
+ while (metaentry.offset != 0)
{
+ // read the raw header
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;
+ file_read(metaentry.offset, raw_meta_header, sizeof(raw_meta_header));
- /* 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);
+ // extract the data
+ metaentry.metatag = be_read(&raw_meta_header[0], 4);
+ metaentry.flags = raw_meta_header[4];
+ metaentry.length = be_read(&raw_meta_header[5], 3);
+ metaentry.next = be_read(&raw_meta_header[8], 8);
- /* 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;
- }
- }
+ // if we got a match, proceed
+ if (metatag == CHDMETATAG_WILDCARD || metaentry.metatag == metatag)
+ if (metaindex-- == 0)
+ return true;
- /* fill in the entry */
- put_bigendian_uint32(hasharray[hashindex].tag, metatag);
- sha1_digest(&sha1, SHA1_DIGEST_SIZE, hasharray[hashindex].sha1);
- hashindex++;
+ // no match, fetch the next link
+ metaentry.prev = metaentry.offset;
+ metaentry.offset = metaentry.next;
}
- /* 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;
+ // if we get here, we didn't find it
+ return false;
}
-/*-------------------------------------------------
- metadata_hash_compare - compare two hash
- entries
--------------------------------------------------*/
+//-------------------------------------------------
+// metadata_set_previous_next - set the 'next'
+// offset of a piece of metadata
+//-------------------------------------------------
-static int CLIB_DECL metadata_hash_compare(const void *elem1, const void *elem2)
+void chd_file::metadata_set_previous_next(UINT64 prevoffset, UINT64 nextoffset)
{
- 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;
+ UINT64 offset = 0;
- /* 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)
+ // if we were the first entry, make the next entry the first
+ if (prevoffset == 0)
{
- 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);
+ offset = m_metaoffset_offset;
+ m_metaoffset = nextoffset;
}
- /* 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;
+ // otherwise, update the link in the previous header
else
- free(data);
-
- return err;
+ offset = prevoffset + 8;
+
+ // create a big-endian version
+ UINT8 rawbuf[sizeof(UINT64)];
+ be_write(rawbuf, nextoffset, 8);
+
+ // write to the header and update our local copy
+ file_write(offset, rawbuf, sizeof(rawbuf));
}
-/*-------------------------------------------------
- zlib_codec_free - free data for the ZLIB
- codec
--------------------------------------------------*/
+//-------------------------------------------------
+// metadata_update_hash - compute the SHA1
+// hash of all metadata that requests it
+//-------------------------------------------------
-static void zlib_codec_free(chd_file *chd)
+void chd_file::metadata_update_hash()
{
- zlib_codec_data *data = (zlib_codec_data *)chd->codecdata;
-
- /* deinit the streams */
- if (data != NULL)
- {
- int i;
-
- inflateEnd(&data->inflater);
- deflateEnd(&data->deflater);
+ // only works for V4 and above, and only for compressed CHDs
+ if (m_version < 4 || !compressed())
+ return;
- /* free our fast memory */
- for (i = 0; i < MAX_ZLIB_ALLOCS; i++)
- if (data->allocptr[i])
- free(data->allocptr[i]);
- free(data);
- }
+ // compute the new overall hash
+ sha1_t fullsha1 = compute_overall_sha1(raw_sha1());
+
+ // create a big-endian version
+ UINT8 rawbuf[sizeof(sha1_t)];
+ be_write_sha1(&rawbuf[0], fullsha1);
+
+ // write to the header
+ file_write(m_sha1_offset, rawbuf, sizeof(rawbuf));
}
-/*-------------------------------------------------
- zlib_codec_compress - compress data using the
- ZLIB codec
--------------------------------------------------*/
+//-------------------------------------------------
+// metadata_hash_compare - compare two hash
+// entries
+//-------------------------------------------------
-static chd_error zlib_codec_compress(chd_file *chd, const void *src, UINT32 *length)
+int CLIB_DECL chd_file::metadata_hash_compare(const void *elem1, const void *elem2)
{
- 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;
+ return memcmp(elem1, elem2, sizeof(metadata_hash));
}
-/*-------------------------------------------------
- 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;
-}
+//**************************************************************************
+// CHD COMPRESSOR
+//**************************************************************************
-/*-------------------------------------------------
- zlib_fast_alloc - fast malloc for ZLIB, which
- allocates and frees memory frequently
--------------------------------------------------*/
+//-------------------------------------------------
+// chd_file_compressor - constructor
+//-------------------------------------------------
-static voidpf zlib_fast_alloc(voidpf opaque, uInt items, uInt size)
+chd_file_compressor::chd_file_compressor()
+ : m_walking_parent(false),
+ m_total_in(0),
+ m_total_out(0),
+ m_read_queue(NULL),
+ m_read_queue_offset(0),
+ m_read_done_offset(0),
+ m_read_error(false),
+ m_work_queue(NULL),
+ m_write_hunk(0)
{
- zlib_codec_data *data = (zlib_codec_data *)opaque;
- UINT32 *ptr;
- int i;
+ // zap arrays
+ memset(m_work_item, 0, sizeof(m_work_item));
+ memset(m_codecs, 0, sizeof(m_codecs));
- /* 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;
+ // allocate work queues
+ m_read_queue = osd_work_queue_alloc(WORK_QUEUE_FLAG_IO);
+ m_work_queue = osd_work_queue_alloc(WORK_QUEUE_FLAG_MULTI);
}
-/*-------------------------------------------------
- zlib_fast_free - fast free for ZLIB, which
- allocates and frees memory frequently
--------------------------------------------------*/
+//-------------------------------------------------
+// ~chd_file_compressor - destructor
+//-------------------------------------------------
-static void zlib_fast_free(voidpf opaque, voidpf address)
+chd_file_compressor::~chd_file_compressor()
{
- 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;
- }
+ // free the work queues
+ osd_work_queue_free(m_read_queue);
+ osd_work_queue_free(m_work_queue);
+
+ // delete allocated arrays
+ for (int codecnum = 0; codecnum < ARRAY_LENGTH(m_codecs); codecnum++)
+ delete m_codecs[codecnum];
}
-/*-------------------------------------------------
- flac_codec_compress - compress data using the
- FLAC codec
--------------------------------------------------*/
-
-
-const int INITIAL_BUFFER_SIZE = 0x20000;
-const int INITIAL_GROW_SIZE = 0x20000;
+//-------------------------------------------------
+// compress_begin - initiate compression
+//-------------------------------------------------
-struct flac_encoder_data
+void chd_file_compressor::compress_begin()
{
- FLAC__int32* pcm;
- FLAC__byte* tempbuffer;
- UINT8* flac_outputbuffer;
- size_t flac_outputbuffer_size;
- FLAC__uint64 flac_output_buffer_curpos;
- FLAC__uint64 flac_output_buffer_total;
-};
-
-static FLAC__StreamEncoderWriteStatus flac_encoder_write_callback(const FLAC__StreamEncoder *encoder, const FLAC__byte buffer[], size_t bytes, unsigned samples, unsigned current_frame, void *client_data)
-{
- if (((flac_encoder_data*)client_data)->flac_output_buffer_curpos + bytes >= ((flac_encoder_data*)client_data)->flac_outputbuffer_size)
+ // reset state
+ m_walking_parent = (m_parent != NULL);
+ m_total_in = 0;
+ m_total_out = 0;
+ m_compsha1.reset();
+
+ // reset our maps
+ m_parent_map.reset();
+ m_current_map.reset();
+
+ // reset read state
+ m_read_queue_offset = 0;
+ m_read_done_offset = 0;
+ m_read_error = false;
+
+ // reset work item state
+ m_work_buffer.resize(hunk_bytes() * (WORK_BUFFER_HUNKS + 1));
+ m_compressed_buffer.resize(hunk_bytes() * WORK_BUFFER_HUNKS);
+ for (int itemnum = 0; itemnum < WORK_BUFFER_HUNKS; itemnum++)
{
- ((flac_encoder_data*)client_data)->flac_outputbuffer = (UINT8*)realloc(((flac_encoder_data*)client_data)->flac_outputbuffer, ((flac_encoder_data*)client_data)->flac_outputbuffer_size+INITIAL_GROW_SIZE);
- ((flac_encoder_data*)client_data)->flac_outputbuffer_size = ((flac_encoder_data*)client_data)->flac_outputbuffer_size+INITIAL_GROW_SIZE;
+ work_item &item = m_work_item[itemnum];
+ item.m_compressor = this;
+ item.m_data = m_work_buffer + hunk_bytes() * itemnum;
+ item.m_compressed = m_compressed_buffer + hunk_bytes() * itemnum;
+ item.m_hash.resize(hunk_bytes() / unit_bytes());
}
-
- memcpy(((flac_encoder_data*)client_data)->flac_outputbuffer+((flac_encoder_data*)client_data)->flac_output_buffer_curpos, buffer, bytes);
- if (((flac_encoder_data*)client_data)->flac_output_buffer_curpos+bytes > ((flac_encoder_data*)client_data)->flac_output_buffer_total)
- ((flac_encoder_data*)client_data)->flac_output_buffer_total=((flac_encoder_data*)client_data)->flac_output_buffer_curpos+bytes;
-
- ((flac_encoder_data*)client_data)->flac_output_buffer_curpos+=bytes;
-
- return FLAC__STREAM_ENCODER_WRITE_STATUS_OK;
-}
-
-#define FLAC_HEADER_SIZE (86)
-
-static chd_error flac_codec_compress(chd_file *chd, const void *src, UINT32 *length, int swap)
-{
- int FLAC_ENCODER_READSIZE = (CD_MAX_SECTOR_DATA/4);
- int FLAC_ENCODER_FULLSIZE = ((CD_MAX_SECTOR_DATA+CD_MAX_SUBCODE_DATA)/4);
-
- flac_encoder_data flac_encoder_client;
- flac_encoder_data* flac_encoder_client_ptr = &flac_encoder_client;
- flac_encoder_client_ptr->flac_outputbuffer = (UINT8*)malloc(INITIAL_BUFFER_SIZE);
- flac_encoder_client_ptr->flac_outputbuffer_size = INITIAL_BUFFER_SIZE;
- flac_encoder_client_ptr->flac_output_buffer_curpos = 0;
- flac_encoder_client_ptr->flac_output_buffer_total = 0;
- flac_encoder_client_ptr->pcm=(FLAC__int32*)malloc(FLAC_ENCODER_READSIZE * 2 * 4);
- flac_encoder_client_ptr->tempbuffer=(FLAC__byte*)malloc(FLAC_ENCODER_READSIZE * 2 * 2);
-
-
-
- FLAC__StreamEncoder *encoder = 0;
- FLAC__bool ok = true;
-
- if((encoder = FLAC__stream_encoder_new()) == NULL)
+
+ // initialize codec instances
+ for (int instance = 0; instance < ARRAY_LENGTH(m_codecs); instance++)
{
- printf("ERROR: allocating encoder\n");
- return CHDERR_COMPRESSION_ERROR;
+ delete m_codecs[instance];
+ m_codecs[instance] = new chd_compressor_group(*this, m_compression);
}
+
+ // reset write state
+ m_write_hunk = 0;
+}
- ok &= FLAC__stream_encoder_set_verify(encoder, false); // we trust libFLAC ;-)
- ok &= FLAC__stream_encoder_set_compression_level(encoder, 8);
- ok &= FLAC__stream_encoder_set_channels(encoder, 2);
- ok &= FLAC__stream_encoder_set_bits_per_sample(encoder, 16);
- ok &= FLAC__stream_encoder_set_sample_rate(encoder, 44100);
- ok &= FLAC__stream_encoder_set_total_samples_estimate(encoder, 0);
- ok &= FLAC__stream_encoder_set_streamable_subset(encoder, false);
- ok &= FLAC__stream_encoder_set_blocksize(encoder, ((CD_MAX_SECTOR_DATA)*CD_FRAMES_PER_HUNK)/4 ); // /4 because this is in SAMPLES, not bytes
- if (!ok)
- {
- printf("error setting up stream encoder\n");
- return CHDERR_COMPRESSION_ERROR;
- }
+//-------------------------------------------------
+// compress_continue - continue compression
+//-------------------------------------------------
- if (FLAC__stream_encoder_init_stream(encoder, flac_encoder_write_callback, NULL, NULL, NULL, flac_encoder_client_ptr) != FLAC__STREAM_ENCODER_INIT_STATUS_OK)
+chd_error chd_file_compressor::compress_continue(double &progress, double &ratio)
+{
+ // if done reading, queue some more
+ while (m_read_queue_offset < m_logicalbytes && osd_work_queue_items(m_read_queue) < 2)
{
- printf("error initializing encoder\n");
- return CHDERR_COMPRESSION_ERROR;
- }
+ // if we got an error, return an error
+ if (m_read_error)
+ return CHDERR_READ_ERROR;
- size_t left = (size_t)chd->header.hunkbytes;
- UINT8* srcdata = (UINT8*)src;
+ // see if we have enough free work items to read the next half of a buffer
+ UINT32 startitem = m_read_queue_offset / hunk_bytes();
+ UINT32 enditem = startitem + WORK_BUFFER_HUNKS / 2;
+ UINT32 curitem;
+ for (curitem = startitem; curitem < enditem; curitem++)
+ if (m_work_item[curitem % WORK_BUFFER_HUNKS].m_status != WS_READY)
+ break;
- while(ok && left)
- {
- memcpy(flac_encoder_client_ptr->tempbuffer, srcdata, FLAC_ENCODER_READSIZE*4);
- srcdata += FLAC_ENCODER_FULLSIZE*4;
+ // if it's not all clear, defer
+ if (curitem != enditem)
+ break;
+
+ // if we're walking the parent, we want one more item to have cleared so we
+ // can read an extra hunk there
+ if (m_walking_parent && m_work_item[curitem % WORK_BUFFER_HUNKS].m_status != WS_READY)
+ break;
+
+ // queue the next read
+ for (curitem = startitem; curitem < enditem; curitem++)
+ m_work_item[curitem % WORK_BUFFER_HUNKS].m_status = WS_READING;
+ osd_work_item_queue(m_read_queue, async_read_static, this, WORK_ITEM_FLAG_AUTO_RELEASE);
+ m_read_queue_offset += WORK_BUFFER_HUNKS * hunk_bytes() / 2;
+ }
+
+ // flush out any finished items
+ while (m_work_item[m_write_hunk % WORK_BUFFER_HUNKS].m_status == WS_COMPLETE)
+ {
+ work_item &item = m_work_item[m_write_hunk % WORK_BUFFER_HUNKS];
+
+ // free any OSD work item
+ if (item.m_osd != NULL)
+ osd_work_item_release(item.m_osd);
+ item.m_osd = NULL;
+
+ // for parent walking, just add to the hashmap
+ if (m_walking_parent)
+ {
+ UINT32 uph = hunk_bytes() / unit_bytes();
+ UINT32 units = uph;
+ if (item.m_hunknum == hunk_count() - 1 || !compressed())
+ units = 1;
+ for (UINT32 unit = 0; unit < units; unit++)
+ if (m_parent_map.find(item.m_hash[unit].m_crc16, item.m_hash[unit].m_sha1) == hashmap::NOT_FOUND)
+ m_parent_map.add(item.m_hunknum * uph + unit, item.m_hash[unit].m_crc16, item.m_hash[unit].m_sha1);
+ }
+
+ // if we're uncompressed, use regular writes
+ else if (!compressed())
{
- size_t i;
- for(i = 0; i < FLAC_ENCODER_READSIZE*2; i++)
+ bool skip = true;
+
+ // see if it's all 0
+ for (UINT32 offs = 0; offs < m_hunkbytes && skip; offs++)
+ if (item.m_data[offs] != 0)
+ skip = false;
+
+ // see if it's in the parent map
+ if (!skip && m_parent != NULL && m_parent_map.find(item.m_hash[0].m_crc16, item.m_hash[0].m_sha1) != hashmap::NOT_FOUND)
+ skip = true;
+
+ // write the block
+ if (!skip)
{
- if (!swap) flac_encoder_client_ptr->pcm[i] = (FLAC__int32)(((FLAC__int16)(FLAC__int8)flac_encoder_client_ptr->tempbuffer[2*i] << 8) | (FLAC__int16)flac_encoder_client_ptr->tempbuffer[2*i+1]);
- else flac_encoder_client_ptr->pcm[i] = (FLAC__int32)(((FLAC__int16)(FLAC__int8)flac_encoder_client_ptr->tempbuffer[2*i+1] << 8) | (FLAC__int16)flac_encoder_client_ptr->tempbuffer[2*i]);
+ chd_error err = write_hunk(item.m_hunknum, item.m_data);
+ if (err != CHDERR_NONE)
+ return err;
+ m_total_out += m_hunkbytes;
+ }
+ }
+
+ // for compressing, process the result
+ else do
+ {
+ // first see if the hunk is in the parent or self maps
+ UINT64 selfhunk = m_current_map.find(item.m_hash[0].m_crc16, item.m_hash[0].m_sha1);
+ if (selfhunk != hashmap::NOT_FOUND)
+ {
+ hunk_copy_from_self(item.m_hunknum, selfhunk);
+ break;
+ }
+
+ // if not, see if it's in the parent map
+ if (m_parent != NULL)
+ {
+ UINT64 parentunit = m_parent_map.find(item.m_hash[0].m_crc16, item.m_hash[0].m_sha1);
+ if (parentunit != hashmap::NOT_FOUND)
+ {
+ hunk_copy_from_parent(item.m_hunknum, parentunit);
+ break;
+ }
+ }
+
+ // otherwise, append it compressed and add to the self map
+ hunk_write_compressed(item.m_hunknum, item.m_compression, item.m_compressed, item.m_complen, item.m_hash[0].m_crc16);
+ m_total_out += item.m_complen;
+ m_current_map.add(item.m_hunknum, item.m_hash[0].m_crc16, item.m_hash[0].m_sha1);
+ } while (0);
+
+ // reset the item and advance
+ item.m_status = WS_READY;
+ m_write_hunk++;
+
+ // if we hit the end, finalize
+ if (m_write_hunk == m_hunkcount)
+ {
+ // if this is just walking the parent, reset and get ready for compression
+ if (m_walking_parent)
+ {
+ m_walking_parent = false;
+ m_read_queue_offset = m_read_done_offset = 0;
+ m_write_hunk = 0;
+ for (int itemnum = 0; itemnum < WORK_BUFFER_HUNKS; itemnum++)
+ m_work_item[itemnum].m_status = WS_READY;
+ }
+
+ // wait for all reads to finish and if we're compressed, write the final SHA1 and map
+ else
+ {
+ osd_work_queue_wait(m_read_queue, 30 * osd_ticks_per_second());
+ if (!compressed())
+ return CHDERR_NONE;
+ set_raw_sha1(m_compsha1.finish());
+ return compress_v5_map();
}
-
- ok = FLAC__stream_encoder_process_interleaved(encoder, flac_encoder_client_ptr->pcm, FLAC_ENCODER_READSIZE);
}
-
-
- left -= (FLAC_ENCODER_FULLSIZE*4);
- }
-
- if (!ok)
- {
- printf("error encoding!\n");
- return CHDERR_COMPRESSION_ERROR;
- }
-
- ok &= FLAC__stream_encoder_finish(encoder);
-
- if (!ok)
- {
- printf("error finishing!\n");
- return CHDERR_COMPRESSION_ERROR;
- }
-
- int totalout = flac_encoder_client_ptr->flac_output_buffer_total-FLAC_HEADER_SIZE;
-
-
-
- FLAC__stream_encoder_delete(encoder);
-
- if (totalout >= chd->header.hunkbytes)
- {
- free(flac_encoder_client_ptr->flac_outputbuffer);
- return CHDERR_COMPRESSION_ERROR;
}
+
+ // update progress and ratio
+ if (m_walking_parent)
+ progress = double(m_read_done_offset) / double(logical_bytes());
+ else
+ progress = double(m_write_hunk) / double(m_hunkcount);
+ ratio = (m_total_in == 0) ? 1.0 : double(m_total_out) / double(m_total_in);
- *length = totalout;
- memcpy(chd->compressed, flac_encoder_client_ptr->flac_outputbuffer+FLAC_HEADER_SIZE, flac_encoder_client_ptr->flac_output_buffer_total-FLAC_HEADER_SIZE);
-
- free(flac_encoder_client_ptr->flac_outputbuffer);
- free(flac_encoder_client_ptr->pcm);
- free(flac_encoder_client_ptr->tempbuffer);
- return CHDERR_NONE;
-}
+ // if we're waiting for work, wait
+ while (m_work_item[m_write_hunk % WORK_BUFFER_HUNKS].m_status != WS_COMPLETE && m_work_item[m_write_hunk % WORK_BUFFER_HUNKS].m_osd != NULL)
+ osd_work_item_wait(m_work_item[m_write_hunk % WORK_BUFFER_HUNKS].m_osd, osd_ticks_per_second());
-static chd_error flac_codec_compress_normal(chd_file *chd, const void *src, UINT32 *length)
-{
- return flac_codec_compress(chd, src, length, 0);
+ return m_walking_parent ? CHDERR_WALKING_PARENT : CHDERR_COMPRESSING;
}
-// this data is always the same for our blocks, so don't store it in the file.
-static UINT8 flacHeader[FLAC_HEADER_SIZE] = {
- 0x66, 0x4C, 0x61, 0x43, 0x00, 0x00, 0x00, 0x22, 0x12, 0x60, 0x12, 0x60,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0xC4, 0x42, 0xF0, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x84, 0x00, 0x00, 0x28, 0x20, 0x00,
- 0x00, 0x00, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6E, 0x63, 0x65, 0x20,
- 0x6C, 0x69, 0x62, 0x46, 0x4C, 0x41, 0x43, 0x20, 0x31, 0x2E, 0x32, 0x2E,
- 0x31, 0x20, 0x32, 0x30, 0x30, 0x37, 0x30, 0x39, 0x31, 0x37, 0x00, 0x00,
- 0x00, 0x00,
-};
-/*-------------------------------------------------
- flac_codec_decompress - decomrpess data using
- the FLAC codec
--------------------------------------------------*/
-
-
-struct flac_decoder_data
-{
- int readoffset;
- size_t readbuffersize;
- UINT8* readbuffer;
- int writeoffset;
- INT16 tempbuffer[(CD_MAX_SECTOR_DATA * CD_FRAMES_PER_HUNK)/2];
- UINT64 amount_to_decode;
-};
+//-------------------------------------------------
+// async_walk_parent - handle asynchronous parent
+// walking operations
+//-------------------------------------------------
-FLAC__StreamDecoderWriteStatus flac_decoder_write_callback(const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 *const buffer[], void *client_data)
+void *chd_file_compressor::async_walk_parent_static(void *param, int threadid)
{
-
- int blocksize = frame->header.blocksize;
- int i = 0;
- while (blocksize && ((flac_decoder_data*)client_data)->amount_to_decode)
- {
- ((flac_decoder_data*)client_data)->tempbuffer[(((flac_decoder_data*)client_data)->writeoffset*2)+0] = buffer[0][i];
- ((flac_decoder_data*)client_data)->tempbuffer[(((flac_decoder_data*)client_data)->writeoffset*2)+1] = buffer[1][i];
-
- blocksize--;
- i++;
- ((flac_decoder_data*)client_data)->amount_to_decode-=4;
- ((flac_decoder_data*)client_data)->writeoffset++;
- }
-
- return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
+ work_item *item = reinterpret_cast<work_item *>(param);
+ item->m_compressor->async_walk_parent(*item);
+ return NULL;
}
-FLAC__StreamDecoderReadStatus flac_decoder_read_callback(const FLAC__StreamDecoder *decoder, FLAC__byte buffer[], size_t *bytes, void *client_data)
+void chd_file_compressor::async_walk_parent(work_item &item)
{
- size_t readsize = *bytes;
- size_t readbuffersize = ((flac_decoder_data*)client_data)->readbuffersize;
-
- if ((((flac_decoder_data*)client_data)->readoffset + readsize) > readbuffersize)
+ // compute CRC-16 and SHA-1 hashes for each unit, unless we're the last one or we're uncompressed
+ UINT32 units = hunk_bytes() / unit_bytes();
+ if (item.m_hunknum == m_hunkcount - 1 || !compressed())
+ units = 1;
+ for (UINT32 unit = 0; unit < units; unit++)
{
- readsize = ((flac_decoder_data*)client_data)->readbuffersize-((flac_decoder_data*)client_data)->readoffset;
+ item.m_hash[unit].m_crc16 = crc16_creator::simple(item.m_data + unit * unit_bytes(), hunk_bytes());
+ item.m_hash[unit].m_sha1 = sha1_creator::simple(item.m_data + unit * unit_bytes(), hunk_bytes());
}
-
- if (readsize==0) return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM;
-
- memcpy(buffer, ((flac_decoder_data*)client_data)->readbuffer+((flac_decoder_data*)client_data)->readoffset, readsize);
-
- ((flac_decoder_data*)client_data)->readoffset += readsize;
-
- return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE;
+ item.m_status = WS_COMPLETE;
}
+//-------------------------------------------------
+// async_compress_hunk - handle asynchronous
+// hunk compression
+//-------------------------------------------------
-void flac_decoder_metadata_callback(const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *metadata, void *client_data)
+void *chd_file_compressor::async_compress_hunk_static(void *param, int threadid)
{
-
+ work_item *item = reinterpret_cast<work_item *>(param);
+ item->m_compressor->async_compress_hunk(*item, threadid);
+ return NULL;
}
-void flac_decoder_error_callback(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status, void *client_data)
+void chd_file_compressor::async_compress_hunk(work_item &item, int threadid)
{
+ // use our thread's codec
+ assert(threadid < ARRAY_LENGTH(m_codecs));
+ item.m_codecs = m_codecs[threadid];
-}
+ // compute CRC-16 and SHA-1 hashes
+ item.m_hash[0].m_crc16 = crc16_creator::simple(item.m_data, hunk_bytes());
+ item.m_hash[0].m_sha1 = sha1_creator::simple(item.m_data, hunk_bytes());
-static chd_error flac_codec_decompress(chd_file *chd, UINT32 srclength, void *dest)
-{
- FLAC__StreamDecoder *decoder = FLAC__stream_decoder_new();
- flac_decoder_data flac_decoder_client;
- flac_decoder_data* flac_decoder_client_ptr = &flac_decoder_client;
+ // find the best compression scheme, unless we already have a self or parent match
+ // (note we may miss a self match from blocks not yet added, but this just results in extra work)
+ if (m_current_map.find(item.m_hash[0].m_crc16, item.m_hash[0].m_sha1) == hashmap::NOT_FOUND &&
+ m_parent_map.find(item.m_hash[0].m_crc16, item.m_hash[0].m_sha1) == hashmap::NOT_FOUND)
+ item.m_compression = item.m_codecs->find_best_compressor(item.m_data, item.m_compressed, item.m_complen);
- flac_decoder_client_ptr->readoffset = 0;
-
- flac_decoder_client_ptr->readbuffersize = srclength+FLAC_HEADER_SIZE;
- flac_decoder_client_ptr->readbuffer = (UINT8*)malloc(flac_decoder_client_ptr->readbuffersize);
- flac_decoder_client_ptr->amount_to_decode = (CD_MAX_SECTOR_DATA*CD_FRAMES_PER_HUNK);
- int frames_to_decode = flac_decoder_client_ptr->amount_to_decode / CD_MAX_SECTOR_DATA;
-
- flac_decoder_client_ptr->writeoffset = 0;
-
- memcpy(flac_decoder_client_ptr->readbuffer, flacHeader, FLAC_HEADER_SIZE);
- memcpy(flac_decoder_client_ptr->readbuffer+FLAC_HEADER_SIZE, chd->compressed, srclength);
+ // mark us complete
+ item.m_status = WS_COMPLETE;
+}
- if (!decoder)
- {
- printf("unable to create FLAC decoder\n");
- return CHDERR_READ_ERROR;
- }
- if(FLAC__stream_decoder_init_stream(
- decoder,
- flac_decoder_read_callback,
- NULL,
- NULL,
- NULL,
- NULL,
- flac_decoder_write_callback,
- flac_decoder_metadata_callback,
- flac_decoder_error_callback,
- flac_decoder_client_ptr ) != FLAC__STREAM_DECODER_INIT_STATUS_OK)
- {
- printf("unable to init FLAC decoder\n");
- return CHDERR_READ_ERROR;
- }
+//-------------------------------------------------
+// async_read - handle asynchronous source file
+// reading
+//-------------------------------------------------
- if (FLAC__stream_decoder_process_until_end_of_metadata(decoder) != FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM)
- {
- printf("Fail FLAC__stream_decoder_process_until_end_of_metadata\n");
- return CHDERR_READ_ERROR;
- }
+void *chd_file_compressor::async_read_static(void *param, int threadid)
+{
+ reinterpret_cast<chd_file_compressor *>(param)->async_read();
+ return NULL;
+}
- /* only ever a single frame */
- if (FLAC__stream_decoder_process_single(decoder) != FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM)
- {
- printf("Fail FLAC__stream_decoder_process_until_end_of_metadata\n");
- return CHDERR_READ_ERROR;
- }
+void chd_file_compressor::async_read()
+{
+ // if in the error or complete state, stop
+ if (m_read_error)
+ return;
+ // determine parameters for the read
+ UINT32 work_buffer_bytes = WORK_BUFFER_HUNKS * hunk_bytes();
+ UINT32 numbytes = work_buffer_bytes / 2;
+ if (m_read_done_offset + numbytes > logical_bytes())
+ numbytes = logical_bytes() - m_read_done_offset;
- int srcoffset = 0;
- UINT8* dest2 = (UINT8*)dest;
- for (int frame = 0; frame<frames_to_decode;frame++)
+ // catch any exceptions coming out of here
+ try
{
- int destoffset = frame * (CD_FRAME_SIZE);
+ // do the read
+ UINT8 *dest = m_work_buffer + (m_read_done_offset % work_buffer_bytes);
+ assert(dest == m_work_buffer || dest == m_work_buffer + work_buffer_bytes/2);
+ UINT64 end_offset = m_read_done_offset + numbytes;
-
- int index;
-
- for (index = 0; index < 2352; index += 2 )
+ // if walking the parent, read in hunks from the parent CHD
+ if (m_walking_parent)
{
- dest2[ destoffset + index +1 ] = flac_decoder_client_ptr->tempbuffer[srcoffset] & 0xff;
- dest2[ destoffset + index ] = flac_decoder_client_ptr->tempbuffer[srcoffset] >> 8;
-
- srcoffset++;
+ UINT8 *curdest = dest;
+ for (UINT64 curoffs = m_read_done_offset; curoffs < end_offset + 1; curoffs += hunk_bytes())
+ {
+ m_parent->read_hunk(curoffs / hunk_bytes(), curdest);
+ curdest += hunk_bytes();
+ }
+ }
+
+ // otherwise, call the virtual function
+ else
+ read_data(dest, m_read_done_offset, numbytes);
+
+ // spawn off work for each hunk
+ for (UINT64 curoffs = m_read_done_offset; curoffs < end_offset; curoffs += hunk_bytes())
+ {
+ UINT32 hunknum = curoffs / hunk_bytes();
+ work_item &item = m_work_item[hunknum % WORK_BUFFER_HUNKS];
+ assert(item.m_status == WS_READING);
+ item.m_status = WS_QUEUED;
+ item.m_hunknum = hunknum;
+ item.m_osd = osd_work_item_queue(m_work_queue, m_walking_parent ? async_walk_parent_static : async_compress_hunk_static, &item, 0);
}
- }
-
- if (FLAC__stream_decoder_finish (decoder) != true)
- {
- printf("unable to finish FLAC decoder\n");
- return CHDERR_READ_ERROR;
+ // continue the running SHA-1
+ if (!m_walking_parent)
+ {
+ if (compressed())
+ m_compsha1.append(dest, numbytes);
+ m_total_in += numbytes;
+ }
+
+ // advance the read pointer
+ m_read_done_offset += numbytes;
}
-
- FLAC__stream_decoder_delete(decoder);
- free(flac_decoder_client_ptr->readbuffer);
-
- return CHDERR_NONE;
-}
-
-
-
-
-/***************************************************************************
- 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')
+ catch (...)
{
- /* 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);
+ m_read_error = true;
}
- 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 = new(std::nothrow) av_codec_data;
- if (data == NULL)
- return CHDERR_OUT_OF_MEMORY;
-
- /* clear the buffers */
- 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;
-}
+//**************************************************************************
+// CHD COMPRESSOR HASHMAP
+//**************************************************************************
+//-------------------------------------------------
+// hashmap - constructor
+//-------------------------------------------------
-/*-------------------------------------------------
- av_codec_free - free data for the A/V
- codec
--------------------------------------------------*/
-
-static void av_codec_free(chd_file *chd)
+chd_file_compressor::hashmap::hashmap()
+ : m_block_list(new entry_block(NULL))
{
- av_codec_data *data = (av_codec_data *)chd->codecdata;
-
- /* deinit avcomp */
- if (data != NULL)
- {
- if (data->compstate != NULL)
- avcomp_free(data->compstate);
- delete data;
- }
+ // initialize the map to empty
+ memset(m_map, 0, sizeof(m_map));
}
-/*-------------------------------------------------
- av_codec_compress - compress data using the
- A/V codec
--------------------------------------------------*/
+//-------------------------------------------------
+// ~hashmap - destructor
+//-------------------------------------------------
-static chd_error av_codec_compress(chd_file *chd, const void *src, UINT32 *length)
+chd_file_compressor::hashmap::~hashmap()
{
- 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;
+ reset();
}
-/*-------------------------------------------------
- av_codec_decompress - decomrpess data using
- the A/V codec
--------------------------------------------------*/
+//-------------------------------------------------
+// reset - reset the state of the map
+//-------------------------------------------------
-static chd_error av_codec_decompress(chd_file *chd, UINT32 srclength, void *dest)
+void chd_file_compressor::hashmap::reset()
{
- 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)
+ // delete all the blocks
+ while (m_block_list->m_next != NULL)
{
- chd_error chderr = av_codec_postinit(chd);
- if (chderr != CHDERR_NONE)
- return chderr;
+ entry_block *block = m_block_list;
+ m_block_list = block->m_next;
+ delete block;
}
-
- /* 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;
+ m_block_list->m_nextalloc = 0;
+
+ // reset the hash
+ memset(m_map, 0, sizeof(m_map));
}
-/*-------------------------------------------------
- av_codec_config - codec-specific configuration
- for the A/V codec
--------------------------------------------------*/
+//-------------------------------------------------
+// find - find an item in the CRC map
+//-------------------------------------------------
-static chd_error av_codec_config(chd_file *chd, int param, void *config)
+UINT64 chd_file_compressor::hashmap::find(crc16_t crc16, sha1_t sha1)
{
- 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)
- {
- av_codec_compress_config *configsrc = reinterpret_cast<av_codec_compress_config *>(config);
- data->compress.video.wrap(configsrc->video, configsrc->video.cliprect());
- data->compress.channels = configsrc->channels;
- data->compress.samples = configsrc->samples;
- memcpy(data->compress.audio, configsrc->audio, sizeof(data->compress.audio));
- data->compress.metalength = configsrc->metalength;
- data->compress.metadata = configsrc->metadata;
- 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)
- {
- av_codec_decompress_config *configsrc = reinterpret_cast<av_codec_decompress_config *>(config);
- data->decompress.video.wrap(configsrc->video, configsrc->video.cliprect());
- data->decompress.maxsamples = configsrc->maxsamples;
- data->decompress.actsamples = configsrc->actsamples;
- memcpy(data->decompress.audio, configsrc->audio, sizeof(data->decompress.audio));
- data->decompress.maxmetalength = configsrc->maxmetalength;
- data->decompress.actmetalength = configsrc->actmetalength;
- data->decompress.metadata = configsrc->metadata;
- if (data->compstate != NULL)
- avcomp_config_decompress(data->compstate, &data->decompress);
- return CHDERR_NONE;
- }
-
- /* anything else is invalid */
- return CHDERR_INVALID_PARAMETER;
+ // look up the entry in the map
+ for (entry_t *entry = m_map[crc16]; entry != NULL; entry = entry->m_next)
+ if (entry->m_sha1 == sha1)
+ return entry->m_itemnum;
+ return NOT_FOUND;
}
-/*-------------------------------------------------
- av_codec_postinit - actual initialization of
- avcomp happens here, on the first attempt
- to compress or decompress data
--------------------------------------------------*/
+//-------------------------------------------------
+// add - add an item to the CRC map
+//-------------------------------------------------
-static chd_error av_codec_postinit(chd_file *chd)
+void chd_file_compressor::hashmap::add(UINT64 itemnum, crc16_t crc16, sha1_t sha1)
{
- 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;
+ // add to the appropriate map
+ if (m_block_list->m_nextalloc == ARRAY_LENGTH(m_block_list->m_array))
+ m_block_list = new entry_block(m_block_list);
+ entry_t *entry = &m_block_list->m_array[m_block_list->m_nextalloc++];
+ entry->m_itemnum = itemnum;
+ entry->m_sha1 = sha1;
+ entry->m_next = m_map[crc16];
+ m_map[crc16] = entry;
}